From 1d9fcfea9a151289fb73a88bf585f3ff0131e5b4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 27 Feb 2024 10:09:32 -0500 Subject: [PATCH] Toponaming/Part: trasnfer in getElementName --- src/App/GeoFeature.cpp | 38 +++- src/App/GeoFeature.h | 6 +- src/App/IndexedName.h | 12 ++ src/App/MappedName.h | 22 +++ src/Mod/Part/App/PartFeature.cpp | 232 ++++++++++++++++++++++++ src/Mod/Part/App/PartFeature.h | 33 ++++ src/Mod/Part/App/TopoShape.cpp | 14 ++ src/Mod/Part/App/TopoShape.h | 8 +- src/Mod/Part/App/TopoShapeExpansion.cpp | 81 +++++++++ 9 files changed, 440 insertions(+), 6 deletions(-) diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index aeb67d8c70..c6a5b8762c 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -25,6 +25,7 @@ #include +#include "ComplexGeoData.h" #include "GeoFeature.h" #include "GeoFeatureGroupExtension.h" #include "ElementNamingUtils.h" @@ -78,17 +79,46 @@ PyObject* GeoFeature::getPyObject() return Py::new_reference_to(PythonObject); } - -std::pair GeoFeature::getElementName( - const char *name, ElementNameType type) const +std::pair +GeoFeature::getElementName(const char *name, ElementNameType type) const { (void)type; std::pair ret; if(!name) return ret; + auto prop = getPropertyOfGeometry(); + if(!prop) return std::make_pair("", name); - ret.second = name; + auto geo = prop->getComplexData(); + if(!geo) return std::make_pair("", name); + + return _getElementName(name, geo->getElementName(name)); +} + +std::pair +GeoFeature::_getElementName(const char *name, const Data::MappedElement &mapped) const +{ + std::pair ret; + if (mapped.index && mapped.name) { + std::ostringstream ss; + ss << Data::ComplexGeoData::elementMapPrefix() + << mapped.name << '.' << mapped.index; + ret.first = ss.str(); + mapped.index.toString(ret.second); + } else if (mapped.name) { +// FC_TRACE("element mapped name " << name << " not found in " << getFullName()); + ret.first = name; + const char *dot = strrchr(name,'.'); + if(dot) { + // deliberately mangle the old style element name to signal a + // missing reference + ret.second = Data::MISSING_PREFIX; + ret.second += dot+1; + } + } else { + mapped.index.toString(ret.second); + } return ret; } diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index 9f71ee58c7..e43ba869e1 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -26,7 +26,7 @@ #include "DocumentObject.h" #include "PropertyGeo.h" - +#include "MappedElement.h" namespace App { @@ -120,6 +120,10 @@ public: * @return Base::Placement The transformation from the global reference coordinate system */ Base::Placement globalPlacement() const; + +protected: + std::pair _getElementName(const char* name, + const Data::MappedElement& mapped) const; }; } //namespace App diff --git a/src/App/IndexedName.h b/src/App/IndexedName.h index 85282b9545..4d5d470e9b 100644 --- a/src/App/IndexedName.h +++ b/src/App/IndexedName.h @@ -147,6 +147,18 @@ public: return result; } + const char * toString(std::string & s) const + { + // Note! s is not cleared on purpose. + std::size_t offset = s.size(); + s += this->type; + if (this->index > 0) + s += std::to_string(this->index); + return s.c_str() + offset; + } + + + /// An indexedName is represented as the simple concatenation of the name and its index, e.g. /// "EDGE1" or "FACE42". friend std::ostream & operator<<(std::ostream & stream, const IndexedName & indexedName) diff --git a/src/App/MappedName.h b/src/App/MappedName.h index 355533e1bd..0d8c30d6d6 100644 --- a/src/App/MappedName.h +++ b/src/App/MappedName.h @@ -474,6 +474,28 @@ public: return appendToBuffer(res, startPosition, len); } + const char * toString(std::string &s, int from=0, int len=-1) const + { + std::size_t offset = s.size(); + int count = this->size(); + if (from < 0) + from = 0; + else if (from >= count) + return s.c_str()+s.size(); + if (len < 0 || len > count - from) + len = count - from; + s.reserve(s.size() + len); + if (from < this->data.size()) { + count = this->data.size() - from; + if (len < count) + count = len; + s.append(this->data.constData()+from, count); + len -= count; + } + s.append(this->postfix.constData(), len); + return s.c_str() + offset; + } + /// Given a (possibly non-empty) std::string buffer, append this instance to it, starting at a /// specified position, and continuing for a specified number of bytes. /// diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index c830b31ac3..98893da657 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -757,3 +757,235 @@ bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& seco } } + +/** + * Override getElementName to support the Export type. Other calls are passed to the original + * method + * @param name The name to search for, or if non existent, name of current Feature is returned + * @param type An element type name. + * @return The element name located, of + */ +std::pair Feature::getElementName(const char* name, + ElementNameType type) const +{ + if (type != ElementNameType::Export) { + return App::GeoFeature::getElementName(name, type); + } + + // This function is overridden to provide higher level shape topo names that + // are generated on demand, e.g. Wire, Shell, Solid, etc. + + auto prop = Base::freecad_dynamic_cast(getPropertyOfGeometry()); + if (!prop) { + return App::GeoFeature::getElementName(name, type); + } + + TopoShape shape = prop->getShape(); + Data::MappedElement mapped = shape.getElementName(name); + auto res = shape.shapeTypeAndIndex(mapped.index); + static const int MinLowerTopoNames = 3; + static const int MaxLowerTopoNames = 10; + if (res.second && !mapped.name) { + // Here means valid index name, but no mapped name, check to see if + // we shall generate the high level topo name. + // + // The general idea of the algorithm is to find the minimum number of + // lower elements that can identify the given higher element, and + // combine their names to generate the name for the higher element. + // + // In theory, all it takes to find one lower element that only appear + // in the given higher element. To make the algorithm more robust + // against model changes, we shall take minimum MinLowerTopoNames lower + // elements. + // + // On the other hand, it may be possible to take too many elements for + // disambiguation. We shall limit to maximum MaxLowerTopoNames. If the + // chosen elements are not enough to disambiguate the higher element, + // we'll include an index for disambiguation. + + auto subshape = shape.getSubTopoShape(res.first, res.second, true); + TopAbs_ShapeEnum lower; + Data::IndexedName idxName; + if (!subshape.isNull()) { + switch (res.first) { + case TopAbs_WIRE: + lower = TopAbs_EDGE; + idxName = Data::IndexedName::fromConst("Edge", 1); + break; + case TopAbs_SHELL: + case TopAbs_SOLID: + case TopAbs_COMPOUND: + case TopAbs_COMPSOLID: + lower = TopAbs_FACE; + idxName = Data::IndexedName::fromConst("Face", 1); + break; + default: + lower = TopAbs_SHAPE; + } + if (lower != TopAbs_SHAPE) { + typedef std::pair> NameEntry; + std::vector indices; + std::vector names; + std::vector ancestors; + int count = 0; + for (auto& ss : subshape.getSubTopoShapes(lower)) { + auto name = ss.getMappedName(idxName); + if (!name) { + continue; + } + indices.emplace_back(name.size(), + shape.findAncestors(ss.getShape(), res.first)); + names.push_back(name); + if (indices.back().second.size() == 1 && ++count >= MinLowerTopoNames) { + break; + } + } + + if (names.size() >= MaxLowerTopoNames) { + std::stable_sort(indices.begin(), + indices.end(), + [](const NameEntry& a, const NameEntry& b) { + return a.second.size() < b.second.size(); + }); + std::vector sorted; + auto pos = 0; + sorted.reserve(names.size()); + for (auto& v : indices) { + size_t size = ancestors.size(); + if (size == 0) { + ancestors = v.second; + } + else if (size > 1) { + for (auto it = ancestors.begin(); it != ancestors.end();) { + if (std::find(v.second.begin(), v.second.end(), *it) + == v.second.end()) { + it = ancestors.erase(it); + if (ancestors.size() == 1) { + break; + } + } + else { + ++it; + } + } + } + auto itPos = sorted.end(); + if (size == 1 || size != ancestors.size()) { + itPos = sorted.begin() + pos; + ++pos; + } + sorted.insert(itPos, names[v.first]); + if (size == 1 && sorted.size() >= MinLowerTopoNames) { + break; + } + } + } + + names.resize(std::min((int)names.size(), MaxLowerTopoNames)); + if (names.size()) { + std::string op; + if (ancestors.size() > 1) { + // The current chosen elements are not enough to + // identify the higher element, generate an index for + // disambiguation. + auto it = std::find(ancestors.begin(), ancestors.end(), res.second); + if (it == ancestors.end()) { + assert(0 && "ancestor not found"); // this shouldn't happened + } + else { + op = Data::POSTFIX_TAG + std::to_string(it - ancestors.begin()); + } + } + + // Note: setting names to shape will change its underlying + // shared element name table. This actually violates the + // const'ness of this function. + // + // To be const correct, we should have made the element + // name table to be implicit sharing (i.e. copy on change). + // + // Not sure if there is any side effect of indirectly + // change the element map inside the Shape property without + // recording the change in undo stack. + // + mapped.name = shape.setElementComboName(mapped.index, + names, + mapped.index.getType(), + op.c_str()); + } + } + } + return App::GeoFeature::_getElementName(name, mapped); + } + + if (!res.second && mapped.name) { + const char* dot = strchr(name, '.'); + if (dot) { + ++dot; + // Here means valid mapped name, but cannot find the corresponding + // indexed name. This usually means the model has been changed. The + // original indexed name is usually appended to the mapped name + // separated by a dot. We use it as a clue to decode the combo name + // set above, and try to single out one sub shape that has all the + // lower elements encoded in the combo name. But since we don't + // always use all the lower elements for encoding, this can only be + // consider a heuristics. + if (Data::hasMissingElement(dot)) { + dot += strlen(Data::MISSING_PREFIX); + } + std::pair occindex = shape.shapeTypeAndIndex(dot); + if (occindex.second > 0) { + auto idxName = Data::IndexedName::fromConst(shape.shapeName(occindex.first).c_str(), + occindex.second); + std::string postfix; + auto names = + shape.decodeElementComboName(idxName, mapped.name, idxName.getType(), &postfix); + std::vector ancestors; + for (auto& name : names) { + auto index = shape.getIndexedName(name); + if (!index) { + ancestors.clear(); + break; + } + auto oidx = shape.shapeTypeAndIndex(index); + auto subshape = shape.getSubShape(oidx.first, oidx.second); + if (subshape.IsNull()) { + ancestors.clear(); + break; + } + auto current = shape.findAncestors(subshape, occindex.first); + if (ancestors.empty()) { + ancestors = std::move(current); + } + else { + for (auto it = ancestors.begin(); it != ancestors.end();) { + if (std::find(current.begin(), current.end(), *it) == current.end()) { + it = ancestors.erase(it); + } + else { + ++it; + } + } + if (ancestors.empty()) { // model changed beyond recognition, bail! + break; + } + } + } + if (ancestors.size() > 1 && boost::starts_with(postfix, Data::POSTFIX_INDEX)) { + std::istringstream iss(postfix.c_str() + strlen(Data::POSTFIX_INDEX)); + int idx; + if (iss >> idx && idx >= 0 && idx < (int)ancestors.size()) { + ancestors.resize(1, ancestors[idx]); + } + } + if (ancestors.size() == 1) { + idxName.setIndex(ancestors.front()); + mapped.index = idxName; + return App::GeoFeature::_getElementName(name, mapped); + } + } + } + } + + return App::GeoFeature::getElementName(name, type); +} \ No newline at end of file diff --git a/src/Mod/Part/App/PartFeature.h b/src/Mod/Part/App/PartFeature.h index 543e03f96d..b93d80b6e0 100644 --- a/src/Mod/Part/App/PartFeature.h +++ b/src/Mod/Part/App/PartFeature.h @@ -64,6 +64,39 @@ public: PyObject* getPyObject() override; + 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; +// +// virtual DocumentObject *getSubObject(const char *subname, PyObject **pyObj, +// Base::Matrix4D *mat, bool transform, int depth) const override; + 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 4cbb2db9d6..e669441deb 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -477,6 +477,20 @@ std::pair TopoShape::shapeTypeAndIndex(const char *name) { return std::make_pair(type,idx); } +std::pair +TopoShape::shapeTypeAndIndex(const Data::IndexedName & element) +{ + if (!element) + return std::make_pair(TopAbs_SHAPE, 0); + static const std::string _subshape("SubShape"); + if (boost::equals(element.getType(), _subshape)) + return std::make_pair(TopAbs_SHAPE, element.getIndex()); + TopAbs_ShapeEnum shapetype = shapeType(element.getType(), true); + if (shapetype == TopAbs_SHAPE) + return std::make_pair(TopAbs_SHAPE, 0); + return std::make_pair(shapetype, element.getIndex()); +} + TopAbs_ShapeEnum TopoShape::shapeType(const char *type, bool silent) { if(type) { initShapeNameMap(); diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index b5840144f3..ca3445f354 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -1131,13 +1131,19 @@ public: static const std::string& shapeName(TopAbs_ShapeEnum type, bool silent = false); const std::string& shapeName(bool silent = false) const; static std::pair shapeTypeAndIndex(const char* name); + static std::pair shapeTypeAndIndex(const Data::IndexedName &name); - Data::MappedName setElementComboName(const Data::IndexedName & element, + Data::MappedName setElementComboName(const Data::IndexedName & element, const std::vector &names, const char *marker=nullptr, const char *op=nullptr, const Data::ElementIDRefs *sids=nullptr); + std::vector decodeElementComboName(const Data::IndexedName& element, + const Data::MappedName& name, + const char* marker = nullptr, + std::string* postfix = nullptr) const; + /** @name sub shape cached functions * * Mapped element names introduces some overhead when getting sub shapes diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index b6829b3260..44ad847205 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4054,6 +4054,87 @@ Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element return elementMap()->setElementName(element, newName, Tag, &sids); } +std::vector +TopoShape::decodeElementComboName(const Data::IndexedName &element, + const Data::MappedName &name, + const char *marker, + std::string *postfix) const +{ + std::vector names; + if (!element) + return names; + if (!marker) + marker = ""; + int plen = (int)elementMapPrefix().size(); + int markerLen = strlen(marker); + int len; + int pos = name.findTagInElementName(nullptr, &len); + if (pos < 0) { + // It is possible to encode combo name without using a tag, e.g. + // Sketcher object creates wire using edges that are created by itself, + // so there will be no tag to encode. + // + // In this case, just search for the brackets + len = name.find("("); + if (len < 0) { + // No bracket is also possible, if there is only one name in the combo + pos = len = name.size(); + } else { + pos = name.find(")"); + if (pos < 0) { + // non closing bracket? + return {}; + } + ++pos; + } + if (len <= (int)markerLen) + return {}; + len -= markerLen+plen; + } + + if (name.find(elementMapPrefix(), len) != len + || name.find(marker, len+plen) != len+plen) + return {}; + + names.emplace_back(name, 0, len); + + std::string text; + len += plen + markerLen; + name.toString(text, len, pos-len); + + if (this->Hasher) { + if (auto id = App::StringID::fromString(names.back().toRawBytes())) { + if (App::StringIDRef sid = this->Hasher->getID(id)) { + names.pop_back(); + names.emplace_back(sid); + } + else + return names; + } + if (auto id = App::StringID::fromString(text.c_str())) { + if (App::StringIDRef sid = this->Hasher->getID(id)) + text = sid.dataToText(); + else + return names; + } + } + if (text.empty() || text[0] != '(') + return names; + auto endPos = text.rfind(')'); + if (endPos == std::string::npos) + return names; + + if (postfix) + *postfix = text.substr(endPos+1); + + text.resize(endPos); + std::istringstream iss(text.c_str()+1); + std::string token; + while(std::getline(iss, token, '|')) + names.emplace_back(token); + return names; +} + /** * Reorient the outer and inner wires of the TopoShape *