From a21d7bbaf37c5bb2f5109a0fa70cd422425796d2 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 27 Feb 2024 15:12:25 -0500 Subject: [PATCH 1/2] Toposhape/Part: Bring in element methods in FeaturePart, TopoShapePy::Init and TopoShape::GetPyObject --- src/Mod/Part/App/PartFeature.cpp | 325 ++++++++++++++++++++++++++++ src/Mod/Part/App/PartFeature.h | 30 +++ src/Mod/Part/App/TopoShape.cpp | 22 +- src/Mod/Part/App/TopoShapePyImp.cpp | 88 +++++++- 4 files changed, 445 insertions(+), 20 deletions(-) diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 070037a940..24c4d64477 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -188,6 +188,331 @@ App::DocumentObject *Feature::getSubObject(const char *subname, } } +static std::vector > +getElementSource(App::DocumentObject *owner, + TopoShape shape, const Data::MappedName & name, char type) +{ + std::set> tagSet; + std::vector > ret; + ret.emplace_back(0,name); + int depth = 0; + while(1) { + Data::MappedName original; + std::vector history; + // It is possible the name does not belong to the shape, e.g. when user + // changes modeling order in PartDesign. So we try to assign the + // document hasher here in case getElementHistory() needs to de-hash + if(!shape.Hasher && owner) + shape.Hasher = owner->getDocument()->getStringHasher(); + long tag = shape.getElementHistory(ret.back().second,&original,&history); + if(!tag) + break; + auto obj = owner; + App::Document *doc = nullptr; + if(owner) { + doc = owner->getDocument(); + for(;;++depth) { + auto linked = owner->getLinkedObject(false,nullptr,false,depth); + if (linked == owner) + break; + owner = linked; + if (owner->getDocument() != doc) { + doc = owner->getDocument(); + break; + } + } + // TODO: 02/24 Toponaming project: It appears that getElementOwner is always nullptr. +// if (owner->isDerivedFrom(App::GeoFeature::getClassTypeId())) { +// auto o = static_cast(owner)->getElementOwner(ret.back().second); +// if (o) +// doc = o->getDocument(); +// } + obj = doc->getObjectByID(tag < 0 ? -tag : tag); + if(type) { + for(auto &hist : history) { + if(shape.elementType(hist)!=type) + return ret; + } + } + } + owner = 0; + if(!obj) { + // Object maybe deleted, but it is still possible to extract the + // source element name from hasher table. + shape.setShape(TopoDS_Shape()); + doc = nullptr; + }else + shape = Part::Feature::getTopoShape(obj,0,false,0,&owner); + if(type && shape.elementType(original)!=type) + break; + + if (std::abs(tag) != ret.back().first + && !tagSet.insert(std::make_pair(doc,tag)).second) { + // Because an object might be deleted, which may be a link/binder + // that points to an external object that contain element name + // using external hash table. We shall prepare for circular element + // map due to looking up in the wrong table. + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + FC_WARN("circular element mapping"); + break; + } + ret.emplace_back(tag,original); + } + return ret; +} + +std::list +Feature::getElementHistory(App::DocumentObject *feature, + const char *name, bool recursive, bool sameType) +{ + std::list ret; + TopoShape shape = getTopoShape(feature); + Data::IndexedName idx(name); + Data::MappedName element; + Data::MappedName prevElement; + if (idx) + element = shape.getMappedName(idx, true); + else if (Data::isMappedElement(name)) + element = Data::MappedName(Data::newElementName(name)); + else + element = Data::MappedName(name); + char element_type=0; + if(sameType) + element_type = shape.elementType(element); + int depth = 0; + do { + Data::MappedName original; + ret.emplace_back(feature,element); + long tag = shape.getElementHistory(element,&original,&ret.back().intermediates); + + ret.back().index = shape.getIndexedName(element); + if (!ret.back().index && prevElement) { + ret.back().index = shape.getIndexedName(prevElement); + if (ret.back().index) { + ret.back().intermediates.insert(ret.back().intermediates.begin(), element); + ret.back().element = prevElement; + } + } + if (ret.back().intermediates.size()) + prevElement = ret.back().intermediates.back(); + else + prevElement = Data::MappedName(); + + App::DocumentObject *obj = nullptr; + if (tag) { + App::Document *doc = feature->getDocument(); + for(;;++depth) { + auto linked = feature->getLinkedObject(false,nullptr,false,depth); + if (linked == feature) + break; + feature = linked; + if (feature->getDocument() != doc) { + doc = feature->getDocument(); + break; + } + } + // TODO: 02/24 Toponaming project: It appears that getElementOwner is always nullptr. +// if(feature->isDerivedFrom(App::GeoFeature::getClassTypeId())) { +// auto owner = static_cast(feature)->getElementOwner(element); +// if(owner) +// doc = owner->getDocument(); +// } + obj = doc->getObjectByID(std::abs(tag)); + } + if(!recursive) { + ret.emplace_back(obj,original); + ret.back().tag = tag; + return ret; + } + if(!obj) + break; + if(element_type) { + for(auto &hist : ret.back().intermediates) { + if(shape.elementType(hist)!=element_type) + return ret; + } + } + feature = obj; + shape = Feature::getTopoShape(feature); + element = original; + if(element_type && shape.elementType(original)!=element_type) + break; + }while(feature); + return ret; +} + +QVector +Feature::getElementFromSource(App::DocumentObject *obj, + const char *subname, + App::DocumentObject *src, + const char *srcSub, + bool single) +{ + QVector res; + if (!obj || !src) + return res; + + auto shape = getTopoShape(obj, subname, false, nullptr, nullptr, true, + /*transform = */ false); + App::DocumentObject *owner = nullptr; + auto srcShape = getTopoShape(src, srcSub, false, nullptr, &owner); + int tagChanges; + Data::MappedElement element; + Data::IndexedName checkingSubname; + std::string sub = Data::noElementName(subname); + auto checkHistory = [&](const Data::MappedName &name, size_t, long, long tag) { + if (std::abs(tag) == owner->getID()) { + if (!tagChanges) + tagChanges = 1; + } else if (tagChanges && ++tagChanges > 3) { + // Once we found the tag, trace no more than 2 addition tag changes + // to limited the search depth. + return true; + } + if (name == element.name) { + std::pair objElement; + std::size_t len = sub.size(); + checkingSubname.toString(sub); + GeoFeature::resolveElement(obj, sub.c_str(), objElement); + sub.resize(len); + if (objElement.second.size()) { + res.push_back(Data::MappedElement(Data::MappedName(objElement.first), + Data::IndexedName(objElement.second.c_str()))); + return true; + } + } + return false; + }; + + // obtain both the old and new style element name + std::pair objElement; + GeoFeature::resolveElement(src,srcSub,objElement,false); + + element.index = Data::IndexedName(objElement.second.c_str()); + if (!objElement.first.empty()) { + // Strip prefix and indexed based name at the tail of the new style element name + auto mappedName = Data::newElementName(objElement.first.c_str()); + auto mapped = Data::isMappedElement(mappedName.c_str()); + if (mapped) + element.name = Data::MappedName(mapped); + } + + // Translate the element name for datum + if (objElement.second == "Plane") + objElement.second = "Face1"; + else if (objElement.second == "Line") + objElement.second = "Edge1"; + else if (objElement.second == "Point") + objElement.second = "Vertex1"; + + // Use the old style name to obtain the shape type + auto type = TopoShape::shapeType( + Data::findElementName(objElement.second.c_str())); + // If the given shape has the same number of sub shapes as the source (e.g. + // a compound operation), then take a shortcut and assume the element index + // remains the same. But we still need to trace the shape history to + // confirm. + if (element.name && shape.countSubShapes(type) == srcShape.countSubShapes(type)) { + tagChanges = 0; + checkingSubname = element.index; + auto mapped = shape.getMappedName(element.index); + shape.traceElement(mapped, checkHistory); + if (res.size()) + return res; + } + + // Try geometry search first + auto subShape = srcShape.getSubShape(objElement.second.c_str()); + std::vector names; + shape.findSubShapesWithSharedVertex(subShape, &names); + if (names.size()) { + for (auto &name : names) { + Data::MappedElement e; + e.index = Data::IndexedName(name.c_str()); + e.name = shape.getMappedName(e.index, true); + res.append(e); + if (single) + break; + } + return res; + } + + if (!element.name) + return res; + + // No shortcut, need to search every element of the same type. This may + // result in multiple matches, e.g. a compound of array of the same + // instance. + const char *shapetype = TopoShape::shapeName(type).c_str(); + for (int i=0, count=shape.countSubShapes(type); i +Feature::getRelatedElements(App::DocumentObject *obj, const char *name, bool sameType, bool withCache) +{ + auto owner = obj; + auto shape = getTopoShape(obj,0,false,0,&owner); + QVector ret; + Data::MappedElement mapped = shape.getElementName(name); + if (!mapped.name) + return ret; + if(withCache && shape.getRelatedElementsCached(mapped.name,sameType,ret)) + return ret; + + char element_type = shape.elementType(mapped.name); + TopAbs_ShapeEnum type = TopoShape::shapeType(element_type,true); + if(type == TopAbs_SHAPE) + return ret; + + auto source = getElementSource(owner,shape,mapped.name,sameType?element_type:0); + for(auto &src : source) { + auto srcIndex = shape.getIndexedName(src.second); + if(srcIndex) { + ret.push_back(Data::MappedElement(src.second,srcIndex)); + shape.cacheRelatedElements(mapped.name,sameType,ret); + return ret; + } + } + + std::map > retMap; + + const char *shapetype = TopoShape::shapeName(type).c_str(); + std::ostringstream ss; + for(size_t i=1;i<=shape.countSubShapes(type);++i) { + Data::MappedElement related; + related.index = Data::IndexedName::fromConst(shapetype, i); + related.name = shape.getMappedName(related.index); + if (!related.name) + continue; + auto src = getElementSource(owner,shape,related.name,sameType?element_type:0); + int idx = (int)source.size()-1; + for(auto rit=src.rbegin();idx>=0&&rit!=src.rend();++rit,--idx) { + // TODO: shall we ignore source tag when comparing? It could cause + // matching unrelated element, but it does help dealing with feature + // reording in PartDesign::Body. + if(rit->second != source[idx].second) + { + ++idx; + break; + } + } + if(idx < (int)source.size()) + retMap[idx].push_back(related); + } + if(retMap.size()) + ret = retMap.begin()->second; + shape.cacheRelatedElements(mapped.name,sameType,ret); + return ret; +} + TopoDS_Shape Feature::getShape(const App::DocumentObject *obj, const char *subname, bool needSubElement, Base::Matrix4D *pmat, App::DocumentObject **powner, bool resolveLink, bool transform) diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index cfba503ca0..98a99749e1 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -35,6 +35,11 @@ class gp_Dir; class BRepBuilderAPI_MakeShape; +namespace Data +{ +struct HistoryItem; +} + namespace Part { @@ -67,6 +72,31 @@ public: std::pair getElementName( const char *name, ElementNameType type=Normal) const override; + static std::list getElementHistory(App::DocumentObject *obj, + const char *name, bool recursive=true, bool sameType=false); + + static QVector + getRelatedElements(App::DocumentObject *obj, const char *name, bool sameType=true, bool withCache=true); + + /** Obtain the element name from a feature based of the element name of its source feature + * + * @param obj: current feature + * @param subname: sub-object/element reference + * @param src: source feature + * @param srcSub: sub-object/element reference of the source + * @param single: if true, then return upon first match is found, or else + * return all matches. Multiple matches are possible for + * compound of multiple instances of the same source shape. + * + * @return Return a vector of pair of new style and old style element names. + */ + static QVector + getElementFromSource(App::DocumentObject *obj, + const char *subname, + App::DocumentObject *src, + const char *srcSub, + bool single = false); + TopLoc_Location getLocation() const; DocumentObject *getSubObject(const char *subname, PyObject **pyObj, diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 25266bf35f..6a075b7213 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -557,44 +557,44 @@ PyObject * TopoShape::getPyObject() { Base::PyObjectBase* prop = nullptr; if (_Shape.IsNull()) { - prop = new TopoShapePy(new TopoShape(_Shape)); + prop = new TopoShapePy(new TopoShape(*this)); } else { TopAbs_ShapeEnum type = _Shape.ShapeType(); switch (type) { case TopAbs_COMPOUND: - prop = new TopoShapeCompoundPy(new TopoShape(_Shape)); + prop = new TopoShapeCompoundPy(new TopoShape(*this)); break; case TopAbs_COMPSOLID: - prop = new TopoShapeCompSolidPy(new TopoShape(_Shape)); + prop = new TopoShapeCompSolidPy(new TopoShape(*this)); break; case TopAbs_SOLID: - prop = new TopoShapeSolidPy(new TopoShape(_Shape)); + prop = new TopoShapeSolidPy(new TopoShape(*this)); break; case TopAbs_SHELL: - prop = new TopoShapeShellPy(new TopoShape(_Shape)); + prop = new TopoShapeShellPy(new TopoShape(*this)); break; case TopAbs_FACE: - prop = new TopoShapeFacePy(new TopoShape(_Shape)); + prop = new TopoShapeFacePy(new TopoShape(*this)); break; case TopAbs_WIRE: - prop = new TopoShapeWirePy(new TopoShape(_Shape)); + prop = new TopoShapeWirePy(new TopoShape(*this)); break; case TopAbs_EDGE: - prop = new TopoShapeEdgePy(new TopoShape(_Shape)); + prop = new TopoShapeEdgePy(new TopoShape(*this)); break; case TopAbs_VERTEX: - prop = new TopoShapeVertexPy(new TopoShape(_Shape)); + prop = new TopoShapeVertexPy(new TopoShape(*this)); break; case TopAbs_SHAPE: default: - prop = new TopoShapePy(new TopoShape(_Shape)); + prop = new TopoShapePy(new TopoShape(*this)); break; } } - prop->setNotTracking(); + prop->setNotTracking(); // TODO: Does this still belong here? return prop; } diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index ce449d5714..2f07919093 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -67,6 +67,7 @@ #endif #include +#include #include #include #include @@ -85,6 +86,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +107,24 @@ using namespace Part; #define M_PI_2 1.57079632679489661923 /* pi/2 */ #endif +static Py_hash_t _TopoShapeHash(PyObject *self) { + if (!self) { + PyErr_SetString(PyExc_TypeError, "descriptor 'hash' of 'Part.TopoShape' object needs an argument"); + return 0; + } + if (!static_cast(self)->isValid()) { + PyErr_SetString(PyExc_ReferenceError, "This object is already deleted most likely through closing a document. This reference is no longer valid!"); + return 0; + } + return static_cast(self)->getTopoShapePtr()->getShape().HashCode(INT_MAX); +} + +struct TopoShapePyInit { + TopoShapePyInit() { + TopoShapePy::Type.tp_hash = _TopoShapeHash; + } +} _TopoShapePyInit; + // returns a string which represents the object e.g. when printed in python std::string TopoShapePy::representation() const { @@ -120,18 +140,68 @@ PyObject *TopoShapePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // return new TopoShapePy(new TopoShape); } -int TopoShapePy::PyInit(PyObject* args, PyObject*) +int TopoShapePy::PyInit(PyObject* args, PyObject* keywds) { - PyObject *pcObj=nullptr; - if (!PyArg_ParseTuple(args, "|O", &pcObj)) +#ifdef FC_USE_TNP_FIX + static char* kwlist[] = {"shape", "op", "tag", "hasher", nullptr}; + long tag = 0; + PyObject* pyHasher = nullptr; + const char* op = nullptr; + PyObject* pcObj = nullptr; + if (!PyArg_ParseTupleAndKeywords(args, + keywds, + "|OsiO!", + kwlist, + &pcObj, + &op, + &tag, + &App::StringHasherPy::Type, + &pyHasher)) { return -1; + } + auto& self = *getTopoShapePtr(); + self.Tag = tag; + if (pyHasher) { + self.Hasher = static_cast(pyHasher)->getStringHasherPtr(); + } + auto shapes = getPyShapes(pcObj); + PY_TRY + { + if (shapes.size() == 1 && !op) { + auto s = shapes.front(); + if (self.Tag) { + if ((s.Tag && self.Tag != s.Tag) + || (self.Hasher && s.getElementMapSize() && self.Hasher != s.Hasher)) { + s.reTagElementMap(self.Tag, self.Hasher); + } + else { + s.Tag = self.Tag; + s.Hasher = self.Hasher; + } + } + self = s; + } + else if (shapes.size()) { + if (!op) { + op = Part::OpCodes::Fuse; + } + self.makeElementBoolean(op, shapes); + } + } + _PY_CATCH_OCC(return (-1)) +#else + PyObject* pcObj = nullptr; + if (!PyArg_ParseTuple(args, "|O", &pcObj)) { + return -1; + } auto shapes = getPyShapes(pcObj); if (pcObj) { TopoShape shape; - PY_TRY { - if (PyObject_TypeCheck(pcObj,&TopoShapePy::Type)) { + PY_TRY + { + if (PyObject_TypeCheck(pcObj, &TopoShapePy::Type)) { shape = *static_cast(pcObj)->getTopoShapePtr(); } else { @@ -139,8 +209,8 @@ int TopoShapePy::PyInit(PyObject* args, PyObject*) bool first = true; for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { if (PyObject_TypeCheck((*it).ptr(), &(Part::GeometryPy::Type))) { - TopoDS_Shape sh = static_cast((*it).ptr())-> - getGeometryPtr()->toShape(); + TopoDS_Shape sh = + static_cast((*it).ptr())->getGeometryPtr()->toShape(); if (first) { first = false; shape.setShape(sh); @@ -152,11 +222,11 @@ int TopoShapePy::PyInit(PyObject* args, PyObject*) } } } - _PY_CATCH_OCC(return(-1)) + _PY_CATCH_OCC(return (-1)) getTopoShapePtr()->setShape(shape.getShape()); } - + #endif return 0; } From 16eecee8128d59b6969ff88c97b87d6e913a56e0 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Wed, 28 Feb 2024 15:56:45 -0500 Subject: [PATCH 2/2] Toposhape/Part:: Fix, relocate and test element methods in ComplexGeoData and TopoShape --- src/App/MappedElement.cpp | 10 +- src/App/MappedElement.h | 9 + src/Mod/Part/App/PartFeature.cpp | 884 +++++++++++------- src/Mod/Part/App/PartFeature.h | 7 +- src/Mod/Part/App/PropertyTopoShape.cpp | 14 +- src/Mod/Part/App/TopoShapePyImp.cpp | 2 + tests/src/Mod/Part/App/FeaturePartCommon.cpp | 2 +- tests/src/Mod/Part/App/PartFeature.cpp | 88 +- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 11 +- 9 files changed, 691 insertions(+), 336 deletions(-) diff --git a/src/App/MappedElement.cpp b/src/App/MappedElement.cpp index 3a0c134945..137c0e8883 100644 --- a/src/App/MappedElement.cpp +++ b/src/App/MappedElement.cpp @@ -28,6 +28,7 @@ # include #endif +#include "DocumentObject.h" #include "MappedElement.h" using namespace Data; @@ -161,4 +162,11 @@ bool ElementNameComparator::operator()(const MappedName& leftName, } } return leftName.size() < rightName.size(); -} \ No newline at end of file +} + +HistoryItem::HistoryItem(App::DocumentObject *obj, const Data::MappedName &name) + :obj(obj),tag(0),element(name) +{ + if(obj) + tag = obj->getID(); +} diff --git a/src/App/MappedElement.h b/src/App/MappedElement.h index 2e1d63e60a..8c49016686 100644 --- a/src/App/MappedElement.h +++ b/src/App/MappedElement.h @@ -99,6 +99,15 @@ struct AppExport MappedElement } }; +struct AppExport HistoryItem { + App::DocumentObject *obj; + long tag; + Data::MappedName element; + Data::IndexedName index; + std::vector intermediates; + HistoryItem(App::DocumentObject *obj, const Data::MappedName &name); +}; + struct AppExport ElementNameComparator { /** Comparison function to make topo name more stable * diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 24c4d64477..2fe5d8c637 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -29,7 +29,10 @@ # include # include # include +# include +# include # include +# include # include # include # include @@ -37,6 +40,7 @@ # include # include # include +# include # include # include # include @@ -56,6 +60,8 @@ #include #include #include +#include +#include #include #include #include @@ -114,59 +120,66 @@ PyObject *Feature::getPyObject() return Py::new_reference_to(PythonObject); } -App::DocumentObject *Feature::getSubObject(const char *subname, - PyObject **pyObj, Base::Matrix4D *pmat, bool transform, int depth) const +App::DocumentObject* Feature::getSubObject(const char* subname, + PyObject** pyObj, + Base::Matrix4D* pmat, + bool transform, + int depth) const { // having '.' inside subname means it is referencing some children object, // instead of any sub-element from ourself - if(subname && !Data::isMappedElement(subname) && strchr(subname,'.')) - return App::DocumentObject::getSubObject(subname,pyObj,pmat,transform,depth); + if (subname && !Data::isMappedElement(subname) && strchr(subname, '.')) { + return App::DocumentObject::getSubObject(subname, pyObj, pmat, transform, depth); + } Base::Matrix4D _mat; - auto &mat = pmat?*pmat:_mat; - if(transform) + auto& mat = pmat ? *pmat : _mat; + if (transform) { mat *= Placement.getValue().toMatrix(); + } - if(!pyObj) { + if (!pyObj) { // TopoShape::hasSubShape is kind of slow, let's cut outself some slack here. return const_cast(this); } try { TopoShape ts(Shape.getShape()); - bool doTransform = mat!=ts.getTransform(); - if(doTransform) + bool doTransform = mat != ts.getTransform(); + if (doTransform) { ts.setShape(ts.getShape().Located(TopLoc_Location())); - if(subname && *subname && !ts.isNull()) + } + if (subname && *subname && !ts.isNull()) { ts = ts.getSubShape(subname); - if(doTransform && !ts.isNull()) { + } + if (doTransform && !ts.isNull()) { static int sCopy = -1; - if(sCopy<0) { + if (sCopy < 0) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Part/General"); - sCopy = hGrp->GetBool("CopySubShape",false)?1:0; + "User parameter:BaseApp/Preferences/Mod/Part/General"); + sCopy = hGrp->GetBool("CopySubShape", false) ? 1 : 0; } - bool copy = sCopy?true:false; - if(!copy) { + bool copy = sCopy ? true : false; + if (!copy) { // Work around OCC bug on transforming circular edge with an // offset surface. The bug probably affect other shape type, // too. - TopExp_Explorer exp(ts.getShape(),TopAbs_EDGE); - if(exp.More()) { + TopExp_Explorer exp(ts.getShape(), TopAbs_EDGE); + if (exp.More()) { auto edge = TopoDS::Edge(exp.Current()); exp.Next(); - if(!exp.More()) { + if (!exp.More()) { BRepAdaptor_Curve curve(edge); copy = curve.GetType() == GeomAbs_Circle; } } } - ts.transformShape(mat,copy,true); + ts.transformShape(mat, copy, true); } - *pyObj = Py::new_reference_to(shape2pyshape(ts)); + *pyObj = Py::new_reference_to(shape2pyshape(ts)); return const_cast(this); } - catch(Standard_Failure &e) { + catch (Standard_Failure& e) { // FIXME: Do not handle the exception here because it leads to a flood of irrelevant and // annoying error messages. // For example: https://forum.freecad.org/viewtopic.php?f=19&t=42216 @@ -178,43 +191,52 @@ App::DocumentObject *Feature::getSubObject(const char *subname, // Avoid name mangling str << e.DynamicType()->get_type_name() << " "; - if (msg) {str << msg;} - else {str << "No OCCT Exception Message";} + if (msg) { + str << msg; + } + else { + str << "No OCCT Exception Message"; + } str << ": " << getFullName(); - if (subname) + if (subname) { str << '.' << subname; + } FC_LOG(str.str()); return nullptr; } } -static std::vector > -getElementSource(App::DocumentObject *owner, - TopoShape shape, const Data::MappedName & name, char type) +static std::vector> getElementSource(App::DocumentObject* owner, + TopoShape shape, + const Data::MappedName& name, + char type) { std::set> tagSet; - std::vector > ret; - ret.emplace_back(0,name); + std::vector> ret; + ret.emplace_back(0, name); int depth = 0; - while(1) { + while (1) { Data::MappedName original; std::vector history; // It is possible the name does not belong to the shape, e.g. when user // changes modeling order in PartDesign. So we try to assign the // document hasher here in case getElementHistory() needs to de-hash - if(!shape.Hasher && owner) + if (!shape.Hasher && owner) { shape.Hasher = owner->getDocument()->getStringHasher(); - long tag = shape.getElementHistory(ret.back().second,&original,&history); - if(!tag) + } + long tag = shape.getElementHistory(ret.back().second, &original, &history); + if (!tag) { break; + } auto obj = owner; - App::Document *doc = nullptr; - if(owner) { + App::Document* doc = nullptr; + if (owner) { doc = owner->getDocument(); - for(;;++depth) { - auto linked = owner->getLinkedObject(false,nullptr,false,depth); - if (linked == owner) + for (;; ++depth) { + auto linked = owner->getLinkedObject(false, nullptr, false, depth); + if (linked == owner) { break; + } owner = linked; if (owner->getDocument() != doc) { doc = owner->getDocument(); @@ -222,68 +244,78 @@ getElementSource(App::DocumentObject *owner, } } // TODO: 02/24 Toponaming project: It appears that getElementOwner is always nullptr. -// if (owner->isDerivedFrom(App::GeoFeature::getClassTypeId())) { -// auto o = static_cast(owner)->getElementOwner(ret.back().second); -// if (o) -// doc = o->getDocument(); -// } + // if (owner->isDerivedFrom(App::GeoFeature::getClassTypeId())) { + // auto o = + // static_cast(owner)->getElementOwner(ret.back().second); + // if (o) + // doc = o->getDocument(); + // } obj = doc->getObjectByID(tag < 0 ? -tag : tag); - if(type) { - for(auto &hist : history) { - if(shape.elementType(hist)!=type) + if (type) { + for (auto& hist : history) { + if (shape.elementType(hist) != type) { return ret; + } } } } owner = 0; - if(!obj) { + if (!obj) { // Object maybe deleted, but it is still possible to extract the // source element name from hasher table. shape.setShape(TopoDS_Shape()); doc = nullptr; - }else - shape = Part::Feature::getTopoShape(obj,0,false,0,&owner); - if(type && shape.elementType(original)!=type) + } + else { + shape = Part::Feature::getTopoShape(obj, 0, false, 0, &owner); + } + if (type && shape.elementType(original) != type) { break; + } - if (std::abs(tag) != ret.back().first - && !tagSet.insert(std::make_pair(doc,tag)).second) { + if (std::abs(tag) != ret.back().first && !tagSet.insert(std::make_pair(doc, tag)).second) { // Because an object might be deleted, which may be a link/binder // that points to an external object that contain element name // using external hash table. We shall prepare for circular element // map due to looking up in the wrong table. - if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { FC_WARN("circular element mapping"); + } break; } - ret.emplace_back(tag,original); + ret.emplace_back(tag, original); } return ret; } -std::list -Feature::getElementHistory(App::DocumentObject *feature, - const char *name, bool recursive, bool sameType) +std::list Feature::getElementHistory(App::DocumentObject* feature, + const char* name, + bool recursive, + bool sameType) { std::list ret; TopoShape shape = getTopoShape(feature); Data::IndexedName idx(name); Data::MappedName element; Data::MappedName prevElement; - if (idx) + if (idx) { element = shape.getMappedName(idx, true); - else if (Data::isMappedElement(name)) + } + else if (Data::isMappedElement(name)) { element = Data::MappedName(Data::newElementName(name)); - else + } + else { element = Data::MappedName(name); - char element_type=0; - if(sameType) + } + char element_type = 0; + if (sameType) { element_type = shape.elementType(element); + } int depth = 0; do { Data::MappedName original; - ret.emplace_back(feature,element); - long tag = shape.getElementHistory(element,&original,&ret.back().intermediates); + ret.emplace_back(feature, element); + long tag = shape.getElementHistory(element, &original, &ret.back().intermediates); ret.back().index = shape.getIndexedName(element); if (!ret.back().index && prevElement) { @@ -293,18 +325,21 @@ Feature::getElementHistory(App::DocumentObject *feature, ret.back().element = prevElement; } } - if (ret.back().intermediates.size()) + if (ret.back().intermediates.size()) { prevElement = ret.back().intermediates.back(); - else + } + else { prevElement = Data::MappedName(); + } - App::DocumentObject *obj = nullptr; + App::DocumentObject* obj = nullptr; if (tag) { - App::Document *doc = feature->getDocument(); - for(;;++depth) { - auto linked = feature->getLinkedObject(false,nullptr,false,depth); - if (linked == feature) + App::Document* doc = feature->getDocument(); + for (;; ++depth) { + auto linked = feature->getLinkedObject(false, nullptr, false, depth); + if (linked == feature) { break; + } feature = linked; if (feature->getDocument() != doc) { doc = feature->getDocument(); @@ -312,59 +347,70 @@ Feature::getElementHistory(App::DocumentObject *feature, } } // TODO: 02/24 Toponaming project: It appears that getElementOwner is always nullptr. -// if(feature->isDerivedFrom(App::GeoFeature::getClassTypeId())) { -// auto owner = static_cast(feature)->getElementOwner(element); -// if(owner) -// doc = owner->getDocument(); -// } + // if(feature->isDerivedFrom(App::GeoFeature::getClassTypeId())) { + // auto owner = + // static_cast(feature)->getElementOwner(element); + // if(owner) + // doc = owner->getDocument(); + // } obj = doc->getObjectByID(std::abs(tag)); } - if(!recursive) { - ret.emplace_back(obj,original); + if (!recursive) { + ret.emplace_back(obj, original); ret.back().tag = tag; return ret; } - if(!obj) + if (!obj) { break; - if(element_type) { - for(auto &hist : ret.back().intermediates) { - if(shape.elementType(hist)!=element_type) + } + if (element_type) { + for (auto& hist : ret.back().intermediates) { + if (shape.elementType(hist) != element_type) { return ret; + } } } feature = obj; shape = Feature::getTopoShape(feature); element = original; - if(element_type && shape.elementType(original)!=element_type) + if (element_type && shape.elementType(original) != element_type) { break; - }while(feature); + } + } while (feature); return ret; } -QVector -Feature::getElementFromSource(App::DocumentObject *obj, - const char *subname, - App::DocumentObject *src, - const char *srcSub, - bool single) +QVector Feature::getElementFromSource(App::DocumentObject* obj, + const char* subname, + App::DocumentObject* src, + const char* srcSub, + bool single) { QVector res; - if (!obj || !src) + if (!obj || !src) { return res; + } - auto shape = getTopoShape(obj, subname, false, nullptr, nullptr, true, + auto shape = getTopoShape(obj, + subname, + false, + nullptr, + nullptr, + true, /*transform = */ false); - App::DocumentObject *owner = nullptr; + App::DocumentObject* owner = nullptr; auto srcShape = getTopoShape(src, srcSub, false, nullptr, &owner); int tagChanges; Data::MappedElement element; Data::IndexedName checkingSubname; std::string sub = Data::noElementName(subname); - auto checkHistory = [&](const Data::MappedName &name, size_t, long, long tag) { + auto checkHistory = [&](const Data::MappedName& name, size_t, long, long tag) { if (std::abs(tag) == owner->getID()) { - if (!tagChanges) + if (!tagChanges) { tagChanges = 1; - } else if (tagChanges && ++tagChanges > 3) { + } + } + else if (tagChanges && ++tagChanges > 3) { // Once we found the tag, trace no more than 2 addition tag changes // to limited the search depth. return true; @@ -372,7 +418,8 @@ Feature::getElementFromSource(App::DocumentObject *obj, if (name == element.name) { std::pair objElement; std::size_t len = sub.size(); - checkingSubname.toString(sub); +// checkingSubname.toString(sub); + checkingSubname.appendToStringBuffer(sub); GeoFeature::resolveElement(obj, sub.c_str(), objElement); sub.resize(len); if (objElement.second.size()) { @@ -386,28 +433,31 @@ Feature::getElementFromSource(App::DocumentObject *obj, // obtain both the old and new style element name std::pair objElement; - GeoFeature::resolveElement(src,srcSub,objElement,false); + GeoFeature::resolveElement(src, srcSub, objElement, false); element.index = Data::IndexedName(objElement.second.c_str()); if (!objElement.first.empty()) { // Strip prefix and indexed based name at the tail of the new style element name auto mappedName = Data::newElementName(objElement.first.c_str()); auto mapped = Data::isMappedElement(mappedName.c_str()); - if (mapped) + if (mapped) { element.name = Data::MappedName(mapped); + } } // Translate the element name for datum - if (objElement.second == "Plane") + if (objElement.second == "Plane") { objElement.second = "Face1"; - else if (objElement.second == "Line") + } + else if (objElement.second == "Line") { objElement.second = "Edge1"; - else if (objElement.second == "Point") + } + else if (objElement.second == "Point") { objElement.second = "Vertex1"; + } // Use the old style name to obtain the shape type - auto type = TopoShape::shapeType( - Data::findElementName(objElement.second.c_str())); + auto type = TopoShape::shapeType(Data::findElementName(objElement.second.c_str())); // If the given shape has the same number of sub shapes as the source (e.g. // a compound operation), then take a shortcut and assume the element index // remains the same. But we still need to trace the shape history to @@ -417,8 +467,9 @@ Feature::getElementFromSource(App::DocumentObject *obj, checkingSubname = element.index; auto mapped = shape.getMappedName(element.index); shape.traceElement(mapped, checkHistory); - if (res.size()) + if (res.size()) { return res; + } } // Try geometry search first @@ -426,90 +477,108 @@ Feature::getElementFromSource(App::DocumentObject *obj, std::vector names; shape.findSubShapesWithSharedVertex(subShape, &names); if (names.size()) { - for (auto &name : names) { + for (auto& name : names) { Data::MappedElement e; e.index = Data::IndexedName(name.c_str()); e.name = shape.getMappedName(e.index, true); res.append(e); - if (single) + if (single) { break; + } } return res; } - if (!element.name) + if (!element.name) { return res; + } // No shortcut, need to search every element of the same type. This may // result in multiple matches, e.g. a compound of array of the same // instance. - const char *shapetype = TopoShape::shapeName(type).c_str(); - for (int i=0, count=shape.countSubShapes(type); i -Feature::getRelatedElements(App::DocumentObject *obj, const char *name, bool sameType, bool withCache) +QVector Feature::getRelatedElements(App::DocumentObject* obj, + const char* name, + HistoryTraceType sameType, + bool withCache) { auto owner = obj; - auto shape = getTopoShape(obj,0,false,0,&owner); + auto shape = getTopoShape(obj, nullptr, false, 0, &owner); QVector ret; Data::MappedElement mapped = shape.getElementName(name); - if (!mapped.name) + if (!mapped.name) { return ret; - if(withCache && shape.getRelatedElementsCached(mapped.name,sameType,ret)) + } + if (withCache && shape.getRelatedElementsCached(mapped.name, sameType, ret)) { return ret; + } char element_type = shape.elementType(mapped.name); - TopAbs_ShapeEnum type = TopoShape::shapeType(element_type,true); - if(type == TopAbs_SHAPE) + TopAbs_ShapeEnum type = TopoShape::shapeType(element_type, true); + if (type == TopAbs_SHAPE) { return ret; + } - auto source = getElementSource(owner,shape,mapped.name,sameType?element_type:0); - for(auto &src : source) { + auto source = + getElementSource(owner, + shape, + mapped.name, + sameType == HistoryTraceType::followTypeChange ? element_type : 0); + for (auto& src : source) { auto srcIndex = shape.getIndexedName(src.second); - if(srcIndex) { - ret.push_back(Data::MappedElement(src.second,srcIndex)); - shape.cacheRelatedElements(mapped.name,sameType,ret); + if (srcIndex) { + ret.push_back(Data::MappedElement(src.second, srcIndex)); + shape.cacheRelatedElements(mapped.name, sameType, ret); return ret; } } - std::map > retMap; + std::map> retMap; - const char *shapetype = TopoShape::shapeName(type).c_str(); + const char* shapetype = TopoShape::shapeName(type).c_str(); std::ostringstream ss; - for(size_t i=1;i<=shape.countSubShapes(type);++i) { + for (size_t i = 1; i <= shape.countSubShapes(type); ++i) { Data::MappedElement related; related.index = Data::IndexedName::fromConst(shapetype, i); related.name = shape.getMappedName(related.index); - if (!related.name) + if (!related.name) { continue; - auto src = getElementSource(owner,shape,related.name,sameType?element_type:0); - int idx = (int)source.size()-1; - for(auto rit=src.rbegin();idx>=0&&rit!=src.rend();++rit,--idx) { + } + auto src = + getElementSource(owner, + shape, + related.name, + sameType == HistoryTraceType::followTypeChange ? element_type : 0); + int idx = (int)source.size() - 1; + for (auto rit = src.rbegin(); idx >= 0 && rit != src.rend(); ++rit, --idx) { // TODO: shall we ignore source tag when comparing? It could cause // matching unrelated element, but it does help dealing with feature // reording in PartDesign::Body. - if(rit->second != source[idx].second) - { + if (rit->second != source[idx].second) { ++idx; break; } } - if(idx < (int)source.size()) + if (idx < (int)source.size()) { retMap[idx].push_back(related); + } } - if(retMap.size()) + if (retMap.size()) { ret = retMap.begin()->second; - shape.cacheRelatedElements(mapped.name,sameType,ret); + } + shape.cacheRelatedElements(mapped.name, sameType, ret); return ret; } @@ -520,180 +589,225 @@ TopoDS_Shape Feature::getShape(const App::DocumentObject *obj, const char *subna return getTopoShape(obj,subname,needSubElement,pmat,powner,resolveLink,transform,true).getShape(); } -struct ShapeCache { - - std::unordered_map ,TopoShape> > cache; - - bool inited = false; - void init() { - if(inited) - return; - inited = true; - //NOLINTBEGIN - App::GetApplication().signalDeleteDocument.connect( - std::bind(&ShapeCache::slotDeleteDocument, this, sp::_1)); - App::GetApplication().signalDeletedObject.connect( - std::bind(&ShapeCache::slotClear, this, sp::_1)); - App::GetApplication().signalChangedObject.connect( - std::bind(&ShapeCache::slotChanged, this, sp::_1,sp::_2)); - //NOLINTEND - } - - void slotDeleteDocument(const App::Document &doc) { - cache.erase(&doc); - } - - void slotChanged(const App::DocumentObject &obj, const App::Property &prop) { - const char *propName = prop.getName(); - if(!App::Property::isValidName(propName)) - return; - if(strcmp(propName,"Shape")==0 - || strcmp(propName,"Group")==0 - || strstr(propName,"Touched")) - slotClear(obj); - } - - void slotClear(const App::DocumentObject &obj) { - auto it = cache.find(obj.getDocument()); - if(it==cache.end()) - return; - auto &map = it->second; - for(auto it2=map.lower_bound(std::make_pair(&obj,std::string())); - it2!=map.end() && it2->first.first==&obj;) - { - it2 = map.erase(it2); - } - } - - bool getShape(const App::DocumentObject *obj, TopoShape &shape, const char *subname=nullptr) { - init(); - auto &entry = cache[obj->getDocument()]; - if(!subname) subname = ""; - auto it = entry.find(std::make_pair(obj,std::string(subname))); - if(it!=entry.end()) { - shape = it->second; - return !shape.isNull(); - } - return false; - } - - void setShape(const App::DocumentObject *obj, const TopoShape &shape, const char *subname=nullptr) { - init(); - if(!subname) subname = ""; - cache[obj->getDocument()][std::make_pair(obj,std::string(subname))] = shape; - } -}; -static ShapeCache _ShapeCache; +// Toponaming project March 2024: This method should be going away when we get to the python layer. void Feature::clearShapeCache() { - _ShapeCache.cache.clear(); +// _ShapeCache.cache.clear(); } -static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subname, - bool needSubElement, Base::Matrix4D *pmat, App::DocumentObject **powner, - bool resolveLink, bool noElementMap, std::vector &linkStack) +static TopoShape _getTopoShape(const App::DocumentObject* obj, + const char* subname, + bool needSubElement, + Base::Matrix4D* pmat, + App::DocumentObject** powner, + bool resolveLink, + bool noElementMap, + const std::set hiddens, + const App::DocumentObject* lastLink) { - (void) noElementMap; - TopoShape shape; - if(!obj) + if (!obj) { return shape; + } - PyObject *pyobj = nullptr; + PyObject* pyobj = nullptr; Base::Matrix4D mat; - if(powner) *powner = nullptr; + if (powner) { + *powner = nullptr; + } std::string _subname; auto subelement = Data::findElementName(subname); - if(!needSubElement && subname) { + if (!needSubElement && subname) { // strip out element name if not needed - if(subelement && *subelement) { - _subname = std::string(subname,subelement); + if (subelement && *subelement) { + _subname = std::string(subname, subelement); subname = _subname.c_str(); } } - if(_ShapeCache.getShape(obj,shape,subname)) { + auto canCache = [&](const App::DocumentObject* o) { + return !lastLink || (hiddens.empty() && !App::GeoFeatureGroupExtension::isNonGeoGroup(o)); + }; + + if (canCache(obj) && PropertyShapeCache::getShape(obj, shape, subname)) { + if (noElementMap) { + shape.resetElementMap(); + shape.Tag = 0; + if ( shape.Hasher ) { + shape.Hasher->clear(); + } + } } - App::DocumentObject *linked = nullptr; - App::DocumentObject *owner = nullptr; + App::DocumentObject* linked = nullptr; + App::DocumentObject* owner = nullptr; Base::Matrix4D linkMat; + App::StringHasherRef hasher; + long tag; { Base::PyGILStateLocker lock; - owner = obj->getSubObject(subname,shape.isNull()?&pyobj:nullptr,&mat,false); - if(!owner) + owner = obj->getSubObject(subname, shape.isNull() ? &pyobj : nullptr, &mat, false); + if (!owner) { return shape; - linked = owner->getLinkedObject(true,&linkMat,false); - if(pmat) { - if(resolveLink && obj!=owner) - *pmat = mat * linkMat; - else - *pmat = mat; } - if(!linked) + tag = owner->getID(); + hasher = owner->getDocument()->getStringHasher(); + linked = owner->getLinkedObject(true, &linkMat, false); + if (pmat) { + if (resolveLink && obj != owner) { + *pmat = mat * linkMat; + } + else { + *pmat = mat; + } + } + if (!linked) { linked = owner; - if(powner) - *powner = resolveLink?linked:owner; + } + if (powner) { + *powner = resolveLink ? linked : owner; + } - if(!shape.isNull()) + if (!shape.isNull()) { return shape; + } - if(pyobj && PyObject_TypeCheck(pyobj,&TopoShapePy::Type)) { + if (pyobj && PyObject_TypeCheck(pyobj, &TopoShapePy::Type)) { shape = *static_cast(pyobj)->getTopoShapePtr(); - if(!shape.isNull()) { - if(obj->getDocument() != linked->getDocument()) - _ShapeCache.setShape(obj,shape,subname); + if (!shape.isNull()) { + if (canCache(obj)) { + if (obj->getDocument() != linked->getDocument() + || mat.hasScale() != Base::ScaleType::NoScaling + || (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) { + PropertyShapeCache::setShape(obj, shape, subname); + } + } + if (noElementMap) { + shape.resetElementMap(); + shape.Tag = 0; + if ( shape.Hasher ) { + shape.Hasher->clear(); + } + } Py_DECREF(pyobj); return shape; } } + else { + if (linked->isDerivedFrom(App::Line::getClassTypeId())) { + static TopoDS_Shape _shape; + if (_shape.IsNull()) { + BRepBuilderAPI_MakeEdge builder(gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0))); + _shape = builder.Shape(); + _shape.Infinite(Standard_True); + } + shape = TopoShape(tag, hasher, _shape); + } + else if (linked->isDerivedFrom(App::Plane::getClassTypeId())) { + static TopoDS_Shape _shape; + if (_shape.IsNull()) { + BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1))); + _shape = builder.Shape(); + _shape.Infinite(Standard_True); + } + shape = TopoShape(tag, hasher, _shape); + } + else if (linked->isDerivedFrom(App::Placement::getClassTypeId())) { + auto element = Data::findElementName(subname); + if (element) { + if (boost::iequals("x", element) || boost::iequals("x-axis", element) + || boost::iequals("y", element) || boost::iequals("y-axis", element) + || boost::iequals("z", element) || boost::iequals("z-axis", element)) { + static TopoDS_Shape _shape; + if (_shape.IsNull()) { + BRepBuilderAPI_MakeEdge builder( + gp_Lin(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1))); + _shape = builder.Shape(); + _shape.Infinite(Standard_True); + } + shape = TopoShape(tag, hasher, _shape); + } + else if (boost::iequals("o", element) || boost::iequals("origin", element)) { + static TopoDS_Shape _shape; + if (_shape.IsNull()) { + BRepBuilderAPI_MakeVertex builder(gp_Pnt(0, 0, 0)); + _shape = builder.Shape(); + _shape.Infinite(Standard_True); + } + shape = TopoShape(tag, hasher, _shape); + } + } + if (shape.isNull()) { + static TopoDS_Shape _shape; + if (_shape.IsNull()) { + BRepBuilderAPI_MakeFace builder(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1))); + _shape = builder.Shape(); + _shape.Infinite(Standard_True); + } + shape = TopoShape(tag, hasher, _shape); + } + } + if (!shape.isNull()) { + shape.transformShape(mat * linkMat, false, true); + return shape; + } + } Py_XDECREF(pyobj); } // nothing can be done if there is sub-element references - if(needSubElement && subelement && *subelement) + if (needSubElement && subelement && *subelement) { return shape; + } - bool scaled = false; - if(obj!=owner) { - if(_ShapeCache.getShape(owner,shape)) { - auto scaled = shape.transformShape(mat,false,true); - if(owner->getDocument()!=obj->getDocument()) { - // shape.reTagElementMap(obj->getID(),obj->getDocument()->getStringHasher()); - _ShapeCache.setShape(obj,shape,subname); - } else if(scaled) - _ShapeCache.setShape(obj,shape,subname); + if (obj != owner) { + if (canCache(owner) && PropertyShapeCache::getShape(owner, shape)) { + bool scaled = shape.transformShape(mat, false, true); + if (owner->getDocument() != obj->getDocument()) { + shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher()); + PropertyShapeCache::setShape(obj, shape, subname); + } + else if (scaled + || (linked != owner && linkMat.hasScale() != Base::ScaleType::NoScaling)) { + PropertyShapeCache::setShape(obj, shape, subname); + } } - if(!shape.isNull()) { + if (!shape.isNull()) { + if (noElementMap) { + shape.resetElementMap(); + shape.Tag = 0; + if ( shape.Hasher) { + shape.Hasher->clear(); + } + } return shape; } } + bool cacheable = true; + auto link = owner->getExtensionByType(true); - if(owner!=linked - && (!link || (!link->_ChildCache.getSize() - && link->getSubElements().size()<=1))) - { + if (owner != linked + && (!link || (!link->_ChildCache.getSize() && link->getSubElements().size() <= 1))) { // if there is a linked object, and there is no child cache (which is used // for special handling of plain group), obtain shape from the linked object - shape = Feature::getTopoShape(linked,nullptr,false,nullptr,nullptr,false,false); - if(shape.isNull()) + shape = Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false); + if (shape.isNull()) { return shape; - if(owner==obj) - shape.transformShape(mat*linkMat,false,true); - else - shape.transformShape(linkMat,false,true); - - } else { - - if(link || owner->getExtensionByType(true)) - linkStack.push_back(owner); - + } + if (owner == obj) { + shape.transformShape(mat * linkMat, false, true); + } + else { + shape.transformShape(linkMat, false, true); + } + shape.reTagElementMap(tag, hasher); + } + else { // Construct a compound of sub objects std::vector shapes; @@ -703,107 +817,212 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna TopoShape baseShape; Base::Matrix4D baseMat; std::string op; - if(link && link->getElementCountValue()) { - linked = link->getTrueLinkedObject(false,&baseMat); - if(linked && linked!=owner) { - baseShape = Feature::getTopoShape(linked,nullptr,false,nullptr,nullptr,false,false); + if (link && link->getElementCountValue()) { + linked = link->getTrueLinkedObject(false, &baseMat); + if (linked && linked != owner) { + baseShape = + Feature::getTopoShape(linked, nullptr, false, nullptr, nullptr, false, false); + if (!link->getShowElementValue()) { + baseShape.reTagElementMap(owner->getID(), + owner->getDocument()->getStringHasher()); + } } } - for(auto &sub : owner->getSubObjects()) { - if(sub.empty()) continue; + for (auto& sub : owner->getSubObjects()) { + if (sub.empty()) { + continue; + } int visible; std::string childName; - App::DocumentObject *parent=nullptr; + App::DocumentObject* parent = nullptr; Base::Matrix4D mat = baseMat; - App::DocumentObject *subObj=nullptr; - if(sub.find('.')==std::string::npos) + App::DocumentObject* subObj = nullptr; + if (sub.find('.') == std::string::npos) { visible = 1; - else { - subObj = owner->resolve(sub.c_str(), &parent, &childName,nullptr,nullptr,&mat,false); - if(!parent || !subObj) - continue; - if(!linkStack.empty() - && parent->getExtensionByType(true,false)) - { - visible = linkStack.back()->isElementVisible(childName.c_str()); - }else - visible = parent->isElementVisible(childName.c_str()); } - if(visible==0) - continue; - TopoShape shape; - if(!subObj || baseShape.isNull()) { - shape = _getTopoShape(owner,sub.c_str(),true,nullptr,&subObj,false,false,linkStack); - if(shape.isNull()) + else { + subObj = + owner->resolve(sub.c_str(), &parent, &childName, nullptr, nullptr, &mat, false); + if (!parent || !subObj) { continue; - if(visible<0 && subObj && !subObj->Visibility.getValue()) - continue; - }else{ - if(link && !link->getShowElementValue()) - shape = baseShape.makeTransform(mat,(Data::POSTFIX_INDEX + childName).c_str()); + } + if (lastLink && App::GeoFeatureGroupExtension::isNonGeoGroup(parent)) { + visible = lastLink->isElementVisible(childName.c_str()); + } else { - shape = baseShape.makeTransform(mat); + visible = parent->isElementVisible(childName.c_str()); + } + } + if (visible == 0) { + continue; + } + + std::set nextHiddens = hiddens; + const App::DocumentObject* nextLink = lastLink; + // Toponaming project March 2024: This appears to be a non toponaming feature: +// if (!checkLinkVisibility(nextHiddens, true, nextLink, owner, sub.c_str())) { +// cacheable = false; +// continue; +// } + + TopoShape shape; + + bool doGetShape = (!subObj || baseShape.isNull()); + if (!doGetShape) { + auto type = mat.hasScale(); + if (type != Base::ScaleType::NoScaling && type != Base::ScaleType::Uniform) { + doGetShape = true; + } + } + if (doGetShape) { + shape = _getTopoShape(owner, + sub.c_str(), + true, + 0, + &subObj, + false, + false, + nextHiddens, + nextLink); + if (shape.isNull()) { + continue; + } + if (visible < 0 && subObj && !subObj->Visibility.getValue()) { + continue; + } + } + else { + if (link && !link->getShowElementValue()) { + shape = + baseShape.makeElementTransform(mat, + (Data::POSTFIX_INDEX + childName).c_str()); + } + else { + shape = baseShape.makeElementTransform(mat); + shape.reTagElementMap(subObj->getID(), + subObj->getDocument()->getStringHasher()); } } shapes.push_back(shape); } - if(!linkStack.empty() && linkStack.back()==owner) - linkStack.pop_back(); - - if(shapes.empty()) + if (shapes.empty()) { return shape; - - shape.makeCompound(shapes); + } + shape.Tag = tag; + shape.Hasher = hasher; + shape.makeElementCompound(shapes); } - _ShapeCache.setShape(owner,shape); + if (cacheable && canCache(owner)) { + PropertyShapeCache::setShape(owner, shape); + } - if(owner!=obj) { - scaled = shape.transformShape(mat,false,true); - if(owner->getDocument()!=obj->getDocument()) { - _ShapeCache.setShape(obj,shape,subname); - }else if(scaled) - _ShapeCache.setShape(obj,shape,subname); + if (owner != obj) { + bool scaled = shape.transformShape(mat, false, true); + if (owner->getDocument() != obj->getDocument()) { + shape.reTagElementMap(obj->getID(), obj->getDocument()->getStringHasher()); + scaled = true; // force cache + } + if (canCache(obj) && scaled) { + PropertyShapeCache::setShape(obj, shape, subname); + } + } + if (noElementMap) { + shape.resetElementMap(); + shape.Tag = 0; + if ( shape.Hasher ) { + shape.Hasher->clear(); + } } return shape; } -TopoShape Feature::getTopoShape(const App::DocumentObject *obj, const char *subname, - bool needSubElement, Base::Matrix4D *pmat, App::DocumentObject **powner, - bool resolveLink, bool transform, bool noElementMap) +TopoShape Feature::getTopoShape(const App::DocumentObject* obj, + const char* subname, + bool needSubElement, + Base::Matrix4D* pmat, + App::DocumentObject** powner, + bool resolveLink, + bool transform, + bool noElementMap) { - if(!obj || !obj->isAttachedToDocument()) - return {}; + if (!obj || !obj->getNameInDocument()) { + return TopoShape(); + } - std::vector linkStack; + const App::DocumentObject* lastLink = 0; + std::set hiddens; + // Toponaming project March 2024: This appears to be a non toponaming feature: +// if (!checkLinkVisibility(hiddens, false, lastLink, obj, subname)) { +// return TopoShape(); +// } // NOTE! _getTopoShape() always return shape without top level // transformation for easy shape caching, i.e. with `transform` set // to false. So we manually apply the top level transform if asked. + if (needSubElement && (!pmat || *pmat == Base::Matrix4D()) + && obj->isDerivedFrom(Part::Feature::getClassTypeId()) + && !obj->hasExtension(App::LinkBaseExtension::getExtensionClassTypeId())) { + // Some OCC shape making is very sensitive to shape transformation. So + // check here if a direct sub shape is required, and bypass all extra + // processing here. + if (subname && *subname && Data::findElementName(subname) == subname) { + TopoShape ts = static_cast(obj)->Shape.getShape(); + if (!transform) { + ts.setShape(ts.getShape().Located(TopLoc_Location()), false); + } + if (noElementMap) { + ts = ts.getSubShape(subname, true); + } + else { + ts = ts.getSubTopoShape(subname, true); + } + if (!ts.isNull()) { + if (powner) { + *powner = const_cast(obj); + } + if (pmat && transform) { + *pmat = static_cast(obj)->Placement.getValue().toMatrix(); + } + return ts; + } + } + } + Base::Matrix4D mat; - auto shape = _getTopoShape(obj, subname, needSubElement, &mat, - powner, resolveLink, noElementMap, linkStack); + auto shape = _getTopoShape(obj, + subname, + needSubElement, + &mat, + powner, + resolveLink, + noElementMap, + hiddens, + lastLink); Base::Matrix4D topMat; - if(pmat || transform) { + if (pmat || transform) { // Obtain top level transformation - if(pmat) + if (pmat) { topMat = *pmat; - if(transform) - obj->getSubObject(nullptr,nullptr,&topMat); + } + if (transform) { + obj->getSubObject(nullptr, nullptr, &topMat); + } // Apply the top level transformation - if(!shape.isNull()) - shape.transformShape(topMat,false,true); + if (!shape.isNull()) { + shape.transformShape(topMat, false, true); + } - if(pmat) + if (pmat) { *pmat = topMat * mat; + } } return shape; - } App::DocumentObject *Feature::getShapeOwner(const App::DocumentObject *obj, const char *subname) @@ -965,6 +1184,31 @@ const App::PropertyComplexGeoData* Feature::getPropertyOfGeometry() const return &Shape; } +bool Feature::isElementMappingDisabled(App::PropertyContainer* container) +{ +#ifdef FC_USE_TNP_FIX + return false; +#else + return true; +#endif + // TODO: March 2024 consider if any of this RT branch logic makes sense: +// if (!container) { +// return false; +// } +// auto prop = propDisableMapping(container, /*forced*/ false); +// if (prop && prop->getValue()) { +// return true; +// } +// if (auto obj = Base::freecad_dynamic_cast(container)) { +// if (auto doc = obj->getDocument()) { +// if (auto prop = propDisableMapping(doc, /*forced*/ false)) { +// return prop->getValue(); +// } +// } +// } +// return false; +} + // --------------------------------------------------------- PROPERTY_SOURCE(Part::FilletBase, Part::Feature) diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 98a99749e1..31975fdc08 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -76,7 +76,10 @@ public: const char *name, bool recursive=true, bool sameType=false); static QVector - getRelatedElements(App::DocumentObject *obj, const char *name, bool sameType=true, bool withCache=true); + getRelatedElements(App::DocumentObject* obj, + const char* name, + HistoryTraceType sameType = HistoryTraceType::followTypeChange, + bool withCache = true); /** Obtain the element name from a feature based of the element name of its source feature * @@ -143,6 +146,8 @@ public: static Feature* create(const TopoShape& shape, const char* name = nullptr, App::Document* document = nullptr); + static bool isElementMappingDisabled(App::PropertyContainer *container); + protected: /// recompute only this object App::DocumentObjectExecReturn *recompute() override; diff --git a/src/Mod/Part/App/PropertyTopoShape.cpp b/src/Mod/Part/App/PropertyTopoShape.cpp index 02d7d45426..a7e7786199 100644 --- a/src/Mod/Part/App/PropertyTopoShape.cpp +++ b/src/Mod/Part/App/PropertyTopoShape.cpp @@ -50,6 +50,7 @@ #include "PartPyCXX.h" #include "PropertyTopoShape.h" #include "TopoShapePy.h" +#include "PartFeature.h" FC_LOG_LEVEL_INIT("App", true, true) @@ -103,14 +104,15 @@ TopoShape PropertyPartShape::getShape() const { _Shape.initCache(-1); auto res = _Shape; - // March, 2024 Toponaming project: There was originally an unused feature to disable elementMapping - // that has not been kept: + // March, 2024 Toponaming project: There was originally an unused feature to disable + // elementMapping that has not been kept: // if (Feature::isElementMappingDisabled(getContainer())) - if ( false ) - res.Tag = -1; - else if (!res.Tag) { - if (auto parent = Base::freecad_dynamic_cast(getContainer())) + // res.Tag = -1; + // else if (!res.Tag) { + if (!res.Tag) { + if (auto parent = Base::freecad_dynamic_cast(getContainer())) { res.Tag = parent->getID(); + } } return res; } diff --git a/src/Mod/Part/App/TopoShapePyImp.cpp b/src/Mod/Part/App/TopoShapePyImp.cpp index 2f07919093..0b768ebce6 100644 --- a/src/Mod/Part/App/TopoShapePyImp.cpp +++ b/src/Mod/Part/App/TopoShapePyImp.cpp @@ -107,6 +107,7 @@ using namespace Part; #define M_PI_2 1.57079632679489661923 /* pi/2 */ #endif +#ifdef FC_USE_TNP_FIX static Py_hash_t _TopoShapeHash(PyObject *self) { if (!self) { PyErr_SetString(PyExc_TypeError, "descriptor 'hash' of 'Part.TopoShape' object needs an argument"); @@ -124,6 +125,7 @@ struct TopoShapePyInit { TopoShapePy::Type.tp_hash = _TopoShapeHash; } } _TopoShapePyInit; +#endif // returns a string which represents the object e.g. when printed in python std::string TopoShapePy::representation() const diff --git a/tests/src/Mod/Part/App/FeaturePartCommon.cpp b/tests/src/Mod/Part/App/FeaturePartCommon.cpp index dc030fb5e7..8a9f5e02aa 100644 --- a/tests/src/Mod/Part/App/FeaturePartCommon.cpp +++ b/tests/src/Mod/Part/App/FeaturePartCommon.cpp @@ -218,6 +218,6 @@ TEST_F(FeaturePartCommonTest, testMapping) #ifndef FC_USE_TNP_FIX EXPECT_EQ(ts1.getElementMap().size(), 0); #else - EXPECT_EQ(ts1.getElementMap().size(), 26); // TODO: This should be 26. + EXPECT_EQ(ts1.getElementMap().size(), 26); #endif } diff --git a/tests/src/Mod/Part/App/PartFeature.cpp b/tests/src/Mod/Part/App/PartFeature.cpp index 0617f4588d..3d4cd037bd 100644 --- a/tests/src/Mod/Part/App/PartFeature.cpp +++ b/tests/src/Mod/Part/App/PartFeature.cpp @@ -6,6 +6,7 @@ #include #include #include "PartTestHelpers.h" +#include "App/MappedElement.h" using namespace Part; using namespace PartTestHelpers; @@ -54,7 +55,7 @@ TEST_F(FeaturePartTest, testGetElementName) #ifndef FC_USE_TNP_FIX EXPECT_EQ(ts.getElementMap().size(), 0); #else - EXPECT_EQ(ts.getElementMap().size(), 0); // TODO: Value and code TBD + EXPECT_EQ(ts.getElementMap().size(), 26); #endif // TBD } @@ -127,3 +128,88 @@ TEST_F(FeaturePartTest, create) // will have only 1 feature EXPECT_EQ(otherDoc->getObjects().size(), 1); } + +TEST_F(FeaturePartTest, getElementHistory) +{ + // Arrange + const char* name = "Part__Box"; + const char* name2 = "Edge2"; // Edge, Vertex, or Face. will work here. + // Act + auto result = Feature::getElementHistory(_boxes[0], name2, true, false); + Data::HistoryItem histItem = result.front(); + // Assert + EXPECT_EQ(result.size(), 1); + EXPECT_NE(histItem.tag, 0); // Make sure we have one. It will vary. + EXPECT_EQ(histItem.index.getIndex(), 2); + EXPECT_STREQ(histItem.index.getType(), "Edge"); + EXPECT_STREQ(histItem.element.toString().c_str(), name2); + EXPECT_EQ(histItem.obj, _boxes[0]); +} + +TEST_F(FeaturePartTest, getRelatedElements) +{ + // Arrange + _common->Base.setValue(_boxes[0]); + _common->Tool.setValue(_boxes[1]); + // Act + _common->execute(); + auto label1 = _common->Label.getValue(); + auto label2 = _boxes[1]->Label.getValue(); + const TopoShape& ts = _common->Shape.getShape(); + auto result = Feature::getRelatedElements(_doc->getObject(label1), + "Edge2", + HistoryTraceType::followTypeChange, + true); + auto result2 = Feature::getRelatedElements(_doc->getObject(label2), + "Edge1", + HistoryTraceType::followTypeChange, + true); + // Assert +#ifdef FC_USE_TNP_FIX + EXPECT_EQ(result.size(), 1); // Found the one. + EXPECT_EQ(result2.size(), 0); // No element map, so no related elements + // The results are always going to vary, so we can't test for specific values: + // EXPECT_STREQ(result.front().name.toString().c_str(),"Edge3;:M;CMN;:H38d:7,E"); +#else + EXPECT_EQ(result.size(), 0); +#endif +} + +// Note that this test is pretty trivial and useless .. but the method in question is never +// called in the codebase. +TEST_F(FeaturePartTest, getElementFromSource) +{ + // Arrange + _common->Base.setValue(_boxes[0]); + _common->Tool.setValue(_boxes[1]); + App::DocumentObject sourceObject; + // const char *sourceSubElement; + // Act + _common->execute(); + auto label1 = _common->Label.getValue(); + auto label2 = _boxes[1]->Label.getValue(); + const TopoShape& ts = _common->Shape.getShape(); + auto element = Feature::getElementFromSource(_common, + "Part__Box001", // "Edge1", + _boxes[0], + "Face1", // "Edge1", + false); + // Assert + EXPECT_EQ(element.size(), 0); +} + +TEST_F(FeaturePartTest, getSubObject) +{ + // Arrange + _common->Base.setValue(_boxes[0]); + _common->Tool.setValue(_boxes[1]); + App::DocumentObject sourceObject; + const char* sourceSubElement; + PyObject* pyObj; + // Act + _common->execute(); + auto result = _boxes[1]->getSubObject("Face5", &pyObj, nullptr, false, 10); + // Assert + ASSERT_NE(result, nullptr); + EXPECT_STREQ(result->getNameInDocument(), "Part__Box001"); +} diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 09d51b945e..5d765d953e 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2028,17 +2028,16 @@ TEST_F(TopoShapeExpansionTest, makeElementSlice) { "Face1;SLC;:H1:4,F;:G2;SLC;:H1:8,V;SLC;:H1:4,V;MAK;:H1:4,V", "Face1;SLC;:H1:4,F;:G3;SLC;:H1:8,V;SLC;:H1:4,V;MAK;:H1:4,V", - // TODO: Prove that this difference is not a problem. - // The next element varies according to platform / OCCT version and thus can't be - // absolutely tested. - // "Face1;SLC;:H1:4,F;:G4;SLC;:H1:8,V;D1;:H1:3,V;SLC;:H1:4,V;MAK;:H1:4,V", + // MacOSX difference: + // "Face1;SLC;:H1:4,F;:G4;SLC;:H1:8,V;D25fd;:H1:6,V;SLC;:H1:4,V;MAK;:H1:4,V", + // "Face1;SLC;:H1:4,F;:G4;SLC;:H1:8,V;D1;:H1:3,V;SLC;:H1:4,V;MAK;:H1:4,V", "Face1;SLC;:H1:4,F;:G4;SLC;:H1:8,V;SLC;:H1:4,V;MAK;:H1:4,V", "Face1;SLC;:H1:4,F;:G5;SLC;:H1:8,E;SLC;:H1:4,E;MAK;:H1:4,E", "Face1;SLC;:H1:4,F;:G6;SLC;:H1:8,E;SLC;:H1:4,E;MAK;:H1:4,E", "Face1;SLC;:H1:4,F;:G7;SLC;:H1:8,E;SLC;:H1:4,E;MAK;:H1:4,E", "Face1;SLC;:H1:4,F;:G8;SLC;:H1:8,E;SLC;:H1:4,E;MAK;:H1:4,E", - })); // Changed with PR#12471. Probably will change again after importing - // other TopoNaming logics + })); // Changed with PR#12471. Probably will change again after + // importing other TopoNaming logics } TEST_F(TopoShapeExpansionTest, makeElementSlices)