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; }