FreeCAD

Форк
0
/
ObjectIdentifier.cpp 
2009 строк · 62.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
#ifndef _PreComp_
26
# include <cassert>
27
#endif
28

29
#include <boost/algorithm/string/predicate.hpp>
30

31
#include <App/DocumentObjectPy.h>
32
#include <Base/GeometryPyCXX.h>
33
#include <Base/Tools.h>
34
#include <Base/Interpreter.h>
35
#include <Base/QuantityPy.h>
36
#include <Base/Console.h>
37
#include <Base/Reader.h>
38
#include <CXX/Objects.hxx>
39

40
#include "ObjectIdentifier.h"
41
#include "Application.h"
42
#include "Document.h"
43
#include "ExpressionParser.h"
44
#include "Link.h"
45
#include "Property.h"
46

47

48
FC_LOG_LEVEL_INIT("Expression",true,true)
49

50
using namespace App;
51
using namespace Base;
52

53
// Path class
54

55
/**
56
 * @brief Quote input string according to quoting rules for an expression: because " and ' are
57
 * used to designate inch and foot units, strings are quoted as <<string>>.
58
 *
59
 * @param input
60
 * @return
61
 */
62

63
std::string App::quote(const std::string &input, bool toPython)
64
{
65
    std::stringstream output;
66

67
    std::string::const_iterator cur = input.begin();
68
    std::string::const_iterator end = input.end();
69

70
    output << (toPython?"'":"<<");
71
    while (cur != end) {
72
        switch (*cur) {
73
        case '\t':
74
            output << "\\t";
75
            break;
76
        case '\n':
77
            output << "\\n";
78
            break;
79
        case '\r':
80
            output << "\\r";
81
            break;
82
        case '\\':
83
            output << "\\\\";
84
            break;
85
        case '\'':
86
            output << "\\'";
87
            break;
88
        case '"':
89
            output << "\\\"";
90
            break;
91
        case '>':
92
            output << (toPython?">":"\\>");
93
            break;
94
        default:
95
            output << *cur;
96
        }
97
        ++cur;
98
    }
99
    output << (toPython?"'":">>");
100

101
    return output.str();
102
}
103

104

105
/**
106
 * @brief Construct an ObjectIdentifier object, given an owner and a single-value property.
107
 * @param _owner Owner of property.
108
 * @param property Name of property.
109
 */
110

111
ObjectIdentifier::ObjectIdentifier(const App::PropertyContainer * _owner,
112
        const std::string & property, int index)
113
    : owner(nullptr)
114
    , documentNameSet(false)
115
    , documentObjectNameSet(false)
116
    , localProperty(false)
117
    , _hash(0)
118
{
119
    if (_owner) {
120
        const DocumentObject * docObj = freecad_dynamic_cast<const DocumentObject>(_owner);
121
        if (!docObj)
122
            FC_THROWM(Base::RuntimeError,"Property must be owned by a document object.");
123
        owner = const_cast<DocumentObject*>(docObj);
124

125
        if (!property.empty()) {
126
            setDocumentObjectName(docObj);
127
        }
128
    }
129
    if (!property.empty()) {
130
        addComponent(SimpleComponent(property));
131
        if(index!=INT_MAX)
132
            addComponent(ArrayComponent(index));
133
    }
134
}
135

136
ObjectIdentifier::ObjectIdentifier(const App::PropertyContainer * _owner, bool localProperty)
137
    : owner(nullptr)
138
    , documentNameSet(false)
139
    , documentObjectNameSet(false)
140
    , localProperty(localProperty)
141
    , _hash(0)
142
{
143
    if (_owner) {
144
        const DocumentObject * docObj = freecad_dynamic_cast<const DocumentObject>(_owner);
145
        if (!docObj)
146
            FC_THROWM(Base::RuntimeError,"Property must be owned by a document object.");
147
        owner = const_cast<DocumentObject*>(docObj);
148
    }
149
}
150

151
/**
152
 * @brief Construct an ObjectIdentifier object given a property. The property is assumed to be single-valued.
153
 * @param prop Property to construct object identifier for.
154
 */
155

156
ObjectIdentifier::ObjectIdentifier(const Property &prop, int index)
157
    : owner(nullptr)
158
    , documentNameSet(false)
159
    , documentObjectNameSet(false)
160
    , localProperty(false)
161
    , _hash(0)
162
{
163
    DocumentObject * docObj = freecad_dynamic_cast<DocumentObject>(prop.getContainer());
164

165
    if (!docObj)
166
        FC_THROWM(Base::TypeError, "Property must be owned by a document object.");
167
    if (!prop.hasName())
168
        FC_THROWM(Base::RuntimeError, "Property must have a name.");
169

170
    owner = const_cast<DocumentObject*>(docObj);
171

172
    setDocumentObjectName(docObj);
173

174
    addComponent(SimpleComponent(String(prop.getName())));
175
    if(index!=INT_MAX)
176
        addComponent(ArrayComponent(index));
177
}
178

179
/**
180
 * @brief Get the name of the property.
181
 * @return Name
182
 */
183

184
std::string App::ObjectIdentifier::getPropertyName() const
185
{
186
    ResolveResults result(*this);
187

188
    assert(result.propertyIndex >=0 && static_cast<std::size_t>(result.propertyIndex) < components.size());
189

190
    return components[result.propertyIndex].getName();
191
}
192

193
/**
194
 * @brief Get Component at given index \a i.
195
 * @param i: Index to get
196
 * @param idx: optional return of adjusted component index
197
 * @return A component.
198
 */
199

200
const App::ObjectIdentifier::Component &App::ObjectIdentifier::getPropertyComponent(int i, int *idx) const
201
{
202
    ResolveResults result(*this);
203

204
    i += result.propertyIndex;
205
    if (i < 0 || i >= static_cast<int>(components.size()))
206
        FC_THROWM(Base::ValueError, "Invalid property component index");
207

208
    if (idx)
209
        *idx = i;
210

211
    return components[i];
212
}
213

214
void App::ObjectIdentifier::setComponent(int idx, Component &&comp)
215
{
216
    if (idx < 0 || idx >= static_cast<int>(components.size()))
217
        FC_THROWM(Base::ValueError, "Invalid component index");
218
    components[idx] = std::move(comp);
219
    _cache.clear();
220
}
221

222
void App::ObjectIdentifier::setComponent(int idx, const Component &comp)
223
{
224
    setComponent(idx, Component(comp));
225
}
226

227
std::vector<ObjectIdentifier::Component> ObjectIdentifier::getPropertyComponents() const {
228
    if(components.size()<=1 || documentObjectName.getString().empty())
229
        return components;
230
    ResolveResults result(*this);
231
    if(result.propertyIndex==0)
232
        return components;
233
    std::vector<ObjectIdentifier::Component> res;
234
    res.insert(res.end(),components.begin()+result.propertyIndex,components.end());
235
    return res;
236
}
237

238
/**
239
 * @brief Compare object identifier with \a other.
240
 * @param other Other object identifier.
241
 * @return true if they are equal.
242
 */
243

244
bool ObjectIdentifier::operator ==(const ObjectIdentifier &other) const
245
{
246
    return owner==other.owner && toString() == other.toString();
247
}
248

249
/**
250
 * @brief Compare object identifier with \a other.
251
 * @param other Other object identifier
252
 * @return true if they differ from each other.
253
 */
254

255
bool ObjectIdentifier::operator !=(const ObjectIdentifier &other) const
256
{
257
    return !(operator==)(other);
258
}
259

260
/**
261
 * @brief Compare object identifier with other.
262
 * @param other Other object identifier.
263
 * @return true if this object is less than the other.
264
 */
265

266
bool ObjectIdentifier::operator <(const ObjectIdentifier &other) const
267
{
268
    if(owner < other.owner)
269
        return true;
270
    if(owner > other.owner)
271
        return false;
272
    return toString() < other.toString();
273
}
274

275
/**
276
 * @brief Return number of components.
277
 * @return Number of components in this identifier.
278
 */
279

280
int ObjectIdentifier::numComponents() const
281
{
282
    return components.size();
283
}
284

285
/**
286
 * @brief Compute number of sub components, i.e excluding the property.
287
 * @return Number of components.
288
 */
289

290
int ObjectIdentifier::numSubComponents() const
291
{
292
    ResolveResults result(*this);
293

294
    return components.size() - result.propertyIndex;
295
}
296

297
bool ObjectIdentifier::verify(const App::Property &prop, bool silent) const {
298
    ResolveResults result(*this);
299
    if(components.size() - result.propertyIndex != 1) {
300
        if(silent)
301
            return false;
302
        FC_THROWM(Base::ValueError,"Invalid property path: single component expected");
303
    }
304
    if(!components[result.propertyIndex].isSimple()) {
305
        if(silent)
306
            return false;
307
        FC_THROWM(Base::ValueError,"Invalid property path: simple component expected");
308
    }
309
    const std::string &name = components[result.propertyIndex].getName();
310
    CellAddress addr;
311
    bool isAddress = addr.parseAbsoluteAddress(name.c_str());
312
    if((isAddress && addr.toString(CellAddress::Cell::ShowRowColumn) != prop.getName()) ||
313
       (!isAddress && name!=prop.getName()))
314
    {
315
        if(silent)
316
            return false;
317
        FC_THROWM(Base::ValueError,"Invalid property path: name mismatch");
318
    }
319
    return true;
320
}
321

322
/**
323
 * @brief Create a string representation of this object identifier.
324
 *
325
 * An identifier is written as document#documentobject.property.subproperty1...subpropertyN
326
 * document# may be dropped; it is assumed to be within owner's document. If documentobject is dropped,
327
 * the property is assumed to be owned by the owner specified in the object identifiers constructor.
328
 *
329
 * @return A string
330
 */
331

332
const std::string &ObjectIdentifier::toString() const
333
{
334
    if(!_cache.empty() || !owner)
335
        return _cache;
336

337
    std::ostringstream s;
338
    ResolveResults result(*this);
339

340
    if(result.propertyIndex >= (int)components.size())
341
        return _cache;
342

343
    if(localProperty ||
344
       (result.resolvedProperty &&
345
        result.resolvedDocumentObject==owner &&
346
        components.size()>1 &&
347
        components[1].isSimple() &&
348
        result.propertyIndex==0))
349
    {
350
        s << '.';
351
    }else if (documentNameSet && !documentName.getString().empty()) {
352
        if(documentObjectNameSet && !documentObjectName.getString().empty())
353
            s << documentName.toString() << "#"
354
              << documentObjectName.toString() << '.';
355
        else if(!result.resolvedDocumentObjectName.getString().empty())
356
            s << documentName.toString() << "#"
357
              << result.resolvedDocumentObjectName.toString() << '.';
358
    } else if (documentObjectNameSet && !documentObjectName.getString().empty()) {
359
        s << documentObjectName.toString() << '.';
360
    } else if (result.propertyIndex > 0) {
361
        components[0].toString(s);
362
        s << '.';
363
    }
364

365
    if(!subObjectName.getString().empty())
366
        s << subObjectName.toString() << '.';
367

368
    s << components[result.propertyIndex].getName();
369
    getSubPathStr(s,result);
370
    const_cast<ObjectIdentifier*>(this)->_cache = s.str();
371
    return _cache;
372
}
373

374
std::string ObjectIdentifier::toPersistentString() const {
375

376
    if(!owner)
377
        return {};
378

379
    std::ostringstream s;
380
    ResolveResults result(*this);
381

382
    if(result.propertyIndex >= (int)components.size())
383
        return {};
384

385
    if(localProperty ||
386
       (result.resolvedProperty &&
387
        result.resolvedDocumentObject==owner &&
388
        components.size()>1 &&
389
        components[1].isSimple() &&
390
        result.propertyIndex==0))
391
    {
392
        s << '.';
393
    }else if(result.resolvedDocumentObject &&
394
        result.resolvedDocumentObject!=owner &&
395
        result.resolvedDocumentObject->isExporting())
396
    {
397
        s << result.resolvedDocumentObject->getExportName(true);
398
        if(documentObjectName.isRealString())
399
            s << '@';
400
        s << '.';
401
    } else if (documentNameSet && !documentName.getString().empty()) {
402
        if(documentObjectNameSet && !documentObjectName.getString().empty())
403
            s << documentName.toString() << "#"
404
                << documentObjectName.toString() << '.';
405
        else if(!result.resolvedDocumentObjectName.getString().empty())
406
            s << documentName.toString() << "#"
407
                << result.resolvedDocumentObjectName.toString() << '.';
408
    } else if (documentObjectNameSet && !documentObjectName.getString().empty()) {
409
        s << documentObjectName.toString() << '.';
410
    } else if (result.propertyIndex > 0) {
411
        components[0].toString(s);
412
        s << '.';
413
    }
414

415
    if(!subObjectName.getString().empty()) {
416
        const char *subname = subObjectName.getString().c_str();
417
        std::string exportName;
418
        s << String(PropertyLinkBase::exportSubName(exportName,
419
                        result.resolvedDocumentObject,subname),true).toString() << '.';
420
    }
421

422
    s << components[result.propertyIndex].getName();
423
    getSubPathStr(s,result);
424
    return s.str();
425
}
426

427
std::size_t ObjectIdentifier::hash() const
428
{
429
    if(_hash && !_cache.empty())
430
        return _hash;
431
    const_cast<ObjectIdentifier*>(this)->_hash = boost::hash_value(toString());
432
    return _hash;
433
}
434

435
bool ObjectIdentifier::replaceObject(ObjectIdentifier &res, const App::DocumentObject *parent,
436
            App::DocumentObject *oldObj, App::DocumentObject *newObj) const
437
{
438
    ResolveResults result(*this);
439

440
    if(!result.resolvedDocumentObject)
441
        return false;
442

443
    auto r = PropertyLinkBase::tryReplaceLink(owner, result.resolvedDocumentObject,
444
            parent, oldObj, newObj, subObjectName.getString().c_str());
445

446
    if(!r.first)
447
        return false;
448

449
    res = *this;
450
    if(r.first != result.resolvedDocumentObject) {
451
        if(r.first->getDocument()!=owner->getDocument()) {
452
            auto doc = r.first->getDocument();
453
            bool useLabel = res.documentName.isRealString();
454
            const char *name = useLabel?doc->Label.getValue():doc->getName();
455
            res.setDocumentName(String(name, useLabel), true);
456
        }
457
        if(documentObjectName.isRealString())
458
            res.documentObjectName = String(r.first->Label.getValue(),true);
459
        else
460
            res.documentObjectName = String(r.first->getNameInDocument(),false,true);
461
    }
462
    res.subObjectName = String(r.second,true);
463
    res._cache.clear();
464
    res.shadowSub.first.clear();
465
    res.shadowSub.second.clear();
466
    return true;
467
}
468

469
/**
470
 * @brief Escape toString representation so it is suitable for being embedded in a python command.
471
 * @return Escaped string.
472
 */
473

474
std::string ObjectIdentifier::toEscapedString() const
475
{
476
    return Base::Tools::escapedUnicodeFromUtf8(toString().c_str());
477
}
478

479
bool ObjectIdentifier::updateLabelReference(
480
        App::DocumentObject *obj, const std::string &ref, const char *newLabel)
481
{
482
    if(!owner)
483
        return false;
484

485
    ResolveResults result(*this);
486

487
    if(!subObjectName.getString().empty() && result.resolvedDocumentObject) {
488
        std::string sub = PropertyLinkBase::updateLabelReference(
489
                result.resolvedDocumentObject, subObjectName.getString().c_str(), obj,ref,newLabel);
490
        if(!sub.empty()) {
491
            subObjectName = String(sub,true);
492
            _cache.clear();
493
            return true;
494
        }
495
    }
496

497
    if(result.resolvedDocument != obj->getDocument())
498
        return false;
499

500
    if(!documentObjectName.getString().empty()) {
501
        if(documentObjectName.isForceIdentifier())
502
            return false;
503

504
        if(!documentObjectName.isRealString() &&
505
           documentObjectName.getString()==obj->getNameInDocument())
506
            return false;
507

508
        if(documentObjectName.getString()!=obj->Label.getValue())
509
            return false;
510

511
        documentObjectName = ObjectIdentifier::String(newLabel, true);
512

513
        _cache.clear();
514
        return true;
515
    }
516

517
    if (result.resolvedDocumentObject==obj &&
518
        result.propertyIndex == 1 &&
519
        result.resolvedDocumentObjectName.isRealString() &&
520
        result.resolvedDocumentObjectName.getString()==obj->Label.getValue())
521
    {
522
        components[0].name = ObjectIdentifier::String(newLabel, true);
523
        _cache.clear();
524
        return true;
525
    }
526

527
    // If object identifier uses the label then resolving the document object will fail.
528
    // So, it must be checked if using the new label will succeed
529
    if (components.size()>1 && components[0].getName()==obj->Label.getValue()) {
530
        ObjectIdentifier id(*this);
531
        id.components[0].name.str = newLabel;
532

533
        ResolveResults result(id);
534

535
        if (result.propertyIndex == 1 && result.resolvedDocumentObject == obj) {
536
            components[0].name = id.components[0].name;
537
            _cache.clear();
538
            return true;
539
        }
540
    }
541

542
    return false;
543
}
544

545
bool ObjectIdentifier::relabeledDocument(ExpressionVisitor &v,
546
        const std::string &oldLabel, const std::string &newLabel)
547
{
548
    if (documentNameSet && documentName.isRealString() && documentName.getString()==oldLabel) {
549
        v.aboutToChange();
550
        documentName = String(newLabel,true);
551
        _cache.clear();
552
        return true;
553
    }
554
    return false;
555
}
556

557
/**
558
 * @brief Get sub field part of a property as a string.
559
 * @return String representation of path.
560
 */
561

562
void ObjectIdentifier::getSubPathStr(std::ostream &s, const ResolveResults &result, bool toPython) const
563
{
564
    std::vector<Component>::const_iterator i = components.begin() + result.propertyIndex + 1;
565
    while (i != components.end()) {
566
        if(i->isSimple())
567
            s << '.';
568
        i->toString(s,toPython);
569
        ++i;
570
    }
571
}
572

573
std::string ObjectIdentifier::getSubPathStr(bool toPython) const {
574
    std::ostringstream ss;
575
    getSubPathStr(ss,ResolveResults(*this),toPython);
576
    return ss.str();
577
}
578

579

580
/**
581
 * @brief Construct a Component part
582
 * @param _name Name of component
583
 * @param _type Type; simple, array, range or map
584
 * @param _begin Array index or beginning of a Range, or INT_MAX for other type.
585
 * @param _end ending of a Range, or INT_MAX for other type.
586
 */
587

588
ObjectIdentifier::Component::Component(const String &_name,
589
        ObjectIdentifier::Component::typeEnum _type, int _begin, int _end, int _step)
590
    : name(_name)
591
    , type(_type)
592
    , begin(_begin)
593
    , end(_end)
594
    , step(_step)
595
{
596
}
597

598
ObjectIdentifier::Component::Component(String &&_name,
599
        ObjectIdentifier::Component::typeEnum _type, int _begin, int _end, int _step)
600
    : name(std::move(_name))
601
    , type(_type)
602
    , begin(_begin)
603
    , end(_end)
604
    , step(_step)
605
{
606
}
607

608

609
size_t ObjectIdentifier::Component::getIndex(size_t count) const {
610
    if(begin>=0) {
611
        if(begin<(int)count)
612
            return begin;
613
    }else {
614
        int idx = begin + (int)count;
615
        if(idx >= 0)
616
            return idx;
617
    }
618
    FC_THROWM(Base::IndexError, "Array out of bound: " << begin << ", " << count);
619
}
620

621
Py::Object ObjectIdentifier::Component::get(const Py::Object &pyobj) const {
622
    Py::Object res;
623
    if(isSimple()) {
624
        if(!pyobj.hasAttr(getName()))
625
            FC_THROWM(Base::AttributeError, "No attribute named '" << getName() << "'");
626
        res = pyobj.getAttr(getName());
627
    } else if(isArray()) {
628
        if(pyobj.isMapping())
629
            res = Py::Mapping(pyobj).getItem(Py::Int(begin));
630
        else
631
            res = Py::Sequence(pyobj).getItem(begin);
632
    }else if(isMap())
633
        res = Py::Mapping(pyobj).getItem(getName());
634
    else {
635
        assert(isRange());
636
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
637
                                    end!=INT_MAX?Py::Int(end).ptr():nullptr,
638
                                    step!=1?Py::Int(step).ptr():nullptr),true);
639
        PyObject *r = PyObject_GetItem(pyobj.ptr(),slice.ptr());
640
        if(!r)
641
            Base::PyException::ThrowException();
642
        res = Py::asObject(r);
643
    }
644
    if(!res.ptr())
645
        Base::PyException::ThrowException();
646
    if(PyModule_Check(res.ptr()) && !ExpressionParser::isModuleImported(res.ptr()))
647
        FC_THROWM(Base::RuntimeError, "Module '" << getName() << "' access denied.");
648
    return res;
649
}
650

651
void ObjectIdentifier::Component::set(Py::Object &pyobj, const Py::Object &value) const {
652
    if(isSimple()) {
653
        if(PyObject_SetAttrString(*pyobj, getName().c_str(), *value ) == -1)
654
            Base::PyException::ThrowException();
655
    } else if(isArray()) {
656
        if(pyobj.isMapping())
657
            Py::Mapping(pyobj).setItem(Py::Int(begin),value);
658
        else
659
            Py::Sequence(pyobj).setItem(begin,value);
660
    }else if(isMap())
661
        Py::Mapping(pyobj).setItem(getName(),value);
662
    else {
663
        assert(isRange());
664
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
665
                                    end!=INT_MAX?Py::Int(end).ptr():nullptr,
666
                                    step!=1?Py::Int(step).ptr():nullptr),true);
667
        if(PyObject_SetItem(pyobj.ptr(),slice.ptr(),value.ptr())<0)
668
            Base::PyException::ThrowException();
669
    }
670
}
671

672
void ObjectIdentifier::Component::del(Py::Object &pyobj) const {
673
    if(isSimple())
674
        pyobj.delAttr(getName());
675
    else if(isArray()) {
676
        if(pyobj.isMapping())
677
            Py::Mapping(pyobj).delItem(Py::Int(begin));
678
        else
679
            PySequence_DelItem(pyobj.ptr(),begin);
680
    } else if(isMap())
681
        Py::Mapping(pyobj).delItem(getName());
682
    else {
683
        assert(isRange());
684
        Py::Object slice(PySlice_New(Py::Int(begin).ptr(),
685
                                    end!=INT_MAX?Py::Int(end).ptr():nullptr,
686
                                    step!=1?Py::Int(step).ptr():nullptr),true);
687
        if(PyObject_DelItem(pyobj.ptr(),slice.ptr())<0)
688
            Base::PyException::ThrowException();
689
    }
690
}
691

692
/**
693
 * @brief Create a simple component part with the given name
694
 * @param _component Name of component.
695
 * @return A new Component object.
696
 */
697

698
ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(const char *_component)
699
{
700
    return Component(String(_component));
701
}
702

703
/**
704
 * @brief Create a simple component part with the given name
705
 * @param _component Name of component.
706
 * @return A new Component object.
707
 */
708

709
ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(const ObjectIdentifier::String &_component)
710
{
711
    return Component(_component);
712
}
713

714
ObjectIdentifier::Component ObjectIdentifier::Component::SimpleComponent(ObjectIdentifier::String &&_component)
715
{
716
    return Component(std::move(_component));
717
}
718

719
/**
720
 * @brief Create an array component with given name and index.
721
 * @param _component Name of component
722
 * @param _index Index of component
723
 * @return A new Component object.
724
 */
725

726
ObjectIdentifier::Component ObjectIdentifier::Component::ArrayComponent(int _index)
727
{
728
    return Component(String(), Component::ARRAY, _index);
729
}
730

731
/**
732
 * @brief Create a map component with given name and key.
733
 * @param _component Name of component
734
 * @param _key Key of component
735
 * @return A new Component object.
736
 */
737

738
ObjectIdentifier::Component ObjectIdentifier::Component::MapComponent(const String & _key)
739
{
740
    return Component(_key, Component::MAP);
741
}
742

743
ObjectIdentifier::Component ObjectIdentifier::Component::MapComponent(String &&_key)
744
{
745
    return Component(std::move(_key), Component::MAP);
746
}
747

748

749
/**
750
 * @brief Create a range component with given begin and end.
751
 * @param _begin beginning index of the range
752
 * @param _end ending index of the range
753
 * @return A new Component object.
754
 */
755

756
ObjectIdentifier::Component ObjectIdentifier::Component::RangeComponent(int _begin, int _end, int _step)
757
{
758
    return Component(String(), Component::RANGE, _begin, _end, _step);
759
}
760

761
/**
762
 * @brief Comparison operator for Component objects.
763
 * @param other The object we want to compare to.
764
 * @return true if they are equal, false if not.
765
 */
766

767
bool ObjectIdentifier::Component::operator ==(const ObjectIdentifier::Component &other) const
768
{
769
    if (type != other.type)
770
        return false;
771

772
    switch (type) {
773
    case SIMPLE:
774
    case MAP:
775
        return name == other.name;
776
    case ARRAY:
777
        return begin == other.begin;
778
    case RANGE:
779
        return begin == other.begin && end == other.end && step==other.step;
780
    default:
781
        assert(0);
782
        return false;
783
    }
784
}
785

786
/**
787
 * @brief Create a string representation of a component.
788
 * @return A string representing the component.
789
 */
790

791
void ObjectIdentifier::Component::toString(std::ostream &ss, bool toPython) const
792
{
793
    switch (type) {
794
    case Component::SIMPLE:
795
        ss << name.getString();
796
        break;
797
    case Component::MAP:
798
        ss << "[" << name.toString(toPython) << "]";
799
        break;
800
    case Component::ARRAY:
801
        ss << "[" << begin << "]";
802
        break;
803
    case Component::RANGE:
804
        ss << '[';
805
        if(begin!=INT_MAX)
806
            ss << begin;
807
        ss << ':';
808
        if(end!=INT_MAX)
809
            ss << end;
810
        if(step!=1)
811
            ss << ':' << step;
812
        ss << ']';
813
        break;
814
    default:
815
        assert(0);
816
    }
817
}
818

819
enum ResolveFlags {
820
    ResolveByIdentifier,
821
    ResolveByLabel,
822
    ResolveAmbiguous,
823
};
824

825
/**
826
 * @brief Search for the document object given by name in doc.
827
 *
828
 * Name might be the internal name or a label. In any case, it must uniquely define
829
 * the document object.
830
 *
831
 * @param doc Document to search
832
 * @param name Name to search for.
833
 * @return Pointer to document object if a unique pointer is found, 0 otherwise.
834
 */
835

836
App::DocumentObject * ObjectIdentifier::getDocumentObject(const App::Document * doc,
837
        const String & name, std::bitset<32> &flags)
838
{
839
    DocumentObject * objectById = nullptr;
840
    DocumentObject * objectByLabel = nullptr;
841

842
    if(!name.isRealString()) {
843
        // No object found with matching label, try using name directly
844
        objectById = doc->getObject(static_cast<const char*>(name));
845

846
        if (objectById) {
847
            flags.set(ResolveByIdentifier);
848
            return objectById;
849
        }
850
        if(name.isForceIdentifier())
851
            return nullptr;
852
    }
853

854
    std::vector<DocumentObject*> docObjects = doc->getObjects();
855
    for (auto docObject : docObjects) {
856
        if (strcmp(docObject->Label.getValue(), static_cast<const char*>(name)) == 0) {
857
            // Found object with matching label
858
            if (objectByLabel)  {
859
                FC_WARN("duplicate object label " << doc->getName() << '#' << static_cast<const char*>(name));
860
                return nullptr;
861
            }
862
            objectByLabel = docObject;
863
        }
864
    }
865

866
    if (!objectByLabel && !objectById) // Not found at all
867
        return nullptr;
868
    else if (!objectByLabel) { // Found by name
869
        flags.set(ResolveByIdentifier);
870
        return objectById;
871
    }
872
    else if (!objectById) { // Found by label
873
        flags.set(ResolveByLabel);
874
        return objectByLabel;
875
    }
876
    else if (objectByLabel == objectById) { // Found by both name and label, same object
877
        flags.set(ResolveByIdentifier);
878
        flags.set(ResolveByLabel);
879
        return objectByLabel;
880
    }
881
    else {
882
        flags.set(ResolveAmbiguous);
883
        return nullptr; // Found by both name and label, two different objects
884
    }
885
}
886

887
/**
888
 * @brief Resolve the object identifier to a concrete document, documentobject, and property.
889
 *
890
 * This method is a helper method that fills out data in the given ResolveResults object.
891
 *
892
 */
893

894
void ObjectIdentifier::resolve(ResolveResults &results) const
895
{
896
    if(!owner)
897
        return;
898

899
    bool docAmbiguous = false;
900

901
    /* Document name specified? */
902
    if (!documentName.getString().empty()) {
903
        results.resolvedDocument = getDocument(documentName,&docAmbiguous);
904
        results.resolvedDocumentName = documentName;
905
    }
906
    else {
907
        results.resolvedDocument = owner->getDocument();
908
        results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
909
    }
910

911
    results.subObjectName = subObjectName;
912
    results.propertyName = "";
913
    results.propertyIndex = 0;
914

915
    // Assume document name and object name from owner if not found
916
    if (!results.resolvedDocument) {
917
        if (!documentName.getString().empty()) {
918
            if(docAmbiguous)
919
                results.flags.set(ResolveAmbiguous);
920
            return;
921
        }
922

923
        results.resolvedDocument = owner->getDocument();
924
        if (!results.resolvedDocument)
925
            return;
926
    }
927

928
    results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
929

930
    /* Document object name specified? */
931
    if (!documentObjectName.getString().empty()) {
932
        results.resolvedDocumentObjectName = documentObjectName;
933
        results.resolvedDocumentObject = getDocumentObject(
934
                results.resolvedDocument, documentObjectName, results.flags);
935
        if (!results.resolvedDocumentObject)
936
            return;
937

938
        if (components.empty())
939
            return;
940

941
        results.propertyName = components[ 0 ].name.getString();
942
        results.propertyIndex = 0;
943
        results.getProperty( *this );
944
    }
945
    else {
946
        /* Document object name not specified, resolve from path */
947

948
        /* One component? */
949
        if (components.size() == 1 || (components.size()>1 && !components[0].isSimple())) {
950
            /* Yes -- then this must be a property, so we get the document object's name from the owner */
951
            results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
952
            results.resolvedDocumentObject = owner;
953
            results.propertyName = components[0].name.getString();
954
            results.propertyIndex = 0;
955
            results.getProperty(*this);
956
        }
957
        else if (components.size() >= 2) {
958
            /* No --  */
959
            if (!components[0].isSimple())
960
                return;
961

962
            results.resolvedDocumentObject = getDocumentObject(
963
                    results.resolvedDocument, components[0].name, results.flags);
964

965
            /* Possible to resolve component to a document object? */
966
            if (results.resolvedDocumentObject) {
967
                /* Yes */
968
                results.resolvedDocumentObjectName = String {
969
                        components[0].name.getString(),
970
                        false,
971
                        results.flags.test(ResolveByIdentifier)};
972
                results.propertyName = components[1].name.getString();
973
                results.propertyIndex = 1;
974
                results.getProperty(*this);
975
                if(!results.resolvedProperty) {
976
                    // If the second component is not a property name, try to
977
                    // interpret the first component as the property name.
978
                    DocumentObject *sobj = nullptr;
979
                    results.resolvedProperty = resolveProperty(
980
                            owner,
981
                            components[0].name.toString().c_str(),
982
                            sobj,
983
                            results.propertyType);
984
                    if(results.resolvedProperty) {
985
                        results.propertyName = components[0].name.getString();
986
                        results.resolvedDocument = owner->getDocument();
987
                        results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
988
                        results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
989
                        results.resolvedDocumentObject = owner;
990
                        results.resolvedSubObject = sobj;
991
                        results.propertyIndex = 0;
992
                    }
993
                }
994
            }
995
            else if (documentName.getString().empty()) {
996
                /* No, assume component is a property, and get document object's name from owner */
997
                results.resolvedDocument = owner->getDocument();
998
                results.resolvedDocumentName = String(results.resolvedDocument->getName(), false, true);
999
                results.resolvedDocumentObjectName = String(owner->getNameInDocument(), false, true);
1000
                results.resolvedDocumentObject = owner->getDocument()->getObject(owner->getNameInDocument());
1001
                results.propertyIndex = 0;
1002
                results.propertyName = components[results.propertyIndex].name.getString();
1003
                results.getProperty(*this);
1004
            }
1005
        }
1006
        else
1007
            return;
1008
    }
1009
}
1010

1011
/**
1012
 * @brief Find a document with the given name.
1013
 * @param name Name of document
1014
 * @return Pointer to document, or 0 if it is not found or not uniquely defined by name.
1015
 */
1016

1017
Document * ObjectIdentifier::getDocument(String name, bool *ambiguous) const
1018
{
1019
    if (name.getString().empty())
1020
        name = getDocumentName();
1021

1022
    App::Document * docById = nullptr;
1023

1024
    if(!name.isRealString()) {
1025
        docById = App::GetApplication().getDocument(name.toString().c_str());
1026
        if (name.isForceIdentifier())
1027
            return docById;
1028
    }
1029

1030
    App::Document * docByLabel = nullptr;
1031
    const std::vector<App::Document*> docs = App::GetApplication().getDocuments();
1032

1033
    for (auto doc : docs) {
1034
        if (doc->Label.getValue() == name.getString()) {
1035
            /* Multiple hits for same label? */
1036
            if (docByLabel) {
1037
                if(ambiguous) *ambiguous = true;
1038
                return nullptr;
1039
            }
1040
            docByLabel = doc;
1041
        }
1042
    }
1043

1044
    /* Not found on id? */
1045
    if (!docById)
1046
        return docByLabel; // Either not found at all, or on label
1047
    else {
1048
        /* Not found on label? */
1049
        if (!docByLabel) /* Then return doc by id */
1050
            return docById;
1051

1052
        /* docByLabel and docById could be equal; that is ok */
1053
        if (docByLabel == docById)
1054
            return docById;
1055
        if (ambiguous)
1056
            *ambiguous = true;
1057
        return nullptr;
1058
    }
1059
}
1060

1061
/**
1062
 * @brief Get the document object for the object identifier.
1063
 * @return Pointer to document object, or 0 if not found or uniquely defined.
1064
 */
1065

1066
DocumentObject *ObjectIdentifier::getDocumentObject() const
1067
{
1068
    const App::Document * doc = getDocument();
1069
    std::bitset<32> dummy;
1070

1071
    if (!doc)
1072
        return nullptr;
1073

1074
    ResolveResults result(*this);
1075

1076
    return getDocumentObject(doc, result.resolvedDocumentObjectName, dummy);
1077
}
1078

1079

1080
enum PseudoPropertyType {
1081
    PseudoNone,
1082
    PseudoShape,
1083
    PseudoPlacement,
1084
    PseudoMatrix,
1085
    PseudoLinkPlacement,
1086
    PseudoLinkMatrix,
1087
    PseudoSelf,
1088
    PseudoApp,
1089
    PseudoPart,
1090
    PseudoRegex,
1091
    PseudoBuiltins,
1092
    PseudoMath,
1093
    PseudoCollections,
1094
    PseudoGui,
1095
    PseudoCadquery,
1096
};
1097

1098
void ObjectIdentifier::getDepLabels(std::vector<std::string> &labels) const {
1099
    getDepLabels(ResolveResults(*this),labels);
1100
}
1101

1102
void ObjectIdentifier::getDepLabels(
1103
        const ResolveResults &result, std::vector<std::string> &labels) const
1104
{
1105
    if(!documentObjectName.getString().empty()) {
1106
        if(documentObjectName.isRealString())
1107
            labels.push_back(documentObjectName.getString());
1108
    } else if(result.propertyIndex == 1)
1109
        labels.push_back(components[0].name.getString());
1110
    if(!subObjectName.getString().empty())
1111
        PropertyLinkBase::getLabelReferences(labels,subObjectName.getString().c_str());
1112
}
1113

1114
ObjectIdentifier::Dependencies
1115
ObjectIdentifier::getDep(bool needProps, std::vector<std::string> *labels) const
1116
{
1117
    Dependencies deps;
1118
    getDep(deps,needProps,labels);
1119
    return deps;
1120
}
1121

1122
void ObjectIdentifier::getDep(Dependencies &deps, bool needProps, std::vector<std::string> *labels) const
1123
{
1124
    ResolveResults result(*this);
1125
    if(labels)
1126
        getDepLabels(result,*labels);
1127

1128
    if(!result.resolvedDocumentObject)
1129
       return;
1130

1131
    if(!needProps) {
1132
        deps[result.resolvedDocumentObject];
1133
        return;
1134
    }
1135

1136
    if(!result.resolvedProperty) {
1137
        if(!result.propertyName.empty())
1138
            deps[result.resolvedDocumentObject].insert(result.propertyName);
1139
        return;
1140
    }
1141

1142
    Base::PyGILStateLocker lock;
1143
    try {
1144
        access(result, nullptr, &deps);
1145
    }
1146
    catch (Py::Exception& e) {
1147
        e.clear();
1148
    }
1149
    catch (Base::Exception &) {
1150
    }
1151
}
1152

1153
/**
1154
 * @brief Get components as a string list.
1155
 * @return List of strings.
1156
 */
1157

1158
std::vector<std::string> ObjectIdentifier::getStringList() const
1159
{
1160
    std::vector<std::string> l;
1161
    ResolveResults result(*this);
1162

1163
    if(!result.resolvedProperty || result.resolvedDocumentObject != owner) {
1164
        if (documentNameSet)
1165
            l.push_back(documentName.toString());
1166

1167
        if (documentObjectNameSet)
1168
            l.push_back(documentObjectName.toString());
1169
    }
1170
    if(!subObjectName.getString().empty()) {
1171
        l.back() += subObjectName.toString();
1172
    }
1173
    std::vector<Component>::const_iterator i = components.begin();
1174
    while (i != components.end()) {
1175
        std::ostringstream ss;
1176
        i->toString(ss);
1177
        l.push_back(ss.str());
1178
        ++i;
1179
    }
1180

1181
    return l;
1182
}
1183

1184
/**
1185
 * @brief Construct the simplest possible object identifier relative to another.
1186
 * @param other The other object identifier.
1187
 * @return A new simplified object identifier.
1188
 */
1189

1190
ObjectIdentifier ObjectIdentifier::relativeTo(const ObjectIdentifier &other) const
1191
{
1192
    ObjectIdentifier result(other.getOwner());
1193
    ResolveResults thisresult(*this);
1194
    ResolveResults otherresult(other);
1195

1196
    if (otherresult.resolvedDocument != thisresult.resolvedDocument)
1197
        result.setDocumentName(std::move(thisresult.resolvedDocumentName), true);
1198
    if (otherresult.resolvedDocumentObject != thisresult.resolvedDocumentObject)
1199
        result.setDocumentObjectName(
1200
                std::move(thisresult.resolvedDocumentObjectName), true, String(subObjectName));
1201

1202
    for (std::size_t i = thisresult.propertyIndex; i < components.size(); ++i)
1203
        result << components[i];
1204

1205
    return result;
1206
}
1207

1208
/**
1209
 * @brief Parse a string to create an object identifier.
1210
 *
1211
 * This method throws an exception if the string is invalid.
1212
 *
1213
 * @param docObj Document object that will own this object identifier.
1214
 * @param str String to parse
1215
 * @return A new object identifier.
1216
 */
1217

1218
ObjectIdentifier ObjectIdentifier::parse(const DocumentObject *docObj, const std::string &str)
1219
{
1220
    std::unique_ptr<Expression> expr(ExpressionParser::parse(docObj, str.c_str()));
1221
    VariableExpression * v = freecad_dynamic_cast<VariableExpression>(expr.get());
1222

1223
    if (v)
1224
        return v->getPath();
1225
    else
1226
        FC_THROWM(Base::RuntimeError,"Invalid property specification.");
1227
}
1228

1229
std::string ObjectIdentifier::resolveErrorString() const
1230
{
1231
    ResolveResults result(*this);
1232

1233
    return result.resolveErrorString();
1234
}
1235

1236
/**
1237
 * @brief << operator, used to add a component to the object identifier.
1238
 * @param value Component object
1239
 * @return Reference to itself.
1240
 */
1241

1242
ObjectIdentifier &ObjectIdentifier::operator <<(const ObjectIdentifier::Component &value)
1243
{
1244
    components.push_back(value);
1245
    _cache.clear();
1246
    return *this;
1247
}
1248

1249
ObjectIdentifier &ObjectIdentifier::operator <<(ObjectIdentifier::Component &&value)
1250
{
1251
    components.push_back(std::move(value));
1252
    _cache.clear();
1253
    return *this;
1254
}
1255

1256

1257
/**
1258
 * @brief Get pointer to property pointed to by this object identifier.
1259
 * @return Point to property if it is uniquely defined, or 0 otherwise.
1260
 */
1261

1262
Property *ObjectIdentifier::getProperty(int *ptype) const
1263
{
1264
    ResolveResults result(*this);
1265
    if(ptype)
1266
        *ptype = result.propertyType;
1267
    return result.resolvedProperty;
1268
}
1269

1270
Property *ObjectIdentifier::resolveProperty(const App::DocumentObject *obj,
1271
        const char *propertyName, App::DocumentObject *&sobj, int &ptype) const
1272
{
1273
    if(obj && !subObjectName.getString().empty()) {
1274
        sobj = obj->getSubObject(subObjectName.toString().c_str());
1275
        obj = sobj;
1276
    }
1277
    if(!obj)
1278
        return nullptr;
1279

1280
    static std::unordered_map<const char*,int,CStringHasher,CStringHasher> _props = {
1281
        {"_shape",PseudoShape},
1282
        {"_pla",PseudoPlacement},
1283
        {"_matrix",PseudoMatrix},
1284
        {"__pla",PseudoLinkPlacement},
1285
        {"__matrix",PseudoLinkMatrix},
1286
        {"_self",PseudoSelf},
1287
        {"_app",PseudoApp},
1288
        {"_part",PseudoPart},
1289
        {"_re",PseudoRegex},
1290
        {"_py", PseudoBuiltins},
1291
        {"_math", PseudoMath},
1292
        {"_coll", PseudoCollections},
1293
        {"_gui",PseudoGui},
1294
        {"_cq",PseudoCadquery},
1295
    };
1296
    auto it = _props.find(propertyName);
1297
    if(it == _props.end())
1298
        ptype = PseudoNone;
1299
    else {
1300
        ptype = it->second;
1301
        if(ptype != PseudoShape &&
1302
           !subObjectName.getString().empty() &&
1303
           !boost::ends_with(subObjectName.getString(),"."))
1304
        {
1305
            return nullptr;
1306
        }
1307
        return &const_cast<App::DocumentObject*>(obj)->Label; //fake the property
1308
    }
1309

1310
    return obj->getPropertyByName(propertyName);
1311
}
1312

1313

1314

1315
/**
1316
 * @brief Create a canonical representation of an object identifier.
1317
 *
1318
 * The main work is actually done by the property's virtual canonicalPath(...) method,
1319
 * which is invoked by this call.
1320
 *
1321
 * @return A new object identifier.
1322
 */
1323

1324
ObjectIdentifier ObjectIdentifier::canonicalPath() const
1325
{
1326
    ObjectIdentifier res(*this);
1327
    ResolveResults result(res);
1328
    if(result.resolvedDocumentObject && result.resolvedDocumentObject!=owner) {
1329
        res.owner = result.resolvedDocumentObject;
1330
        res._cache.clear();
1331
    }
1332
    res.resolveAmbiguity(result);
1333
    if(!result.resolvedProperty || result.propertyType!=PseudoNone)
1334
        return res;
1335
    return result.resolvedProperty->canonicalPath(res);
1336
}
1337

1338
static const std::map<std::string,std::string> *_DocumentMap;
1339
ObjectIdentifier::DocumentMapper::DocumentMapper(const std::map<std::string,std::string> &map)
1340
{
1341
    assert(!_DocumentMap);
1342
    _DocumentMap = &map;
1343
}
1344

1345
ObjectIdentifier::DocumentMapper::~DocumentMapper()
1346
{
1347
    _DocumentMap = nullptr;
1348
}
1349

1350
/**
1351
 * @brief Set the document name for this object identifier.
1352
 *
1353
 * If force is true, the document name will always be included in the string representation.
1354
 *
1355
 * @param name Name of document object.
1356
 * @param force Force name to be set
1357
 */
1358

1359
void ObjectIdentifier::setDocumentName(ObjectIdentifier::String &&name, bool force)
1360
{
1361
    if(name.getString().empty())
1362
        force = false;
1363
    documentNameSet = force;
1364
    _cache.clear();
1365
    if(!name.getString().empty() && _DocumentMap) {
1366
        if(name.isRealString()) {
1367
            auto iter = _DocumentMap->find(name.toString());
1368
            if(iter!=_DocumentMap->end()) {
1369
                documentName = String(iter->second,true);
1370
                return;
1371
            }
1372
        }else{
1373
            auto iter = _DocumentMap->find(name.getString());
1374
            if(iter!=_DocumentMap->end()) {
1375
                documentName = String(iter->second,false,true);
1376
                return;
1377
            }
1378
        }
1379
    }
1380
    documentName = std::move(name);
1381
}
1382

1383
/**
1384
 * @brief Get the document name from this object identifier
1385
 *
1386
 * @return Document name as a String object.
1387
 */
1388

1389
ObjectIdentifier::String ObjectIdentifier::getDocumentName() const
1390
{
1391
    ResolveResults result(*this);
1392

1393
    return result.resolvedDocumentName;
1394
}
1395

1396
/**
1397
 * @brief Set the document object name of this object identifier.
1398
 *
1399
 * If force is true, the document object will not be resolved dynamically from the
1400
 * object identifier's components, but used as given by this method.
1401
 *
1402
 * @param name Name of document object.
1403
 * @param force Force name to be set.
1404
 */
1405

1406
void ObjectIdentifier::setDocumentObjectName(ObjectIdentifier::String &&name, bool force,
1407
        ObjectIdentifier::String &&subname, bool checkImport)
1408
{
1409
    if(checkImport) {
1410
        name.checkImport(owner);
1411
        subname.checkImport(owner,nullptr,&name);
1412
    }
1413

1414
    documentObjectName = std::move(name);
1415
    documentObjectNameSet = force;
1416
    subObjectName = std::move(subname);
1417

1418
    _cache.clear();
1419
}
1420

1421
void ObjectIdentifier::setDocumentObjectName(const App::DocumentObject *obj, bool force,
1422
        ObjectIdentifier::String &&subname, bool checkImport)
1423
{
1424
    if(!owner || !obj || !obj->isAttachedToDocument() || !obj->getDocument())
1425
        FC_THROWM(Base::RuntimeError,"invalid object");
1426

1427
    if(checkImport)
1428
        subname.checkImport(owner,obj);
1429

1430
    if(obj == owner)
1431
        force = false;
1432
    else
1433
        localProperty = false;
1434
    if(obj->getDocument() == owner->getDocument())
1435
        setDocumentName(String());
1436
    else if(!documentNameSet) {
1437
        if(obj->getDocument() == owner->getDocument())
1438
            setDocumentName(String());
1439
        else {
1440
            documentNameSet = true;
1441
            documentName = String(obj->getDocument()->getName(),false,true);
1442
        }
1443
    }else if(documentName.isRealString())
1444
        documentName = String(obj->getDocument()->Label.getStrValue(),true);
1445
    else
1446
        documentName = String(obj->getDocument()->getName(),false,true);
1447

1448
    documentObjectNameSet = force;
1449
    documentObjectName = String(obj->getNameInDocument(),false,true);
1450
    subObjectName = std::move(subname);
1451

1452
    _cache.clear();
1453
}
1454

1455

1456
/**
1457
 * @brief Get the document object name
1458
 * @return String with name of document object as resolved by object identifier.
1459
 */
1460

1461
ObjectIdentifier::String ObjectIdentifier::getDocumentObjectName() const
1462
{
1463
    ResolveResults result(*this);
1464

1465
    return result.resolvedDocumentObjectName;
1466
}
1467

1468
bool ObjectIdentifier::hasDocumentObjectName(bool forced) const {
1469
    return !documentObjectName.getString().empty() && (!forced || documentObjectNameSet);
1470
}
1471

1472
/**
1473
 * @brief Get a string representation of this object identifier.
1474
 * @return String representation.
1475
 */
1476

1477
std::string ObjectIdentifier::String::toString(bool toPython) const
1478
{
1479
    if (isRealString())
1480
        return quote(str,toPython);
1481
    else
1482
        return str;
1483
}
1484

1485
void ObjectIdentifier::String::checkImport(const App::DocumentObject *owner,
1486
        const App::DocumentObject *obj, String *objName)
1487
{
1488
    if(owner && owner->getDocument() && !str.empty() &&
1489
       ExpressionParser::ExpressionImporter::reader()) {
1490
        auto reader = ExpressionParser::ExpressionImporter::reader();
1491
        if (obj || objName) {
1492
            bool restoreLabel = false;
1493
            str = PropertyLinkBase::importSubName(*reader,str.c_str(),restoreLabel);
1494
            if (restoreLabel) {
1495
                if (!obj) {
1496
                    std::bitset<32> flags;
1497
                    obj = getDocumentObject(owner->getDocument(),*objName,flags);
1498
                    if (!obj) {
1499
                        FC_ERR("Cannot find object " << objName->toString());
1500
                    }
1501
                }
1502

1503
                if (obj) {
1504
                    PropertyLinkBase::restoreLabelReference(obj,str);
1505
                }
1506
            }
1507
        }
1508
        else if (str.back()!='@') {
1509
            str = reader->getName(str.c_str());
1510
        }
1511
        else {
1512
            str.resize(str.size()-1);
1513
            auto mapped = reader->getName(str.c_str());
1514
            auto objForMapped = owner->getDocument()->getObject(mapped);
1515
            if (!objForMapped) {
1516
                FC_ERR("Cannot find object " << str);
1517
            }
1518
            else {
1519
                isString = true;
1520
                forceIdentifier = false;
1521
                str = objForMapped->Label.getValue();
1522
            }
1523
        }
1524
    }
1525
}
1526

1527
Py::Object ObjectIdentifier::access(const ResolveResults &result,
1528
        Py::Object *value, Dependencies *deps) const
1529
{
1530
    if(!result.resolvedDocumentObject || !result.resolvedProperty ||
1531
       (!subObjectName.getString().empty() && !result.resolvedSubObject))
1532
    {
1533
        FC_THROWM(Base::RuntimeError, result.resolveErrorString()
1534
           << " in '" << toString() << "'");
1535
    }
1536

1537
    Py::Object pyobj;
1538
    int ptype = result.propertyType;
1539

1540
    // NOTE! We do not keep reference of the imported module, assuming once
1541
    // imported they'll live (because of sys.modules) till the application
1542
    // dies.
1543
#define GET_MODULE(_name) do {\
1544
        static PyObject *pymod;\
1545
        if(!pymod) {\
1546
           pymod = PyImport_ImportModule(#_name);\
1547
            if(!pymod)\
1548
                Base::PyException::ThrowException();\
1549
            else\
1550
                Py_DECREF(pymod);\
1551
        }\
1552
        pyobj = Py::Object(pymod);\
1553
    }while(0)
1554

1555
    size_t idx = result.propertyIndex+1;
1556
    switch(ptype) {
1557
    case PseudoApp:
1558
        GET_MODULE(FreeCAD);
1559
        break;
1560
    case PseudoGui:
1561
        GET_MODULE(FreeCADGui);
1562
        break;
1563
    case PseudoPart:
1564
        GET_MODULE(Part);
1565
        break;
1566
    case PseudoCadquery:
1567
        GET_MODULE(freecad.fc_cadquery);
1568
        break;
1569
    case PseudoRegex:
1570
        GET_MODULE(re);
1571
        break;
1572
    case PseudoBuiltins:
1573
        GET_MODULE(builtins);
1574
        break;
1575
    case PseudoMath:
1576
        GET_MODULE(math);
1577
        break;
1578
    case PseudoCollections:
1579
        GET_MODULE(collections);
1580
        break;
1581
    case PseudoShape: {
1582
        GET_MODULE(Part);
1583
        Py::Callable func(pyobj.getAttr("getShape"));
1584
        Py::Tuple tuple(1);
1585
        tuple.setItem(0,Py::Object(result.resolvedDocumentObject->getPyObject(),true));
1586
        if(result.subObjectName.getString().empty())
1587
            pyobj = func.apply(tuple);
1588
        else{
1589
            Py::Dict dict;
1590
            dict.setItem("subname",Py::String(result.subObjectName.getString()));
1591
            dict.setItem("needSubElement",Py::True());
1592
            pyobj = func.apply(tuple,dict);
1593
        }
1594
        break;
1595
    } default: {
1596
        Base::Matrix4D mat;
1597
        auto obj = result.resolvedDocumentObject;
1598
        switch(ptype) {
1599
        case PseudoPlacement:
1600
        case PseudoMatrix:
1601
        case PseudoLinkPlacement:
1602
        case PseudoLinkMatrix:
1603
            obj->getSubObject(result.subObjectName.getString().c_str(),nullptr,&mat);
1604
            break;
1605
        default:
1606
            break;
1607
        }
1608
        if(result.resolvedSubObject)
1609
            obj = result.resolvedSubObject;
1610
        switch(ptype) {
1611
        case PseudoPlacement:
1612
            pyobj = Py::Placement(Base::Placement(mat));
1613
            break;
1614
        case PseudoMatrix:
1615
            pyobj = Py::Matrix(mat);
1616
            break;
1617
        case PseudoLinkPlacement:
1618
        case PseudoLinkMatrix: {
1619
            auto linked = obj->getLinkedObject(true,&mat,false);
1620
            if(!linked || linked==obj) {
1621
                auto ext = obj->getExtensionByType<App::LinkBaseExtension>(true);
1622
                if(ext)
1623
                    ext->getTrueLinkedObject(true,&mat);
1624
            }
1625
            if(ptype == PseudoLinkPlacement)
1626
                pyobj = Py::Placement(Base::Placement(mat));
1627
            else
1628
                pyobj = Py::Matrix(mat);
1629
            break;
1630
        }
1631
        case PseudoSelf:
1632
            pyobj = Py::Object(obj->getPyObject(),true);
1633
            break;
1634
        default: {
1635
            // NOTE! We cannot directly call Property::getPyObject(), but
1636
            // instead, must obtain the property's python object through
1637
            // DocumentObjectPy::getAttr(). Because, PyObjectBase has internal
1638
            // attribute tracking only if we obtain attribute through
1639
            // getAttr(). Without attribute tracking, we can't do things like
1640
            //
1641
            //      obj.Placement.Base.x = 10.
1642
            //
1643
            // What happens is that the when Python interpreter calls
1644
            //
1645
            //      Base.setAttr('x', 10),
1646
            //
1647
            // PyObjectBase will lookup Base's parent, i.e. Placement, and call
1648
            //
1649
            //      Placement.setAttr('Base', Base),
1650
            //
1651
            // and in turn calls
1652
            //
1653
            //      obj.setAttr('Placement',Placement)
1654
            //
1655
            // The tracking logic is implemented in PyObjectBase::__getattro/__setattro
1656

1657
            auto container = result.resolvedProperty->getContainer();
1658
            if(container
1659
                    && container!=result.resolvedDocumentObject
1660
                    && container!=result.resolvedSubObject)
1661
            {
1662
                if(!container->isDerivedFrom(DocumentObject::getClassTypeId()))
1663
                    FC_WARN("Invalid property container");
1664
                else
1665
                    obj = static_cast<DocumentObject*>(container);
1666
            }
1667
            pyobj = Py::Object(obj->getPyObject(),true);
1668
            idx = result.propertyIndex;
1669
            break;
1670
        }}}
1671
    }
1672

1673
    auto setPropDep = [deps](DocumentObject *obj, Property *prop, const char *propName) {
1674
        if(!deps || !obj)
1675
            return;
1676
        if(prop && prop->getContainer()!=obj) {
1677
            auto linkTouched = Base::freecad_dynamic_cast<PropertyBool>(
1678
                    obj->getPropertyByName("_LinkTouched"));
1679
            if(linkTouched)
1680
                propName = linkTouched->getName();
1681
            else {
1682
                auto propOwner = Base::freecad_dynamic_cast<DocumentObject>(prop->getContainer());
1683
                if(propOwner)
1684
                    obj = propOwner;
1685
                else
1686
                    propName = nullptr;
1687
            }
1688
        }
1689
        auto &propset = (*deps)[obj];
1690
        // inserting a blank name in the propset indicates the dependency is
1691
        // on all properties of the corresponding object.
1692
        if (propset.size() != 1 || !propset.begin()->empty()) {
1693
            if (!propName) {
1694
                propset.clear();
1695
                propset.insert("");
1696
            }
1697
            else {
1698
                propset.insert(propName);
1699
            }
1700
        }
1701
        return;
1702
    };
1703

1704
    App::DocumentObject *lastObj = result.resolvedDocumentObject;
1705
    if(result.resolvedSubObject) {
1706
        setPropDep(lastObj,nullptr,nullptr);
1707
        lastObj = result.resolvedSubObject;
1708
    }
1709
    if(ptype == PseudoNone)
1710
        setPropDep(lastObj, result.resolvedProperty, result.resolvedProperty->getName());
1711
    else
1712
        setPropDep(lastObj,nullptr,nullptr);
1713
    lastObj = nullptr;
1714

1715
    if(components.empty())
1716
        return pyobj;
1717

1718
    size_t count = components.size();
1719
    if(value) --count;
1720
    assert(idx<=count);
1721

1722
    for(;idx<count;++idx)  {
1723
        if(PyObject_TypeCheck(*pyobj, &DocumentObjectPy::Type))
1724
            lastObj = static_cast<DocumentObjectPy*>(*pyobj)->getDocumentObjectPtr();
1725
        else if(lastObj) {
1726
            const char *attr = components[idx].getName().c_str();
1727
            auto prop = lastObj->getPropertyByName(attr);
1728
            setPropDep(lastObj,prop,attr);
1729
            lastObj = nullptr;
1730
        }
1731
        pyobj = components[idx].get(pyobj);
1732
    }
1733
    if(value) {
1734
        components[idx].set(pyobj,*value);
1735
        return Py::Object();
1736
    }
1737
    return pyobj;
1738
}
1739

1740
/**
1741
 * @brief Get the value of the property or field pointed to by this object identifier.
1742
 *
1743
 * All type of objects are supported. Some types are casted to FC native
1744
 * type, including: Int, Float, String, Unicode String, and Quantities. Others
1745
 * are just kept as Python object wrapped by App::any.
1746
 *
1747
 * @param pathValue: if true, calls the property's getPathValue(), which is
1748
 * necessary for Qunatities to work.
1749
 *
1750
 * @return The value of the property or field.
1751
 */
1752

1753
App::any ObjectIdentifier::getValue(bool pathValue, bool *isPseudoProperty) const
1754
{
1755
    ResolveResults rs(*this);
1756

1757
    if(isPseudoProperty) {
1758
        *isPseudoProperty = rs.propertyType!=PseudoNone;
1759
        if(rs.propertyType == PseudoSelf
1760
                && isLocalProperty()
1761
                && rs.propertyIndex+1 < (int)components.size()
1762
                && owner->getPropertyByName(components[rs.propertyIndex+1].getName().c_str()))
1763
        {
1764
            *isPseudoProperty = false;
1765
        }
1766
    }
1767

1768
    if(rs.resolvedProperty && rs.propertyType==PseudoNone && pathValue)
1769
        return rs.resolvedProperty->getPathValue(*this);
1770

1771
    Base::PyGILStateLocker lock;
1772
    try {
1773
        return pyObjectToAny(access(rs));
1774
    }catch(Py::Exception &) {
1775
        Base::PyException::ThrowException();
1776
    }
1777
    return {};
1778
}
1779

1780
Py::Object ObjectIdentifier::getPyValue(bool pathValue, bool *isPseudoProperty) const
1781
{
1782
    ResolveResults rs(*this);
1783

1784
    if(isPseudoProperty) {
1785
        *isPseudoProperty = rs.propertyType!=PseudoNone;
1786
        if(rs.propertyType == PseudoSelf
1787
                && isLocalProperty()
1788
                && rs.propertyIndex+1 < (int)components.size()
1789
                && owner->getPropertyByName(components[rs.propertyIndex+1].getName().c_str()))
1790
        {
1791
            *isPseudoProperty = false;
1792
        }
1793
    }
1794

1795
    if(rs.resolvedProperty && rs.propertyType==PseudoNone && pathValue) {
1796
        Py::Object res;
1797
        if(rs.resolvedProperty->getPyPathValue(*this,res))
1798
            return res;
1799
    }
1800

1801
    try {
1802
        return access(rs);
1803
    }catch(Py::Exception &) {
1804
        Base::PyException::ThrowException();
1805
    }
1806
    return Py::Object();
1807
}
1808

1809
/**
1810
 * @brief Set value of a property or field pointed to by this object identifier.
1811
 *
1812
 * This method uses Python to do the actual work. and a limited set of types that
1813
 * can be in the App::any variable is supported: Base::Quantity, double,
1814
 * char*, const char*, int, unsigned int, short, unsigned short, char, and unsigned char.
1815
 *
1816
 * @param value Value to set
1817
 */
1818

1819
void ObjectIdentifier::setValue(const App::any &value) const
1820
{
1821
    std::stringstream ss;
1822
    ResolveResults rs(*this);
1823
    if(rs.propertyType)
1824
        FC_THROWM(Base::RuntimeError,"Cannot set pseudo property");
1825

1826
    Base::PyGILStateLocker lock;
1827
    try {
1828
        Py::Object pyvalue = pyObjectFromAny(value);
1829
        access(rs,&pyvalue);
1830
    }catch(Py::Exception &) {
1831
        Base::PyException::ThrowException();
1832
    }
1833
}
1834

1835
const std::string &ObjectIdentifier::getSubObjectName(bool newStyle) const {
1836
    if(newStyle && !shadowSub.first.empty())
1837
        return shadowSub.first;
1838
    if(!shadowSub.second.empty())
1839
        return shadowSub.second;
1840
    return subObjectName.getString();
1841
}
1842

1843
const std::string &ObjectIdentifier::getSubObjectName() const {
1844
    return subObjectName.getString();
1845
}
1846

1847
void ObjectIdentifier::importSubNames(const ObjectIdentifier::SubNameMap &subNameMap)
1848
{
1849
    if(!owner || !owner->getDocument())
1850
        return;
1851
    ResolveResults result(*this);
1852
    auto it = subNameMap.find(std::make_pair(result.resolvedDocumentObject,std::string()));
1853
    if(it!=subNameMap.end()) {
1854
        auto obj = owner->getDocument()->getObject(it->second.c_str());
1855
        if(!obj) {
1856
            FC_ERR("Failed to find import object " << it->second << " from "
1857
                    << result.resolvedDocumentObject->getFullName());
1858
            return;
1859
        }
1860
        documentNameSet = false;
1861
        documentName.str.clear();
1862
        if(documentObjectName.isRealString())
1863
            documentObjectName.str = obj->Label.getValue();
1864
        else
1865
            documentObjectName.str = obj->getNameInDocument();
1866
        _cache.clear();
1867
    }
1868
    if(subObjectName.getString().empty())
1869
        return;
1870
    it = subNameMap.find(std::make_pair(
1871
                result.resolvedDocumentObject,subObjectName.str));
1872
    if(it==subNameMap.end())
1873
        return;
1874
    subObjectName = String(it->second,true);
1875
    _cache.clear();
1876
    shadowSub.first.clear();
1877
    shadowSub.second.clear();
1878
}
1879

1880
bool ObjectIdentifier::updateElementReference(ExpressionVisitor &v,
1881
        App::DocumentObject *feature, bool reverse)
1882
{
1883
    assert(v.getPropertyLink());
1884
    if(subObjectName.getString().empty())
1885
        return false;
1886

1887
    ResolveResults result(*this);
1888
    if(!result.resolvedSubObject)
1889
        return false;
1890
    if(v.getPropertyLink()->_updateElementReference(
1891
            feature,result.resolvedDocumentObject,subObjectName.str,shadowSub,reverse)) {
1892
        _cache.clear();
1893
        v.aboutToChange();
1894
        return true;
1895
    }
1896
    return false;
1897
}
1898

1899
bool ObjectIdentifier::adjustLinks(ExpressionVisitor &v, const std::set<App::DocumentObject *> &inList) {
1900
    ResolveResults result(*this);
1901
    if(!result.resolvedDocumentObject)
1902
        return false;
1903
    if(result.resolvedSubObject) {
1904
        PropertyLinkSub prop;
1905
        prop.setValue(result.resolvedDocumentObject, {subObjectName.getString()});
1906
        if(prop.adjustLink(inList)) {
1907
            v.aboutToChange();
1908
            documentObjectName = String(prop.getValue()->getNameInDocument(),false,true);
1909
            subObjectName = String(prop.getSubValues().front(),true);
1910
            _cache.clear();
1911
            return true;
1912
        }
1913
    }
1914
    return false;
1915
}
1916

1917
bool ObjectIdentifier::isTouched() const {
1918
    try {
1919
        ResolveResults result(*this);
1920
        if(result.resolvedProperty) {
1921
            if(result.propertyType==PseudoNone)
1922
                return result.resolvedProperty->isTouched();
1923
            else
1924
                return result.resolvedDocumentObject->isTouched();
1925
        }
1926
    }catch(...) {}
1927
    return false;
1928
}
1929

1930
void ObjectIdentifier::resolveAmbiguity() {
1931
    if(!owner || !owner->isAttachedToDocument() || isLocalProperty() ||
1932
       (documentObjectNameSet && !documentObjectName.getString().empty() &&
1933
        (documentObjectName.isRealString() || documentObjectName.isForceIdentifier())))
1934
    {
1935
        return;
1936
    }
1937

1938
    ResolveResults result(*this);
1939
    resolveAmbiguity(result);
1940
}
1941

1942
void ObjectIdentifier::resolveAmbiguity(ResolveResults &result) {
1943

1944
    if(!result.resolvedDocumentObject)
1945
        return;
1946

1947
    if(result.propertyIndex==1)
1948
        components.erase(components.begin());
1949

1950
    String subname = subObjectName;
1951
    if(result.resolvedDocumentObject == owner) {
1952
        setDocumentObjectName(owner,false,std::move(subname));
1953
    }else if(result.flags.test(ResolveByIdentifier))
1954
        setDocumentObjectName(result.resolvedDocumentObject,true,std::move(subname));
1955
    else
1956
        setDocumentObjectName(
1957
                String(result.resolvedDocumentObject->Label.getStrValue(),true,false),true,std::move(subname));
1958

1959
    if(result.resolvedDocumentObject->getDocument() == owner->getDocument())
1960
        setDocumentName(String());
1961
}
1962

1963
/** Construct and initialize a ResolveResults object, given an ObjectIdentifier instance.
1964
 *
1965
 * The constructor will invoke the ObjectIdentifier's resolve() method to initialize the object's data.
1966
 */
1967

1968
ObjectIdentifier::ResolveResults::ResolveResults(const ObjectIdentifier &oi)
1969
    : propertyType(PseudoNone)
1970
{
1971
    oi.resolve(*this);
1972
}
1973

1974
std::string ObjectIdentifier::ResolveResults::resolveErrorString() const
1975
{
1976
    std::ostringstream ss;
1977
    if (!resolvedDocument) {
1978
        if(flags.test(ResolveAmbiguous))
1979
            ss << "Ambiguous document name/label '"
1980
               << resolvedDocumentName.getString() << "'";
1981
        else
1982
            ss << "Document '" << resolvedDocumentName.toString() << "' not found";
1983
    } else if (!resolvedDocumentObject) {
1984
        if(flags.test(ResolveAmbiguous))
1985
            ss << "Ambiguous document object name '"
1986
                << resolvedDocumentObjectName.getString() << "'";
1987
        else
1988
            ss << "Document object '" << resolvedDocumentObjectName.toString()
1989
                << "' not found";
1990
    } else if (!subObjectName.getString().empty() && !resolvedSubObject) {
1991
        ss << "Sub-object '" << resolvedDocumentObjectName.getString()
1992
            << '.' << subObjectName.toString() << "' not found";
1993
    } else if (!resolvedProperty) {
1994
        if(propertyType != PseudoShape &&
1995
           !subObjectName.getString().empty() &&
1996
           !boost::ends_with(subObjectName.getString(),"."))
1997
        {
1998
            ss << "Non geometry subname reference must end with '.'";
1999
        }else
2000
            ss << "Property '" << propertyName << "' not found";
2001
    }
2002

2003
    return ss.str();
2004
}
2005

2006
void ObjectIdentifier::ResolveResults::getProperty(const ObjectIdentifier &oi) {
2007
    resolvedProperty = oi.resolveProperty(
2008
            resolvedDocumentObject,propertyName.c_str(),resolvedSubObject,propertyType);
2009
}
2010

2011

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

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

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

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