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.
This commit is contained in:
Zheng, Lei
2019-09-01 19:45:57 +08:00
committed by wmayer
parent 4042d83362
commit f57283f598
6 changed files with 139 additions and 101 deletions

View File

@@ -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<std::string> &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<std::string> &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<DocumentObject*>(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<const PropertyXLink>(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<const PropertyXLink>(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<subs.size();++i) {
auto &sub = subs[i];
element = Data::ComplexGeoData::findElementName(sub.c_str());
if(element && element[0] && boost::starts_with(sub,mySubName))
mySubElements.push_back(element);
}
}
void LinkBaseExtension::slotChangedPlainGroup(const App::DocumentObject &obj, const App::Property &prop) {
@@ -982,8 +1004,6 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop
_ChildCache.setValue();
parseSubName();
syncElementList();
}else if(prop == getSubElementsProperty()) {
syncElementList();
}else if(prop == getLinkTransformProperty()) {
auto linkPlacement = getLinkPlacementProperty();
auto placement = getPlacementProperty();
@@ -1019,14 +1039,9 @@ bool LinkBaseExtension::linkTransform() const {
}
void LinkBaseExtension::syncElementList() {
const auto &subElements = getSubElementsValue();
auto sub = getSubElementsProperty();
auto transform = getLinkTransformProperty();
auto link = getLinkedObjectProperty();
auto xlink = freecad_dynamic_cast<const PropertyXLink>(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<PropertyXLink>(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<std::string> 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) {

View File

@@ -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<std::string>, App::PropertyStringList, std::vector<std::string>(), \
"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<std::string> &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<Property *> props;
std::unordered_set<const App::DocumentObject*> myHiddenElements;
mutable std::string mySubElement;
mutable std::vector<std::string> mySubElements;
mutable std::string mySubName;
std::unordered_map<const App::DocumentObject*,
@@ -439,7 +435,6 @@ public:
LINK_PARAM_EXT(TRANSFORM)\
LINK_PARAM_EXT(LINK_PLACEMENT)\
LINK_PARAM_EXT(PLACEMENT)\
LINK_PARAM_EXT(SUB_ELEMENT)\
LINK_PARAM_EXT(SHOW_ELEMENT)\
LINK_PARAM_EXT_TYPE(COUNT,App::PropertyIntegerConstraint)\
LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)
@@ -475,7 +470,6 @@ public:
LINK_PARAM_EXT(TRANSFORM) \
LINK_PARAM_EXT(LINK_PLACEMENT)\
LINK_PARAM_EXT(PLACEMENT)\
LINK_PARAM_EXT(SUB_ELEMENT)
// defines the actual properties
LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT)

View File

@@ -267,7 +267,7 @@ StdCmdLinkMakeRelative::StdCmdLinkMakeRelative()
: Command("Std_LinkMakeRelative")
{
sGroup = QT_TR_NOOP("Link");
sMenuText = QT_TR_NOOP("Make relative link");
sMenuText = QT_TR_NOOP("Make sub-link");
sToolTipText = QT_TR_NOOP("Create a sub-object or sub-element link");
sWhatsThis = "Std_LinkMakeRelative";
sStatusTip = sToolTipText;
@@ -285,25 +285,46 @@ void StdCmdLinkMakeRelative::activated(int) {
FC_ERR("no active document");
return;
}
Command::openCommand("Make relative link");
Command::openCommand("Make sub-link");
try {
std::vector<std::string> newNames;
std::map<std::pair<App::DocumentObject*,std::string>,
std::pair<App::DocumentObject*, std::vector<std::string> > > 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();

View File

@@ -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<std::string> 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<std::string> &elements) const
App::DocumentObject *owner, const char *subname,
const std::vector<std::string> &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<App::PropertyXLink>(ext->getLinkedObjectValue()))
!freecad_dynamic_cast<App::PropertyXLink>(ext->getLinkedObjectProperty()))
return false;
return true;
}
std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj,
App::DocumentObject *owner, const char *subname, const std::vector<std::string> &elements)
App::DocumentObject *owner, const char *subname,
const std::vector<std::string> &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();

View File

@@ -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<std::string> &elements) const override;
const char *subname, const std::vector<std::string> &subElements) const override;
std::string dropObjectEx(App::DocumentObject*, App::DocumentObject*,
const char *subname, const std::vector<std::string> &elements) override;
const char *subname, const std::vector<std::string> &subElements) override;
bool onDelete(const std::vector<std::string> &) override;
bool canDelete(App::DocumentObject* obj) const override;

View File

@@ -369,7 +369,10 @@ static TopoShape _getTopoShape(const App::DocumentObject *obj, const char *subna
}
auto link = owner->getExtensionByType<App::LinkBaseExtension>(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<App::GroupExtension>(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<App::GroupExtension>(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())