/*************************************************************************** * Copyright (c) 2002 Jürgen Riegel * * * * 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 #include #include #include "PropertyLinks.h" #include "Application.h" #include "Document.h" #include "DocumentObject.h" #include "DocumentObjectPy.h" #include "DocumentObserver.h" #include "ObjectIdentifier.h" #include "ElementNamingUtils.h" #include "GeoFeature.h" FC_LOG_LEVEL_INIT("PropertyLinks", true, true) using namespace App; using namespace Base; using namespace std; namespace sp = std::placeholders; //************************************************************************** //************************************************************************** // PropertyLinkBase //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkBase, App::Property) // clang-format off static std::unordered_map> _LabelMap; static std::unordered_map> _ElementRefMap; // clang-format on PropertyLinkBase::PropertyLinkBase() = default; PropertyLinkBase::~PropertyLinkBase() { unregisterLabelReferences(); unregisterElementReference(); } void PropertyLinkBase::setAllowExternal(bool allow) { setFlag(LinkAllowExternal, allow); } void PropertyLinkBase::setSilentRestore(bool allow) { setFlag(LinkSilentRestore, allow); } void PropertyLinkBase::setReturnNewElement(bool enable) { setFlag(LinkNewElement, enable); } void PropertyLinkBase::hasSetValue() { auto owner = freecad_cast(getContainer()); if (owner) { owner->clearOutListCache(); } Property::hasSetValue(); } bool PropertyLinkBase::isSame(const Property& other) const { if (&other == this) { return true; } if (other.isDerivedFrom() || getScope() != static_cast(&other)->getScope()) { return false; } static std::vector ret; static std::vector subs; static std::vector ret2; static std::vector subs2; ret.clear(); subs.clear(); ret2.clear(); subs2.clear(); getLinks(ret, true, &subs, false); static_cast(&other)->getLinks(ret2, true, &subs2, true); return ret == ret2 && subs == subs2; } void PropertyLinkBase::unregisterElementReference() { for (auto obj : _ElementRefs) { auto it = _ElementRefMap.find(obj); if (it != _ElementRefMap.end()) { it->second.erase(this); if (it->second.empty()) { _ElementRefMap.erase(it); } } } _ElementRefs.clear(); } void PropertyLinkBase::unregisterLabelReferences() { for (auto& label : _LabelRefs) { auto it = _LabelMap.find(label); if (it != _LabelMap.end()) { it->second.erase(this); if (it->second.empty()) { _LabelMap.erase(it); } } } _LabelRefs.clear(); } void PropertyLinkBase::getLabelReferences(std::vector& subs, const char* subname) { const char* dot; for (; (subname = strchr(subname, '$')) != nullptr; subname = dot + 1) { ++subname; dot = strchr(subname, '.'); if (!dot) { break; } subs.emplace_back(subname, dot - subname); } } void PropertyLinkBase::registerLabelReferences(std::vector&& labels, bool reset) { if (reset) { unregisterLabelReferences(); } for (auto& label : labels) { auto res = _LabelRefs.insert(std::move(label)); if (res.second) { _LabelMap[*res.first].insert(this); } } } void PropertyLinkBase::checkLabelReferences(const std::vector& subs, bool reset) { if (reset) { unregisterLabelReferences(); } std::vector labels; for (auto& sub : subs) { labels.clear(); getLabelReferences(labels, sub.c_str()); registerLabelReferences(std::move(labels), false); } } std::string PropertyLinkBase::updateLabelReference(const App::DocumentObject* parent, const char* subname, const App::DocumentObject* obj, const std::string& ref, const char* newLabel) { if (!obj || !obj->isAttachedToDocument() || !parent || !parent->isAttachedToDocument()) { return {}; } // Because the label is allowed to be the same across different // hierarchies, we have to search for all occurrences, and make sure the // referenced sub-object at the found hierarchy is actually the given // object. for (const char* pos = subname; ((pos = strstr(pos, ref.c_str())) != nullptr); pos += ref.size()) { auto sub = std::string(subname, pos + ref.size() - subname); auto sobj = parent->getSubObject(sub.c_str()); if (sobj == obj) { sub = subname; sub.replace(pos + 1 - subname, ref.size() - 2, newLabel); return sub; } } return {}; } std::vector>> PropertyLinkBase::updateLabelReferences(App::DocumentObject* obj, const char* newLabel) { std::vector>> ret; if (!obj || !obj->isAttachedToDocument()) { return ret; } auto it = _LabelMap.find(obj->Label.getStrValue()); if (it == _LabelMap.end()) { return ret; } std::string ref("$"); ref += obj->Label.getValue(); ref += '.'; std::vector props; props.reserve(it->second.size()); props.insert(props.end(), it->second.begin(), it->second.end()); for (auto prop : props) { if (!prop->getContainer()) { continue; } std::unique_ptr copy(prop->CopyOnLabelChange(obj, ref, newLabel)); if (copy) { ret.emplace_back(prop, std::move(copy)); } } return ret; } static std::string propertyName(const Property* prop) { if (!prop) { return {}; } if (!prop->getContainer() || !prop->hasName()) { auto xlink = freecad_cast(prop); if (xlink) { return propertyName(xlink->parent()); } } return prop->getFullName(); } const std::unordered_set& PropertyLinkBase::getElementReferences(DocumentObject* feature) { static std::unordered_set none; auto it = _ElementRefMap.find(feature); if (it == _ElementRefMap.end()) { return none; } return it->second; } void PropertyLinkBase::updateElementReferences(DocumentObject* feature, bool reverse) { if (!feature || !feature->getNameInDocument()) { return; } auto it = _ElementRefMap.find(feature); if (it == _ElementRefMap.end()) { return; } std::vector props; props.reserve(it->second.size()); props.insert(props.end(), it->second.begin(), it->second.end()); for (auto prop : props) { if (prop->getContainer()) { try { prop->updateElementReference(feature, reverse, true); } catch (Base::Exception& e) { e.reportException(); FC_ERR("Failed to update element reference of " << propertyName(prop)); } catch (std::exception& e) { FC_ERR("Failed to update element reference of " << propertyName(prop) << ": " << e.what()); } } } } void PropertyLinkBase::_registerElementReference(App::DocumentObject* obj, std::string& sub, ShadowSub& shadow) { if (!obj || !obj->getNameInDocument() || sub.empty()) { return; } if (shadow.newName.empty()) { _updateElementReference(nullptr, obj, sub, shadow, false); return; } GeoFeature* geo = nullptr; const char* element = nullptr; ShadowSub elementName; GeoFeature::resolveElement(obj, sub.c_str(), elementName, true, GeoFeature::ElementNameType::Export, nullptr, &element, &geo); if (!geo || !element || !element[0]) { return; } if (_ElementRefs.insert(geo).second) { _ElementRefMap[geo].insert(this); } } class StringGuard { public: explicit StringGuard(char* c) : c(c) { v1 = c[0]; v2 = c[1]; c[0] = '.'; c[1] = 0; } ~StringGuard() { c[0] = v1; c[1] = v2; } char* c; char v1; char v2; }; void PropertyLinkBase::restoreLabelReference(const DocumentObject* obj, std::string& subname, ShadowSub* shadow) { std::ostringstream ss; char* sub = &subname[0]; char* next = sub; for (char* dot = strchr(next, '.'); dot; next = dot + 1, dot = strchr(next, '.')) { if (dot != next && dot[-1] != '@') { continue; } DocumentObject* sobj; try { StringGuard guard(dot - 1); sobj = obj->getSubObject(subname.c_str()); if (!sobj) { FC_ERR("Failed to restore label reference " << obj->getFullName() << '.' << ss.str()); return; } } catch (...) { throw; } ss.write(sub, next - sub); ss << '$' << sobj->Label.getStrValue() << '.'; sub = dot + 1; } if (sub == subname.c_str()) { return; } size_t count = sub - subname.c_str(); const auto& newSub = ss.str(); if (shadow && shadow->oldName.size() >= count) { shadow->oldName = newSub + (shadow->oldName.c_str() + count); } if (shadow && shadow->newName.size() >= count) { shadow->newName = newSub + (shadow->newName.c_str() + count); } subname = newSub + sub; } bool PropertyLinkBase::_updateElementReference(DocumentObject* feature, App::DocumentObject* obj, std::string& sub, ShadowSub& shadow, bool reverse, bool notify) { if (!obj || !obj->getNameInDocument()) { return false; } ShadowSub elementName; const char* subname; if (shadow.newName.size()) { subname = shadow.newName.c_str(); } else if (shadow.oldName.size()) { subname = shadow.oldName.c_str(); } else { subname = sub.c_str(); } GeoFeature* geo = nullptr; const char* element = nullptr; auto ret = GeoFeature::resolveElement(obj, subname, elementName, true, GeoFeature::ElementNameType::Export, feature, &element, &geo); if (!ret || !geo || !element || !element[0]) { if (elementName.oldName.size()) { shadow.oldName.swap(elementName.oldName); } return false; } if (_ElementRefs.insert(geo).second) { _ElementRefMap[geo].insert(this); } if (!reverse) { if (elementName.newName.empty()) { shadow.oldName.swap(elementName.oldName); return false; } if (shadow == elementName) { return false; } } bool missing = GeoFeature::hasMissingElement(elementName.oldName.c_str()); if (feature == geo && (missing || reverse)) { // If the referenced element is missing, or we are generating element // map for the first time, or we are re-generating the element map due // to version change, i.e. 'reverse', try search by geometry first const char* oldElement = Data::findElementName(shadow.oldName.c_str()); if (!Data::hasMissingElement(oldElement)) { const auto& names = geo->searchElementCache(oldElement); if (names.size()) { missing = false; std::string newsub(subname, strlen(subname) - strlen(element)); newsub += names.front(); GeoFeature::resolveElement(obj, newsub.c_str(), elementName, true, GeoFeature::ElementNameType::Export, feature); const auto& oldName = shadow.newName.size() ? shadow.newName : shadow.oldName; const auto& newName = elementName.newName.size() ? elementName.newName : elementName.oldName; if (oldName != newName) { FC_LOG(propertyName(this) << " auto change element reference " << ret->getFullName() << " " << oldName << " -> " << newName); } } } } if (notify) { aboutToSetValue(); } auto updateSub = [&](const std::string& newSub) { if (sub != newSub) { // signalUpdateElementReference(sub, newSub); sub = newSub; } }; if (missing) { FC_WARN(propertyName(this) << " missing element reference " << ret->getFullName() << " " << (elementName.newName.size() ? elementName.newName : elementName.oldName)); shadow.oldName.swap(elementName.oldName); } else { FC_TRACE(propertyName(this) << " element reference shadow update " << ret->getFullName() << " " << shadow.newName << " -> " << elementName.newName); shadow.swap(elementName); if (shadow.newName.size() && Data::hasMappedElementName(sub.c_str())) { updateSub(shadow.newName); } } if (reverse) { if (shadow.newName.size() && Data::hasMappedElementName(sub.c_str())) { updateSub(shadow.newName); } else { updateSub(shadow.oldName); } return true; } if (missing) { if (sub != shadow.newName) { updateSub(shadow.oldName); } return true; } auto pos2 = shadow.newName.rfind('.'); if (pos2 == std::string::npos) { return true; } ++pos2; auto pos = sub.rfind('.'); if (pos == std::string::npos) { pos = 0; } else { ++pos; } if (pos == pos2) { if (sub.compare(pos, sub.size() - pos, &shadow.newName[pos2]) != 0) { FC_LOG("element reference update " << sub << " -> " << shadow.newName); std::string newSub(sub); newSub.replace(pos, sub.size() - pos, &shadow.newName[pos2]); updateSub(newSub); } } else if (sub != shadow.oldName) { FC_LOG("element reference update " << sub << " -> " << shadow.oldName); updateSub(shadow.oldName); } return true; } std::pair PropertyLinkBase::tryReplaceLink(const PropertyContainer* owner, DocumentObject* obj, const DocumentObject* parent, DocumentObject* oldObj, DocumentObject* newObj, const char* subname) { std::pair res; res.first = 0; if (!obj) { return res; } if (oldObj == obj) { if (owner == parent) { res.first = newObj; if (subname) { res.second = subname; } return res; } return res; } else if (newObj == obj) { // This means the new object is already sub-object of this parent // (consider a case of swapping the tool and base object of the Cut // feature). We'll swap the old and new object. return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); } if (!subname || !subname[0]) { return res; } App::DocumentObject* prev = obj; std::size_t prevPos = 0; std::string sub = subname; for (auto pos = sub.find('.'); pos != std::string::npos; pos = sub.find('.', pos)) { ++pos; char c = sub[pos]; if (c == '.') { continue; } sub[pos] = 0; auto sobj = obj->getSubObject(sub.c_str()); sub[pos] = c; if (!sobj) { break; } if (sobj == oldObj) { if (prev == parent) { if (sub[prevPos] == '$') { sub.replace(prevPos + 1, pos - 1 - prevPos, newObj->Label.getValue()); } else { sub.replace(prevPos, pos - 1 - prevPos, newObj->getNameInDocument()); } res.first = obj; res.second = std::move(sub); return res; } break; } else if (sobj == newObj) { return tryReplaceLink(owner, obj, parent, newObj, oldObj, subname); } else if (prev == parent) { break; } prev = sobj; prevPos = pos; } return res; } std::pair> PropertyLinkBase::tryReplaceLinkSubs(const PropertyContainer* owner, DocumentObject* obj, const DocumentObject* parent, DocumentObject* oldObj, DocumentObject* newObj, const std::vector& subs) { std::pair> res; res.first = 0; if (!obj) { return res; } auto r = tryReplaceLink(owner, obj, parent, oldObj, newObj); if (r.first) { res.first = r.first; res.second = subs; return res; } for (auto it = subs.begin(); it != subs.end(); ++it) { auto r = tryReplaceLink(owner, obj, parent, oldObj, newObj, it->c_str()); if (r.first) { if (!res.first) { res.first = r.first; res.second.insert(res.second.end(), subs.begin(), it); } res.second.push_back(std::move(r.second)); } else if (res.first) { res.second.push_back(*it); } } return res; } //************************************************************************** //************************************************************************** // PropertyLinkListBase //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyLinkListBase, App::PropertyLinkBase) //************************************************************************** //************************************************************************** // PropertyLink //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyLink, App::PropertyLinkBase) TYPESYSTEM_SOURCE(App::PropertyLinkChild, App::PropertyLink) TYPESYSTEM_SOURCE(App::PropertyLinkGlobal, App::PropertyLink) TYPESYSTEM_SOURCE(App::PropertyLinkHidden, App::PropertyLink) //************************************************************************** // Construction/Destruction PropertyLink::PropertyLink() = default; PropertyLink::~PropertyLink() { resetLink(); } //************************************************************************** // Base class implementer void PropertyLink::resetLink() { // in case this property gets dynamically removed // maintain the back link in the DocumentObject class if it is from a document object if (_pcScope != LinkScope::Hidden && _pcLink && getContainer() && getContainer()->isDerivedFrom()) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy)) { if (_pcLink) { _pcLink->_removeBackLink(parent); } } } _pcLink = nullptr; } void PropertyLink::setValue(App::DocumentObject* lValue) { auto parent = dynamic_cast(getContainer()); if (!testFlag(LinkAllowExternal) && parent && lValue && parent->getDocument() != lValue->getDocument()) { throw Base::ValueError("PropertyLink does not support external object"); } aboutToSetValue(); // maintain the back link in the DocumentObject class if it is from a document object if (_pcScope != LinkScope::Hidden && parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy)) { if (_pcLink) { _pcLink->_removeBackLink(parent); } if (lValue) { lValue->_addBackLink(parent); } } } _pcLink = lValue; hasSetValue(); } App::DocumentObject* PropertyLink::getValue() const { return _pcLink; } App::DocumentObject* PropertyLink::getValue(Base::Type t) const { return (_pcLink && _pcLink->isDerivedFrom(t)) ? _pcLink : nullptr; } PyObject* PropertyLink::getPyObject() { if (_pcLink) { return _pcLink->getPyObject(); } else { Py_Return; } } void PropertyLink::setPyObject(PyObject* value) { Base::PyTypeCheck(&value, &DocumentObjectPy::Type); if (value) { DocumentObjectPy* pcObject = static_cast(value); setValue(pcObject->getDocumentObjectPtr()); } else { setValue(nullptr); } } void PropertyLink::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << "getExportName() : "") << "\"/>" << std::endl; } void PropertyLink::Restore(Base::XMLReader& reader) { // read my element reader.readElement("Link"); // get the value of my attribute std::string name = reader.getName(reader.getAttribute("value")); // Property not in a DocumentObject! assert(getContainer()->isDerivedFrom()); if (!name.empty()) { DocumentObject* parent = static_cast(getContainer()); App::Document* document = parent->getDocument(); DocumentObject* object = document ? document->getObject(name.c_str()) : nullptr; if (!object) { if (reader.isVerbose()) { Base::Console().warning("Lost link to '%s' while loading, maybe " "an object was not loaded correctly\n", name.c_str()); } } else if (parent == object) { if (reader.isVerbose()) { Base::Console().warning("Object '%s' links to itself, nullify it\n", name.c_str()); } object = nullptr; } setValue(object); } else { setValue(nullptr); } } Property* PropertyLink::Copy() const { PropertyLink* p = new PropertyLink(); p->_pcLink = _pcLink; return p; } void PropertyLink::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } setValue(static_cast(from)._pcLink); } void PropertyLink::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { (void)newStyle; (void)subs; if ((all || _pcScope != LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument()) { objs.push_back(_pcLink); } } void PropertyLink::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { (void)subname; if (!all && _pcScope == LinkScope::Hidden) { return; // Don't get hidden links unless all is specified. } if (obj && _pcLink == obj) { identifiers.emplace_back(*this); } } void PropertyLink::breakLink(App::DocumentObject* obj, bool clear) { if (_pcLink == obj || (clear && getContainer() == obj)) { setValue(nullptr); } } bool PropertyLink::adjustLink(const std::set& inList) { (void)inList; return false; } Property* PropertyLink::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { auto res = tryReplaceLink(getContainer(), _pcLink, parent, oldObj, newObj); if (res.first) { auto p = new PropertyLink(); p->_pcLink = res.first; return p; } return nullptr; } //************************************************************************** // PropertyLinkList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyLinkList, App::PropertyLinkListBase) TYPESYSTEM_SOURCE(App::PropertyLinkListChild, App::PropertyLinkList) TYPESYSTEM_SOURCE(App::PropertyLinkListGlobal, App::PropertyLinkList) TYPESYSTEM_SOURCE(App::PropertyLinkListHidden, App::PropertyLinkList) //************************************************************************** // Construction/Destruction PropertyLinkList::PropertyLinkList() = default; PropertyLinkList::~PropertyLinkList() { // in case this property gety dynamically removed // maintain the back link in the DocumentObject class if (_pcScope != LinkScope::Hidden && !_lValueList.empty() && getContainer() && getContainer()->isDerivedFrom()) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy)) { for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } } } } void PropertyLinkList::setSize(int newSize) { for (int i = newSize; i < (int)_lValueList.size(); ++i) { auto obj = _lValueList[i]; if (!obj || !obj->isAttachedToDocument()) { continue; } _nameMap.erase(obj->getNameInDocument()); if (_pcScope != LinkScope::Hidden) { obj->_removeBackLink(static_cast(getContainer())); } } _lValueList.resize(newSize); } void PropertyLinkList::setSize(int newSize, const_reference def) { auto oldSize = getSize(); setSize(newSize); for (auto i = oldSize; i < newSize; ++i) { _lValueList[i] = def; } } void PropertyLinkList::set1Value(int idx, DocumentObject* const& value) { DocumentObject* obj = nullptr; if (idx >= 0 && idx < (int)_lValueList.size()) { obj = _lValueList[idx]; if (obj == value) { return; } } if (!value || !value->isAttachedToDocument()) { throw Base::ValueError("invalid document object"); } _nameMap.clear(); if (getContainer() && getContainer()->isDerivedFrom()) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { if (obj) { obj->_removeBackLink(static_cast(getContainer())); } if (value) { value->_addBackLink(static_cast(getContainer())); } } } inherited::set1Value(idx, value); } void PropertyLinkList::setValues(const std::vector& value) { if (value.size() == 1 && !value[0]) { // one null element means clear, as backward compatibility for old code setValues(std::vector()); return; } auto parent = freecad_cast(getContainer()); for (auto obj : value) { if (!obj || !obj->isAttachedToDocument()) { throw Base::ValueError("PropertyLinkList: invalid document object"); } if (!testFlag(LinkAllowExternal) && parent && parent->getDocument() != obj->getDocument()) { throw Base::ValueError("PropertyLinkList does not support external object"); } } _nameMap.clear(); // maintain the back link in the DocumentObject class if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } for (auto* obj : value) { if (obj) { obj->_addBackLink(parent); } } } } inherited::setValues(value); } PyObject* PropertyLinkList::getPyObject() { int count = getSize(); #if 0 // FIXME: Should switch to tuple Py::Tuple sequence(count); #else Py::List sequence(count); #endif for (int i = 0; i < count; i++) { auto obj = _lValueList[i]; if (obj && obj->isAttachedToDocument()) { sequence.setItem(i, Py::asObject(_lValueList[i]->getPyObject())); } else { sequence.setItem(i, Py::None()); } } return Py::new_reference_to(sequence); } DocumentObject* PropertyLinkList::getPyValue(PyObject* item) const { Base::PyTypeCheck(&item, &DocumentObjectPy::Type); return item ? static_cast(item)->getDocumentObjectPtr() : nullptr; } void PropertyLinkList::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << "" << endl; writer.incInd(); for (int i = 0; i < getSize(); i++) { DocumentObject* obj = _lValueList[i]; if (obj) { writer.Stream() << writer.ind() << "getExportName() << "\"/>" << endl; } else { writer.Stream() << writer.ind() << "" << endl; } } writer.decInd(); writer.Stream() << writer.ind() << "" << endl; } void PropertyLinkList::Restore(Base::XMLReader& reader) { // read my element reader.readElement("LinkList"); // get the value of my attribute int count = reader.getAttribute("count"); App::PropertyContainer* container = getContainer(); if (!container) { throw Base::RuntimeError("Property is not part of a container"); } if (!container->isDerivedFrom()) { std::stringstream str; str << "Container is not a document object (" << container->getTypeId().getName() << ")"; throw Base::TypeError(str.str()); } std::vector values; values.reserve(count); for (int i = 0; i < count; i++) { reader.readElement("Link"); std::string name = reader.getName(reader.getAttribute("value")); // In order to do copy/paste it must be allowed to have defined some // referenced objects in XML which do not exist anymore in the new // document. Thus, we should silently ignore this. // Property not in an object! DocumentObject* father = static_cast(getContainer()); App::Document* document = father->getDocument(); DocumentObject* child = document ? document->getObject(name.c_str()) : nullptr; if (child) { values.push_back(child); } else if (reader.isVerbose()) { FC_WARN("Lost link to " << (document ? document->getName() : "") << " " << name << " while loading, maybe an object was not loaded correctly"); } } reader.readEndElement("LinkList"); // assignment setValues(values); } Property* PropertyLinkList::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { std::vector links; bool copied = false; bool found = false; for (auto it = _lValueList.begin(); it != _lValueList.end(); ++it) { auto res = tryReplaceLink(getContainer(), *it, parent, oldObj, newObj); if (res.first) { found = true; if (!copied) { copied = true; links.insert(links.end(), _lValueList.begin(), it); } links.push_back(res.first); } else if (*it == newObj) { // in case newObj already exists here, we shall remove all existing // entry, and insert it to take over oldObj's position. if (!copied) { copied = true; links.insert(links.end(), _lValueList.begin(), it); } } else if (copied) { links.push_back(*it); } } if (!found) { return nullptr; } auto p = new PropertyLinkList(); p->_lValueList = std::move(links); return p; } Property* PropertyLinkList::Copy() const { PropertyLinkList* p = new PropertyLinkList(); p->_lValueList = _lValueList; return p; } void PropertyLinkList::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } setValues(static_cast(from)._lValueList); } unsigned int PropertyLinkList::getMemSize() const { return static_cast(_lValueList.size() * sizeof(App::DocumentObject*)); } DocumentObject* PropertyLinkList::find(const char* name, int* pindex) const { const int DONT_MAP_UNDER = 10; if (!name) { return nullptr; } if (_lValueList.size() <= DONT_MAP_UNDER) { int index = -1; for (auto obj : _lValueList) { ++index; if (obj && obj->getNameInDocument() && boost::equals(name, obj->getNameInDocument())) { if (pindex) { *pindex = index; } return obj; } } return nullptr; } // We're using a map. Do we need to (re)create it? if (_nameMap.empty() || _nameMap.size() > _lValueList.size()) { _nameMap.clear(); for (int i = 0; i < (int)_lValueList.size(); ++i) { auto obj = _lValueList[i]; if (obj && obj->isAttachedToDocument()) { _nameMap[obj->getNameInDocument()] = i; } } } // Now lookup up in that map auto it = _nameMap.find(name); if (it == _nameMap.end()) { return nullptr; } if (pindex) { *pindex = it->second; } return _lValueList[it->second]; } DocumentObject* PropertyLinkList::findUsingMap(const std::string& name, int* pindex) const { if (_nameMap.size() == _lValueList.size()) { auto it = _nameMap.find(name); if (it == _nameMap.end()) { return nullptr; } if (pindex) { *pindex = it->second; } return _lValueList[it->second]; } return find(name.c_str(), pindex); } void PropertyLinkList::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { (void)subs; (void)newStyle; if (all || _pcScope != LinkScope::Hidden) { objs.reserve(objs.size() + _lValueList.size()); for (auto obj : _lValueList) { if (obj && obj->isAttachedToDocument()) { objs.push_back(obj); } } } } void PropertyLinkList::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { (void)subname; if (!obj || (!all && _pcScope == LinkScope::Hidden)) { return; } int i = -1; for (auto docObj : _lValueList) { ++i; if (docObj == obj) { identifiers.emplace_back(*this, i); break; } } } void PropertyLinkList::breakLink(App::DocumentObject* obj, bool clear) { if (clear && getContainer() == obj) { setValues({}); return; } std::vector values; values.reserve(_lValueList.size()); for (auto o : _lValueList) { if (o != obj) { values.push_back(o); } } if (values.size() != _lValueList.size()) { setValues(values); } } bool PropertyLinkList::adjustLink(const std::set& inList) { (void)inList; return false; } //************************************************************************** // PropertyLinkSub //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyLinkSub, App::PropertyLinkBase) TYPESYSTEM_SOURCE(App::PropertyLinkSubChild, App::PropertyLinkSub) TYPESYSTEM_SOURCE(App::PropertyLinkSubGlobal, App::PropertyLinkSub) TYPESYSTEM_SOURCE(App::PropertyLinkSubHidden, App::PropertyLinkSub) //************************************************************************** // Construction/Destruction PropertyLinkSub::PropertyLinkSub() = default; PropertyLinkSub::~PropertyLinkSub() { // in case this property is dynamically removed if (_pcLinkSub && getContainer() && getContainer()->isDerivedFrom()) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { if (_pcLinkSub) { _pcLinkSub->_removeBackLink(parent); } } } } void PropertyLinkSub::setSyncSubObject(bool enable) { _Flags.set((std::size_t)LinkSyncSubObject, enable); } void PropertyLinkSub::setValue(App::DocumentObject* lValue, const std::vector& SubList, std::vector&& shadows) { setValue(lValue, std::vector(SubList), std::move(shadows)); } void PropertyLinkSub::setValue(App::DocumentObject* lValue, std::vector&& subs, std::vector&& shadows) { auto parent = freecad_cast(getContainer()); if (lValue) { if (!lValue->isAttachedToDocument()) { throw Base::ValueError("PropertyLinkSub: invalid document object"); } if (!testFlag(LinkAllowExternal) && parent && parent->getDocument() != lValue->getDocument()) { throw Base::ValueError("PropertyLinkSub does not support external object"); } } aboutToSetValue(); if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { if (_pcLinkSub) { _pcLinkSub->_removeBackLink(parent); } if (lValue) { lValue->_addBackLink(parent); } } } _pcLinkSub = lValue; _cSubList = std::move(subs); if (shadows.size() == _cSubList.size()) { _ShadowSubList = std::move(shadows); onContainerRestored(); // re-register element references } else { updateElementReference(nullptr); } checkLabelReferences(_cSubList); hasSetValue(); } App::DocumentObject* PropertyLinkSub::getValue() const { return _pcLinkSub; } const std::vector& PropertyLinkSub::getSubValues() const { return _cSubList; } static inline const std::string& getSubNameWithStyle(const std::string& subName, const PropertyLinkBase::ShadowSub& shadow, bool newStyle, std::string& tmp) { if (!newStyle) { if (!shadow.oldName.empty()) { return shadow.oldName; } } else if (!shadow.newName.empty()) { if (Data::hasMissingElement(shadow.oldName.c_str())) { auto pos = shadow.newName.rfind('.'); if (pos != std::string::npos) { tmp = shadow.newName.substr(0, pos + 1); tmp += shadow.oldName; return tmp; } } return shadow.newName; } return subName; } std::vector PropertyLinkSub::getSubValues(bool newStyle) const { assert(_cSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_cSubList.size()); std::string tmp; for (size_t i = 0; i < _ShadowSubList.size(); ++i) { ret.push_back(getSubNameWithStyle(_cSubList[i], _ShadowSubList[i], newStyle, tmp)); } return ret; } std::vector PropertyLinkSub::getSubValuesStartsWith(const char* starter, bool newStyle) const { assert(_cSubList.size() == _ShadowSubList.size()); std::vector ret; std::string tmp; for (size_t i = 0; i < _ShadowSubList.size(); ++i) { const auto& sub = getSubNameWithStyle(_cSubList[i], _ShadowSubList[i], newStyle, tmp); auto element = Data::findElementName(sub.c_str()); if (element && boost::starts_with(element, starter)) { ret.emplace_back(element); } } return ret; } App::DocumentObject* PropertyLinkSub::getValue(Base::Type t) const { return (_pcLinkSub && _pcLinkSub->isDerivedFrom(t)) ? _pcLinkSub : nullptr; } PyObject* PropertyLinkSub::getPyObject() { Py::Tuple tup(2); Py::List list(static_cast(_cSubList.size())); if (_pcLinkSub) { tup[0] = Py::asObject(_pcLinkSub->getPyObject()); int i = 0; for (auto& sub : getSubValues(testFlag(LinkNewElement))) { list[i++] = Py::String(sub); } tup[1] = list; return Py::new_reference_to(tup); } else { return Py::new_reference_to(Py::None()); } } void PropertyLinkSub::setPyObject(PyObject* value) { if (PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) { DocumentObjectPy* pcObject = static_cast(value); setValue(pcObject->getDocumentObjectPtr()); } else if (PyTuple_Check(value) || PyList_Check(value)) { Py::Sequence seq(value); if (seq.size() == 0) { setValue(nullptr); } else if (seq.size() != 2) { throw Base::ValueError("Expect input sequence of size 2"); } else if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))) { DocumentObjectPy* pcObj = static_cast(seq[0].ptr()); static const char* errMsg = "type of second element in tuple must be str or sequence of str"; PropertyString propString; if (seq[1].isString()) { std::vector vals; propString.setPyObject(seq[1].ptr()); vals.emplace_back(propString.getValue()); setValue(pcObj->getDocumentObjectPtr(), std::move(vals)); } else if (seq[1].isSequence()) { Py::Sequence list(seq[1]); std::vector vals(list.size()); unsigned int i = 0; for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it, ++i) { if (!(*it).isString()) { throw Base::TypeError(errMsg); } propString.setPyObject((*it).ptr()); vals[i] = propString.getValue(); } setValue(pcObj->getDocumentObjectPtr(), std::move(vals)); } else { throw Base::TypeError(errMsg); } } else { std::string error = std::string("type of first element in tuple must be 'DocumentObject', not "); error += seq[0].ptr()->ob_type->tp_name; throw Base::TypeError(error); } } else if (Py_None == value) { setValue(nullptr); } else { std::string error = std::string( "type must be 'DocumentObject', 'NoneType' or ('DocumentObject',['String',]) not "); error += value->ob_type->tp_name; throw Base::TypeError(error); } } static bool updateLinkReference(App::PropertyLinkBase* prop, App::DocumentObject* feature, bool reverse, bool notify, App::DocumentObject* link, std::vector& subs, std::vector& mapped, std::vector& shadows) { if (!feature) { shadows.clear(); prop->unregisterElementReference(); } shadows.resize(subs.size()); if (!link || !link->isAttachedToDocument()) { return false; } auto owner = freecad_cast(prop->getContainer()); if (owner && owner->isRestoring()) { return false; } int i = 0; bool touched = false; for (auto& sub : subs) { if (prop->_updateElementReference(feature, link, sub, shadows[i++], reverse, notify && !touched)) { touched = true; } } if (!touched) { return false; } for (int idx : mapped) { if (idx < (int)subs.size() && !shadows[idx].newName.empty()) { subs[idx] = shadows[idx].newName; } } mapped.clear(); if (owner && feature) { owner->onUpdateElementReference(prop); } return true; } void PropertyLinkSub::afterRestore() { _ShadowSubList.resize(_cSubList.size()); if (!testFlag(LinkRestoreLabel) || !_pcLinkSub || !_pcLinkSub->isAttachedToDocument()) { return; } setFlag(LinkRestoreLabel, false); for (std::size_t i = 0; i < _cSubList.size(); ++i) { restoreLabelReference(_pcLinkSub, _cSubList[i], &_ShadowSubList[i]); } } void PropertyLinkSub::onContainerRestored() { unregisterElementReference(); if (!_pcLinkSub || !_pcLinkSub->isAttachedToDocument()) { return; } for (std::size_t i = 0; i < _cSubList.size(); ++i) { _registerElementReference(_pcLinkSub, _cSubList[i], _ShadowSubList[i]); } } void PropertyLinkSub::updateElementReference(DocumentObject* feature, bool reverse, bool notify) { if (!updateLinkReference(this, feature, reverse, notify, _pcLinkSub, _cSubList, _mapped, _ShadowSubList)) { return; } if (notify) { hasSetValue(); } } bool PropertyLinkSub::referenceChanged() const { return !_mapped.empty(); } std::string PropertyLinkBase::importSubName(Base::XMLReader& reader, const char* sub, bool& restoreLabel) { if (!reader.doNameMapping()) { return sub; } std::ostringstream str; for (const char* dot = strchr(sub, '.'); dot; sub = dot + 1, dot = strchr(sub, '.')) { size_t count = dot - sub; const char* tail = "."; if (count && dot[-1] == '@') { // tail=='@' means we are exporting a label reference. So retain // this marker so that the label can be restored in afterRestore(). tail = "@."; --count; restoreLabel = true; } str << reader.getName(std::string(sub, count).c_str()) << tail; } str << sub; return str.str(); } const char* PropertyLinkBase::exportSubName(std::string& output, const App::DocumentObject* obj, const char* sub, bool first_obj) { std::ostringstream str; const char* res = sub; if (!sub || !sub[0]) { return res; } bool touched = false; if (first_obj) { auto dot = strchr(sub, '.'); if (!dot) { return res; } const char* hash; for (hash = sub; hash < dot && *hash != '#'; ++hash) {} App::Document* doc = nullptr; if (*hash == '#') { doc = GetApplication().getDocument(std::string(sub, hash - sub).c_str()); } else { hash = nullptr; if (obj && obj->isAttachedToDocument()) { doc = obj->getDocument(); } } if (!doc) { FC_ERR("Failed to get document for the first object in " << sub); return res; } obj = doc->getObject(std::string(sub, dot - sub).c_str()); if (!obj || !obj->isAttachedToDocument()) { return res; } if (hash) { if (!obj->isExporting()) { str << doc->getName() << '#'; } sub = hash + 1; } } else if (!obj || !obj->isAttachedToDocument()) { return res; } for (const char* dot = strchr(sub, '.'); dot; sub = dot + 1, dot = strchr(sub, '.')) { // name with trailing '.' auto name = std::string(sub, dot - sub + 1); if (first_obj) { first_obj = false; } else { obj = obj->getSubObject(name.c_str()); } if (!obj || !obj->isAttachedToDocument()) { FC_WARN("missing sub object '" << name << "' in '" << sub << "'"); break; } if (obj->isExporting()) { if (name[0] == '$') { if (name.compare(1, name.size() - 2, obj->Label.getValue()) != 0) { str << obj->getExportName(true) << "@."; touched = true; continue; } } else if (name.compare(0, name.size() - 1, obj->getNameInDocument()) == 0) { str << obj->getExportName(true) << '.'; touched = true; continue; } } str << name; } if (!touched) { return res; } str << sub; output = str.str(); return output.c_str(); } App::DocumentObject* PropertyLinkBase::tryImport(const App::Document* doc, const App::DocumentObject* obj, const std::map& nameMap) { if (doc && obj && obj->isAttachedToDocument()) { auto it = nameMap.find(obj->getExportName(true)); if (it != nameMap.end()) { obj = doc->getObject(it->second.c_str()); if (!obj) { FC_THROWM(Base::RuntimeError, "Cannot find import object " << it->second); } } } return const_cast(obj); } std::string PropertyLinkBase::tryImportSubName(const App::DocumentObject* obj, const char* _subname, const App::Document* doc, const std::map& nameMap) { if (!doc || !obj || !obj->isAttachedToDocument()) { return {}; } std::ostringstream ss; std::string subname(_subname); char* sub = &subname[0]; char* next = sub; for (char* dot = strchr(next, '.'); dot; next = dot + 1, dot = strchr(next, '.')) { StringGuard guard(dot); auto sobj = obj->getSubObject(subname.c_str()); if (!sobj) { FC_ERR("Failed to restore label reference " << obj->getFullName() << '.' << subname); return {}; } dot[0] = 0; if (next[0] == '$') { if (strcmp(next + 1, sobj->Label.getValue()) != 0) { continue; } } else if (strcmp(next, sobj->getNameInDocument()) != 0) { continue; } auto it = nameMap.find(sobj->getExportName(true)); if (it == nameMap.end()) { continue; } auto imported = doc->getObject(it->second.c_str()); if (!imported) { FC_THROWM(RuntimeError, "Failed to find imported object " << it->second); } ss.write(sub, next - sub); if (next[0] == '$') { ss << '$' << imported->Label.getStrValue() << '.'; } else { ss << it->second << '.'; } sub = dot + 1; } if (sub != subname.c_str()) { return ss.str(); } return {}; } void PropertyLinkBase::_getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, const std::vector& subs, const std::vector& shadows) const { if (!subname) { identifiers.emplace_back(*this); return; } App::SubObjectT objT(obj, subname); auto subObject = objT.getSubObject(); auto subElement = objT.getOldElementName(); int i = -1; for (const auto& sub : subs) { ++i; if (sub == subname) { identifiers.emplace_back(*this); return; } if (!subObject) { continue; } // After above, there is a subobject and the subname doesn't match our current entry App::SubObjectT sobjT(obj, sub.c_str()); if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this); return; } // And the oldElementName ( short, I.E. "Edge5" ) doesn't match. if (i < (int)shadows.size()) { const auto& [shadowNewName, shadowOldName] = shadows[i]; if (shadowNewName == subname || shadowOldName == subname) { identifiers.emplace_back(*this); return; } if (!subObject) { continue; } App::SubObjectT shadowobjT(obj, shadowNewName.empty() ? shadowOldName.c_str() : shadowNewName.c_str()); if (shadowobjT.getSubObject() == subObject && shadowobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this); return; } } } } #define ATTR_SHADOWED "shadowed" #define ATTR_SHADOW "shadow" #define ATTR_MAPPED "mapped" #define IGNORE_SHADOW false void PropertyLinkSub::Save(Base::Writer& writer) const { assert(_cSubList.size() == _ShadowSubList.size()); std::string internal_name; // it can happen that the object is still alive but is not part of the document anymore and thus // returns 0 if (_pcLinkSub && _pcLinkSub->isAttachedToDocument()) { internal_name = _pcLinkSub->getExportName(); } writer.Stream() << writer.ind() << "" << std::endl; writer.incInd(); auto owner = freecad_cast(getContainer()); bool exporting = owner && owner->isExporting(); for (unsigned int i = 0; i < _cSubList.size(); i++) { const auto& shadow = _ShadowSubList[i]; // shadow.oldName stores the old style element name. For backward // compatibility reason, we shall store the old name into attribute // 'value' whenever possible. const auto& sub = shadow.oldName.empty() ? _cSubList[i] : shadow.oldName; writer.Stream() << writer.ind() << "" << endl; } writer.decInd(); writer.Stream() << writer.ind() << "" << endl; } void PropertyLinkSub::Restore(Base::XMLReader& reader) { // read my element reader.readElement("LinkSub"); // get the values of my attributes std::string name = reader.getName(reader.getAttribute("value")); int count = reader.getAttribute("count"); // Property not in a DocumentObject! assert(getContainer()->isDerivedFrom()); App::Document* document = static_cast(getContainer())->getDocument(); DocumentObject* pcObject = nullptr; if (!name.empty()) { pcObject = document ? document->getObject(name.c_str()) : nullptr; if (!pcObject) { if (reader.isVerbose()) { FC_WARN("Lost link to " << name << " while loading, maybe an object was not loaded correctly"); } } } std::vector mapped; std::vector values(count); std::vector shadows(count); bool restoreLabel = false; // Sub may store '.' separated object names, so be aware of the possible mapping when import for (int i = 0; i < count; i++) { reader.readElement("Sub"); shadows[i].oldName = importSubName(reader, reader.getAttribute("value"), restoreLabel); if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { values[i] = shadows[i].newName = importSubName(reader, reader.getAttribute(ATTR_SHADOWED), restoreLabel); } else { values[i] = shadows[i].oldName; if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) { shadows[i].newName = importSubName(reader, reader.getAttribute(ATTR_SHADOW), restoreLabel); } } if (reader.hasAttribute(ATTR_MAPPED)) { mapped.push_back(i); } } setFlag(LinkRestoreLabel, restoreLabel); reader.readEndElement("LinkSub"); if (pcObject) { setValue(pcObject, std::move(values), std::move(shadows)); _mapped = std::move(mapped); } else { setValue(nullptr); } } template std::vector updateLinkSubs(const App::DocumentObject* obj, const std::vector& subs, Func* f, Args&&... args) { if (!obj || !obj->isAttachedToDocument()) { return {}; } std::vector res; for (auto it = subs.begin(); it != subs.end(); ++it) { const auto& sub = *it; auto new_sub = (*f)(obj, sub.c_str(), args...); if (new_sub.size()) { if (res.empty()) { res.reserve(subs.size()); res.insert(res.end(), subs.begin(), it); } res.push_back(std::move(new_sub)); } else if (!res.empty()) { res.push_back(sub); } } return res; } Property* PropertyLinkSub::CopyOnImportExternal(const std::map& nameMap) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument()) { return nullptr; } if (!_pcLinkSub || !_pcLinkSub->isAttachedToDocument()) { return nullptr; } auto subs = updateLinkSubs(_pcLinkSub, _cSubList, &tryImportSubName, owner->getDocument(), nameMap); auto linked = tryImport(owner->getDocument(), _pcLinkSub, nameMap); if (subs.empty() && linked == _pcLinkSub) { return nullptr; } PropertyLinkSub* p = new PropertyLinkSub(); p->_pcLinkSub = linked; if (subs.empty()) { p->_cSubList = _cSubList; } else { p->_cSubList = std::move(subs); } return p; } Property* PropertyLinkSub::CopyOnLabelChange(App::DocumentObject* obj, const std::string& ref, const char* newLabel) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument()) { return nullptr; } if (!_pcLinkSub || !_pcLinkSub->isAttachedToDocument()) { return nullptr; } auto subs = updateLinkSubs(_pcLinkSub, _cSubList, &updateLabelReference, obj, ref, newLabel); if (subs.empty()) { return nullptr; } PropertyLinkSub* p = new PropertyLinkSub(); p->_pcLinkSub = _pcLinkSub; p->_cSubList = std::move(subs); return p; } Property* PropertyLinkSub::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { auto res = tryReplaceLinkSubs(getContainer(), _pcLinkSub, parent, oldObj, newObj, _cSubList); if (res.first) { PropertyLinkSub* p = new PropertyLinkSub(); p->_pcLinkSub = res.first; p->_cSubList = std::move(res.second); return p; } return nullptr; } Property* PropertyLinkSub::Copy() const { PropertyLinkSub* p = new PropertyLinkSub(); p->_pcLinkSub = _pcLinkSub; p->_cSubList = _cSubList; p->_ShadowSubList = _ShadowSubList; return p; } void PropertyLinkSub::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } auto& link = static_cast(from); setValue(link._pcLinkSub, link._cSubList, std::vector(link._ShadowSubList)); } void PropertyLinkSub::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { if (all || _pcScope != LinkScope::Hidden) { if (_pcLinkSub && _pcLinkSub->isAttachedToDocument()) { objs.push_back(_pcLinkSub); if (subs) { *subs = getSubValues(newStyle); } } } } void PropertyLinkSub::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { if (all || _pcScope != LinkScope::Hidden) { if (obj && obj == _pcLinkSub) { _getLinksTo(identifiers, obj, subname, _cSubList, _ShadowSubList); } } } void PropertyLinkSub::breakLink(App::DocumentObject* obj, bool clear) { if (obj == _pcLinkSub || (clear && getContainer() == obj)) { setValue(nullptr); } } static App::DocumentObject* adjustLinkSubs(App::PropertyLinkBase* prop, const std::set& inList, App::DocumentObject* link, std::vector& subs, std::map>* links = nullptr) { App::DocumentObject* newLink = nullptr; for (auto& sub : subs) { size_t pos = sub.find('.'); for (; pos != std::string::npos; pos = sub.find('.', pos + 1)) { auto sobj = link->getSubObject(sub.substr(0, pos + 1).c_str()); if (!sobj || (!prop->testFlag(PropertyLinkBase::LinkAllowExternal) && sobj->getDocument() != link->getDocument())) { pos = std::string::npos; break; } if (!newLink) { if (inList.contains(sobj)) { continue; } newLink = sobj; if (links) { (*links)[sobj].push_back(sub.substr(pos + 1)); } else { sub = sub.substr(pos + 1); } } else if (links) { (*links)[sobj].push_back(sub.substr(pos + 1)); } else if (sobj == newLink) { sub = sub.substr(pos + 1); } break; } if (pos == std::string::npos) { return nullptr; } } return newLink; } bool PropertyLinkSub::adjustLink(const std::set& inList) { if (_pcScope == LinkScope::Hidden) { return false; } if (!_pcLinkSub || !_pcLinkSub->isAttachedToDocument() || !inList.contains(_pcLinkSub)) { return false; } auto subs = _cSubList; auto link = adjustLinkSubs(this, inList, _pcLinkSub, subs); if (link) { setValue(link, std::move(subs)); return true; } return false; } //************************************************************************** // PropertyLinkSubList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyLinkSubList, App::PropertyLinkBase) TYPESYSTEM_SOURCE(App::PropertyLinkSubListChild, App::PropertyLinkSubList) TYPESYSTEM_SOURCE(App::PropertyLinkSubListGlobal, App::PropertyLinkSubList) TYPESYSTEM_SOURCE(App::PropertyLinkSubListHidden, App::PropertyLinkSubList) //************************************************************************** // Construction/Destruction PropertyLinkSubList::PropertyLinkSubList() = default; PropertyLinkSubList::~PropertyLinkSubList() { // in case this property is dynamically removed // maintain backlinks if (!_lValueList.empty() && getContainer() && getContainer()->isDerivedFrom()) { App::DocumentObject* parent = static_cast(getContainer()); // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } } } } void PropertyLinkSubList::setSyncSubObject(bool enable) { _Flags.set((std::size_t)LinkSyncSubObject, enable); } void PropertyLinkSubList::verifyObject(App::DocumentObject* obj, App::DocumentObject* parent) { if (obj) { if (!obj->isAttachedToDocument()) { throw Base::ValueError("PropertyLinkSubList: invalid document object"); } if (!testFlag(LinkAllowExternal) && parent && parent->getDocument() != obj->getDocument()) { throw Base::ValueError("PropertyLinkSubList does not support external object"); } } } void PropertyLinkSubList::setSize(int newSize) { _lValueList.resize(newSize); _lSubList.resize(newSize); _ShadowSubList.resize(newSize); } int PropertyLinkSubList::getSize() const { return static_cast(_lValueList.size()); } void PropertyLinkSubList::setValue(DocumentObject* lValue, const char* SubName) { auto parent = freecad_cast(getContainer()); verifyObject(lValue, parent); // maintain backlinks if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } if (lValue) { lValue->_addBackLink(parent); } } } if (lValue) { aboutToSetValue(); _lValueList.resize(1); _lValueList[0] = lValue; _lSubList.resize(1); _lSubList[0] = SubName; } else { aboutToSetValue(); _lValueList.clear(); _lSubList.clear(); } updateElementReference(nullptr); checkLabelReferences(_lSubList); hasSetValue(); } void PropertyLinkSubList::setValues(const std::vector& lValue, const std::vector& lSubNames) { auto parent = freecad_cast(getContainer()); for (auto obj : lValue) { verifyObject(obj, parent); } if (lValue.size() != lSubNames.size()) { throw Base::ValueError( "PropertyLinkSubList::setValues: size of subelements list != size of objects list"); } // maintain backlinks. if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document // object to ensure that this works for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } // maintain backlinks. lValue can contain items multiple times, but we trust the // document object to ensure that the backlink is only added once for (auto* obj : lValue) { if (obj) { obj->_addBackLink(parent); } } } } aboutToSetValue(); _lValueList = lValue; _lSubList.resize(lSubNames.size()); int i = 0; for (std::vector::const_iterator it = lSubNames.begin(); it != lSubNames.end(); ++it, ++i) { if (*it) { _lSubList[i] = *it; } } updateElementReference(nullptr); checkLabelReferences(_lSubList); hasSetValue(); } void PropertyLinkSubList::setValues(const std::vector& lValue, const std::vector& lSubNames, std::vector&& ShadowSubList) { setValues(std::vector(lValue), std::vector(lSubNames), std::move(ShadowSubList)); } void PropertyLinkSubList::setValues(std::vector&& lValue, std::vector&& lSubNames, std::vector&& ShadowSubList) { auto parent = freecad_cast(getContainer()); for (auto obj : lValue) { verifyObject(obj, parent); } if (lValue.size() != lSubNames.size()) { throw Base::ValueError( "PropertyLinkSubList::setValues: size of subelements list != size of objects list"); } // maintain backlinks. if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document // object to ensure that this works for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } // maintain backlinks. lValue can contain items multiple times, but we trust the // document object to ensure that the backlink is only added once for (auto* obj : lValue) { if (obj) { obj->_addBackLink(parent); } } } } aboutToSetValue(); _lValueList = std::move(lValue); _lSubList = std::move(lSubNames); if (ShadowSubList.size() == _lSubList.size()) { _ShadowSubList = std::move(ShadowSubList); onContainerRestored(); // re-register element references } else { updateElementReference(nullptr); } checkLabelReferences(_lSubList); hasSetValue(); } void PropertyLinkSubList::setValue(DocumentObject* lValue, const std::vector& SubList) { auto parent = dynamic_cast(getContainer()); verifyObject(lValue, parent); // maintain backlinks. if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document // object to ensure that this works for (auto* obj : _lValueList) { if (obj) { obj->_removeBackLink(parent); } } // maintain backlinks. lValue can contain items multiple times, but we trust the // document object to ensure that the backlink is only added once if (lValue) { lValue->_addBackLink(parent); } } } aboutToSetValue(); std::size_t size = SubList.size(); this->_lValueList.clear(); this->_lSubList.clear(); if (size == 0) { if (lValue) { this->_lValueList.push_back(lValue); this->_lSubList.emplace_back(); } } else { this->_lSubList = SubList; this->_lValueList.insert(this->_lValueList.begin(), size, lValue); } updateElementReference(nullptr); checkLabelReferences(_lSubList); hasSetValue(); } void PropertyLinkSubList::addValue(App::DocumentObject* obj, const std::vector& subs, bool reset) { auto parent = freecad_cast(getContainer()); verifyObject(obj, parent); // maintain backlinks. if (parent) { // before accessing internals make sure the object is not about to be destroyed // otherwise the backlink contains dangling pointers if (!parent->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { //_lValueList can contain items multiple times, but we trust the document // object to ensure that this works if (reset) { for (auto* value : _lValueList) { if (value && value == obj) { value->_removeBackLink(parent); } } } // maintain backlinks. lValue can contain items multiple times, but we trust the // document object to ensure that the backlink is only added once if (obj) { obj->_addBackLink(parent); } } } std::vector valueList; std::vector subList; if (reset) { for (std::size_t i = 0; i < _lValueList.size(); i++) { if (_lValueList[i] != obj) { valueList.push_back(_lValueList[i]); subList.push_back(_lSubList[i]); } } } else { valueList = _lValueList; subList = _lSubList; } std::size_t size = subs.size(); if (size == 0) { if (obj) { valueList.push_back(obj); subList.emplace_back(); } } else if (obj) { subList.insert(subList.end(), subs.begin(), subs.end()); valueList.insert(valueList.end(), size, obj); } aboutToSetValue(); _lValueList = valueList; _lSubList = subList; updateElementReference(nullptr); checkLabelReferences(_lSubList); hasSetValue(); } const string PropertyLinkSubList::getPyReprString() const { assert(this->_lValueList.size() == this->_lSubList.size()); if (this->_lValueList.empty()) { return std::string("None"); } std::stringstream strm; strm << "["; for (std::size_t i = 0; i < this->_lSubList.size(); i++) { if (i > 0) { strm << ",("; } else { strm << "("; } App::DocumentObject* obj = this->_lValueList[i]; if (obj) { strm << "App.getDocument('" << obj->getDocument()->getName() << "').getObject('" << obj->getNameInDocument() << "')"; } else { strm << "None"; } strm << ","; strm << "'" << this->_lSubList[i] << "'"; strm << ")"; } strm << "]"; return strm.str(); } DocumentObject* PropertyLinkSubList::getValue() const { App::DocumentObject* ret = nullptr; // FIXME: cache this to avoid iterating each time, to improve speed for (auto i : this->_lValueList) { if (!ret) { ret = i; } if (ret != i) { return nullptr; } } return ret; } int PropertyLinkSubList::removeValue(App::DocumentObject* lValue) { assert(this->_lValueList.size() == this->_lSubList.size()); std::size_t num = std::count(this->_lValueList.begin(), this->_lValueList.end(), lValue); if (num == 0) { return 0; } std::vector links; std::vector subs; links.reserve(this->_lValueList.size() - num); subs.reserve(this->_lSubList.size() - num); for (std::size_t i = 0; i < this->_lValueList.size(); ++i) { if (this->_lValueList[i] != lValue) { links.push_back(this->_lValueList[i]); subs.push_back(this->_lSubList[i]); } } setValues(links, subs); return static_cast(num); } void PropertyLinkSubList::setSubListValues(const std::vector& values) { std::vector links; std::vector subs; for (std::vector::const_iterator it = values.begin(); it != values.end(); ++it) { if (it->second.empty()) { links.push_back(it->first); subs.emplace_back(); continue; } for (std::vector::const_iterator jt = it->second.begin(); jt != it->second.end(); ++jt) { links.push_back(it->first); subs.push_back(*jt); } } setValues(links, subs); } std::vector PropertyLinkSubList::getSubListValues(bool newStyle) const { std::vector values; if (_lValueList.size() != _lSubList.size()) { throw Base::ValueError("PropertyLinkSubList::getSubListValues: size of subelements list != " "size of objects list"); } assert(_ShadowSubList.size() == _lSubList.size()); for (std::size_t i = 0; i < _lValueList.size(); i++) { App::DocumentObject* link = _lValueList[i]; std::string sub; if (newStyle && !_ShadowSubList[i].newName.empty()) { sub = _ShadowSubList[i].newName; } else if (!newStyle && !_ShadowSubList[i].oldName.empty()) { sub = _ShadowSubList[i].oldName; } else { sub = _lSubList[i]; } if (values.empty() || values.back().first != link) { // new object started, start a new subset. values.emplace_back(link, std::vector()); } values.back().second.push_back(sub); } return values; } PyObject* PropertyLinkSubList::getPyObject() { std::vector subLists = getSubListValues(); std::size_t count = subLists.size(); #if 0 // FIXME: Should switch to tuple Py::Tuple sequence(count); #else Py::List sequence(count); #endif for (std::size_t i = 0; i < count; i++) { Py::Tuple tup(2); tup[0] = Py::asObject(subLists[i].first->getPyObject()); const std::vector& sub = subLists[i].second; Py::Tuple items(sub.size()); for (std::size_t j = 0; j < sub.size(); j++) { items[j] = Py::String(sub[j]); } tup[1] = items; sequence[i] = tup; } return Py::new_reference_to(sequence); } void PropertyLinkSubList::setPyObject(PyObject* value) { try { // try PropertyLinkSub syntax PropertyLinkSub dummy; dummy.setPyObject(value); this->setValue(dummy.getValue(), dummy.getSubValues()); return; } catch (...) { } try { // try PropertyLinkList syntax PropertyLinkList dummy; dummy.setPyObject(value); const auto& values = dummy.getValues(); std::vector subs(values.size()); this->setValues(values, subs); return; } catch (...) { } static const char* errMsg = "Expects sequence of items of type DocObj, (DocObj,SubName), or (DocObj, (SubName,...))"; if (!PyTuple_Check(value) && !PyList_Check(value)) { throw Base::TypeError(errMsg); } Py::Sequence list(value); Py::Sequence::size_type size = list.size(); std::vector values; values.reserve(size); std::vector SubNames; SubNames.reserve(size); for (Py::Sequence::size_type i = 0; i < size; i++) { Py::Object item = list[i]; if ((item.isTuple() || item.isSequence()) && PySequence_Size(*item) == 2) { Py::Sequence seq(item); if (PyObject_TypeCheck(seq[0].ptr(), &(DocumentObjectPy::Type))) { auto obj = static_cast(seq[0].ptr())->getDocumentObjectPtr(); PropertyString propString; if (seq[1].isString()) { values.push_back(obj); propString.setPyObject(seq[1].ptr()); SubNames.emplace_back(propString.getValue()); } else if (seq[1].isSequence()) { Py::Sequence list(seq[1]); for (Py::Sequence::iterator it = list.begin(); it != list.end(); ++it) { if (!(*it).isString()) { throw Base::TypeError(errMsg); } values.push_back(obj); propString.setPyObject((*it).ptr()); SubNames.emplace_back(propString.getValue()); } } else { throw Base::TypeError(errMsg); } } } else if (PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { DocumentObjectPy* pcObj; pcObj = static_cast(*item); values.push_back(pcObj->getDocumentObjectPtr()); SubNames.emplace_back(); } else { throw Base::TypeError(errMsg); } } setValues(values, SubNames); } void PropertyLinkSubList::afterRestore() { assert(_lSubList.size() == _ShadowSubList.size()); if (!testFlag(LinkRestoreLabel)) { return; } setFlag(LinkRestoreLabel, false); for (size_t i = 0; i < _lSubList.size(); ++i) { restoreLabelReference(_lValueList[i], _lSubList[i], &_ShadowSubList[i]); } } void PropertyLinkSubList::onContainerRestored() { unregisterElementReference(); for (size_t i = 0; i < _lSubList.size(); ++i) { _registerElementReference(_lValueList[i], _lSubList[i], _ShadowSubList[i]); } } void PropertyLinkSubList::updateElementReference(DocumentObject* feature, bool reverse, bool notify) { if (!feature) { _ShadowSubList.clear(); unregisterElementReference(); } _ShadowSubList.resize(_lSubList.size()); auto owner = freecad_cast(getContainer()); if (owner && owner->isRestoring()) { return; } int i = 0; bool touched = false; for (auto& sub : _lSubList) { auto obj = _lValueList[i]; if (_updateElementReference(feature, obj, sub, _ShadowSubList[i++], reverse, notify && !touched)) { touched = true; } } if (!touched) { return; } std::vector mapped; mapped.reserve(_mapped.size()); for (int idx : _mapped) { if (idx < (int)_lSubList.size()) { if (!_ShadowSubList[idx].newName.empty()) { _lSubList[idx] = _ShadowSubList[idx].newName; } else { mapped.push_back(idx); } } } _mapped.swap(mapped); if (owner && feature) { owner->onUpdateElementReference(this); } if (notify) { hasSetValue(); } } bool PropertyLinkSubList::referenceChanged() const { return !_mapped.empty(); } void PropertyLinkSubList::Save(Base::Writer& writer) const { assert(_lSubList.size() == _ShadowSubList.size()); int count = 0; for (auto obj : _lValueList) { if (obj && obj->isAttachedToDocument()) { ++count; } } writer.Stream() << writer.ind() << "" << endl; writer.incInd(); auto owner = freecad_cast(getContainer()); bool exporting = owner && owner->isExporting(); for (int i = 0; i < getSize(); i++) { auto obj = _lValueList[i]; if (!obj || !obj->isAttachedToDocument()) { continue; } const auto& shadow = _ShadowSubList[i]; // shadow.oldName stores the old style element name. For backward // compatibility reason, we shall store the old name into attribute // 'value' whenever possible. const auto& sub = shadow.oldName.empty() ? _lSubList[i] : shadow.oldName; writer.Stream() << writer.ind() << "getExportName() << "\" sub=\""; if (exporting) { std::string exportName; writer.Stream() << encodeAttribute(exportSubName(exportName, obj, sub.c_str())); if (!shadow.oldName.empty() && _lSubList[i] == shadow.newName) { writer.Stream() << "\" " ATTR_MAPPED "=\"1"; } } else { writer.Stream() << encodeAttribute(sub); if (!_lSubList[i].empty()) { if (sub != _lSubList[i]) { // Stores the actual value that is shadowed. For new version FC, // we will restore this shadowed value instead. writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(_lSubList[i]); } else if (!shadow.newName.empty()) { // Here means the user set value is old style element name. // We shall then store the shadow somewhere else. writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadow.newName); } } } writer.Stream() << "\"/>" << endl; } writer.decInd(); writer.Stream() << writer.ind() << "" << endl; } void PropertyLinkSubList::Restore(Base::XMLReader& reader) { // read my element reader.readElement("LinkSubList"); // get the value of my attribute int count = reader.getAttribute("count"); std::vector values; values.reserve(count); std::vector SubNames; SubNames.reserve(count); std::vector shadows; shadows.reserve(count); DocumentObject* father = freecad_cast(getContainer()); App::Document* document = father ? father->getDocument() : nullptr; std::vector mapped; bool restoreLabel = false; for (int i = 0; i < count; i++) { reader.readElement("Link"); std::string name = reader.getName(reader.getAttribute("obj")); // In order to do copy/paste it must be allowed to have defined some // referenced objects in XML which do not exist anymore in the new // document. Thus, we should silently ignore this. // Property not in an object! DocumentObject* child = document ? document->getObject(name.c_str()) : nullptr; if (child) { values.push_back(child); shadows.emplace_back(); auto& shadow = shadows.back(); shadow.oldName = importSubName(reader, reader.getAttribute("sub"), restoreLabel); if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { shadow.newName = importSubName(reader, reader.getAttribute(ATTR_SHADOWED), restoreLabel); SubNames.push_back(shadow.newName); } else { SubNames.push_back(shadow.oldName); if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) { shadow.newName = importSubName(reader, reader.getAttribute(ATTR_SHADOW), restoreLabel); } } if (reader.hasAttribute(ATTR_MAPPED)) { mapped.push_back(i); } } else if (reader.isVerbose()) { Base::Console().warning("Lost link to '%s' while loading, maybe " "an object was not loaded correctly\n", name.c_str()); } } setFlag(LinkRestoreLabel, restoreLabel); reader.readEndElement("LinkSubList"); // assignment setValues(values, SubNames, std::move(shadows)); _mapped.swap(mapped); } bool PropertyLinkSubList::upgrade(Base::XMLReader& reader, const char* typeName) { Base::Type type = Base::Type::fromName(typeName); if (type.isDerivedFrom(PropertyLink::getClassTypeId())) { PropertyLink prop; prop.setContainer(getContainer()); prop.Restore(reader); setValue(prop.getValue()); return true; } else if (type.isDerivedFrom(PropertyLinkList::getClassTypeId())) { PropertyLinkList prop; prop.setContainer(getContainer()); prop.Restore(reader); std::vector subnames; subnames.resize(prop.getSize()); setValues(prop.getValues(), subnames); return true; } else if (type.isDerivedFrom(PropertyLinkSub::getClassTypeId())) { PropertyLinkSub prop; prop.setContainer(getContainer()); prop.Restore(reader); setValue(prop.getValue(), prop.getSubValues()); return true; } return false; } Property* PropertyLinkSubList::CopyOnImportExternal(const std::map& nameMap) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument() || _lValueList.size() != _lSubList.size()) { return nullptr; } std::vector values; std::vector subs; auto itSub = _lSubList.begin(); for (auto itValue = _lValueList.begin(); itValue != _lValueList.end(); ++itValue, ++itSub) { auto value = *itValue; const auto& sub = *itSub; if (!value || !value->isAttachedToDocument()) { if (!values.empty()) { values.push_back(value); subs.push_back(sub); } continue; } auto linked = tryImport(owner->getDocument(), value, nameMap); auto new_sub = tryImportSubName(value, sub.c_str(), owner->getDocument(), nameMap); if (linked != value || !new_sub.empty()) { if (values.empty()) { values.reserve(_lValueList.size()); values.insert(values.end(), _lValueList.begin(), itValue); subs.reserve(_lSubList.size()); subs.insert(subs.end(), _lSubList.begin(), itSub); } values.push_back(linked); subs.push_back(std::move(new_sub)); } else if (!values.empty()) { values.push_back(linked); subs.push_back(sub); } } if (values.empty()) { return nullptr; } std::unique_ptr p(new PropertyLinkSubList); p->_lValueList = std::move(values); p->_lSubList = std::move(subs); return p.release(); } Property* PropertyLinkSubList::CopyOnLabelChange(App::DocumentObject* obj, const std::string& ref, const char* newLabel) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument()) { return nullptr; } std::vector values; std::vector subs; auto itSub = _lSubList.begin(); for (auto itValue = _lValueList.begin(); itValue != _lValueList.end(); ++itValue, ++itSub) { auto value = *itValue; const auto& sub = *itSub; if (!value || !value->isAttachedToDocument()) { if (!values.empty()) { values.push_back(value); subs.push_back(sub); } continue; } auto new_sub = updateLabelReference(value, sub.c_str(), obj, ref, newLabel); if (!new_sub.empty()) { if (values.empty()) { values.reserve(_lValueList.size()); values.insert(values.end(), _lValueList.begin(), itValue); subs.reserve(_lSubList.size()); subs.insert(subs.end(), _lSubList.begin(), itSub); } values.push_back(value); subs.push_back(std::move(new_sub)); } else if (!values.empty()) { values.push_back(value); subs.push_back(sub); } } if (values.empty()) { return nullptr; } std::unique_ptr p(new PropertyLinkSubList); p->_lValueList = std::move(values); p->_lSubList = std::move(subs); return p.release(); } Property* PropertyLinkSubList::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { std::vector values; std::vector subs; auto itSub = _lSubList.begin(); std::vector positions; for (auto itValue = _lValueList.begin(); itValue != _lValueList.end(); ++itValue, ++itSub) { auto value = *itValue; const auto& sub = *itSub; if (!value || !value->isAttachedToDocument()) { if (!values.empty()) { values.push_back(value); subs.push_back(sub); } continue; } auto res = tryReplaceLink(getContainer(), value, parent, oldObj, newObj, sub.c_str()); if (res.first) { if (values.empty()) { values.reserve(_lValueList.size()); values.insert(values.end(), _lValueList.begin(), itValue); subs.reserve(_lSubList.size()); subs.insert(subs.end(), _lSubList.begin(), itSub); } if (res.first == newObj) { // check for duplication auto itS = subs.begin(); for (auto itV = values.begin(); itV != values.end();) { if (*itV == res.first && *itS == res.second) { itV = values.erase(itV); itS = subs.erase(itS); } else { ++itV; ++itS; } } positions.push_back(values.size()); } values.push_back(res.first); subs.push_back(std::move(res.second)); } else if (!values.empty()) { bool duplicate = false; if (value == newObj) { for (auto pos : positions) { if (sub == subs[pos]) { duplicate = true; break; } } } if (!duplicate) { values.push_back(value); subs.push_back(sub); } } } if (values.empty()) { return nullptr; } std::unique_ptr p(new PropertyLinkSubList); p->_lValueList = std::move(values); p->_lSubList = std::move(subs); return p.release(); } Property* PropertyLinkSubList::Copy() const { PropertyLinkSubList* p = new PropertyLinkSubList(); p->_lValueList = _lValueList; p->_lSubList = _lSubList; p->_ShadowSubList = _ShadowSubList; return p; } void PropertyLinkSubList::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } auto& link = static_cast(from); setValues(link._lValueList, link._lSubList, std::vector(link._ShadowSubList)); } unsigned int PropertyLinkSubList::getMemSize() const { unsigned int size = static_cast(_lValueList.size() * sizeof(App::DocumentObject*)); for (int i = 0; i < getSize(); i++) { size += _lSubList[i].size(); } return size; } std::vector PropertyLinkSubList::getSubValues(bool newStyle) const { assert(_lSubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_ShadowSubList.size()); std::string tmp; for (size_t i = 0; i < _ShadowSubList.size(); ++i) { ret.push_back(getSubNameWithStyle(_lSubList[i], _ShadowSubList[i], newStyle, tmp)); } return ret; } void PropertyLinkSubList::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { if (all || _pcScope != LinkScope::Hidden) { objs.reserve(objs.size() + _lValueList.size()); for (auto obj : _lValueList) { if (obj && obj->isAttachedToDocument()) { objs.push_back(obj); } } if (subs) { auto _subs = getSubValues(newStyle); subs->reserve(subs->size() + _subs.size()); std::move(_subs.begin(), _subs.end(), std::back_inserter(*subs)); } } } void PropertyLinkSubList::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { if (!obj || (!all && _pcScope == LinkScope::Hidden)) { return; } App::SubObjectT objT(obj, subname); auto subObject = objT.getSubObject(); auto subElement = objT.getOldElementName(); int i = -1; for (const auto& docObj : _lValueList) { ++i; if (docObj != obj) { continue; } // If we don't specify a subname we looking for all; or if the subname is in our // property, add this entry to our result if (!subname || (i < (int)_lSubList.size() && subname == _lSubList[i])) { identifiers.emplace_back(*this, i); continue; } // If we couldn't find any subobjects or this object's index is in our list, ignore it if (!subObject || i < (int)_lSubList.size()) { continue; } App::SubObjectT sobjT(obj, _lSubList[i].c_str()); if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this); continue; } if (i < (int)_ShadowSubList.size()) { const auto& shadow = _ShadowSubList[i]; App::SubObjectT sobjT(obj, shadow.newName.empty() ? shadow.oldName.c_str() : shadow.newName.c_str()); if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this); continue; } } } } void PropertyLinkSubList::breakLink(App::DocumentObject* obj, bool clear) { std::vector values; std::vector subs; if (clear && getContainer() == obj) { setValues(values, subs); return; } assert(_lValueList.size() == _lSubList.size()); values.reserve(_lValueList.size()); subs.reserve(_lSubList.size()); int i = -1; for (auto o : _lValueList) { ++i; if (o == obj) { continue; } values.push_back(o); subs.push_back(_lSubList[i]); } if (values.size() != _lValueList.size()) { setValues(values, subs); } } bool PropertyLinkSubList::adjustLink(const std::set& inList) { if (_pcScope == LinkScope::Hidden) { return false; } auto subs = _lSubList; auto links = _lValueList; int idx = -1; bool touched = false; for (std::string& sub : subs) { ++idx; auto& link = links[idx]; if (!link || !link->isAttachedToDocument() || !inList.contains(link)) { continue; } touched = true; size_t pos = sub.find('.'); for (; pos != std::string::npos; pos = sub.find('.', pos + 1)) { auto sobj = link->getSubObject(sub.substr(0, pos + 1).c_str()); if (!sobj || sobj->getDocument() != link->getDocument()) { pos = std::string::npos; break; } if (!inList.contains(sobj)) { link = sobj; sub = sub.substr(pos + 1); break; } } if (pos == std::string::npos) { return false; } } if (touched) { setValues(links, subs); } return touched; } //************************************************************************** // DocInfo //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Key on absolute path. // Because of possible symbolic links, multiple entry may refer to the same // file. We used to rely on QFileInfo::canonicalFilePath to resolve it, but // has now been changed to simply use the absoluteFilePath(), and rely on user // to be aware of possible duplicated file location. The reason being that // some user (especially Linux user) use symlink to organize file tree. using DocInfoMap = std::map; DocInfoMap _DocInfoMap; class App::DocInfo: public std::enable_shared_from_this { public: using Connection = boost::signals2::scoped_connection; Connection connFinishRestoreDocument; Connection connPendingReloadDocument; Connection connDeleteDocument; Connection connSaveDocument; Connection connDeletedObject; DocInfoMap::iterator myPos; std::string myPath; App::Document* pcDoc {nullptr}; std::set links; static std::string getDocPath(const char* filename, App::Document* pDoc, bool relative, QString* fullPath = nullptr) { bool absolute; // The path could be an URI, in that case // TODO: build a far much more resilient approach to test for an URI QString path = QString::fromUtf8(filename); if (path.startsWith(QLatin1String("https://"))) { // We do have an URI if (fullPath) { *fullPath = path; } return std::string(filename); } // make sure the filename is absolute path path = QDir::cleanPath(path); if ((absolute = QFileInfo(path).isAbsolute())) { if (fullPath) { *fullPath = path; } if (!relative) { return std::string(path.toUtf8().constData()); } } const char* docPath = pDoc->getFileName(); if (!docPath || *docPath == 0) { throw Base::RuntimeError("Owner document not saved"); } QDir docDir(QFileInfo(QString::fromUtf8(docPath)).absoluteDir()); if (!absolute) { path = QDir::cleanPath(docDir.absoluteFilePath(path)); if (fullPath) { *fullPath = path; } } if (relative) { return std::string(docDir.relativeFilePath(path).toUtf8().constData()); } else { return std::string(path.toUtf8().constData()); } } static DocInfoPtr get(const char* filename, App::Document* pDoc, PropertyXLink* l, const char* objName) { QString path; l->filePath = getDocPath(filename, pDoc, true, &path); FC_LOG("finding doc " << filename); auto it = _DocInfoMap.find(path); DocInfoPtr info; if (it != _DocInfoMap.end()) { info = it->second; if (!info->pcDoc) { QString fullpath(info->getFullPath()); if (fullpath.size() && App::GetApplication().addPendingDocument( fullpath.toUtf8().constData(), objName, l->testFlag(PropertyLinkBase::LinkAllowPartial)) == 0) { for (App::Document* doc : App::GetApplication().getDocuments()) { if (getFullPath(doc->getFileName()) == fullpath) { info->attach(doc); break; } } } } } else { info = std::make_shared(); auto ret = _DocInfoMap.insert(std::make_pair(path, info)); info->init(ret.first, objName, l); } if (info->pcDoc) { // make sure to attach only external object auto owner = freecad_cast(l->getContainer()); if (owner && owner->getDocument() == info->pcDoc) { return info; } } info->links.insert(l); return info; } static QString getFullPath(const char* p) { QString path = QString::fromUtf8(p); if (path.isEmpty()) { return path; } if (path.startsWith(QLatin1String("https://"))) { return path; } else { return QFileInfo(path).absoluteFilePath(); } } QString getFullPath() const { QString path = myPos->first; if (path.startsWith(QLatin1String("https://"))) { return path; } else { return QFileInfo(myPos->first).absoluteFilePath(); } } const char* filePath() const { return myPath.c_str(); } void deinit() { FC_LOG("deinit " << (pcDoc ? pcDoc->getName() : filePath())); assert(links.empty()); connFinishRestoreDocument.disconnect(); connPendingReloadDocument.disconnect(); connDeleteDocument.disconnect(); connSaveDocument.disconnect(); connDeletedObject.disconnect(); auto me = shared_from_this(); _DocInfoMap.erase(myPos); myPos = _DocInfoMap.end(); myPath.clear(); pcDoc = nullptr; } void init(DocInfoMap::iterator pos, const char* objName, PropertyXLink* l) { myPos = pos; myPath = myPos->first.toUtf8().constData(); App::Application& app = App::GetApplication(); // NOLINTBEGIN connFinishRestoreDocument = app.signalFinishRestoreDocument.connect( std::bind(&DocInfo::slotFinishRestoreDocument, this, sp::_1)); connPendingReloadDocument = app.signalPendingReloadDocument.connect( std::bind(&DocInfo::slotFinishRestoreDocument, this, sp::_1)); connDeleteDocument = app.signalDeleteDocument.connect(std::bind(&DocInfo::slotDeleteDocument, this, sp::_1)); connSaveDocument = app.signalSaveDocument.connect(std::bind(&DocInfo::slotSaveDocument, this, sp::_1)); // NOLINTEND QString fullpath(getFullPath()); if (fullpath.isEmpty()) { FC_ERR("document not found " << filePath()); } else { for (App::Document* doc : App::GetApplication().getDocuments()) { if (getFullPath(doc->getFileName()) == fullpath) { if (doc->testStatus(App::Document::PartialDoc) && !doc->getObject(objName)) { break; } attach(doc); return; } } FC_LOG("document pending " << filePath()); app.addPendingDocument(fullpath.toUtf8().constData(), objName, l->testFlag(PropertyLinkBase::LinkAllowPartial)); } } void attach(Document* doc) { assert(!pcDoc); pcDoc = doc; FC_LOG("attaching " << doc->getName() << ", " << doc->getFileName()); std::map> parentLinks; for (auto it = links.begin(), itNext = it; it != links.end(); it = itNext) { ++itNext; auto link = *it; if (link->_pcLink) { continue; } if (link->parentProp) { parentLinks[link->parentProp].push_back(link); continue; } auto obj = doc->getObject(link->objectName.c_str()); if (obj) { link->restoreLink(obj); } else if (doc->testStatus(App::Document::PartialDoc)) { App::GetApplication().addPendingDocument(doc->FileName.getValue(), link->objectName.c_str(), false); FC_WARN("reloading partial document '" << doc->FileName.getValue() << "' due to object " << link->objectName); } else { FC_WARN("object '" << link->objectName << "' not found in document '" << doc->getName() << "'"); } } for (auto& v : parentLinks) { v.first->setFlag(PropertyLinkBase::LinkRestoring); v.first->aboutToSetValue(); for (auto link : v.second) { auto obj = doc->getObject(link->objectName.c_str()); if (obj) { link->restoreLink(obj); } else if (doc->testStatus(App::Document::PartialDoc)) { App::GetApplication().addPendingDocument(doc->FileName.getValue(), link->objectName.c_str(), false); FC_WARN("reloading partial document '" << doc->FileName.getValue() << "' due to object " << link->objectName); } else { FC_WARN("object '" << link->objectName << "' not found in document '" << doc->getName() << "'"); } } v.first->hasSetValue(); v.first->setFlag(PropertyLinkBase::LinkRestoring, false); } } void remove(PropertyXLink* l) { auto it = links.find(l); if (it != links.end()) { links.erase(it); if (links.empty()) { deinit(); } } } static void restoreDocument(const App::Document& doc) { auto it = _DocInfoMap.find(getFullPath(doc.FileName.getValue())); if (it == _DocInfoMap.end()) { return; } it->second->slotFinishRestoreDocument(doc); } void slotFinishRestoreDocument(const App::Document& doc) { if (pcDoc) { return; } QString fullpath(getFullPath()); if (!fullpath.isEmpty() && getFullPath(doc.getFileName()) == fullpath) { attach(const_cast(&doc)); } } void slotSaveDocument(const App::Document& doc) { if (!pcDoc) { slotFinishRestoreDocument(doc); return; } if (&doc != pcDoc) { return; } QFileInfo info(myPos->first); QString path(info.absoluteFilePath()); const char* filename = doc.getFileName(); QString docPath(getFullPath(filename)); if (path.isEmpty() || path != docPath) { FC_LOG("document '" << doc.getName() << "' path changed"); auto me = shared_from_this(); auto ret = _DocInfoMap.insert(std::make_pair(docPath, me)); if (!ret.second) { // is that even possible? FC_WARN("document '" << doc.getName() << "' path exists, detach"); slotDeleteDocument(doc); return; } _DocInfoMap.erase(myPos); myPos = ret.first; std::set tmp; tmp.swap(links); for (auto link : tmp) { auto owner = static_cast(link->getContainer()); // adjust file path for each PropertyXLink DocInfo::get(filename, owner->getDocument(), link, link->objectName.c_str()); } } // time stamp changed, touch the linking document. std::set docs; for (auto link : links) { auto linkdoc = static_cast(link->getContainer())->getDocument(); auto ret = docs.insert(linkdoc); if (ret.second) { // This will signal the Gui::Document to call setModified(); FC_LOG("touch document " << linkdoc->getName() << " on time stamp change of " << link->getFullName()); linkdoc->Comment.touch(); } } } void slotDeleteDocument(const App::Document& doc) { for (auto it = links.begin(), itNext = it; it != links.end(); it = itNext) { ++itNext; auto link = *it; auto obj = freecad_cast(link->getContainer()); if (obj && obj->getDocument() == &doc) { links.erase(it); // must call unlink here, so that PropertyLink::resetLink can // remove back link before the owner object is marked as being // destroyed link->unlink(); } } if (links.empty()) { deinit(); return; } if (pcDoc != &doc) { return; } std::map> parentLinks; for (auto link : links) { link->setFlag(PropertyLinkBase::LinkDetached); if (link->parentProp) { parentLinks[link->parentProp].push_back(link); } else { parentLinks[nullptr].push_back(link); } } for (auto& v : parentLinks) { if (v.first) { v.first->setFlag(PropertyLinkBase::LinkDetached); v.first->aboutToSetValue(); } for (auto l : v.second) { l->detach(); } if (v.first) { v.first->hasSetValue(); v.first->setFlag(PropertyLinkBase::LinkDetached, false); } } pcDoc = nullptr; } bool hasXLink(const App::Document* doc) const { for (auto link : links) { auto obj = freecad_cast(link->getContainer()); if (obj && obj->getDocument() == doc) { return true; } } return false; } static void breakLinks(App::DocumentObject* obj, bool clear) { auto doc = obj->getDocument(); for (auto itD = _DocInfoMap.begin(), itDNext = itD; itD != _DocInfoMap.end(); itD = itDNext) { ++itDNext; auto docInfo = itD->second; if (docInfo->pcDoc != doc) { continue; } auto& links = docInfo->links; std::set parentLinks; for (auto it = links.begin(), itNext = it; it != links.end(); it = itNext) { ++itNext; auto link = *it; if (link->_pcLink != obj && !(clear && link->getContainer() == obj)) { continue; } if (link->parentProp) { parentLinks.insert(link->parentProp); } else { link->breakLink(obj, clear); } } for (auto link : parentLinks) { link->breakLink(obj, clear); } } } }; void PropertyLinkBase::breakLinks(App::DocumentObject* link, const std::vector& objs, bool clear) { std::vector props; for (auto obj : objs) { props.clear(); obj->getPropertyList(props); for (auto prop : props) { auto linkProp = freecad_cast(prop); if (linkProp) { linkProp->breakLink(link, clear); } } } DocInfo::breakLinks(link, clear); } //************************************************************************** // PropertyXLink //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyXLink, App::PropertyLink) PropertyXLink::PropertyXLink(bool _allowPartial, PropertyLinkBase* parent) : parentProp(parent) { setAllowPartial(_allowPartial); setAllowExternal(true); setSyncSubObject(true); if (parent) { setContainer(parent->getContainer()); } } PropertyXLink::~PropertyXLink() { try { unlink(); } catch (std::bad_weak_ptr &) { FC_WARN("Bad pointer exception caught when destroying PropertyXLink\n"); } } void PropertyXLink::setSyncSubObject(bool enable) { _Flags.set((std::size_t)LinkSyncSubObject, enable); } void PropertyXLink::unlink() { if (docInfo) { docInfo->remove(this); docInfo.reset(); } objectName.clear(); resetLink(); } void PropertyXLink::detach() { if (docInfo && _pcLink) { aboutToSetValue(); resetLink(); updateElementReference(nullptr); hasSetValue(); } } void PropertyXLink::aboutToSetValue() { if (parentProp) { parentProp->aboutToSetChildValue(*this); } else { PropertyLinkBase::aboutToSetValue(); } } void PropertyXLink::hasSetValue() { if (parentProp) { parentProp->hasSetChildValue(*this); } else { PropertyLinkBase::hasSetValue(); } } void PropertyXLink::setSubName(const char* subname) { std::vector subs; if (!Base::Tools::isNullOrEmpty(subname)) { subs.emplace_back(subname); } aboutToSetValue(); setSubValues(std::move(subs)); hasSetValue(); } void PropertyXLink::setSubValues(std::vector&& subs, std::vector&& shadows) { _SubList = std::move(subs); _ShadowSubList.clear(); if (shadows.size() == _SubList.size()) { _ShadowSubList = std::move(shadows); onContainerRestored(); // re-register element references } else { updateElementReference(nullptr); } checkLabelReferences(_SubList); } void PropertyXLink::setValue(App::DocumentObject* lValue) { setValue(lValue, nullptr); } void PropertyXLink::setValue(App::DocumentObject* lValue, const char* subname) { std::vector subs; if (!Base::Tools::isNullOrEmpty(subname)) { subs.emplace_back(subname); } setValue(lValue, std::move(subs)); } void PropertyXLink::restoreLink(App::DocumentObject* lValue) { assert(!_pcLink && lValue && docInfo); auto owner = freecad_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { throw Base::RuntimeError("invalid container"); } bool touched = owner->isTouched(); setFlag(LinkDetached, false); setFlag(LinkRestoring); aboutToSetValue(); if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { lValue->_addBackLink(owner); } _pcLink = lValue; updateElementReference(nullptr); hasSetValue(); setFlag(LinkRestoring, false); if (!touched && owner->isTouched() && docInfo && docInfo->pcDoc && stamp == docInfo->pcDoc->LastModifiedDate.getValue()) { owner->purgeTouched(); } } void PropertyXLink::setValue(App::DocumentObject* lValue, std::vector&& subs, std::vector&& shadows) { if (_pcLink == lValue && _SubList == subs) { return; } if (lValue && (!lValue->isAttachedToDocument() || !lValue->getDocument())) { throw Base::ValueError("Invalid object"); } auto owner = freecad_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { throw Base::RuntimeError("invalid container"); } if (lValue == owner) { throw Base::ValueError("self linking"); } aboutToSetValue(); DocInfoPtr info; const char* name = ""; if (lValue) { name = lValue->getNameInDocument(); if (lValue->getDocument() != owner->getDocument()) { if (!docInfo || lValue->getDocument() != docInfo->pcDoc) { const char* filename = lValue->getDocument()->getFileName(); if (!filename || *filename == 0) { throw Base::RuntimeError("Linked document not saved"); } FC_LOG("xlink set to new document " << lValue->getDocument()->getName()); info = DocInfo::get(filename, owner->getDocument(), this, name); assert(info && info->pcDoc == lValue->getDocument()); } else { info = docInfo; } } } setFlag(LinkDetached, false); if (!owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { if (_pcLink) { _pcLink->_removeBackLink(owner); } if (lValue) { lValue->_addBackLink(owner); } } if (docInfo != info) { unlink(); docInfo = info; } if (!docInfo) { filePath.clear(); } _pcLink = lValue; if (docInfo && docInfo->pcDoc) { stamp = docInfo->pcDoc->LastModifiedDate.getValue(); } objectName = name; setSubValues(std::move(subs), std::move(shadows)); hasSetValue(); } void PropertyXLink::setValue(std::string&& filename, std::string&& name, std::vector&& subs, std::vector&& shadows) { if (name.empty()) { setValue(nullptr, std::move(subs), std::move(shadows)); return; } auto owner = freecad_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { throw Base::RuntimeError("invalid container"); } DocumentObject* pObject = nullptr; DocInfoPtr info; if (!filename.empty()) { owner->getDocument()->signalLinkXsetValue(filename); info = DocInfo::get(filename.c_str(), owner->getDocument(), this, name.c_str()); if (info->pcDoc) { pObject = info->pcDoc->getObject(name.c_str()); } } else { pObject = owner->getDocument()->getObject(name.c_str()); } if (pObject) { setValue(pObject, std::move(subs), std::move(shadows)); return; } setFlag(LinkDetached, false); aboutToSetValue(); if (_pcLink && !owner->testStatus(ObjectStatus::Destroy) && _pcScope != LinkScope::Hidden) { _pcLink->_removeBackLink(owner); } _pcLink = nullptr; if (docInfo != info) { unlink(); docInfo = info; } if (!docInfo) { filePath.clear(); } if (docInfo && docInfo->pcDoc) { stamp = docInfo->pcDoc->LastModifiedDate.getValue(); } objectName = std::move(name); setSubValues(std::move(subs), std::move(shadows)); hasSetValue(); } void PropertyXLink::setValue(App::DocumentObject* link, const std::vector& subs, std::vector&& shadows) { setValue(link, std::vector(subs), std::move(shadows)); } App::Document* PropertyXLink::getDocument() const { return docInfo ? docInfo->pcDoc : nullptr; } const char* PropertyXLink::getDocumentPath() const { return docInfo ? docInfo->filePath() : filePath.c_str(); } const char* PropertyXLink::getObjectName() const { return objectName.c_str(); } bool PropertyXLink::upgrade(Base::XMLReader& reader, const char* typeName) { if (strcmp(typeName, App::PropertyLinkGlobal::getClassTypeId().getName()) == 0 || strcmp(typeName, App::PropertyLink::getClassTypeId().getName()) == 0 || strcmp(typeName, App::PropertyLinkChild::getClassTypeId().getName()) == 0) { PropertyLink::Restore(reader); return true; } FC_ERR("Cannot upgrade from " << typeName); return false; } int PropertyXLink::checkRestore(std::string* msg) const { if (!docInfo) { if (!_pcLink && !objectName.empty()) { // this condition means linked object not found if (msg) { std::ostringstream ss; ss << "Link not restored" << std::endl; ss << "Object: " << objectName; if (!filePath.empty()) { ss << std::endl << "File: " << filePath; } *msg = ss.str(); } return 2; } return 0; } if (!_pcLink) { if (testFlag(LinkSilentRestore)) { return 0; } if (testFlag(LinkAllowPartial) && (!docInfo->pcDoc || docInfo->pcDoc->testStatus(App::Document::PartialDoc))) { return 0; } if (msg) { std::ostringstream ss; ss << "Link not restored" << std::endl; ss << "Linked object: " << objectName; if (docInfo->pcDoc) { ss << std::endl << "Linked document: " << docInfo->pcDoc->Label.getValue(); } else if (!filePath.empty()) { ss << std::endl << "Linked file: " << filePath; } *msg = ss.str(); } return 2; } if (!docInfo->pcDoc || stamp == docInfo->pcDoc->LastModifiedDate.getValue()) { return 0; } if (msg) { std::ostringstream ss; ss << "Time stamp changed on link " << _pcLink->getFullName(); *msg = ss.str(); } return 1; } void PropertyXLink::afterRestore() { assert(_SubList.size() == _ShadowSubList.size()); if (!testFlag(LinkRestoreLabel) || !_pcLink || !_pcLink->isAttachedToDocument()) { return; } setFlag(LinkRestoreLabel, false); for (size_t i = 0; i < _SubList.size(); ++i) { restoreLabelReference(_pcLink, _SubList[i], &_ShadowSubList[i]); } } void PropertyXLink::onContainerRestored() { if (!_pcLink || !_pcLink->isAttachedToDocument()) { return; } for (size_t i = 0; i < _SubList.size(); ++i) { _registerElementReference(_pcLink, _SubList[i], _ShadowSubList[i]); } } void PropertyXLink::updateElementReference(DocumentObject* feature, bool reverse, bool notify) { if (!updateLinkReference(this, feature, reverse, notify, _pcLink, _SubList, _mapped, _ShadowSubList)) { return; } if (notify) { hasSetValue(); } } bool PropertyXLink::referenceChanged() const { return !_mapped.empty(); } void PropertyXLink::Save(Base::Writer& writer) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument()) { return; } assert(_SubList.size() == _ShadowSubList.size()); auto exporting = owner->isExporting(); if (_pcLink && exporting && _pcLink->isExporting()) { // this means, we are exporting the owner and the linked object together. // Lets save the export name writer.Stream() << writer.ind() << "getExportName(); } else { const char* path = filePath.c_str(); std::string _path; if (exporting) { // Here means we are exporting the owner but not exporting the // linked object. Try to use absolute file path for easy transition // into document at different directory if (docInfo) { _path = docInfo->filePath(); } else { auto pDoc = owner->getDocument(); const char* docPath = pDoc->getFileName(); if (!Base::Tools::isNullOrEmpty(docPath)) { if (!filePath.empty()) { _path = DocInfo::getDocPath(filePath.c_str(), pDoc, false); } else { _path = docPath; } } else { FC_WARN("PropertyXLink export without saving the document"); } } if (!_path.empty()) { path = _path.c_str(); } } writer.Stream() << writer.ind() << "pcDoc ? docInfo->pcDoc->LastModifiedDate.getValue() : "") << "\" name=\"" << objectName; } if (testFlag(LinkAllowPartial)) { writer.Stream() << "\" partial=\"1"; } if (_SubList.empty()) { writer.Stream() << "\"/>" << std::endl; } else if (_SubList.size() == 1) { const auto& subName = _SubList[0]; const auto& shadowSub = _ShadowSubList[0]; const auto& sub = shadowSub.oldName.empty() ? subName : shadowSub.oldName; if (exporting) { std::string exportName; writer.Stream() << "\" sub=\"" << encodeAttribute(exportSubName(exportName, _pcLink, sub.c_str())); if (!shadowSub.oldName.empty() && shadowSub.newName == subName) { writer.Stream() << "\" " ATTR_MAPPED "=\"1"; } } else { writer.Stream() << "\" sub=\"" << encodeAttribute(sub); if (!sub.empty()) { if (sub != subName) { writer.Stream() << "\" " ATTR_SHADOWED "=\"" << encodeAttribute(subName); } else if (!shadowSub.newName.empty()) { writer.Stream() << "\" " ATTR_SHADOW "=\"" << encodeAttribute(shadowSub.newName); } } } writer.Stream() << "\"/>" << std::endl; } else { writer.Stream() << "\" count=\"" << _SubList.size() << "\">" << std::endl; writer.incInd(); for (unsigned int i = 0; i < _SubList.size(); i++) { const auto& shadow = _ShadowSubList[i]; // shadow.oldName stores the old style element name. For backward // compatibility reason, we shall store the old name into attribute // 'value' whenever possible. const auto& sub = shadow.oldName.empty() ? _SubList[i] : shadow.oldName; writer.Stream() << writer.ind() << "" << endl; } writer.decInd(); writer.Stream() << writer.ind() << "" << endl; } } void PropertyXLink::Restore(Base::XMLReader& reader) { // read my element reader.readElement("XLink"); std::string stampAttr, file; if (reader.hasAttribute("stamp")) { stampAttr = reader.getAttribute("stamp"); } if (reader.hasAttribute("file")) { file = reader.getAttribute("file"); } setFlag(LinkAllowPartial, reader.hasAttribute("partial") && reader.getAttribute("partial")); std::string name; if (file.empty()) { name = reader.getName(reader.getAttribute("name")); } else { name = reader.getAttribute("name"); } assert(getContainer()->isDerivedFrom()); DocumentObject* object = nullptr; if (!name.empty() && file.empty()) { DocumentObject* parent = static_cast(getContainer()); Document* document = parent->getDocument(); object = document ? document->getObject(name.c_str()) : nullptr; if (!object) { if (reader.isVerbose()) { FC_WARN("Lost link to '" << name << "' while loading, maybe " "an object was not loaded correctly"); } } } std::vector subs; std::vector shadows; std::vector mapped; bool restoreLabel = false; if (reader.hasAttribute("sub")) { if (reader.hasAttribute(ATTR_MAPPED)) { mapped.push_back(0); } subs.emplace_back(); auto& subname = subs.back(); shadows.emplace_back(); auto& shadow = shadows.back(); shadow.oldName = importSubName(reader, reader.getAttribute("sub"), restoreLabel); if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { subname = shadow.newName = importSubName(reader, reader.getAttribute(ATTR_SHADOWED), restoreLabel); } else { subname = shadow.oldName; if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) { shadow.newName = importSubName(reader, reader.getAttribute(ATTR_SHADOW), restoreLabel); } } } else if (reader.hasAttribute("count")) { int count = reader.getAttribute("count"); subs.resize(count); shadows.resize(count); for (int i = 0; i < count; i++) { reader.readElement("Sub"); shadows[i].oldName = importSubName(reader, reader.getAttribute("value"), restoreLabel); if (reader.hasAttribute(ATTR_SHADOWED) && !IGNORE_SHADOW) { subs[i] = shadows[i].newName = importSubName(reader, reader.getAttribute(ATTR_SHADOWED), restoreLabel); } else { subs[i] = shadows[i].oldName; if (reader.hasAttribute(ATTR_SHADOW) && !IGNORE_SHADOW) { shadows[i].newName = importSubName(reader, reader.getAttribute(ATTR_SHADOW), restoreLabel); } } if (reader.hasAttribute(ATTR_MAPPED)) { mapped.push_back(i); } } reader.readEndElement("XLink"); } setFlag(LinkRestoreLabel, restoreLabel); if (name.empty()) { setValue(nullptr); return; } if (!file.empty() || (!object && !name.empty())) { this->stamp = stampAttr; setValue(std::move(file), std::move(name), std::move(subs), std::move(shadows)); } else { setValue(object, std::move(subs), std::move(shadows)); } _mapped = std::move(mapped); } Property* PropertyXLink::CopyOnImportExternal(const std::map& nameMap) const { auto owner = freecad_cast(getContainer()); if (!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument()) { return nullptr; } auto subs = updateLinkSubs(_pcLink, _SubList, &tryImportSubName, owner->getDocument(), nameMap); auto linked = tryImport(owner->getDocument(), _pcLink, nameMap); if (subs.empty() && linked == _pcLink) { return nullptr; } std::unique_ptr p(new PropertyXLink); copyTo(*p, linked, &subs); return p.release(); } Property* PropertyXLink::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { auto res = tryReplaceLinkSubs(getContainer(), _pcLink, parent, oldObj, newObj, _SubList); if (!res.first) { return nullptr; } std::unique_ptr p(new PropertyXLink); copyTo(*p, res.first, &res.second); return p.release(); } Property* PropertyXLink::CopyOnLabelChange(App::DocumentObject* obj, const std::string& ref, const char* newLabel) const { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->getDocument() || !_pcLink || !_pcLink->isAttachedToDocument()) { return nullptr; } auto subs = updateLinkSubs(_pcLink, _SubList, &updateLabelReference, obj, ref, newLabel); if (subs.empty()) { return nullptr; } std::unique_ptr p(new PropertyXLink); copyTo(*p, _pcLink, &subs); return p.release(); } void PropertyXLink::copyTo(PropertyXLink& other, DocumentObject* linked, std::vector* subs) const { if (!linked) { linked = _pcLink; } if (linked && linked->isAttachedToDocument()) { other.docName = linked->getDocument()->getName(); other.objectName = linked->getNameInDocument(); other.docInfo.reset(); other.filePath.clear(); } else { other.objectName = objectName; other.docName.clear(); other.docInfo = docInfo; other.filePath = filePath; } if (subs) { other._SubList = std::move(*subs); } else { other._SubList = _SubList; other._ShadowSubList = _ShadowSubList; } other._Flags = _Flags; } Property* PropertyXLink::Copy() const { std::unique_ptr p(new PropertyXLink); copyTo(*p); return p.release(); } void PropertyXLink::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } const auto& other = static_cast(from); if (!other.docName.empty()) { auto doc = GetApplication().getDocument(other.docName.c_str()); if (!doc) { FC_WARN("Document '" << other.docName << "' not found"); return; } auto obj = doc->getObject(other.objectName.c_str()); if (!obj) { FC_WARN("Object '" << other.docName << '#' << other.objectName << "' not found"); return; } setValue(obj, std::vector(other._SubList), std::vector(other._ShadowSubList)); } else { setValue(std::string(other.filePath), std::string(other.objectName), std::vector(other._SubList), std::vector(other._ShadowSubList)); } setFlag(LinkAllowPartial, other.testFlag(LinkAllowPartial)); } bool PropertyXLink::supportXLink(const App::Property* prop) { return prop->isDerivedFrom() || prop->isDerivedFrom() || prop->isDerivedFrom(); } bool PropertyXLink::hasXLink(const App::Document* doc) { for (auto& v : _DocInfoMap) { if (v.second->hasXLink(doc)) { return true; } } return false; } bool PropertyXLink::hasXLink(const std::vector& objs, std::vector* unsaved) { std::set docs; bool ret = false; for (auto o : objs) { if (o && o->isAttachedToDocument() && docs.insert(o->getDocument()).second) { if (!hasXLink(o->getDocument())) { continue; } if (!unsaved) { return true; } ret = true; if (!o->getDocument()->isSaved()) { unsaved->push_back(o->getDocument()); } } } return ret; } void PropertyXLink::restoreDocument(const App::Document& doc) { DocInfo::restoreDocument(doc); } std::map> PropertyXLink::getDocumentOutList(App::Document* doc) { std::map> ret; for (auto& v : _DocInfoMap) { for (auto link : v.second->links) { if (!v.second->pcDoc || link->getScope() == LinkScope::Hidden || link->testStatus(Property::PropTransient) || link->testStatus(Property::Transient) || link->testStatus(Property::PropNoPersist)) { continue; } auto obj = dynamic_cast(link->getContainer()); if (!obj || !obj->isAttachedToDocument() || !obj->getDocument()) { continue; } if (doc && obj->getDocument() != doc) { continue; } ret[obj->getDocument()].insert(v.second->pcDoc); } } return ret; } std::map> PropertyXLink::getDocumentInList(App::Document* doc) { std::map> ret; for (auto& v : _DocInfoMap) { if (!v.second->pcDoc || (doc && doc != v.second->pcDoc)) { continue; } auto& docs = ret[v.second->pcDoc]; for (auto link : v.second->links) { if (link->getScope() == LinkScope::Hidden || link->testStatus(Property::PropTransient) || link->testStatus(Property::Transient) || link->testStatus(Property::PropNoPersist)) { continue; } auto obj = dynamic_cast(link->getContainer()); if (obj && obj->isAttachedToDocument() && obj->getDocument()) { docs.insert(obj->getDocument()); } } } return ret; } PyObject* PropertyXLink::getPyObject() { if (!_pcLink) { Py_Return; } const auto& subs = getSubValues(false); if (subs.empty()) { return _pcLink->getPyObject(); } Py::Tuple ret(2); ret.setItem(0, Py::Object(_pcLink->getPyObject(), true)); PropertyString propString; if (subs.size() == 1) { propString.setValue(subs.front()); ret.setItem(1, Py::asObject(propString.getPyObject())); } else { Py::List list(subs.size()); int i = 0; for (auto& sub : subs) { propString.setValue(sub); list[i++] = Py::asObject(propString.getPyObject()); } ret.setItem(1, list); } return Py::new_reference_to(ret); } void PropertyXLink::setPyObject(PyObject* value) { if (PySequence_Check(value)) { Py::Sequence seq(value); if (seq.size() != 2) { throw Base::ValueError("Expect input sequence of size 2"); } std::vector subs; Py::Object pyObj(seq[0].ptr()); Py::Object pySub(seq[1].ptr()); if (pyObj.isNone()) { setValue(nullptr); return; } else if (!PyObject_TypeCheck(pyObj.ptr(), &DocumentObjectPy::Type)) { throw Base::TypeError("Expect the first element to be of 'DocumentObject'"); } PropertyString propString; if (pySub.isString()) { propString.setPyObject(pySub.ptr()); subs.push_back(propString.getStrValue()); } else if (pySub.isSequence()) { Py::Sequence seq(pySub); subs.reserve(seq.size()); for (Py_ssize_t i = 0; i < seq.size(); ++i) { Py::Object sub(seq[i]); if (!sub.isString()) { throw Base::TypeError("Expect only string inside second argument"); } propString.setPyObject(sub.ptr()); subs.push_back(propString.getStrValue()); } } else { throw Base::TypeError("Expect the second element to be a string or sequence of string"); } setValue(static_cast(pyObj.ptr())->getDocumentObjectPtr(), std::move(subs)); } else if (PyObject_TypeCheck(value, &(DocumentObjectPy::Type))) { setValue(static_cast(value)->getDocumentObjectPtr()); } else if (Py_None == value) { setValue(nullptr); } else { throw Base::TypeError( "type must be 'DocumentObject', 'None', or '(DocumentObject, SubName)' or " "'DocumentObject, [SubName..])"); } } const char* PropertyXLink::getSubName(bool newStyle) const { if (_SubList.empty() || _ShadowSubList.empty()) { return ""; } return getSubNameWithStyle(_SubList[0], _ShadowSubList[0], newStyle, tmpShadow).c_str(); } void PropertyXLink::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { if ((all || _pcScope != LinkScope::Hidden) && _pcLink && _pcLink->isAttachedToDocument()) { objs.push_back(_pcLink); if (subs && _SubList.size() == _ShadowSubList.size()) { *subs = getSubValues(newStyle); } } } void PropertyXLink::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { if (all || _pcScope != LinkScope::Hidden) { if (obj && obj == _pcLink) { _getLinksTo(identifiers, obj, subname, _SubList, _ShadowSubList); } } } bool PropertyXLink::adjustLink(const std::set& inList) { if (_pcScope == LinkScope::Hidden) { return false; } if (!_pcLink || !_pcLink->isAttachedToDocument() || !inList.contains(_pcLink)) { return false; } auto subs = _SubList; auto link = adjustLinkSubs(this, inList, _pcLink, subs); if (link) { setValue(link, std::move(subs)); return true; } return false; } std::vector PropertyXLink::getSubValues(bool newStyle) const { assert(_SubList.size() == _ShadowSubList.size()); std::vector ret; ret.reserve(_SubList.size()); std::string tmp; for (size_t i = 0; i < _ShadowSubList.size(); ++i) { ret.push_back(getSubNameWithStyle(_SubList[i], _ShadowSubList[i], newStyle, tmp)); } return ret; } std::vector PropertyXLink::getSubValuesStartsWith(const char* starter, bool newStyle) const { (void)newStyle; std::vector temp; for (const auto& it : _SubList) { if (strncmp(starter, it.c_str(), strlen(starter)) == 0) { temp.push_back(it); } } return temp; } void PropertyXLink::setAllowPartial(bool enable) { setFlag(LinkAllowPartial, enable); if (enable) { return; } auto owner = dynamic_cast(getContainer()); if (!owner) { return; } if (!App::GetApplication().isRestoring() && !owner->getDocument()->isPerformingTransaction() && !_pcLink && docInfo && !filePath.empty() && !objectName.empty() && (!docInfo->pcDoc || docInfo->pcDoc->testStatus(Document::PartialDoc))) { auto path = docInfo->getDocPath(filePath.c_str(), owner->getDocument(), false); if (!path.empty()) { App::GetApplication().openDocument(path.c_str()); } } } //************************************************************************** // PropertyXLinkSub //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyXLinkSub, App::PropertyXLink) TYPESYSTEM_SOURCE(App::PropertyXLinkSubHidden, App::PropertyXLinkSub) PropertyXLinkSub::PropertyXLinkSub(bool allowPartial, PropertyLinkBase* parent) : PropertyXLink(allowPartial, parent) {} PropertyXLinkSub::~PropertyXLinkSub() = default; bool PropertyXLinkSub::upgrade(Base::XMLReader& reader, const char* typeName) { if (strcmp(typeName, PropertyLinkSubGlobal::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkSub::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkSubChild::getClassTypeId().getName()) == 0) { App::PropertyLinkSub linkProp; linkProp.setContainer(getContainer()); linkProp.Restore(reader); setValue(linkProp.getValue(), linkProp.getSubValues()); return true; } return PropertyXLink::upgrade(reader, typeName); } PyObject* PropertyXLinkSub::getPyObject() { if (!_pcLink) { Py_Return; } Py::Tuple ret(2); ret.setItem(0, Py::Object(_pcLink->getPyObject(), true)); const auto& subs = getSubValues(false); Py::List list(subs.size()); int i = 0; PropertyString propString; for (auto& sub : subs) { propString.setValue(sub); list[i++] = Py::asObject(propString.getPyObject()); } ret.setItem(1, list); return Py::new_reference_to(ret); } //************************************************************************** // PropertyXLinkSubList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyXLinkSubList, App::PropertyLinkBase) //************************************************************************** // Construction/Destruction PropertyXLinkSubList::PropertyXLinkSubList() { _pcScope = LinkScope::Global; setSyncSubObject(true); } PropertyXLinkSubList::~PropertyXLinkSubList() = default; void PropertyXLinkSubList::setSyncSubObject(bool enable) { _Flags.set((std::size_t)LinkSyncSubObject, enable); } int PropertyXLinkSubList::getSize() const { return static_cast(_Links.size()); } void PropertyXLinkSubList::setValue(DocumentObject* lValue, const char* SubName) { std::map> values; if (lValue) { auto& subs = values[lValue]; if (SubName) { subs.emplace_back(SubName); } } setValues(std::move(values)); } void PropertyXLinkSubList::setValues(const std::vector& lValue, const std::vector& lSubNames) { #define CHECK_SUB_SIZE(_l, _r) \ do { \ if (_l.size() != _r.size()) \ FC_THROWM(Base::ValueError, "object and subname size mismatch"); \ } while (0) CHECK_SUB_SIZE(lValue, lSubNames); std::map> values; int i = 0; for (auto& obj : lValue) { const char* sub = lSubNames[i++]; if (sub) { values[obj].emplace_back(sub); } } setValues(std::move(values)); } void PropertyXLinkSubList::setValues(const std::vector& lValue, const std::vector& lSubNames) { CHECK_SUB_SIZE(lValue, lSubNames); std::map> values; int i = 0; for (auto& obj : lValue) { values[obj].push_back(lSubNames[i++]); } setValues(std::move(values)); } void PropertyXLinkSubList::setSubListValues(const std::vector& svalues) { std::map> values; for (auto& v : svalues) { auto& s = values[v.first]; s.reserve(s.size() + v.second.size()); s.insert(s.end(), v.second.begin(), v.second.end()); } setValues(std::move(values)); } void PropertyXLinkSubList::setValues( const std::map>& values) { setValues(std::map>(values)); } void PropertyXLinkSubList::setValues( std::map>&& values) { for (auto& v : values) { if (!v.first || !v.first->isAttachedToDocument()) { FC_THROWM(Base::ValueError, "invalid document object"); } } atomic_change guard(*this); for (auto it = _Links.begin(), itNext = it; it != _Links.end(); it = itNext) { ++itNext; auto iter = values.find(it->getValue()); if (iter == values.end()) { _Links.erase(it); continue; } it->setSubValues(std::move(iter->second)); values.erase(iter); } for (auto& v : values) { _Links.emplace_back(testFlag(LinkAllowPartial), this); _Links.back().setValue(v.first, std::move(v.second)); } guard.tryInvoke(); } void PropertyXLinkSubList::addValue(App::DocumentObject* obj, const std::vector& subs, bool reset) { addValue(obj, std::vector(subs), reset); } void PropertyXLinkSubList::addValue(App::DocumentObject* obj, std::vector&& subs, bool reset) { if (!obj || !obj->isAttachedToDocument()) { FC_THROWM(Base::ValueError, "invalid document object"); } for (auto& l : _Links) { if (l.getValue() == obj) { auto s = l.getSubValues(); if (s.empty() || reset) { l.setSubValues(std::move(subs)); } else { s.reserve(s.size() + subs.size()); std::move(subs.begin(), subs.end(), std::back_inserter(s)); l.setSubValues(std::move(s)); } return; } } atomic_change guard(*this); _Links.emplace_back(testFlag(LinkAllowPartial), this); _Links.back().setValue(obj, std::move(subs)); guard.tryInvoke(); } void PropertyXLinkSubList::setValue(DocumentObject* lValue, const std::vector& SubList) { std::map> values; if (lValue) { values[lValue] = SubList; } setValues(std::move(values)); } void PropertyXLinkSubList::setValues(const std::vector& values) { atomic_change guard(*this); _Links.clear(); for (auto obj : values) { _Links.emplace_back(testFlag(LinkAllowPartial), this); _Links.back().setValue(obj); } guard.tryInvoke(); } void PropertyXLinkSubList::set1Value(int idx, DocumentObject* value, const std::vector& SubList) { if (idx < -1 || idx > getSize()) { throw Base::RuntimeError("index out of bound"); } if (idx < 0 || idx + 1 == getSize()) { if (SubList.empty()) { addValue(value, SubList); return; } atomic_change guard(*this); _Links.emplace_back(testFlag(LinkAllowPartial), this); _Links.back().setValue(value); guard.tryInvoke(); return; } auto it = _Links.begin(); for (; idx; --idx) { ++it; } it->setValue(value, SubList); } const string PropertyXLinkSubList::getPyReprString() const { if (_Links.empty()) { return std::string("None"); } std::ostringstream ss; ss << '['; for (auto& link : _Links) { auto obj = link.getValue(); if (!obj || !obj->isAttachedToDocument()) { continue; } ss << "(App.getDocument('" << obj->getDocument()->getName() << "').getObject('" << obj->getNameInDocument() << "'), ("; const auto& subs = link.getSubValues(); if (subs.empty()) { ss << "''"; } else { for (auto& sub : subs) { ss << "'" << sub << "',"; } } ss << ")), "; } ss << ']'; return ss.str(); } DocumentObject* PropertyXLinkSubList::getValue() const { if (!_Links.empty()) { return _Links.begin()->getValue(); } return nullptr; } int PropertyXLinkSubList::removeValue(App::DocumentObject* lValue) { atomic_change guard(*this, false); int ret = 0; for (auto it = _Links.begin(); it != _Links.end();) { if (it->getValue() != lValue) { ++it; } else { guard.aboutToChange(); it = _Links.erase(it); ++ret; } } guard.tryInvoke(); return ret; } PyObject* PropertyXLinkSubList::getPyObject() { Py::List list; for (auto& link : _Links) { auto obj = link.getValue(); if (!obj || !obj->isAttachedToDocument()) { continue; } Py::Tuple tup(2); tup[0] = Py::asObject(obj->getPyObject()); const auto& subs = link.getSubValues(); Py::Tuple items(subs.size()); for (std::size_t j = 0; j < subs.size(); j++) { items[j] = Py::String(subs[j]); } tup[1] = items; list.append(tup); } return Py::new_reference_to(list); } void PropertyXLinkSubList::setPyObject(PyObject* value) { try { // try PropertyLinkSub syntax PropertyLinkSub dummy; dummy.setAllowExternal(true); dummy.setPyObject(value); this->setValue(dummy.getValue(), dummy.getSubValues()); return; } catch (Base::Exception&) { } if (!PyTuple_Check(value) && !PyList_Check(value)) { throw Base::TypeError( "Invalid type. Accepts (DocumentObject, (subname...)) or sequence of such type."); } Py::Sequence seq(value); std::map> values; try { for (Py_ssize_t i = 0; i < seq.size(); ++i) { PropertyLinkSub link; link.setAllowExternal(true); link.setPyObject(seq[i].ptr()); const auto& subs = link.getSubValues(); auto& s = values[link.getValue()]; s.reserve(s.size() + subs.size()); s.insert(s.end(), subs.begin(), subs.end()); } } catch (Base::Exception&) { throw Base::TypeError( "Invalid type inside sequence. Must be type of (DocumentObject, (subname...))"); } setValues(std::move(values)); } void PropertyXLinkSubList::afterRestore() { for (auto& l : _Links) { l.afterRestore(); } } void PropertyXLinkSubList::onContainerRestored() { for (auto& l : _Links) { l.onContainerRestored(); } } void PropertyXLinkSubList::updateElementReference(DocumentObject* feature, bool reverse, bool notify) { for (auto& l : _Links) { l.updateElementReference(feature, reverse, notify); } } bool PropertyXLinkSubList::referenceChanged() const { for (auto& l : _Links) { if (l.referenceChanged()) { return true; } } return false; } void PropertyXLinkSubList::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << "" << endl; writer.incInd(); for (auto& l : _Links) { l.Save(writer); } writer.decInd(); writer.Stream() << writer.ind() << "" << endl; } void PropertyXLinkSubList::Restore(Base::XMLReader& reader) { reader.readElement("XLinkSubList"); setFlag(LinkAllowPartial, reader.hasAttribute("partial") && reader.getAttribute("partial")); int count = reader.getAttribute("count"); atomic_change guard(*this, false); _Links.clear(); for (int i = 0; i < count; ++i) { _Links.emplace_back(false, this); _Links.back().Restore(reader); } reader.readEndElement("XLinkSubList"); guard.tryInvoke(); } Property* PropertyXLinkSubList::CopyOnImportExternal(const std::map& nameMap) const { std::unique_ptr copy; auto it = _Links.begin(); for (; it != _Links.end(); ++it) { copy.reset(it->CopyOnImportExternal(nameMap)); if (copy) { break; } } if (!copy) { return nullptr; } std::unique_ptr p(new PropertyXLinkSubList); for (auto iter = _Links.begin(); iter != it; ++iter) { p->_Links.emplace_back(); iter->copyTo(p->_Links.back()); } p->_Links.emplace_back(); static_cast(*copy).copyTo(p->_Links.back()); for (++it; it != _Links.end(); ++it) { p->_Links.emplace_back(); copy.reset(it->CopyOnImportExternal(nameMap)); if (copy) { static_cast(*copy).copyTo(p->_Links.back()); } else { it->copyTo(p->_Links.back()); } } return p.release(); } Property* PropertyXLinkSubList::CopyOnLabelChange(App::DocumentObject* obj, const std::string& ref, const char* newLabel) const { std::unique_ptr copy; auto it = _Links.begin(); for (; it != _Links.end(); ++it) { copy.reset(it->CopyOnLabelChange(obj, ref, newLabel)); if (copy) { break; } } if (!copy) { return nullptr; } std::unique_ptr p(new PropertyXLinkSubList); for (auto iter = _Links.begin(); iter != it; ++iter) { p->_Links.emplace_back(); iter->copyTo(p->_Links.back()); } p->_Links.emplace_back(); static_cast(*copy).copyTo(p->_Links.back()); for (++it; it != _Links.end(); ++it) { p->_Links.emplace_back(); copy.reset(it->CopyOnLabelChange(obj, ref, newLabel)); if (copy) { static_cast(*copy).copyTo(p->_Links.back()); } else { it->copyTo(p->_Links.back()); } } return p.release(); } Property* PropertyXLinkSubList::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { std::unique_ptr copy; PropertyXLinkSub* copied = nullptr; std::set subs; auto it = _Links.begin(); for (; it != _Links.end(); ++it) { copy.reset(it->CopyOnLinkReplace(parent, oldObj, newObj)); if (copy) { copied = static_cast(copy.get()); if (copied->getValue() == newObj) { for (auto& sub : copied->getSubValues()) { subs.insert(sub); } } break; } } if (!copy) { return nullptr; } std::unique_ptr p(new PropertyXLinkSubList); for (auto iter = _Links.begin(); iter != it; ++iter) { if (iter->getValue() == newObj && copied->getValue() == newObj) { // merge subnames in case new object already exists for (auto& sub : iter->getSubValues()) { if (subs.insert(sub).second) { copied->_SubList.push_back(sub); } } } else { p->_Links.emplace_back(); iter->copyTo(p->_Links.back()); } } p->_Links.emplace_back(); copied->copyTo(p->_Links.back()); copied = &p->_Links.back(); for (++it; it != _Links.end(); ++it) { if ((it->getValue() == newObj || it->getValue() == oldObj) && copied->getValue() == newObj) { // merge subnames in case new object already exists for (auto& sub : it->getSubValues()) { if (subs.insert(sub).second) { copied->_SubList.push_back(sub); } } continue; } p->_Links.emplace_back(); copy.reset(it->CopyOnLinkReplace(parent, oldObj, newObj)); if (copy) { static_cast(*copy).copyTo(p->_Links.back()); } else { it->copyTo(p->_Links.back()); } } return p.release(); } Property* PropertyXLinkSubList::Copy() const { PropertyXLinkSubList* p = new PropertyXLinkSubList(); for (auto& l : _Links) { p->_Links.emplace_back(testFlag(LinkAllowPartial), p); l.copyTo(p->_Links.back()); } return p; } void PropertyXLinkSubList::Paste(const Property& from) { if (!from.isDerivedFrom()) { throw Base::TypeError("Incompatible property to paste to"); } aboutToSetValue(); _Links.clear(); for (auto& l : static_cast(from)._Links) { _Links.emplace_back(testFlag(LinkAllowPartial), this); _Links.back().Paste(l); } hasSetValue(); } unsigned int PropertyXLinkSubList::getMemSize() const { unsigned int size = 0; for (auto& l : _Links) { size += l.getMemSize(); } return size; } const std::vector& PropertyXLinkSubList::getSubValues(App::DocumentObject* obj) const { for (auto& l : _Links) { if (l.getValue() == obj) { return l.getSubValues(); } } FC_THROWM(Base::RuntimeError, "object not found"); } std::vector PropertyXLinkSubList::getSubValues(App::DocumentObject* obj, bool newStyle) const { for (auto& l : _Links) { if (l.getValue() == obj) { return l.getSubValues(newStyle); } } return {}; } void PropertyXLinkSubList::getLinks(std::vector& objs, bool all, std::vector* subs, bool newStyle) const { if (all || _pcScope != LinkScope::Hidden) { if (!subs) { objs.reserve(objs.size() + _Links.size()); for (auto& l : _Links) { auto obj = l.getValue(); if (obj && obj->isAttachedToDocument()) { objs.push_back(obj); } } return; } size_t count = 0; for (auto& l : _Links) { auto obj = l.getValue(); if (obj && obj->isAttachedToDocument()) { count += std::max((int)l.getSubValues().size(), 1); } } if (!count) { objs.reserve(objs.size() + _Links.size()); for (auto& l : _Links) { auto obj = l.getValue(); if (obj && obj->isAttachedToDocument()) { objs.push_back(obj); } } return; } objs.reserve(objs.size() + count); subs->reserve(subs->size() + count); for (auto& l : _Links) { auto obj = l.getValue(); if (obj && obj->isAttachedToDocument()) { auto subnames = l.getSubValues(newStyle); if (subnames.empty()) { subnames.emplace_back(""); } for (auto& sub : subnames) { objs.push_back(obj); subs->push_back(std::move(sub)); } } } } } // Same algorithm as _getLinksTo above, but returns all matches void PropertyXLinkSubList::_getLinksToList( std::vector& identifiers, App::DocumentObject* obj, const char* subname, const std::vector& subs, const std::vector& shadows) const { if (!subname) { identifiers.emplace_back(*this); return; } App::SubObjectT objT(obj, subname); auto subObject = objT.getSubObject(); auto subElement = objT.getOldElementName(); int i = -1; for (const auto& sub : subs) { ++i; if (sub == subname) { identifiers.emplace_back(*this, i); continue; } if (!subObject) { continue; } // There is a subobject and the subname doesn't match our current entry App::SubObjectT sobjT(obj, sub.c_str()); if (sobjT.getSubObject() == subObject && sobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this, i); continue; } // The oldElementName ( short, I.E. "Edge5" ) doesn't match. if (i < (int)shadows.size()) { const auto& [shadowNewName, shadowOldName] = shadows[i]; if (shadowNewName == subname || shadowOldName == subname) { identifiers.emplace_back(*this, i); continue; } if (!subObject) { continue; } App::SubObjectT shadowobjT(obj, shadowNewName.empty() ? shadowOldName.c_str() : shadowNewName.c_str()); if (shadowobjT.getSubObject() == subObject && shadowobjT.getOldElementName() == subElement) { identifiers.emplace_back(*this, i); continue; } } } } void PropertyXLinkSubList::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { if (!all && _pcScope != LinkScope::Hidden) { return; } for (auto& l : _Links) { if (obj && obj == l._pcLink) { _getLinksToList(identifiers, obj, subname, l._SubList, l._ShadowSubList); } } } void PropertyXLinkSubList::breakLink(App::DocumentObject* obj, bool clear) { if (clear && getContainer() == obj) { setValue(nullptr); return; } atomic_change guard(*this, false); for (auto& l : _Links) { if (l.getValue() == obj) { guard.aboutToChange(); l.setValue(nullptr); } } guard.tryInvoke(); } bool PropertyXLinkSubList::adjustLink(const std::set& inList) { if (_pcScope == LinkScope::Hidden) { return false; } std::map> values; bool touched = false; int count = 0; for (auto& l : _Links) { auto obj = l.getValue(); if (!obj || !obj->isAttachedToDocument()) { ++count; continue; } if (inList.contains(obj) && adjustLinkSubs(this, inList, obj, l._SubList, &values)) { touched = true; } } if (touched) { decltype(_Links) tmp; if (count) { // XLink allows detached state, i.e. with closed external document. So // we need to preserve empty link for (auto it = _Links.begin(), itNext = it; it != _Links.end(); it = itNext) { ++itNext; if (!it->getValue()) { tmp.splice(tmp.end(), _Links, it); } } } setValues(std::move(values)); _Links.splice(_Links.end(), tmp); } return touched; } int PropertyXLinkSubList::checkRestore(std::string* msg) const { for (auto& l : _Links) { int res; if ((res = l.checkRestore(msg))) { return res; } } return 0; } bool PropertyXLinkSubList::upgrade(Base::XMLReader& reader, const char* typeName) { if (strcmp(typeName, PropertyLinkListGlobal::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkList::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkListChild::getClassTypeId().getName()) == 0) { PropertyLinkList linkProp; linkProp.setContainer(getContainer()); linkProp.Restore(reader); setValues(linkProp.getValues()); return true; } else if (strcmp(typeName, PropertyLinkSubListGlobal::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkSubList::getClassTypeId().getName()) == 0 || strcmp(typeName, PropertyLinkSubListChild::getClassTypeId().getName()) == 0) { PropertyLinkSubList linkProp; linkProp.setContainer(getContainer()); linkProp.Restore(reader); std::map> values; const auto& objs = linkProp.getValues(); const auto& subs = linkProp.getSubValues(); assert(objs.size() == subs.size()); for (size_t i = 0; i < objs.size(); ++i) { values[objs[i]].push_back(subs[i]); } setValues(std::move(values)); return true; } _Links.clear(); _Links.emplace_back(testFlag(LinkAllowPartial), this); if (!_Links.back().upgrade(reader, typeName)) { _Links.clear(); return false; } return true; } void PropertyXLinkSubList::setAllowPartial(bool enable) { setFlag(LinkAllowPartial, enable); for (auto& l : _Links) { l.setAllowPartial(enable); } } void PropertyXLinkSubList::hasSetChildValue(Property&) { if (!signalCounter) { hasSetValue(); } } void PropertyXLinkSubList::aboutToSetChildValue(Property&) { if (!signalCounter || !hasChanged) { aboutToSetValue(); if (signalCounter) { hasChanged = true; } } } std::vector PropertyXLinkSubList::getValues() const { std::vector xLinks; getLinks(xLinks); return (xLinks); } //************************************************************************** // PropertyXLinkList //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE(App::PropertyXLinkList, App::PropertyXLinkSubList) //************************************************************************** // Construction/Destruction PropertyXLinkList::PropertyXLinkList() = default; PropertyXLinkList::~PropertyXLinkList() = default; PyObject* PropertyXLinkList::getPyObject() { for (auto& link : _Links) { auto obj = link.getValue(); if (!obj || !obj->isAttachedToDocument()) { continue; } if (link.hasSubName()) { return PropertyXLinkSubList::getPyObject(); } } Py::List list; for (auto& link : _Links) { auto obj = link.getValue(); if (!obj || !obj->isAttachedToDocument()) { continue; } list.append(Py::asObject(obj->getPyObject())); } return Py::new_reference_to(list); } void PropertyXLinkList::setPyObject(PyObject* value) { try { // try PropertyLinkList syntax PropertyLinkList dummy; dummy.setAllowExternal(true); dummy.setPyObject(value); this->setValues(dummy.getValues()); return; } catch (Base::Exception&) { } PropertyXLinkSubList::setPyObject(value); } //************************************************************************** // PropertyXLinkContainer //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyXLinkContainer, App::PropertyLinkBase) PropertyXLinkContainer::PropertyXLinkContainer() { _pcScope = LinkScope::Global; _LinkRestored = false; } PropertyXLinkContainer::~PropertyXLinkContainer() = default; void PropertyXLinkContainer::afterRestore() { _DocMap.clear(); if (!_XLinkRestores) { return; } _Deps.clear(); for (auto& info : *_XLinkRestores) { auto obj = info.xlink->getValue(); if (!obj) { continue; } if (!info.docName.empty()) { if (info.docName != obj->getDocument()->getName()) { _DocMap[info.docName] = obj->getDocument()->getName(); } if (info.docLabel != obj->getDocument()->Label.getValue()) { _DocMap[App::quote(info.docLabel)] = obj->getDocument()->Label.getValue(); } } if (_Deps.insert(std::make_pair(obj, info.xlink->getScope() == LinkScope::Hidden)).second) { _XLinks[obj->getFullName()] = std::move(info.xlink); onAddDep(obj); } } _XLinkRestores.reset(); } void PropertyXLinkContainer::breakLink(App::DocumentObject* obj, bool clear) { if (!obj || !obj->isAttachedToDocument()) { return; } auto owner = dynamic_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { return; } if (!clear || obj != owner) { auto it = _Deps.find(obj); if (it == _Deps.end()) { return; } aboutToSetValue(); onBreakLink(obj); if (obj->getDocument() != owner->getDocument()) { _XLinks.erase(obj->getFullName()); } else if (!it->second) { obj->_removeBackLink(owner); } _Deps.erase(it); onRemoveDep(obj); hasSetValue(); return; } if (obj != owner) { return; } for (auto& v : _Deps) { auto key = v.first; if (!key || !key->isAttachedToDocument()) { continue; } onBreakLink(key); if (!v.second && key->getDocument() == owner->getDocument()) { key->_removeBackLink(owner); } } _XLinks.clear(); _Deps.clear(); } int PropertyXLinkContainer::checkRestore(std::string* msg) const { if (_LinkRestored) { for (auto& v : _XLinks) { int res = v.second->checkRestore(msg); if (res) { return res; } } } return 0; } void PropertyXLinkContainer::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << " docSet; auto owner = freecad_cast(getContainer()); if (owner && !owner->isExporting()) { // Document name and label can change on restore, we shall record the // current document name and label and pair it with the associated // xlink, so that we can restore them correctly. int i = -1; for (auto& v : _XLinks) { ++i; auto obj = v.second->getValue(); if (obj && obj->getDocument()) { docSet.insert(std::make_pair(obj->getDocument(), i)); } } if (!docSet.empty()) { writer.Stream() << "\" docs=\"" << docSet.size(); } } std::ostringstream ss; int hidden = 0; int i = -1; for (auto& v : _XLinks) { ++i; if (v.second->getScope() == LinkScope::Hidden) { ss << i << ' '; ++hidden; } } if (hidden) { writer.Stream() << "\" hidden=\"" << ss.str(); } writer.Stream() << "\">" << std::endl; writer.incInd(); for (auto& v : docSet) { writer.Stream() << writer.ind() << "getName() << "\" label=\"" << encodeAttribute(v.first->Label.getValue()) << "\" index=\"" << v.second << "\"/>" << std::endl; } for (auto& v : _XLinks) { v.second->Save(writer); } writer.decInd(); writer.Stream() << writer.ind() << "" << std::endl; } void PropertyXLinkContainer::Restore(Base::XMLReader& reader) { reader.readElement("XLinks"); auto count = reader.getAttribute("count"); _XLinkRestores = std::make_unique>(count); if (reader.hasAttribute("hidden")) { std::istringstream iss(reader.getAttribute("hidden")); int index; while (iss >> index) { if (index >= 0 && index < static_cast(count)) { _XLinkRestores->at(index).hidden = true; } } } if (reader.hasAttribute("docs")) { auto docCount = reader.getAttribute("docs"); _DocMap.clear(); for (unsigned i = 0; i < docCount; ++i) { reader.readElement("DocMap"); auto index = reader.getAttribute("index"); if (index >= count) { FC_ERR(propertyName(this) << " invalid document map entry"); continue; } auto& info = _XLinkRestores->at(index); info.docName = reader.getAttribute("name"); info.docLabel = reader.getAttribute("label"); } } for (auto& info : *_XLinkRestores) { info.xlink.reset(createXLink()); if (info.hidden) { info.xlink->setScope(LinkScope::Hidden); } info.xlink->Restore(reader); } reader.readEndElement("XLinks"); } void PropertyXLinkContainer::aboutToSetChildValue(App::Property& prop) { auto xlink = dynamic_cast(&prop); if (xlink && xlink->testFlag(LinkDetached)) { auto obj = const_cast(xlink->getValue()); if (_Deps.erase(obj)) { _onBreakLink(xlink->getValue()); onRemoveDep(obj); } } } void PropertyXLinkContainer::onBreakLink(DocumentObject*) {} void PropertyXLinkContainer::_onBreakLink(DocumentObject* obj) { try { onBreakLink(obj); } catch (Base::Exception& e) { e.reportException(); FC_ERR("Exception on breaking link property " << getFullName()); } catch (std::exception& e) { FC_ERR("Exception on breaking link property " << getFullName() << ": " << e.what()); } catch (...) { FC_ERR("Exception on breaking link property " << getFullName()); } } PropertyXLink* PropertyXLinkContainer::createXLink() { return new PropertyXLink(false, this); } bool PropertyXLinkContainer::isLinkedToDocument(const App::Document& doc) const { auto iter = _XLinks.lower_bound(doc.getName()); if (iter != _XLinks.end()) { size_t len = strlen(doc.getName()); return iter->first.size() > len && iter->first[len] == '#' && boost::starts_with(iter->first, doc.getName()); } return false; } void PropertyXLinkContainer::updateDeps(std::map&& newDeps) { auto owner = freecad_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { return; } newDeps.erase(owner); for (auto& v : newDeps) { auto obj = v.first; if (obj && obj->isAttachedToDocument()) { auto it = _Deps.find(obj); if (it != _Deps.end()) { if (v.second != it->second) { if (v.second) { obj->_removeBackLink(owner); } else { obj->_addBackLink(owner); } } _Deps.erase(it); continue; } if (owner->getDocument() != obj->getDocument()) { auto& xlink = _XLinks[obj->getFullName()]; if (!xlink) { xlink.reset(createXLink()); xlink->setValue(obj); } xlink->setScope(v.second ? LinkScope::Hidden : LinkScope::Global); } else if (!v.second) { obj->_addBackLink(owner); } onAddDep(obj); } } for (auto& v : _Deps) { auto obj = v.first; if (!obj || !obj->isAttachedToDocument()) { continue; } if (obj->getDocument() == owner->getDocument()) { if (!v.second) { obj->_removeBackLink(owner); } } else { _XLinks.erase(obj->getFullName()); } onRemoveDep(obj); } _Deps = std::move(newDeps); _LinkRestored = testFlag(LinkRestoring); if (!_LinkRestored && !testFlag(LinkDetached)) { for (auto it = _XLinks.begin(), itNext = it; it != _XLinks.end(); it = itNext) { ++itNext; if (!it->second->getValue()) { _XLinks.erase(it); } } } } void PropertyXLinkContainer::clearDeps() { auto owner = dynamic_cast(getContainer()); if (!owner || !owner->isAttachedToDocument()) { return; } if (!owner->testStatus(ObjectStatus::Destroy)) { for (auto& v : _Deps) { auto obj = v.first; if (!v.second && obj && obj->isAttachedToDocument() && obj->getDocument() == owner->getDocument()) { obj->_removeBackLink(owner); } } } _Deps.clear(); _XLinks.clear(); _LinkRestored = false; } void PropertyXLinkContainer::getLinks(std::vector& objs, bool all, std::vector* /*subs*/, bool /*newStyle*/) const { for (auto& v : _Deps) { if (all || !v.second) { objs.push_back(v.first); } } }