FreeCAD

Форк
0
/
Link.cpp 
2321 строка · 85.7 Кб
1
/****************************************************************************
2
 *   Copyright (c) 2017 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
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
#include <boost/property_map/property_map.hpp>
25

26
#include <boost/range.hpp>
27
#include <boost/algorithm/string/predicate.hpp>
28
#include <Base/Tools.h>
29
#include <Base/Uuid.h>
30

31
#include "Application.h"
32
#include "ElementNamingUtils.h"
33
#include "ComplexGeoDataPy.h"
34
#include "Document.h"
35
#include "DocumentObserver.h"
36
#include "GeoFeatureGroupExtension.h"
37
#include "Link.h"
38
#include "LinkBaseExtensionPy.h"
39

40
//FIXME: ISO C++11 requires at least one argument for the "..." in a variadic macro
41
#if defined(__clang__)
42
# pragma clang diagnostic push
43
# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
44
#endif
45

46
FC_LOG_LEVEL_INIT("App::Link", true,true)
47

48
using namespace App;
49
using namespace Base;
50
namespace sp = std::placeholders;
51

52
using CharRange = boost::iterator_range<const char*>;
53

54
////////////////////////////////////////////////////////////////////////
55

56
/*[[[cog
57
import LinkParams
58
LinkParams.define()
59
]]]*/
60

61
namespace {
62

63
// Auto generated code. See class document of LinkParams.
64
class LinkParamsP: public ParameterGrp::ObserverType {
65
public:
66
    // Auto generated code. See class document of LinkParams.
67
    ParameterGrp::handle handle;
68

69
    // Auto generated code. See class document of LinkParams.
70
    std::unordered_map<const char *,void(*)(LinkParamsP*),App::CStringHasher,App::CStringHasher> funcs;
71

72
    bool CopyOnChangeApplyToAll; // Auto generated code. See class document of LinkParams.
73

74
    // Auto generated code. See class document of LinkParams.
75
    LinkParamsP() {
76
        handle = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Link");
77
        handle->Attach(this);
78

79
        CopyOnChangeApplyToAll = handle->GetBool("CopyOnChangeApplyToAll", true);
80
        funcs["CopyOnChangeApplyToAll"] = &LinkParamsP::updateCopyOnChangeApplyToAll;
81
    }
82

83
    // Auto generated code. See class document of LinkParams.
84
    ~LinkParamsP() override = default;
85

86
    // Auto generated code. See class document of LinkParams.
87
    void OnChange(Base::Subject<const char*> &, const char* sReason) override {
88
        if(!sReason)
89
            return;
90
        auto it = funcs.find(sReason);
91
        if(it == funcs.end())
92
            return;
93
        it->second(this);
94
    }
95

96

97
    // Auto generated code. See class document of LinkParams.
98
    static void updateCopyOnChangeApplyToAll(LinkParamsP *self) {
99
        self->CopyOnChangeApplyToAll = self->handle->GetBool("CopyOnChangeApplyToAll", true);
100
    }
101
};
102

103
// Auto generated code. See class document of LinkParams.
104
LinkParamsP *instance() {
105
    static LinkParamsP *inst = new LinkParamsP;
106
    return inst;
107
}
108

109
} // Anonymous namespace
110

111
// Auto generated code. See class document of LinkParams.
112
ParameterGrp::handle LinkParams::getHandle() {
113
    return instance()->handle;
114
}
115

116
// Auto generated code. See class document of LinkParams.
117
const char *LinkParams::docCopyOnChangeApplyToAll() {
118
    return QT_TRANSLATE_NOOP("LinkParams",
119
"Stores the last user choice of whether to apply CopyOnChange setup to all links\n"
120
"that reference the same configurable object");
121
}
122

123
// Auto generated code. See class document of LinkParams.
124
const bool & LinkParams::getCopyOnChangeApplyToAll() {
125
    return instance()->CopyOnChangeApplyToAll;
126
}
127

128
// Auto generated code. See class document of LinkParams.
129
const bool & LinkParams::defaultCopyOnChangeApplyToAll() {
130
    static const bool def = true;
131
    return def;
132
}
133

134
// Auto generated code. See class document of LinkParams.
135
void LinkParams::setCopyOnChangeApplyToAll(const bool &v) {
136
    instance()->handle->SetBool("CopyOnChangeApplyToAll",v);
137
    instance()->CopyOnChangeApplyToAll = v;
138
}
139

140
// Auto generated code. See class document of LinkParams.
141
void LinkParams::removeCopyOnChangeApplyToAll() {
142
    instance()->handle->RemoveBool("CopyOnChangeApplyToAll");
143
}
144
//[[[end]]]
145

146
///////////////////////////////////////////////////////////////////////////////
147

148
EXTENSION_PROPERTY_SOURCE(App::LinkBaseExtension, App::DocumentObjectExtension)
149

150
LinkBaseExtension::LinkBaseExtension()
151
{
152
    initExtensionType(LinkBaseExtension::getExtensionClassTypeId());
153
    EXTENSION_ADD_PROPERTY_TYPE(_LinkTouched, (false), " Link",
154
            PropertyType(Prop_Hidden|Prop_NoPersist),0);
155
    EXTENSION_ADD_PROPERTY_TYPE(_ChildCache, (), " Link",
156
            PropertyType(Prop_Hidden|Prop_NoPersist|Prop_ReadOnly),0);
157
    _ChildCache.setScope(LinkScope::Global);
158
    EXTENSION_ADD_PROPERTY_TYPE(_LinkOwner, (0), " Link",
159
            PropertyType(Prop_Hidden|Prop_Output),0);
160
    props.resize(PropMax,nullptr);
161
}
162

163
PyObject* LinkBaseExtension::getExtensionPyObject() {
164
    if (ExtensionPythonObject.is(Py::_None())){
165
        // ref counter is set to 1
166
        ExtensionPythonObject = Py::Object(new LinkBaseExtensionPy(this),true);
167
    }
168
    return Py::new_reference_to(ExtensionPythonObject);
169
}
170

171
const std::vector<LinkBaseExtension::PropInfo> &LinkBaseExtension::getPropertyInfo() const {
172
    static std::vector<LinkBaseExtension::PropInfo> PropsInfo;
173
    if(PropsInfo.empty()) {
174
        BOOST_PP_SEQ_FOR_EACH(LINK_PROP_INFO,PropsInfo,LINK_PARAMS);
175
    }
176
    return PropsInfo;
177
}
178

179
const LinkBaseExtension::PropInfoMap &LinkBaseExtension::getPropertyInfoMap() const {
180
    static PropInfoMap PropsMap;
181
    if(PropsMap.empty()) {
182
        const auto &infos = getPropertyInfo();
183
        for(const auto &info : infos)
184
            PropsMap[info.name] = info;
185
    }
186
    return PropsMap;
187
}
188

189
Property *LinkBaseExtension::getProperty(int idx) {
190
    if(idx>=0 && idx<(int)props.size())
191
        return props[idx];
192
    return nullptr;
193
}
194

195
Property *LinkBaseExtension::getProperty(const char *name) {
196
    const auto &info = getPropertyInfoMap();
197
    auto it = info.find(name);
198
    if(it == info.end())
199
        return nullptr;
200
    return getProperty(it->second.index);
201
}
202

203
void LinkBaseExtension::setProperty(int idx, Property *prop) {
204
    const auto &infos = getPropertyInfo();
205
    if(idx<0 || idx>=(int)infos.size())
206
        LINK_THROW(Base::RuntimeError,"App::LinkBaseExtension: property index out of range");
207

208
    if(props[idx]) {
209
        props[idx]->setStatus(Property::LockDynamic,false);
210
        props[idx] = nullptr;
211
    }
212
    if(!prop)
213
        return;
214
    if(!prop->isDerivedFrom(infos[idx].type)) {
215
        std::ostringstream str;
216
        str << "App::LinkBaseExtension: expected property type '" <<
217
            infos[idx].type.getName() << "', instead of '" <<
218
            prop->getClassTypeId().getName() << "'";
219
        LINK_THROW(Base::TypeError,str.str().c_str());
220
    }
221

222
    props[idx] = prop;
223
    props[idx]->setStatus(Property::LockDynamic,true);
224

225
    switch(idx) {
226
    case PropLinkMode: {
227
        static const char *linkModeEnums[] = {"None","Auto Delete","Auto Link","Auto Unlink",nullptr};
228
        auto propLinkMode = static_cast<PropertyEnumeration*>(prop);
229
        if(!propLinkMode->hasEnums())
230
            propLinkMode->setEnums(linkModeEnums);
231
        break;
232
    }
233
    case PropLinkCopyOnChange: {
234
        static const char *enums[] = {"Disabled","Enabled","Owned","Tracking",nullptr};
235
        auto propEnum = static_cast<PropertyEnumeration*>(prop);
236
        if(!propEnum->hasEnums())
237
            propEnum->setEnums(enums);
238
        break;
239
    }
240
    case PropLinkCopyOnChangeTouched:
241
    case PropLinkCopyOnChangeSource:
242
    case PropLinkCopyOnChangeGroup:
243
        prop->setStatus(Property::Hidden, true);
244
        break;
245
    case PropLinkTransform:
246
    case PropLinkPlacement:
247
    case PropPlacement:
248
        if(getLinkTransformProperty() &&
249
           getLinkPlacementProperty() &&
250
           getPlacementProperty())
251
        {
252
            bool transform = getLinkTransformValue();
253
            getPlacementProperty()->setStatus(Property::Hidden, transform);
254
            getLinkPlacementProperty()->setStatus(Property::Hidden, !transform);
255
        }
256
        break;
257
    case PropElementList:
258
        getElementListProperty()->setScope(LinkScope::Global);
259
        getElementListProperty()->setStatus(Property::Hidden, true);
260
        // fall through
261
    case PropLinkedObject:
262
        // Make ElementList as read-only if we are not a group (i.e. having
263
        // LinkedObject property), because it is for holding array elements.
264
        if(getElementListProperty())
265
            getElementListProperty()->setStatus(
266
                Property::Immutable, getLinkedObjectProperty() != nullptr);
267
        break;
268
    case PropVisibilityList:
269
        getVisibilityListProperty()->setStatus(Property::Immutable, true);
270
        getVisibilityListProperty()->setStatus(Property::Hidden, true);
271
        break;
272
    }
273

274
    if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) {
275
        const char *propName;
276
        if(!prop)
277
            propName = "<null>";
278
        else if(prop->getContainer())
279
            propName = prop->getName();
280
        else
281
            propName = extensionGetPropertyName(prop);
282
        if(!Property::isValidName(propName))
283
            propName = "?";
284
        FC_TRACE("set property " << infos[idx].name << ": " << propName);
285
    }
286
}
287

288
static const char _GroupPrefix[] = "Configuration (";
289

290
App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute() {
291
    // The actual value of LinkTouched is not important, just to notify view
292
    // provider that the link (in fact, its dependents, i.e. linked ones) have
293
    // recomputed.
294
    _LinkTouched.touch();
295

296
    if(getLinkedObjectProperty()) {
297
        DocumentObject *linked = getTrueLinkedObject(true);
298
        if(!linked) {
299
            std::ostringstream ss;
300
            ss << "Link broken!";
301
            auto xlink = Base::freecad_dynamic_cast<PropertyXLink>(
302
                    getLinkedObjectProperty());
303
            if (xlink) {
304
                const char *objname = xlink->getObjectName();
305
                if (objname && objname[0])
306
                    ss << "\nObject: " << objname;
307
                const char *filename = xlink->getFilePath();
308
                if (filename && filename[0])
309
                    ss << "\nFile: " << filename;
310
            }
311
            return new App::DocumentObjectExecReturn(ss.str().c_str());
312
        }
313

314
        App::DocumentObject *container = getContainer();
315
        auto source = getLinkCopyOnChangeSourceValue();
316
        if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking
317
                   && getLinkCopyOnChangeTouchedValue())
318
        {
319
            syncCopyOnChange();
320
        }
321

322
        // the previous linked object could be deleted by syncCopyOnChange - #12281
323
        linked = getTrueLinkedObject(true);
324
        if(!linked) {
325
            return new App::DocumentObjectExecReturn("Error in processing variable link");
326
        }
327

328
        PropertyPythonObject *proxy = nullptr;
329
        if(getLinkExecuteProperty()
330
                && !boost::iequals(getLinkExecuteValue(), "none")
331
                && (!_LinkOwner.getValue()
332
                    || !container->getDocument()->getObjectByID(_LinkOwner.getValue())))
333
        {
334
            // Check if this is an element link. Do not invoke appLinkExecute()
335
            // if so, because it will be called from the link array.
336
            proxy = Base::freecad_dynamic_cast<PropertyPythonObject>(
337
                    linked->getPropertyByName("Proxy"));
338
        }
339
        if(proxy) {
340
            Base::PyGILStateLocker lock;
341
            const char *errMsg = "Linked proxy execute failed";
342
            try {
343
                Py::Tuple args(3);
344
                Py::Object proxyValue = proxy->getValue();
345
                const char *method = getLinkExecuteValue();
346
                if(!method || !method[0])
347
                    method = "appLinkExecute";
348
                if(proxyValue.hasAttr(method)) {
349
                    Py::Object attr = proxyValue.getAttr(method);
350
                    if(attr.ptr() && attr.isCallable()) {
351
                        Py::Tuple args(4);
352
                        args.setItem(0, Py::asObject(linked->getPyObject()));
353
                        args.setItem(1, Py::asObject(container->getPyObject()));
354
                        if(!_getElementCountValue()) {
355
                            Py::Callable(attr).apply(args);
356
                        } else {
357
                            const auto &elements = _getElementListValue();
358
                            for(int i=0; i<_getElementCountValue(); ++i) {
359
                                args.setItem(2, Py::Int(i));
360
                                if(i < (int)elements.size())
361
                                    args.setItem(3, Py::asObject(elements[i]->getPyObject()));
362
                                else
363
                                    args.setItem(3, Py::Object());
364
                                Py::Callable(attr).apply(args);
365
                            }
366
                        }
367
                    }
368
                }
369
            } catch (Py::Exception &) {
370
                Base::PyException e;
371
                e.ReportException();
372
                return new App::DocumentObjectExecReturn(errMsg);
373
            } catch (Base::Exception &e) {
374
                e.ReportException();
375
                return new App::DocumentObjectExecReturn(errMsg);
376
            }
377
        }
378

379
        auto parent = getContainer();
380
        setupCopyOnChange(parent);
381

382
        if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) {
383
            hasCopyOnChange = false;
384
            std::vector<Property*> props;
385
            parent->getPropertyList(props);
386
            for(auto prop : props) {
387
                if(isCopyOnChangeProperty(parent, *prop)) {
388
                    try {
389
                        parent->removeDynamicProperty(prop->getName());
390
                    } catch (Base::Exception &e) {
391
                        e.ReportException();
392
                    } catch (...) {
393
                    }
394
                }
395
            }
396
        }
397
    }
398
    return inherited::extensionExecute();
399
}
400

401
short LinkBaseExtension::extensionMustExecute() {
402
    auto link = getLink();
403
    if(!link)
404
        return 0;
405
    return link->mustExecute();
406
}
407

408
std::vector<App::DocumentObject*>
409
LinkBaseExtension::getOnChangeCopyObjects(
410
        std::vector<App::DocumentObject *> *excludes,
411
        App::DocumentObject *src)
412
{
413
    auto parent = getContainer();
414
    if (!src)
415
        src = getLinkCopyOnChangeSourceValue();
416
    if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
417
        return {};
418

419
    auto res = Document::getDependencyList({src}, Document::DepSort);
420
    for (auto it=res.begin(); it!=res.end();) {
421
        auto obj = *it;
422
        if (obj == src) {
423
            ++it;
424
            continue;
425
        }
426
        auto prop = Base::freecad_dynamic_cast<PropertyMap>(
427
                obj->getPropertyByName("_CopyOnChangeControl"));
428
        static std::map<std::string, std::string> dummy;
429
        const auto & map = prop && prop->getContainer()==obj ? prop->getValues() : dummy;
430
        const char *v = "";
431
        if (src->getDocument() != obj->getDocument())
432
            v = "-";
433
        auto iter = map.find("*");
434
        if (iter != map.end())
435
            v = iter->second.c_str();
436
        else if ((iter = map.find(parent->getNameInDocument())) != map.end())
437
            v = iter->second.c_str();
438
        if (boost::equals(v, "-")) {
439
            if (excludes)
440
                excludes->push_back(obj);
441
            else {
442
                it = res.erase(it);
443
                continue;
444
            }
445
        }
446
        ++it;
447
    }
448
    return res;
449
}
450

451
void LinkBaseExtension::setOnChangeCopyObject(
452
        App::DocumentObject *obj, OnChangeCopyOptions options)
453
{
454
    auto parent = getContainer();
455
    Base::Flags<OnChangeCopyOptions> flags(options);
456
    bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude);
457
    bool external = parent->getDocument() != obj->getDocument();
458
    auto prop = Base::freecad_dynamic_cast<PropertyMap>(
459
            obj->getPropertyByName("_CopyOnChangeControl"));
460

461
    if (external == exclude && !prop)
462
        return;
463

464
    if (!prop) {
465
        try {
466
            prop = static_cast<PropertyMap*>(
467
                    obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl"));
468
        } catch (Base::Exception &e) {
469
            e.ReportException();
470
        }
471
        if (!prop) {
472
            FC_ERR("Failed to setup copy on change object " << obj->getFullName());
473
            return;
474
        }
475
    }
476

477
    const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getDagKey();
478
    if (external)
479
        prop->setValue(key, exclude ? "" : "+");
480
    else
481
        prop->setValue(key, exclude ? "-" : "");
482
}
483

484
// The purpose of this function is to synchronize the mutated copy to the
485
// original linked CopyOnChange object. It will make a new copy if any of the
486
// non-CopyOnChange property of the original object has changed.
487
void LinkBaseExtension::syncCopyOnChange()
488
{
489
    if (!getLinkCopyOnChangeValue())
490
        return;
491
    auto linkProp = getLinkedObjectProperty();
492
    auto srcProp = getLinkCopyOnChangeSourceProperty();
493
    auto srcTouched = getLinkCopyOnChangeTouchedProperty();
494
    if (!linkProp
495
            || !srcProp
496
            || !srcTouched
497
            || !srcProp->getValue()
498
            || !linkProp->getValue()
499
            || srcProp->getValue() == linkProp->getValue())
500
        return;
501

502
    auto parent = getContainer();
503

504
    auto linked = linkProp->getValue();
505

506
    std::vector<App::DocumentObjectT> oldObjs;
507
    std::vector<App::DocumentObject*> objs;
508

509
    // CopyOnChangeGroup is a hidden dynamic property for holding a LinkGroup
510
    // for holding the mutated copy of the original linked object and all its
511
    // dependencies.
512
    LinkGroup *copyOnChangeGroup = nullptr;
513
    if (auto prop = getLinkCopyOnChangeGroupProperty()) {
514
        copyOnChangeGroup = Base::freecad_dynamic_cast<LinkGroup>(prop->getValue());
515
        if (!copyOnChangeGroup) {
516
            // Create the LinkGroup if not exist
517
            auto group = new LinkGroup;
518
            group->LinkMode.setValue(LinkModeAutoDelete);
519
            parent->getDocument()->addObject(group, "CopyOnChangeGroup");
520
            prop->setValue(group);
521
        } else {
522
            // If it exists, then obtain all copied objects. Note that we stores
523
            // the dynamic property _SourceUUID in oldObjs if possible, in order
524
            // to match the possible new copy later.
525
            objs = copyOnChangeGroup->ElementList.getValues();
526
            for (auto obj : objs) {
527
                if (!obj->isAttachedToDocument())
528
                    continue;
529
                auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
530
                        obj->getPropertyByName("_SourceUUID"));
531
                if (prop && prop->getContainer() == obj)
532
                    oldObjs.emplace_back(prop);
533
                else
534
                    oldObjs.emplace_back(obj);
535
            }
536
            std::sort(objs.begin(), objs.end());
537
        }
538
    }
539

540
    // Obtain the original linked object and its dependency in depending order.
541
    // The last being the original linked object.
542
    auto srcObjs = getOnChangeCopyObjects();
543
    // Refresh signal connection to monitor changes
544
    monitorOnChangeCopyObjects(srcObjs);
545

546
    // Copy the objects. Document::export/importObjects() (called by
547
    // copyObject()) will generate a _ObjectUUID for each source object and
548
    // match it with a _SourceUUID in the copy.
549
    auto copiedObjs = parent->getDocument()->copyObject(srcObjs);
550
    if(copiedObjs.empty())
551
        return;
552

553
    // copyObject() will return copy in order of the same order of the input,
554
    // so the last object will be the copy of the original linked object
555
    auto newLinked = copiedObjs.back();
556

557
    // We are copying from the original linked object and we've already mutated
558
    // it, so we need to copy all CopyOnChange properties from the mutated
559
    // object to the new copy.
560
    std::vector<App::Property*> propList;
561
    linked->getPropertyList(propList);
562
    for (auto prop : propList) {
563
        if(!prop->testStatus(Property::CopyOnChange)
564
                || prop->getContainer()!=linked)
565
            continue;
566
        auto p = newLinked->getPropertyByName(prop->getName());
567
        if (p && p->getTypeId() == prop->getTypeId()) {
568
            std::unique_ptr<Property> pCopy(prop->Copy());
569
            p->Paste(*pCopy);
570
        }
571
    }
572

573
    if (copyOnChangeGroup) {
574
        // The order of the copied objects is in dependency order (because of
575
        // getOnChangeCopyObjects()). We reverse it here so that we can later
576
        // on delete it in reverse order to avoid error (because some parent
577
        // objects may want to delete their own children).
578
        std::reverse(copiedObjs.begin(), copiedObjs.end());
579
        copyOnChangeGroup->ElementList.setValues(copiedObjs);
580
    }
581

582
    // Create a map to find the corresponding replacement of the new copies to
583
    // the mutated object. The reason for doing so is that we are copying from
584
    // the original linked object and its dependency, not the mutated objects
585
    // which are old copies. There could be arbitrary changes in the originals
586
    // which may add or remove or change depending orders, while the
587
    // replacement happens between the new and old copies.
588

589
    std::map<Base::Uuid, App::DocumentObjectT> newObjs;
590
    for (auto obj : copiedObjs) {
591
        auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
592
                obj->getPropertyByName("_SourceUUID"));
593
        if (prop)
594
            newObjs.insert(std::make_pair(prop->getValue(), obj));
595
    }
596

597
    std::vector<std::pair<App::DocumentObject*, App::DocumentObject*> > replacements;
598
    for (const auto &objT : oldObjs) {
599
        auto prop = Base::freecad_dynamic_cast<PropertyUUID>(objT.getProperty());
600
        if (!prop)
601
            continue;
602
        auto it = newObjs.find(prop->getValue());
603
        if (it == newObjs.end())
604
            continue;
605
        auto oldObj = objT.getObject();
606
        auto newObj = it->second.getObject();
607
        if (oldObj && newObj)
608
            replacements.emplace_back(oldObj, newObj);
609
    }
610

611
    std::vector<std::pair<App::DocumentObjectT, std::unique_ptr<App::Property> > > propChanges;
612
    if (!replacements.empty()) {
613
        std::sort(copiedObjs.begin(), copiedObjs.end());
614

615
        // Global search for links affected by the replacement. We accumulate
616
        // the changes in propChanges without applying, in order to avoid any
617
        // side effect of changing while searching.
618
        for(auto doc : App::GetApplication().getDocuments()) {
619
            for(auto o : doc->getObjects()) {
620
                if (o == parent
621
                        || std::binary_search(objs.begin(), objs.end(), o)
622
                        || std::binary_search(copiedObjs.begin(), copiedObjs.end(), o))
623
                    continue;
624
                propList.clear();
625
                o->getPropertyList(propList);
626
                for(auto prop : propList) {
627
                    if (prop->getContainer() != o)
628
                        continue;
629
                    auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
630
                    if(!linkProp)
631
                        continue;
632
                    for (const auto &v : replacements) {
633
                        std::unique_ptr<App::Property> copy(
634
                                linkProp->CopyOnLinkReplace(parent,v.first,v.second));
635
                        if(!copy)
636
                            continue;
637
                        propChanges.emplace_back(App::DocumentObjectT(prop),std::move(copy));
638
                    }
639
                }
640
            }
641
        }
642
    }
643

644
    Base::StateLocker guard(pauseCopyOnChange);
645
    linkProp->setValue(newLinked);
646
    newLinked->Visibility.setValue(false);
647
    srcTouched->setValue(false);
648

649
    // Apply the global link changes.
650
    for(const auto &v : propChanges) {
651
        auto prop = v.first.getProperty();
652
        if(prop)
653
            prop->Paste(*v.second.get());
654
    }
655

656
    // Finally, remove all old copies. oldObjs stores type of DocumentObjectT
657
    // which stores the object name. Before removing, we need to make sure the
658
    // object exists, and it is not some new object with the same name, by
659
    // checking objs which stores DocumentObject pointer.
660
    for (const auto &objT : oldObjs) {
661
        auto obj = objT.getObject();
662
        if (obj && std::binary_search(objs.begin(), objs.end(), obj))
663
            obj->getDocument()->removeObject(obj->getNameInDocument());
664
    }
665
}
666

667
bool LinkBaseExtension::isLinkedToConfigurableObject() const
668
{
669
    if (auto linked = getLinkedObjectValue()) {
670
        std::vector<App::Property*> propList;
671
        linked->getPropertyList(propList);
672
        for (auto prop : propList) {
673
            if(prop->testStatus(Property::CopyOnChange)
674
                    && prop->getContainer()==linked)
675
                return true;
676
        }
677
    }
678
    return false;
679
}
680

681
bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) {
682
    if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic))
683
        return false;
684
    auto group = prop.getGroup();
685
    return group && boost::starts_with(group,_GroupPrefix);
686
}
687

688
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, bool checkSource) {
689
    copyOnChangeConns.clear();
690
    copyOnChangeSrcConns.clear();
691

692
    auto linked = getTrueLinkedObject(false);
693
    if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
694
        return;
695

696
    if (checkSource && !pauseCopyOnChange) {
697
        PropertyLink *source = getLinkCopyOnChangeSourceProperty();
698
        if (source) {
699
            source->setValue(linked);
700
            if (auto touched = getLinkCopyOnChangeTouchedProperty())
701
                touched->setValue(false);
702
        }
703
    }
704

705
    hasCopyOnChange = setupCopyOnChange(parent,linked,&copyOnChangeConns,hasCopyOnChange);
706
    if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned
707
            && getLinkedObjectValue()
708
            && getLinkedObjectValue() == getLinkCopyOnChangeSourceValue())
709
    {
710
        makeCopyOnChange();
711
    }
712
}
713

714
bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked,
715
        std::vector<boost::signals2::scoped_connection> *copyOnChangeConns, bool checkExisting)
716
{
717
    if(!parent || !linked)
718
        return false;
719

720
    bool res = false;
721

722
    std::unordered_map<Property*, Property*> newProps;
723
    std::vector<Property*> props;
724
    linked->getPropertyList(props);
725
    for(auto prop : props) {
726
        if(!prop->testStatus(Property::CopyOnChange)
727
                || prop->getContainer()!=linked)
728
            continue;
729

730
        res = true;
731

732
        const char* linkedGroupName = prop->getGroup();
733
        if(!linkedGroupName || !linkedGroupName[0])
734
            linkedGroupName = "Base";
735

736
        std::string groupName;
737
        groupName = _GroupPrefix;
738
        if(boost::starts_with(linkedGroupName,_GroupPrefix))
739
            groupName += linkedGroupName + sizeof(_GroupPrefix)-1;
740
        else {
741
            groupName += linkedGroupName;
742
            groupName += ")";
743
        }
744

745
        auto p = parent->getPropertyByName(prop->getName());
746
        if(p) {
747
            if(p->getContainer()!=parent)
748
                p = nullptr;
749
            else {
750
                const char* otherGroupName = p->getGroup();
751
                if(!otherGroupName || !boost::starts_with(otherGroupName, _GroupPrefix)) {
752
                    FC_WARN(p->getFullName() << " shadows another CopyOnChange property "
753
                            << prop->getFullName());
754
                    continue;
755
                }
756
                if(p->getTypeId() != prop->getTypeId() || groupName != otherGroupName) {
757
                    parent->removeDynamicProperty(p->getName());
758
                    p = nullptr;
759
                }
760
            }
761
        }
762
        if(!p) {
763
            p = parent->addDynamicProperty(prop->getTypeId().getName(),
764
                    prop->getName(), groupName.c_str(), prop->getDocumentation());
765
            std::unique_ptr<Property> pcopy(prop->Copy());
766
            Base::ObjectStatusLocker<Property::Status,Property> guard(Property::User3, p);
767
            if(pcopy) {
768
                p->Paste(*pcopy);
769
            }
770
            p->setStatusValue(prop->getStatus());
771
        }
772
        newProps[p] = prop;
773
    }
774

775
    if(checkExisting) {
776
        props.clear();
777
        parent->getPropertyList(props);
778
        for(auto prop : props) {
779
            if(prop->getContainer()!=parent)
780
                continue;
781
            auto gname = prop->getGroup();
782
            if(!gname || !boost::starts_with(gname, _GroupPrefix))
783
                continue;
784
            if(!newProps.count(prop))
785
                parent->removeDynamicProperty(prop->getName());
786
        }
787
    }
788

789
    if(!copyOnChangeConns)
790
        return res;
791

792
    for(const auto &v : newProps) {
793
        // sync configuration properties
794
        copyOnChangeConns->push_back(v.second->signalChanged.connect([parent](const Property &prop) {
795
            if(!prop.testStatus(Property::CopyOnChange))
796
                return;
797
            auto p = parent->getPropertyByName(prop.getName());
798
            if(p && p->getTypeId()==prop.getTypeId()) {
799
                std::unique_ptr<Property> pcopy(prop.Copy());
800
                // temperoray set Output to prevent touching
801
                p->setStatus(Property::Output, true);
802
                // temperoray block copy on change
803
                Base::ObjectStatusLocker<Property::Status,Property> guard(Property::User3, p);
804
                if(pcopy)
805
                    p->Paste(*pcopy);
806
                p->setStatusValue(prop.getStatus());
807
            }
808
        }));
809
    }
810

811
    return res;
812
}
813

814
void LinkBaseExtension::checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop)
815
{
816
    if(!parent || !parent->getDocument()
817
               || parent->getDocument()->isPerformingTransaction())
818
        return;
819

820
    auto linked = getLinkedObjectValue();
821
    if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled
822
               || !isCopyOnChangeProperty(parent,prop))
823
        return;
824

825
    if(getLinkCopyOnChangeValue() == CopyOnChangeOwned ||
826
            (getLinkCopyOnChangeValue() == CopyOnChangeTracking
827
             && linked != getLinkCopyOnChangeSourceValue()))
828
    {
829
        auto p = linked->getPropertyByName(prop.getName());
830
        if(p && p->getTypeId()==prop.getTypeId()) {
831
            std::unique_ptr<Property> pcopy(prop.Copy());
832
            if(pcopy)
833
                p->Paste(*pcopy);
834
        }
835
        return;
836
    }
837

838
    auto linkedProp = linked->getPropertyByName(prop.getName());
839
    if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop))
840
        return;
841

842
    auto copied = makeCopyOnChange();
843
    if (copied) {
844
        linkedProp = copied->getPropertyByName(prop.getName());
845
        if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
846
            std::unique_ptr<Property> pcopy(prop.Copy());
847
            if(pcopy)
848
                linkedProp->Paste(*pcopy);
849
        }
850
    }
851
}
852

853
App::DocumentObject *LinkBaseExtension::makeCopyOnChange() {
854
    auto linked = getLinkedObjectValue();
855
    if (pauseCopyOnChange || !linked)
856
        return nullptr;
857
    auto parent = getContainer();
858
    auto srcobjs = getOnChangeCopyObjects(nullptr, linked);
859
    for (auto obj : srcobjs) {
860
        if (obj->testStatus(App::PartialObject)) {
861
            FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: "
862
                    << obj->getFullName());
863
        }
864
    }
865
    auto objs = parent->getDocument()->copyObject(srcobjs);
866
    if(objs.empty())
867
        return nullptr;
868

869
    monitorOnChangeCopyObjects(srcobjs);
870

871
    linked = objs.back();
872
    linked->Visibility.setValue(false);
873

874
    Base::StateLocker guard(pauseCopyOnChange);
875
    getLinkedObjectProperty()->setValue(linked);
876
    if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled)
877
        getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned);
878

879
    if (auto prop = getLinkCopyOnChangeGroupProperty()) {
880
        if (auto obj = prop->getValue()) {
881
            if (obj->isAttachedToDocument() && obj->getDocument())
882
                obj->getDocument()->removeObject(obj->getNameInDocument());
883
        }
884
        auto group = new LinkGroup;
885
        group->LinkMode.setValue(LinkModeAutoDelete);
886
        getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup");
887
        prop->setValue(group);
888

889
        // The order of the copied objects is in dependency order (because of
890
        // getOnChangeCopyObjects()). We reverse it here so that we can later
891
        // on delete it in reverse order to avoid error (because some parent
892
        // objects may want to delete their own children).
893
        std::reverse(objs.begin(), objs.end());
894
        group->ElementList.setValues(objs);
895
    }
896

897
    return linked;
898
}
899

900
void LinkBaseExtension::monitorOnChangeCopyObjects(
901
        const std::vector<App::DocumentObject*> &objs)
902
{
903
    copyOnChangeSrcConns.clear();
904
    if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
905
        return;
906
    for(auto obj : objs) {
907
        obj->setStatus(App::ObjectStatus::TouchOnColorChange, true);
908
        copyOnChangeSrcConns.emplace_back(obj->signalChanged.connect(
909
            [this](const DocumentObject &, const Property &) {
910
                if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) {
911
                    if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
912
                        prop->setValue(true);
913
                }
914
            }));
915
    }
916
}
917

918
App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const {
919
    if(!mySubElements.empty() && !mySubElements[0].empty())
920
        return nullptr;
921
    auto linked = getTrueLinkedObject(false);
922
    if(!linked)
923
        return nullptr;
924
    return linked->getExtensionByType<GroupExtension>(true,false);
925
}
926

927
App::PropertyLinkList *LinkBaseExtension::_getElementListProperty() const {
928
    auto group = linkedPlainGroup();
929
    if(group)
930
        return &group->Group;
931
    return const_cast<PropertyLinkList*>(getElementListProperty());
932
}
933

934
const std::vector<App::DocumentObject*> &LinkBaseExtension::_getElementListValue() const {
935
    if(_ChildCache.getSize())
936
        return _ChildCache.getValues();
937
    if(getElementListProperty())
938
        return getElementListProperty()->getValues();
939
    static const std::vector<DocumentObject*> empty;
940
    return empty;
941
}
942

943
App::PropertyBool *LinkBaseExtension::_getShowElementProperty() const {
944
    auto prop = getShowElementProperty();
945
    if(prop && !linkedPlainGroup())
946
        return const_cast<App::PropertyBool*>(prop);
947
    return nullptr;
948
}
949

950
bool LinkBaseExtension::_getShowElementValue() const {
951
    auto prop = _getShowElementProperty();
952
    if(prop)
953
        return prop->getValue();
954
    return true;
955
}
956

957
App::PropertyInteger *LinkBaseExtension::_getElementCountProperty() const {
958
    auto prop = getElementCountProperty();
959
    if(prop && !linkedPlainGroup())
960
        return const_cast<App::PropertyInteger*>(prop);
961
    return nullptr;
962
}
963

964
int LinkBaseExtension::_getElementCountValue() const {
965
    auto prop = _getElementCountProperty();
966
    if(prop)
967
        return prop->getValue();
968
    return 0;
969
}
970

971
bool LinkBaseExtension::extensionHasChildElement() const {
972
    if(!_getElementListValue().empty()
973
            || (_getElementCountValue() && _getShowElementValue()))
974
        return true;
975
    if (getLinkClaimChildValue())
976
        return false;
977
    DocumentObject *linked = getTrueLinkedObject(false);
978
    if(linked) {
979
        if(linked->hasChildElement())
980
            return true;
981
    }
982
    return false;
983
}
984

985
int LinkBaseExtension::extensionSetElementVisible(const char *element, bool visible) {
986
    int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element);
987
    if(index>=0) {
988
        auto propElementVis = getVisibilityListProperty();
989
        if(!propElementVis || !element || !element[0])
990
            return -1;
991
        if(propElementVis->getSize()<=index) {
992
            if(visible)
993
                return 1;
994
            propElementVis->setSize(index+1, true);
995
        }
996
        propElementVis->setStatus(Property::User3,true);
997
        propElementVis->set1Value(index,visible);
998
        propElementVis->setStatus(Property::User3,false);
999
        const auto &elements = _getElementListValue();
1000
        if(index<(int)elements.size()) {
1001
            if(!visible)
1002
                myHiddenElements.insert(elements[index]);
1003
            else
1004
                myHiddenElements.erase(elements[index]);
1005
        }
1006
        return 1;
1007
    }
1008
    DocumentObject *linked = getTrueLinkedObject(false);
1009
    if(linked)
1010
        return linked->setElementVisible(element,visible);
1011
    return -1;
1012
}
1013

1014
int LinkBaseExtension::extensionIsElementVisible(const char *element) {
1015
    int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element);
1016
    if(index>=0) {
1017
        auto propElementVis = getVisibilityListProperty();
1018
        if(propElementVis) {
1019
            if(propElementVis->getSize()<=index || propElementVis->getValues()[index])
1020
                return 1;
1021
            return 0;
1022
        }
1023
        return -1;
1024
    }
1025
    DocumentObject *linked = getTrueLinkedObject(false);
1026
    if(linked)
1027
        return linked->isElementVisible(element);
1028
    return -1;
1029
}
1030

1031
const DocumentObject *LinkBaseExtension::getContainer() const {
1032
    auto ext = getExtendedContainer();
1033
    if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId()))
1034
        LINK_THROW(Base::RuntimeError,"Link: container not derived from document object");
1035
    return static_cast<const DocumentObject *>(ext);
1036
}
1037

1038
DocumentObject *LinkBaseExtension::getContainer(){
1039
    auto ext = getExtendedContainer();
1040
    if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId()))
1041
        LINK_THROW(Base::RuntimeError,"Link: container not derived from document object");
1042
    return static_cast<DocumentObject *>(ext);
1043
}
1044

1045
DocumentObject *LinkBaseExtension::getLink(int depth) const{
1046
    if (!GetApplication().checkLinkDepth(depth, MessageOption::Error))
1047
        return nullptr;
1048
    if(getLinkedObjectProperty())
1049
        return getLinkedObjectValue();
1050
    return nullptr;
1051
}
1052

1053
int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname) {
1054
    if(!subname || Data::isMappedElement(subname))
1055
        return -1;
1056
    const char *dot = strchr(subname,'.');
1057
    if(!dot) dot= subname+strlen(subname);
1058
    if(dot == subname)
1059
        return -1;
1060
    int idx = 0;
1061
    for(const char *c=subname;c!=dot;++c) {
1062
        if(!isdigit(*c))
1063
            return -1;
1064
        idx = idx*10 + *c -'0';
1065
    }
1066
    if(psubname) {
1067
        if(*dot)
1068
            *psubname = dot+1;
1069
        else
1070
            *psubname = dot;
1071
    }
1072
    return idx;
1073
}
1074

1075
int LinkBaseExtension::getElementIndex(const char *subname, const char **psubname) const {
1076
    if(!subname || Data::isMappedElement(subname))
1077
        return -1;
1078
    int idx = -1;
1079
    const char *dot = strchr(subname,'.');
1080
    if(!dot) dot= subname+strlen(subname);
1081

1082
    if(isdigit(subname[0])) {
1083
        // If the name start with digits, treat as index reference
1084
        idx = getArrayIndex(subname,nullptr);
1085
        if(idx<0)
1086
            return -1;
1087
        if(_getElementCountProperty()) {
1088
            if(idx>=_getElementCountValue())
1089
                return -1;
1090
        }else if(idx>=(int)_getElementListValue().size())
1091
            return -1;
1092
    }else if(!_getShowElementValue() && _getElementCountValue()) {
1093
        // If elements are collapsed, we check first for LinkElement naming
1094
        // pattern, which is the owner object name + "_i" + index
1095
        const char *name = subname[0]=='$'?subname+1:subname;
1096
        auto owner = getContainer();
1097
        if(owner && owner->isAttachedToDocument()) {
1098
            std::string ownerName(owner->getNameInDocument());
1099
            ownerName += '_';
1100
            if(boost::algorithm::starts_with(name,ownerName.c_str())) {
1101
                for(const char *txt=dot-1;txt>=name+ownerName.size();--txt) {
1102
                    if(*txt == 'i') {
1103
                        idx = getArrayIndex(txt+1,nullptr);
1104
                        if(idx<0 || idx>=_getElementCountValue())
1105
                            idx = -1;
1106
                        break;
1107
                    }
1108
                    if(!isdigit(*txt))
1109
                        break;
1110
                }
1111
            }
1112
        }
1113
        if(idx<0) {
1114
            // Then check for the actual linked object's name or label, and
1115
            // redirect that reference to the first array element
1116
            auto linked = getTrueLinkedObject(false);
1117
            if(!linked || !linked->isAttachedToDocument())
1118
                return -1;
1119
            if(subname[0]=='$') {
1120
                CharRange sub(subname+1, dot);
1121
                if (boost::equals(sub, linked->Label.getValue()))
1122
                    idx = 0;
1123
            } else {
1124
                CharRange sub(subname, dot);
1125
                if (boost::equals(sub, linked->getNameInDocument()))
1126
                    idx = 0;
1127
            }
1128
            if(idx<0) {
1129
                // Lastly, try to get sub object directly from the linked object
1130
                auto sobj = linked->getSubObject(std::string(subname, dot-subname+1).c_str());
1131
                if(!sobj)
1132
                    return -1;
1133
                if(psubname)
1134
                    *psubname = subname;
1135
                return 0;
1136
            }
1137
        }
1138
    }else if(subname[0]!='$') {
1139
        // Try search by element objects' name
1140
        std::string name(subname,dot);
1141
        if(_ChildCache.getSize()) {
1142
            auto obj=_ChildCache.find(name,&idx);
1143
            if(obj) {
1144
                auto group = obj->getExtensionByType<GroupExtension>(true,false);
1145
                if(group) {
1146
                    int nidx = getElementIndex(dot+1,psubname);
1147
                    if(nidx >= 0)
1148
                        return nidx;
1149
                }
1150
            }
1151
        } else if(getElementListProperty())
1152
            getElementListProperty()->find(name.c_str(),&idx);
1153
        if(idx<0)
1154
            return -1;
1155
    }else {
1156
        // Try search by label if the reference name start with '$'
1157
        ++subname;
1158
        std::string name(subname,dot-subname);
1159
        const auto &elements = _getElementListValue();
1160
        if(enableLabelCache) {
1161
            if(myLabelCache.empty())
1162
                cacheChildLabel(1);
1163
            auto it = myLabelCache.find(name);
1164
            if(it == myLabelCache.end())
1165
                return -1;
1166
            idx = it->second;
1167
        }else{
1168
            idx = 0;
1169
            for(auto element : elements) {
1170
                if(element->Label.getStrValue() == name)
1171
                    break;
1172
                ++idx;
1173
            }
1174
        }
1175
        if(idx<0 || idx>=(int)elements.size())
1176
            return -1;
1177
        auto obj = elements[idx];
1178
        if(obj && _ChildCache.getSize()) {
1179
            auto group = obj->getExtensionByType<GroupExtension>(true,false);
1180
            if(group) {
1181
                int nidx = getElementIndex(dot+1,psubname);
1182
                if(nidx >= 0)
1183
                    return nidx;
1184
            }
1185
        }
1186
    }
1187
    if(psubname)
1188
        *psubname = dot[0]?dot+1:dot;
1189
    return idx;
1190
}
1191

1192
void LinkBaseExtension::elementNameFromIndex(int idx, std::ostream &ss) const {
1193
    const auto &elements = _getElementListValue();
1194
    if(idx < 0 || idx >= (int)elements.size())
1195
        return;
1196

1197
    auto obj = elements[idx];
1198
    if(_ChildCache.getSize()) {
1199
        auto group = GroupExtension::getGroupOfObject(obj);
1200
        if(group && _ChildCache.find(group->getNameInDocument(),&idx))
1201
            elementNameFromIndex(idx,ss);
1202
    }
1203
    ss << obj->getNameInDocument() << '.';
1204
}
1205

1206
Base::Vector3d LinkBaseExtension::getScaleVector() const {
1207
    if(getScaleVectorProperty())
1208
        return getScaleVectorValue();
1209
    double s = getScaleValue();
1210
    return Base::Vector3d(s,s,s);
1211
}
1212

1213
Base::Matrix4D LinkBaseExtension::getTransform(bool transform) const {
1214
    Base::Matrix4D mat;
1215
    if(transform) {
1216
        if(getLinkPlacementProperty())
1217
            mat = getLinkPlacementValue().toMatrix();
1218
        else if(getPlacementProperty())
1219
            mat = getPlacementValue().toMatrix();
1220
    }
1221
    if(getScaleProperty() || getScaleVectorProperty()) {
1222
        Base::Matrix4D s;
1223
        s.scale(getScaleVector());
1224
        mat *= s;
1225
    }
1226
    return mat;
1227
}
1228

1229
bool LinkBaseExtension::extensionGetSubObjects(std::vector<std::string> &ret, int reason) const {
1230
    if(!getLinkedObjectProperty() && getElementListProperty()) {
1231
        for(auto obj : getElementListProperty()->getValues()) {
1232
            if(obj && obj->isAttachedToDocument()) {
1233
                std::string name(obj->getNameInDocument());
1234
                name+='.';
1235
                ret.push_back(name);
1236
            }
1237
        }
1238
        return true;
1239
    }
1240
    if(mySubElements.empty() || mySubElements[0].empty()) {
1241
        DocumentObject *linked = getTrueLinkedObject(true);
1242
        if(linked) {
1243
            if(!_getElementCountValue())
1244
                ret = linked->getSubObjects(reason);
1245
            else{
1246
                char index[30];
1247
                for(int i=0,count=_getElementCountValue();i<count;++i) {
1248
                    snprintf(index,sizeof(index),"%d.",i);
1249
                    ret.emplace_back(index);
1250
                }
1251
            }
1252
        }
1253
    } else if(mySubElements.size()>1) {
1254
        ret = mySubElements;
1255
    }
1256
    return true;
1257
}
1258

1259
bool LinkBaseExtension::extensionGetSubObject(DocumentObject *&ret, const char *subname,
1260
        PyObject **pyObj, Base::Matrix4D *mat, bool transform, int depth) const
1261
{
1262
    ret = nullptr;
1263
    auto obj = getContainer();
1264
    if(!subname || !subname[0]) {
1265
        ret = const_cast<DocumentObject*>(obj);
1266
        Base::Matrix4D _mat;
1267
        if(mat) {
1268
            // 'mat' here is used as an output to return the accumulated
1269
            // transformation up until this object. Since 'subname' is empty
1270
            // here, it means the we are at the end of the hierarchy. We shall
1271
            // not include scale in the output transformation.
1272
            //
1273
            // Think of it this way, the transformation along object hierarchy
1274
            // is public, while transformation through linkage is private to
1275
            // link itself.
1276
            if(transform) {
1277
                if(getLinkPlacementProperty())
1278
                    *mat *= getLinkPlacementValue().toMatrix();
1279
                else if(getPlacementProperty())
1280
                    *mat *= getPlacementValue().toMatrix();
1281
            }
1282
            _mat = *mat;
1283
        }
1284

1285
        if(pyObj && !_getElementCountValue()
1286
                && _getElementListValue().empty() && mySubElements.size()<=1)
1287
        {
1288
            // Scale will be included here
1289
            if(getScaleProperty() || getScaleVectorProperty()) {
1290
                Base::Matrix4D s;
1291
                s.scale(getScaleVector());
1292
                _mat *= s;
1293
            }
1294
            auto linked = getTrueLinkedObject(false,&_mat,depth);
1295
            if(linked && linked!=obj) {
1296
                linked->getSubObject(mySubElements.empty()?nullptr:mySubElements.front().c_str(),
1297
                                     pyObj,&_mat,false,depth+1);
1298
                checkGeoElementMap(obj,linked,pyObj,nullptr);
1299
            }
1300
        }
1301
        return true;
1302
    }
1303

1304
    if(mat) *mat *= getTransform(transform);
1305

1306
    //DocumentObject *element = 0;
1307
    bool isElement = false;
1308
    int idx = getElementIndex(subname,&subname);
1309
    if(idx>=0) {
1310
        const auto &elements = _getElementListValue();
1311
        if(!elements.empty()) {
1312
            if(idx>=(int)elements.size() || !elements[idx] || !elements[idx]->isAttachedToDocument())
1313
                return true;
1314
            ret = elements[idx]->getSubObject(subname,pyObj,mat,true,depth+1);
1315
            // do not resolve the link if this element is the last referenced object
1316
            if(!subname || Data::isMappedElement(subname) || !strchr(subname,'.'))
1317
                ret = elements[idx];
1318
            return true;
1319
        }
1320

1321
        int elementCount = _getElementCountValue();
1322
        if(idx>=elementCount)
1323
            return true;
1324
        isElement = true;
1325
        if(mat) {
1326
            auto placementList = getPlacementListProperty();
1327
            if(placementList && placementList->getSize()>idx)
1328
                *mat *= (*placementList)[idx].toMatrix();
1329
            auto scaleList = getScaleListProperty();
1330
            if(scaleList && scaleList->getSize()>idx) {
1331
                Base::Matrix4D s;
1332
                s.scale((*scaleList)[idx]);
1333
                *mat *= s;
1334
            }
1335
        }
1336
    }
1337

1338
    auto linked = getTrueLinkedObject(false,mat,depth);
1339
    if(!linked || linked==obj)
1340
        return true;
1341

1342
    Base::Matrix4D matNext;
1343

1344
    // Because of the addition of LinkClaimChild, the linked object may be
1345
    // claimed as the first child. Regardless of the current value of
1346
    // LinkClaimChild, we must accept sub-object path that contains the linked
1347
    // object, because other link property may store such reference.
1348
    if (const char* dot=strchr(subname,'.')) {
1349
        auto group = getLinkCopyOnChangeGroupValue();
1350
        if (subname[0] == '$') {
1351
            CharRange sub(subname+1,dot);
1352
            if (group && boost::equals(sub, group->Label.getValue()))
1353
                linked = group;
1354
            else if(!boost::equals(sub, linked->Label.getValue()))
1355
                dot = nullptr;
1356
        } else {
1357
            CharRange sub(subname,dot);
1358
            if (group && boost::equals(sub, group->getNameInDocument()))
1359
                linked = group;
1360
            else if (!boost::equals(sub, linked->getNameInDocument()))
1361
                dot = nullptr;
1362
        }
1363
        if (dot) {
1364
            // Because of external linked object, It is possible for and
1365
            // child object to have the exact same internal name or label
1366
            // as the parent object. To resolve this potential ambiguity,
1367
            // try assuming the current subname is referring to the parent
1368
            // (i.e. the linked object), and if it fails, try again below.
1369
            if(mat) matNext = *mat;
1370
            ret = linked->getSubObject(dot+1,pyObj,mat?&matNext:nullptr,false,depth+1);
1371
            if (ret && dot[1])
1372
                subname = dot+1;
1373
        }
1374
    }
1375

1376
    if (!ret) {
1377
        if(mat) matNext = *mat;
1378
        ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
1379
    }
1380

1381
    std::string postfix;
1382
    if(ret) {
1383
        // do not resolve the link if we are the last referenced object
1384
        if(subname && !Data::isMappedElement(subname) && strchr(subname,'.')) {
1385
            if(mat)
1386
                *mat = matNext;
1387
        }
1388
        // This is a useless check as 'element' is never set to a value other than null
1389
        //else if(element) {
1390
        //    ret = element;
1391
        //}
1392
        else if(!isElement) {
1393
            ret = const_cast<DocumentObject*>(obj);
1394
        }
1395
        else {
1396
            if(idx) {
1397
                postfix = Data::POSTFIX_INDEX;
1398
                postfix += std::to_string(idx);
1399
            }
1400
            if(mat)
1401
                *mat = matNext;
1402
        }
1403
    }
1404
    checkGeoElementMap(obj,linked,pyObj,!postfix.empty()?postfix.c_str():nullptr);
1405
    return true;
1406
}
1407

1408
void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj,
1409
        const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const
1410
{
1411
    if(!pyObj || !*pyObj || (!postfix && obj->getDocument()==linked->getDocument()) ||
1412
       !PyObject_TypeCheck(*pyObj, &Data::ComplexGeoDataPy::Type))
1413
        return;
1414

1415
    // auto geoData = static_cast<Data::ComplexGeoDataPy*>(*pyObj)->getComplexGeoDataPtr();
1416
    // geoData->reTagElementMap(obj->getID(),obj->getDocument()->Hasher,postfix);
1417
}
1418

1419
void LinkBaseExtension::onExtendedUnsetupObject() {
1420
    if(!getElementListProperty())
1421
        return;
1422
    detachElements();
1423
    if (auto obj = getLinkCopyOnChangeGroupValue()) {
1424
        if(obj->isAttachedToDocument() && !obj->isRemoving())
1425
            obj->getDocument()->removeObject(obj->getNameInDocument());
1426
    }
1427
}
1428

1429
DocumentObject *LinkBaseExtension::getTrueLinkedObject(
1430
        bool recurse, Base::Matrix4D *mat, int depth, bool noElement) const
1431
{
1432
    if(noElement && extensionIsDerivedFrom(LinkElement::getExtensionClassTypeId())
1433
            && !static_cast<const LinkElement*>(this)->canDelete())
1434
    {
1435
        return nullptr;
1436
    }
1437

1438
    auto ret = getLink(depth);
1439
    if(!ret)
1440
        return nullptr;
1441
    bool transform = linkTransform();
1442
    const char *subname = getSubName();
1443
    if(subname || (mat && transform)) {
1444
        ret = ret->getSubObject(subname,nullptr,mat,transform,depth+1);
1445
        transform = false;
1446
    }
1447
    if(ret && recurse)
1448
        ret = ret->getLinkedObject(recurse,mat,transform,depth+1);
1449
    if(ret && !ret->isAttachedToDocument())
1450
        return nullptr;
1451
    return ret;
1452
}
1453

1454
bool LinkBaseExtension::extensionGetLinkedObject(DocumentObject *&ret,
1455
        bool recurse, Base::Matrix4D *mat, bool transform, int depth) const
1456
{
1457
    if(mat)
1458
        *mat *= getTransform(transform);
1459
    ret = nullptr;
1460
    if(!_getElementCountValue())
1461
        ret = getTrueLinkedObject(recurse,mat,depth);
1462
    if(!ret)
1463
        ret = const_cast<DocumentObject*>(getContainer());
1464
    // always return true to indicate we've handled getLinkObject() call
1465
    return true;
1466
}
1467

1468
void LinkBaseExtension::extensionOnChanged(const Property *prop) {
1469
    auto parent = getContainer();
1470
    if(parent && !parent->isRestoring() && prop && !prop->testStatus(Property::User3))
1471
        update(parent,prop);
1472
    inherited::extensionOnChanged(prop);
1473
}
1474

1475
void LinkBaseExtension::parseSubName() const {
1476
    // If user has ever linked to some sub-element, the Link shall always accept
1477
    // sub-element linking in the future, which affects how ViewProviderLink
1478
    // dropObjectEx() behave. So we will push an empty string later even if no
1479
    // sub-element linking this time.
1480
    bool hasSubElement = !mySubElements.empty();
1481
    mySubElements.clear();
1482
    mySubName.clear();
1483
    auto xlink = freecad_dynamic_cast<const PropertyXLink>(getLinkedObjectProperty());
1484
    if(!xlink || xlink->getSubValues().empty()) {
1485
        if(hasSubElement)
1486
            mySubElements.emplace_back("");
1487
        return;
1488
    }
1489
    const auto &subs = xlink->getSubValues();
1490
    auto subname = subs.front().c_str();
1491
    auto element = Data::findElementName(subname);
1492
    if(!element || !element[0]) {
1493
        mySubName = subs[0];
1494
        if(hasSubElement)
1495
            mySubElements.emplace_back("");
1496
        return;
1497
    }
1498
    mySubElements.emplace_back(element);
1499
    mySubName = std::string(subname,element-subname);
1500
    for(std::size_t i=1;i<subs.size();++i) {
1501
        auto &sub = subs[i];
1502
        element = Data::findElementName(sub.c_str());
1503
        if(element && element[0] && boost::starts_with(sub,mySubName))
1504
            mySubElements.emplace_back(element);
1505
    }
1506
}
1507

1508
void LinkBaseExtension::slotChangedPlainGroup(const App::DocumentObject &obj, const App::Property &prop) {
1509
    auto group = obj.getExtensionByType<GroupExtension>(true,false);
1510
    if(group && &prop == &group->Group)
1511
        updateGroup();
1512
}
1513

1514
void LinkBaseExtension::updateGroup() {
1515
    std::vector<GroupExtension*> groups;
1516
    std::unordered_set<const App::DocumentObject*> groupSet;
1517
    auto group = linkedPlainGroup();
1518
    if(group) {
1519
        groups.push_back(group);
1520
        groupSet.insert(group->getExtendedObject());
1521
    }else{
1522
        for(auto o : getElementListProperty()->getValues()) {
1523
            if(!o || !o->isAttachedToDocument())
1524
                continue;
1525
            auto ext = o->getExtensionByType<GroupExtension>(true,false);
1526
            if(ext) {
1527
                groups.push_back(ext);
1528
                groupSet.insert(o);
1529
            }
1530
        }
1531
    }
1532
    std::vector<App::DocumentObject*> children;
1533
    if(!groups.empty()) {
1534
        children = getElementListValue();
1535
        std::set<DocumentObject*> childSet(children.begin(),children.end());
1536
        for(auto ext : groups) {
1537
            auto group = ext->getExtendedObject();
1538
            auto &conn = plainGroupConns[group];
1539
            if(!conn.connected()) {
1540
                FC_LOG("new group connection " << getExtendedObject()->getFullName()
1541
                        << " -> " << group->getFullName());
1542
                //NOLINTBEGIN
1543
                conn = group->signalChanged.connect(
1544
                        std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2));
1545
                //NOLINTEND
1546
            }
1547
            std::size_t count = children.size();
1548
            ext->getAllChildren(children,childSet);
1549
            for(;count<children.size();++count) {
1550
                auto child = children[count];
1551
                if(!child->getExtensionByType<GroupExtension>(true,false))
1552
                    continue;
1553
                groupSet.insert(child);
1554
                auto &conn = plainGroupConns[child];
1555
                if(!conn.connected()) {
1556
                    FC_LOG("new group connection " << getExtendedObject()->getFullName()
1557
                            << " -> " << child->getFullName());
1558
                    //NOLINTBEGIN
1559
                    conn = child->signalChanged.connect(
1560
                            std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2));
1561
                    //NOLINTEND
1562
                }
1563
            }
1564
        }
1565
    }
1566
    for(auto it=plainGroupConns.begin();it!=plainGroupConns.end();) {
1567
        if(!groupSet.count(it->first))
1568
            it = plainGroupConns.erase(it);
1569
        else
1570
            ++it;
1571
    }
1572
    if(children != _ChildCache.getValues())
1573
        _ChildCache.setValue(children);
1574
}
1575

1576
void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop) {
1577
    if(!prop)
1578
        return;
1579

1580
    if(prop == getLinkPlacementProperty() || prop == getPlacementProperty()) {
1581
        auto src = getLinkPlacementProperty();
1582
        auto dst = getPlacementProperty();
1583
        if(src!=prop) std::swap(src,dst);
1584
        if(src && dst) {
1585
            dst->setStatus(Property::User3,true);
1586
            dst->setValue(src->getValue());
1587
            dst->setStatus(Property::User3,false);
1588
        }
1589
    }else if(prop == getScaleProperty()) {
1590
        if(!prop->testStatus(Property::User3) && getScaleVectorProperty()) {
1591
            auto s = getScaleValue();
1592
            auto p = getScaleVectorProperty();
1593
            p->setStatus(Property::User3,true);
1594
            p->setValue(s,s,s);
1595
            p->setStatus(Property::User3,false);
1596
        }
1597
    }else if(prop == getScaleVectorProperty()) {
1598
        if(!prop->testStatus(Property::User3) && getScaleProperty()) {
1599
            const auto &v = getScaleVectorValue();
1600
            if(v.x == v.y && v.x == v.z) {
1601
                auto p = getScaleProperty();
1602
                p->setStatus(Property::User3,true);
1603
                p->setValue(v.x);
1604
                p->setStatus(Property::User3,false);
1605
            }
1606
        }
1607
    }else if(prop == _getShowElementProperty()) {
1608
        if(_getShowElementValue())
1609
            update(parent,_getElementCountProperty());
1610
        else {
1611
            auto objs = getElementListValue();
1612

1613
            // preserve element properties in ourself
1614
            std::vector<Base::Placement> placements;
1615
            placements.reserve(objs.size());
1616
            std::vector<Base::Vector3d> scales;
1617
            scales.reserve(objs.size());
1618
            for(auto obj : objs) {
1619
                auto element = freecad_dynamic_cast<LinkElement>(obj);
1620
                if(element) {
1621
                    placements.push_back(element->Placement.getValue());
1622
                    scales.push_back(element->getScaleVector());
1623
                }else{
1624
                    placements.emplace_back();
1625
                    scales.emplace_back(1,1,1);
1626
                }
1627
            }
1628
            // touch the property again to make sure view provider has been
1629
            // signaled before clearing the elements
1630
            getShowElementProperty()->setStatus(App::Property::User3, true);
1631
            getShowElementProperty()->touch();
1632
            getShowElementProperty()->setStatus(App::Property::User3, false);
1633

1634
            getElementListProperty()->setValues(std::vector<App::DocumentObject*>());
1635

1636
            if(getPlacementListProperty()) {
1637
                getPlacementListProperty()->setStatus(Property::User3, getScaleListProperty() != nullptr);
1638
                getPlacementListProperty()->setValue(placements);
1639
                getPlacementListProperty()->setStatus(Property::User3, false);
1640
            }
1641
            if(getScaleListProperty())
1642
                getScaleListProperty()->setValue(scales);
1643

1644
            for(auto obj : objs) {
1645
                if(obj && obj->isAttachedToDocument())
1646
                    obj->getDocument()->removeObject(obj->getNameInDocument());
1647
            }
1648
        }
1649
    }else if(prop == _getElementCountProperty()) {
1650
        size_t elementCount = getElementCountValue()<0?0:(size_t)getElementCountValue();
1651

1652
        auto propVis = getVisibilityListProperty();
1653
        if(propVis) {
1654
            if(propVis->getSize()>(int)elementCount)
1655
                propVis->setSize(getElementCountValue(),true);
1656
        }
1657

1658
        if(!_getShowElementValue()) {
1659
            if(getScaleListProperty()) {
1660
                auto scales = getScaleListValue();
1661
                scales.resize(elementCount,Base::Vector3d(1,1,1));
1662
                getScaleListProperty()->setStatus(Property::User3,true);
1663
                getScaleListProperty()->setValue(scales);
1664
                getScaleListProperty()->setStatus(Property::User3,false);
1665
            }
1666
            if(getPlacementListProperty()) {
1667
                auto placements = getPlacementListValue();
1668
                if(placements.size()<elementCount) {
1669
                    for(size_t i=placements.size();i<elementCount;++i)
1670
                        placements.emplace_back(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation());
1671
                }else
1672
                    placements.resize(elementCount);
1673
                getPlacementListProperty()->setStatus(Property::User3,true);
1674
                getPlacementListProperty()->setValue(placements);
1675
                getPlacementListProperty()->setStatus(Property::User3,false);
1676
            }
1677
        }else if(getElementListProperty()) {
1678
            auto objs = getElementListValue();
1679
            if(elementCount>objs.size()) {
1680
                std::string name = parent->getNameInDocument();
1681
                auto doc = parent->getDocument();
1682
                name += "_i";
1683
                name = doc->getUniqueObjectName(name.c_str());
1684
                if(name[name.size()-1] != 'i')
1685
                    name += "_i";
1686
                auto offset = name.size();
1687
                auto placementProp = getPlacementListProperty();
1688
                auto scaleProp = getScaleListProperty();
1689
                const auto &vis = getVisibilityListValue();
1690

1691
                auto owner = getContainer();
1692
                long ownerID = owner?owner->getID():0;
1693

1694
                for(size_t i=objs.size();i<elementCount;++i) {
1695
                    name.resize(offset);
1696
                    name += std::to_string(i);
1697

1698
                    // It is possible to have orphan LinkElement here due to,
1699
                    // for example, undo and redo. So we try to re-claim the
1700
                    // children element first.
1701
                    auto obj = freecad_dynamic_cast<LinkElement>(doc->getObject(name.c_str()));
1702
                    if(obj && (!obj->_LinkOwner.getValue() || obj->_LinkOwner.getValue()==ownerID)) {
1703
                        obj->Visibility.setValue(false);
1704
                    } else {
1705
                        obj = new LinkElement;
1706
                        parent->getDocument()->addObject(obj,name.c_str());
1707
                    }
1708

1709
                    if(vis.size()>i && !vis[i])
1710
                        myHiddenElements.insert(obj);
1711

1712
                    if(placementProp && placementProp->getSize()>(int)i)
1713
                        obj->Placement.setValue(placementProp->getValues()[i]);
1714
                    else{
1715
                        Base::Placement pla(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation());
1716
                        obj->Placement.setValue(pla);
1717
                    }
1718
                    if(scaleProp && scaleProp->getSize()>(int)i)
1719
                        obj->Scale.setValue(scaleProp->getValues()[i].x);
1720
                    else
1721
                        obj->Scale.setValue(1);
1722
                    objs.push_back(obj);
1723
                }
1724
                if(getPlacementListProperty())
1725
                    getPlacementListProperty()->setSize(0);
1726
                if(getScaleListProperty())
1727
                    getScaleListProperty()->setSize(0);
1728

1729
                getElementListProperty()->setValue(objs);
1730

1731
            }else if(elementCount<objs.size()){
1732
                std::vector<App::DocumentObject*> tmpObjs;
1733
                auto owner = getContainer();
1734
                long ownerID = owner?owner->getID():0;
1735
                while(objs.size()>elementCount) {
1736
                    auto element = freecad_dynamic_cast<LinkElement>(objs.back());
1737
                    if(element && element->_LinkOwner.getValue()==ownerID)
1738
                        tmpObjs.push_back(objs.back());
1739
                    objs.pop_back();
1740
                }
1741
                getElementListProperty()->setValue(objs);
1742
                for(auto obj : tmpObjs) {
1743
                    if(obj && obj->isAttachedToDocument())
1744
                        obj->getDocument()->removeObject(obj->getNameInDocument());
1745
                }
1746
            }
1747
        }
1748
    }else if(prop == getVisibilityListProperty()) {
1749
        if(_getShowElementValue()) {
1750
            const auto &elements = _getElementListValue();
1751
            const auto &vis = getVisibilityListValue();
1752
            myHiddenElements.clear();
1753
            for(size_t i=0;i<vis.size();++i) {
1754
                if(i>=elements.size())
1755
                    break;
1756
                if(!vis[i])
1757
                    myHiddenElements.insert(elements[i]);
1758
            }
1759
        }
1760
    }else if(prop == getElementListProperty() || prop == &_ChildCache) {
1761

1762
        if(prop == getElementListProperty()) {
1763
            _ChildCache.setStatus(Property::User3,true);
1764
            updateGroup();
1765
            _ChildCache.setStatus(Property::User3,false);
1766
        }
1767

1768
        const auto &elements = _getElementListValue();
1769

1770
        if(enableLabelCache)
1771
            myLabelCache.clear();
1772

1773
        // Element list changed, we need to sychrnoize VisibilityList.
1774
        if(_getShowElementValue() && getVisibilityListProperty()) {
1775
            if(parent->getDocument()->isPerformingTransaction()) {
1776
                update(parent,getVisibilityListProperty());
1777
            }else{
1778
                boost::dynamic_bitset<> vis;
1779
                vis.resize(elements.size(),true);
1780
                std::unordered_set<const App::DocumentObject *> hiddenElements;
1781
                for(size_t i=0;i<elements.size();++i) {
1782
                    if(myHiddenElements.find(elements[i])!=myHiddenElements.end()) {
1783
                        hiddenElements.insert(elements[i]);
1784
                        vis[i] = false;
1785
                    }
1786
                }
1787
                myHiddenElements.swap(hiddenElements);
1788
                if(vis != getVisibilityListValue()) {
1789
                    auto propVis = getVisibilityListProperty();
1790
                    propVis->setStatus(Property::User3,true);
1791
                    propVis->setValue(vis);
1792
                    propVis->setStatus(Property::User3,false);
1793
                }
1794
            }
1795
        }
1796
        syncElementList();
1797
        if(_getShowElementValue()
1798
                && _getElementCountProperty()
1799
                && getElementListProperty()
1800
                && getElementCountValue()!=getElementListProperty()->getSize())
1801
        {
1802
            getElementCountProperty()->setValue(
1803
                    getElementListProperty()->getSize());
1804
        }
1805
    }else if(prop == getLinkedObjectProperty()) {
1806
        auto group = linkedPlainGroup();
1807
        if(getShowElementProperty())
1808
            getShowElementProperty()->setStatus(Property::Hidden, !!group);
1809
        if(getElementCountProperty())
1810
            getElementCountProperty()->setStatus(Property::Hidden, !!group);
1811
        if(group)
1812
            updateGroup();
1813
        else if(_ChildCache.getSize())
1814
            _ChildCache.setValue();
1815
        parseSubName();
1816
        syncElementList();
1817

1818
        if(getLinkCopyOnChangeValue()==CopyOnChangeOwned
1819
                && !pauseCopyOnChange
1820
                && !parent->getDocument()->isPerformingTransaction())
1821
            getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled);
1822
        else
1823
            setupCopyOnChange(parent, true);
1824

1825
    }else if(prop == getLinkCopyOnChangeProperty()) {
1826
        setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr);
1827
    } else if (prop == getLinkCopyOnChangeSourceProperty()) {
1828
        if (auto source = getLinkCopyOnChangeSourceValue()) {
1829
            this->connCopyOnChangeSource = source->signalChanged.connect(
1830
                [this](const DocumentObject & obj, const Property &prop) {
1831
                    auto src = getLinkCopyOnChangeSourceValue();
1832
                    if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
1833
                        return;
1834
                    if (App::Document::isAnyRestoring()
1835
                            || obj.testStatus(ObjectStatus::NoTouch)
1836
                            || (prop.getType() & Prop_Output)
1837
                            || prop.testStatus(Property::Output))
1838
                        return;
1839
                    if (auto propTouch = getLinkCopyOnChangeTouchedProperty())
1840
                        propTouch->setValue(true);
1841
                });
1842
        } else
1843
            this->connCopyOnChangeSource.disconnect();
1844

1845
    }else if(prop == getLinkTransformProperty()) {
1846
        auto linkPlacement = getLinkPlacementProperty();
1847
        auto placement = getPlacementProperty();
1848
        if(linkPlacement && placement) {
1849
            bool transform = getLinkTransformValue();
1850
            placement->setStatus(Property::Hidden,transform);
1851
            linkPlacement->setStatus(Property::Hidden,!transform);
1852
        }
1853
        syncElementList();
1854

1855
    } else {
1856
        checkCopyOnChange(parent, *prop);
1857
    }
1858
}
1859

1860
void LinkBaseExtension::cacheChildLabel(int enable) const {
1861
    enableLabelCache = enable?true:false;
1862
    myLabelCache.clear();
1863
    if(enable<=0)
1864
        return;
1865

1866
    int idx = 0;
1867
    for(auto child : _getElementListValue()) {
1868
        if(child && child->isAttachedToDocument())
1869
            myLabelCache[child->Label.getStrValue()] = idx;
1870
        ++idx;
1871
    }
1872
}
1873

1874
bool LinkBaseExtension::linkTransform() const {
1875
    if(!getLinkTransformProperty() &&
1876
       !getLinkPlacementProperty() &&
1877
       !getPlacementProperty())
1878
        return true;
1879
    return getLinkTransformValue();
1880
}
1881

1882
void LinkBaseExtension::syncElementList() {
1883
    auto transform = getLinkTransformProperty();
1884
    auto link = getLinkedObjectProperty();
1885
    auto xlink = freecad_dynamic_cast<const PropertyXLink>(link);
1886

1887
    auto owner = getContainer();
1888
    auto ownerID = owner?owner->getID():0;
1889
    auto elements = getElementListValue();
1890
    for (auto i : elements) {
1891
        auto element = freecad_dynamic_cast<LinkElement>(i);
1892
        if (!element
1893
            || (element->_LinkOwner.getValue()
1894
                && element->_LinkOwner.getValue() != ownerID))
1895
            continue;
1896

1897
        element->_LinkOwner.setValue(ownerID);
1898

1899
        element->LinkTransform.setStatus(Property::Hidden, transform != nullptr);
1900
        element->LinkTransform.setStatus(Property::Immutable, transform != nullptr);
1901
        if (transform && element->LinkTransform.getValue() != transform->getValue())
1902
            element->LinkTransform.setValue(transform->getValue());
1903

1904
        element->LinkedObject.setStatus(Property::Hidden, link != nullptr);
1905
        element->LinkedObject.setStatus(Property::Immutable, link != nullptr);
1906
        if (element->LinkCopyOnChange.getValue() == 2)
1907
            continue;
1908
        if (xlink) {
1909
            if (element->LinkedObject.getValue() != xlink->getValue()
1910
                || element->LinkedObject.getSubValues() != xlink->getSubValues()) {
1911
                element->LinkedObject.setValue(xlink->getValue(), xlink->getSubValues());
1912
            }
1913
        }
1914
        else if (element->LinkedObject.getValue() != link->getValue()
1915
                 || !element->LinkedObject.getSubValues().empty()) {
1916
            element->setLink(-1, link->getValue());
1917
        }
1918
    }
1919
}
1920

1921
void LinkBaseExtension::onExtendedDocumentRestored() {
1922
    inherited::onExtendedDocumentRestored();
1923
    myHiddenElements.clear();
1924
    auto parent = getContainer();
1925
    if(!parent)
1926
        return;
1927
    if(hasOldSubElement) {
1928
        hasOldSubElement = false;
1929
        // SubElements was stored as a PropertyStringList. It is now migrated to be
1930
        // stored inside PropertyXLink.
1931
        auto xlink = freecad_dynamic_cast<PropertyXLink>(getLinkedObjectProperty());
1932
        if(!xlink)
1933
            FC_ERR("Failed to restore SubElements for " << parent->getFullName());
1934
        else if(!xlink->getValue())
1935
            FC_ERR("Discard SubElements of " << parent->getFullName() << " due to null link");
1936
        else if(xlink->getSubValues().size() > 1)
1937
            FC_ERR("Failed to restore SubElements for " << parent->getFullName()
1938
                    << " due to conflict subnames");
1939
        else if(xlink->getSubValues().empty()) {
1940
            auto subs = xlink->getSubValues();
1941
            xlink->setSubValues(std::move(subs));
1942
        } else {
1943
            std::set<std::string> subset(mySubElements.begin(),mySubElements.end());
1944
            auto sub = xlink->getSubValues().front();
1945
            auto element = Data::findElementName(sub.c_str());
1946
            if(element && element[0]) {
1947
                subset.insert(element);
1948
                sub.resize(element - sub.c_str());
1949
            }
1950
            std::vector<std::string> subs;
1951
            for(const auto &s : subset)
1952
                subs.push_back(sub + s);
1953
            xlink->setSubValues(std::move(subs));
1954
        }
1955
    }
1956
    if(getScaleVectorProperty() && getScaleProperty()) {
1957
        // Scale vector is added later. The code here is for migration.
1958
        const auto &v = getScaleVectorValue();
1959
        double s = getScaleValue();
1960
        if(v.x == v.y && v.x == v.z && v.x != s)
1961
            getScaleVectorProperty()->setValue(s,s,s);
1962
    }
1963
    update(parent,getVisibilityListProperty());
1964
    if (auto prop = getLinkedObjectProperty()) {
1965
        Base::StateLocker guard(pauseCopyOnChange);
1966
        update(parent,prop);
1967
    }
1968
    update(parent,getLinkCopyOnChangeSourceProperty());
1969
    update(parent,getElementListProperty());
1970
    if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
1971
        monitorOnChangeCopyObjects(getOnChangeCopyObjects());
1972
}
1973

1974
void LinkBaseExtension::_handleChangedPropertyName(
1975
        Base::XMLReader &reader, const char * TypeName, const char *PropName)
1976
{
1977
    if(strcmp(PropName,"SubElements")==0
1978
        && strcmp(TypeName,PropertyStringList::getClassTypeId().getName())==0)
1979
    {
1980
        PropertyStringList prop;
1981
        prop.setContainer(getContainer());
1982
        prop.Restore(reader);
1983
        if(prop.getSize()) {
1984
            mySubElements = prop.getValues();
1985
            hasOldSubElement = true;
1986
        }
1987
    }
1988
}
1989

1990
void LinkBaseExtension::setLink(int index, DocumentObject *obj,
1991
    const char *subname, const std::vector<std::string> &subElements)
1992
{
1993
    auto parent = getContainer();
1994
    if(!parent)
1995
        LINK_THROW(Base::RuntimeError,"No parent container");
1996

1997
    if(obj && !App::Document::isAnyRestoring()) {
1998
        auto inSet = parent->getInListEx(true);
1999
        inSet.insert(parent);
2000
        if(inSet.find(obj)!=inSet.end())
2001
            LINK_THROW(Base::RuntimeError,"Cyclic dependency");
2002
    }
2003

2004
    auto linkProp = getLinkedObjectProperty();
2005

2006
    // If we are a group (i.e. no LinkObject property), and the index is
2007
    // negative with a non-zero 'obj' assignment, we treat this as group
2008
    // expansion by changing the index to one pass the existing group size
2009
    if(index<0 && obj && !linkProp && getElementListProperty())
2010
        index = getElementListProperty()->getSize();
2011

2012
    if(index>=0) {
2013
        // LinkGroup assignment
2014

2015
        if(linkProp || !getElementListProperty())
2016
            LINK_THROW(Base::RuntimeError,"Cannot set link element");
2017

2018
        DocumentObject *old = nullptr;
2019
        const auto &elements = getElementListProperty()->getValues();
2020
        if(!obj) {
2021
            if(index>=(int)elements.size())
2022
                LINK_THROW(Base::ValueError,"Link element index out of bound");
2023
            std::vector<DocumentObject*> objs;
2024
            old = elements[index];
2025
            for(int i=0;i<(int)elements.size();++i) {
2026
                if(i!=index)
2027
                    objs.push_back(elements[i]);
2028
            }
2029
            getElementListProperty()->setValue(objs);
2030
        }else if(!obj->isAttachedToDocument())
2031
            LINK_THROW(Base::ValueError,"Invalid object");
2032
        else{
2033
            if(index>(int)elements.size())
2034
                LINK_THROW(Base::ValueError,"Link element index out of bound");
2035

2036
            if(index < (int)elements.size())
2037
                old = elements[index];
2038

2039
            int idx = -1;
2040
            if(getLinkModeValue()>=LinkModeAutoLink ||
2041
               (subname && subname[0]) ||
2042
               !subElements.empty() ||
2043
               obj->getDocument()!=parent->getDocument() ||
2044
               (getElementListProperty()->find(obj->getNameInDocument(),&idx) && idx!=index))
2045
            {
2046
                std::string name = parent->getDocument()->getUniqueObjectName("Link");
2047
                auto link = new Link;
2048
                link->_LinkOwner.setValue(parent->getID());
2049
                parent->getDocument()->addObject(link,name.c_str());
2050
                link->setLink(-1,obj,subname,subElements);
2051
                auto linked = link->getTrueLinkedObject(true);
2052
                if(linked)
2053
                    link->Label.setValue(linked->Label.getValue());
2054
                auto pla = freecad_dynamic_cast<PropertyPlacement>(obj->getPropertyByName("Placement"));
2055
                if(pla)
2056
                    link->Placement.setValue(pla->getValue());
2057
                link->Visibility.setValue(false);
2058
                obj = link;
2059
            }
2060

2061
            if(old == obj)
2062
                return;
2063

2064
            getElementListProperty()->set1Value(index,obj);
2065
        }
2066
        detachElement(old);
2067
        return;
2068
    }
2069

2070
    if(!linkProp) {
2071
        // Reaching here means, we are group (i.e. no LinkedObject), and
2072
        // index<0, and 'obj' is zero. We shall clear the whole group
2073

2074
        if(obj || !getElementListProperty())
2075
            LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured");
2076
        detachElements();
2077
        return;
2078
    }
2079

2080
    // Here means we are assigning a Link
2081

2082
    auto xlink = freecad_dynamic_cast<PropertyXLink>(linkProp);
2083
    if(obj) {
2084
        if(!obj->isAttachedToDocument())
2085
            LINK_THROW(Base::ValueError,"Invalid document object");
2086
        if(!xlink) {
2087
            if(parent && obj->getDocument()!=parent->getDocument())
2088
                LINK_THROW(Base::ValueError,"Cannot link to external object without PropertyXLink");
2089
        }
2090
    }
2091

2092
    if(!xlink) {
2093
        if(!subElements.empty() || (subname && subname[0]))
2094
            LINK_THROW(Base::RuntimeError,"SubName/SubElement link requires PropertyXLink");
2095
        linkProp->setValue(obj);
2096
        return;
2097
    }
2098

2099
    std::vector<std::string> subs;
2100
    if(!subElements.empty()) {
2101
        subs.reserve(subElements.size());
2102
        for(const auto &s : subElements) {
2103
            subs.emplace_back(subname?subname:"");
2104
            subs.back() += s;
2105
        }
2106
    } else if(subname && subname[0])
2107
        subs.emplace_back(subname);
2108
    xlink->setValue(obj,std::move(subs));
2109
}
2110

2111
void LinkBaseExtension::detachElements()
2112
{
2113
    std::vector<App::DocumentObjectT> objs;
2114
    for (auto obj : getElementListValue())
2115
        objs.emplace_back(obj);
2116
    getElementListProperty()->setValue();
2117
    for(const auto &objT : objs)
2118
        detachElement(objT.getObject());
2119
}
2120

2121
void LinkBaseExtension::detachElement(DocumentObject *obj) {
2122
    if(!obj || !obj->isAttachedToDocument() || obj->isRemoving())
2123
        return;
2124
    auto ext = obj->getExtensionByType<LinkBaseExtension>(true);
2125
    auto owner = getContainer();
2126
    long ownerID = owner?owner->getID():0;
2127
    if(getLinkModeValue()==LinkModeAutoUnlink) {
2128
        if(!ext || ext->_LinkOwner.getValue()!=ownerID)
2129
            return;
2130
    }else if(getLinkModeValue()!=LinkModeAutoDelete) {
2131
        if(ext && ext->_LinkOwner.getValue()==ownerID)
2132
            ext->_LinkOwner.setValue(0);
2133
        return;
2134
    }
2135
    obj->getDocument()->removeObject(obj->getNameInDocument());
2136
}
2137

2138
std::vector<App::DocumentObject*> LinkBaseExtension::getLinkedChildren(bool filter) const{
2139
    if(!filter)
2140
        return _getElementListValue();
2141
    std::vector<App::DocumentObject*> ret;
2142
    for(auto o : _getElementListValue()) {
2143
        if(!o->hasExtension(GroupExtension::getExtensionClassTypeId(),false))
2144
            ret.push_back(o);
2145
    }
2146
    return ret;
2147
}
2148

2149
const char *LinkBaseExtension::flattenSubname(const char *subname) const {
2150
    if(subname && _ChildCache.getSize()) {
2151
        const char *sub = subname;
2152
        std::string s;
2153
        for(const char* dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) {
2154
            DocumentObject *obj = nullptr;
2155
            s.clear();
2156
            s.append(sub,dot+1);
2157
            extensionGetSubObject(obj,s.c_str());
2158
            if(!obj)
2159
                break;
2160
            if(!obj->hasExtension(GroupExtension::getExtensionClassTypeId(),false))
2161
                return sub;
2162
        }
2163
    }
2164
    return subname;
2165
}
2166

2167
void LinkBaseExtension::expandSubname(std::string &subname) const {
2168
    if(!_ChildCache.getSize())
2169
        return;
2170

2171
    const char *pos = nullptr;
2172
    int index = getElementIndex(subname.c_str(),&pos);
2173
    if(index<0)
2174
        return;
2175
    std::ostringstream ss;
2176
    elementNameFromIndex(index,ss);
2177
    ss << pos;
2178
    subname = ss.str();
2179
}
2180

2181
static bool isExcludedProperties(const char *name) {
2182
#define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true;
2183
    CHECK_EXCLUDE_PROP(Shape);
2184
    CHECK_EXCLUDE_PROP(Proxy);
2185
    CHECK_EXCLUDE_PROP(Placement);
2186
    return false;
2187
}
2188

2189
Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const {
2190
    if (checkingProperty)
2191
        return inherited::extensionGetPropertyByName(name);
2192
    Base::StateLocker guard(checkingProperty);
2193
    if(isExcludedProperties(name))
2194
        return nullptr;
2195
    auto owner = getContainer();
2196
    if (owner) {
2197
        App::Property *prop = owner->getPropertyByName(name);
2198
        if(prop)
2199
            return prop;
2200
        if(owner->canLinkProperties()) {
2201
            auto linked = getTrueLinkedObject(true);
2202
            if(linked)
2203
                return linked->getPropertyByName(name);
2204
        }
2205
    }
2206
    return nullptr;
2207
}
2208

2209
bool LinkBaseExtension::isLinkMutated() const
2210
{
2211
    return getLinkCopyOnChangeValue() != CopyOnChangeDisabled
2212
        && getLinkedObjectValue()
2213
        && (!getLinkCopyOnChangeSourceValue()
2214
            || (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue()));
2215
}
2216

2217
///////////////////////////////////////////////////////////////////////////////////////////
2218

2219
namespace App {
2220
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkBaseExtensionPython, App::LinkBaseExtension)
2221

2222
// explicit template instantiation
2223
template class AppExport ExtensionPythonT<LinkBaseExtension>;
2224

2225
}
2226

2227
//////////////////////////////////////////////////////////////////////////////
2228

2229
EXTENSION_PROPERTY_SOURCE(App::LinkExtension, App::LinkBaseExtension)
2230

2231
LinkExtension::LinkExtension()
2232
{
2233
    initExtensionType(LinkExtension::getExtensionClassTypeId());
2234

2235
    LINK_PROPS_ADD_EXTENSION(LINK_PARAMS_EXT);
2236
}
2237

2238
///////////////////////////////////////////////////////////////////////////////////////////
2239

2240
namespace App {
2241
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkExtensionPython, App::LinkExtension)
2242

2243
// explicit template instantiation
2244
template class AppExport ExtensionPythonT<App::LinkExtension>;
2245

2246
}
2247

2248
///////////////////////////////////////////////////////////////////////////////////////////
2249

2250
PROPERTY_SOURCE_WITH_EXTENSIONS(App::Link, App::DocumentObject)
2251

2252
Link::Link() {
2253
    LINK_PROPS_ADD(LINK_PARAMS_LINK);
2254
    LinkExtension::initExtension(this);
2255
    static const PropertyIntegerConstraint::Constraints s_constraints = {0,INT_MAX,1};
2256
    ElementCount.setConstraints(&s_constraints);
2257
}
2258

2259
bool Link::canLinkProperties() const {
2260
    return true;
2261
}
2262

2263
//////////////////////////////////////////////////////////////////////////////////////////
2264

2265
namespace App {
2266
PROPERTY_SOURCE_TEMPLATE(App::LinkPython, App::Link)
2267
template<> const char* App::LinkPython::getViewProviderName() const {
2268
    return "Gui::ViewProviderLinkPython";
2269
}
2270
template class AppExport FeaturePythonT<App::Link>;
2271
}
2272

2273
//////////////////////////////////////////////////////////////////////////////////////////
2274

2275
PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkElement, App::DocumentObject)
2276

2277
LinkElement::LinkElement() {
2278
    LINK_PROPS_ADD(LINK_PARAMS_ELEMENT);
2279
    LinkBaseExtension::initExtension(this);
2280
}
2281

2282
bool LinkElement::canDelete() const {
2283
    if(!_LinkOwner.getValue())
2284
        return true;
2285

2286
    auto owner = getContainer();
2287
    return !owner || !owner->getDocument()->getObjectByID(_LinkOwner.getValue());
2288
}
2289

2290
//////////////////////////////////////////////////////////////////////////////////////////
2291

2292
namespace App {
2293
PROPERTY_SOURCE_TEMPLATE(App::LinkElementPython, App::LinkElement)
2294
template<> const char* App::LinkElementPython::getViewProviderName() const {
2295
    return "Gui::ViewProviderLinkPython";
2296
}
2297
template class AppExport FeaturePythonT<App::LinkElement>;
2298
}
2299

2300
//////////////////////////////////////////////////////////////////////////////////////////
2301

2302
PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkGroup, App::DocumentObject)
2303

2304
LinkGroup::LinkGroup() {
2305
    LINK_PROPS_ADD(LINK_PARAMS_GROUP);
2306
    LinkBaseExtension::initExtension(this);
2307
}
2308

2309
//////////////////////////////////////////////////////////////////////////////////////////
2310

2311
namespace App {
2312
PROPERTY_SOURCE_TEMPLATE(App::LinkGroupPython, App::LinkGroup)
2313
template<> const char* App::LinkGroupPython::getViewProviderName() const {
2314
    return "Gui::ViewProviderLinkPython";
2315
}
2316
template class AppExport FeaturePythonT<App::LinkGroup>;
2317
}
2318

2319
#if defined(__clang__)
2320
# pragma clang diagnostic pop
2321
#endif
2322

2323

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

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

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

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