PropertyExpressionEngine: convert to link type property

PropertyExpressionEngine is changed to derived from a new class
PropertyExpressionContainer, which is in turn derives from
PropertyXLinkContainer. This makes PropertyExpressionEngine a link type
property that is capable of external linking. It now uses the unified
link property APIs for dependency management and tracking of object
life time, re-labeling, etc.

ObjectIdentifier is modified to support sub-object reference, but is
not exposed to end-user, because expression syntax is kept mostly
unchanged, which will be submitted in future PR. There is, however,
one small change in expression syntax (ExpressionParser.y) to introduce
local property reference to avoid ambiguity mentioned in
FreeCAD/FreeCAD#1619

Modified Expression/ExpressionModifier interface to support various link
property API for link modification.
This commit is contained in:
Zheng, Lei
2019-06-29 17:30:51 +08:00
committed by wmayer
parent e85bf9cd0e
commit 93e60caa35
25 changed files with 3895 additions and 1645 deletions

View File

@@ -27,6 +27,7 @@
#include <Base/Interpreter.h>
#include <Base/Writer.h>
#include <Base/Reader.h>
#include <Base/Tools.h>
#include "Expression.h"
#include "ExpressionVisitors.h"
#include "PropertyExpressionEngine.h"
@@ -42,44 +43,44 @@ using namespace App;
using namespace Base;
using namespace boost;
class ObjectDeletedExpressionVisitor : public ExpressionVisitor {
public:
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyExpressionContainer , App::PropertyXLinkContainer);
ObjectDeletedExpressionVisitor(const App::DocumentObject * _obj)
: obj(_obj)
, found(false)
{
static std::set<PropertyExpressionContainer*> _ExprContainers;
PropertyExpressionContainer::PropertyExpressionContainer() {
static bool inited;
if(!inited) {
inited = true;
GetApplication().signalRelabelDocument.connect(PropertyExpressionContainer::slotRelabelDocument);
}
_ExprContainers.insert(this);
}
/**
* @brief Visit each node in the expression, and if it is a VariableExpression object check if it references obj
* @param node Node to visit
*/
PropertyExpressionContainer::~PropertyExpressionContainer() {
_ExprContainers.erase(this);
}
void visit(Expression * node) {
VariableExpression *expr = freecad_dynamic_cast<VariableExpression>(node);
if (expr && expr->getPath().getDocumentObject() == obj)
found = true;
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);
}
}
bool isFound() const { return found; }
///////////////////////////////////////////////////////////////////////////////////////
private:
const App::DocumentObject * obj;
bool found;
};
TYPESYSTEM_SOURCE(App::PropertyExpressionEngine , App::Property);
TYPESYSTEM_SOURCE(App::PropertyExpressionEngine , App::PropertyExpressionContainer);
/**
* @brief Construct a new PropertyExpressionEngine object.
*/
PropertyExpressionEngine::PropertyExpressionEngine()
: Property()
, AtomicPropertyChangeInterface()
, running(false)
: running(false)
, validator(0)
{
}
@@ -110,81 +111,74 @@ Property *PropertyExpressionEngine::Copy() const
PropertyExpressionEngine * engine = new PropertyExpressionEngine();
for (ExpressionMap::const_iterator it = expressions.begin(); it != expressions.end(); ++it)
engine->expressions[it->first] = ExpressionInfo(boost::shared_ptr<Expression>(it->second.expression->copy()), it->second.comment.c_str());
engine->expressions[it->first] = ExpressionInfo(boost::shared_ptr<Expression>(it->second.expression->copy()));
engine->validator = validator;
return engine;
}
void PropertyExpressionEngine::hasSetValue()
{
App::DocumentObject *owner = dynamic_cast<App::DocumentObject*>(getContainer());
if(!owner || !owner->getNameInDocument() || owner->isRestoring() || testFlag(LinkDetached)) {
PropertyExpressionContainer::hasSetValue();
return;
}
std::set<App::DocumentObject*> deps;
std::vector<std::string> labels;
unregisterElementReference();
UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> 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));
PropertyExpressionContainer::hasSetValue();
}
void PropertyExpressionEngine::Paste(const Property &from)
{
const PropertyExpressionEngine * fromee = static_cast<const PropertyExpressionEngine*>(&from);
AtomicPropertyChange signaller(*this);
#ifndef USE_OLD_DAG
//maintain backlinks, verify that this property is owned by a DocumentObject
App::DocumentObject* parent = dynamic_cast<App::DocumentObject*>(getContainer());
if (parent) {
ExpressionMap::const_iterator i = expressions.begin();
while (i != expressions.end()) {
std::set<ObjectIdentifier> deps;
i->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && (docObj != parent))
docObj->_removeBackLink(parent);
++j;
}
++i;
}
}
#endif
expressions.clear();
for (ExpressionMap::const_iterator it = fromee->expressions.begin(); it != fromee->expressions.end(); ++it) {
expressions[it->first] = ExpressionInfo(boost::shared_ptr<Expression>(it->second.expression->copy()), it->second.comment.c_str());
#ifndef USE_OLD_DAG
if (parent) {
//maintain backlinks
std::set<ObjectIdentifier> deps;
it->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && (docObj != parent))
docObj->_addBackLink(parent);
++j;
}
}
#endif
expressionChanged(it->first);
for(auto &e : fromee->expressions) {
expressions[e.first] = ExpressionInfo(
boost::shared_ptr<Expression>(e.second.expression->copy()));
expressionChanged(e.first);
}
validator = fromee->validator;
signaller.tryInvoke();
}
void PropertyExpressionEngine::Save(Base::Writer &writer) const
{
writer.Stream() << writer.ind() << "<ExpressionEngine count=\"" << expressions.size() <<"\">" << std::endl;
writer.incInd();
writer.Stream() << writer.ind() << "<ExpressionEngine count=\"" << expressions.size();
if(PropertyExpressionContainer::_XLinks.empty()) {
writer.Stream() << "\">" << std::endl;
writer.incInd();
} else {
writer.Stream() << "\" xlink=\"1\">" << std::endl;
writer.incInd();
PropertyExpressionContainer::Save(writer);
}
for (ExpressionMap::const_iterator it = expressions.begin(); it != expressions.end(); ++it) {
writer.Stream() << writer.ind() << "<Expression path=\"" << Property::encodeAttribute(it->first.toString()) <<"\"" <<
" expression=\"" << Property::encodeAttribute(it->second.expression->toString()) << "\"";
if (it->second.comment.size() > 0)
writer.Stream() << " comment=\"" << Property::encodeAttribute(it->second.comment) << "\"";
writer.Stream() << writer.ind() << "<Expression path=\""
<< Property::encodeAttribute(it->first.toString()) <<"\" expression=\""
<< Property::encodeAttribute(it->second.expression->toString(true)) << "\"";
if (it->second.expression->comment.size() > 0)
writer.Stream() << " comment=\""
<< Property::encodeAttribute(it->second.expression->comment) << "\"";
writer.Stream() << "/>" << std::endl;
}
writer.decInd();
@@ -194,19 +188,22 @@ void PropertyExpressionEngine::Save(Base::Writer &writer) const
void PropertyExpressionEngine::Restore(Base::XMLReader &reader)
{
reader.readElement("ExpressionEngine");
int count = reader.getAttributeAsFloat("count");
restoredExpressions.clear();
if(reader.hasAttribute("xlink") && reader.getAttributeAsInteger("xlink"))
PropertyExpressionContainer::Restore(reader);
restoredExpressions.reset(new std::vector<RestoredExpression>);
restoredExpressions->reserve(count);
for (int i = 0; i < count; ++i) {
DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
reader.readElement("Expression");
ObjectIdentifier path = ObjectIdentifier::parse(docObj, reader.getAttribute("path"));
boost::shared_ptr<Expression> expression(ExpressionParser::parse(docObj, reader.getAttribute("expression")));
const char * comment = reader.hasAttribute("comment") ? reader.getAttribute("comment") : 0;
restoredExpressions[path] = ExpressionInfo(expression, comment);
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");
@@ -227,8 +224,6 @@ void PropertyExpressionEngine::buildGraphStructures(const ObjectIdentifier & pat
boost::unordered_map<int, ObjectIdentifier> & revNodes,
std::vector<Edge> & edges) const
{
std::set<ObjectIdentifier> deps;
/* Insert target property into nodes structure */
if (nodes.find(path) == nodes.end()) {
int s = nodes.size();
@@ -239,26 +234,20 @@ void PropertyExpressionEngine::buildGraphStructures(const ObjectIdentifier & pat
else
revNodes[nodes[path]] = path;
/* Get the dependencies for this expression */
expression->getDeps(deps);
/* Insert dependencies into nodes structure */
std::set<ObjectIdentifier>::const_iterator di = deps.begin();
while (di != deps.end()) {
Property * prop = di->getProperty();
if (prop) {
ObjectIdentifier cPath(di->canonicalPath());
if (nodes.find(cPath) == nodes.end()) {
int s = nodes.size();
nodes[cPath] = s;
for(auto &dep : expression->getDeps()) {
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.push_back(std::make_pair(nodes[path], nodes[cPath]));
}
edges.push_back(std::make_pair(nodes[path], nodes[cPath]));
}
++di;
}
}
@@ -268,7 +257,7 @@ void PropertyExpressionEngine::buildGraphStructures(const ObjectIdentifier & pat
* @return New ObjectIdentifier
*/
const ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier &p) const
ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier &p) const
{
DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
@@ -276,15 +265,15 @@ const ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdent
if (!docObj)
throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject.");
Property * prop = p.getProperty();
int ptype;
Property * prop = p.getProperty(&ptype);
// p pointing to a property...?
if (!prop)
throw Base::RuntimeError("Property not found");
throw Base::RuntimeError(p.resolveErrorString().c_str());
// ... in the same container as I?
if (prop->getContainer() != getContainer())
throw Base::RuntimeError("Property does not belong to same container as PropertyExpressionEngine");
if(ptype || prop->getContainer()!=getContainer())
return p;
// In case someone calls this with p pointing to a PropertyExpressionEngine for some reason
if (prop->isDerivedFrom(PropertyExpressionEngine::classTypeId))
@@ -304,61 +293,37 @@ size_t PropertyExpressionEngine::numExpressions() const
return expressions.size();
}
/**
* @brief Slot called when a document object is renamed.
* @param obj Renamed object
*/
void PropertyExpressionEngine::slotObjectRenamed(const DocumentObject &obj)
{
#ifdef FC_PROPERTYEXPRESSIONENGINE_LOG
std::clog << "Object " << obj.getOldLabel() << " renamed to " << obj.Label.getValue() << std::endl;
#endif
DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
/* In a document object, and on undo stack? */
if (!docObj || docObj->getNameInDocument() == 0)
return;
RelabelDocumentObjectExpressionVisitor<PropertyExpressionEngine> v(*this, obj.getOldLabel(), obj.Label.getStrValue());
for (ExpressionMap::iterator it = expressions.begin(); it != expressions.end(); ++it) {
bool changed = v.getChanged();
it->second.expression->visit(v);
if (changed != v.getChanged())
expressionChanged(it->first);
}
}
void PropertyExpressionEngine::slotObjectDeleted(const DocumentObject &obj)
void PropertyExpressionEngine::afterRestore()
{
DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
if(restoredExpressions && docObj) {
Base::FlagToggler<bool> flag(restoring);
AtomicPropertyChange signaller(*this);
/* In a document object, and on undo stack? */
if (!docObj || docObj->getNameInDocument() == 0)
return;
PropertyExpressionContainer::afterRestore();
ObjectIdentifier::DocumentMapper mapper(this->_DocMap);
ObjectDeletedExpressionVisitor v(&obj);
for (ExpressionMap::iterator it = expressions.begin(); it != expressions.end(); ++it) {
it->second.expression->visit(v);
if (v.isFound()) {
touch(); // Touch to force recompute; that will trigger a proper error
return;
for(auto &info : *restoredExpressions) {
ObjectIdentifier path = ObjectIdentifier::parse(docObj, info.path);
boost::shared_ptr<Expression> expression(Expression::parse(docObj, info.expr.c_str()));
if(expression)
expression->comment = std::move(info.comment);
setValue(path, expression);
}
signaller.tryInvoke();
}
restoredExpressions.reset();
}
void PropertyExpressionEngine::onDocumentRestored()
{
AtomicPropertyChange signaller(*this);
for (ExpressionMap::iterator it = restoredExpressions.begin(); it != restoredExpressions.end(); ++it)
setValue(it->first, it->second.expression, it->second.comment.size() > 0 ? it->second.comment.c_str() : 0);
void PropertyExpressionEngine::onContainerRestored() {
Base::FlagToggler<bool> flag(restoring);
unregisterElementReference();
UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this);
for(auto &e : expressions) {
auto expr = e.second.expression;
if(expr)
expr->visit(v);
}
}
/**
@@ -387,7 +352,7 @@ const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentif
* @param comment Optional comment.
*/
void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, boost::shared_ptr<Expression> expr, const char *comment)
void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, boost::shared_ptr<Expression> expr)
{
ObjectIdentifier usePath(canonicalPath(path));
const Property * prop = usePath.getProperty();
@@ -402,83 +367,17 @@ void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, boost::sh
if (expr) {
std::string error = validateExpression(usePath, expr);
if (error.size() > 0)
throw Base::RuntimeError(error.c_str());
AtomicPropertyChange signaller(*this);
#ifndef USE_OLD_DAG
// When overriding an ObjectIdentifier key then first remove
// the dependency caused by the expression as otherwise it happens
// that the same object dependency is added twice for the same
// identifier. This makes it impossible to properly clear dependencies
// and thus leads to topological errors on recompute.
//
// Verify that this property is owned by a DocumentObject
App::DocumentObject* parent = dynamic_cast<App::DocumentObject*>(getContainer());
if (parent) {
if (it != expressions.end() && it->second.expression) {
std::set<ObjectIdentifier> deps;
it->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && (docObj != parent))
docObj->_removeBackLink(parent);
++j;
}
}
}
#endif
expressions[usePath] = ExpressionInfo(expr, comment);
#ifndef USE_OLD_DAG
//maintain the backlinks in the documentobject graph datastructure
if (parent) {
std::set<ObjectIdentifier> deps;
expr->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && (docObj != parent))
docObj->_addBackLink(parent);
++j;
}
}
#endif
expressions[usePath] = ExpressionInfo(expr);
expressionChanged(usePath);
}
else {
signaller.tryInvoke();
} else {
AtomicPropertyChange signaller(*this);
#ifndef USE_OLD_DAG
//verify that this property is owned by a DocumentObject
//verify that the ObjectIdentifier usePath is part of the expression map and
//that the expression is not null
App::DocumentObject* parent = dynamic_cast<App::DocumentObject*>(getContainer());
if (parent && it != expressions.end() && it->second.expression) {
//maintain the backlinks in the documentobject graph datastructure
std::set<ObjectIdentifier> deps;
it->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && (docObj != parent))
docObj->_removeBackLink(parent);
++j;
}
}
#endif
expressions.erase(usePath);
expressionChanged(usePath);
signaller.tryInvoke();
}
}
@@ -509,14 +408,29 @@ struct cycle_detector : public boost::dfs_visitor<> {
*/
void PropertyExpressionEngine::buildGraph(const ExpressionMap & exprs,
boost::unordered_map<int, ObjectIdentifier> & revNodes, DiGraph & g) const
boost::unordered_map<int, ObjectIdentifier> & revNodes,
DiGraph & g, ExecuteOption option) const
{
boost::unordered_map<ObjectIdentifier, int> nodes;
std::vector<Edge> edges;
// Build data structure for graph
for (ExpressionMap::const_iterator it = exprs.begin(); it != exprs.end(); ++it)
for (ExpressionMap::const_iterator it = exprs.begin(); it != exprs.end(); ++it) {
if(option!=ExecuteAll) {
auto prop = it->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(it->first, it->second.expression, nodes, revNodes, edges);
}
// Create graph
g = DiGraph(revNodes.size());
@@ -544,13 +458,13 @@ void PropertyExpressionEngine::buildGraph(const ExpressionMap & exprs,
* order, in case properties depends on each other.
*/
std::vector<App::ObjectIdentifier> PropertyExpressionEngine::computeEvaluationOrder()
std::vector<App::ObjectIdentifier> PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option)
{
std::vector<App::ObjectIdentifier> evaluationOrder;
boost::unordered_map<int, ObjectIdentifier> revNodes;
DiGraph g;
buildGraph(expressions, revNodes, g);
buildGraph(expressions, revNodes, g, option);
/* Compute evaluation order for expressions */
std::vector<int> c;
@@ -569,7 +483,7 @@ std::vector<App::ObjectIdentifier> PropertyExpressionEngine::computeEvaluationOr
* @return StdReturn on success.
*/
DocumentObjectExecReturn *App::PropertyExpressionEngine::execute()
DocumentObjectExecReturn *App::PropertyExpressionEngine::execute(ExecuteOption option, bool *touched)
{
DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
@@ -579,6 +493,24 @@ DocumentObjectExecReturn *App::PropertyExpressionEngine::execute()
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.
*/
@@ -595,7 +527,7 @@ DocumentObjectExecReturn *App::PropertyExpressionEngine::execute()
resetter r(running);
// Compute evaluation order
std::vector<App::ObjectIdentifier> evaluationOrder = computeEvaluationOrder();
std::vector<App::ObjectIdentifier> evaluationOrder = computeEvaluationOrder(option);
std::vector<ObjectIdentifier>::const_iterator it = evaluationOrder.begin();
#ifdef FC_PROPERTYEXPRESSIONENGINE_LOG
@@ -603,7 +535,7 @@ DocumentObjectExecReturn *App::PropertyExpressionEngine::execute()
#endif
/* Evaluate the expressions, and update properties */
while (it != evaluationOrder.end()) {
for (;it != evaluationOrder.end();++it) {
// Get property to update
Property * prop = it->getProperty();
@@ -617,69 +549,28 @@ DocumentObjectExecReturn *App::PropertyExpressionEngine::execute()
if (parent != docObj)
throw Base::RuntimeError("Invalid property owner.");
// Evaluate expression
std::unique_ptr<Expression> e(expressions[*it].expression->eval());
#ifdef FC_PROPERTYEXPRESSIONENGINE_LOG
{
Base::Quantity q;
boost::any value = e->getValueAsAny();
if (value.type() == typeid(Base::Quantity))
q = boost::any_cast<Base::Quantity>(value);
else if (value.type() == typeid(double))
q = boost::any_cast<double>(value);
else {
std::clog << "Unknown return value for expression.";
q = 0;
}
std::clog << "Assigning value " << q.getValue() << " to " << (*it).toString().c_str() << " (" << prop->getName() << ")" << std::endl;
}
#endif
/* Set value of property */
prop->setPathValue(*it, e->getValueAsAny());
++it;
try {
// Evaluate expression
std::unique_ptr<Expression> e(expressions[*it].expression->eval());
auto value = e->getValueAsAny();
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->getName() << "'";
e.setMessage(ss.str());
throw;
}
}
return DocumentObject::StdReturn;
}
/**
* @brief Find document objects that the expressions depend on.
* @param docObjs Dependencies
*/
void PropertyExpressionEngine::getDocumentObjectDeps(std::vector<DocumentObject *> &docObjs) const
{
DocumentObject * owner = freecad_dynamic_cast<DocumentObject>(getContainer());
if (owner == 0)
return;
ExpressionMap::const_iterator i = expressions.begin();
while (i != expressions.end()) {
std::set<ObjectIdentifier> deps;
i->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj && docObj != owner)
docObjs.push_back(docObj);
++j;
}
++i;
}
}
/**
* @brief Find paths to document object.
* @param obj Document object
@@ -691,30 +582,16 @@ void PropertyExpressionEngine::getPathsToDocumentObject(DocumentObject* obj,
{
DocumentObject * owner = freecad_dynamic_cast<DocumentObject>(getContainer());
if (owner == 0)
if (owner == 0 || owner==obj)
return;
ExpressionMap::const_iterator i = expressions.begin();
while (i != expressions.end()) {
std::set<ObjectIdentifier> deps;
i->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
DocumentObject* docObj = p.getDocumentObject();
if (docObj == obj && docObj != owner) {
paths.push_back(i->first);
break;
}
++j;
}
++i;
for(auto &v : expressions) {
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());
}
}
@@ -725,48 +602,12 @@ void PropertyExpressionEngine::getPathsToDocumentObject(DocumentObject* obj,
bool PropertyExpressionEngine::depsAreTouched() const
{
ExpressionMap::const_iterator i = expressions.begin();
while (i != expressions.end()) {
std::set<ObjectIdentifier> deps;
i->second.expression->getDeps(deps);
std::set<ObjectIdentifier>::const_iterator j = deps.begin();
while (j != deps.end()) {
const ObjectIdentifier & p = *j;
Property* prop = p.getProperty();
if (prop && prop->isTouched())
return true;
++j;
}
++i;
}
for(auto obj : _Deps)
if(obj->isTouched())
return true;
return false;
}
/**
* @brief Get a map of all registered expressions.
* @return Map of expressions.
*/
boost::unordered_map<const ObjectIdentifier, const PropertyExpressionEngine::ExpressionInfo> PropertyExpressionEngine::getExpressions() const
{
boost::unordered_map<const ObjectIdentifier, const ExpressionInfo> result;
ExpressionMap::const_iterator i = expressions.begin();
while (i != expressions.end()) {
result.insert(std::make_pair(i->first, i->second));
++i;
}
return result;
}
/**
* @brief Validate the given path and expression.
* @param path Object Identifier for expression.
@@ -785,33 +626,16 @@ std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier
return error;
}
// Get dependencies from expression
std::set<App::ObjectIdentifier> exprDeps;
expr->getDeps(exprDeps);
// Get document object
DocumentObject * pathDocObj = usePath.getDocumentObject();
assert(pathDocObj);
// Check for document object dependencies
for (std::set<App::ObjectIdentifier>::const_iterator j = exprDeps.begin(); j != exprDeps.end(); ++j) {
DocumentObject * docObj = (*j).getDocumentObject();
// Skip internal dependencies;
if (docObj == pathDocObj)
continue;
// Get dependencies for the document object pointed to be *j
std::vector<DocumentObject*> targets;
targets.push_back(docObj);
// Does the dependency resolve to a document? If not, ignore it
if ((*j).getDocument()) {
std::vector<DocumentObject*> deps = (*j).getDocument()->getDependencyList(targets);
for (std::vector<DocumentObject*>::const_iterator i = deps.begin(); i != deps.end(); ++i) {
if (*i == pathDocObj)
return (*j).toString() + " reference creates a cyclic dependency.";
}
auto inList = pathDocObj->getInListEx(true);
for(auto docObj : expr->getDepObjects()) {
if(inList.count(docObj)) {
std::stringstream ss;
ss << "cyclic reference to " << docObj->getFullName();
return ss.str();
}
}
@@ -899,3 +723,181 @@ 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 attension
*
void PropertyExpressionEngine::breakLink(App::DocumentObject *obj, bool clear) {
auto owner = dynamic_cast<App::DocumentObject*>(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<DocumentObject*> &inList) {
auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
if(!owner)
return false;
bool found = false;
for(auto obj : _Deps) {
if(inList.count(obj)) {
found = true;
break;
}
}
if(!found)
return false;
AtomicPropertyChange signaler(*this);
for(auto &v : expressions) {
try {
if(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<PropertyExpressionEngine> v(*this,feature,reverse);
for(auto &e : expressions) {
e.second.expression->visit(v);
if(v.changed()) {
expressionChanged(e.first);
v.reset();
}
}
if(feature && v.changed()) {
auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
if(owner)
owner->onUpdateElementReference(this);
}
}
bool PropertyExpressionEngine::referenceChanged() const {
return false;
}
Property *PropertyExpressionEngine::CopyOnImportExternal(
const std::map<std::string,std::string> &nameMap) const
{
std::unique_ptr<PropertyExpressionEngine> engine;
for(auto it=expressions.begin();it!=expressions.end();++it) {
boost::shared_ptr<Expression> expr(it->second.expression->importSubNames(nameMap));
if(!expr && !engine)
continue;
if(!engine) {
engine.reset(new PropertyExpressionEngine);
for(auto it2=expressions.begin();it2!=it;++it2) {
engine->expressions[it2->first] = ExpressionInfo(
boost::shared_ptr<Expression>(it2->second.expression->copy()));
}
}else if(!expr)
expr = it->second.expression;
engine->expressions[it->first] = ExpressionInfo(expr);
}
if(!engine)
return 0;
engine->validator = validator;
return engine.release();
}
Property *PropertyExpressionEngine::CopyOnLabelChange(App::DocumentObject *obj,
const std::string &ref, const char *newLabel) const
{
std::unique_ptr<PropertyExpressionEngine> engine;
for(auto it=expressions.begin();it!=expressions.end();++it) {
boost::shared_ptr<Expression> expr(it->second.expression->updateLabelReference(obj,ref,newLabel));
if(!expr && !engine)
continue;
if(!engine) {
engine.reset(new PropertyExpressionEngine);
for(auto it2=expressions.begin();it2!=it;++it2) {
engine->expressions[it2->first] = ExpressionInfo(
boost::shared_ptr<Expression>(it2->second.expression->copy()));
}
}else if(!expr)
expr = it->second.expression;
engine->expressions[it->first] = ExpressionInfo(expr);
}
if(!engine)
return 0;
engine->validator = validator;
return engine.release();
}
Property *PropertyExpressionEngine::CopyOnLinkReplace(const App::DocumentObject *parent,
App::DocumentObject *oldObj, App::DocumentObject *newObj) const
{
std::unique_ptr<PropertyExpressionEngine> engine;
for(auto it=expressions.begin();it!=expressions.end();++it) {
boost::shared_ptr<Expression> expr(
it->second.expression->replaceObject(parent,oldObj,newObj));
if(!expr && !engine)
continue;
if(!engine) {
engine.reset(new PropertyExpressionEngine);
for(auto it2=expressions.begin();it2!=it;++it2) {
engine->expressions[it2->first] = ExpressionInfo(
boost::shared_ptr<Expression>(it2->second.expression->copy()));
}
}else if(!expr)
expr = it->second.expression;
engine->expressions[it->first] = ExpressionInfo(expr);
}
if(!engine)
return 0;
engine->validator = validator;
return engine.release();
}
std::map<App::ObjectIdentifier, const App::Expression*>
PropertyExpressionEngine::getExpressions() const
{
std::map<App::ObjectIdentifier, const Expression*> res;
for(auto &v : expressions)
res[v.first] = v.second.expression.get();
return res;
}
void PropertyExpressionEngine::setExpressions(
std::map<App::ObjectIdentifier, App::ExpressionPtr> &&exprs)
{
AtomicPropertyChange signaller(*this);
for(auto &v : exprs)
setValue(v.first,std::move(v.second));
}
void PropertyExpressionEngine::onRelabeledDocument(const App::Document &doc)
{
RelabelDocumentExpressionVisitor v(doc);
for(auto &e : expressions)
e.second.expression->visit(v);
}