diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index ad2bb4bacb..cd67807a6b 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -40,6 +40,7 @@ #include "DocumentObjectExtension.h" #include "DocumentObjectGroup.h" #include "GeoFeatureGroupExtension.h" +#include "Link.h" #include "ObjectIdentifier.h" #include "PropertyExpressionEngine.h" #include "PropertyLinks.h" @@ -887,19 +888,69 @@ DocumentObject *DocumentObject::getSubObject(const char *subname, return ret; } -std::vector DocumentObject::getSubObjectList(const char *subname) const { +std::vector +DocumentObject::getSubObjectList(const char *subname, + std::vector *sublist, + bool flatten) const +{ std::vector res; res.push_back(const_cast(this)); + if (sublist) sublist->push_back(0); if(!subname || !subname[0]) return res; - std::string sub(subname); + auto element = Data::findElementName(subname); + std::string sub(subname,element-subname); + App::DocumentObject *container = nullptr; + + bool lastChild = false; + if (flatten) { + auto linked = getLinkedObject(); + if (linked->getExtensionByType(true)) + container = const_cast(this); + else if (auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(linked)) { + container = grp; + lastChild = true; + } + } for(auto pos=sub.find('.');pos!=std::string::npos;pos=sub.find('.',pos+1)) { char c = sub[pos+1]; sub[pos+1] = 0; auto sobj = getSubObject(sub.c_str()); if(!sobj || !sobj->isAttachedToDocument()) - break; + continue; + + if (flatten) { + auto linked = sobj->getLinkedObject(); + if (container) { + auto grp = App::GeoFeatureGroupExtension::getGroupOfObject(linked); + if (grp != container) + container = nullptr; + else { + if (lastChild && res.size()) { + res.pop_back(); + if (sublist) + sublist->pop_back(); + } + lastChild = true; + } + } + if (linked->getExtensionByType(true)) { + container = linked; + lastChild = false; + } + else if (linked != sobj || sobj->hasChildElement()) { + // Check for Link or LinkGroup + container = nullptr; + } + else if (auto ext = sobj->getExtensionByType(true)) { + // check for Link array + if (ext->getElementCountValue()) + container = nullptr; + } + } res.push_back(sobj); + if (sublist) + sublist->push_back(pos+1); sub[pos+1] = c; } return res; diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 7052f92d7e..b9dbe5307c 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -377,8 +377,18 @@ public: virtual DocumentObject *getSubObject(const char *subname, PyObject **pyObj=nullptr, Base::Matrix4D *mat=nullptr, bool transform=true, int depth=0) const; - /// Return a list of objects referenced by a given subname including this object - std::vector getSubObjectList(const char *subname) const; + /** Return a list of objects referenced by a given subname including this object + * @param subname: the sub name path + * @param subsizes: optional sub name sizes for each returned object, that is, + * ret[i] = getSubObject(std::string(subname, subsizes[i]).c_str()); + * @param flatten: whether to flatten the object hierarchies that belong to + * the same geo feature group, e.g. (Part.Fusion.Box -> Part.Box) + * + * @return Return a list of object along the path. + */ + std::vector getSubObjectList(const char *subname, + std::vector *subsizes = nullptr, + bool flatten = false) const; /// reason of calling getSubObjects() enum GSReason { diff --git a/src/App/DocumentObserver.cpp b/src/App/DocumentObserver.cpp index f492f3c110..f81ff01425 100644 --- a/src/App/DocumentObserver.cpp +++ b/src/App/DocumentObserver.cpp @@ -30,6 +30,7 @@ #include "Document.h" #include "DocumentObserver.h" #include "GeoFeature.h" +#include "Link.h" using namespace App; namespace sp = std::placeholders; @@ -341,6 +342,79 @@ bool SubObjectT::operator==(const SubObjectT &other) const { && subname == other.subname; } +bool SubObjectT::normalize(NormalizeOptions options) +{ + bool noElement = options.testFlag(NormalizeOption::NoElement); + bool flatten = !options.testFlag(NormalizeOption::NoFlatten); + bool keepSub = options.testFlag(NormalizeOption::KeepSubName); + bool convertIndex = options.testFlag(NormalizeOption::ConvertIndex); + + std::ostringstream ss; + std::vector subs; + auto obj = getObject(); + if(!obj) + return false; + auto objs = obj->getSubObjectList(subname.c_str(), &subs, flatten); + if (objs.empty()) + return false; + for (unsigned i=1; igetExtensionByType(true)) { + if (ext->getElementCountValue() && !ext->getShowElementValue()) { + // if the parent is a collapsed link array element, then we + // have to keep the index no matter what, because there is + // no sub-object corresponding to an array element. + _keepSub = true; + } + } + } + if (_keepSub) + ss << std::string(sub, end); + else + ss << objs[i]->getNameInDocument() << "."; + } + if (objs.size() > 1 && objs.front()->getSubObject(ss.str().c_str()) != objs.back()) { + // something went wrong + return false; + } + if (!noElement) + ss << getOldElementName(); + std::string sub = ss.str(); + if (objs.front() != obj || subname != sub) { + *this = objs.front(); + subname = std::move(sub); + return true; + } + return false; +} + +SubObjectT App::SubObjectT::normalized(NormalizeOptions options) const +{ + SubObjectT res(*this); + res.normalize(options); + return res; +} + void SubObjectT::setSubName(const char *s) { subname = s?s:""; } @@ -357,6 +431,15 @@ const char *SubObjectT::getElementName() const { return Data::findElementName(subname.c_str()); } +bool SubObjectT::hasSubObject() const { + return Data::findElementName(subname.c_str()) != subname.c_str(); +} + +bool SubObjectT::hasSubElement() const { + auto element = getElementName(); + return element && element[0]; +} + std::string SubObjectT::getNewElementName() const { std::pair element; auto obj = getObject(); diff --git a/src/App/DocumentObserver.h b/src/App/DocumentObserver.h index 6ff92b857a..64f6578652 100644 --- a/src/App/DocumentObserver.h +++ b/src/App/DocumentObserver.h @@ -25,6 +25,7 @@ #define APP_DOCUMENTOBSERVER_H #include +#include #include #include #include @@ -226,6 +227,12 @@ public: /// Return the sub-element (Face, Edge, etc) of the subname path const char *getElementName() const; + /// Check if there is any sub object reference + bool hasSubObject() const; + + /// Check if there is any sub element reference + bool hasSubElement() const; + /// Return the new style sub-element name std::string getNewElementName() const; @@ -244,6 +251,36 @@ public: std::string getSubObjectPython(bool force=true) const; + /// Options used by normalize() + enum class NormalizeOption { + /// Do not include sub-element reference in the output path + NoElement = 0x01, + /** Do not flatten the output path. If not specified, the output path + * will be flatten to exclude intermediate objects that belong to the + * same geo feature group before resolving. For example, + * Part.Fusion.Box. -> Part.Box. + */ + NoFlatten = 0x02, + /** Do not change the sub-object component inside the path. Each + * component of the subname object path can be either the object + * internal name, the label of the object if starts with '$', or an + * integer index. If this option is not specified, each component will + * be converted to object internal name, except for integer index. + */ + KeepSubName = 0x04, + /** Convert integer index in the path to sub-object internal name */ + ConvertIndex = 0x08, + }; + using NormalizeOptions = Base::Flags; + + /** Normalize the subname path to use only the object internal name and old style element name + * @return Return whether the subname has been changed + */ + bool normalize(NormalizeOptions options = NormalizeOption::NoElement); + + /// Return a normalize copy of itself + SubObjectT normalized(NormalizeOptions options = NormalizeOption::NoElement) const; + private: std::string subname; }; @@ -558,4 +595,6 @@ private: } //namespace App +ENABLE_BITMASK_OPERATORS(App::SubObjectT::NormalizeOption) + #endif // APP_DOCUMENTOBSERVER_H