// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2015 Eivind Kvedalen * * * * 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 #include #include #include #include #include #include #include #include "PropertyExpressionEngine.h" #include "ExpressionVisitors.h" FC_LOG_LEVEL_INIT("App", true); using namespace App; using namespace Base; using namespace boost; namespace sp = std::placeholders; TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyExpressionContainer, App::PropertyXLinkContainer) static std::set _ExprContainers; PropertyExpressionContainer::PropertyExpressionContainer() { static bool inited; if (!inited) { inited = true; GetApplication().signalRelabelDocument.connect( PropertyExpressionContainer::slotRelabelDocument); GetApplication().signalRenameDynamicProperty.connect( PropertyExpressionContainer::slotRenameDynamicProperty); } _ExprContainers.insert(this); } PropertyExpressionContainer::~PropertyExpressionContainer() { _ExprContainers.erase(this); } void PropertyExpressionContainer::slotRelabelDocument(const App::Document& doc) { // For use a private _ExprContainers to track all living // PropertyExpressionContainer including those inside undo/redo stack, // because document relabel is not undoable/redoable. if (doc.getOldLabel() != doc.Label.getValue()) { for (auto prop : _ExprContainers) { prop->onRelabeledDocument(doc); } } } void PropertyExpressionContainer::slotRenameDynamicProperty(const App::Property& prop, const char* oldName) { for (auto container : _ExprContainers) { container->onRenameDynamicProperty(prop, oldName); } } /////////////////////////////////////////////////////////////////////////////////////// struct PropertyExpressionEngine::Private { // For some reason, MSVC has trouble with vector of scoped_connection if // defined in header, hence the private structure here. std::vector conns; std::unordered_map> propMap; }; /////////////////////////////////////////////////////////////////////////////////////// TYPESYSTEM_SOURCE(App::PropertyExpressionEngine, App::PropertyExpressionContainer) PropertyExpressionEngine::PropertyExpressionEngine() : validator(0) {} PropertyExpressionEngine::~PropertyExpressionEngine() = default; // fixme Should probably return something else than 0. unsigned int PropertyExpressionEngine::getMemSize() const { return 0; } Property* PropertyExpressionEngine::Copy() const { PropertyExpressionEngine* engine = new PropertyExpressionEngine(); for (const auto& it : expressions) { ExpressionInfo info; if (it.second.expression) { info.expression = std::shared_ptr(it.second.expression->copy()); } engine->expressions[it.first] = info; } engine->validator = validator; return engine; } void PropertyExpressionEngine::hasSetValue() { App::DocumentObject* owner = dynamic_cast(getContainer()); if (!owner || !owner->isAttachedToDocument() || owner->isRestoring() || testFlag(LinkDetached)) { PropertyExpressionContainer::hasSetValue(); return; } std::map deps; std::vector labels; unregisterElementReference(); UpdateElementReferenceExpressionVisitor v(*this); for (auto& e : expressions) { auto expr = e.second.expression; if (expr) { expr->getDepObjects(deps, &labels); if (!restoring) { expr->visit(v); } } } registerLabelReferences(std::move(labels)); updateDeps(std::move(deps)); if (pimpl) { pimpl->conns.clear(); pimpl->propMap.clear(); } // check if there is any hidden references bool hasHidden = false; for (auto& v : _Deps) { if (v.second) { hasHidden = true; break; } } if (hasHidden) { if (!pimpl) { pimpl = std::make_unique(); } for (auto& e : expressions) { auto expr = e.second.expression; if (!expr) { continue; } for (auto& dep : expr->getIdentifiers()) { if (!dep.second) { continue; } const ObjectIdentifier& var = dep.first; for (auto& vdep : var.getDep(true)) { auto obj = vdep.first; auto objName = obj->getFullName() + "."; for (auto& propName : vdep.second) { std::string key = objName + propName; auto& propDeps = pimpl->propMap[key]; if (propDeps.empty()) { // NOLINTBEGIN if (!propName.empty()) { pimpl->conns.emplace_back(obj->signalChanged.connect( std::bind(&PropertyExpressionEngine::slotChangedProperty, this, sp::_1, sp::_2))); } else { pimpl->conns.emplace_back(obj->signalChanged.connect( std::bind(&PropertyExpressionEngine::slotChangedObject, this, sp::_1, sp::_2))); } // NOLINTEND } propDeps.push_back(e.first); } } } } } PropertyExpressionContainer::hasSetValue(); } void PropertyExpressionEngine::updateHiddenReference(const std::string& key) { if (!pimpl) { return; } auto it = pimpl->propMap.find(key); if (it == pimpl->propMap.end()) { return; } for (auto& var : it->second) { auto it = expressions.find(var); if (it == expressions.end() || it->second.busy) { continue; } Property* myProp = var.getProperty(); if (!myProp) { continue; } Base::StateLocker guard(it->second.busy); App::any value; try { value = it->second.expression->getValueAsAny(); if (!isAnyEqual(value, myProp->getPathValue(var))) { myProp->setPathValue(var, value); } } catch (Base::Exception& e) { e.reportException(); FC_ERR("Failed to evaluate property binding " << myProp->getFullName() << " on change of " << key); } catch (std::bad_cast&) { FC_ERR("Invalid type '" << value.type().name() << "' in property binding " << myProp->getFullName() << " on change of " << key); } catch (std::exception& e) { FC_ERR(e.what()); FC_ERR("Failed to evaluate property binding " << myProp->getFullName() << " on change of " << key); } } } void PropertyExpressionEngine::slotChangedObject(const App::DocumentObject& obj, const App::Property&) { updateHiddenReference(obj.getFullName()); } void PropertyExpressionEngine::slotChangedProperty(const App::DocumentObject&, const App::Property& prop) { updateHiddenReference(prop.getFullName()); } void PropertyExpressionEngine::Paste(const Property& from) { const PropertyExpressionEngine& fromee = dynamic_cast(from); AtomicPropertyChange signaller(*this); expressions.clear(); for (auto& e : fromee.expressions) { ExpressionInfo info; if (e.second.expression) { info.expression = std::shared_ptr(e.second.expression->copy()); } expressions[e.first] = info; expressionChanged(e.first); } validator = fromee.validator; signaller.tryInvoke(); } void PropertyExpressionEngine::Save(Base::Writer& writer) const { writer.Stream() << writer.ind() << "" << std::endl; writer.incInd(); } else { writer.Stream() << R"(" xlink="1">)" << std::endl; writer.incInd(); PropertyExpressionContainer::Save(writer); } for (const auto& it : expressions) { std::string expression, comment; if (it.second.expression) { expression = it.second.expression->toString(true); comment = it.second.expression->comment; } writer.Stream() << writer.ind() << "" << std::endl; } writer.decInd(); writer.Stream() << writer.ind() << "" << std::endl; } void PropertyExpressionEngine::Restore(Base::XMLReader& reader) { reader.readElement("ExpressionEngine"); int count = reader.getAttribute("count"); if (reader.hasAttribute("xlink") && reader.getAttribute("xlink")) { PropertyExpressionContainer::Restore(reader); } restoredExpressions = std::make_unique>(); restoredExpressions->reserve(count); for (int i = 0; i < count; ++i) { reader.readElement("Expression"); restoredExpressions->emplace_back(); auto& info = restoredExpressions->back(); info.path = reader.getAttribute("path"); info.expr = reader.getAttribute("expression"); if (reader.hasAttribute("comment")) { info.comment = reader.getAttribute("comment"); } } reader.readEndElement("ExpressionEngine"); } void PropertyExpressionEngine::buildGraphStructures( const ObjectIdentifier& path, const std::shared_ptr expression, boost::unordered_map& nodes, boost::unordered_map& revNodes, std::vector& edges) const { /* Insert target property into nodes structure */ if (nodes.find(path) == nodes.end()) { int s = nodes.size(); revNodes[s] = path; nodes[path] = s; } else { revNodes[nodes[path]] = path; } /* Insert dependencies into nodes structure */ ExpressionDeps deps; if (expression) { deps = expression->getDeps(); } for (auto& dep : deps) { for (auto& info : dep.second) { if (info.first.empty()) { continue; } for (auto& oid : info.second) { ObjectIdentifier cPath(oid.canonicalPath()); if (nodes.find(cPath) == nodes.end()) { int s = nodes.size(); nodes[cPath] = s; } edges.emplace_back(nodes[path], nodes[cPath]); } } } } ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& oid) const { DocumentObject* docObj = freecad_cast(getContainer()); // Am I owned by a DocumentObject? if (!docObj) { throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject."); } int ptype; Property* prop = oid.getProperty(&ptype); // oid pointing to a property...? if (!prop) { throw Base::RuntimeError(oid.resolveErrorString().c_str()); } if (ptype || prop->getContainer() != getContainer()) { return oid; } // In case someone calls this with p pointing to a PropertyExpressionEngine for some reason if (prop->isDerivedFrom(PropertyExpressionEngine::classTypeId)) { return oid; } // Dispatch call to actual canonicalPath implementation return oid.canonicalPath(); } size_t PropertyExpressionEngine::numExpressions() const { return expressions.size(); } void PropertyExpressionEngine::afterRestore() { DocumentObject* docObj = freecad_cast(getContainer()); if (restoredExpressions && docObj) { Base::FlagToggler flag(restoring); AtomicPropertyChange signaller(*this); PropertyExpressionContainer::afterRestore(); ObjectIdentifier::DocumentMapper mapper(this->_DocMap); for (auto& info : *restoredExpressions) { tryRestoreExpression(docObj, info); } signaller.tryInvoke(); } restoredExpressions.reset(); } void PropertyExpressionEngine::tryRestoreExpression(DocumentObject* docObj, const RestoredExpression& info) { try { ObjectIdentifier path = ObjectIdentifier::parse(docObj, info.path); if (!info.expr.empty()) { std::shared_ptr expression( Expression::parse(docObj, info.expr)); if (expression) { expression->comment = info.comment; } setValue(path, expression); } } catch (const Base::Exception& e) { FC_ERR("Failed to restore " << docObj->getFullName() << '.' << getName() << ": " << e.what()); } } void PropertyExpressionEngine::onContainerRestored() { Base::FlagToggler flag(restoring); unregisterElementReference(); UpdateElementReferenceExpressionVisitor v(*this); for (auto& e : expressions) { auto expr = e.second.expression; if (expr) { expr->visit(v); } } } const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentifier& path) const { // Get a canonical path ObjectIdentifier usePath(canonicalPath(path)); auto i = expressions.find(usePath); if (i != expressions.end()) { return i->second; } return boost::any(); } void PropertyExpressionEngine::setValue(const ObjectIdentifier& path, std::shared_ptr expr) { ObjectIdentifier usePath(canonicalPath(path)); const Property* prop = usePath.getProperty(); // Try to access value; it should trigger an exception if it is not supported, or if the path is // invalid prop->getPathValue(usePath); // Check if the current expression equals the new one and do nothing if so to reduce unneeded // computations auto it = expressions.find(usePath); if (it != expressions.end() && (expr == it->second.expression || (expr && it->second.expression && expr->isSame(*it->second.expression)))) { return; } if (expr) { std::string error = validateExpression(usePath, expr); if (!error.empty()) { throw Base::RuntimeError(error.c_str()); } AtomicPropertyChange signaller(*this); expressions[usePath] = ExpressionInfo(expr); expressionChanged(usePath); signaller.tryInvoke(); } else if (it != expressions.end()) { AtomicPropertyChange signaller(*this); expressions.erase(it); expressionChanged(usePath); signaller.tryInvoke(); } } /* The cycle_detector struct is used by the boost graph routines to detect * cycles in the graph. */ struct cycle_detector: public boost::dfs_visitor<> { cycle_detector(bool& has_cycle, int& src) : _has_cycle(has_cycle) , _src(src) {} template void back_edge(Edge e, Graph& g) { _has_cycle = true; _src = source(e, g); } protected: bool& _has_cycle; int& _src; }; void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs, boost::unordered_map& revNodes, DiGraph& g, ExecuteOption option) const { boost::unordered_map nodes; std::vector edges; // Build data structure for graph for (const auto& expr : exprs) { if (option != ExecuteAll) { auto prop = expr.first.getProperty(); if (!prop) { throw Base::RuntimeError("Path does not resolve to a property."); } bool is_output = prop->testStatus(App::Property::Output) || (prop->getType() & App::Prop_Output); if ((is_output && option == ExecuteNonOutput) || (!is_output && option == ExecuteOutput)) { continue; } if (option == ExecuteOnRestore && !prop->testStatus(Property::Transient) && !(prop->getType() & Prop_Transient) && !prop->testStatus(Property::EvalOnRestore)) { continue; } } buildGraphStructures(expr.first, expr.second.expression, nodes, revNodes, edges); } // Create graph g = DiGraph(nodes.size()); // Add edges to graph for (const auto& edge : edges) { add_edge(edge.first, edge.second, g); } // Check for cycles bool has_cycle = false; int src = -1; cycle_detector vis(has_cycle, src); depth_first_search(g, visitor(vis)); if (has_cycle) { std::string s = revNodes[src].toString() + " reference creates a cyclic dependency."; throw Base::RuntimeError(s.c_str()); } } std::vector PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option) { std::vector evaluationOrder; boost::unordered_map revNodes; DiGraph g; buildGraph(expressions, revNodes, g, option); /* Compute evaluation order for expressions */ std::vector c; topological_sort(g, std::back_inserter(c)); for (int i : c) { // we return the evaluation order for our properties, not the dependencies // the topo sort will contain node ids for both our props and their deps if (revNodes.find(i) != revNodes.end()) { evaluationOrder.push_back(revNodes[i]); } } return evaluationOrder; } DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption option, bool* touched) { DocumentObject* docObj = freecad_cast(getContainer()); if (!docObj) { throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject."); } if (running) { return DocumentObject::StdReturn; } if (option == ExecuteOnRestore) { bool found = false; for (auto& e : expressions) { auto prop = e.first.getProperty(); if (!prop) { continue; } if (prop->testStatus(App::Property::Transient) || (prop->getType() & App::Prop_Transient) || prop->testStatus(App::Property::EvalOnRestore)) { found = true; break; } } if (!found) { return DocumentObject::StdReturn; } } /* Resetter class, to ensure that the "running" variable gets set to false, even if * an exception is thrown. */ class resetter { public: explicit resetter(bool& b) : _b(b) { _b = true; } ~resetter() { _b = false; } private: bool& _b; }; resetter r(running); // Compute evaluation order std::vector evaluationOrder = computeEvaluationOrder(option); std::vector::const_iterator it = evaluationOrder.begin(); #ifdef FC_PROPERTYEXPRESSIONENGINE_LOG std::clog << "Computing expressions for " << getName() << std::endl; #endif /* Evaluate the expressions, and update properties */ for (; it != evaluationOrder.end(); ++it) { // Get property to update Property* prop = it->getProperty(); if (!prop) { throw Base::RuntimeError("Path does not resolve to a property."); } DocumentObject* parent = freecad_cast(prop->getContainer()); /* Make sure property belongs to the same container as this PropertyExpressionEngine */ if (parent != docObj) { throw Base::RuntimeError("Invalid property owner."); } /* Set value of property */ App::any value; try { // Evaluate expression std::shared_ptr expression = expressions[*it].expression; if (expression) { value = expression->getValueAsAny(); // Enable value comparison for all expression bindings to reduce // unnecessary touch and recompute. // // This optimization is necessary for some hidden reference to // work because it introduce dependency loop. The repeativtive // recompute can be stopped if the expression evaluates the same // value. // // In the future, we can generalize the optimization to all // property modification, i.e. do not touch unless value change // // if (option == ExecuteOnRestore && prop->testStatus(Property::EvalOnRestore)) { if (isAnyEqual(value, prop->getPathValue(*it))) { continue; } if (touched) { *touched = true; } } prop->setPathValue(*it, value); } } catch (Base::Exception& e) { std::ostringstream ss; ss << e.what() << std::endl << "in property binding '" << prop->getFullName() << "'"; e.setMessage(ss.str()); throw; } catch (std::bad_cast&) { std::ostringstream ss; ss << "Invalid type '" << value.type().name() << "'"; ss << "\nin property binding '" << prop->getFullName() << "'"; throw Base::TypeError(ss.str().c_str()); } catch (std::exception& e) { std::ostringstream ss; ss << e.what() << "\nin property binding '" << prop->getFullName() << "'"; throw Base::RuntimeError(ss.str().c_str()); } } return DocumentObject::StdReturn; } void PropertyExpressionEngine::getPathsToDocumentObject( DocumentObject* obj, std::vector& paths) const { DocumentObject* owner = freecad_cast(getContainer()); if (!owner || owner == obj) { return; } for (auto& v : expressions) { if (!v.second.expression) { continue; } const auto& deps = v.second.expression->getDeps(); auto it = deps.find(obj); if (it == deps.end()) { continue; } for (auto& dep : it->second) { paths.insert(paths.end(), dep.second.begin(), dep.second.end()); } } } bool PropertyExpressionEngine::depsAreTouched() const { for (auto& v : _Deps) { // v.second indicates if it is a hidden reference if (!v.second && v.first->isTouched()) { return true; } } return false; } std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path, std::shared_ptr expr) const { std::string error; ObjectIdentifier usePath(canonicalPath(path)); if (validator) { error = validator(usePath, expr); if (!error.empty()) { return error; } } // Get document object DocumentObject* pathDocObj = usePath.getDocumentObject(); assert(pathDocObj); auto inList = pathDocObj->getInListEx(true); for (auto& v : expr->getDepObjects()) { auto docObj = v.first; if (!v.second && inList.contains(docObj)) { std::stringstream ss; ss << "cyclic reference to " << docObj->getFullName(); return ss.str(); } } // Check for internal document object dependencies // Copy current expressions ExpressionMap newExpressions = expressions; // Add expression in question std::shared_ptr exprClone(expr->copy()); newExpressions[usePath].expression = exprClone; // Build graph; an exception will be thrown if it is not a DAG try { boost::unordered_map revNodes; DiGraph g; buildGraph(newExpressions, revNodes, g); } catch (const Base::Exception& e) { return e.what(); } return {}; } void PropertyExpressionEngine::renameExpressions( const std::map& paths) { ExpressionMap newExpressions; std::map canonicalPaths; /* ensure input map uses canonical paths */ for (const auto& path : paths) { canonicalPaths[canonicalPath(path.first)] = path.second; } for (auto i = expressions.begin(); i != expressions.end(); ++i) { auto j = canonicalPaths.find(i->first); // Renamed now? if (j != canonicalPaths.end()) { newExpressions[j->second] = i->second; } else { newExpressions[i->first] = i->second; } } aboutToSetValue(); expressions = newExpressions; for (auto i = expressions.begin(); i != expressions.end(); ++i) { expressionChanged(i->first); } hasSetValue(); } void PropertyExpressionEngine::renameObjectIdentifiers( const std::map& paths) { for (const auto& it : expressions) { RenameObjectIdentifierExpressionVisitor v(*this, paths, it.first); it.second.expression->visit(v); } } PyObject* PropertyExpressionEngine::getPyObject() { Py::List list; for (const auto& it : expressions) { Py::Tuple tuple(2); tuple.setItem(0, Py::String(it.first.toString())); auto expr = it.second.expression; tuple.setItem(1, expr ? Py::String(expr->toString()) : Py::None()); list.append(tuple); } return Py::new_reference_to(list); } void PropertyExpressionEngine::setPyObject(PyObject*) { throw Base::RuntimeError("Property is read-only"); } /* The policy implemented in the following function is to auto erase binding in * case linked object is gone. I think it is better to cause error and get * user's attention * void PropertyExpressionEngine::breakLink(App::DocumentObject *obj, bool clear) { auto owner = dynamic_cast(getContainer()); if(!owner) return; if(_Deps.count(obj)==0 && (!clear || obj!=owner || _Deps.empty())) return; AtomicPropertyChange signaler(*this); for(auto it=expressions.begin(),itNext=it;it!=expressions.end();it=itNext) { ++itNext; const auto &deps = it->second.expression->getDepObjects(); if(clear) { // here means we are breaking all expression, except those that has // no depdenecy or self dependency if(deps.empty() || (deps.size()==1 && *deps.begin()==owner)) continue; }else if(!deps.count(obj)) continue; auto path = it->first; expressions.erase(it); expressionChanged(path); } } */ bool PropertyExpressionEngine::adjustLink(const std::set& inList) { auto owner = dynamic_cast(getContainer()); if (!owner) { return false; } bool found = false; for (auto& v : _Deps) { if (inList.contains(v.first)) { found = true; break; } } if (!found) { return false; } AtomicPropertyChange signaler(*this); for (auto& v : expressions) { try { if (v.second.expression && v.second.expression->adjustLinks(inList)) { expressionChanged(v.first); } } catch (Base::Exception& e) { std::ostringstream ss; ss << "Failed to adjust link for " << owner->getFullName() << " in expression " << v.second.expression->toString() << ": " << e.what(); throw Base::RuntimeError(ss.str()); } } return true; } void PropertyExpressionEngine::updateElementReference(DocumentObject* feature, bool reverse, bool notify) { (void)notify; if (!feature) { unregisterElementReference(); } UpdateElementReferenceExpressionVisitor v(*this, feature, reverse); for (auto& e : expressions) { if (e.second.expression) { e.second.expression->visit(v); if (v.changed()) { expressionChanged(e.first); v.reset(); } } } if (feature && v.changed()) { auto owner = dynamic_cast(getContainer()); if (owner) { owner->onUpdateElementReference(this); } } } bool PropertyExpressionEngine::referenceChanged() const { return false; } Property* PropertyExpressionEngine::CopyOnImportExternal( const std::map& nameMap) const { std::unique_ptr engine; for (auto it = expressions.begin(); it != expressions.end(); ++it) { #ifdef BOOST_NO_CXX11_SMART_PTR std::shared_ptr expr(it->second.expression->importSubNames(nameMap).release()); #else std::shared_ptr expr(it->second.expression->importSubNames(nameMap)); #endif if (!expr && !engine) { continue; } if (!engine) { engine = std::make_unique(); for (auto it2 = expressions.begin(); it2 != it; ++it2) { engine->expressions[it2->first] = ExpressionInfo(std::shared_ptr(it2->second.expression->copy())); } } else if (!expr) { expr = it->second.expression; } engine->expressions[it->first] = ExpressionInfo(expr); } if (!engine) { return nullptr; } engine->validator = validator; return engine.release(); } Property* PropertyExpressionEngine::CopyOnLabelChange(App::DocumentObject* obj, const std::string& ref, const char* newLabel) const { std::unique_ptr engine; for (auto it = expressions.begin(); it != expressions.end(); ++it) { #ifdef BOOST_NO_CXX11_SMART_PTR std::shared_ptr expr( it->second.expression->updateLabelReference(obj, ref, newLabel).release()); #else std::shared_ptr expr( it->second.expression->updateLabelReference(obj, ref, newLabel)); #endif if (!expr && !engine) { continue; } if (!engine) { engine = std::make_unique(); for (auto it2 = expressions.begin(); it2 != it; ++it2) { ExpressionInfo info; if (it2->second.expression) { info.expression = std::shared_ptr(it2->second.expression->copy()); } engine->expressions[it2->first] = info; } } else if (!expr) { expr = it->second.expression; } engine->expressions[it->first] = ExpressionInfo(expr); } if (!engine) { return nullptr; } engine->validator = validator; return engine.release(); } Property* PropertyExpressionEngine::CopyOnLinkReplace(const App::DocumentObject* parent, App::DocumentObject* oldObj, App::DocumentObject* newObj) const { std::unique_ptr engine; for (auto it = expressions.begin(); it != expressions.end(); ++it) { #ifdef BOOST_NO_CXX11_SMART_PTR std::shared_ptr expr( it->second.expression->replaceObject(parent, oldObj, newObj).release()); #else std::shared_ptr expr( it->second.expression->replaceObject(parent, oldObj, newObj)); #endif if (!expr && !engine) { continue; } if (!engine) { engine = std::make_unique(); for (auto it2 = expressions.begin(); it2 != it; ++it2) { ExpressionInfo info; if (it2->second.expression) { info.expression = std::shared_ptr(it2->second.expression->copy()); } engine->expressions[it2->first] = info; } } else if (!expr) { expr = it->second.expression; } engine->expressions[it->first] = ExpressionInfo(expr); } if (!engine) { return nullptr; } engine->validator = validator; return engine.release(); } std::map PropertyExpressionEngine::getExpressions() const { std::map res; for (auto& v : expressions) { res[v.first] = v.second.expression.get(); } return res; } void PropertyExpressionEngine::setExpressions( std::map&& exprs) { AtomicPropertyChange signaller(*this); #ifdef BOOST_NO_CXX11_SMART_PTR for (auto& v : exprs) { setValue(v.first, std::shared_ptr(v.second.release())); } #else for (auto& v : exprs) { setValue(v.first, std::move(v.second)); } #endif } void PropertyExpressionEngine::onRelabeledDocument(const App::Document& doc) { RelabelDocumentExpressionVisitor v(doc); for (auto& e : expressions) { if (e.second.expression) { e.second.expression->visit(v); } } } void PropertyExpressionEngine::onRenameDynamicProperty(const App::Property& prop, const char* oldName) { ObjectIdentifier oldNameId = ObjectIdentifier(prop.getContainer(), std::string(oldName)); ObjectIdentifier newNameId = ObjectIdentifier(prop); const std::map paths = { {oldNameId, newNameId}, }; renameObjectIdentifiers(paths); } void PropertyExpressionEngine::getLinksTo(std::vector& identifiers, App::DocumentObject* obj, const char* subname, bool all) const { Expression::DepOption option = all ? Expression::DepOption::DepAll : Expression::DepOption::DepNormal; App::SubObjectT objT(obj, subname); auto sobj = objT.getSubObject(); auto subElement = objT.getOldElementName(); for (auto& [expressionId, expressionInfo] : expressions) { const auto& deps = expressionInfo.expression->getDeps(option); auto it = deps.find(obj); if (it == deps.end()) { continue; } auto [docObj, map] = *it; for (auto& [key, paths] : map) { if (!subname) { identifiers.push_back(expressionId); break; } if (std::ranges::any_of(paths, [subname, obj, sobj, &subElement](const auto& path) { if (path.getSubObjectName() == subname) { return true; } App::SubObjectT sobjT(obj, path.getSubObjectName().c_str()); return (sobjT.getSubObject() == sobj && sobjT.getOldElementName() == subElement); })) { identifiers.push_back(expressionId); } } } }