FreeCAD

Форк
0
/
PropertyExpressionEngine.cpp 
1081 строка · 36.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2015 Eivind Kvedalen <eivind@kvedalen.name>             *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  is distributed in the hope that it will be useful,      *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24

25
#include <App/Application.h>
26
#include <App/Document.h>
27
#include <App/DocumentObject.h>
28
#include <Base/Reader.h>
29
#include <Base/Tools.h>
30
#include <Base/Writer.h>
31
#include <CXX/Objects.hxx>
32

33
#include "PropertyExpressionEngine.h"
34
#include "ExpressionVisitors.h"
35

36

37
FC_LOG_LEVEL_INIT("App", true);
38

39
using namespace App;
40
using namespace Base;
41
using namespace boost;
42
namespace sp = std::placeholders;
43

44
TYPESYSTEM_SOURCE_ABSTRACT(App::PropertyExpressionContainer , App::PropertyXLinkContainer)
45

46
static std::set<PropertyExpressionContainer*> _ExprContainers;
47

48
PropertyExpressionContainer::PropertyExpressionContainer() {
49
    static bool inited;
50
    if(!inited) {
51
        inited = true;
52
        GetApplication().signalRelabelDocument.connect(PropertyExpressionContainer::slotRelabelDocument);
53
    }
54
    _ExprContainers.insert(this);
55
}
56

57
PropertyExpressionContainer::~PropertyExpressionContainer() {
58
    _ExprContainers.erase(this);
59
}
60

61
void PropertyExpressionContainer::slotRelabelDocument(const App::Document &doc) {
62
    // For use a private _ExprContainers to track all living
63
    // PropertyExpressionContainer including those inside undo/redo stack,
64
    // because document relabel is not undoable/redoable.
65

66
    if(doc.getOldLabel() != doc.Label.getValue()) {
67
        for(auto prop : _ExprContainers)
68
            prop->onRelabeledDocument(doc);
69
    }
70
}
71

72
///////////////////////////////////////////////////////////////////////////////////////
73

74
struct PropertyExpressionEngine::Private {
75
    // For some reason, MSVC has trouble with vector of scoped_connection if
76
    // defined in header, hence the private structure here.
77
    std::vector<boost::signals2::scoped_connection> conns;
78
    std::unordered_map<std::string, std::vector<ObjectIdentifier> > propMap;
79
};
80

81
///////////////////////////////////////////////////////////////////////////////////////
82

83
TYPESYSTEM_SOURCE(App::PropertyExpressionEngine , App::PropertyExpressionContainer)
84

85
/**
86
 * @brief Construct a new PropertyExpressionEngine object.
87
 */
88

89
PropertyExpressionEngine::PropertyExpressionEngine()
90
    : validator(0)
91
{
92
}
93

94
/**
95
 * @brief Destroy the PropertyExpressionEngine object.
96
 */
97

98
PropertyExpressionEngine::~PropertyExpressionEngine() = default;
99

100
/**
101
 * @brief Estimate memory size of this property.
102
 *
103
 * \fixme Should probably return something else than 0.
104
 *
105
 * @return Size of object.
106
 */
107

108
unsigned int PropertyExpressionEngine::getMemSize() const
109
{
110
    return 0;
111
}
112

113
Property *PropertyExpressionEngine::Copy() const
114
{
115
    PropertyExpressionEngine * engine = new PropertyExpressionEngine();
116

117
    for (const auto & it : expressions) {
118
        ExpressionInfo info;
119
        if (it.second.expression)
120
            info.expression = std::shared_ptr<Expression>(it.second.expression->copy());
121
        engine->expressions[it.first] = info;
122
    }
123

124
    engine->validator = validator;
125

126
    return engine;
127
}
128

129
void PropertyExpressionEngine::hasSetValue()
130
{
131
    App::DocumentObject *owner = dynamic_cast<App::DocumentObject*>(getContainer());
132
    if(!owner || !owner->isAttachedToDocument() || owner->isRestoring() || testFlag(LinkDetached)) {
133
        PropertyExpressionContainer::hasSetValue();
134
        return;
135
    }
136

137
    std::map<App::DocumentObject*,bool> deps;
138
    std::vector<std::string> labels;
139
    unregisterElementReference();
140
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this);
141
    for(auto &e : expressions) {
142
        auto expr = e.second.expression;
143
        if(expr) {
144
            expr->getDepObjects(deps,&labels);
145
            if(!restoring)
146
                expr->visit(v);
147
        }
148
    }
149
    registerLabelReferences(std::move(labels));
150

151
    updateDeps(std::move(deps));
152

153
    if(pimpl) {
154
        pimpl->conns.clear();
155
        pimpl->propMap.clear();
156
    }
157
    // check if there is any hidden references
158
    bool hasHidden = false;
159
    for(auto &v : _Deps) {
160
        if(v.second) {
161
            hasHidden = true;
162
            break;
163
        }
164
    }
165
    if(hasHidden) {
166
        if(!pimpl) {
167
            pimpl = std::make_unique<Private>();
168
        }
169
        for(auto &e : expressions) {
170
            auto expr = e.second.expression;
171
            if(!expr) continue;
172
            for(auto &dep : expr->getIdentifiers()) {
173
                if(!dep.second)
174
                    continue;
175
                const ObjectIdentifier &var = dep.first;
176
                for(auto &vdep : var.getDep(true)) {
177
                    auto obj = vdep.first;
178
                    auto objName = obj->getFullName() + ".";
179
                    for(auto &propName : vdep.second) {
180
                        std::string key = objName + propName;
181
                        auto &propDeps = pimpl->propMap[key];
182
                        if(propDeps.empty()) {
183
                            //NOLINTBEGIN
184
                            if(!propName.empty()) {
185
                                pimpl->conns.emplace_back(obj->signalChanged.connect(std::bind(
186
                                            &PropertyExpressionEngine::slotChangedProperty,this,sp::_1,sp::_2)));
187
                            }
188
                            else {
189
                                pimpl->conns.emplace_back(obj->signalChanged.connect(std::bind(
190
                                            &PropertyExpressionEngine::slotChangedObject,this,sp::_1,sp::_2)));
191
                            }
192
                            //NOLINTEND
193
                        }
194
                        propDeps.push_back(e.first);
195
                    }
196
                }
197
            }
198
        }
199
    }
200

201
    PropertyExpressionContainer::hasSetValue();
202
}
203

204
void PropertyExpressionEngine::updateHiddenReference(const std::string &key) {
205
    if(!pimpl)
206
        return;
207
    auto it = pimpl->propMap.find(key);
208
    if(it == pimpl->propMap.end())
209
        return;
210
    for(auto &var : it->second) {
211
        auto it = expressions.find(var);
212
        if(it == expressions.end() || it->second.busy)
213
            continue;
214
        Property *myProp = var.getProperty();
215
        if(!myProp)
216
            continue;
217
        Base::StateLocker guard(it->second.busy);
218
        App::any value;
219
        try {
220
            value = it->second.expression->getValueAsAny();
221
            if(!isAnyEqual(value, myProp->getPathValue(var)))
222
                myProp->setPathValue(var, value);
223
        }catch(Base::Exception &e) {
224
            e.ReportException();
225
            FC_ERR("Failed to evaluate property binding "
226
                    << myProp->getFullName() << " on change of " << key);
227
        }catch(std::bad_cast &) {
228
            FC_ERR("Invalid type '" << value.type().name()
229
                    << "' in property binding " << myProp->getFullName()
230
                    << " on change of " << key);
231
        }catch(std::exception &e) {
232
            FC_ERR(e.what());
233
            FC_ERR("Failed to evaluate property binding "
234
                    << myProp->getFullName() << " on change of " << key);
235
        }
236
    }
237
}
238

239
void PropertyExpressionEngine::slotChangedObject(const App::DocumentObject &obj, const App::Property &) {
240
    updateHiddenReference(obj.getFullName());
241
}
242

243
void PropertyExpressionEngine::slotChangedProperty(const App::DocumentObject &, const App::Property &prop) {
244
    updateHiddenReference(prop.getFullName());
245
}
246

247
void PropertyExpressionEngine::Paste(const Property &from)
248
{
249
    const PropertyExpressionEngine &fromee = dynamic_cast<const PropertyExpressionEngine&>(from);
250

251
    AtomicPropertyChange signaller(*this);
252

253
    expressions.clear();
254
    for(auto &e : fromee.expressions) {
255
        ExpressionInfo info;
256
        if (e.second.expression)
257
            info.expression = std::shared_ptr<Expression>(e.second.expression->copy());
258
        expressions[e.first] = info;
259
        expressionChanged(e.first);
260
    }
261
    validator = fromee.validator;
262
    signaller.tryInvoke();
263
}
264

265
void PropertyExpressionEngine::Save(Base::Writer &writer) const
266
{
267
    writer.Stream() << writer.ind() << "<ExpressionEngine count=\"" <<  expressions.size();
268
    if(PropertyExpressionContainer::_XLinks.empty()) {
269
        writer.Stream() << "\">" << std::endl;
270
        writer.incInd();
271
    } else {
272
        writer.Stream() << R"(" xlink="1">)" << std::endl;
273
        writer.incInd();
274
        PropertyExpressionContainer::Save(writer);
275
    }
276
    for (const auto & it : expressions) {
277
        std::string expression, comment;
278
        if (it.second.expression) {
279
            expression = it.second.expression->toString(true);
280
            comment = it.second.expression->comment;
281
        }
282

283
        writer.Stream() << writer.ind() << "<Expression path=\""
284
            << Property::encodeAttribute(it.first.toString()) <<"\" expression=\""
285
            << Property::encodeAttribute(expression) << "\"";
286
        if (!comment.empty())
287
            writer.Stream() << " comment=\""
288
                << Property::encodeAttribute(comment) << "\"";
289
        writer.Stream() << "/>" << std::endl;
290
    }
291
    writer.decInd();
292
    writer.Stream() << writer.ind() << "</ExpressionEngine>" << std::endl;
293
}
294

295
void PropertyExpressionEngine::Restore(Base::XMLReader &reader)
296
{
297
    reader.readElement("ExpressionEngine");
298
    int count = reader.getAttributeAsFloat("count");
299

300
    if(reader.hasAttribute("xlink") && reader.getAttributeAsInteger("xlink"))
301
        PropertyExpressionContainer::Restore(reader);
302

303
    restoredExpressions = std::make_unique<std::vector<RestoredExpression>>();
304
    restoredExpressions->reserve(count);
305
    for (int i = 0; i < count; ++i) {
306

307
        reader.readElement("Expression");
308
        restoredExpressions->emplace_back();
309
        auto &info = restoredExpressions->back();
310
        info.path = reader.getAttribute("path");
311
        info.expr = reader.getAttribute("expression");
312
        if(reader.hasAttribute("comment"))
313
            info.comment = reader.getAttribute("comment");
314
    }
315

316
    reader.readEndElement("ExpressionEngine");
317
}
318

319
/**
320
 * @brief Update graph structure with given path and expression.
321
 * @param path Path
322
 * @param expression Expression to query for dependencies
323
 * @param nodes Map with nodes of graph, including dependencies of 'expression'
324
 * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies.
325
 * @param edges Edges in graph
326
 */
327

328
void PropertyExpressionEngine::buildGraphStructures(const ObjectIdentifier & path,
329
                                                    const std::shared_ptr<Expression> expression,
330
                                                    boost::unordered_map<ObjectIdentifier, int> & nodes,
331
                                                    boost::unordered_map<int, ObjectIdentifier> & revNodes,
332
                                                    std::vector<Edge> & edges) const
333
{
334
    /* Insert target property into nodes structure */
335
    if (nodes.find(path) == nodes.end()) {
336
        int s = nodes.size();
337

338
        revNodes[s] = path;
339
        nodes[path] = s;
340
    }
341
    else {
342
        revNodes[nodes[path]] = path;
343
    }
344

345
    /* Insert dependencies into nodes structure */
346
    ExpressionDeps deps;
347
    if (expression)
348
        deps = expression->getDeps();
349

350
    for(auto &dep : deps) {
351
        for(auto &info : dep.second) {
352
            if(info.first.empty())
353
                continue;
354
            for(auto &oid : info.second) {
355
                ObjectIdentifier cPath(oid.canonicalPath());
356
                if (nodes.find(cPath) == nodes.end()) {
357
                    int s = nodes.size();
358
                    nodes[cPath] = s;
359
                }
360
                edges.emplace_back(nodes[path], nodes[cPath]);
361
            }
362
        }
363
    }
364
}
365

366
/**
367
 * @brief Create a canonical object identifier of the given object \a p.
368
 * @param p ObjectIndentifier
369
 * @return New ObjectIdentifier
370
 */
371

372
ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier &p) const
373
{
374
    DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
375

376
    // Am I owned by a DocumentObject?
377
    if (!docObj)
378
        throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject.");
379

380
    int ptype;
381
    Property * prop = p.getProperty(&ptype);
382

383
    // p pointing to a property...?
384
    if (!prop)
385
        throw Base::RuntimeError(p.resolveErrorString().c_str());
386

387
    if(ptype || prop->getContainer()!=getContainer())
388
        return p;
389

390
    // In case someone calls this with p pointing to a PropertyExpressionEngine for some reason
391
    if (prop->isDerivedFrom(PropertyExpressionEngine::classTypeId))
392
        return p;
393

394
    // Dispatch call to actual canonicalPath implementation
395
    return p.canonicalPath();
396
}
397

398
/**
399
 * @brief Number of expressions managed by this object.
400
 * @return Number of expressions.
401
 */
402

403
size_t PropertyExpressionEngine::numExpressions() const
404
{
405
    return expressions.size();
406
}
407

408
void PropertyExpressionEngine::afterRestore()
409
{
410
    DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
411
    if(restoredExpressions && docObj) {
412
        Base::FlagToggler<bool> flag(restoring);
413
        AtomicPropertyChange signaller(*this);
414

415
        PropertyExpressionContainer::afterRestore();
416
        ObjectIdentifier::DocumentMapper mapper(this->_DocMap);
417

418
        for(auto &info : *restoredExpressions) {
419
            ObjectIdentifier path = ObjectIdentifier::parse(docObj, info.path);
420
            if (!info.expr.empty()) {
421
                std::shared_ptr<Expression> expression(Expression::parse(docObj, info.expr.c_str()));
422
                if(expression)
423
                    expression->comment = std::move(info.comment);
424
                setValue(path, expression);
425
            }
426
        }
427
        signaller.tryInvoke();
428
    }
429
    restoredExpressions.reset();
430
}
431

432
void PropertyExpressionEngine::onContainerRestored() {
433
    Base::FlagToggler<bool> flag(restoring);
434
    unregisterElementReference();
435
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this);
436
    for(auto &e : expressions) {
437
        auto expr = e.second.expression;
438
        if(expr)
439
            expr->visit(v);
440
    }
441
}
442

443
/**
444
 * @brief Get expression for \a path.
445
 * @param path ObjectIndentifier to query for.
446
 * @return Expression for \a path, or empty boost::any if not found.
447
 */
448

449
const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentifier & path) const
450
{
451
    // Get a canonical path
452
    ObjectIdentifier usePath(canonicalPath(path));
453

454
    ExpressionMap::const_iterator i = expressions.find(usePath);
455

456
    if (i != expressions.end())
457
        return i->second;
458
    else
459
        return boost::any();
460
}
461

462
/**
463
 * @brief Set expression with optional comment for \a path.
464
 * @param path Path to update
465
 * @param expr New expression
466
 * @param comment Optional comment.
467
 */
468

469
void PropertyExpressionEngine::setValue(const ObjectIdentifier & path, std::shared_ptr<Expression> expr)
470
{
471
    ObjectIdentifier usePath(canonicalPath(path));
472
    const Property * prop = usePath.getProperty();
473

474
    // Try to access value; it should trigger an exception if it is not supported, or if the path is invalid
475
    prop->getPathValue(usePath);
476

477
    // Check if the current expression equals the new one and do nothing if so to reduce unneeded computations
478
    ExpressionMap::iterator it = expressions.find(usePath);
479
    if(it != expressions.end()
480
            && (expr == it->second.expression ||
481
                (expr && it->second.expression
482
                 && expr->isSame(*it->second.expression))))
483
    {
484
        return;
485
    }
486

487
    if (expr) {
488
        std::string error = validateExpression(usePath, expr);
489
        if (!error.empty())
490
            throw Base::RuntimeError(error.c_str());
491
        AtomicPropertyChange signaller(*this);
492
        expressions[usePath] = ExpressionInfo(expr);
493
        expressionChanged(usePath);
494
        signaller.tryInvoke();
495
    } else if (it != expressions.end()) {
496
        AtomicPropertyChange signaller(*this);
497
        expressions.erase(it);
498
        expressionChanged(usePath);
499
        signaller.tryInvoke();
500
    }
501
}
502

503
/**
504
 * @brief The cycle_detector struct is used by the boost graph routines to detect cycles in the graph.
505
 */
506

507
struct cycle_detector : public boost::dfs_visitor<> {
508
    cycle_detector( bool& has_cycle, int & src)
509
      : _has_cycle(has_cycle), _src(src) { }
510

511
    template <class Edge, class Graph>
512
    void back_edge(Edge e, Graph&g) {
513
      _has_cycle = true;
514
      _src = source(e, g);
515
    }
516

517
  protected:
518
    bool& _has_cycle;
519
    int & _src;
520
};
521

522
/**
523
 * @brief Build a graph of all expressions in \a exprs.
524
 * @param exprs Expressions to use in graph
525
 * @param revNodes Map from int[nodeid] to ObjectIndentifer.
526
 * @param g Graph to update. May contain additional nodes than in revNodes, because of outside dependencies.
527
 */
528

529
void PropertyExpressionEngine::buildGraph(const ExpressionMap & exprs,
530
                    boost::unordered_map<int, ObjectIdentifier> & revNodes,
531
                    DiGraph & g, ExecuteOption option) const
532
{
533
    boost::unordered_map<ObjectIdentifier, int> nodes;
534
    std::vector<Edge> edges;
535

536
    // Build data structure for graph
537
    for (const auto & expr : exprs) {
538
        if(option!=ExecuteAll) {
539
            auto prop = expr.first.getProperty();
540
            if(!prop)
541
                throw Base::RuntimeError("Path does not resolve to a property.");
542
            bool is_output = prop->testStatus(App::Property::Output)||(prop->getType()&App::Prop_Output);
543
            if((is_output && option==ExecuteNonOutput) || (!is_output && option==ExecuteOutput))
544
                continue;
545
            if(option == ExecuteOnRestore
546
                    && !prop->testStatus(Property::Transient)
547
                    && !(prop->getType() & Prop_Transient)
548
                    && !prop->testStatus(Property::EvalOnRestore))
549
                continue;
550
        }
551
        buildGraphStructures(expr.first, expr.second.expression, nodes, revNodes, edges);
552
    }
553

554
    // Create graph
555
    g = DiGraph(nodes.size());
556

557
    // Add edges to graph
558
    for (const auto & edge : edges)
559
        add_edge(edge.first, edge.second, g);
560

561
    // Check for cycles
562
    bool has_cycle = false;
563
    int src = -1;
564
    cycle_detector vis(has_cycle, src);
565
    depth_first_search(g, visitor(vis));
566

567
    if (has_cycle) {
568
        std::string s =  revNodes[src].toString() + " reference creates a cyclic dependency.";
569

570
        throw Base::RuntimeError(s.c_str());
571
    }
572
}
573

574
/**
575
 * The code below builds a graph for all expressions in the engine, and
576
 * finds any circular dependencies. It also computes the internal evaluation
577
 * order, in case properties depends on each other.
578
 */
579

580
std::vector<App::ObjectIdentifier> PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option)
581
{
582
    std::vector<App::ObjectIdentifier> evaluationOrder;
583
    boost::unordered_map<int, ObjectIdentifier> revNodes;
584
    DiGraph g;
585

586
    buildGraph(expressions, revNodes, g, option);
587

588
    /* Compute evaluation order for expressions */
589
    std::vector<int> c;
590
    topological_sort(g, std::back_inserter(c));
591

592
    for (int i : c) {
593
        // we return the evaluation order for our properties, not the dependencies
594
        // the topo sort will contain node ids for both our props and their deps
595
        if (revNodes.find(i) != revNodes.end())
596
            evaluationOrder.push_back(revNodes[i]);
597
    }
598

599
    return evaluationOrder;
600
}
601

602
/**
603
 * @brief Compute and update values of all registered expressions.
604
 * @return StdReturn on success.
605
 */
606

607
DocumentObjectExecReturn *App::PropertyExpressionEngine::execute(ExecuteOption option, bool *touched)
608
{
609
    DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(getContainer());
610

611
    if (!docObj)
612
        throw Base::RuntimeError("PropertyExpressionEngine must be owned by a DocumentObject.");
613

614
    if (running)
615
        return DocumentObject::StdReturn;
616

617
    if(option == ExecuteOnRestore) {
618
        bool found = false;
619
        for(auto &e : expressions) {
620
            auto prop = e.first.getProperty();
621
            if(!prop)
622
                continue;
623
            if(prop->testStatus(App::Property::Transient)
624
                    || (prop->getType()&App::Prop_Transient)
625
                    || prop->testStatus(App::Property::EvalOnRestore))
626
            {
627
                found = true;
628
                break;
629
            }
630
        }
631
        if(!found)
632
            return DocumentObject::StdReturn;
633
    }
634

635
    /* Resetter class, to ensure that the "running" variable gets set to false, even if
636
     * an exception is thrown.
637
     */
638

639
    class resetter {
640
    public:
641
        explicit resetter(bool & b) : _b(b) { _b = true; }
642
        ~resetter() { _b = false; }
643

644
    private:
645
        bool & _b;
646
    };
647

648
    resetter r(running);
649

650
    // Compute evaluation order
651
    std::vector<App::ObjectIdentifier> evaluationOrder = computeEvaluationOrder(option);
652
    std::vector<ObjectIdentifier>::const_iterator it = evaluationOrder.begin();
653

654
#ifdef FC_PROPERTYEXPRESSIONENGINE_LOG
655
    std::clog << "Computing expressions for " << getName() << std::endl;
656
#endif
657

658
    /* Evaluate the expressions, and update properties */
659
    for (;it != evaluationOrder.end();++it) {
660

661
        // Get property to update
662
        Property * prop = it->getProperty();
663

664
        if (!prop)
665
            throw Base::RuntimeError("Path does not resolve to a property.");
666

667
        DocumentObject* parent = freecad_dynamic_cast<DocumentObject>(prop->getContainer());
668

669
        /* Make sure property belongs to the same container as this PropertyExpressionEngine */
670
        if (parent != docObj)
671
            throw Base::RuntimeError("Invalid property owner.");
672

673
        /* Set value of property */
674
        App::any value;
675
        try {
676
            // Evaluate expression
677
            std::shared_ptr<App::Expression> expression = expressions[*it].expression;
678
            if (expression) {
679
                value = expression->getValueAsAny();
680

681
                // Enable value comparison for all expression bindings to reduce
682
                // unnecessary touch and recompute.
683
                //
684
                // This optimization is necessary for some hidden reference to
685
                // work because it introduce dependency loop. The repeativtive
686
                // recompute can be stopped if the expression evaluates the same
687
                // value.
688
                //
689
                // In the future, we can generalize the optimization to all
690
                // property modification, i.e. do not touch unless value change
691
                //
692
                // if (option == ExecuteOnRestore && prop->testStatus(Property::EvalOnRestore))
693
                {
694
                    if (isAnyEqual(value, prop->getPathValue(*it)))
695
                        continue;
696
                    if (touched)
697
                        *touched = true;
698
                }
699
                prop->setPathValue(*it, value);
700
            }
701
        }catch(Base::Exception &e) {
702
            std::ostringstream ss;
703
            ss << e.what() << std::endl << "in property binding '" << prop->getFullName() << "'";
704
            e.setMessage(ss.str());
705
            throw;
706
        }catch(std::bad_cast &) {
707
            std::ostringstream ss;
708
            ss << "Invalid type '" << value.type().name() << "'";
709
            ss << "\nin property binding '" << prop->getFullName() << "'";
710
            throw Base::TypeError(ss.str().c_str());
711
        }catch(std::exception &e) {
712
            std::ostringstream ss;
713
            ss << e.what() << "\nin property binding '" << prop->getFullName() << "'";
714
            throw Base::RuntimeError(ss.str().c_str());
715
        }
716
    }
717
    return DocumentObject::StdReturn;
718
}
719

720
/**
721
 * @brief Find paths to document object.
722
 * @param obj Document object
723
 * @param paths Object identifier
724
 */
725

726
void PropertyExpressionEngine::getPathsToDocumentObject(DocumentObject* obj,
727
                                 std::vector<App::ObjectIdentifier> & paths) const
728
{
729
    DocumentObject * owner = freecad_dynamic_cast<DocumentObject>(getContainer());
730

731
    if (!owner || owner==obj)
732
        return;
733

734
    for(auto &v : expressions) {
735
        if (!v.second.expression)
736
            continue;
737
        const auto &deps = v.second.expression->getDeps();
738
        auto it = deps.find(obj);
739
        if(it==deps.end())
740
            continue;
741
        for(auto &dep : it->second)
742
            paths.insert(paths.end(),dep.second.begin(),dep.second.end());
743
    }
744
}
745

746
/**
747
 * @brief Determine whether any dependencies of any of the registered expressions have been touched.
748
 * @return True if at least on dependency has been touched.
749
 */
750

751
bool PropertyExpressionEngine::depsAreTouched() const
752
{
753
    for(auto &v : _Deps) {
754
        // v.second indicates if it is a hidden reference
755
        if(!v.second && v.first->isTouched())
756
            return true;
757
    }
758
    return false;
759
}
760

761
/**
762
 * @brief Validate the given path and expression.
763
 * @param path Object Identifier for expression.
764
 * @param expr Expression tree.
765
 * @return Empty string on success, error message on failure.
766
 */
767

768
std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier &path, std::shared_ptr<const Expression> expr) const
769
{
770
    std::string error;
771
    ObjectIdentifier usePath(canonicalPath(path));
772

773
    if (validator) {
774
        error = validator(usePath, expr);
775
        if (!error.empty())
776
            return error;
777
    }
778

779
    // Get document object
780
    DocumentObject * pathDocObj = usePath.getDocumentObject();
781
    assert(pathDocObj);
782

783
    auto inList = pathDocObj->getInListEx(true);
784
    for(auto &v : expr->getDepObjects()) {
785
        auto docObj = v.first;
786
        if(!v.second && inList.count(docObj)) {
787
            std::stringstream ss;
788
            ss << "cyclic reference to " << docObj->getFullName();
789
            return ss.str();
790
        }
791
    }
792

793
    // Check for internal document object dependencies
794

795
    // Copy current expressions
796
    ExpressionMap newExpressions = expressions;
797

798
    // Add expression in question
799
    std::shared_ptr<Expression> exprClone(expr->copy());
800
    newExpressions[usePath].expression = exprClone;
801

802
    // Build graph; an exception will be thrown if it is not a DAG
803
    try {
804
        boost::unordered_map<int, ObjectIdentifier> revNodes;
805
        DiGraph g;
806

807
        buildGraph(newExpressions, revNodes, g);
808
    }
809
    catch (const Base::Exception & e) {
810
        return e.what();
811
    }
812

813
    return {};
814
}
815

816
/**
817
 * @brief Rename paths based on \a paths.
818
 * @param paths Map with current and new object identifier.
819
 */
820

821
void PropertyExpressionEngine::renameExpressions(const std::map<ObjectIdentifier, ObjectIdentifier> & paths)
822
{
823
    ExpressionMap newExpressions;
824
    std::map<ObjectIdentifier, ObjectIdentifier> canonicalPaths;
825

826
    /* ensure input map uses canonical paths */
827
    for (const auto & path : paths)
828
        canonicalPaths[canonicalPath(path.first)] = path.second;
829

830
    for (ExpressionMap::const_iterator i = expressions.begin(); i != expressions.end(); ++i) {
831
        std::map<ObjectIdentifier, ObjectIdentifier>::const_iterator j = canonicalPaths.find(i->first);
832

833
        // Renamed now?
834
        if (j != canonicalPaths.end())
835
            newExpressions[j->second] = i->second;
836
        else
837
            newExpressions[i->first] = i->second;
838
    }
839

840
    aboutToSetValue();
841
    expressions = newExpressions;
842
    for (ExpressionMap::const_iterator i = expressions.begin(); i != expressions.end(); ++i)
843
        expressionChanged(i->first);
844

845
    hasSetValue();
846
}
847

848
/**
849
 * @brief Rename object identifiers in the registered expressions.
850
 * @param paths Map with current and new object identifiers.
851
 */
852

853
void PropertyExpressionEngine::renameObjectIdentifiers(const std::map<ObjectIdentifier, ObjectIdentifier> &paths)
854
{
855
    for (const auto & it : expressions) {
856
        RenameObjectIdentifierExpressionVisitor<PropertyExpressionEngine> v(*this, paths, it.first);
857
        it.second.expression->visit(v);
858
    }
859
}
860

861
PyObject *PropertyExpressionEngine::getPyObject()
862
{
863
    Py::List list;
864
    for (const auto & it : expressions) {
865
        Py::Tuple tuple(2);
866
        tuple.setItem(0, Py::String(it.first.toString()));
867
        auto expr = it.second.expression;
868
        tuple.setItem(1, expr ? Py::String(expr->toString()) : Py::None());
869
        list.append(tuple);
870
    }
871
    return Py::new_reference_to(list);
872
}
873

874
void PropertyExpressionEngine::setPyObject(PyObject *)
875
{
876
    throw Base::RuntimeError("Property is read-only");
877
}
878

879
/* The policy implemented in the following function is to auto erase binding in
880
 * case linked object is gone. I think it is better to cause error and get
881
 * user's attention
882
 *
883
void PropertyExpressionEngine::breakLink(App::DocumentObject *obj, bool clear) {
884
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
885
    if(!owner)
886
        return;
887
    if(_Deps.count(obj)==0 && (!clear || obj!=owner || _Deps.empty()))
888
        return;
889
    AtomicPropertyChange signaler(*this);
890
    for(auto it=expressions.begin(),itNext=it;it!=expressions.end();it=itNext) {
891
        ++itNext;
892
        const auto &deps = it->second.expression->getDepObjects();
893
        if(clear) {
894
            // here means we are breaking all expression, except those that has
895
            // no depdenecy or self dependency
896
            if(deps.empty() || (deps.size()==1 && *deps.begin()==owner))
897
                continue;
898
        }else if(!deps.count(obj))
899
            continue;
900
        auto path = it->first;
901
        expressions.erase(it);
902
        expressionChanged(path);
903
    }
904
}
905
*/
906

907
bool PropertyExpressionEngine::adjustLink(const std::set<DocumentObject*> &inList) {
908
    auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
909
    if(!owner)
910
        return false;
911
    bool found = false;
912
    for(auto &v : _Deps) {
913
        if(inList.count(v.first)) {
914
            found = true;
915
            break;
916
        }
917
    }
918
    if(!found)
919
        return false;
920

921
    AtomicPropertyChange signaler(*this);
922
    for(auto &v : expressions) {
923
        try {
924
            if(v.second.expression && v.second.expression->adjustLinks(inList))
925
                expressionChanged(v.first);
926
        }catch(Base::Exception &e) {
927
            std::ostringstream ss;
928
            ss << "Failed to adjust link for " << owner->getFullName() << " in expression "
929
                << v.second.expression->toString() << ": " << e.what();
930
            throw Base::RuntimeError(ss.str());
931
        }
932
    }
933
    return true;
934
}
935

936
void PropertyExpressionEngine::updateElementReference(DocumentObject *feature, bool reverse, bool notify)
937
{
938
    (void)notify;
939
    if(!feature)
940
        unregisterElementReference();
941
    UpdateElementReferenceExpressionVisitor<PropertyExpressionEngine> v(*this,feature,reverse);
942
    for(auto &e : expressions) {
943
        if (e.second.expression) {
944
            e.second.expression->visit(v);
945
            if (v.changed()) {
946
                expressionChanged(e.first);
947
                v.reset();
948
            }
949
        }
950
    }
951
    if(feature && v.changed()) {
952
        auto owner = dynamic_cast<App::DocumentObject*>(getContainer());
953
        if(owner)
954
            owner->onUpdateElementReference(this);
955
    }
956
}
957

958
bool PropertyExpressionEngine::referenceChanged() const {
959
    return false;
960
}
961

962
Property *PropertyExpressionEngine::CopyOnImportExternal(
963
        const std::map<std::string,std::string> &nameMap) const
964
{
965
    std::unique_ptr<PropertyExpressionEngine>  engine;
966
    for(auto it=expressions.begin();it!=expressions.end();++it) {
967
#ifdef BOOST_NO_CXX11_SMART_PTR
968
        std::shared_ptr<Expression> expr(it->second.expression->importSubNames(nameMap).release());
969
#else
970
        std::shared_ptr<Expression> expr(it->second.expression->importSubNames(nameMap));
971
#endif
972
        if(!expr && !engine)
973
            continue;
974
        if(!engine) {
975
            engine = std::make_unique<PropertyExpressionEngine>();
976
            for(auto it2=expressions.begin();it2!=it;++it2) {
977
                engine->expressions[it2->first] = ExpressionInfo(
978
                        std::shared_ptr<Expression>(it2->second.expression->copy()));
979
            }
980
        }else if(!expr)
981
            expr = it->second.expression;
982
        engine->expressions[it->first] = ExpressionInfo(expr);
983
    }
984
    if(!engine)
985
        return nullptr;
986
    engine->validator = validator;
987
    return engine.release();
988
}
989

990
Property *PropertyExpressionEngine::CopyOnLabelChange(App::DocumentObject *obj,
991
        const std::string &ref, const char *newLabel) const
992
{
993
    std::unique_ptr<PropertyExpressionEngine>  engine;
994
    for(auto it=expressions.begin();it!=expressions.end();++it) {
995
#ifdef BOOST_NO_CXX11_SMART_PTR
996
        std::shared_ptr<Expression> expr(it->second.expression->updateLabelReference(obj,ref,newLabel).release());
997
#else
998
        std::shared_ptr<Expression> expr(it->second.expression->updateLabelReference(obj,ref,newLabel));
999
#endif
1000
        if(!expr && !engine)
1001
            continue;
1002
        if(!engine) {
1003
            engine = std::make_unique<PropertyExpressionEngine>();
1004
            for(auto it2=expressions.begin();it2!=it;++it2) {
1005
                ExpressionInfo info;
1006
                if (it2->second.expression)
1007
                    info.expression = std::shared_ptr<Expression>(it2->second.expression->copy());
1008
                engine->expressions[it2->first] = info;
1009
            }
1010
        }else if(!expr)
1011
            expr = it->second.expression;
1012
        engine->expressions[it->first] = ExpressionInfo(expr);
1013
    }
1014
    if(!engine)
1015
        return nullptr;
1016
    engine->validator = validator;
1017
    return engine.release();
1018
}
1019

1020
Property *PropertyExpressionEngine::CopyOnLinkReplace(const App::DocumentObject *parent,
1021
        App::DocumentObject *oldObj, App::DocumentObject *newObj) const
1022
{
1023
    std::unique_ptr<PropertyExpressionEngine>  engine;
1024
    for(auto it=expressions.begin();it!=expressions.end();++it) {
1025
#ifdef BOOST_NO_CXX11_SMART_PTR
1026
        std::shared_ptr<Expression> expr(
1027
                it->second.expression->replaceObject(parent,oldObj,newObj).release());
1028
#else
1029
        std::shared_ptr<Expression> expr(
1030
                it->second.expression->replaceObject(parent,oldObj,newObj));
1031
#endif
1032
        if(!expr && !engine)
1033
            continue;
1034
        if(!engine) {
1035
            engine = std::make_unique<PropertyExpressionEngine>();
1036
            for(auto it2=expressions.begin();it2!=it;++it2) {
1037
                ExpressionInfo info;
1038
                if (it2->second.expression)
1039
                    info.expression = std::shared_ptr<Expression>(it2->second.expression->copy());
1040
                engine->expressions[it2->first] = info;
1041
            }
1042
        }else if(!expr)
1043
            expr = it->second.expression;
1044
        engine->expressions[it->first] = ExpressionInfo(expr);
1045
    }
1046
    if(!engine)
1047
        return nullptr;
1048
    engine->validator = validator;
1049
    return engine.release();
1050
}
1051

1052
std::map<App::ObjectIdentifier, const App::Expression*>
1053
PropertyExpressionEngine::getExpressions() const
1054
{
1055
    std::map<App::ObjectIdentifier, const Expression*> res;
1056
    for(auto &v : expressions)
1057
        res[v.first] = v.second.expression.get();
1058
    return res;
1059
}
1060

1061
void PropertyExpressionEngine::setExpressions(
1062
        std::map<App::ObjectIdentifier, App::ExpressionPtr> &&exprs)
1063
{
1064
    AtomicPropertyChange signaller(*this);
1065
#ifdef BOOST_NO_CXX11_SMART_PTR
1066
    for(auto &v : exprs)
1067
        setValue(v.first,std::shared_ptr<Expression>(v.second.release()));
1068
#else
1069
    for(auto &v : exprs)
1070
        setValue(v.first,std::move(v.second));
1071
#endif
1072
}
1073

1074
void PropertyExpressionEngine::onRelabeledDocument(const App::Document &doc)
1075
{
1076
    RelabelDocumentExpressionVisitor v(doc);
1077
    for(auto &e : expressions) {
1078
        if (e.second.expression)
1079
            e.second.expression->visit(v);
1080
    }
1081
}
1082

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.