diff --git a/src/App/Link.cpp b/src/App/Link.cpp index 3e67cbe16c..c845d3b247 100644 --- a/src/App/Link.cpp +++ b/src/App/Link.cpp @@ -28,6 +28,7 @@ #include #include +#include #include "Application.h" #include "Document.h" #include "GroupExtension.h" @@ -53,7 +54,7 @@ namespace bp = boost::placeholders; EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension) LinkBaseExtension::LinkBaseExtension(void) - :enableLabelCache(false),hasOldSubElement(false) + :enableLabelCache(false),hasOldSubElement(false),hasCopyOnChange(true) { initExtensionType(LinkBaseExtension::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY_TYPE(_LinkTouched, (false), " Link", @@ -139,7 +140,15 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) { if(!propLinkMode->getEnums()) propLinkMode->setEnums(linkModeEnums); break; - } case PropLinkTransform: + } + case PropLinkCopyOnChange: { + static const char *enums[] = {"Disabled","Enabled","Owned",0}; + auto propEnum = freecad_dynamic_cast(prop); + if(!propEnum->getEnums()) + propEnum->setEnums(enums); + break; + } + case PropLinkTransform: case PropLinkPlacement: case PropPlacement: if(getLinkTransformProperty() && @@ -181,6 +190,8 @@ void LinkBaseExtension::setProperty(int idx, Property *prop) { } } +static const char _GroupPrefix[] = "Configuration ("; + App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) { // The actual value of LinkRecompouted is not important, just to notify view // provider that the link (in fact, its dependents, i.e. linked ones) have @@ -193,6 +204,24 @@ App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute(void) { return new App::DocumentObjectExecReturn("Link broken"); App::DocumentObject *container = getContainer(); + setupCopyOnChange(container); + + if(hasCopyOnChange && getLinkCopyOnChangeValue()==0) { + hasCopyOnChange = false; + std::vector props; + container->getPropertyList(props); + for(auto prop : props) { + if(isCopyOnChangeProperty(container, *prop)) { + try { + container->removeDynamicProperty(prop->getName()); + } catch (Base::Exception &e) { + e.ReportException(); + } catch (...) { + } + } + } + } + PropertyPythonObject *proxy = 0; if(getLinkExecuteProperty() && !boost::iequals(getLinkExecuteValue(), "none") @@ -253,6 +282,165 @@ short LinkBaseExtension::extensionMustExecute(void) { return link->mustExecute(); } +bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) { + if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic)) + return false; + auto group = prop.getGroup(); + return group && boost::starts_with(group,_GroupPrefix); +} + +void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent) { + copyOnChangeConns.clear(); + + auto linked = getTrueLinkedObject(false); + if(!linked || getLinkCopyOnChangeValue()==0) + return; + + hasCopyOnChange = setupCopyOnChange(parent,linked,©OnChangeConns,hasCopyOnChange); +} + +bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked, + std::vector *copyOnChangeConns, bool checkExisting) +{ + if(!parent || !linked) + return false; + + bool res = false; + + std::unordered_map newProps; + std::vector props; + linked->getPropertyList(props); + for(auto prop : props) { + if(!prop->testStatus(Property::CopyOnChange) + || prop->getContainer()!=linked) + continue; + + res = true; + + const char* linkedGroupName = prop->getGroup(); + if(!linkedGroupName || !linkedGroupName[0]) + linkedGroupName = "Base"; + + std::string groupName; + groupName = _GroupPrefix; + if(boost::starts_with(linkedGroupName,_GroupPrefix)) + groupName += linkedGroupName + sizeof(_GroupPrefix)-1; + else { + groupName += linkedGroupName; + groupName += ")"; + } + + auto p = parent->getPropertyByName(prop->getName()); + if(p) { + if(p->getContainer()!=parent) + p = 0; + else { + const char* otherGroupName = p->getGroup(); + if(!otherGroupName || !boost::starts_with(otherGroupName, _GroupPrefix)) { + FC_WARN(p->getFullName() << " shadows another CopyOnChange property " + << prop->getFullName()); + continue; + } + if(p->getTypeId() != prop->getTypeId() || groupName != otherGroupName) { + parent->removeDynamicProperty(p->getName()); + p = 0; + } + } + } + if(!p) { + p = parent->addDynamicProperty(prop->getTypeId().getName(), + prop->getName(), groupName.c_str(), prop->getDocumentation()); + std::unique_ptr pcopy(prop->Copy()); + Base::ObjectStatusLocker guard(Property::User3, p); + if(pcopy) { + p->Paste(*pcopy); + } + p->setStatusValue(prop->getStatus()); + } + newProps[p] = prop; + } + + if(checkExisting) { + props.clear(); + parent->getPropertyList(props); + for(auto prop : props) { + if(prop->getContainer()!=parent) + continue; + auto gname = prop->getGroup(); + if(!gname || !boost::starts_with(gname, _GroupPrefix)) + continue; + if(!newProps.count(prop)) + parent->removeDynamicProperty(prop->getName()); + } + } + + if(!copyOnChangeConns) + return res; + + for(auto &v : newProps) { + // sync configuration properties + copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) { + if(!prop.testStatus(Property::CopyOnChange)) + return; + auto p = parent->getPropertyByName(prop.getName()); + if(p && p->getTypeId()==prop.getTypeId()) { + std::unique_ptr pcopy(prop.Copy()); + // temperoray set Output to prevent touching + p->setStatus(Property::Output, true); + // temperoray block copy on change + Base::ObjectStatusLocker guard(Property::User3, p); + if(pcopy) + p->Paste(*pcopy); + p->setStatusValue(prop.getStatus()); + } + })); + } + + return res; +} + +void LinkBaseExtension::checkCopyOnChange( + App::DocumentObject *parent, const App::Property &prop) +{ + if(parent->getDocument()->isPerformingTransaction()) + return; + + auto linked = getTrueLinkedObject(false); + if(!linked || getLinkCopyOnChangeValue()==0 + || !isCopyOnChangeProperty(parent,prop)) + return; + + if(getLinkCopyOnChangeValue()==2) { + auto p = linked->getPropertyByName(prop.getName()); + if(p && p->getTypeId()==prop.getTypeId()) { + std::unique_ptr pcopy(prop.Copy()); + if(pcopy) + p->Paste(*pcopy); + } + return; + } + + auto linkedProp = linked->getPropertyByName(prop.getName()); + if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop)) + return; + + auto objs = parent->getDocument()->copyObject({linked},true); + if(objs.empty()) + return; + + linked = objs.back(); + linked->Visibility.setValue(false); + linkedProp = linked->getPropertyByName(prop.getName()); + if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) { + std::unique_ptr pcopy(prop.Copy()); + if(pcopy) + linkedProp->Paste(*pcopy); + } + getLinkCopyOnChangeProperty()->setValue((long)0); + getLinkedObjectProperty()->setValue(linked); + getLinkCopyOnChangeProperty()->setValue(2); +} + App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const { if(mySubElements.size() && mySubElements[0].size()) return 0; @@ -1098,6 +1286,15 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop _ChildCache.setValue(); parseSubName(); syncElementList(); + + if(getLinkCopyOnChangeValue()==2) + getLinkCopyOnChangeProperty()->setValue(1); + else + setupCopyOnChange(parent); + + }else if(prop == getLinkCopyOnChangeProperty()) { + setupCopyOnChange(parent); + }else if(prop == getLinkTransformProperty()) { auto linkPlacement = getLinkPlacementProperty(); auto placement = getPlacementProperty(); @@ -1107,6 +1304,9 @@ void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop linkPlacement->setStatus(Property::Hidden,!transform); } syncElementList(); + + } else { + checkCopyOnChange(parent, *prop); } } @@ -1156,6 +1356,8 @@ void LinkBaseExtension::syncElementList() { element->LinkedObject.setStatus(Property::Hidden,link!=0); element->LinkedObject.setStatus(Property::Immutable,link!=0); + if(element->LinkCopyOnChange.getValue()==2) + continue; if(xlink) { if(element->LinkedObject.getValue()!=xlink->getValue() || element->LinkedObject.getSubValues()!=xlink->getSubValues()) diff --git a/src/App/Link.h b/src/App/Link.h index 221f5b4068..0a38bf8a41 100644 --- a/src/App/Link.h +++ b/src/App/Link.h @@ -96,6 +96,14 @@ public: (LinkTransform, bool, App::PropertyBool, false, \ "Set to false to override linked object's placement", ##__VA_ARGS__) +#define LINK_PARAM_COPY_ON_CHANGE(...) \ + (LinkCopyOnChange, long, App::PropertyEnumeration, ((long)0), \ + "Disabled: disable copy on change\n"\ + "Enabled: enable copy linked object on change of any of its propert marked as CopyOnChange\n"\ + "Owned: indicate the linked object has been copied and is own owned by the link. And the\n"\ + " the link will try to sync any change of the original linked object back to the copy.",\ + ##__VA_ARGS__) + #define LINK_PARAM_SCALE(...) \ (Scale, double, App::PropertyFloat, 1.0, "Scale factor", ##__VA_ARGS__) @@ -162,6 +170,7 @@ public: LINK_PARAM(MODE)\ LINK_PARAM(LINK_EXECUTE)\ LINK_PARAM(COLORED_ELEMENTS)\ + LINK_PARAM(COPY_ON_CHANGE)\ enum PropIndex { #define LINK_PINDEX_DEFINE(_1,_2,_param) LINK_PINDEX(_param), @@ -293,11 +302,18 @@ public: void cacheChildLabel(int enable=-1) const; + static bool setupCopyOnChange(App::DocumentObject *obj, App::DocumentObject *linked, + std::vector *copyOnChangeConns, bool checkExisting); + + static bool isCopyOnChangeProperty(App::DocumentObject *obj, const Property &prop); + protected: void _handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName); void parseSubName() const; void update(App::DocumentObject *parent, const Property *prop); + void checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop); + void setupCopyOnChange(App::DocumentObject *parent); void syncElementList(); void detachElement(App::DocumentObject *obj); void checkGeoElementMap(const App::DocumentObject *obj, @@ -318,10 +334,12 @@ protected: mutable std::unordered_map myLabelCache; // for label based subname lookup mutable bool enableLabelCache; - bool hasOldSubElement; mutable bool checkingProperty = false; + + std::vector copyOnChangeConns; + bool hasCopyOnChange; }; /////////////////////////////////////////////////////////////////////////// @@ -456,6 +474,7 @@ public: LINK_PARAM_EXT_TYPE(COUNT,App::PropertyIntegerConstraint)\ LINK_PARAM_EXT(LINK_EXECUTE)\ LINK_PARAM_EXT_ATYPE(COLORED_ELEMENTS,App::Prop_Hidden)\ + LINK_PARAM_EXT(COPY_ON_CHANGE)\ LINK_PROPS_DEFINE(LINK_PARAMS_LINK) @@ -495,6 +514,7 @@ public: LINK_PARAM_EXT(TRANSFORM) \ LINK_PARAM_EXT(LINK_PLACEMENT)\ LINK_PARAM_EXT(PLACEMENT)\ + LINK_PARAM_EXT(COPY_ON_CHANGE)\ // defines the actual properties LINK_PROPS_DEFINE(LINK_PARAMS_ELEMENT) diff --git a/src/App/Property.cpp b/src/App/Property.cpp index 04b08190b1..1c7bdce0d4 100644 --- a/src/App/Property.cpp +++ b/src/App/Property.cpp @@ -256,7 +256,8 @@ void Property::setStatusValue(unsigned long status) { |(1<isChecked())