From f57283f598aa970271c4d9f5cf57cd2ab9cc6ab2 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sun, 1 Sep 2019 19:45:57 +0800 Subject: [PATCH] Link: change sub-element linking mechanism Previous multi-sub-element linking (e.g. Face, Edge) is supported through SubElements of type PropertyStringList, which does not support geometry element update tracking. This patch changes it to use PropertyXLink's new multi-subname capability. --- src/App/Link.cpp | 104 ++++++++++++++++++------------- src/App/Link.h | 14 ++--- src/Gui/CommandLink.cpp | 49 ++++++++++----- src/Gui/ViewProviderLink.cpp | 35 ++++++----- src/Gui/ViewProviderLink.h | 4 +- src/Mod/Part/App/PartFeature.cpp | 34 +++++----- 6 files changed, 139 insertions(+), 101 deletions(-) diff --git a/src/App/Link.cpp b/src/App/Link.cpp index d134b22412..f5b152e7e8 100644 --- a/src/App/Link.cpp +++ b/src/App/Link.cpp @@ -188,8 +188,7 @@ short LinkBaseExtension::extensionMustExecute(void) { } App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const { - auto subs = getSubElementsProperty(); - if(subs && subs->getSize()) + if(mySubElements.size() && mySubElements[0].size()) return 0; auto linked = getTrueLinkedObject(false); if(!linked) @@ -498,7 +497,7 @@ bool LinkBaseExtension::extensionGetSubObjects(std::vector &ret, in } return true; } - if(mySubElement.empty() && getSubElementsValue().empty()) { + if(mySubElements.empty() || mySubElements[0].empty()) { DocumentObject *linked = getTrueLinkedObject(true); if(linked) { if(!_getElementCountValue()) @@ -511,6 +510,8 @@ bool LinkBaseExtension::extensionGetSubObjects(std::vector &ret, in } } } + } else if(mySubElements.size()>1) { + ret = mySubElements; } return true; } @@ -523,13 +524,17 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char * auto obj = getContainer(); if(!subname || !subname[0]) { ret = const_cast(obj); - if(pyObj && !_getElementCountValue() && _getElementListValue().empty()) { + if(pyObj && !_getElementCountValue() + && _getElementListValue().empty() && mySubElements.size()<=1) + { Base::Matrix4D matNext; if(mat) matNext = *mat; auto linked = getTrueLinkedObject(false,mat?&matNext:0,depth); if(linked && linked!=obj) { if(mat) *mat = matNext; - linked->getSubObject(mySubElement.c_str(),pyObj,mat,false,depth+1); + linked->getSubObject( + mySubElements.empty()?0:mySubElements.front().c_str(), + pyObj,mat,false,depth+1); checkGeoElementMap(obj,linked,pyObj,0); } } @@ -544,8 +549,6 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char * if(elements.size()) { if(idx>=(int)elements.size() || !elements[idx] || !elements[idx]->getNameInDocument()) return true; - if(!subname || !subname[0]) - subname = mySubElement.c_str(); ret = elements[idx]->getSubObject(subname,pyObj,mat,true,depth+1); // do not resolve the link if this element is the last referenced object if(!subname || Data::ComplexGeoData::isMappedElement(subname) || !strchr(subname,'.')) @@ -576,8 +579,6 @@ bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char * Base::Matrix4D matNext; if(mat) matNext = *mat; - if(!subname || !subname[0]) - subname = mySubElement.c_str(); ret = linked->getSubObject(subname,pyObj,mat?&matNext:0,false,depth+1); std::string postfix; if(ret) { @@ -668,15 +669,36 @@ void LinkBaseExtension::extensionOnChanged(const Property *prop) { } void LinkBaseExtension::parseSubName() const { - auto xlink = freecad_dynamic_cast(getLinkedObjectProperty()); - const char* subname = xlink?xlink->getSubName():0; + // If user has ever linked to some sub-element, the Link shall always accept + // sub-element linking in the future, which affects how ViewProviderLink + // dropObjectEx() behave. So we will push an empty string later even if no + // sub-element linking this time. + bool hasSubElement = !mySubElements.empty(); + mySubElements.clear(); mySubName.clear(); - mySubElement.clear(); - if(!subname || !subname[0]) + auto xlink = freecad_dynamic_cast(getLinkedObjectProperty()); + if(!xlink || xlink->getSubValues().empty()) { + if(hasSubElement) + mySubElements.emplace_back(""); return; + } + const auto &subs = xlink->getSubValues(); + auto subname = subs.front().c_str(); auto element = Data::ComplexGeoData::findElementName(subname); + if(!element || !element[0]) { + mySubName = subs[0]; + if(hasSubElement) + mySubElements.emplace_back(""); + return; + } + mySubElements.push_back(element); mySubName = std::string(subname,element-subname); - mySubElement = element; + for(std::size_t i=1;i(link); - std::string subname; - if(xlink) - subname = xlink->getSubName(); auto owner = getContainer(); auto ownerID = owner?owner->getID():0; @@ -1038,9 +1053,6 @@ void LinkBaseExtension::syncElementList() { element->myOwner = ownerID; - element->SubElements.setStatus(Property::Hidden,sub!=0); - element->SubElements.setStatus(Property::Immutable,sub!=0); - element->LinkTransform.setStatus(Property::Hidden,transform!=0); element->LinkTransform.setStatus(Property::Immutable,transform!=0); if(transform && element->LinkTransform.getValue()!=transform->getValue()) @@ -1048,13 +1060,16 @@ void LinkBaseExtension::syncElementList() { element->LinkedObject.setStatus(Property::Hidden,link!=0); element->LinkedObject.setStatus(Property::Immutable,link!=0); - if(link) { - if(element->LinkedObject.getValue()!=link->getValue() || - subname != element->LinkedObject.getSubName() || - subElements != element->SubElements.getValue()) + if(xlink) { + if(element->LinkedObject.getValue()!=xlink->getValue() || + element->LinkedObject.getSubValues()!=xlink->getSubValues()) { - element->setLink(-1,link->getValue(),subname.c_str(),subElements); + element->LinkedObject.setValue(xlink->getValue(), xlink->getSubValues()); } + } else if(element->LinkedObject.getValue()!=link->getValue() || + element->LinkedObject.getSubValues().size()) + { + element->setLink(-1,link->getValue()); } } } @@ -1167,10 +1182,6 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj, // Here means we are assigning a Link auto xlink = freecad_dynamic_cast(linkProp); - auto subElementProp = getSubElementsProperty(); - if(subElements.size() && !subElementProp) - LINK_THROW(Base::RuntimeError,"No SubElements Property configured"); - if(obj) { if(!obj->getNameInDocument()) LINK_THROW(Base::ValueError,"Invalid document object"); @@ -1180,18 +1191,23 @@ void LinkBaseExtension::setLink(int index, DocumentObject *obj, } } - if(subname && subname[0] && !xlink) - LINK_THROW(Base::RuntimeError,"SubName link requires PropertyXLink"); - - if(subElementProp && subElements.size()) { - subElementProp->setStatus(Property::User3, true); - subElementProp->setValue(subElements); - subElementProp->setStatus(Property::User3, false); - } - if(xlink) - xlink->setValue(obj,subname); - else + if(!xlink) { + if(subElements.size() || (subname && subname[0])) + LINK_THROW(Base::RuntimeError,"SubName/SubElement link requires PropertyXLink"); linkProp->setValue(obj); + return; + } + + std::vector subs; + if(subElements.size()) { + subs.reserve(subElements.size()); + for(const auto &s : subElements) { + subs.emplace_back(subname?subname:""); + subs.back() += s; + } + } else if(subname && subname[0]) + subs.emplace_back(subname); + xlink->setValue(obj,std::move(subs)); } void LinkBaseExtension::detachElement(DocumentObject *obj) { diff --git a/src/App/Link.h b/src/App/Link.h index ed8ac7a88c..5b85626451 100644 --- a/src/App/Link.h +++ b/src/App/Link.h @@ -87,10 +87,6 @@ public: #define LINK_PARAM_OBJECT(...) \ (LinkedObject, App::DocumentObject*, App::PropertyLink, 0, "Linked object", ##__VA_ARGS__) -#define LINK_PARAM_SUB_ELEMENT(...) \ - (SubElements, std::vector, App::PropertyStringList, std::vector(), \ - "Non-object Sub-element list of the linked object, e.g. Face1", ##__VA_ARGS__) - #define LINK_PARAM_TRANSFORM(...) \ (LinkTransform, bool, App::PropertyBool, false, \ "Set to false to override linked object's placement", ##__VA_ARGS__) @@ -145,7 +141,6 @@ public: LINK_PARAM(PLACEMENT)\ LINK_PARAM(LINK_PLACEMENT)\ LINK_PARAM(OBJECT)\ - LINK_PARAM(SUB_ELEMENT)\ LINK_PARAM(TRANSFORM)\ LINK_PARAM(SCALE)\ LINK_PARAM(SCALE_VECTOR)\ @@ -239,9 +234,10 @@ public: parseSubName(); return mySubName.size()?mySubName.c_str():0; } - const char *getSubElement() const { + + const std::vector &getSubElements() const { parseSubName(); - return mySubElement.size()?mySubElement.c_str():0; + return mySubElements; } bool extensionGetSubObject(DocumentObject *&ret, const char *subname, @@ -300,7 +296,7 @@ protected: protected: std::vector props; std::unordered_set myHiddenElements; - mutable std::string mySubElement; + mutable std::vector mySubElements; mutable std::string mySubName; std::unordered_map newNames; + std::map, + std::pair > > linkInfo; for(auto &sel : Selection().getCompleteSelection(0)) { - std::string name = doc->getUniqueObjectName("Link"); - FCMD_DOC_CMD(doc,"addObject('App::Link','" << name << "').setLink(" - << getObjectCmd(sel.pObject) << ",'" << sel.SubName << "')"); - auto link = doc->getObject(name.c_str()); - FCMD_OBJ_CMD(link,"LinkTransform = True"); - setLinkLabel(sel.pResolvedObject,doc->getName(),name.c_str()); - - newNames.push_back(std::move(name)); + if(!sel.pObject || !sel.pObject->getNameInDocument()) + continue; + auto key = std::make_pair(sel.pObject, + Data::ComplexGeoData::noElementName(sel.SubName)); + auto element = Data::ComplexGeoData::findElementName(sel.SubName); + auto &info = linkInfo[key]; + info.first = sel.pResolvedObject; + if(element && element[0]) + info.second.emplace_back(element); } + Selection().selStackPush(); Selection().clearCompleteSelection(); - for(auto &name : newNames) - Selection().addSelection(doc->getName(),name.c_str()); - Selection().selStackPush(); + for(auto &v : linkInfo) { + auto &key = v.first; + auto &info = v.second; + + std::string name = doc->getUniqueObjectName("Link"); + + std::ostringstream ss; + ss << '['; + for(auto &s : info.second) + ss << "'" << s << "',"; + ss << ']'; + FCMD_DOC_CMD(doc,"addObject('App::Link','" << name << "').setLink(" + << getObjectCmd(key.first) << ",'" << key.second + << "'," << ss.str() << ")"); + auto link = doc->getObject(name.c_str()); + FCMD_OBJ_CMD(link,"LinkTransform = True"); + setLinkLabel(info.first,doc->getName(),name.c_str()); + + Selection().addSelection(doc->getName(),name.c_str()); + } + Selection().selStackPush(); Command::commitCommand(); } catch (const Base::Exception& e) { Command::abortCommand(); diff --git a/src/Gui/ViewProviderLink.cpp b/src/Gui/ViewProviderLink.cpp index a9fa99e5f3..6b0b928387 100644 --- a/src/Gui/ViewProviderLink.cpp +++ b/src/Gui/ViewProviderLink.cpp @@ -1820,22 +1820,16 @@ void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App: pcTransform->scaleFactor.setValue(v.x,v.y,v.z); linkView->renderDoubleSide(v.x*v.y*v.z < 0); } - }else if(prop == ext->getLinkedObjectProperty() || - prop == ext->getSubElementsProperty()) - { + }else if(prop == ext->getLinkedObjectProperty()) { + if(!prop->testStatus(App::Property::User3)) { std::vector subs; const char *subname = ext->getSubName(); std::string sub; if(subname) sub = subname; - const char *subElement = ext->getSubElement(); - if(subElement) { - hasSubElement = true; - subs.push_back(sub+subElement); - }else - hasSubElement = false; - for(const auto &s : ext->getSubElementsValue()) { + hasSubElement = false; + for(const auto &s : ext->getSubElements()) { if(s.empty()) continue; hasSubElement = true; subs.push_back(sub+s); @@ -2163,7 +2157,8 @@ bool ViewProviderLink::canDropObjects() const { } bool ViewProviderLink::canDropObjectEx(App::DocumentObject *obj, - App::DocumentObject *owner, const char *subname, const std::vector &elements) const + App::DocumentObject *owner, const char *subname, + const std::vector &subElements) const { if(pcObject == obj || pcObject == owner) return false; @@ -2180,18 +2175,19 @@ bool ViewProviderLink::canDropObjectEx(App::DocumentObject *obj, if(linkedVdp->getObject()==obj || linkedVdp->getObject()==owner) return false; } - return linked->canDropObjectEx(obj,owner,subname,elements); + return linked->canDropObjectEx(obj,owner,subname,subElements); } } if(obj->getDocument() != getObject()->getDocument() && - !freecad_dynamic_cast(ext->getLinkedObjectValue())) + !freecad_dynamic_cast(ext->getLinkedObjectProperty())) return false; return true; } std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj, - App::DocumentObject *owner, const char *subname, const std::vector &elements) + App::DocumentObject *owner, const char *subname, + const std::vector &subElements) { auto ext = getLinkExtension(); if(isGroup(ext)) { @@ -2206,10 +2202,15 @@ std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj, if(!hasSubName) { auto linked = getLinkedView(false,ext); if(linked) - return linked->dropObjectEx(obj,owner,subname,elements); + return linked->dropObjectEx(obj,owner,subname,subElements); } - if(owner) - ext->setLink(-1,owner,subname); + if(owner) { + if(ext->getSubElements().size()) + ext->setLink(-1,owner,subname,subElements); + else + ext->setLink(-1,owner,subname); + } else if(ext->getSubElements().size()) + ext->setLink(-1,obj,0,subElements); else ext->setLink(-1,obj,0); return std::string(); diff --git a/src/Gui/ViewProviderLink.h b/src/Gui/ViewProviderLink.h index 1e274f132b..a87e3c723f 100644 --- a/src/Gui/ViewProviderLink.h +++ b/src/Gui/ViewProviderLink.h @@ -222,9 +222,9 @@ public: bool canDropObjects() const override; bool canDragAndDropObject(App::DocumentObject*) const override; bool canDropObjectEx(App::DocumentObject *obj, App::DocumentObject *owner, - const char *subname, const std::vector &elements) const override; + const char *subname, const std::vector &subElements) const override; std::string dropObjectEx(App::DocumentObject*, App::DocumentObject*, - const char *subname, const std::vector &elements) override; + const char *subname, const std::vector &subElements) override; bool onDelete(const std::vector &) override; bool canDelete(App::DocumentObject* obj) const override; diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 57aea59385..e5825384bb 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -369,7 +369,10 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna } auto link = owner->getExtensionByType(true); - if(owner!=linked && (!link || !link->_ChildCache.getSize())) { + 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,0,false,0,0,false,false); @@ -405,25 +408,28 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna for(auto &sub : owner->getSubObjects()) { if(sub.empty()) continue; int visible; - if(sub[sub.size()-1] != '.') - sub += '.'; std::string childName; App::DocumentObject *parent=0; Base::Matrix4D mat; - auto subObj = owner->resolve(sub.c_str(), &parent, &childName,0,0,&mat,false); - if(!parent || !subObj) - continue; - if(linkStack.size() - && parent->getExtensionByType(true,false)) - { - visible = linkStack.back()->isElementVisible(childName.c_str()); - }else - visible = parent->isElementVisible(childName.c_str()); + App::DocumentObject *subObj=0; + if(sub.find('.')==std::string::npos) + visible = 1; + else { + subObj = owner->resolve(sub.c_str(), &parent, &childName,0,0,&mat,false); + if(!parent || !subObj) + continue; + if(linkStack.size() + && 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(baseShape.isNull()) { - shape = _getTopoShape(owner,sub.c_str(),false,0,&subObj,false,false,linkStack); + if(!subObj || baseShape.isNull()) { + shape = _getTopoShape(owner,sub.c_str(),true,0,&subObj,false,false,linkStack); if(shape.isNull()) continue; if(visible<0 && subObj && !subObj->Visibility.getValue())