diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md new file mode 100644 index 0000000000..374cf9312f --- /dev/null +++ b/PRIVACY_POLICY.md @@ -0,0 +1,30 @@ +# FreeCAD Privacy Policy + +The FreeCAD application does not collect, transmit, share or use any Personal Data. + +FreeCAD is community-developed Free Software. The community does not condone the unauthorized usage of private data, so our software does not gather or send personal data. + +The FreeCAD website is mostly static, it does not contain any trackers, neither ours nor third-party. The website uses cookies to remember logged in status, timezone and other +data related to navigating the site. + +The website does not contain advertisements. + +The software does not contain advertisements or trackers either. + +## Caveats + +FreeCAD is able to load or save files to/from remote servers (for some protocols and platforms). If you choose to load or save a remote file, your IP or other private data might be shared as part of the normal connection flow for the given protocol. This is out of our control and it is up to you to decide whether you trust a remote host. + +The FreeCAD eco system includes user developed workbenches. These workbenches can be installed/updated using the Add-on Manager. The Add-on Manager retrieves workbenches from remote servers across the internet. Add-on workbenches are not checked for malicious content. It is your responsibility to decide whether you trust an add-on workbench. + +FreeCAD is meant to manipulate CAD files which may contain metadata. It is your responsibility to verify the metadata contained in your files before you share them with others. These files may contain local directory paths which could reveal user names if the user name forms part of the path - as in “C:\MrsCAD\Documents\myFreeCADFile.FCstd”. + +While running and for subsequent runs, FreeCAD uses local persistent storage for logs, configuration files, cache, thumbnails, recently accessed files and other information which may contain private data. This stays on local storage. + +When reading the online version of the User Manual within FreeCAD, manual contents is requested through HTTPS connections. + +FreeCAD is Free Software and therefore may be packaged by other people, who may include additional software or modify the source code. We do not vouch for these third-party packages and cannot tell you what they contain and what they do regarding your privacy. The official packages are explicitly listed in our download page. + + + - [based on the GIMP privacy policy](https://www.gimp.org/about/privacy.html) + diff --git a/cMake/FreeCAD_Helpers/CompilerChecksAndSetups.cmake b/cMake/FreeCAD_Helpers/CompilerChecksAndSetups.cmake index 445a4865fc..4def26bf57 100644 --- a/cMake/FreeCAD_Helpers/CompilerChecksAndSetups.cmake +++ b/cMake/FreeCAD_Helpers/CompilerChecksAndSetups.cmake @@ -73,7 +73,11 @@ macro(CompilerChecksAndSetups) endif() else(BUILD_DYNAMIC_LINK_PYTHON) if(CMAKE_COMPILER_IS_CLANGXX) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--undefined,dynamic_lookup") + if(APPLE) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,dynamic_lookup") + elseif(UNIX) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--undefined,dynamic_lookup") + endif() endif() endif(BUILD_DYNAMIC_LINK_PYTHON) endif(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANGXX) diff --git a/src/App/ComplexGeoData.cpp b/src/App/ComplexGeoData.cpp index cf99b2a22c..b2653b58e3 100644 --- a/src/App/ComplexGeoData.cpp +++ b/src/App/ComplexGeoData.cpp @@ -637,6 +637,11 @@ unsigned int ComplexGeoData::getMemSize() const return 0; } +std::vector ComplexGeoData::getHigherElements(const char *, bool) const +{ + return {}; +} + void ComplexGeoData::setMappedChildElements(const std::vector & children) { // DO NOT reset element map if there is one. Because we allow mixing child diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index 5b0ec19c83..504d3dbb93 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -321,6 +321,9 @@ public: /// Get the current element map size size_t getElementMapSize(bool flush=true) const; + /// Return the higher level element names of the given element + virtual std::vector getHigherElements(const char *name, bool silent=false) const; + /// Return the current element map version virtual std::string getElementMapVersion() const; diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index afd027ef45..4cb34bedd6 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -26,6 +26,7 @@ #include #include "ComplexGeoData.h" +#include "Document.h" #include "GeoFeature.h" #include "GeoFeatureGroupExtension.h" #include "ElementNamingUtils.h" @@ -146,12 +147,22 @@ DocumentObject *GeoFeature::resolveElement(DocumentObject *obj, const char *subn subname = ""; const char *element = Data::findElementName(subname); if(_element) *_element = element; +#ifdef FC_USE_TNP_FIX + elementName.first.clear(); + elementName.second.clear(); + auto sobj = obj->getSubObject(std::string(subname, element).c_str()); + if(!sobj) + return nullptr; + auto linked = sobj->getLinkedObject(true); + auto geo = Base::freecad_dynamic_cast(linked); +#else auto sobj = obj->getSubObject(subname); if(!sobj) return nullptr; obj = sobj->getLinkedObject(true); auto geo = dynamic_cast(obj); - if(geoFeature) +#endif + if(geoFeature) *geoFeature = geo; if(!obj || (filter && obj!=filter)) return nullptr; @@ -189,3 +200,45 @@ void GeoFeature::setMaterialAppearance(const App::Material& material) { Q_UNUSED(material) } + +#ifdef FC_USE_TNP_FIX +bool GeoFeature::hasMissingElement(const char *subname) { + return Data::hasMissingElement(subname); + if(!subname) + return false; + auto dot = strrchr(subname,'.'); + if(!dot) + return subname[0]=='?'; + return dot[1]=='?'; +} + +void GeoFeature::updateElementReference() { + auto prop = getPropertyOfGeometry(); + if(!prop) return; + auto geo = prop->getComplexData(); + if(!geo) return; + bool reset = false; + PropertyLinkBase::updateElementReferences(this,reset); +} + +void GeoFeature::onChanged(const Property *prop) { + if(prop==getPropertyOfGeometry()) { + if(getDocument() && !getDocument()->testStatus(Document::Restoring) + && !getDocument()->isPerformingTransaction()) + { + updateElementReference(); + } + } + DocumentObject::onChanged(prop); +} + + +std::vector +GeoFeature::getHigherElements(const char *element, bool silent) const +{ + auto prop = getPropertyOfGeometry(); + if (!prop) + return {}; + return prop->getComplexData()->getHigherElements(element, silent); +} +#endif diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index fb3d244b4e..3dcdffd6eb 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -140,7 +140,21 @@ public: * appearance from an App::Material object. */ virtual void setMaterialAppearance(const App::Material& material); +#ifdef FC_USE_TNP_FIX + static bool hasMissingElement(const char *subname); + /// Return the object that owns the shape that contains the give element name + virtual DocumentObject *getElementOwner(const Data::MappedName & /*name*/) const + {return nullptr;} + + /// Return the higher level element names of the given element + virtual std::vector getHigherElements(const char *name, bool silent=false) const; + +protected: + void onChanged(const Property* prop) override; +// void onDocumentRestored() override; + void updateElementReference(); +#endif protected: std::pair _getElementName(const char* name, const Data::MappedElement& mapped) const; diff --git a/src/App/MeasureManagerPy.xml b/src/App/MeasureManagerPy.xml index e2cd74efec..51b082c6ce 100644 --- a/src/App/MeasureManagerPy.xml +++ b/src/App/MeasureManagerPy.xml @@ -32,7 +32,7 @@ measureType : Measure.MeasureBasePython The actual measure type. - + getMeasureTypes() -> List[(id, label, pythonMeasureType)] diff --git a/src/App/MeasureManagerPyImp.cpp b/src/App/MeasureManagerPyImp.cpp index 46103b7f51..be7854b98e 100644 --- a/src/App/MeasureManagerPyImp.cpp +++ b/src/App/MeasureManagerPyImp.cpp @@ -63,7 +63,7 @@ PyObject* MeasureManagerPy::addMeasureType(PyObject *args) } -PyObject* MeasureManagerPy::getMeasureTypes(PyObject *args) +PyObject* MeasureManagerPy::getMeasureTypes() { Py::List types; for (auto & it : MeasureManager::getMeasureTypes()) { @@ -73,7 +73,7 @@ PyObject* MeasureManagerPy::getMeasureTypes(PyObject *args) type.setItem(2, Py::Object(it->pythonClass)); types.append(type); - } + } return Py::new_reference_to(types); -} \ No newline at end of file +} diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index 1f42f10985..c0c02d3e45 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -38,6 +38,8 @@ #include "DocumentObject.h" #include "DocumentObjectPy.h" #include "ObjectIdentifier.h" +#include "ElementNamingUtils.h" +#include "GeoFeature.h" FC_LOG_LEVEL_INIT("PropertyLinks",true,true) @@ -55,6 +57,9 @@ namespace sp = std::placeholders; TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase , App::Property) static std::unordered_map > _LabelMap; + +static std::unordered_map > _ElementRefMap; + PropertyLinkBase::PropertyLinkBase() = default; PropertyLinkBase::~PropertyLinkBase() { @@ -97,6 +102,18 @@ bool PropertyLinkBase::isSame(const Property &other) const } void PropertyLinkBase::unregisterElementReference() { +#ifdef FC_USE_TNP_FIX + for (auto obj : _ElementRefs) { + auto it = _ElementRefMap.find(obj); + if (it != _ElementRefMap.end()) { + it->second.erase(this); + if (it->second.empty()) { + _ElementRefMap.erase(it); + } + } + } + _ElementRefs.clear(); +#endif } void PropertyLinkBase::unregisterLabelReferences() @@ -202,15 +219,71 @@ static std::string propertyName(const Property *prop) { } void PropertyLinkBase::updateElementReferences(DocumentObject *feature, bool reverse) { +#ifdef FC_USE_TNP_FIX + if (!feature || !feature->getNameInDocument()) { + return; + } + auto it = _ElementRefMap.find(feature); + if (it == _ElementRefMap.end()) { + return; + } + std::vector props; + props.reserve(it->second.size()); + props.insert(props.end(), it->second.begin(), it->second.end()); + for (auto prop : props) { + if (prop->getContainer()) { + try { + prop->updateElementReference(feature, reverse, true); + } + catch (Base::Exception& e) { + e.ReportException(); + FC_ERR("Failed to update element reference of " << propertyName(prop)); + } + catch (std::exception& e) { + FC_ERR("Failed to update element reference of " << propertyName(prop) << ": " + << e.what()); + } + } + } +#else (void)feature; (void)reverse; +#endif } void PropertyLinkBase::_registerElementReference(App::DocumentObject *obj, std::string &sub, ShadowSub &shadow) { +#ifdef FC_USE_TNP_FIX + if (!obj || !obj->getNameInDocument() || sub.empty()) { + return; + } + if (shadow.first.empty()) { + _updateElementReference(0, obj, sub, shadow, false); + return; + } + GeoFeature* geo = nullptr; + const char* element = nullptr; + std::pair elementName; + GeoFeature::resolveElement(obj, + sub.c_str(), + elementName, + true, + GeoFeature::ElementNameType::Export, + 0, + &element, + &geo); + if (!geo || !element || !element[0]) { + return; + } + + if (_ElementRefs.insert(geo).second) { + _ElementRefMap[geo].insert(this); + } +#else (void)obj; (void)sub; (void)shadow; +#endif } class StringGuard { @@ -275,12 +348,157 @@ bool PropertyLinkBase::_updateElementReference(DocumentObject *feature, App::DocumentObject *obj, std::string &sub, ShadowSub &shadow, bool reverse, bool notify) { +#ifdef FC_USE_TNP_FIX + if (!obj || !obj->getNameInDocument()) { + return false; + } + ShadowSub elementName; + const char* subname; + if (shadow.first.size()) { + subname = shadow.first.c_str(); + } + else if (shadow.second.size()) { + subname = shadow.second.c_str(); + } + else { + subname = sub.c_str(); + } + GeoFeature* geo = nullptr; + const char* element = nullptr; + auto ret = GeoFeature::resolveElement(obj, + subname, + elementName, + true, + GeoFeature::ElementNameType::Export, + feature, + &element, + &geo); + if (!ret || !geo || !element || !element[0]) { + if (elementName.second.size()) { + shadow.second.swap(elementName.second); + } + return false; + } + + if (_ElementRefs.insert(geo).second) { + _ElementRefMap[geo].insert(this); + } + + if (!reverse) { + if (elementName.first.empty()) { + shadow.second.swap(elementName.second); + return false; + } + if (shadow == elementName) { + return false; + } + } + + bool missing = Data::hasMissingElement(elementName.second.c_str()); + if (feature == geo && (missing || reverse)) { + // If the referenced element is missing, or we are generating element + // map for the first time, or we are re-generating the element map due + // to version change, i.e. 'reverse', try search by geometry first + const char* oldElement = Data::findElementName(shadow.second.c_str()); + if (!Data::hasMissingElement(oldElement)) { + // const auto& names = geo->searchElementCache(oldElement); + std::vector names; // searchElementCache isn't implemented. + if (names.size()) { + missing = false; + std::string newsub(subname, strlen(subname) - strlen(element)); + newsub += names.front(); + GeoFeature::resolveElement(obj, + newsub.c_str(), + elementName, + true, + GeoFeature::ElementNameType::Export, + feature); + const auto& oldName = shadow.first.size() ? shadow.first : shadow.second; + const auto& newName = + elementName.first.size() ? elementName.first : elementName.second; + if (oldName != newName) { + FC_WARN(propertyName(this) + << " auto change element reference " << ret->getFullName() << " " + << oldName << " -> " << newName); + } + } + } + } + + if (notify) { + aboutToSetValue(); + } + + auto updateSub = [&](const std::string& newSub) { + if (sub != newSub) { + // signalUpdateElementReference(sub, newSub); + sub = newSub; + } + }; + + if (missing) { + FC_WARN(propertyName(this) + << " missing element reference " << ret->getFullName() << " " + << (elementName.first.size() ? elementName.first : elementName.second)); + shadow.second.swap(elementName.second); + } + else { + FC_TRACE(propertyName(this) << " element reference shadow update " << ret->getFullName() + << " " << shadow.first << " -> " << elementName.first); + shadow.swap(elementName); + if (shadow.first.size() && Data::hasMappedElementName(sub.c_str())) { + updateSub(shadow.first); + } + } + + if (reverse) { + if (shadow.first.size() && Data::hasMappedElementName(sub.c_str())) { + updateSub(shadow.first); + } + else { + updateSub(shadow.second); + } + return true; + } + if (missing) { + if (sub != shadow.first) { + updateSub(shadow.second); + } + return true; + } + auto pos2 = shadow.first.rfind('.'); + if (pos2 == std::string::npos) { + return true; + } + ++pos2; + auto pos = sub.rfind('.'); + if (pos == std::string::npos) { + pos = 0; + } + else { + ++pos; + } + if (pos == pos2) { + if (sub.compare(pos, sub.size() - pos, &shadow.first[pos2]) != 0) { + FC_LOG("element reference update " << sub << " -> " << shadow.first); + std::string newSub(sub); + newSub.replace(pos, sub.size() - pos, &shadow.first[pos2]); + updateSub(newSub); + } + } + else if (sub != shadow.second) { + FC_LOG("element reference update " << sub << " -> " << shadow.second); + updateSub(shadow.second); + } + return true; +#else (void)feature; (void)obj; (void)reverse; (void)notify; shadow.second = sub; return false; +#endif } std::pair @@ -290,41 +508,62 @@ PropertyLinkBase::tryReplaceLink(const PropertyContainer *owner, DocumentObject std::pair res; res.first = 0; - if(oldObj == obj) { - if(owner == parent) { + if (oldObj == obj) { + if (owner == parent) { res.first = newObj; - if(subname) res.second = subname; + if (subname) { + res.second = subname; + } return res; } return res; +#ifdef FC_USE_TNP_FIX } - if(!subname || !subname[0]) + else if (newObj == obj) { + // This means the new object is already sub-object of this parent + // (consider a case of swapping the tool and base object of the Cut + // feature). We'll swap the old and new object. + return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); +#endif + } + if (!subname || !subname[0]) { return res; + } - App::DocumentObject *prev = obj; + App::DocumentObject* prev = obj; std::size_t prevPos = 0; std::string sub = subname; - for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos)) { + for (auto pos = sub.find('.'); pos != std::string::npos; pos = sub.find('.', pos)) { ++pos; char c = sub[pos]; sub[pos] = 0; auto sobj = obj->getSubObject(sub.c_str()); sub[pos] = c; - if(!sobj) + if (!sobj) { break; - if(sobj == oldObj) { - if(prev == parent) { - if(sub[prevPos] == '$') - sub.replace(prevPos+1,pos-1-prevPos,newObj->Label.getValue()); - else - sub.replace(prevPos,pos-1-prevPos,newObj->getNameInDocument()); + } + if (sobj == oldObj) { + if (prev == parent) { + if (sub[prevPos] == '$') { + sub.replace(prevPos + 1, pos - 1 - prevPos, newObj->Label.getValue()); + } + else { + sub.replace(prevPos, pos - 1 - prevPos, newObj->getNameInDocument()); + } res.first = obj; res.second = std::move(sub); return res; } break; - }else if(prev == parent) +#ifdef FC_USE_TNP_FIX + } + else if (sobj == newObj) { + return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); +#endif + } + else if (prev == parent) { break; + } prev = sobj; prevPos = pos; } @@ -381,7 +620,6 @@ TYPESYSTEM_SOURCE(App::PropertyLinkHidden , App::PropertyLink) PropertyLink::PropertyLink() = default; - PropertyLink::~PropertyLink() { resetLink(); @@ -910,18 +1148,19 @@ void PropertyLinkSub::setValue(App::DocumentObject * lValue, std::vector &&subs, std::vector &&shadows) { auto parent = Base::freecad_dynamic_cast(getContainer()); - if(lValue) { - if(!lValue->isAttachedToDocument()) + if (lValue) { + if (!lValue->isAttachedToDocument()) throw Base::ValueError("PropertyLinkSub: invalid document object"); - if(!testFlag(LinkAllowExternal) && parent && parent->getDocument()!=lValue->getDocument()) + if (!testFlag(LinkAllowExternal) && parent + && parent->getDocument() != lValue->getDocument()) throw Base::ValueError("PropertyLinkSub does not support external object"); } aboutToSetValue(); #ifndef USE_OLD_DAG - if(parent) { + if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers - if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope!=LinkScope::Hidden) { + if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { if (_pcLinkSub) _pcLinkSub->_removeBackLink(parent); if (lValue) @@ -929,11 +1168,12 @@ void PropertyLinkSub::setValue(App::DocumentObject * lValue, } } #endif - _pcLinkSub=lValue; - _cSubList=std::move(subs); - if(shadows.size()==_cSubList.size()) + _pcLinkSub = lValue; + _cSubList = std::move(subs); + if (shadows.size() == _cSubList.size()) { _ShadowSubList = std::move(shadows); - else + onContainerRestored(); // re-register element references + } else updateElementReference(nullptr); checkLabelReferences(_cSubList); hasSetValue(); @@ -950,13 +1190,26 @@ const std::vector& PropertyLinkSub::getSubValues() const } static inline const std::string &getSubNameWithStyle(const std::string &subName, - const PropertyLinkBase::ShadowSub &shadow, bool newStyle) + const PropertyLinkBase::ShadowSub &shadow, bool newStyle, std::string &tmp) { if(!newStyle) { if(!shadow.second.empty()) return shadow.second; - }else if(!shadow.first.empty()) + }else if(!shadow.first.empty()) { +#ifdef FC_USE_TNP_FIX + if (Data::hasMissingElement(shadow.second.c_str())) { + auto pos = shadow.first.rfind('.'); + if (pos != std::string::npos) { + tmp = shadow.first.substr(0, pos+1); + tmp += shadow.second; + return tmp; + } + } +#else + (void) tmp; +#endif return shadow.first; + } return subName; } @@ -964,13 +1217,27 @@ std::vector PropertyLinkSub::getSubValues(bool newStyle) const { assert(_cSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_cSubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } std::vector PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const { +#ifdef FC_USE_TNP_FIX + assert(_cSubList.size() == _ShadowSubList.size()); + std::vector ret; + std::string tmp; + for(size_t i=0;i<_ShadowSubList.size();++i) { + const auto &sub = getSubNameWithStyle(_cSubList[i],_ShadowSubList[i],newStyle,tmp); + auto element = Data::findElementName(sub.c_str()); + if(element && boost::starts_with(element,starter)) + ret.emplace_back(element); + } + return ret; + +#else (void)newStyle; std::vector temp; @@ -980,6 +1247,7 @@ std::vector PropertyLinkSub::getSubValuesStartsWith(const char* sta } } return temp; +#endif } App::DocumentObject * PropertyLinkSub::getValue(Base::Type t) const @@ -993,8 +1261,14 @@ PyObject *PropertyLinkSub::getPyObject() Py::List list(static_cast(_cSubList.size())); if (_pcLinkSub) { tup[0] = Py::asObject(_pcLinkSub->getPyObject()); +#ifdef FC_USE_TNP_FIX + int i = 0; + for (auto &sub : getSubValues(true)) + list[i++] = Py::String(sub); +#else for(unsigned int i = 0;i<_cSubList.size(); i++) list[i] = Py::String(_cSubList[i]); +#endif tup[1] = list; return Py::new_reference_to(tup); } @@ -1460,6 +1734,9 @@ Property *PropertyLinkSub::Copy() const PropertyLinkSub *p= new PropertyLinkSub(); p->_pcLinkSub = _pcLinkSub; p->_cSubList = _cSubList; +#ifdef FC_USE_TNP_FIX + p->_ShadowSubList = _ShadowSubList; +#endif return p; } @@ -1468,7 +1745,12 @@ void PropertyLinkSub::Paste(const Property &from) if(!from.isDerivedFrom(PropertyLinkSub::getClassTypeId())) throw Base::TypeError("Incompatible property to paste to"); auto &link = static_cast(from); +#ifdef FC_USE_TNP_FIX + setValue(link._pcLinkSub, link._cSubList, + std::vector(link._ShadowSubList)); +#else setValue(link._pcLinkSub, link._cSubList); +#endif } void PropertyLinkSub::getLinks(std::vector &objs, @@ -1727,9 +2009,10 @@ void PropertyLinkSubList::setValues(std::vector&& lValue, aboutToSetValue(); _lValueList = std::move(lValue); _lSubList = std::move(lSubNames); - if(ShadowSubList.size()==_lSubList.size()) + if(ShadowSubList.size()==_lSubList.size()) { _ShadowSubList = std::move(ShadowSubList); - else + onContainerRestored(); // re-register element references + } else updateElementReference(nullptr); checkLabelReferences(_lSubList); hasSetValue(); @@ -1914,14 +2197,26 @@ void PropertyLinkSubList::setSubListValues(const std::vector links; std::vector subs; - +#ifdef FC_USE_TNP_FIX + for (std::vector::const_iterator it = values.begin(); it != values.end(); ++it) { + if (it->second.empty()) { + links.push_back(it->first); + subs.emplace_back(); + continue; + } + for (std::vector::const_iterator jt = it->second.begin(); jt != it->second.end(); ++jt) { + links.push_back(it->first); + subs.push_back(*jt); + } + } +#else for (const auto & value : values) { for (const auto& jt : value.second) { links.push_back(value.first); subs.push_back(jt); } } - +#endif setValues(links, subs); } @@ -2386,6 +2681,9 @@ Property *PropertyLinkSubList::Copy() const PropertyLinkSubList *p = new PropertyLinkSubList(); p->_lValueList = _lValueList; p->_lSubList = _lSubList; +#ifdef FC_USE_TNP_FIX + p->_ShadowSubList = _ShadowSubList; +#endif return p; } @@ -2394,7 +2692,12 @@ void PropertyLinkSubList::Paste(const Property &from) if(!from.isDerivedFrom(PropertyLinkSubList::getClassTypeId())) throw Base::TypeError("Incompatible property to paste to"); auto &link = static_cast(from); +#ifdef FC_USE_TNP_FIX + setValues(link._lValueList, link._lSubList, + std::vector(link._ShadowSubList)); +#else setValues(link._lValueList, link._lSubList); +#endif } unsigned int PropertyLinkSubList::getMemSize () const @@ -2409,8 +2712,9 @@ std::vector PropertyLinkSubList::getSubValues(bool newStyle) const assert(_lSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_ShadowSubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_lSubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } @@ -2965,9 +3269,12 @@ void PropertyXLink::setSubValues(std::vector &&subs, { _SubList = std::move(subs); _ShadowSubList.clear(); - if(shadows.size() == _SubList.size()) + if(shadows.size() == _SubList.size()) { _ShadowSubList = std::move(shadows); - else +#ifdef FC_USE_TNP_FIX + onContainerRestored(); // re-register element references +#endif + } else updateElementReference(nullptr); checkLabelReferences(_SubList); } @@ -3468,8 +3775,12 @@ void PropertyXLink::copyTo(PropertyXLink &other, } if(subs) other._SubList = std::move(*subs); - else + else { other._SubList = _SubList; +#ifdef FC_USE_TNP_FIX + other._ShadowSubList = _ShadowSubList; +#endif + } other._Flags = _Flags; } @@ -3497,10 +3808,19 @@ void PropertyXLink::Paste(const Property &from) FC_WARN("Object '" << other.docName << '#' << other.objectName << "' not found"); return; } +#ifdef FC_USE_TNP_FIX + setValue(obj,std::vector(other._SubList), + std::vector(other._ShadowSubList)); + } else + setValue(std::string(other.filePath),std::string(other.objectName), + std::vector(other._SubList), + std::vector(other._ShadowSubList)); +#else setValue(obj,std::vector(other._SubList)); } else setValue(std::string(other.filePath),std::string(other.objectName), std::vector(other._SubList)); +#endif setFlag(LinkAllowPartial,other.testFlag(LinkAllowPartial)); } @@ -3652,7 +3972,7 @@ void PropertyXLink::setPyObject(PyObject *value) { const char *PropertyXLink::getSubName(bool newStyle) const { if(_SubList.empty() || _ShadowSubList.empty()) return ""; - return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle).c_str(); + return getSubNameWithStyle(_SubList[0],_ShadowSubList[0],newStyle,tmpShadow).c_str(); } void PropertyXLink::getLinks(std::vector &objs, @@ -3683,8 +4003,9 @@ std::vector PropertyXLink::getSubValues(bool newStyle) const { assert(_SubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_SubList.size()); + std::string tmp; for(size_t i=0;i<_ShadowSubList.size();++i) - ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle)); + ret.push_back(getSubNameWithStyle(_SubList[i],_ShadowSubList[i],newStyle,tmp)); return ret; } diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index 7fc84bd22e..124865f66c 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -1169,6 +1169,7 @@ protected: std::vector _ShadowSubList; std::vector _mapped; PropertyLinkBase *parentProp; + mutable std::string tmpShadow; }; diff --git a/src/Base/PreCompiled.h b/src/Base/PreCompiled.h index 791715b249..1764c97d15 100644 --- a/src/Base/PreCompiled.h +++ b/src/Base/PreCompiled.h @@ -76,6 +76,7 @@ #include #include #include +#include // streams #include diff --git a/src/Base/Sequencer.cpp b/src/Base/Sequencer.cpp index 3ab27b74be..e573943917 100644 --- a/src/Base/Sequencer.cpp +++ b/src/Base/Sequencer.cpp @@ -26,6 +26,7 @@ #ifndef _PreComp_ #include #include +#include #endif #include "Sequencer.h" diff --git a/src/Doc/CONTRIBUTORS b/src/Doc/CONTRIBUTORS index 50cf4bbd0f..65bded6c35 100644 --- a/src/Doc/CONTRIBUTORS +++ b/src/Doc/CONTRIBUTORS @@ -194,6 +194,7 @@ Tomas Pavlicek (pavltom) totyg triplus trzyha +Turan Furkan Topak ulrich1a UR_ Uwe Stöhr (donovaly) diff --git a/src/Gui/ArcEngine.cpp b/src/Gui/ArcEngine.cpp index a0edab0d00..33bfc4424b 100644 --- a/src/Gui/ArcEngine.cpp +++ b/src/Gui/ArcEngine.cpp @@ -23,6 +23,7 @@ #include "PreCompiled.h" #include "ArcEngine.h" +#include #include # include @@ -132,4 +133,4 @@ void ArcEngine::defaultValues() SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(2)); SbVec3f point3(7.07f, 7.07f, 0.0); SO_ENGINE_OUTPUT(midpoint, SoSFVec3f, setValue(point3)); -} \ No newline at end of file +} diff --git a/src/Gui/MDIViewPyWrap.cpp b/src/Gui/MDIViewPyWrap.cpp index faeed4973b..0be622db10 100644 --- a/src/Gui/MDIViewPyWrap.cpp +++ b/src/Gui/MDIViewPyWrap.cpp @@ -173,6 +173,10 @@ MDIViewPyWrap::MDIViewPyWrap(const Py::Object& py, Gui::Document* pcDocument,QWi QWidget* widget = ptr->widget(); if (widget) { setCentralWidget(widget); + QString title = widget->windowTitle(); + if (!title.isEmpty()) { + setWindowTitle(title); + } } } catch (Py::Exception&) { diff --git a/src/Gui/TaskMeasure.h b/src/Gui/TaskMeasure.h index ed92e6ab6e..e4d394fb6c 100644 --- a/src/Gui/TaskMeasure.h +++ b/src/Gui/TaskMeasure.h @@ -60,14 +60,11 @@ public: void setMeasureObject(Measure::MeasureBase* obj); private: - QColumnView* dialog{nullptr}; - void onSelectionChanged(const Gui::SelectionChanges& msg) override; Measure::MeasureBase *_mMeasureObject = nullptr; QLineEdit* valueResult{nullptr}; - QLabel* labelResult{nullptr}; QComboBox* modeSwitch{nullptr}; void removeObject(); diff --git a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py index 6f0c9971be..b9d51a6750 100644 --- a/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py +++ b/src/Mod/AddonManager/Widgets/addonmanager_widget_progress_bar.py @@ -78,7 +78,7 @@ class WidgetProgressBar(QtWidgets.QWidget): self.progress_bar.setMaximum(_TOTAL_INCREMENTS) self.stop_button.clicked.connect(self.stop_clicked) self.stop_button.setIcon( - QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/media-playback-stop.svg")) + QtGui.QIcon.fromTheme("stop", QtGui.QIcon(":/icons/debug-stop.svg")) ) self.vertical_layout.addLayout(self.horizontal_layout) self.vertical_layout.addWidget(self.status_label) diff --git a/src/Mod/Arch/ArchCurtainWall.py b/src/Mod/Arch/ArchCurtainWall.py index 6b67f3f929..32193c178f 100644 --- a/src/Mod/Arch/ArchCurtainWall.py +++ b/src/Mod/Arch/ArchCurtainWall.py @@ -259,6 +259,7 @@ class CurtainWall(ArchComponent.Component): obj.addProperty("App::PropertyVector","VerticalDirection","CurtainWall", QT_TRANSLATE_NOOP("App::Property","The vertical direction reference to be used by this object to deduce vertical/horizontal directions. Keep it close to the actual vertical direction of your curtain wall")) obj.VerticalDirection = FreeCAD.Vector(0,0,1) + self.Type = "CurtainWall" def onDocumentRestored(self,obj): diff --git a/src/Mod/Arch/ArchSchedule.py b/src/Mod/Arch/ArchSchedule.py index 7ee88f0b75..f4399bf5f2 100644 --- a/src/Mod/Arch/ArchSchedule.py +++ b/src/Mod/Arch/ArchSchedule.py @@ -332,6 +332,7 @@ class _ArchSchedule: q = None if obj.Unit[i]: unit = obj.Unit[i] + unit = unit.replace("^","") # get rid of existing power symbol unit = unit.replace("2","^2") unit = unit.replace("3","^3") unit = unit.replace("²","^2") diff --git a/src/Mod/CAM/libarea/CMakeLists.txt b/src/Mod/CAM/libarea/CMakeLists.txt index 738f863c41..7616f54b99 100644 --- a/src/Mod/CAM/libarea/CMakeLists.txt +++ b/src/Mod/CAM/libarea/CMakeLists.txt @@ -163,7 +163,11 @@ target_link_libraries(area area-native ${area_LIBS} ${area_native_LIBS}) # TODO why CMAKE_SHARED_LINKER_FLAGS is not used here? # This is a dirty workaround! if(NOT BUILD_DYNAMIC_LINK_PYTHON AND CMAKE_COMPILER_IS_CLANGXX) - target_link_libraries(area "-Wl,--undefined,dynamic_lookup") + if(APPLE) + target_link_libraries(area "-Wl,-undefined,dynamic_lookup") + else(UNIX) + target_link_libraries(area "-Wl,--undefined,dynamic_lookup") + endif() endif() SET_BIN_DIR(area area /Mod/CAM) diff --git a/src/Mod/Draft/draftguitools/gui_lines.py b/src/Mod/Draft/draftguitools/gui_lines.py index e667a37578..8286d65742 100644 --- a/src/Mod/Draft/draftguitools/gui_lines.py +++ b/src/Mod/Draft/draftguitools/gui_lines.py @@ -77,6 +77,7 @@ class Line(gui_base_original.Creator): self.obj = self.doc.addObject("Part::Feature", self.featureName) gui_utils.format_object(self.obj) + self.obj.ViewObject.ShowInTree = False self.call = self.view.addEventCallback("SoEvent", self.action) _toolmsg(translate("draft", "Pick first point")) diff --git a/src/Mod/Draft/draftviewproviders/view_point.py b/src/Mod/Draft/draftviewproviders/view_point.py index a735f45f45..6243df975c 100644 --- a/src/Mod/Draft/draftviewproviders/view_point.py +++ b/src/Mod/Draft/draftviewproviders/view_point.py @@ -45,8 +45,7 @@ class ViewProviderPoint(ViewProviderDraft): vobj.setEditorMode('DisplayMode', mode) vobj.setEditorMode('Lighting', mode) vobj.setEditorMode('LineMaterial', mode) - vobj.setEditorMode('ShapeColor', mode) - vobj.setEditorMode('ShapeMaterial', mode) + vobj.setEditorMode('ShapeAppearance', mode) vobj.setEditorMode('Transparency', mode) def getIcon(self): diff --git a/src/Mod/Measure/App/MeasureBase.h b/src/Mod/Measure/App/MeasureBase.h index fe256dc940..42dd209d8c 100644 --- a/src/Mod/Measure/App/MeasureBase.h +++ b/src/Mod/Measure/App/MeasureBase.h @@ -67,7 +67,7 @@ public: virtual QString getResultString(); virtual std::vector getInputProps(); - virtual App::Property* getResultProp() {return {};}; + virtual App::Property* getResultProp() {return {};} virtual Base::Placement getPlacement(); // Return the objects that are measured diff --git a/src/Mod/Measure/App/MeasureLength.h b/src/Mod/Measure/App/MeasureLength.h index 3942925754..6904205a7e 100644 --- a/src/Mod/Measure/App/MeasureLength.h +++ b/src/Mod/Measure/App/MeasureLength.h @@ -63,7 +63,7 @@ public: App::Property* getResultProp() override {return &this->Length;} // Return a placement for the viewprovider, just use the first element for now - Base::Placement getPlacement(); + Base::Placement getPlacement() override; // Return the object we are measuring std::vector getSubject() const override; diff --git a/src/Mod/Measure/Gui/Command.cpp b/src/Mod/Measure/Gui/Command.cpp index f7cbe5442f..1dfe2a942d 100644 --- a/src/Mod/Measure/Gui/Command.cpp +++ b/src/Mod/Measure/Gui/Command.cpp @@ -28,6 +28,5 @@ using namespace std; void CreateMeasureCommands() { - Base::Console().Message("Init MeasureGui\n"); - Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); -} \ No newline at end of file + Base::Console().Log("Init MeasureGui\n"); +} diff --git a/src/Mod/Part/App/MeasureClient.cpp b/src/Mod/Part/App/MeasureClient.cpp index 0c099a78a0..002ad7581b 100644 --- a/src/Mod/Part/App/MeasureClient.cpp +++ b/src/Mod/Part/App/MeasureClient.cpp @@ -53,13 +53,6 @@ #include #include -#include -#include -#include -#include -#include -#include - #include "VectorAdapter.h" #include "PartFeature.h" diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 7090a17108..cf6acf1ba1 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -4081,6 +4081,7 @@ bool TopoShape::findPlane(gp_Pln& pln, double tol, double atol) const } #else bool TopoShape::findPlane(gp_Pln &pln, double tol, double atol) const { + (void)atol; if(_Shape.IsNull()) return false; TopoDS_Shape shape = _Shape; diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index d0e095278f..a6894f9ab1 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -261,6 +261,13 @@ enum class OpenResult allowOpenResult }; +// See BRepFeat_MakeRevol +enum class RevolMode { + CutFromBase = 0, + FuseWithBase = 1, + None = 2 +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -1081,7 +1088,6 @@ public: /** Make revolved shell around a basis shape * - * @param base: the basis shape * @param axis: the revolving axis * @param d: rotation angle in degree * @param face_maker: optional type name of the the maker used to make a @@ -1097,6 +1103,67 @@ public: } + /** Make revolved shell around a basis shape + * + * @param base: the basis shape + * @param axis: the revolving axis + * @param d: rotation angle in degree + * @param face_maker: optional type name of the the maker used to make a + * face from basis shape + * @param supportface: the bottom face for the revolution, or null + * @param uptoface: the upper limit face for the revolution, or null + * @param Mode: the opencascade defined modes + * @param Modify: if opencascade should modify existing shapes + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape& makeElementRevolution(const TopoShape& _base, + const gp_Ax1& axis, + double d, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const char* face_maker = nullptr, + RevolMode Mode = RevolMode::None, + Standard_Boolean Modify = Standard_True, + const char* op = nullptr); + + /** Make revolved shell around a basis shape + * + * @param axis: the revolving axis + * @param d: rotation angle in degree + * @param face_maker: optional type name of the the maker used to make a + * face from basis shape + * @param supportface: the bottom face for the revolution, or null + * @param uptoface: the upper limit face for the revolution, or null + * @param Mode: the opencascade defined modes + * @param Modify: if opencascade should modify existing shapes + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape& makeElementRevolution(const gp_Ax1& axis, + double d, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const char* face_maker = nullptr, + RevolMode Mode = RevolMode::None, + Standard_Boolean Modify = Standard_True, + const char* op = nullptr) const + { + return TopoShape(0, Hasher).makeElementRevolution(*this, + axis, + d, + supportface, + uptoface, + face_maker, + Mode, + Modify, + op); + } + /** Make a prism that is a linear sweep of a basis shape * * @param base: the basis shape diff --git a/src/Mod/Part/App/TopoShapeEdgePy.xml b/src/Mod/Part/App/TopoShapeEdgePy.xml index e4dfa01168..06d28d4ef5 100644 --- a/src/Mod/Part/App/TopoShapeEdgePy.xml +++ b/src/Mod/Part/App/TopoShapeEdgePy.xml @@ -358,11 +358,11 @@ Part.show(s) split(paramval) -> Wire -- Args: - paramval (float or int): The parameter value along the Edge at which to + paramval (float or list_of_floats): The parameter values along the Edge at which to split it e.g: - x = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90) - y = x.derivative3At(x.FirstParameter + 0.5 * (x.LastParameter - x.FirstParameter)) + edge = Part.makeCircle(1, FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,0,1), 0, 90) + wire = edge.split([0.5, 1.0]) Returns: Wire: wire made up of two Edges diff --git a/src/Mod/Part/App/TopoShapeEdgePyImp.cpp b/src/Mod/Part/App/TopoShapeEdgePyImp.cpp index 68cf55808e..6b39d05fd8 100644 --- a/src/Mod/Part/App/TopoShapeEdgePyImp.cpp +++ b/src/Mod/Part/App/TopoShapeEdgePyImp.cpp @@ -711,10 +711,16 @@ PyObject* TopoShapeEdgePy::split(PyObject *args) BRepBuilderAPI_MakeWire mkWire; Handle(Geom_Curve) c = adapt.Curve().Curve(); + const TopoDS_Edge& edge = TopoDS::Edge(this->getTopoShapePtr()->getShape()); + BRep_Builder builder; + TopoDS_Edge e; std::vector::iterator end = par.end() - 1; for (std::vector::iterator it = par.begin(); it != end; ++it) { - BRepBuilderAPI_MakeEdge mkBuilder(c, it[0], it[1]); - mkWire.Add(mkBuilder.Edge()); + BRepBuilderAPI_MakeEdge mke(c, it[0], it[1]); + e = mke.Edge(); + builder.Transfert(edge, e); + builder.Range(e, it[0], it[1], false); + mkWire.Add(e); } return new TopoShapeWirePy(new TopoShape(mkWire.Shape())); diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 24d9e4d6a2..2a01402ff9 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -106,6 +106,7 @@ #include #include #include +#include FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -4439,6 +4440,51 @@ TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base, return makeElementShape(mkRevol, base, op); } +TopoShape& TopoShape::makeElementRevolution(const TopoShape& _base, + const gp_Ax1& axis, + double d, + const TopoDS_Face& supportface, + const TopoDS_Face& uptoface, + const char* face_maker, + RevolMode Mode, + Standard_Boolean Modify, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Revolve; + } + + TopoShape base(_base); + if (base.isNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (face_maker && !base.hasSubShape(TopAbs_FACE)) { + if (!base.hasSubShape(TopAbs_WIRE)) { + base = base.makeElementWires(); + } + base = base.makeElementFace(nullptr, face_maker, nullptr); + } + + BRepFeat_MakeRevol mkRevol; + for (TopExp_Explorer xp(base.getShape(), TopAbs_FACE); xp.More(); xp.Next()) { + mkRevol.Init(_base.getShape(), + xp.Current(), + supportface, + axis, + static_cast(Mode), + Modify); + mkRevol.Perform(uptoface); + if (!mkRevol.IsDone()) { + throw Base::RuntimeError("Revolution: Up to face: Could not revolve the sketch!"); + } + base = mkRevol.Shape(); + if (Mode == RevolMode::None) { + Mode = RevolMode::FuseWithBase; + } + } + return makeElementShape(mkRevol, base, op); +} + TopoShape& TopoShape::makeElementDraft(const TopoShape& shape, const std::vector& _faces, const gp_Dir& pullDirection, diff --git a/src/Mod/Part/parttests/BRep_tests.py b/src/Mod/Part/parttests/BRep_tests.py index 20dcd90469..eb22622cf2 100644 --- a/src/Mod/Part/parttests/BRep_tests.py +++ b/src/Mod/Part/parttests/BRep_tests.py @@ -10,7 +10,7 @@ class BRepTests(unittest.TestCase): This is a unit test for PR #13507 """ num = 18 - alt = [0,1] * num + alt = [0, 1] * num pts = [App.Vector(i, alt[i], 0) for i in range(num)] bsc = Part.BSplineCurve() @@ -24,3 +24,26 @@ class BRepTests(unittest.TestCase): self.assertFalse(proj.isNull()) self.assertEqual(len(proj.Edges), 1) + def testEdgeSplitFace(self): + coords2d = [(0.5, -0.5), (1.0, -0.5), (1.0, 0.5), (0.5, 0.5)] + pts2d = [App.Base.Vector2d(u, v) for u, v in coords2d] + pts2d.append(pts2d[0]) + + sphere = Part.Sphere() + edges = [] + for i in range(1, len(pts2d)): + ls = Part.Geom2d.Line2dSegment(pts2d[i - 1], pts2d[i]) + edges.append(ls.toShape(sphere)) + + split = edges[0].split(0.25) + new_edges = split.Edges + edges[1:] + wire = Part.Wire(new_edges) + face = Part.Face(wire, "Part::FaceMakerSimple") + self.assertTrue(face.isValid()) + + def testEdgeSplitReplace(self): + cyl = Part.makeCylinder(2, 5) + e1 = cyl.Edge3 + split = e1.split([1.0, 2.0]) + newcyl = cyl.replaceShape([(e1, split), (cyl.Vertex2, split.Vertex1)]) + self.assertTrue(newcyl.isValid()) diff --git a/src/Mod/PartDesign/App/FeatureExtrude.cpp b/src/Mod/PartDesign/App/FeatureExtrude.cpp index f5bdc15e57..0f51b74881 100644 --- a/src/Mod/PartDesign/App/FeatureExtrude.cpp +++ b/src/Mod/PartDesign/App/FeatureExtrude.cpp @@ -431,7 +431,6 @@ App::DocumentObjectExecReturn* FeatureExtrude::buildExtrusion(ExtrudeOptions opt bool makeface = options.testFlag(ExtrudeOption::MakeFace); bool fuse = options.testFlag(ExtrudeOption::MakeFuse); bool legacyPocket = options.testFlag(ExtrudeOption::LegacyPocket); - bool inverseDirection = options.testFlag(ExtrudeOption::InverseDirection); std::string method(Type.getValueAsString()); diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index b03e48d175..9f884a752d 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -38,6 +38,7 @@ #include #include "FeatureRevolution.h" +#include "Mod/Part/App/TopoShapeOpCode.h" using namespace PartDesign; @@ -78,24 +79,29 @@ short Revolution::mustExecute() const return ProfileBased::mustExecute(); } -App::DocumentObjectExecReturn *Revolution::execute() +App::DocumentObjectExecReturn* Revolution::execute() { // Validate parameters // All angles are in radians unless explicitly stated double angleDeg = Angle.getValue(); - if (angleDeg > 360.0) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large")); + if (angleDeg > 360.0) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Angle of revolution too large")); + } double angle = Base::toRadians(angleDeg); - if (angle < Precision::Angular()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small")); + if (angle < Precision::Angular()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Angle of revolution too small")); + } double angle2 = Base::toRadians(Angle2.getValue()); - TopoDS_Shape sketchshape; + TopoShape sketchshape; try { sketchshape = getVerifiedFace(); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } @@ -103,27 +109,36 @@ App::DocumentObjectExecReturn *Revolution::execute() TopoShape base; try { base = getBaseTopoShape(); - } catch (const Base::Exception&) { + } + catch (const Base::Exception&) { // fall back to support (for legacy features) - base = TopoShape(); } // update Axis from ReferenceAxis try { updateAxis(); - } catch (const Base::Exception& e) { + } + catch (const Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } - // get revolve axis - Base::Vector3d b = Base.getValue(); - gp_Pnt pnt(b.x,b.y,b.z); - Base::Vector3d v = Axis.getValue(); - gp_Dir dir(v.x,v.y,v.z); - try { - if (sketchshape.IsNull()) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + // get revolve axis + Base::Vector3d b = Base.getValue(); + gp_Pnt pnt(b.x, b.y, b.z); + Base::Vector3d v = Axis.getValue(); + + if (v.IsNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Reference axis is invalid")); + } + + gp_Dir dir(v.x, v.y, v.z); + + if (sketchshape.isNull()) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Creating a face from sketch failed")); + } RevolMethod method = methodFromString(Type.getValueAsString()); @@ -132,51 +147,99 @@ App::DocumentObjectExecReturn *Revolution::execute() pnt.Transform(invObjLoc.Transformation()); dir.Transform(invObjLoc.Transformation()); base.move(invObjLoc); - sketchshape.Move(invObjLoc); + sketchshape.move(invObjLoc); // Check distance between sketchshape and axis - to avoid failures and crashes TopExp_Explorer xp; - xp.Init(sketchshape, TopAbs_FACE); - for (;xp.More(); xp.Next()) { - if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); + xp.Init(sketchshape.getShape(), TopAbs_FACE); + for (; xp.More(); xp.Next()) { + if (checkLineCrossesFace(gp_Lin(pnt, dir), TopoDS::Face(xp.Current()))) { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Revolve axis intersects the sketch")); + } } // Create a fresh support even when base exists so that it can be used for patterns +#ifdef FC_USE_TNP_FIX + TopoShape result; +#else TopoDS_Shape result; +#endif TopoDS_Face supportface = getSupportFace(); supportface.Move(invObjLoc); - if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst || method == RevolMethod::ToLast) { + if (method == RevolMethod::ToFace || method == RevolMethod::ToFirst + || method == RevolMethod::ToLast) { TopoDS_Face upToFace; if (method == RevolMethod::ToFace) { getFaceFromLinkSub(upToFace, UpToFace); upToFace.Move(invObjLoc); } - else - throw Base::RuntimeError("ProfileBased: Revolution up to first/last is not yet supported"); + else { + throw Base::RuntimeError( + "ProfileBased: Revolution up to first/last is not yet supported"); + } // TODO: This method is designed for extrusions. needs to be adapted for revolutions. // getUpToFace(upToFace, base, supportface, sketchshape, method, dir); - TopoDS_Face supportface = getSupportFace(); + // TopoDS_Face supportface = getSupportFace(); supportface.Move(invObjLoc); - if (Reversed.getValue()) + if (Reversed.getValue()) { dir.Reverse(); + } - TopExp_Explorer Ex(supportface,TopAbs_WIRE); - if (!Ex.More()) + TopExp_Explorer Ex(supportface, TopAbs_WIRE); + if (!Ex.More()) { supportface = TopoDS_Face(); + } RevolMode mode = RevolMode::None; - generateRevolution(result, base.getShape(), sketchshape, supportface, upToFace, gp_Ax1(pnt, dir), method, mode, Standard_True); +#ifdef FC_USE_TNP_FIX + // revolve the face to a solid + // TopoShape result(0); + try { + result = base.makeElementRevolution(gp_Ax1(pnt, dir), angle, supportface, upToFace); + } + catch (Standard_Failure&) { + return new App::DocumentObjectExecReturn("Could not revolve the sketch!"); + } +#else + generateRevolution(result, + base.getShape(), + sketchshape.getShape(), + supportface, + upToFace, + gp_Ax1(pnt, dir), + method, + mode, + Standard_True); +#endif } else { bool midplane = Midplane.getValue(); bool reversed = Reversed.getValue(); - generateRevolution(result, sketchshape, gp_Ax1(pnt, dir), angle, angle2, midplane, reversed, method); + generateRevolution(result, + sketchshape.getShape(), + gp_Ax1(pnt, dir), + angle, + angle2, + midplane, + reversed, + method); } +#ifdef FC_USE_TNP_FIX + if (!result.isNull()) { + result = refineShapeIfActive(result); + // set the additive shape property for later usage in e.g. pattern + this->AddSubShape.setValue(result); + + if (!base.isNull()) { + result = result.makeElementFuse(base); + result = refineShapeIfActive(result); + } +#else if (!result.IsNull()) { result = refineShapeIfActive(result); // set the additive shape property for later usage in e.g. pattern @@ -186,16 +249,21 @@ App::DocumentObjectExecReturn *Revolution::execute() // Let's call algorithm computing a fuse operation: BRepAlgoAPI_Fuse mkFuse(base.getShape(), result); // Let's check if the fusion has been successful - if (!mkFuse.IsDone()) - throw Part::BooleanException(QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed")); + if (!mkFuse.IsDone()) { + throw Part::BooleanException( + QT_TRANSLATE_NOOP("Exception", "Fusion with base feature failed")); + } result = mkFuse.Shape(); result = refineShapeIfActive(result); } +#endif this->Shape.setValue(getSolid(result)); } - else - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); + else { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Could not revolve the sketch!")); + } // eventually disable some settings that are not valid for the current method updateProperties(method); @@ -204,11 +272,15 @@ App::DocumentObjectExecReturn *Revolution::execute() } catch (Standard_Failure& e) { - if (std::string(e.GetMessageString()) == "TopoDS::Face") - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Could not create face from sketch.\n" - "Intersecting sketch entities in a sketch are not allowed.")); - else + if (std::string(e.GetMessageString()) == "TopoDS::Face") { + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", + "Could not create face from sketch.\n" + "Intersecting sketch entities in a sketch are not allowed.")); + } + else { return new App::DocumentObjectExecReturn(e.GetMessageString()); + } } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); @@ -257,7 +329,12 @@ Revolution::RevolMethod Revolution::methodFromString(const std::string& methodSt return RevolMethod::Dimension; } +#ifdef FC_USE_TNP_FIX +void Revolution::generateRevolution(TopoShape& revol, +#else void Revolution::generateRevolution(TopoDS_Shape& revol, + +#endif const TopoDS_Shape& sketchshape, const gp_Ax1& axis, const double angle, @@ -267,46 +344,49 @@ void Revolution::generateRevolution(TopoDS_Shape& revol, RevolMethod method) { if (method == RevolMethod::Dimension || method == RevolMethod::TwoDimensions || method == RevolMethod::ThroughAll) { - double angleTotal = angle; - double angleOffset = 0.; + double angleTotal = angle; + double angleOffset = 0.; - if (method == RevolMethod::TwoDimensions) { - // Rotate the face by `angle2`/`angle` to get "second" angle - angleTotal += angle2; - angleOffset = angle2 * -1.0; - } - else if (midplane) { - // Rotate the face by half the angle to get Revolution symmetric to sketch plane - angleOffset = -angle / 2; - } + if (method == RevolMethod::TwoDimensions) { + // Rotate the face by `angle2`/`angle` to get "second" angle + angleTotal += angle2; + angleOffset = angle2 * -1.0; + } + else if (midplane) { + // Rotate the face by half the angle to get Revolution symmetric to sketch plane + angleOffset = -angle / 2; + } - if (fabs(angleTotal) < Precision::Angular()) - throw Base::ValueError("Cannot create a revolution with zero angle."); + if (fabs(angleTotal) < Precision::Angular()) + throw Base::ValueError("Cannot create a revolution with zero angle."); - gp_Ax1 revolAx(axis); - if (reversed) { - revolAx.Reverse(); - } + gp_Ax1 revolAx(axis); + if (reversed) { + revolAx.Reverse(); + } - TopoDS_Shape from = sketchshape; - if (method == RevolMethod::TwoDimensions || midplane) { - gp_Trsf mov; - mov.SetRotation(revolAx, angleOffset); - TopLoc_Location loc(mov); - from.Move(loc); - } + TopoDS_Shape from = sketchshape; + if (method == RevolMethod::TwoDimensions || midplane) { + gp_Trsf mov; + mov.SetRotation(revolAx, angleOffset); + TopLoc_Location loc(mov); + from.Move(loc); + } - // revolve the face to a solid - // BRepPrimAPI is the only option that allows use of this shape for patterns. - // See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673. - BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal); +#ifdef FC_USE_TNP_FIX + revol = TopoShape(from).makeElementRevolve(revolAx,angleTotal); +#else + // revolve the face to a solid + // BRepPrimAPI is the only option that allows use of this shape for patterns. + // See https://forum.freecadweb.org/viewtopic.php?f=8&t=70185&p=611673#p611673. + BRepPrimAPI_MakeRevol RevolMaker(from, revolAx, angleTotal); - if (!RevolMaker.IsDone()) - throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!"); - else - revol = RevolMaker.Shape(); - } - else { + if (!RevolMaker.IsDone()) + throw Base::RuntimeError("ProfileBased: RevolMaker failed! Could not revolve the sketch!"); + else + revol = RevolMaker.Shape(); +#endif + } else { std::stringstream str; str << "ProfileBased: Internal error: Unknown method for generateRevolution()"; throw Base::RuntimeError(str.str()); diff --git a/src/Mod/PartDesign/App/FeatureRevolution.h b/src/Mod/PartDesign/App/FeatureRevolution.h index bf80298875..583eb1b1fd 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.h +++ b/src/Mod/PartDesign/App/FeatureRevolution.h @@ -95,7 +95,11 @@ protected: /** * Generates a revolution of the input sketchshape and stores it in the given \a revol. */ +#ifdef FC_USE_TNP_FIX + void generateRevolution(TopoShape& revol, +#else void generateRevolution(TopoDS_Shape& revol, +#endif const TopoDS_Shape& sketchshape, const gp_Ax1& ax1, const double angle, diff --git a/src/Mod/PartDesign/App/FeatureSketchBased.cpp b/src/Mod/PartDesign/App/FeatureSketchBased.cpp index ebf18c3402..964f9fe014 100644 --- a/src/Mod/PartDesign/App/FeatureSketchBased.cpp +++ b/src/Mod/PartDesign/App/FeatureSketchBased.cpp @@ -280,7 +280,7 @@ TopoDS_Shape ProfileBased::getVerifiedFace(bool silent) const { } TopoShape ProfileBased::getTopoShapeVerifiedFace(bool silent, - bool doFit, + [[maybe_unused]]bool doFit, // TODO: Remove parameter bool allowOpen, const App::DocumentObject* profile, const std::vector& _subs) const diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index a027aca68b..a66113eb22 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -498,14 +498,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): self.Doc.recompute() # Assert # self.assertEqual(len(body.Shape.childShapes()), 1) - if App.GuiUp: - # Todo: This triggers a 'hasher mismatch' warning in TopoShape::mapSubElement as called by - # flushElementMap. This appears to be the case whenever you have a parent with a hasher - # that has children without hashmaps. The warning seems to be spurious in this case, but - # perhaps there is a solution involving setting hashmaps on all elements. - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) - else: - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) self.assertEqual(body.Shape.ElementMapSize,30) self.assertEqual(sketch.Shape.ElementMapSize,12) self.assertEqual(pad.Shape.ElementMapSize,30) @@ -520,7 +513,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): return # Act revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution') - revolution.ReferenceAxis = body.Origin.OriginFeatures[1] + revolution.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis']) revolution.Profile = sketch # Causing segfault body.addObject(sketch) body.addObject(revolution) @@ -582,7 +575,7 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Act helix = self.Doc.addObject('PartDesign::AdditiveHelix', 'Helix') helix.Profile = sketch - helix.ReferenceAxis = body.Origin.OriginFeatures[2] + helix.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis']) body.addObject(sketch) body.addObject(helix) self.Doc.recompute() @@ -789,6 +782,174 @@ class TestTopologicalNamingProblem(unittest.TestCase): self.assertEqual(plane.Shape.ElementMapSize, 0) self.assertEqual(pad.Shape.ElementMapSize, 26) + def testChangeSketch(self): + # Arrange + self.Body = self.Doc.addObject('PartDesign::Body', 'Body') + # Make first offset cube Pad + self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + self.Body.addObject(self.PadSketch) + TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2)) + self.Doc.recompute() + self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad") + self.Body.addObject(self.Pad) + self.Pad.Profile = self.PadSketch + self.Pad.Length = 10 + self.Doc.recompute() + + self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001') + self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',]) + self.Sketch001.MapMode = 'FlatFace' + App.ActiveDocument.recompute() + + self.Sketch001.addExternal("Pad","Edge10") + self.Sketch001.addExternal("Pad","Edge7") + + geoList = [] + geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000), + App.Vector(0.000000, 0.000000, 1.000000), 5.000000)) + self.Sketch001.addGeometry(geoList,False) + del geoList + self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000)) + self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3)) + App.ActiveDocument.recompute() + self.Doc.recompute() + + self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001') + self.Pad001.Profile = self.Doc.getObject('Sketch001') + self.Pad001.Length = 10 + App.ActiveDocument.recompute() + self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis']) + self.Sketch001.Visibility = False + App.ActiveDocument.recompute() + + self.Pad001.Length = 10.000000 + self.Pad001.TaperAngle = 0.000000 + self.Pad001.UseCustomVector = 0 + self.Pad001.Direction = (0, 0, 1) + self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis']) + self.Pad001.AlongSketchNormal = 1 + self.Pad001.Type = 0 + self.Pad001.UpToFace = None + self.Pad001.Reversed = 0 + self.Pad001.Midplane = 0 + self.Pad001.Offset = 0 + self.Doc.recompute() + self.Doc.getObject('Pad').Visibility = False + + self.Doc.getObject('Sketch001').Visibility = False + + # Modify the original sketch to generate TNP issue + geoList = [] + geoList.append(Part.LineSegment(App.Vector(2.510468, 22.837425, 0.000000), + App.Vector(2.510468, 19.933617, 0.000000))) + geoList.append(Part.LineSegment(App.Vector(2.510468, 19.933617, 0.000000), + App.Vector(4.869811, 19.933617, 0.000000))) + geoList.append(Part.LineSegment(App.Vector(4.869811, 19.933617, 0.000000), + App.Vector(4.869811, 22.837425, 0.000000))) + geoList.append(Part.LineSegment(App.Vector(4.869811, 22.837425, 0.000000), + App.Vector(2.510468, 22.837425, 0.000000))) + self.PadSketch.addGeometry(geoList,False) + del geoList + + constraintList = [] + constraintList.append(Sketcher.Constraint('Coincident', 4, 2, 5, 1)) + constraintList.append(Sketcher.Constraint('Coincident', 5, 2, 6, 1)) + constraintList.append(Sketcher.Constraint('Coincident', 6, 2, 7, 1)) + constraintList.append(Sketcher.Constraint('Coincident', 7, 2, 4, 1)) + constraintList.append(Sketcher.Constraint('Vertical', 4)) + constraintList.append(Sketcher.Constraint('Vertical', 6)) + constraintList.append(Sketcher.Constraint('Horizontal', 5)) + constraintList.append(Sketcher.Constraint('Horizontal', 7)) + self.PadSketch.addConstraint(constraintList) + del constraintList + self.Doc.recompute() + # Assert + if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + self.assertEqual(self.Body.Shape.BoundBox.XMin,0) + self.assertEqual(self.Body.Shape.BoundBox.YMin,0) + self.assertEqual(self.Body.Shape.BoundBox.ZMin,0) + self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37) + self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2) + self.assertEqual(self.Body.Shape.BoundBox.ZMax,20) + + def testApplyFillet(self): + # Arrange + self.Body = self.Doc.addObject('PartDesign::Body', 'Body') + # Make first offset cube Pad + self.PadSketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + self.Body.addObject(self.PadSketch) + TestSketcherApp.CreateRectangleSketch(self.PadSketch, (0, 0), (31.37, 25.2)) + self.Doc.recompute() + self.Pad = self.Doc.addObject("PartDesign::Pad", "Pad") + self.Body.addObject(self.Pad) + self.Pad.Profile = self.PadSketch + self.Pad.Length = 10 + self.Doc.recompute() + + self.Sketch001 = self.Body.newObject('Sketcher::SketchObject','Sketch001') + self.Sketch001.AttachmentSupport = (self.Doc.getObject('Pad'),['Face6',]) + self.Sketch001.MapMode = 'FlatFace' + App.ActiveDocument.recompute() + + self.Sketch001.addExternal("Pad","Edge10") + self.Sketch001.addExternal("Pad","Edge7") + + geoList = [] + geoList.append(Part.Circle(App.Vector(15.093666, 13.036922, 0.000000), + App.Vector(0.000000, 0.000000, 1.000000), 5.000000)) + self.Sketch001.addGeometry(geoList,False) + del geoList + self.Sketch001.addConstraint(Sketcher.Constraint('Radius',0,5.000000)) + self.Sketch001.addConstraint(Sketcher.Constraint('Symmetric',-3,2,-4,1,0,3)) + App.ActiveDocument.recompute() + self.Doc.recompute() + + self.Pad001 = self.Body.newObject('PartDesign::Pad','Pad001') + self.Pad001.Profile = self.Doc.getObject('Sketch001') + self.Pad001.Length = 10 + App.ActiveDocument.recompute() + self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'),['N_Axis']) + self.Sketch001.Visibility = False + App.ActiveDocument.recompute() + + self.Pad001.Length = 10.000000 + self.Pad001.TaperAngle = 0.000000 + self.Pad001.UseCustomVector = 0 + self.Pad001.Direction = (0, 0, 1) + self.Pad001.ReferenceAxis = (self.Doc.getObject('Sketch001'), ['N_Axis']) + self.Pad001.AlongSketchNormal = 1 + self.Pad001.Type = 0 + self.Pad001.UpToFace = None + self.Pad001.Reversed = 0 + self.Pad001.Midplane = 0 + self.Pad001.Offset = 0 + self.Doc.recompute() + self.Doc.getObject('Pad').Visibility = False + + self.Doc.getObject('Sketch001').Visibility = False + + area1 = self.Pad.Shape.Area + # Act + self.Doc.getObject('Sketch').fillet(2,3,App.Vector(6.673934,25.000000,0),App.Vector(0.000000,21.980343,0),4.740471,True,True,False) + self.Doc.recompute() + # filleted = self.Pad001.Shape.makeFillet(1,self.Pad001.Shape.Edges[0:2]) + # self.filleted = Part.show(filleted,"Filleted") + # self.Body.addObject(self.filleted) + area2 = self.Pad.Shape.Area + print(area1,area2) + + # Assert + if self.Body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + self.assertEqual(self.Body.Shape.BoundBox.XMin,0) + self.assertEqual(self.Body.Shape.BoundBox.YMin,0) + self.assertEqual(self.Body.Shape.BoundBox.ZMin,0) + self.assertEqual(self.Body.Shape.BoundBox.XMax,31.37) + self.assertEqual(self.Body.Shape.BoundBox.YMax,25.2) + self.assertEqual(self.Body.Shape.BoundBox.ZMax,20) + + def create_t_sketch(self): self.Doc.getObject('Body').newObject('Sketcher::SketchObject', 'Sketch') geo_list = [ diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp index 8344a427b5..9bf503e569 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp +++ b/src/Mod/Sketcher/Gui/DrawSketchHandler.cpp @@ -996,7 +996,6 @@ void DrawSketchHandler::createAutoConstraints(const std::vector& geoId2); } break; case Sketcher::Symmetric: { - Sketcher::PointPos posId2 = cstr.PosId; Gui::cmdAppObjectArgs( sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Symmetric',%d,1,%d,2,%d,%d)) ", diff --git a/src/Mod/Start/App/ExamplesModel.cpp b/src/Mod/Start/App/ExamplesModel.cpp index 9d6bb6d3b2..c1b8c10334 100644 --- a/src/Mod/Start/App/ExamplesModel.cpp +++ b/src/Mod/Start/App/ExamplesModel.cpp @@ -31,7 +31,6 @@ using namespace Start; -FC_LOG_LEVEL_INIT(ExamplesModel) ExamplesModel::ExamplesModel(QObject* parent) : DisplayedFilesModel(parent) diff --git a/src/Mod/Start/Gui/AppStartGui.cpp b/src/Mod/Start/Gui/AppStartGui.cpp index ad2898e9dc..561485d147 100644 --- a/src/Mod/Start/Gui/AppStartGui.cpp +++ b/src/Mod/Start/Gui/AppStartGui.cpp @@ -115,6 +115,7 @@ PyObject* initModule() PyMOD_INIT_FUNC(StartGui) { static StartGui::StartLauncher* launcher = new StartGui::StartLauncher(); + Q_UNUSED(launcher) Base::Console().Log("Loading GUI of Start module... "); PyObject* mod = StartGui::initModule(); diff --git a/src/Mod/Start/Gui/CMakeLists.txt b/src/Mod/Start/Gui/CMakeLists.txt index 863164955f..d120018941 100644 --- a/src/Mod/Start/Gui/CMakeLists.txt +++ b/src/Mod/Start/Gui/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${Boost_INCLUDE_DIRS} + ${COIN3D_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS} ${QtCore_INCLUDE_DIRS} ${QtSvg_INCLUDE_DIRS} diff --git a/src/Mod/Start/Gui/GeneralSettingsWidget.h b/src/Mod/Start/Gui/GeneralSettingsWidget.h index 79aa0dea54..7f596d83ee 100644 --- a/src/Mod/Start/Gui/GeneralSettingsWidget.h +++ b/src/Mod/Start/Gui/GeneralSettingsWidget.h @@ -25,7 +25,7 @@ #define FREECAD_START_GENERALSETTINGSWIDGET_H #include -#include <3rdParty/GSL/include/gsl/pointers> +#include class QLabel; class QComboBox; diff --git a/src/Mod/Start/Gui/ThemeSelectorWidget.cpp b/src/Mod/Start/Gui/ThemeSelectorWidget.cpp index 7226659a9e..e8eaadb6bc 100644 --- a/src/Mod/Start/Gui/ThemeSelectorWidget.cpp +++ b/src/Mod/Start/Gui/ThemeSelectorWidget.cpp @@ -31,7 +31,7 @@ #endif #include "ThemeSelectorWidget.h" -#include <3rdParty/GSL/include/gsl/pointers> +#include #include #include #include diff --git a/src/Mod/TechDraw/App/DrawTemplate.cpp b/src/Mod/TechDraw/App/DrawTemplate.cpp index 527b5a1c44..29de742c97 100644 --- a/src/Mod/TechDraw/App/DrawTemplate.cpp +++ b/src/Mod/TechDraw/App/DrawTemplate.cpp @@ -100,6 +100,32 @@ DrawPage* DrawTemplate::getParentPage() const return page; } +// Return the counts related to pages, namely collated page index and total page count +std::pair DrawTemplate::getPageNumbers() const +{ + std::vector pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); + std::vector pageNames; + for (auto page : pages) { + if (page->isAttachedToDocument() && + !page->testStatus(App::ObjectStatus::Remove)) { + pageNames.push_back(QString::fromUtf8(page->Label.getValue())); + } + } + QCollator collator; + std::sort(pageNames.begin(), pageNames.end(), collator); + + int pos = 0; + DrawPage *page = getParentPage(); + if (page) { + auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue())); + if (it != pageNames.end()) { + pos = it - pageNames.begin() + 1; + } + } + + return std::pair(pos, (int) pageNames.size()); +} + QString DrawTemplate::getAutofillValue(const QString &id) const { // author @@ -133,32 +159,23 @@ QString DrawTemplate::getAutofillValue(const QString &id) const } // sheet else if (id.compare(QString::fromUtf8(Autofill::Sheet)) == 0) { - std::vector pages = getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); - std::vector pageNames; - for (auto page : pages) { - if (page->isAttachedToDocument() && - !page->testStatus(App::ObjectStatus::Remove)) { - pageNames.push_back(QString::fromUtf8(page->Label.getValue())); - } - } - QCollator collator; - std::sort(pageNames.begin(), pageNames.end(), collator); - - int pos = 0; - DrawPage *page = getParentPage(); - if (page) { - auto it = std::find(pageNames.begin(), pageNames.end(), QString::fromUtf8(page->Label.getValue())); - if (it != pageNames.end()) { - pos = it - pageNames.begin() + 1; - } - } - - return QString::asprintf("%d / %d", pos, (int) pageNames.size()); + std::pair pageNumbers = getPageNumbers(); + return QString::asprintf("%d / %d", pageNumbers.first, pageNumbers.second); } // title else if (id.compare(QString::fromUtf8(Autofill::Title)) == 0) { return QString::fromUtf8(getDocument()->Label.getValue()); } + // page number + else if (id.compare(QString::fromUtf8(Autofill::PageNumber)) == 0) { + std::pair pageNumbers = getPageNumbers(); + return QString::number(pageNumbers.first); + } + // page total + else if (id.compare(QString::fromUtf8(Autofill::PageCount)) == 0) { + std::pair pageNumbers = getPageNumbers(); + return QString::number(pageNumbers.second); + } return QString(); } diff --git a/src/Mod/TechDraw/App/DrawTemplate.h b/src/Mod/TechDraw/App/DrawTemplate.h index f2c6be5390..66d8904e27 100644 --- a/src/Mod/TechDraw/App/DrawTemplate.h +++ b/src/Mod/TechDraw/App/DrawTemplate.h @@ -56,6 +56,7 @@ public: virtual double getHeight() const; virtual DrawPage* getParentPage() const; + virtual std::pair getPageNumbers() const; virtual QString getAutofillValue(const QString &id) const; @@ -76,6 +77,8 @@ public: static constexpr const char *Scale = "scale"; static constexpr const char *Sheet = "sheet"; static constexpr const char *Title = "title"; + static constexpr const char *PageNumber = "page_number"; + static constexpr const char *PageCount = "page_count"; }; private: diff --git a/src/Mod/TechDraw/App/Preferences.cpp b/src/Mod/TechDraw/App/Preferences.cpp index 3517849a19..3028531c51 100644 --- a/src/Mod/TechDraw/App/Preferences.cpp +++ b/src/Mod/TechDraw/App/Preferences.cpp @@ -178,7 +178,7 @@ int Preferences::balloonShape() QString Preferences::defaultTemplate() { std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/"; - std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg"; + std::string defaultFileName = defaultDir + "Default_Template_A4_Landscape.svg"; std::string prefFileName = getPreferenceGroup("Files")->GetASCII("TemplateFile", defaultFileName.c_str()); if (prefFileName.empty()) { prefFileName = defaultFileName; diff --git a/src/Mod/TechDraw/Templates/Default_Template_A4_Landscape.svg b/src/Mod/TechDraw/Templates/Default_Template_A4_Landscape.svg new file mode 100644 index 0000000000..cc20e1b8be --- /dev/null +++ b/src/Mod/TechDraw/Templates/Default_Template_A4_Landscape.svg @@ -0,0 +1,11 @@ + + + + + + image/svg+xml + + + + + diff --git a/src/Mod/Test/TestPerf.py b/src/Mod/Test/TestPerf.py new file mode 100644 index 0000000000..954ff3d40b --- /dev/null +++ b/src/Mod/Test/TestPerf.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2024 bgbsww@gmail.com * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +import sys +import unittest +import FreeCAD as App +import Part + +try: + from guppy import hpy + + Memtest = True +except ImportError: + Memtest = False + +try: + import cProfile + + Pyprofile = True +except ImportError: + Pyprofile = False + + +class PerfTestCase(unittest.TestCase): + """ + Special Test Case that takes a list of filenames after the "--pass" parameter to FreeCAD, and + runs a performance test by opening them, starting instrumentation, calling recompute(), and + then saving results. + + Intended to be run as " FreeCAD -t TestPerf --pass + + External perf profiling requires Python 3.12 or better, and a linux platform. + cProfile profiling and guppy memory information can run anywhere. + """ + + def setUp(self): + if "--pass" in sys.argv: + self.fileList = sys.argv[sys.argv.index("--pass") + 1 :] + else: + raise FileNotFoundError("Must provide filename parameter(s) via --pass") + if Part.Shape().ElementMapVersion == "": + self.tnp = "" + else: + self.tnp = ".tnp" + if Memtest: + # Use filename of first model with ".mprofile" appended for python memory use info. + self.memfile = open(self.fileList[0] + self.tnp + ".mprofile", "w", encoding="utf-8") + + def testAll(self): + if Pyprofile: + # Generate a cProfile file as a python only time profile. + profile = cProfile.Profile() + profile.enable() + try: + # This is Python 3.12 on supported platforms ( linux ) only so that if we are run under + # an external 'perf' command, we report the python data. This can be extremely useful, + # because it contains not only time consumed, but python and c++ calls that took place + # so deep analysis can be performed on the resulting file. See calling script in + # tools/profile/perftest.sh for a wrapper. + sys.activate_stack_trampoline("perf") + except AttributeError: + pass # Totally okay if we don't have that, we can use the cProfile if it's there. + + # Walk all files after the --pass. Normally one to avoid result intermingling. + for fileName in self.fileList: + doc = App.openDocument(fileName) + doc.recompute() # The heart of the performance measurement. + if Memtest: + # If guppy is available, take a heap snapshot and save it. Note that if multiple + # files are provided then their heap data sets will be appended to the same file. + dumpdata = hpy().heap() + dumpdata.stat.dump(self.memfile) + self.memfile.flush() + App.closeDocument(doc.Name) + + try: + sys.deactivate_stack_trampoline() + except AttributeError: + pass + if Pyprofile: + profile.disable() + # Use filename of first model with ".cprofile" appended for python profiling information. + profile.dump_stats(self.fileList[0] + self.tnp + ".cprofile") + if Memtest: + self.memfile.close() diff --git a/tests/src/App/DocumentObserver.cpp b/tests/src/App/DocumentObserver.cpp index 5f85866977..0b4c27ada6 100644 --- a/tests/src/App/DocumentObserver.cpp +++ b/tests/src/App/DocumentObserver.cpp @@ -15,7 +15,7 @@ using namespace App; using namespace Data; -class DocumentObserverTest: public ::testing::Test +class DISABLED_DocumentObserverTest: public ::testing::Test { protected: static void SetUpTestSuite() @@ -40,7 +40,7 @@ protected: // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes) }; -TEST_F(DocumentObserverTest, hasSubObject) +TEST_F(DISABLED_DocumentObserverTest, hasSubObject) { // Arrange @@ -83,7 +83,7 @@ TEST_F(DocumentObserverTest, hasSubObject) EXPECT_TRUE(hasSubObj); } -TEST_F(DocumentObserverTest, hasSubElement) +TEST_F(DISABLED_DocumentObserverTest, hasSubElement) { // Arrange @@ -127,7 +127,7 @@ TEST_F(DocumentObserverTest, hasSubElement) EXPECT_TRUE(hasSubEl); } -TEST_F(DocumentObserverTest, normalize) +TEST_F(DISABLED_DocumentObserverTest, normalize) { // Arrange @@ -307,7 +307,7 @@ TEST_F(DocumentObserverTest, normalize) EXPECT_TRUE(normalizeConvertIndex); } -TEST_F(DocumentObserverTest, normalized) +TEST_F(DISABLED_DocumentObserverTest, normalized) { // Arrange diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 44652a97d2..36ca66be35 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1803,6 +1803,7 @@ TEST_F(TopoShapeExpansionTest, makeElementThickSolid) std::vector shapes = {subFaces[0], subFaces[1]}; // Act TopoShape& result = cube1TS.makeElementThickSolid(cube1TS, shapes, 0.1, 1e-07); + (void)result; auto elements = elementMap(cube1TS); // Assert EXPECT_EQ(cube1TS.countSubElements("Wire"), 16); diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp index 64a1b579c1..e002cdedec 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -312,11 +312,11 @@ TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap) EXPECT_EQ(tmpShape.getElementMapSize(), 26); // For all the mappedElements ... - for (const auto& mappedElement : tmpShape.getElementMap()) { - // TODO: This no longer works, it needs a different check. We don't set MAK - // EXPECT_NE(mappedElement.name.find(OpCodes::Maker), - // -1); // ... we check that there's the "MAK" OpCode - } + // for (const auto& mappedElement : tmpShape.getElementMap()) { + // TODO: This no longer works, it needs a different check. We don't set MAK + // EXPECT_NE(mappedElement.name.find(OpCodes::Maker), + // -1); // ... we check that there's the "MAK" OpCode + //} } } diff --git a/tools/profile/perftest.sh b/tools/profile/perftest.sh new file mode 100755 index 0000000000..952d818c02 --- /dev/null +++ b/tools/profile/perftest.sh @@ -0,0 +1,36 @@ +#! /bin/bash + +# Example file to drive the performance profiling python test from the shell. +# Built to support the Topological Naming Problem fixes from early 2024; +# this will likely need tweaking for your use. + +notnp= #/bin/FreeCAD${cmd} +tnp= #>"${results}" +