diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 0c6fceb43f..3cd42a548b 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -444,13 +444,14 @@ void Document::exportGraphviz(std::ostream& out) const // Create subgraphs for all documentobjects that it depends on; it will depend on some property there auto i = expressions.begin(); while (i != expressions.end()) { - std::set deps; + std::map deps; i->second->getIdentifiers(deps); - std::set::const_iterator j = deps.begin(); - while (j != deps.end()) { - DocumentObject * o = j->getDocumentObject(); + for(auto j=deps.begin(); j!=deps.end(); ++j) { + if(j->second) + continue; + DocumentObject * o = j->first.getDocumentObject(); // Doesn't exist already? if (o && !GraphList[o]) { @@ -469,7 +470,6 @@ void Document::exportGraphviz(std::ostream& out) const } } - ++j; } ++i; } @@ -554,24 +554,23 @@ void Document::exportGraphviz(std::ostream& out) const while (i != expressions.end()) { // Get dependencies - std::set deps; + std::map deps; i->second->getIdentifiers(deps); // Create subgraphs for all documentobjects that it depends on; it will depend on some property there - std::set::const_iterator j = deps.begin(); - while (j != deps.end()) { - DocumentObject * depObjDoc = j->getDocumentObject(); - std::map::const_iterator k = GlobalVertexList.find(getId(*j)); + for(auto j=deps.begin(); j!=deps.end(); ++j) { + if(j->second) + continue; + DocumentObject * depObjDoc = j->first.getDocumentObject(); + std::map::const_iterator k = GlobalVertexList.find(getId(j->first)); if (k == GlobalVertexList.end()) { Graph * depSgraph = GraphList[depObjDoc] ? GraphList[depObjDoc] : &DepList; - LocalVertexList[getId(*j)] = add_vertex(*depSgraph); - GlobalVertexList[getId(*j)] = vertex_no++; - setPropertyVertexAttributes(*depSgraph, LocalVertexList[getId(*j)], j->getPropertyName() + j->getSubPathStr()); + LocalVertexList[getId(j->first)] = add_vertex(*depSgraph); + GlobalVertexList[getId(j->first)] = vertex_no++; + setPropertyVertexAttributes(*depSgraph, LocalVertexList[getId(j->first)], j->first.getPropertyName() + j->first.getSubPathStr()); } - - ++j; } ++i; } @@ -703,17 +702,18 @@ void Document::exportGraphviz(std::ostream& out) const auto i = expressions.begin(); while (i != expressions.end()) { - std::set deps; + std::map deps; i->second->getIdentifiers(deps); // Create subgraphs for all documentobjects that it depends on; it will depend on some property there - std::set::const_iterator k = deps.begin(); - while (k != deps.end()) { - DocumentObject * depObjDoc = k->getDocumentObject(); + for(auto k=deps.begin(); k!=deps.end(); ++k) { + if(k->second) + continue; + DocumentObject * depObjDoc = k->first.getDocumentObject(); Edge edge; bool inserted; - tie(edge, inserted) = add_edge(GlobalVertexList[getId(i->first)], GlobalVertexList[getId(*k)], DepList); + tie(edge, inserted) = add_edge(GlobalVertexList[getId(i->first)], GlobalVertexList[getId(k->first)], DepList); // Add this edge to the set of all expression generated edges existingEdges.insert(std::make_pair(docObj, depObjDoc)); @@ -721,7 +721,6 @@ void Document::exportGraphviz(std::ostream& out) const // Edges between properties should be a bit smaller, and dashed edgeAttrMap[edge]["arrowsize"] = "0.5"; edgeAttrMap[edge]["style"] = "dashed"; - ++k; } ++i; } diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index d7d69884fc..eb324cb0a5 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -240,17 +240,7 @@ std::string unquote(const std::string & input) // // ExpressionVistor // -void ExpressionVisitor::getDeps(Expression &e, ExpressionDeps &deps) { - e._getDeps(deps); -} - -void ExpressionVisitor::getDepObjects(Expression &e, - std::set &deps, std::vector *labels) -{ - e._getDepObjects(deps,labels); -} - -void ExpressionVisitor::getIdentifiers(Expression &e, std::set &ids) { +void ExpressionVisitor::getIdentifiers(Expression &e, std::map &ids) { e._getIdentifiers(ids); } @@ -905,58 +895,52 @@ Expression * Expression::parse(const DocumentObject *owner, const std::string &b return ExpressionParser::parse(owner, buffer.c_str()); } -class GetDepsExpressionVisitor : public ExpressionVisitor { -public: - GetDepsExpressionVisitor(ExpressionDeps &deps) - :deps(deps) - {} - - virtual void visit(Expression &e) { - this->getDeps(e,deps); +void Expression::getDeps(ExpressionDeps &deps, int option) const { + for(auto &v : getIdentifiers()) { + bool hidden = v.second; + const ObjectIdentifier &var = v.first; + if((hidden && option==DepNormal) + || (!hidden && option==DepHidden)) + continue; + for(auto &dep : var.getDep(true)) { + DocumentObject *obj = dep.first; + for(auto &propName : dep.second) { + deps[obj][propName].push_back(var); + } + } } - - ExpressionDeps &deps; -}; - -void Expression::getDeps(ExpressionDeps &deps) const { - GetDepsExpressionVisitor v(deps); - const_cast(this)->visit(v); } -ExpressionDeps Expression::getDeps() const { +ExpressionDeps Expression::getDeps(int option) const { ExpressionDeps deps; - getDeps(deps); + getDeps(deps, option); return deps; } -class GetDepObjsExpressionVisitor : public ExpressionVisitor { -public: - GetDepObjsExpressionVisitor(std::set &deps, std::vector *labels) - :deps(deps),labels(labels) - {} - - virtual void visit(Expression &e) { - this->getDepObjects(e,deps,labels); +void Expression::getDepObjects( + std::map &deps, std::vector *labels) const +{ + for(auto &v : getIdentifiers()) { + bool hidden = v.second; + const ObjectIdentifier &var = v.first; + for(auto &dep : var.getDep(false,labels)) { + DocumentObject *obj = dep.first; + auto res = deps.insert(std::make_pair(obj,hidden)); + if(!hidden || res.second) + res.first->second = hidden; + } } - - std::set &deps; - std::vector *labels; -}; - -void Expression::getDepObjects(std::set &deps, std::vector *labels) const { - GetDepObjsExpressionVisitor v(deps,labels); - const_cast(this)->visit(v); } -std::set Expression::getDepObjects(std::vector *labels) const { - std::set deps; +std::map Expression::getDepObjects(std::vector *labels) const { + std::map deps; getDepObjects(deps,labels); return deps; } class GetIdentifiersExpressionVisitor : public ExpressionVisitor { public: - GetIdentifiersExpressionVisitor(std::set &deps) + GetIdentifiersExpressionVisitor(std::map &deps) :deps(deps) {} @@ -964,16 +948,16 @@ public: this->getIdentifiers(e,deps); } - std::set &deps; + std::map &deps; }; -void Expression::getIdentifiers(std::set &deps) const { +void Expression::getIdentifiers(std::map &deps) const { GetIdentifiersExpressionVisitor v(deps); const_cast(this)->visit(v); } -std::set Expression::getIdentifiers() const { - std::set deps; +std::map Expression::getIdentifiers() const { + std::map deps; getIdentifiers(deps); return deps; } @@ -1016,7 +1000,7 @@ ExpressionPtr Expression::importSubNames(const std::map if(!owner || !owner->getDocument()) return 0; ObjectIdentifier::SubNameMap subNameMap; - for(auto &dep : getDeps()) { + for(auto &dep : getDeps(DepAll)) { for(auto &info : dep.second) { for(auto &path : info.second) { auto obj = path.getDocumentObject(); @@ -1064,7 +1048,8 @@ ExpressionPtr Expression::updateLabelReference( if(ref.size()<=2) return ExpressionPtr(); std::vector labels; - getDepObjects(&labels); + for(auto &v : getIdentifiers()) + v.first.getDepLabels(labels); for(auto &label : labels) { // ref contains something like $label. and we need to strip '$' and '.' if(ref.compare(1,ref.size()-2,label)==0) { @@ -1709,6 +1694,32 @@ bool OperatorExpression::isRightAssociative() const TYPESYSTEM_SOURCE(App::FunctionExpression, App::UnitExpression) +static int _HiddenReference; + +struct HiddenReference { + HiddenReference(bool cond) + :cond(cond) + { + if(cond) + ++_HiddenReference; + } + ~HiddenReference() { + if(cond) + --_HiddenReference; + } + + static bool check(int option) { + return (option==Expression::DepNormal && _HiddenReference) + || (option==Expression::DepHidden && !_HiddenReference); + } + + static bool isHidden() { + return _HiddenReference!=0; + } + + bool cond; +}; + FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f, std::string &&name, std::vector _args) : UnitExpression(_owner) , f(_f) @@ -1736,6 +1747,7 @@ FunctionExpression::FunctionExpression(const DocumentObject *_owner, Function _f case FLOOR: case MINVERT: case STR: + case HIDDENREF: if (args.size() != 1) EXPR_THROW("Invalid number of arguments: exactly one required."); break; @@ -2087,6 +2099,8 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std return res; } else if (f == STR) { return Py::String(args[0]->getPyValue().as_string()); + } else if (f == HIDDENREF) { + return args[0]->getPyValue(); } Py::Object e1 = args[0]->getPyValue(); @@ -2219,7 +2233,7 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std unit = v1.getUnit(); break; default: - _EXPR_THROW("Unknown function: " << f,expr); + _EXPR_THROW("Unknown function: " << f,0); } /* Compute result */ @@ -2299,7 +2313,7 @@ Py::Object FunctionExpression::evaluate(const Expression *expr, int f, const std output = floor(value); break; default: - _EXPR_THROW("Unknown function: " << f,expr); + _EXPR_THROW("Unknown function: " << f,0); } return Py::asObject(new QuantityPy(new Quantity(scaler * output, unit))); @@ -2421,6 +2435,8 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const ss << "create("; break;; case STR: ss << "str("; break;; + case HIDDENREF: + ss << "hiddenref("; break;; default: ss << fname << "("; break;; } @@ -2454,6 +2470,7 @@ void FunctionExpression::_visit(ExpressionVisitor &v) { std::vector::const_iterator i = args.begin(); + HiddenReference ref(f == HIDDENREF); while (i != args.end()) { (*i)->visit(v); ++i; @@ -2592,24 +2609,12 @@ Expression *VariableExpression::_copy() const return new VariableExpression(owner, var); } -void VariableExpression::_getDeps(ExpressionDeps &deps) const +void VariableExpression::_getIdentifiers(std::map &deps) const { - auto dep = var.getDep(); - if(dep.first) - deps[dep.first][dep.second].push_back(var); -} - -void VariableExpression::_getDepObjects( - std::set &deps, std::vector *labels) const -{ - auto dep = var.getDep(labels); - if(dep.first) - deps.insert(dep.first); -} - -void VariableExpression::_getIdentifiers(std::set &deps) const -{ - deps.insert(var); + bool hidden = HiddenReference::isHidden(); + auto res = deps.insert(std::make_pair(var,hidden)); + if(!hidden || res.second) + res.first->second = hidden; } bool VariableExpression::_relabeledDocument(const std::string &oldName, @@ -2977,16 +2982,19 @@ Expression *RangeExpression::simplify() const return copy(); } -void RangeExpression::_getDeps(ExpressionDeps &deps) const +void RangeExpression::_getIdentifiers(std::map &deps) const { + bool hidden = HiddenReference::isHidden(); + assert(owner); Range i(getRange()); - auto &dep = deps[owner]; do { - std::string address = i.address(); - dep[address].push_back(ObjectIdentifier(owner,address)); + ObjectIdentifier var(owner,i.address()); + auto res = deps.insert(std::make_pair(var,hidden)); + if(!hidden || res.second) + res.first->second = hidden; } while (i.next()); } @@ -3243,6 +3251,7 @@ static void initParser(const App::DocumentObject *owner) registered_functions["minvert"] = FunctionExpression::MINVERT; registered_functions["create"] = FunctionExpression::CREATE; registered_functions["str"] = FunctionExpression::STR; + registered_functions["hiddenref"] = FunctionExpression::HIDDENREF; // Aggregates registered_functions["sum"] = FunctionExpression::SUM; diff --git a/src/App/Expression.h b/src/App/Expression.h index d0071500b0..8d394db807 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -51,7 +51,9 @@ typedef std::unique_ptr ExpressionPtr; AppExport bool isAnyEqual(const App::any &v1, const App::any &v2); AppExport Base::Quantity anyToQuantity(const App::any &value, const char *errmsg = 0); +// Map of depending objects to a map of depending property name to the full referencing object identifier typedef std::map > > ExpressionDeps; + class AppExport ExpressionVisitor { public: virtual ~ExpressionVisitor() {} @@ -62,9 +64,7 @@ public: virtual App::PropertyLinkBase* getPropertyLink() {return 0;} protected: - void getIdentifiers(Expression &e, std::set &); - void getDeps(Expression &e, ExpressionDeps &); - void getDepObjects(Expression &e, std::set &, std::vector *); + void getIdentifiers(Expression &e, std::map &); bool adjustLinks(Expression &e, const std::set &inList); bool relabeledDocument(Expression &e, const std::string &oldName, const std::string &newName); bool renameObjectIdentifier(Expression &e, @@ -135,14 +135,19 @@ public: virtual int priority() const; - void getIdentifiers(std::set &) const; - std::set getIdentifiers() const; + void getIdentifiers(std::map &) const; + std::map getIdentifiers() const; - void getDeps(ExpressionDeps &deps) const; - ExpressionDeps getDeps() const; + enum DepOption { + DepNormal, + DepHidden, + DepAll, + }; + void getDeps(ExpressionDeps &deps, int option=DepNormal) const; + ExpressionDeps getDeps(int option=DepNormal) const; - std::set getDepObjects(std::vector *labels=0) const; - void getDepObjects(std::set &, std::vector *labels=0) const; + std::map getDepObjects(std::vector *labels=0) const; + void getDepObjects(std::map &, std::vector *labels=0) const; ExpressionPtr importSubNames(const std::map &nameMap) const; @@ -188,9 +193,7 @@ protected: virtual bool _isIndexable() const {return false;} virtual Expression *_copy() const = 0; virtual void _toString(std::ostream &ss, bool persistent, int indent=0) const = 0; - virtual void _getDeps(ExpressionDeps &) const {} - virtual void _getDepObjects(std::set &, std::vector *) const {} - virtual void _getIdentifiers(std::set &) const {} + virtual void _getIdentifiers(std::map &) const {} virtual bool _adjustLinks(const std::set &, ExpressionVisitor &) {return false;} virtual bool _updateElementReference(App::DocumentObject *,bool,ExpressionVisitor &) {return false;} virtual bool _relabeledDocument(const std::string &, const std::string &, ExpressionVisitor &) {return false;} diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index e0dfe1ee0d..c354216299 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -273,6 +273,7 @@ public: MINVERT, // invert matrix/placement/rotation CREATE, // create new object of a given type STR, // stringify + HIDDENREF, // hidden reference that has no dependency check // Aggregates AGGREGATES, @@ -345,9 +346,7 @@ protected: virtual Py::Object _getPyValue() const override; virtual void _toString(std::ostream &ss, bool persistent, int indent) const override; virtual bool _isIndexable() const override; - virtual void _getDeps(ExpressionDeps &) const override; - virtual void _getDepObjects(std::set &, std::vector *) const override; - virtual void _getIdentifiers(std::set &) const override; + virtual void _getIdentifiers(std::map &) const override; virtual bool _adjustLinks(const std::set &, ExpressionVisitor &) override; virtual void _importSubNames(const ObjectIdentifier::SubNameMap &) override; virtual void _updateLabelReference(App::DocumentObject *, const std::string &, const char *) override; @@ -435,7 +434,7 @@ protected: virtual Expression * _copy() const override; virtual void _toString(std::ostream &ss, bool persistent, int indent) const override; virtual Py::Object _getPyValue() const override; - virtual void _getDeps(ExpressionDeps &) const override; + virtual void _getIdentifiers(std::map &) const override; virtual bool _renameObjectIdentifier(const std::map &, const ObjectIdentifier &, ExpressionVisitor &) override; virtual void _moveCells(const CellAddress &, int, int, ExpressionVisitor &) override; diff --git a/src/App/ObjectIdentifier.cpp b/src/App/ObjectIdentifier.cpp index 0aebc7f48d..bf0afb7867 100644 --- a/src/App/ObjectIdentifier.cpp +++ b/src/App/ObjectIdentifier.cpp @@ -33,18 +33,19 @@ /// Here the FreeCAD includes sorted by Base,App,Gui...... #include -#include +#include +#include +#include +#include +#include +#include "ComplexGeoData.h" #include "Property.h" #include "Application.h" #include "Document.h" #include "DocumentObject.h" #include "ObjectIdentifier.h" #include "ExpressionParser.h" -#include -#include -#include -#include -#include +#include "Link.h" FC_LOG_LEVEL_INIT("Expression",true,true) @@ -1086,32 +1087,58 @@ enum PseudoPropertyType { PseudoCadquery, }; -std::pair ObjectIdentifier::getDep(std::vector *labels) const { +void ObjectIdentifier::getDepLabels(std::vector &labels) const { + getDepLabels(ResolveResults(*this),labels); +} + +void ObjectIdentifier::getDepLabels( + const ResolveResults &result, std::vector &labels) const +{ + if(documentObjectName.getString().size()) { + if(documentObjectName.isRealString()) + labels.push_back(documentObjectName.getString()); + } else if(result.propertyIndex == 1) + labels.push_back(components[0].name.getString()); + if(subObjectName.getString().size()) + PropertyLinkBase::getLabelReferences(labels,subObjectName.getString().c_str()); +} + +ObjectIdentifier::Dependencies +ObjectIdentifier::getDep(bool needProps, std::vector *labels) const +{ + Dependencies deps; + getDep(deps,needProps,labels); + return deps; +} + +void ObjectIdentifier::getDep( + Dependencies &deps, bool needProps, std::vector *labels) const +{ ResolveResults result(*this); - if(labels) { - if(documentObjectName.getString().size()) { - if(documentObjectName.isRealString()) - labels->push_back(documentObjectName.getString()); - } else if(result.propertyIndex == 1) - labels->push_back(components[0].name.getString()); - if(subObjectName.getString().size()) - PropertyLinkBase::getLabelReferences(*labels,subObjectName.getString().c_str()); + if(labels) + getDepLabels(result,*labels); + + if(!result.resolvedDocumentObject) + return; + + if(!needProps) { + deps[result.resolvedDocumentObject]; + return; } - if(subObjectName.getString().empty()) { - if(result.propertyType==PseudoNone) { - CellAddress addr; - if(addr.parseAbsoluteAddress(result.propertyName.c_str())) - return std::make_pair(result.resolvedDocumentObject,addr.toString(true)); - return std::make_pair(result.resolvedDocumentObject,result.propertyName); - }else if(result.propertyType == PseudoSelf - && result.resolvedDocumentObject - && result.propertyIndex+1 < (int)components.size()) - { - return std::make_pair(result.resolvedDocumentObject, - components[result.propertyIndex+1].getName()); - } + + if(!result.resolvedProperty) { + if(result.propertyName.size()) + deps[result.resolvedDocumentObject].insert(result.propertyName); + return; + } + + Base::PyGILStateLocker lock; + try { + access(result,0,&deps); + }catch(Py::Exception &) { + Base::PyException e; + }catch(...){ } - return std::make_pair(result.resolvedDocumentObject,std::string()); } /** @@ -1241,7 +1268,7 @@ Property *ObjectIdentifier::resolveProperty(const App::DocumentObject *obj, if(!obj) return 0; - static std::map _props = { + static std::unordered_map _props = { {"_shape",PseudoShape}, {"_pla",PseudoPlacement}, {"_matrix",PseudoMatrix}, @@ -1270,23 +1297,8 @@ Property *ObjectIdentifier::resolveProperty(const App::DocumentObject *obj, } return &const_cast(obj)->Label; //fake the property } - - auto prop = obj->getPropertyByName(propertyName); - if(prop && !prop->testStatus(Property::Hidden) && !(prop->getType() & PropertyType::Prop_Hidden)) - return prop; - - auto linked = obj->getLinkedObject(true); - if(!linked || linked==obj) { - auto ext = obj->getExtensionByType(true); - if(!ext) - return prop; - linked = ext->getTrueLinkedObject(true); - if(!linked || linked==obj) - return prop; - } - - auto linkedProp = linked->getPropertyByName(propertyName); - return linkedProp?linkedProp:prop; + + return obj->getPropertyByName(propertyName); } @@ -1503,7 +1515,8 @@ void ObjectIdentifier::String::checkImport(const App::DocumentObject *owner, } } -Py::Object ObjectIdentifier::access(const ResolveResults &result, Py::Object *value) const +Py::Object ObjectIdentifier::access(const ResolveResults &result, + Py::Object *value, Dependencies *deps) const { if(!result.resolvedDocumentObject || !result.resolvedProperty || (subObjectName.getString().size() && !result.resolvedSubObject)) @@ -1647,13 +1660,65 @@ Py::Object ObjectIdentifier::access(const ResolveResults &result, Py::Object *va break; }}} } + + auto setPropDep = [deps](DocumentObject *obj, Property *prop, const char *propName) { + if(!deps || !obj) + return; + if(prop && prop->getContainer()!=obj) { + auto linkTouched = Base::freecad_dynamic_cast( + obj->getPropertyByName("_LinkTouched")); + if(linkTouched) + propName = linkTouched->getName(); + else { + auto propOwner = Base::freecad_dynamic_cast(prop->getContainer()); + if(propOwner) + obj = propOwner; + else + propName = 0; + } + } + auto &propset = (*deps)[obj]; + // inserting a null name in the propset indicates the dependency is + // on all properties of the corresponding object. + if(propset.size()!=1 || propset.begin()->size()) { + if(!propName) + propset.clear(); + propset.insert(propName); + } + return; + }; + + App::DocumentObject *lastObj = result.resolvedDocumentObject; + if(result.resolvedSubObject) { + setPropDep(lastObj,0,0); + lastObj = result.resolvedSubObject; + } + if(ptype == PseudoNone) + setPropDep(lastObj, result.resolvedProperty, result.resolvedProperty->getName()); + else + setPropDep(lastObj,0,0); + lastObj = 0; + if(components.empty()) return pyobj; + size_t count = components.size(); if(value) --count; assert(idx<=count); - for(;idx(*pyobj)->getDocumentObjectPtr(); + else if(lastObj) { + const char *attr = components[idx].getName().c_str(); + auto prop = lastObj->getPropertyByName(attr); + if(!prop && pyobj.hasAttr(attr)) + attr = 0; + setPropDep(lastObj,prop,attr); + lastObj = 0; + } pyobj = components[idx].get(pyobj); + } if(value) { components[idx].set(pyobj,*value); return Py::Object(); diff --git a/src/App/ObjectIdentifier.h b/src/App/ObjectIdentifier.h index f47d7808b7..5c09f1035b 100644 --- a/src/App/ObjectIdentifier.h +++ b/src/App/ObjectIdentifier.h @@ -342,7 +342,45 @@ public: bool relabeledDocument(ExpressionVisitor &v, const std::string &oldLabel, const std::string &newLabel); - std::pair getDep(std::vector *labels=0) const; + /** Type for storing dependency of an ObjectIdentifier + * + * The dependency is a map from document object to a set of property names. + * An object identifier may references multiple objects using syntax like + * 'Part.Group[0].Width'. + * + * Also, we use set of string instead of set of Property pointer, because + * the property may not exist at the time this ObjectIdentifier is + * constructed. + */ + typedef std::map > Dependencies; + + /** Get dependencies of this object identifier + * + * @param needProps: whether need property dependencies. + * @param labels: optional return of any label references. + * + * In case of multi-object references, like 'Part.Group[0].Width', if no + * property dependency is required, then this function will only return the + * first referred object dependency. Or else, all object and property + * dependencies will be returned. + */ + Dependencies getDep(bool needProps, std::vector *labels=0) const; + + /** Get dependencies of this object identifier + * + * @param deps: returns the depdenencies. + * @param needProps: whether need property dependencies. + * @param labels: optional return of any label references. + * + * In case of multi-object references, like 'Part.Group[0].Width', if no + * property dependency is required, then this function will only return the + * first referred object dependency. Or else, all object and property + * dependencies will be returned. + */ + void getDep(Dependencies &deps, bool needProps, std::vector *labels=0) const; + + /// Returns all label references + void getDepLabels(std::vector &labels) const; App::Document *getDocument(String name = String(), bool *ambiguous=0) const; @@ -422,7 +460,8 @@ protected: void getSubPathStr(std::ostream &ss, const ResolveResults &result, bool toPython=false) const; - Py::Object access(const ResolveResults &rs, Py::Object *value=0) const; + Py::Object access(const ResolveResults &rs, + Py::Object *value=0, Dependencies *deps=0) const; void resolve(ResolveResults & results) const; void resolveAmbiguity(ResolveResults &results); @@ -430,6 +469,8 @@ protected: static App::DocumentObject *getDocumentObject( const App::Document *doc, const String &name, std::bitset<32> &flags); + void getDepLabels(const ResolveResults &result, std::vector &labels) const; + App::DocumentObject * owner; String documentName; String documentObjectName; diff --git a/src/App/PropertyExpressionEngine.cpp b/src/App/PropertyExpressionEngine.cpp index f8f8e84f5d..cf7a143ffa 100644 --- a/src/App/PropertyExpressionEngine.cpp +++ b/src/App/PropertyExpressionEngine.cpp @@ -37,6 +37,7 @@ #include #include +FC_LOG_LEVEL_INIT("App",true); using namespace App; using namespace Base; @@ -72,6 +73,15 @@ void PropertyExpressionContainer::slotRelabelDocument(const App::Document &doc) /////////////////////////////////////////////////////////////////////////////////////// +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) /** @@ -125,7 +135,7 @@ void PropertyExpressionEngine::hasSetValue() return; } - std::set deps; + std::map deps; std::vector labels; unregisterElementReference(); UpdateElementReferenceExpressionVisitor v(*this); @@ -141,9 +151,95 @@ void PropertyExpressionEngine::hasSetValue() 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.reset(new Private); + 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()) { + if(propName.size()) + pimpl->conns.push_back(obj->signalChanged.connect(boost::bind( + &PropertyExpressionEngine::slotChangedProperty,this,_1,_2))); + else + pimpl->conns.push_back(obj->signalChanged.connect(boost::bind( + &PropertyExpressionEngine::slotChangedObject,this,_1,_2))); + } + 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); @@ -562,17 +658,17 @@ DocumentObjectExecReturn *App::PropertyExpressionEngine::execute(ExecuteOption o prop->setPathValue(*it, value); }catch(Base::Exception &e) { std::ostringstream ss; - ss << e.what() << std::endl << "in property binding '" << prop->getName() << "'"; + 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->getName() << "'"; + 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->getName() << "'"; + ss << e.what() << "\nin property binding '" << prop->getFullName() << "'"; throw Base::RuntimeError(ss.str().c_str()); } } @@ -610,9 +706,11 @@ void PropertyExpressionEngine::getPathsToDocumentObject(DocumentObject* obj, bool PropertyExpressionEngine::depsAreTouched() const { - for(auto obj : _Deps) - if(obj->isTouched()) + for(auto &v : _Deps) { + // v.second inidcates if it is a hidden reference + if(!v.second && v.first->isTouched()) return true; + } return false; } @@ -639,8 +737,9 @@ std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier assert(pathDocObj); auto inList = pathDocObj->getInListEx(true); - for(auto docObj : expr->getDepObjects()) { - if(inList.count(docObj)) { + for(auto &v : expr->getDepObjects()) { + auto docObj = v.first; + if(!v.second && inList.count(docObj)) { std::stringstream ss; ss << "cyclic reference to " << docObj->getFullName(); return ss.str(); @@ -765,8 +864,8 @@ bool PropertyExpressionEngine::adjustLink(const std::set &inLis if(!owner) return false; bool found = false; - for(auto obj : _Deps) { - if(inList.count(obj)) { + for(auto &v : _Deps) { + if(inList.count(v.first)) { found = true; break; } diff --git a/src/App/PropertyExpressionEngine.h b/src/App/PropertyExpressionEngine.h index 5510a74961..926ff856d3 100644 --- a/src/App/PropertyExpressionEngine.h +++ b/src/App/PropertyExpressionEngine.h @@ -85,9 +85,11 @@ public: struct ExpressionInfo { std::shared_ptr expression; /**< The actual expression tree */ + bool busy; ExpressionInfo(std::shared_ptr expression = std::shared_ptr()) { this->expression = expression; + this->busy = false; } ExpressionInfo(const ExpressionInfo & other) { @@ -186,6 +188,10 @@ private: boost::unordered_map &revNodes, DiGraph &g, ExecuteOption option=ExecuteAll) const; + void slotChangedObject(const App::DocumentObject &obj, const App::Property &prop); + void slotChangedProperty(const App::DocumentObject &obj, const App::Property &prop); + void updateHiddenReference(const std::string &key); + bool running; /**< Boolean used to avoid loops */ bool restoring = false; @@ -201,6 +207,9 @@ private: /**< Expressions are read from file to this map first before they are validated and inserted into the actual map */ std::unique_ptr > restoredExpressions; + struct Private; + std::unique_ptr pimpl; + friend class AtomicPropertyChange; }; diff --git a/src/App/PropertyLinks.cpp b/src/App/PropertyLinks.cpp index af4102c879..6fdc905610 100644 --- a/src/App/PropertyLinks.cpp +++ b/src/App/PropertyLinks.cpp @@ -4483,7 +4483,7 @@ void PropertyXLinkContainer::afterRestore() { if(info.docLabel != obj->getDocument()->Label.getValue()) _DocMap[App::quote(info.docLabel)] = obj->getDocument()->Label.getValue(); } - if(_Deps.insert(obj).second) + if(_Deps.insert(std::make_pair(obj,info.xlink->getScope()==LinkScope::Hidden)).second) _XLinks[obj->getFullName()] = std::move(info.xlink); } _XLinkRestores.reset(); @@ -4496,24 +4496,27 @@ void PropertyXLinkContainer::breakLink(App::DocumentObject *obj, bool clear) { if(!owner || !owner->getNameInDocument()) return; if(!clear || obj!=owner) { - if(!_Deps.erase(obj)) + auto it = _Deps.find(obj); + if(it == _Deps.end()) return; aboutToSetValue(); onBreakLink(obj); - if(obj->getDocument() == owner->getDocument()) - obj->_removeBackLink(owner); - else + if (obj->getDocument() != owner->getDocument()) _XLinks.erase(obj->getFullName()); + else if (!it->second) + obj->_removeBackLink(owner); + _Deps.erase(it); hasSetValue(); return; } if(obj!=owner) return; - for(auto obj : _Deps) { + for(auto &v : _Deps) { + auto obj = v.first; if(!obj || !obj->getNameInDocument()) continue; onBreakLink(obj); - if(obj->getDocument()==owner->getDocument()) + if(!v.second && obj->getDocument()==owner->getDocument()) obj->_removeBackLink(owner); } _XLinks.clear(); @@ -4553,6 +4556,19 @@ void PropertyXLinkContainer::Save (Base::Writer &writer) const { 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(); @@ -4575,6 +4591,15 @@ void PropertyXLinkContainer::Restore(Base::XMLReader &reader) { auto count = reader.getAttributeAsUnsigned("count"); _XLinkRestores.reset(new std::vector(count)); + if(reader.hasAttribute("hidden")) { + std::istringstream iss(reader.getAttribute("hidden")); + int index; + while(iss >> index) { + if(index>=0 && index(count)) + _XLinkRestores->at(index).hidden = true; + } + } + if(reader.hasAttribute("docs")) { auto docCount = reader.getAttributeAsUnsigned("docs"); _DocMap.clear(); @@ -4593,6 +4618,8 @@ void PropertyXLinkContainer::Restore(Base::XMLReader &reader) { for(auto &info : *_XLinkRestores) { info.xlink.reset(createXLink()); + if(info.hidden) + info.xlink->setScope(LinkScope::Hidden); info.xlink->Restore(reader); } reader.readEndElement("XLinks"); @@ -4624,16 +4651,23 @@ bool PropertyXLinkContainer::isLinkedToDocument(const App::Document &doc) const return false; } -void PropertyXLinkContainer::updateDeps(std::set &&newDeps) { +void PropertyXLinkContainer::updateDeps(std::map &&newDeps) { auto owner = Base::freecad_dynamic_cast(getContainer()); if(!owner || !owner->getNameInDocument()) return; newDeps.erase(owner); - for(auto obj : newDeps) { + for(auto &v : newDeps) { + auto obj = v.first; if(obj && obj->getNameInDocument()) { 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; } @@ -4643,21 +4677,21 @@ void PropertyXLinkContainer::updateDeps(std::set &&newDeps) { xlink.reset(createXLink()); xlink->setValue(obj); } + xlink->setScope(v.second?LinkScope::Hidden:LinkScope::Global); } -#ifndef USE_OLD_DAG - else + else if(!v.second) obj->_addBackLink(owner); -#endif + onAddDep(obj); } } - for(auto obj : _Deps) { + for(auto &v : _Deps) { + auto obj = v.first; if(!obj || !obj->getNameInDocument()) continue; if(obj->getDocument()==owner->getDocument()) { -#ifndef USE_OLD_DAG - obj->_removeBackLink(owner); -#endif + if(!v.second) + obj->_removeBackLink(owner); }else _XLinks.erase(obj->getFullName()); onRemoveDep(obj); @@ -4681,8 +4715,9 @@ void PropertyXLinkContainer::clearDeps() { return; #ifndef USE_OLD_DAG if (!owner->testStatus(ObjectStatus::Destroy)) { - for(auto obj : _Deps) { - if(obj && obj->getNameInDocument() && obj->getDocument()==owner->getDocument()) + for(auto &v : _Deps) { + auto obj = v.first; + if(!v.second && obj && obj->getNameInDocument() && obj->getDocument()==owner->getDocument()) obj->_removeBackLink(owner); } } @@ -4692,8 +4727,11 @@ void PropertyXLinkContainer::clearDeps() { _LinkRestored = false; } -void PropertyXLinkContainer::getLinks(std::vector &objs, - bool, std::vector * /*subs*/, bool /*newStyle*/) const +void PropertyXLinkContainer::getLinks(std::vector &objs, + bool all, std::vector * /*subs*/, bool /*newStyle*/) const { - objs.insert(objs.end(),_Deps.begin(),_Deps.end()); + for(auto &v : _Deps) { + if(all || !v.second) + objs.push_back(v.first); + } } diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index 7b82113a15..5bc813d5e0 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -1339,11 +1339,11 @@ protected: virtual void onBreakLink(App::DocumentObject *obj); virtual void onAddDep(App::DocumentObject *) {} virtual void onRemoveDep(App::DocumentObject *) {} - void updateDeps(std::set &&newDeps); + void updateDeps(std::map &&newDeps); void clearDeps(); protected: - std::set _Deps; + std::map _Deps; std::map > _XLinks; std::map _DocMap; bool _LinkRestored; @@ -1353,6 +1353,7 @@ private: std::unique_ptr xlink; std::string docName; std::string docLabel; + bool hidden=false; }; std::unique_ptr > _XLinkRestores; }; diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 265f8d7697..571453c1a1 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -1023,38 +1023,39 @@ void PropertySheet::addDependencies(CellAddress key) if (expression == 0) return; - for(auto &dep : expression->getDeps()) { + for(auto &var : expression->getIdentifiers()) { + for(auto &dep : var.first.getDep(true)) { + App::DocumentObject *docObj = dep.first; + App::Document *doc = docObj->getDocument(); - App::DocumentObject *docObj = dep.first; - App::Document *doc = docObj->getDocument(); + std::string docObjName = docObj->getFullName(); - std::string docObjName = docObj->getFullName(); + owner->observeDocument(doc); - owner->observeDocument(doc); + documentObjectToCellMap[docObjName].insert(key); + cellToDocumentObjectMap[key].insert(docObjName); + ++updateCount; - documentObjectToCellMap[docObjName].insert(key); - cellToDocumentObjectMap[key].insert(docObjName); - ++updateCount; + for(auto &name : dep.second) { + std::string propName = docObjName + "." + name; + FC_LOG("dep " << key.toString() << " -> " << name); - for(auto &props : dep.second) { - std::string propName = docObjName + "." + props.first; - FC_LOG("dep " << key.toString() << " -> " << propName); + // Insert into maps + propertyNameToCellMap[propName].insert(key); + cellToPropertyNameMap[key].insert(propName); - // Insert into maps - propertyNameToCellMap[propName].insert(key); - cellToPropertyNameMap[key].insert(propName); + // Also an alias? + if (docObj==owner && name.size()) { + auto j = revAliasProp.find(name); - // Also an alias? - if (docObj==owner && props.first.size()) { - std::map::const_iterator j = revAliasProp.find(props.first); + if (j != revAliasProp.end()) { + propName = docObjName + "." + j->second.toString(); + FC_LOG("dep " << key.toString() << " -> " << propName); - if (j != revAliasProp.end()) { - propName = docObjName + "." + j->second.toString(); - FC_LOG("dep " << key.toString() << " -> " << propName); - - // Insert into maps - propertyNameToCellMap[propName].insert(key); - cellToPropertyNameMap[key].insert(propName); + // Insert into maps + propertyNameToCellMap[propName].insert(key); + cellToPropertyNameMap[key].insert(propName); + } } } } @@ -1123,6 +1124,16 @@ void PropertySheet::removeDependencies(CellAddress key) void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const char *propName) { + auto itD = _Deps.find(const_cast(owner)); + if(itD!=_Deps.end() && itD->second) { + // Check for hidden reference. Because a hidden reference is not + // protected by cyclic dependency checking, we need to take special + // care to prevent it from misbehave. + Sheet *sheet = Base::freecad_dynamic_cast(getContainer()); + if(!sheet || sheet->testStatus(App::ObjectStatus::Recompute2)) + return; + } + // First, search without actual property name for sub-object/link // references, i.e indirect references. The dependencies of these // references are too complex to track exactly, so we only track the @@ -1135,7 +1146,7 @@ void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const setDirty(cell); } - if (propName) { + if (propName && *propName) { // Now, we check for direct property references it = propertyNameToCellMap.find(fullName + propName); if (it != propertyNameToCellMap.end()) { @@ -1302,7 +1313,7 @@ void PropertySheet::hasSetValue() updateCount = 0; - std::set deps; + std::map deps; std::vector labels; unregisterElementReference(); UpdateElementReferenceExpressionVisitor v(*this); @@ -1384,8 +1395,9 @@ bool PropertySheet::adjustLink(const std::set &inList) { continue; try { bool need_adjust = false; - for(auto docObj : expr->getDepObjects()) { - if (docObj && docObj != owner && inList.count(docObj)) { + for(auto &v : expr->getDepObjects()) { + auto docObj = v.first; + if (v.second && docObj && docObj!=owner && inList.count(docObj)) { need_adjust = true; break; }