/**************************************************************************** * Copyright (c) 2017 Zheng Lei (realthunder) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ****************************************************************************/ #include "PreCompiled.h" #include #include #include #include #include #include "Application.h" #include "ElementNamingUtils.h" #include "ComplexGeoDataPy.h" #include "Document.h" #include "DocumentObserver.h" #include "GeoFeatureGroupExtension.h" #include "Link.h" #include "LinkBaseExtensionPy.h" //FIXME: ISO C++11 requires at least one argument for the "..." in a variadic macro #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" #endif FC_LOG_LEVEL_INIT("App::Link", true,true) using namespace App; using namespace Base; namespace sp = std::placeholders; using CharRange = boost::iterator_range; //////////////////////////////////////////////////////////////////////// /*[[[cog import LinkParams LinkParams.define() ]]]*/ namespace { // Auto generated code. See class document of LinkParams. class LinkParamsP: public ParameterGrp::ObserverType { public: // Auto generated code. See class document of LinkParams. ParameterGrp::handle handle; // Auto generated code. See class document of LinkParams. std::unordered_map funcs; bool CopyOnChangeApplyToAll; // Auto generated code. See class document of LinkParams. // Auto generated code. See class document of LinkParams. LinkParamsP() { handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Link"); handle->Attach(this); CopyOnChangeApplyToAll = handle->GetBool("CopyOnChangeApplyToAll", true); funcs["CopyOnChangeApplyToAll"] = &LinkParamsP::updateCopyOnChangeApplyToAll; } // Auto generated code. See class document of LinkParams. ~LinkParamsP() override = default; // Auto generated code. See class document of LinkParams. void OnChange(Base::Subject &, const char* sReason) override { if(!sReason) return; auto it = funcs.find(sReason); if(it == funcs.end()) return; it->second(this); } // Auto generated code. See class document of LinkParams. static void updateCopyOnChangeApplyToAll(LinkParamsP *self) { self->CopyOnChangeApplyToAll = self->handle->GetBool("CopyOnChangeApplyToAll", true); } }; // Auto generated code. See class document of LinkParams. LinkParamsP *instance() { static LinkParamsP *inst = new LinkParamsP; return inst; } } // Anonymous namespace // Auto generated code. See class document of LinkParams. ParameterGrp::handle LinkParams::getHandle() { return instance()->handle; } // Auto generated code. See class document of LinkParams. const char *LinkParams::docCopyOnChangeApplyToAll() { return QT_TRANSLATE_NOOP("LinkParams", "Stores the last user choice of whether to apply CopyOnChange setup to all links\n" "that reference the same configurable object"); } // Auto generated code. See class document of LinkParams. const bool & LinkParams::getCopyOnChangeApplyToAll() { return instance()->CopyOnChangeApplyToAll; } // Auto generated code. See class document of LinkParams. const bool & LinkParams::defaultCopyOnChangeApplyToAll() { static const bool def = true; return def; } // Auto generated code. See class document of LinkParams. void LinkParams::setCopyOnChangeApplyToAll(const bool &v) { instance()->handle->SetBool("CopyOnChangeApplyToAll",v); instance()->CopyOnChangeApplyToAll = v; } // Auto generated code. See class document of LinkParams. void LinkParams::removeCopyOnChangeApplyToAll() { instance()->handle->RemoveBool("CopyOnChangeApplyToAll"); } //[[[end]]] /////////////////////////////////////////////////////////////////////////////// EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension) LinkBaseExtension::LinkBaseExtension() { initExtensionType(LinkBaseExtension::getExtensionClassTypeId()); EXTENSION_ADD_PROPERTY_TYPE(_LinkTouched, (false), " Link", PropertyType(Prop_Hidden|Prop_NoPersist),0); EXTENSION_ADD_PROPERTY_TYPE(_ChildCache, (), " Link", PropertyType(Prop_Hidden|Prop_NoPersist|Prop_ReadOnly),0); _ChildCache.setScope(LinkScope::Global); EXTENSION_ADD_PROPERTY_TYPE(_LinkOwner, (0), " Link", PropertyType(Prop_Hidden|Prop_Output),0); props.resize(PropMax,nullptr); } PyObject* LinkBaseExtension::getExtensionPyObject() { if (ExtensionPythonObject.is(Py::_None())){ // ref counter is set to 1 ExtensionPythonObject = Py::Object(new LinkBaseExtensionPy(this),true); } return Py::new_reference_to(ExtensionPythonObject); } const std::vector &LinkBaseExtension::getPropertyInfo() const { static std::vector PropsInfo; if(PropsInfo.empty()) { BOOST_PP_SEQ_FOR_EACH(LINK_PROP_INFO,PropsInfo,LINK_PARAMS); } return PropsInfo; } const LinkBaseExtension::PropInfoMap &LinkBaseExtension::getPropertyInfoMap() const { static PropInfoMap PropsMap; if(PropsMap.empty()) { const auto &infos = getPropertyInfo(); for(const auto &info : infos) PropsMap[info.name] = info; } return PropsMap; } Property *LinkBaseExtension::getProperty(int idx) { if(idx>=0 && idx<(int)props.size()) return props[idx]; return nullptr; } Property *LinkBaseExtension::getProperty(const char *name) { const auto &info = getPropertyInfoMap(); auto it = info.find(name); if(it == info.end()) return nullptr; return getProperty(it->second.index); } void LinkBaseExtension::setProperty(int idx, Property *prop) { const auto &infos = getPropertyInfo(); if(idx<0 || idx>=(int)infos.size()) LINK_THROW(Base::RuntimeError,"App::LinkBaseExtension: property index out of range"); if(props[idx]) { props[idx]->setStatus(Property::LockDynamic,false); props[idx] = nullptr; } if(!prop) return; if(!prop->isDerivedFrom(infos[idx].type)) { std::ostringstream str; str << "App::LinkBaseExtension: expected property type '" << infos[idx].type.getName() << "', instead of '" << prop->getClassTypeId().getName() << "'"; LINK_THROW(Base::TypeError,str.str().c_str()); } props[idx] = prop; props[idx]->setStatus(Property::LockDynamic,true); switch(idx) { case PropLinkMode: { static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",nullptr}; auto propLinkMode = static_cast(prop); if(!propLinkMode->hasEnums()) propLinkMode->setEnums(linkModeEnums); break; } case PropLinkCopyOnChange: { static const char *enums[] = {"Disabled","Enabled","Owned","Tracking",nullptr}; auto propEnum = static_cast(prop); if(!propEnum->hasEnums()) propEnum->setEnums(enums); break; } case PropLinkCopyOnChangeSource: case PropLinkCopyOnChangeGroup: if (auto linkProp = Base::freecad_dynamic_cast(prop)) { linkProp->setScope(LinkScope::Global); } // fall through case PropLinkCopyOnChangeTouched: prop->setStatus(Property::Hidden, true); break; case PropLinkTransform: case PropLinkPlacement: case PropPlacement: if(getLinkTransformProperty() && getLinkPlacementProperty() && getPlacementProperty()) { bool transform = getLinkTransformValue(); getPlacementProperty()->setStatus(Property::Hidden, transform); getLinkPlacementProperty()->setStatus(Property::Hidden, !transform); } break; case PropElementList: getElementListProperty()->setScope(LinkScope::Global); getElementListProperty()->setStatus(Property::Hidden, true); // fall through case PropLinkedObject: // Make ElementList as read-only if we are not a group (i.e. having // LinkedObject property), because it is for holding array elements. if(getElementListProperty()) { getElementListProperty()->setStatus( Property::Immutable, getLinkedObjectProperty() != nullptr); } if (auto linkProp = getLinkedObjectProperty()) { linkProp->setScope(LinkScope::Global); } break; case PropVisibilityList: getVisibilityListProperty()->setStatus(Property::Immutable, true); getVisibilityListProperty()->setStatus(Property::Hidden, true); break; } if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) { const char *propName; if(!prop) propName = ""; else if(prop->getContainer()) propName = prop->getName(); else propName = extensionGetPropertyName(prop); if(!Property::isValidName(propName)) propName = "?"; FC_TRACE("set property " << infos[idx].name << ": " << propName); } } static const char _GroupPrefix[] = "Configuration ("; App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute() { // The actual value of LinkTouched is not important, just to notify view // provider that the link (in fact, its dependents, i.e. linked ones) have // recomputed. _LinkTouched.touch(); if(getLinkedObjectProperty()) { DocumentObject *linked = getTrueLinkedObject(true); if(!linked) { std::ostringstream ss; ss << "Link broken!"; auto xlink = Base::freecad_dynamic_cast( getLinkedObjectProperty()); if (xlink) { const char *objname = xlink->getObjectName(); if (objname && objname[0]) ss << "\nObject: " << objname; const char *filename = xlink->getFilePath(); if (filename && filename[0]) ss << "\nFile: " << filename; } return new App::DocumentObjectExecReturn(ss.str().c_str()); } App::DocumentObject *container = getContainer(); auto source = getLinkCopyOnChangeSourceValue(); if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking && getLinkCopyOnChangeTouchedValue()) { syncCopyOnChange(); } // the previous linked object could be deleted by syncCopyOnChange - #12281 linked = getTrueLinkedObject(true); if(!linked) { return new App::DocumentObjectExecReturn("Error in processing variable link"); } PropertyPythonObject *proxy = nullptr; if(getLinkExecuteProperty() && !boost::iequals(getLinkExecuteValue(), "none") && (!_LinkOwner.getValue() || !container->getDocument()->getObjectByID(_LinkOwner.getValue()))) { // Check if this is an element link. Do not invoke appLinkExecute() // if so, because it will be called from the link array. proxy = Base::freecad_dynamic_cast( linked->getPropertyByName("Proxy")); } if(proxy) { Base::PyGILStateLocker lock; const char *errMsg = "Linked proxy execute failed"; try { Py::Tuple args(3); Py::Object proxyValue = proxy->getValue(); const char *method = getLinkExecuteValue(); if(!method || !method[0]) method = "appLinkExecute"; if(proxyValue.hasAttr(method)) { Py::Object attr = proxyValue.getAttr(method); if(attr.ptr() && attr.isCallable()) { Py::Tuple args(4); args.setItem(0, Py::asObject(linked->getPyObject())); args.setItem(1, Py::asObject(container->getPyObject())); if(!_getElementCountValue()) { Py::Callable(attr).apply(args); } else { const auto &elements = _getElementListValue(); for(int i=0; i<_getElementCountValue(); ++i) { args.setItem(2, Py::Int(i)); if(i < (int)elements.size()) args.setItem(3, Py::asObject(elements[i]->getPyObject())); else args.setItem(3, Py::Object()); Py::Callable(attr).apply(args); } } } } } catch (Py::Exception &) { Base::PyException e; e.ReportException(); return new App::DocumentObjectExecReturn(errMsg); } catch (Base::Exception &e) { e.ReportException(); return new App::DocumentObjectExecReturn(errMsg); } } auto parent = getContainer(); setupCopyOnChange(parent); if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) { hasCopyOnChange = false; std::vector props; parent->getPropertyList(props); for(auto prop : props) { if(isCopyOnChangeProperty(parent, *prop)) { try { parent->removeDynamicProperty(prop->getName()); } catch (Base::Exception &e) { e.ReportException(); } catch (...) { } } } } } return inherited::extensionExecute(); } short LinkBaseExtension::extensionMustExecute() { auto link = getLink(); if(!link) return 0; return link->mustExecute(); } std::vector LinkBaseExtension::getOnChangeCopyObjects( std::vector *excludes, App::DocumentObject *src) { auto parent = getContainer(); if (!src) src = getLinkCopyOnChangeSourceValue(); if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled) return {}; auto res = Document::getDependencyList({src}, Document::DepSort); for (auto it=res.begin(); it!=res.end();) { auto obj = *it; if (obj == src) { ++it; continue; } auto prop = Base::freecad_dynamic_cast( obj->getPropertyByName("_CopyOnChangeControl")); static std::map dummy; const auto & map = prop && prop->getContainer()==obj ? prop->getValues() : dummy; const char *v = ""; if (src->getDocument() != obj->getDocument()) v = "-"; auto iter = map.find("*"); if (iter != map.end()) v = iter->second.c_str(); else if ((iter = map.find(parent->getNameInDocument())) != map.end()) v = iter->second.c_str(); if (boost::equals(v, "-")) { if (excludes) excludes->push_back(obj); else { it = res.erase(it); continue; } } ++it; } return res; } void LinkBaseExtension::setOnChangeCopyObject( App::DocumentObject *obj, OnChangeCopyOptions options) { auto parent = getContainer(); Base::Flags flags(options); bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude); bool external = parent->getDocument() != obj->getDocument(); auto prop = Base::freecad_dynamic_cast( obj->getPropertyByName("_CopyOnChangeControl")); if (external == exclude && !prop) return; if (!prop) { try { prop = static_cast( obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl")); } catch (Base::Exception &e) { e.ReportException(); } if (!prop) { FC_ERR("Failed to setup copy on change object " << obj->getFullName()); return; } } const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getDagKey(); if (external) prop->setValue(key, exclude ? "" : "+"); else prop->setValue(key, exclude ? "-" : ""); } // The purpose of this function is to synchronize the mutated copy to the // original linked CopyOnChange object. It will make a new copy if any of the // non-CopyOnChange property of the original object has changed. void LinkBaseExtension::syncCopyOnChange() { if (!getLinkCopyOnChangeValue()) return; auto linkProp = getLinkedObjectProperty(); auto srcProp = getLinkCopyOnChangeSourceProperty(); auto srcTouched = getLinkCopyOnChangeTouchedProperty(); if (!linkProp || !srcProp || !srcTouched || !srcProp->getValue() || !linkProp->getValue() || srcProp->getValue() == linkProp->getValue()) return; auto parent = getContainer(); auto linked = linkProp->getValue(); std::vector oldObjs; std::vector objs; // CopyOnChangeGroup is a hidden dynamic property for holding a LinkGroup // for holding the mutated copy of the original linked object and all its // dependencies. LinkGroup *copyOnChangeGroup = nullptr; if (auto prop = getLinkCopyOnChangeGroupProperty()) { copyOnChangeGroup = Base::freecad_dynamic_cast(prop->getValue()); if (!copyOnChangeGroup) { // Create the LinkGroup if not exist auto group = new LinkGroup; group->LinkMode.setValue(LinkModeAutoDelete); parent->getDocument()->addObject(group, "CopyOnChangeGroup"); prop->setValue(group); } else { // If it exists, then obtain all copied objects. Note that we stores // the dynamic property _SourceUUID in oldObjs if possible, in order // to match the possible new copy later. objs = copyOnChangeGroup->ElementList.getValues(); for (auto obj : objs) { if (!obj->isAttachedToDocument()) continue; auto prop = Base::freecad_dynamic_cast( obj->getPropertyByName("_SourceUUID")); if (prop && prop->getContainer() == obj) oldObjs.emplace_back(prop); else oldObjs.emplace_back(obj); } std::sort(objs.begin(), objs.end()); } } // Obtain the original linked object and its dependency in depending order. // The last being the original linked object. auto srcObjs = getOnChangeCopyObjects(); // Refresh signal connection to monitor changes monitorOnChangeCopyObjects(srcObjs); // Copy the objects. Document::export/importObjects() (called by // copyObject()) will generate a _ObjectUUID for each source object and // match it with a _SourceUUID in the copy. auto copiedObjs = parent->getDocument()->copyObject(srcObjs); if(copiedObjs.empty()) return; // copyObject() will return copy in order of the same order of the input, // so the last object will be the copy of the original linked object auto newLinked = copiedObjs.back(); // We are copying from the original linked object and we've already mutated // it, so we need to copy all CopyOnChange properties from the mutated // object to the new copy. std::vector propList; linked->getPropertyList(propList); for (auto prop : propList) { if(!prop->testStatus(Property::CopyOnChange) || prop->getContainer()!=linked) continue; auto p = newLinked->getPropertyByName(prop->getName()); if (p && p->getTypeId() == prop->getTypeId()) { std::unique_ptr pCopy(prop->Copy()); p->Paste(*pCopy); } } if (copyOnChangeGroup) { // The order of the copied objects is in dependency order (because of // getOnChangeCopyObjects()). We reverse it here so that we can later // on delete it in reverse order to avoid error (because some parent // objects may want to delete their own children). std::reverse(copiedObjs.begin(), copiedObjs.end()); copyOnChangeGroup->ElementList.setValues(copiedObjs); } // Create a map to find the corresponding replacement of the new copies to // the mutated object. The reason for doing so is that we are copying from // the original linked object and its dependency, not the mutated objects // which are old copies. There could be arbitrary changes in the originals // which may add or remove or change depending orders, while the // replacement happens between the new and old copies. std::map newObjs; for (auto obj : copiedObjs) { auto prop = Base::freecad_dynamic_cast( obj->getPropertyByName("_SourceUUID")); if (prop) newObjs.insert(std::make_pair(prop->getValue(), obj)); } std::vector > replacements; for (const auto &objT : oldObjs) { auto prop = Base::freecad_dynamic_cast(objT.getProperty()); if (!prop) continue; auto it = newObjs.find(prop->getValue()); if (it == newObjs.end()) continue; auto oldObj = objT.getObject(); auto newObj = it->second.getObject(); if (oldObj && newObj) replacements.emplace_back(oldObj, newObj); } std::vector > > propChanges; if (!replacements.empty()) { std::sort(copiedObjs.begin(), copiedObjs.end()); // Global search for links affected by the replacement. We accumulate // the changes in propChanges without applying, in order to avoid any // side effect of changing while searching. for(auto doc : App::GetApplication().getDocuments()) { for(auto o : doc->getObjects()) { if (o == parent || std::binary_search(objs.begin(), objs.end(), o) || std::binary_search(copiedObjs.begin(), copiedObjs.end(), o)) continue; propList.clear(); o->getPropertyList(propList); for(auto prop : propList) { if (prop->getContainer() != o) continue; auto linkProp = Base::freecad_dynamic_cast(prop); if(!linkProp) continue; for (const auto &v : replacements) { std::unique_ptr copy( linkProp->CopyOnLinkReplace(parent,v.first,v.second)); if(!copy) continue; propChanges.emplace_back(App::DocumentObjectT(prop),std::move(copy)); } } } } } Base::StateLocker guard(pauseCopyOnChange); linkProp->setValue(newLinked); newLinked->Visibility.setValue(false); srcTouched->setValue(false); // Apply the global link changes. for(const auto &v : propChanges) { auto prop = v.first.getProperty(); if(prop) prop->Paste(*v.second.get()); } // Finally, remove all old copies. oldObjs stores type of DocumentObjectT // which stores the object name. Before removing, we need to make sure the // object exists, and it is not some new object with the same name, by // checking objs which stores DocumentObject pointer. for (const auto &objT : oldObjs) { auto obj = objT.getObject(); if (obj && std::binary_search(objs.begin(), objs.end(), obj)) obj->getDocument()->removeObject(obj->getNameInDocument()); } } bool LinkBaseExtension::isLinkedToConfigurableObject() const { if (auto linked = getLinkedObjectValue()) { std::vector propList; linked->getPropertyList(propList); for (auto prop : propList) { if(prop->testStatus(Property::CopyOnChange) && prop->getContainer()==linked) return true; } } return false; } 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, bool checkSource) { copyOnChangeConns.clear(); copyOnChangeSrcConns.clear(); auto linked = getTrueLinkedObject(false); if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled) return; if (checkSource && !pauseCopyOnChange) { PropertyLink *source = getLinkCopyOnChangeSourceProperty(); if (source) { source->setValue(linked); if (auto touched = getLinkCopyOnChangeTouchedProperty()) touched->setValue(false); } } hasCopyOnChange = setupCopyOnChange(parent,linked,©OnChangeConns,hasCopyOnChange); if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned && getLinkedObjectValue() && getLinkedObjectValue() == getLinkCopyOnChangeSourceValue()) { makeCopyOnChange(); } } 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 = nullptr; 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 = nullptr; } } } 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(const 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 || !parent->getDocument() || parent->getDocument()->isPerformingTransaction()) return; auto linked = getLinkedObjectValue(); if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled || !isCopyOnChangeProperty(parent,prop)) return; if(getLinkCopyOnChangeValue() == CopyOnChangeOwned || (getLinkCopyOnChangeValue() == CopyOnChangeTracking && linked != getLinkCopyOnChangeSourceValue())) { 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 copied = makeCopyOnChange(); if (copied) { linkedProp = copied->getPropertyByName(prop.getName()); if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) { std::unique_ptr pcopy(prop.Copy()); if(pcopy) linkedProp->Paste(*pcopy); } } } App::DocumentObject *LinkBaseExtension::makeCopyOnChange() { auto linked = getLinkedObjectValue(); if (pauseCopyOnChange || !linked) return nullptr; auto parent = getContainer(); auto srcobjs = getOnChangeCopyObjects(nullptr, linked); for (auto obj : srcobjs) { if (obj->testStatus(App::PartialObject)) { FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: " << obj->getFullName()); } } auto objs = parent->getDocument()->copyObject(srcobjs); if(objs.empty()) return nullptr; monitorOnChangeCopyObjects(srcobjs); linked = objs.back(); linked->Visibility.setValue(false); Base::StateLocker guard(pauseCopyOnChange); getLinkedObjectProperty()->setValue(linked); if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled) getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned); if (auto prop = getLinkCopyOnChangeGroupProperty()) { if (auto obj = prop->getValue()) { if (obj->isAttachedToDocument() && obj->getDocument()) obj->getDocument()->removeObject(obj->getNameInDocument()); } auto group = new LinkGroup; group->LinkMode.setValue(LinkModeAutoDelete); getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup"); prop->setValue(group); // The order of the copied objects is in dependency order (because of // getOnChangeCopyObjects()). We reverse it here so that we can later // on delete it in reverse order to avoid error (because some parent // objects may want to delete their own children). std::reverse(objs.begin(), objs.end()); group->ElementList.setValues(objs); } return linked; } void LinkBaseExtension::monitorOnChangeCopyObjects( const std::vector &objs) { copyOnChangeSrcConns.clear(); if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled) return; for(auto obj : objs) { obj->setStatus(App::ObjectStatus::TouchOnColorChange, true); copyOnChangeSrcConns.emplace_back(obj->signalChanged.connect( [this](const DocumentObject &, const Property &) { if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) { if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled) prop->setValue(true); } })); } } App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const { if(!mySubElements.empty() && !mySubElements[0].empty()) return nullptr; auto linked = getTrueLinkedObject(false); if(!linked) return nullptr; return linked->getExtensionByType(true,false); } App::PropertyLinkList *LinkBaseExtension::_getElementListProperty() const { auto group = linkedPlainGroup(); if(group) return &group->Group; return const_cast(getElementListProperty()); } const std::vector &LinkBaseExtension::_getElementListValue() const { if(_ChildCache.getSize()) return _ChildCache.getValues(); if(getElementListProperty()) return getElementListProperty()->getValues(); static const std::vector empty; return empty; } App::PropertyBool *LinkBaseExtension::_getShowElementProperty() const { auto prop = getShowElementProperty(); if(prop && !linkedPlainGroup()) return const_cast(prop); return nullptr; } bool LinkBaseExtension::_getShowElementValue() const { auto prop = _getShowElementProperty(); if(prop) return prop->getValue(); return true; } App::PropertyInteger *LinkBaseExtension::_getElementCountProperty() const { auto prop = getElementCountProperty(); if(prop && !linkedPlainGroup()) return const_cast(prop); return nullptr; } int LinkBaseExtension::_getElementCountValue() const { auto prop = _getElementCountProperty(); if(prop) return prop->getValue(); return 0; } bool LinkBaseExtension::extensionHasChildElement() const { if(!_getElementListValue().empty() || (_getElementCountValue() && _getShowElementValue())) return true; if (getLinkClaimChildValue()) return false; DocumentObject *linked = getTrueLinkedObject(false); if(linked) { if(linked->hasChildElement()) return true; } return false; } int LinkBaseExtension::extensionSetElementVisible(const char *element, bool visible) { int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element); if(index>=0) { auto propElementVis = getVisibilityListProperty(); if(!propElementVis || !element || !element[0]) return -1; if(propElementVis->getSize()<=index) { if(visible) return 1; propElementVis->setSize(index+1, true); } propElementVis->setStatus(Property::User3,true); propElementVis->set1Value(index,visible); propElementVis->setStatus(Property::User3,false); const auto &elements = _getElementListValue(); if(index<(int)elements.size()) { if(!visible) myHiddenElements.insert(elements[index]); else myHiddenElements.erase(elements[index]); } return 1; } DocumentObject *linked = getTrueLinkedObject(false); if(linked) return linked->setElementVisible(element,visible); return -1; } int LinkBaseExtension::extensionIsElementVisible(const char *element) { int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element); if(index>=0) { auto propElementVis = getVisibilityListProperty(); if(propElementVis) { if(propElementVis->getSize()<=index || propElementVis->getValues()[index]) return 1; return 0; } return -1; } DocumentObject *linked = getTrueLinkedObject(false); if(linked) return linked->isElementVisible(element); return -1; } const DocumentObject *LinkBaseExtension::getContainer() const { auto ext = getExtendedContainer(); if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId())) LINK_THROW(Base::RuntimeError,"Link: container not derived from document object"); return static_cast(ext); } DocumentObject *LinkBaseExtension::getContainer(){ auto ext = getExtendedContainer(); if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId())) LINK_THROW(Base::RuntimeError,"Link: container not derived from document object"); return static_cast(ext); } DocumentObject *LinkBaseExtension::getLink(int depth) const{ if (!GetApplication().checkLinkDepth(depth, MessageOption::Error)) return nullptr; if(getLinkedObjectProperty()) return getLinkedObjectValue(); return nullptr; } int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname) { if(!subname || Data::isMappedElement(subname)) return -1; const char *dot = strchr(subname,'.'); if(!dot) dot= subname+strlen(subname); if(dot == subname) return -1; int idx = 0; for(const char *c=subname;c!=dot;++c) { if(!isdigit(*c)) return -1; idx = idx*10 + *c -'0'; } if(psubname) { if(*dot) *psubname = dot+1; else *psubname = dot; } return idx; } int LinkBaseExtension::getElementIndex(const char *subname, const char **psubname) const { if(!subname || Data::isMappedElement(subname)) return -1; int idx = -1; const char *dot = strchr(subname,'.'); if(!dot) dot= subname+strlen(subname); if(isdigit(subname[0])) { // If the name start with digits, treat as index reference idx = getArrayIndex(subname,nullptr); if(idx<0) return -1; if(_getElementCountProperty()) { if(idx>=_getElementCountValue()) return -1; }else if(idx>=(int)_getElementListValue().size()) return -1; }else if(!_getShowElementValue() && _getElementCountValue()) { // If elements are collapsed, we check first for LinkElement naming // pattern, which is the owner object name + "_i" + index const char *name = subname[0]=='$'?subname+1:subname; auto owner = getContainer(); if(owner && owner->isAttachedToDocument()) { std::string ownerName(owner->getNameInDocument()); ownerName += '_'; if(boost::algorithm::starts_with(name,ownerName.c_str())) { for(const char *txt=dot-1;txt>=name+ownerName.size();--txt) { if(*txt == 'i') { idx = getArrayIndex(txt+1,nullptr); if(idx<0 || idx>=_getElementCountValue()) idx = -1; break; } if(!isdigit(*txt)) break; } } } if(idx<0) { // Then check for the actual linked object's name or label, and // redirect that reference to the first array element auto linked = getTrueLinkedObject(false); if(!linked || !linked->isAttachedToDocument()) return -1; if(subname[0]=='$') { CharRange sub(subname+1, dot); if (boost::equals(sub, linked->Label.getValue())) idx = 0; } else { CharRange sub(subname, dot); if (boost::equals(sub, linked->getNameInDocument())) idx = 0; } if(idx<0) { // Lastly, try to get sub object directly from the linked object auto sobj = linked->getSubObject(std::string(subname, dot-subname+1).c_str()); if(!sobj) return -1; if(psubname) *psubname = subname; return 0; } } }else if(subname[0]!='$') { // Try search by element objects' name std::string name(subname,dot); if(_ChildCache.getSize()) { auto obj=_ChildCache.find(name,&idx); if(obj) { auto group = obj->getExtensionByType(true,false); if(group) { int nidx = getElementIndex(dot+1,psubname); if(nidx >= 0) return nidx; } } } else if(getElementListProperty()) getElementListProperty()->find(name.c_str(),&idx); if(idx<0) return -1; }else { // Try search by label if the reference name start with '$' ++subname; std::string name(subname,dot-subname); const auto &elements = _getElementListValue(); if(enableLabelCache) { if(myLabelCache.empty()) cacheChildLabel(1); auto it = myLabelCache.find(name); if(it == myLabelCache.end()) return -1; idx = it->second; }else{ idx = 0; for(auto element : elements) { if(element->Label.getStrValue() == name) break; ++idx; } } if(idx<0 || idx>=(int)elements.size()) return -1; auto obj = elements[idx]; if(obj && _ChildCache.getSize()) { auto group = obj->getExtensionByType(true,false); if(group) { int nidx = getElementIndex(dot+1,psubname); if(nidx >= 0) return nidx; } } } if(psubname) *psubname = dot[0]?dot+1:dot; return idx; } void LinkBaseExtension::elementNameFromIndex(int idx, std::ostream &ss) const { const auto &elements = _getElementListValue(); if(idx < 0 || idx >= (int)elements.size()) return; auto obj = elements[idx]; if(_ChildCache.getSize()) { auto group = GroupExtension::getGroupOfObject(obj); if(group && _ChildCache.find(group->getNameInDocument(),&idx)) elementNameFromIndex(idx,ss); } ss << obj->getNameInDocument() << '.'; } Base::Vector3d LinkBaseExtension::getScaleVector() const { if(getScaleVectorProperty()) return getScaleVectorValue(); double s = getScaleValue(); return Base::Vector3d(s,s,s); } Base::Matrix4D LinkBaseExtension::getTransform(bool transform) const { Base::Matrix4D mat; if(transform) { if(getLinkPlacementProperty()) mat = getLinkPlacementValue().toMatrix(); else if(getPlacementProperty()) mat = getPlacementValue().toMatrix(); } if(getScaleProperty() || getScaleVectorProperty()) { Base::Matrix4D s; s.scale(getScaleVector()); mat *= s; } return mat; } bool LinkBaseExtension::extensionGetSubObjects(std::vector &ret, int reason) const { if(!getLinkedObjectProperty() && getElementListProperty()) { for(auto obj : getElementListProperty()->getValues()) { if(obj && obj->isAttachedToDocument()) { std::string name(obj->getNameInDocument()); name+='.'; ret.push_back(name); } } return true; } if(mySubElements.empty() || mySubElements[0].empty()) { DocumentObject *linked = getTrueLinkedObject(true); if(linked) { if(!_getElementCountValue()) ret = linked->getSubObjects(reason); else{ char index[30]; for(int i=0,count=_getElementCountValue();i1) { ret = mySubElements; } return true; } bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *subname, PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const { ret = nullptr; auto obj = getContainer(); if(!subname || !subname[0]) { ret = const_cast(obj); Base::Matrix4D _mat; if(mat) { // 'mat' here is used as an output to return the accumulated // transformation up until this object. Since 'subname' is empty // here, it means the we are at the end of the hierarchy. We shall // not include scale in the output transformation. // // Think of it this way, the transformation along object hierarchy // is public, while transformation through linkage is private to // link itself. if(transform) { if(getLinkPlacementProperty()) *mat *= getLinkPlacementValue().toMatrix(); else if(getPlacementProperty()) *mat *= getPlacementValue().toMatrix(); } _mat = *mat; } if(pyObj && !_getElementCountValue() && _getElementListValue().empty() && mySubElements.size()<=1) { // Scale will be included here if(getScaleProperty() || getScaleVectorProperty()) { Base::Matrix4D s; s.scale(getScaleVector()); _mat *= s; } auto linked = getTrueLinkedObject(false,&_mat,depth); if(linked && linked!=obj) { linked->getSubObject(mySubElements.empty()?nullptr:mySubElements.front().c_str(), pyObj,&_mat,false,depth+1); checkGeoElementMap(obj,linked,pyObj,nullptr); } } return true; } if(mat) *mat *= getTransform(transform); //DocumentObject *element = 0; bool isElement = false; int idx = getElementIndex(subname,&subname); if(idx>=0) { const auto &elements = _getElementListValue(); if(!elements.empty()) { if(idx>=(int)elements.size() || !elements[idx] || !elements[idx]->isAttachedToDocument()) return true; 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::isMappedElement(subname) || !strchr(subname,'.')) ret = elements[idx]; return true; } int elementCount = _getElementCountValue(); if(idx>=elementCount) return true; isElement = true; if(mat) { auto placementList = getPlacementListProperty(); if(placementList && placementList->getSize()>idx) *mat *= (*placementList)[idx].toMatrix(); auto scaleList = getScaleListProperty(); if(scaleList && scaleList->getSize()>idx) { Base::Matrix4D s; s.scale((*scaleList)[idx]); *mat *= s; } } } auto linked = getTrueLinkedObject(false,mat,depth); if(!linked || linked==obj) return true; Base::Matrix4D matNext; // Because of the addition of LinkClaimChild, the linked object may be // claimed as the first child. Regardless of the current value of // LinkClaimChild, we must accept sub-object path that contains the linked // object, because other link property may store such reference. if (const char* dot=strchr(subname,'.')) { auto group = getLinkCopyOnChangeGroupValue(); if (subname[0] == '$') { CharRange sub(subname+1,dot); if (group && boost::equals(sub, group->Label.getValue())) linked = group; else if(!boost::equals(sub, linked->Label.getValue())) dot = nullptr; } else { CharRange sub(subname,dot); if (group && boost::equals(sub, group->getNameInDocument())) linked = group; else if (!boost::equals(sub, linked->getNameInDocument())) dot = nullptr; } if (dot) { // Because of external linked object, It is possible for and // child object to have the exact same internal name or label // as the parent object. To resolve this potential ambiguity, // try assuming the current subname is referring to the parent // (i.e. the linked object), and if it fails, try again below. if(mat) matNext = *mat; ret = linked->getSubObject(dot+1,pyObj,mat?&matNext:nullptr,false,depth+1); if (ret && dot[1]) subname = dot+1; } } if (!ret) { if(mat) matNext = *mat; ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1); } std::string postfix; if(ret) { // do not resolve the link if we are the last referenced object if(subname && !Data::isMappedElement(subname) && strchr(subname,'.')) { if(mat) *mat = matNext; } // This is a useless check as 'element' is never set to a value other than null //else if(element) { // ret = element; //} else if(!isElement) { ret = const_cast(obj); } else { if(idx) { postfix = Data::POSTFIX_INDEX; postfix += std::to_string(idx); } if(mat) *mat = matNext; } } checkGeoElementMap(obj,linked,pyObj,!postfix.empty()?postfix.c_str():nullptr); return true; } void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj, const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const { if(!pyObj || !*pyObj || (!postfix && obj->getDocument()==linked->getDocument()) || !PyObject_TypeCheck(*pyObj, &Data::ComplexGeoDataPy::Type)) return; // auto geoData = static_cast(*pyObj)->getComplexGeoDataPtr(); // geoData->reTagElementMap(obj->getID(),obj->getDocument()->Hasher,postfix); auto geoData = static_cast(*pyObj)->getComplexGeoDataPtr(); std::string _postfix; if (linked && obj && linked->getDocument() != obj->getDocument()) { _postfix = Data::POSTFIX_EXTERNAL_TAG; if (postfix) { if (!boost::starts_with(postfix, Data::ComplexGeoData::elementMapPrefix())) _postfix += Data::ComplexGeoData::elementMapPrefix(); _postfix += postfix; } postfix = _postfix.c_str(); } geoData->reTagElementMap(obj->getID(),obj->getDocument()->getStringHasher(),postfix); } void LinkBaseExtension::onExtendedUnsetupObject() { if(!getElementListProperty()) return; detachElements(); if (auto obj = getLinkCopyOnChangeGroupValue()) { if(obj->isAttachedToDocument() && !obj->isRemoving()) obj->getDocument()->removeObject(obj->getNameInDocument()); } } DocumentObject *LinkBaseExtension::getTrueLinkedObject( bool recurse, Base::Matrix4D *mat, int depth, bool noElement) const { if(noElement && extensionIsDerivedFrom(LinkElement::getExtensionClassTypeId()) && !static_cast(this)->canDelete()) { return nullptr; } auto ret = getLink(depth); if(!ret) return nullptr; bool transform = linkTransform(); const char *subname = getSubName(); if(subname || (mat && transform)) { ret = ret->getSubObject(subname,nullptr,mat,transform,depth+1); transform = false; } if(ret && recurse) ret = ret->getLinkedObject(recurse,mat,transform,depth+1); if(ret && !ret->isAttachedToDocument()) return nullptr; return ret; } bool LinkBaseExtension::extensionGetLinkedObject(DocumentObject *&ret, bool recurse, Base::Matrix4D *mat, bool transform, int depth) const { if(mat) *mat *= getTransform(transform); ret = nullptr; if(!_getElementCountValue()) ret = getTrueLinkedObject(recurse,mat,depth); if(!ret) ret = const_cast(getContainer()); // always return true to indicate we've handled getLinkObject() call return true; } void LinkBaseExtension::extensionOnChanged(const Property *prop) { auto parent = getContainer(); if(parent && !parent->isRestoring() && prop && !prop->testStatus(Property::User3)) update(parent,prop); inherited::extensionOnChanged(prop); } void LinkBaseExtension::parseSubName() const { // 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(); 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::findElementName(subname); if(!element || !element[0]) { mySubName = subs[0]; if(hasSubElement) mySubElements.emplace_back(""); return; } mySubElements.emplace_back(element); mySubName = std::string(subname,element-subname); for(std::size_t i=1;i(true,false); if(group && &prop == &group->Group) updateGroup(); } void LinkBaseExtension::updateGroup() { std::vector groups; std::unordered_set groupSet; auto group = linkedPlainGroup(); if(group) { groups.push_back(group); groupSet.insert(group->getExtendedObject()); }else{ for(auto o : getElementListProperty()->getValues()) { if(!o || !o->isAttachedToDocument()) continue; auto ext = o->getExtensionByType(true,false); if(ext) { groups.push_back(ext); groupSet.insert(o); } } } std::vector children; if(!groups.empty()) { children = getElementListValue(); std::set childSet(children.begin(),children.end()); for(auto ext : groups) { auto group = ext->getExtendedObject(); auto &conn = plainGroupConns[group]; if(!conn.connected()) { FC_LOG("new group connection " << getExtendedObject()->getFullName() << " -> " << group->getFullName()); //NOLINTBEGIN conn = group->signalChanged.connect( std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2)); //NOLINTEND } std::size_t count = children.size(); ext->getAllChildren(children,childSet); for(;countgetExtensionByType(true,false)) continue; groupSet.insert(child); auto &conn = plainGroupConns[child]; if(!conn.connected()) { FC_LOG("new group connection " << getExtendedObject()->getFullName() << " -> " << child->getFullName()); //NOLINTBEGIN conn = child->signalChanged.connect( std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2)); //NOLINTEND } } } } for(auto it=plainGroupConns.begin();it!=plainGroupConns.end();) { if(!groupSet.count(it->first)) it = plainGroupConns.erase(it); else ++it; } if(children != _ChildCache.getValues()) _ChildCache.setValue(children); } void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop) { if(!prop) return; if(prop == getLinkPlacementProperty() || prop == getPlacementProperty()) { auto src = getLinkPlacementProperty(); auto dst = getPlacementProperty(); if(src!=prop) std::swap(src,dst); if(src && dst) { dst->setStatus(Property::User3,true); dst->setValue(src->getValue()); dst->setStatus(Property::User3,false); } }else if(prop == getScaleProperty()) { if(!prop->testStatus(Property::User3) && getScaleVectorProperty()) { auto s = getScaleValue(); auto p = getScaleVectorProperty(); p->setStatus(Property::User3,true); p->setValue(s,s,s); p->setStatus(Property::User3,false); } }else if(prop == getScaleVectorProperty()) { if(!prop->testStatus(Property::User3) && getScaleProperty()) { const auto &v = getScaleVectorValue(); if(v.x == v.y && v.x == v.z) { auto p = getScaleProperty(); p->setStatus(Property::User3,true); p->setValue(v.x); p->setStatus(Property::User3,false); } } }else if(prop == _getShowElementProperty()) { if(_getShowElementValue()) update(parent,_getElementCountProperty()); else { auto objs = getElementListValue(); // preserve element properties in ourself std::vector placements; placements.reserve(objs.size()); std::vector scales; scales.reserve(objs.size()); for(auto obj : objs) { auto element = freecad_dynamic_cast(obj); if(element) { placements.push_back(element->Placement.getValue()); scales.push_back(element->getScaleVector()); }else{ placements.emplace_back(); scales.emplace_back(1,1,1); } } // touch the property again to make sure view provider has been // signaled before clearing the elements getShowElementProperty()->setStatus(App::Property::User3, true); getShowElementProperty()->touch(); getShowElementProperty()->setStatus(App::Property::User3, false); getElementListProperty()->setValues(std::vector()); if(getPlacementListProperty()) { getPlacementListProperty()->setStatus(Property::User3, getScaleListProperty() != nullptr); getPlacementListProperty()->setValue(placements); getPlacementListProperty()->setStatus(Property::User3, false); } if(getScaleListProperty()) getScaleListProperty()->setValue(scales); for(auto obj : objs) { if(obj && obj->isAttachedToDocument()) obj->getDocument()->removeObject(obj->getNameInDocument()); } } }else if(prop == _getElementCountProperty()) { size_t elementCount = getElementCountValue()<0?0:(size_t)getElementCountValue(); auto propVis = getVisibilityListProperty(); if(propVis) { if(propVis->getSize()>(int)elementCount) propVis->setSize(getElementCountValue(),true); } if(!_getShowElementValue()) { if(getScaleListProperty()) { auto scales = getScaleListValue(); scales.resize(elementCount,Base::Vector3d(1,1,1)); getScaleListProperty()->setStatus(Property::User3,true); getScaleListProperty()->setValue(scales); getScaleListProperty()->setStatus(Property::User3,false); } if(getPlacementListProperty()) { auto placements = getPlacementListValue(); if(placements.size()setStatus(Property::User3,true); getPlacementListProperty()->setValue(placements); getPlacementListProperty()->setStatus(Property::User3,false); } }else if(getElementListProperty()) { auto objs = getElementListValue(); if(elementCount>objs.size()) { std::string name = parent->getNameInDocument(); auto doc = parent->getDocument(); name += "_i"; name = doc->getUniqueObjectName(name.c_str()); if(name[name.size()-1] != 'i') name += "_i"; auto offset = name.size(); auto placementProp = getPlacementListProperty(); auto scaleProp = getScaleListProperty(); const auto &vis = getVisibilityListValue(); auto owner = getContainer(); long ownerID = owner?owner->getID():0; for(size_t i=objs.size();i(doc->getObject(name.c_str())); if(obj && (!obj->_LinkOwner.getValue() || obj->_LinkOwner.getValue()==ownerID)) { obj->Visibility.setValue(false); } else { obj = new LinkElement; parent->getDocument()->addObject(obj,name.c_str()); } if(vis.size()>i && !vis[i]) myHiddenElements.insert(obj); if(placementProp && placementProp->getSize()>(int)i) obj->Placement.setValue(placementProp->getValues()[i]); else{ Base::Placement pla(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation()); obj->Placement.setValue(pla); } if(scaleProp && scaleProp->getSize()>(int)i) obj->Scale.setValue(scaleProp->getValues()[i].x); else obj->Scale.setValue(1); objs.push_back(obj); } if(getPlacementListProperty()) getPlacementListProperty()->setSize(0); if(getScaleListProperty()) getScaleListProperty()->setSize(0); getElementListProperty()->setValue(objs); }else if(elementCount tmpObjs; auto owner = getContainer(); long ownerID = owner?owner->getID():0; while(objs.size()>elementCount) { auto element = freecad_dynamic_cast(objs.back()); if(element && element->_LinkOwner.getValue()==ownerID) tmpObjs.push_back(objs.back()); objs.pop_back(); } getElementListProperty()->setValue(objs); for(auto obj : tmpObjs) { if(obj && obj->isAttachedToDocument()) obj->getDocument()->removeObject(obj->getNameInDocument()); } } } }else if(prop == getVisibilityListProperty()) { if(_getShowElementValue()) { const auto &elements = _getElementListValue(); const auto &vis = getVisibilityListValue(); myHiddenElements.clear(); for(size_t i=0;i=elements.size()) break; if(!vis[i]) myHiddenElements.insert(elements[i]); } } }else if(prop == getElementListProperty() || prop == &_ChildCache) { if(prop == getElementListProperty()) { _ChildCache.setStatus(Property::User3,true); updateGroup(); _ChildCache.setStatus(Property::User3,false); } const auto &elements = _getElementListValue(); if(enableLabelCache) myLabelCache.clear(); // Element list changed, we need to sychrnoize VisibilityList. if(_getShowElementValue() && getVisibilityListProperty()) { if(parent->getDocument()->isPerformingTransaction()) { update(parent,getVisibilityListProperty()); }else{ boost::dynamic_bitset<> vis; vis.resize(elements.size(),true); std::unordered_set hiddenElements; for(size_t i=0;isetStatus(Property::User3,true); propVis->setValue(vis); propVis->setStatus(Property::User3,false); } } } syncElementList(); if(_getShowElementValue() && _getElementCountProperty() && getElementListProperty() && getElementCountValue()!=getElementListProperty()->getSize()) { getElementCountProperty()->setValue( getElementListProperty()->getSize()); } }else if(prop == getLinkedObjectProperty()) { auto group = linkedPlainGroup(); if(getShowElementProperty()) getShowElementProperty()->setStatus(Property::Hidden, !!group); if(getElementCountProperty()) getElementCountProperty()->setStatus(Property::Hidden, !!group); if(group) updateGroup(); else if(_ChildCache.getSize()) _ChildCache.setValue(); parseSubName(); syncElementList(); if(getLinkCopyOnChangeValue()==CopyOnChangeOwned && !pauseCopyOnChange && !parent->getDocument()->isPerformingTransaction()) getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled); else setupCopyOnChange(parent, true); }else if(prop == getLinkCopyOnChangeProperty()) { setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr); } else if (prop == getLinkCopyOnChangeSourceProperty()) { if (auto source = getLinkCopyOnChangeSourceValue()) { this->connCopyOnChangeSource = source->signalChanged.connect( [this](const DocumentObject & obj, const Property &prop) { auto src = getLinkCopyOnChangeSourceValue(); if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled) return; if (App::Document::isAnyRestoring() || obj.testStatus(ObjectStatus::NoTouch) || (prop.getType() & Prop_Output) || prop.testStatus(Property::Output)) return; if (auto propTouch = getLinkCopyOnChangeTouchedProperty()) propTouch->setValue(true); }); } else this->connCopyOnChangeSource.disconnect(); }else if(prop == getLinkTransformProperty()) { auto linkPlacement = getLinkPlacementProperty(); auto placement = getPlacementProperty(); if(linkPlacement && placement) { bool transform = getLinkTransformValue(); placement->setStatus(Property::Hidden,transform); linkPlacement->setStatus(Property::Hidden,!transform); } syncElementList(); } else { checkCopyOnChange(parent, *prop); } } void LinkBaseExtension::cacheChildLabel(int enable) const { enableLabelCache = enable?true:false; myLabelCache.clear(); if(enable<=0) return; int idx = 0; for(auto child : _getElementListValue()) { if(child && child->isAttachedToDocument()) myLabelCache[child->Label.getStrValue()] = idx; ++idx; } } bool LinkBaseExtension::linkTransform() const { if(!getLinkTransformProperty() && !getLinkPlacementProperty() && !getPlacementProperty()) return true; return getLinkTransformValue(); } void LinkBaseExtension::syncElementList() { auto transform = getLinkTransformProperty(); auto link = getLinkedObjectProperty(); auto xlink = freecad_dynamic_cast(link); auto owner = getContainer(); auto ownerID = owner?owner->getID():0; auto elements = getElementListValue(); for (auto i : elements) { auto element = freecad_dynamic_cast(i); if (!element || (element->_LinkOwner.getValue() && element->_LinkOwner.getValue() != ownerID)) continue; element->_LinkOwner.setValue(ownerID); element->LinkTransform.setStatus(Property::Hidden, transform != nullptr); element->LinkTransform.setStatus(Property::Immutable, transform != nullptr); if (transform && element->LinkTransform.getValue() != transform->getValue()) element->LinkTransform.setValue(transform->getValue()); element->LinkedObject.setStatus(Property::Hidden, link != nullptr); element->LinkedObject.setStatus(Property::Immutable, link != nullptr); if (element->LinkCopyOnChange.getValue() == 2) continue; if (xlink) { if (element->LinkedObject.getValue() != xlink->getValue() || element->LinkedObject.getSubValues() != xlink->getSubValues()) { element->LinkedObject.setValue(xlink->getValue(), xlink->getSubValues()); } } else if (element->LinkedObject.getValue() != link->getValue() || !element->LinkedObject.getSubValues().empty()) { element->setLink(-1, link->getValue()); } } } void LinkBaseExtension::onExtendedDocumentRestored() { inherited::onExtendedDocumentRestored(); myHiddenElements.clear(); auto parent = getContainer(); if(!parent) return; if(hasOldSubElement) { hasOldSubElement = false; // SubElements was stored as a PropertyStringList. It is now migrated to be // stored inside PropertyXLink. auto xlink = freecad_dynamic_cast(getLinkedObjectProperty()); if(!xlink) FC_ERR("Failed to restore SubElements for " << parent->getFullName()); else if(!xlink->getValue()) FC_ERR("Discard SubElements of " << parent->getFullName() << " due to null link"); else if(xlink->getSubValues().size() > 1) FC_ERR("Failed to restore SubElements for " << parent->getFullName() << " due to conflict subnames"); else if(xlink->getSubValues().empty()) { auto subs = xlink->getSubValues(); xlink->setSubValues(std::move(subs)); } else { std::set subset(mySubElements.begin(),mySubElements.end()); auto sub = xlink->getSubValues().front(); auto element = Data::findElementName(sub.c_str()); if(element && element[0]) { subset.insert(element); sub.resize(element - sub.c_str()); } std::vector subs; for(const auto &s : subset) subs.push_back(sub + s); xlink->setSubValues(std::move(subs)); } } if(getScaleVectorProperty() && getScaleProperty()) { // Scale vector is added later. The code here is for migration. const auto &v = getScaleVectorValue(); double s = getScaleValue(); if(v.x == v.y && v.x == v.z && v.x != s) getScaleVectorProperty()->setValue(s,s,s); } update(parent,getVisibilityListProperty()); if (auto prop = getLinkedObjectProperty()) { Base::StateLocker guard(pauseCopyOnChange); update(parent,prop); } update(parent,getLinkCopyOnChangeSourceProperty()); update(parent,getElementListProperty()); if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled) monitorOnChangeCopyObjects(getOnChangeCopyObjects()); } void LinkBaseExtension::_handleChangedPropertyName( Base::XMLReader &reader, const char * TypeName, const char *PropName) { if(strcmp(PropName,"SubElements")==0 && strcmp(TypeName,PropertyStringList::getClassTypeId().getName())==0) { PropertyStringList prop; prop.setContainer(getContainer()); prop.Restore(reader); if(prop.getSize()) { mySubElements = prop.getValues(); hasOldSubElement = true; } } } void LinkBaseExtension::setLink(int index, DocumentObject *obj, const char *subname, const std::vector &subElements) { auto parent = getContainer(); if(!parent) LINK_THROW(Base::RuntimeError,"No parent container"); if(obj && !App::Document::isAnyRestoring()) { auto inSet = parent->getInListEx(true); inSet.insert(parent); if(inSet.find(obj)!=inSet.end()) LINK_THROW(Base::RuntimeError,"Cyclic dependency"); } auto linkProp = getLinkedObjectProperty(); // If we are a group (i.e. no LinkObject property), and the index is // negative with a non-zero 'obj' assignment, we treat this as group // expansion by changing the index to one pass the existing group size if(index<0 && obj && !linkProp && getElementListProperty()) index = getElementListProperty()->getSize(); if(index>=0) { // LinkGroup assignment if(linkProp || !getElementListProperty()) LINK_THROW(Base::RuntimeError,"Cannot set link element"); DocumentObject *old = nullptr; const auto &elements = getElementListProperty()->getValues(); if(!obj) { if(index>=(int)elements.size()) LINK_THROW(Base::ValueError,"Link element index out of bound"); std::vector objs; old = elements[index]; for(int i=0;i<(int)elements.size();++i) { if(i!=index) objs.push_back(elements[i]); } getElementListProperty()->setValue(objs); }else if(!obj->isAttachedToDocument()) LINK_THROW(Base::ValueError,"Invalid object"); else{ if(index>(int)elements.size()) LINK_THROW(Base::ValueError,"Link element index out of bound"); if(index < (int)elements.size()) old = elements[index]; int idx = -1; if(getLinkModeValue()>=LinkModeAutoLink || (subname && subname[0]) || !subElements.empty() || obj->getDocument()!=parent->getDocument() || (getElementListProperty()->find(obj->getNameInDocument(),&idx) && idx!=index)) { std::string name = parent->getDocument()->getUniqueObjectName("Link"); auto link = new Link; link->_LinkOwner.setValue(parent->getID()); parent->getDocument()->addObject(link,name.c_str()); link->setLink(-1,obj,subname,subElements); auto linked = link->getTrueLinkedObject(true); if(linked) link->Label.setValue(linked->Label.getValue()); auto pla = freecad_dynamic_cast(obj->getPropertyByName("Placement")); if(pla) link->Placement.setValue(pla->getValue()); link->Visibility.setValue(false); obj = link; } if(old == obj) return; getElementListProperty()->set1Value(index,obj); } detachElement(old); return; } if(!linkProp) { // Reaching here means, we are group (i.e. no LinkedObject), and // index<0, and 'obj' is zero. We shall clear the whole group if(obj || !getElementListProperty()) LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured"); detachElements(); return; } // Here means we are assigning a Link auto xlink = freecad_dynamic_cast(linkProp); if(obj) { if(!obj->isAttachedToDocument()) LINK_THROW(Base::ValueError,"Invalid document object"); if(!xlink) { if(parent && obj->getDocument()!=parent->getDocument()) LINK_THROW(Base::ValueError,"Cannot link to external object without PropertyXLink"); } } if(!xlink) { if(!subElements.empty() || (subname && subname[0])) LINK_THROW(Base::RuntimeError,"SubName/SubElement link requires PropertyXLink"); linkProp->setValue(obj); return; } std::vector subs; if(!subElements.empty()) { 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::detachElements() { std::vector objs; for (auto obj : getElementListValue()) objs.emplace_back(obj); getElementListProperty()->setValue(); for(const auto &objT : objs) detachElement(objT.getObject()); } void LinkBaseExtension::detachElement(DocumentObject *obj) { if(!obj || !obj->isAttachedToDocument() || obj->isRemoving()) return; auto ext = obj->getExtensionByType(true); auto owner = getContainer(); long ownerID = owner?owner->getID():0; if(getLinkModeValue()==LinkModeAutoUnlink) { if(!ext || ext->_LinkOwner.getValue()!=ownerID) return; }else if(getLinkModeValue()!=LinkModeAutoDelete) { if(ext && ext->_LinkOwner.getValue()==ownerID) ext->_LinkOwner.setValue(0); return; } obj->getDocument()->removeObject(obj->getNameInDocument()); } std::vector LinkBaseExtension::getLinkedChildren(bool filter) const{ if(!filter) return _getElementListValue(); std::vector ret; for(auto o : _getElementListValue()) { if(!o->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) ret.push_back(o); } return ret; } const char *LinkBaseExtension::flattenSubname(const char *subname) const { if(subname && _ChildCache.getSize()) { const char *sub = subname; std::string s; for(const char* dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) { DocumentObject *obj = nullptr; s.clear(); s.append(sub,dot+1); extensionGetSubObject(obj,s.c_str()); if(!obj) break; if(!obj->hasExtension(GroupExtension::getExtensionClassTypeId(),false)) return sub; } } return subname; } void LinkBaseExtension::expandSubname(std::string &subname) const { if(!_ChildCache.getSize()) return; const char *pos = nullptr; int index = getElementIndex(subname.c_str(),&pos); if(index<0) return; std::ostringstream ss; elementNameFromIndex(index,ss); ss << pos; subname = ss.str(); } static bool isExcludedProperties(const char *name) { #define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true; CHECK_EXCLUDE_PROP(Shape); CHECK_EXCLUDE_PROP(Proxy); CHECK_EXCLUDE_PROP(Placement); return false; } Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const { if (checkingProperty) return inherited::extensionGetPropertyByName(name); Base::StateLocker guard(checkingProperty); if(isExcludedProperties(name)) return nullptr; auto owner = getContainer(); if (owner) { App::Property *prop = owner->getPropertyByName(name); if(prop) return prop; if(owner->canLinkProperties()) { auto linked = getTrueLinkedObject(true); if(linked) return linked->getPropertyByName(name); } } return nullptr; } bool LinkBaseExtension::isLinkMutated() const { return getLinkCopyOnChangeValue() != CopyOnChangeDisabled && getLinkedObjectValue() && (!getLinkCopyOnChangeSourceValue() || (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue())); } /////////////////////////////////////////////////////////////////////////////////////////// namespace App { EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkBaseExtensionPython, App::LinkBaseExtension) // explicit template instantiation template class AppExport ExtensionPythonT; } ////////////////////////////////////////////////////////////////////////////// EXTENSION_PROPERTY_SOURCE(App::LinkExtension, App::LinkBaseExtension) LinkExtension::LinkExtension() { initExtensionType(LinkExtension::getExtensionClassTypeId()); LINK_PROPS_ADD_EXTENSION(LINK_PARAMS_EXT); } /////////////////////////////////////////////////////////////////////////////////////////// namespace App { EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkExtensionPython, App::LinkExtension) // explicit template instantiation template class AppExport ExtensionPythonT; } /////////////////////////////////////////////////////////////////////////////////////////// PROPERTY_SOURCE_WITH_EXTENSIONS(App::Link, App::DocumentObject) Link::Link() { LINK_PROPS_ADD(LINK_PARAMS_LINK); LinkExtension::initExtension(this); static const PropertyIntegerConstraint::Constraints s_constraints = {0,INT_MAX,1}; ElementCount.setConstraints(&s_constraints); } bool Link::canLinkProperties() const { return true; } ////////////////////////////////////////////////////////////////////////////////////////// namespace App { PROPERTY_SOURCE_TEMPLATE(App::LinkPython, App::Link) template<> const char* App::LinkPython::getViewProviderName() const { return "Gui::ViewProviderLinkPython"; } template class AppExport FeaturePythonT; } ////////////////////////////////////////////////////////////////////////////////////////// PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkElement, App::DocumentObject) LinkElement::LinkElement() { LINK_PROPS_ADD(LINK_PARAMS_ELEMENT); LinkBaseExtension::initExtension(this); } bool LinkElement::canDelete() const { if(!_LinkOwner.getValue()) return true; auto owner = getContainer(); return !owner || !owner->getDocument()->getObjectByID(_LinkOwner.getValue()); } ////////////////////////////////////////////////////////////////////////////////////////// namespace App { PROPERTY_SOURCE_TEMPLATE(App::LinkElementPython, App::LinkElement) template<> const char* App::LinkElementPython::getViewProviderName() const { return "Gui::ViewProviderLinkPython"; } template class AppExport FeaturePythonT; } ////////////////////////////////////////////////////////////////////////////////////////// PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkGroup, App::DocumentObject) LinkGroup::LinkGroup() { LINK_PROPS_ADD(LINK_PARAMS_GROUP); LinkBaseExtension::initExtension(this); } ////////////////////////////////////////////////////////////////////////////////////////// namespace App { PROPERTY_SOURCE_TEMPLATE(App::LinkGroupPython, App::LinkGroup) template<> const char* App::LinkGroupPython::getViewProviderName() const { return "Gui::ViewProviderLinkPython"; } template class AppExport FeaturePythonT; } #if defined(__clang__) # pragma clang diagnostic pop #endif