FreeCAD

Форк
0
/
Link.cpp 
2376 строк · 87.3 Кб
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 PropLinkCopyOnChangeSource:
241
    case PropLinkCopyOnChangeGroup:
242
        if (auto linkProp = Base::freecad_dynamic_cast<PropertyLinkBase>(prop)) {
243
            linkProp->setScope(LinkScope::Global);
244
        }
245
        // fall through
246
    case PropLinkCopyOnChangeTouched:
247
        prop->setStatus(Property::Hidden, true);
248
        break;
249
    case PropLinkTransform:
250
    case PropLinkPlacement:
251
    case PropPlacement:
252
        if(getLinkTransformProperty() &&
253
           getLinkPlacementProperty() &&
254
           getPlacementProperty())
255
        {
256
            bool transform = getLinkTransformValue();
257
            getPlacementProperty()->setStatus(Property::Hidden, transform);
258
            getLinkPlacementProperty()->setStatus(Property::Hidden, !transform);
259
        }
260
        break;
261
    case PropElementList:
262
        getElementListProperty()->setScope(LinkScope::Global);
263
        getElementListProperty()->setStatus(Property::Hidden, true);
264
        // fall through
265
    case PropLinkedObject:
266
        // Make ElementList as read-only if we are not a group (i.e. having
267
        // LinkedObject property), because it is for holding array elements.
268
        if(getElementListProperty()) {
269
            getElementListProperty()->setStatus(
270
                Property::Immutable, getLinkedObjectProperty() != nullptr);
271
        }
272
        if (auto linkProp = getLinkedObjectProperty()) {
273
            linkProp->setScope(LinkScope::Global);
274
        }
275
        break;
276
    case PropVisibilityList:
277
        getVisibilityListProperty()->setStatus(Property::Immutable, true);
278
        getVisibilityListProperty()->setStatus(Property::Hidden, true);
279
        break;
280
    }
281

282
    if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_TRACE)) {
283
        const char *propName;
284
        if(!prop)
285
            propName = "<null>";
286
        else if(prop->getContainer())
287
            propName = prop->getName();
288
        else
289
            propName = extensionGetPropertyName(prop);
290
        if(!Property::isValidName(propName))
291
            propName = "?";
292
        FC_TRACE("set property " << infos[idx].name << ": " << propName);
293
    }
294
}
295

296
static const char _GroupPrefix[] = "Configuration (";
297

298
App::DocumentObjectExecReturn *LinkBaseExtension::extensionExecute() {
299
    // The actual value of LinkTouched is not important, just to notify view
300
    // provider that the link (in fact, its dependents, i.e. linked ones) have
301
    // recomputed.
302
    _LinkTouched.touch();
303

304
    if(getLinkedObjectProperty()) {
305
        DocumentObject *linked = getTrueLinkedObject(true);
306
        if(!linked) {
307
            std::ostringstream ss;
308
            ss << "Link broken!";
309
            auto xlink = Base::freecad_dynamic_cast<PropertyXLink>(
310
                    getLinkedObjectProperty());
311
            if (xlink) {
312
                const char *objname = xlink->getObjectName();
313
                if (objname && objname[0])
314
                    ss << "\nObject: " << objname;
315
                const char *filename = xlink->getFilePath();
316
                if (filename && filename[0])
317
                    ss << "\nFile: " << filename;
318
            }
319
            return new App::DocumentObjectExecReturn(ss.str().c_str());
320
        }
321

322
        App::DocumentObject *container = getContainer();
323
        auto source = getLinkCopyOnChangeSourceValue();
324
        if (source && getLinkCopyOnChangeValue() == CopyOnChangeTracking
325
                   && getLinkCopyOnChangeTouchedValue())
326
        {
327
            syncCopyOnChange();
328
        }
329

330
        // the previous linked object could be deleted by syncCopyOnChange - #12281
331
        linked = getTrueLinkedObject(true);
332
        if(!linked) {
333
            return new App::DocumentObjectExecReturn("Error in processing variable link");
334
        }
335

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

387
        auto parent = getContainer();
388
        setupCopyOnChange(parent);
389

390
        if(hasCopyOnChange && getLinkCopyOnChangeValue()==CopyOnChangeDisabled) {
391
            hasCopyOnChange = false;
392
            std::vector<Property*> props;
393
            parent->getPropertyList(props);
394
            for(auto prop : props) {
395
                if(isCopyOnChangeProperty(parent, *prop)) {
396
                    try {
397
                        parent->removeDynamicProperty(prop->getName());
398
                    } catch (Base::Exception &e) {
399
                        e.ReportException();
400
                    } catch (...) {
401
                    }
402
                }
403
            }
404
        }
405
    }
406
    return inherited::extensionExecute();
407
}
408

409
short LinkBaseExtension::extensionMustExecute() {
410
    auto link = getLink();
411
    if(!link)
412
        return 0;
413
    return link->mustExecute();
414
}
415

416
std::vector<App::DocumentObject*>
417
LinkBaseExtension::getOnChangeCopyObjects(
418
        std::vector<App::DocumentObject *> *excludes,
419
        App::DocumentObject *src)
420
{
421
    auto parent = getContainer();
422
    if (!src)
423
        src = getLinkCopyOnChangeSourceValue();
424
    if (!src || getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
425
        return {};
426

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

459
void LinkBaseExtension::setOnChangeCopyObject(
460
        App::DocumentObject *obj, OnChangeCopyOptions options)
461
{
462
    auto parent = getContainer();
463
    Base::Flags<OnChangeCopyOptions> flags(options);
464
    bool exclude = flags.testFlag(OnChangeCopyOptions::Exclude);
465
    bool external = parent->getDocument() != obj->getDocument();
466
    auto prop = Base::freecad_dynamic_cast<PropertyMap>(
467
            obj->getPropertyByName("_CopyOnChangeControl"));
468

469
    if (external == exclude && !prop)
470
        return;
471

472
    if (!prop) {
473
        try {
474
            prop = static_cast<PropertyMap*>(
475
                    obj->addDynamicProperty("App::PropertyMap", "_CopyOnChangeControl"));
476
        } catch (Base::Exception &e) {
477
            e.ReportException();
478
        }
479
        if (!prop) {
480
            FC_ERR("Failed to setup copy on change object " << obj->getFullName());
481
            return;
482
        }
483
    }
484

485
    const char *key = flags.testFlag(OnChangeCopyOptions::ApplyAll) ? "*" : parent->getDagKey();
486
    if (external)
487
        prop->setValue(key, exclude ? "" : "+");
488
    else
489
        prop->setValue(key, exclude ? "-" : "");
490
}
491

492
// The purpose of this function is to synchronize the mutated copy to the
493
// original linked CopyOnChange object. It will make a new copy if any of the
494
// non-CopyOnChange property of the original object has changed.
495
void LinkBaseExtension::syncCopyOnChange()
496
{
497
    if (!getLinkCopyOnChangeValue())
498
        return;
499
    auto linkProp = getLinkedObjectProperty();
500
    auto srcProp = getLinkCopyOnChangeSourceProperty();
501
    auto srcTouched = getLinkCopyOnChangeTouchedProperty();
502
    if (!linkProp
503
            || !srcProp
504
            || !srcTouched
505
            || !srcProp->getValue()
506
            || !linkProp->getValue()
507
            || srcProp->getValue() == linkProp->getValue())
508
        return;
509

510
    auto parent = getContainer();
511

512
    auto linked = linkProp->getValue();
513

514
    std::vector<App::DocumentObjectT> oldObjs;
515
    std::vector<App::DocumentObject*> objs;
516

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

548
    // Obtain the original linked object and its dependency in depending order.
549
    // The last being the original linked object.
550
    auto srcObjs = getOnChangeCopyObjects();
551
    // Refresh signal connection to monitor changes
552
    monitorOnChangeCopyObjects(srcObjs);
553

554
    // Copy the objects. Document::export/importObjects() (called by
555
    // copyObject()) will generate a _ObjectUUID for each source object and
556
    // match it with a _SourceUUID in the copy.
557
    auto copiedObjs = parent->getDocument()->copyObject(srcObjs);
558
    if(copiedObjs.empty())
559
        return;
560

561
    // copyObject() will return copy in order of the same order of the input,
562
    // so the last object will be the copy of the original linked object
563
    auto newLinked = copiedObjs.back();
564

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

581
    if (copyOnChangeGroup) {
582
        // The order of the copied objects is in dependency order (because of
583
        // getOnChangeCopyObjects()). We reverse it here so that we can later
584
        // on delete it in reverse order to avoid error (because some parent
585
        // objects may want to delete their own children).
586
        std::reverse(copiedObjs.begin(), copiedObjs.end());
587
        copyOnChangeGroup->ElementList.setValues(copiedObjs);
588
    }
589

590
    // Create a map to find the corresponding replacement of the new copies to
591
    // the mutated object. The reason for doing so is that we are copying from
592
    // the original linked object and its dependency, not the mutated objects
593
    // which are old copies. There could be arbitrary changes in the originals
594
    // which may add or remove or change depending orders, while the
595
    // replacement happens between the new and old copies.
596

597
    std::map<Base::Uuid, App::DocumentObjectT> newObjs;
598
    for (auto obj : copiedObjs) {
599
        auto prop = Base::freecad_dynamic_cast<PropertyUUID>(
600
                obj->getPropertyByName("_SourceUUID"));
601
        if (prop)
602
            newObjs.insert(std::make_pair(prop->getValue(), obj));
603
    }
604

605
    std::vector<std::pair<App::DocumentObject*, App::DocumentObject*> > replacements;
606
    for (const auto &objT : oldObjs) {
607
        auto prop = Base::freecad_dynamic_cast<PropertyUUID>(objT.getProperty());
608
        if (!prop)
609
            continue;
610
        auto it = newObjs.find(prop->getValue());
611
        if (it == newObjs.end())
612
            continue;
613
        auto oldObj = objT.getObject();
614
        auto newObj = it->second.getObject();
615
        if (oldObj && newObj)
616
            replacements.emplace_back(oldObj, newObj);
617
    }
618

619
    std::vector<std::pair<App::DocumentObjectT, std::unique_ptr<App::Property> > > propChanges;
620
    if (!replacements.empty()) {
621
        std::sort(copiedObjs.begin(), copiedObjs.end());
622

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

652
    Base::StateLocker guard(pauseCopyOnChange);
653
    linkProp->setValue(newLinked);
654
    newLinked->Visibility.setValue(false);
655
    srcTouched->setValue(false);
656

657
    // Apply the global link changes.
658
    for(const auto &v : propChanges) {
659
        auto prop = v.first.getProperty();
660
        if(prop)
661
            prop->Paste(*v.second.get());
662
    }
663

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

675
bool LinkBaseExtension::isLinkedToConfigurableObject() const
676
{
677
    if (auto linked = getLinkedObjectValue()) {
678
        std::vector<App::Property*> propList;
679
        linked->getPropertyList(propList);
680
        for (auto prop : propList) {
681
            if(prop->testStatus(Property::CopyOnChange)
682
                    && prop->getContainer()==linked)
683
                return true;
684
        }
685
    }
686
    return false;
687
}
688

689
bool LinkBaseExtension::isCopyOnChangeProperty(DocumentObject *obj, const App::Property &prop) {
690
    if(obj!=prop.getContainer() || !prop.testStatus(App::Property::PropDynamic))
691
        return false;
692
    auto group = prop.getGroup();
693
    return group && boost::starts_with(group,_GroupPrefix);
694
}
695

696
void LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, bool checkSource) {
697
    copyOnChangeConns.clear();
698
    copyOnChangeSrcConns.clear();
699

700
    auto linked = getTrueLinkedObject(false);
701
    if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
702
        return;
703

704
    if (checkSource && !pauseCopyOnChange) {
705
        PropertyLink *source = getLinkCopyOnChangeSourceProperty();
706
        if (source) {
707
            source->setValue(linked);
708
            if (auto touched = getLinkCopyOnChangeTouchedProperty())
709
                touched->setValue(false);
710
        }
711
    }
712

713
    hasCopyOnChange = setupCopyOnChange(parent,linked,&copyOnChangeConns,hasCopyOnChange);
714
    if (hasCopyOnChange && getLinkCopyOnChangeValue() == CopyOnChangeOwned
715
            && getLinkedObjectValue()
716
            && getLinkedObjectValue() == getLinkCopyOnChangeSourceValue())
717
    {
718
        makeCopyOnChange();
719
    }
720
}
721

722
bool LinkBaseExtension::setupCopyOnChange(DocumentObject *parent, DocumentObject *linked,
723
        std::vector<boost::signals2::scoped_connection> *copyOnChangeConns, bool checkExisting)
724
{
725
    if(!parent || !linked)
726
        return false;
727

728
    bool res = false;
729

730
    std::unordered_map<Property*, Property*> newProps;
731
    std::vector<Property*> props;
732
    linked->getPropertyList(props);
733
    for(auto prop : props) {
734
        if(!prop->testStatus(Property::CopyOnChange)
735
                || prop->getContainer()!=linked)
736
            continue;
737

738
        res = true;
739

740
        const char* linkedGroupName = prop->getGroup();
741
        if(!linkedGroupName || !linkedGroupName[0])
742
            linkedGroupName = "Base";
743

744
        std::string groupName;
745
        groupName = _GroupPrefix;
746
        if(boost::starts_with(linkedGroupName,_GroupPrefix))
747
            groupName += linkedGroupName + sizeof(_GroupPrefix)-1;
748
        else {
749
            groupName += linkedGroupName;
750
            groupName += ")";
751
        }
752

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

783
    if(checkExisting) {
784
        props.clear();
785
        parent->getPropertyList(props);
786
        for(auto prop : props) {
787
            if(prop->getContainer()!=parent)
788
                continue;
789
            auto gname = prop->getGroup();
790
            if(!gname || !boost::starts_with(gname, _GroupPrefix))
791
                continue;
792
            if(!newProps.count(prop))
793
                parent->removeDynamicProperty(prop->getName());
794
        }
795
    }
796

797
    if(!copyOnChangeConns)
798
        return res;
799

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

819
    return res;
820
}
821

822
void LinkBaseExtension::checkCopyOnChange(App::DocumentObject *parent, const App::Property &prop)
823
{
824
    if(!parent || !parent->getDocument()
825
               || parent->getDocument()->isPerformingTransaction())
826
        return;
827

828
    auto linked = getLinkedObjectValue();
829
    if(!linked || getLinkCopyOnChangeValue()==CopyOnChangeDisabled
830
               || !isCopyOnChangeProperty(parent,prop))
831
        return;
832

833
    if(getLinkCopyOnChangeValue() == CopyOnChangeOwned ||
834
            (getLinkCopyOnChangeValue() == CopyOnChangeTracking
835
             && linked != getLinkCopyOnChangeSourceValue()))
836
    {
837
        auto p = linked->getPropertyByName(prop.getName());
838
        if(p && p->getTypeId()==prop.getTypeId()) {
839
            std::unique_ptr<Property> pcopy(prop.Copy());
840
            if(pcopy)
841
                p->Paste(*pcopy);
842
        }
843
        return;
844
    }
845

846
    auto linkedProp = linked->getPropertyByName(prop.getName());
847
    if(!linkedProp || linkedProp->getTypeId()!=prop.getTypeId() || linkedProp->isSame(prop))
848
        return;
849

850
    auto copied = makeCopyOnChange();
851
    if (copied) {
852
        linkedProp = copied->getPropertyByName(prop.getName());
853
        if(linkedProp && linkedProp->getTypeId()==prop.getTypeId()) {
854
            std::unique_ptr<Property> pcopy(prop.Copy());
855
            if(pcopy)
856
                linkedProp->Paste(*pcopy);
857
        }
858
    }
859
}
860

861
App::DocumentObject *LinkBaseExtension::makeCopyOnChange() {
862
    auto linked = getLinkedObjectValue();
863
    if (pauseCopyOnChange || !linked)
864
        return nullptr;
865
    auto parent = getContainer();
866
    auto srcobjs = getOnChangeCopyObjects(nullptr, linked);
867
    for (auto obj : srcobjs) {
868
        if (obj->testStatus(App::PartialObject)) {
869
            FC_THROWM(Base::RuntimeError, "Cannot copy partial loaded object: "
870
                    << obj->getFullName());
871
        }
872
    }
873
    auto objs = parent->getDocument()->copyObject(srcobjs);
874
    if(objs.empty())
875
        return nullptr;
876

877
    monitorOnChangeCopyObjects(srcobjs);
878

879
    linked = objs.back();
880
    linked->Visibility.setValue(false);
881

882
    Base::StateLocker guard(pauseCopyOnChange);
883
    getLinkedObjectProperty()->setValue(linked);
884
    if (getLinkCopyOnChangeValue() == CopyOnChangeEnabled)
885
        getLinkCopyOnChangeProperty()->setValue(CopyOnChangeOwned);
886

887
    if (auto prop = getLinkCopyOnChangeGroupProperty()) {
888
        if (auto obj = prop->getValue()) {
889
            if (obj->isAttachedToDocument() && obj->getDocument())
890
                obj->getDocument()->removeObject(obj->getNameInDocument());
891
        }
892
        auto group = new LinkGroup;
893
        group->LinkMode.setValue(LinkModeAutoDelete);
894
        getContainer()->getDocument()->addObject(group, "CopyOnChangeGroup");
895
        prop->setValue(group);
896

897
        // The order of the copied objects is in dependency order (because of
898
        // getOnChangeCopyObjects()). We reverse it here so that we can later
899
        // on delete it in reverse order to avoid error (because some parent
900
        // objects may want to delete their own children).
901
        std::reverse(objs.begin(), objs.end());
902
        group->ElementList.setValues(objs);
903
    }
904

905
    return linked;
906
}
907

908
void LinkBaseExtension::monitorOnChangeCopyObjects(
909
        const std::vector<App::DocumentObject*> &objs)
910
{
911
    copyOnChangeSrcConns.clear();
912
    if (getLinkCopyOnChangeValue() == CopyOnChangeDisabled)
913
        return;
914
    for(auto obj : objs) {
915
        obj->setStatus(App::ObjectStatus::TouchOnColorChange, true);
916
        copyOnChangeSrcConns.emplace_back(obj->signalChanged.connect(
917
            [this](const DocumentObject &, const Property &) {
918
                if (auto prop = this->getLinkCopyOnChangeTouchedProperty()) {
919
                    if (this->getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
920
                        prop->setValue(true);
921
                }
922
            }));
923
    }
924
}
925

926
App::GroupExtension *LinkBaseExtension::linkedPlainGroup() const {
927
    if(!mySubElements.empty() && !mySubElements[0].empty())
928
        return nullptr;
929
    auto linked = getTrueLinkedObject(false);
930
    if(!linked)
931
        return nullptr;
932
    return linked->getExtensionByType<GroupExtension>(true,false);
933
}
934

935
App::PropertyLinkList *LinkBaseExtension::_getElementListProperty() const {
936
    auto group = linkedPlainGroup();
937
    if(group)
938
        return &group->Group;
939
    return const_cast<PropertyLinkList*>(getElementListProperty());
940
}
941

942
const std::vector<App::DocumentObject*> &LinkBaseExtension::_getElementListValue() const {
943
    if(_ChildCache.getSize())
944
        return _ChildCache.getValues();
945
    if(getElementListProperty())
946
        return getElementListProperty()->getValues();
947
    static const std::vector<DocumentObject*> empty;
948
    return empty;
949
}
950

951
App::PropertyBool *LinkBaseExtension::_getShowElementProperty() const {
952
    auto prop = getShowElementProperty();
953
    if(prop && !linkedPlainGroup())
954
        return const_cast<App::PropertyBool*>(prop);
955
    return nullptr;
956
}
957

958
bool LinkBaseExtension::_getShowElementValue() const {
959
    auto prop = _getShowElementProperty();
960
    if(prop)
961
        return prop->getValue();
962
    return true;
963
}
964

965
App::PropertyInteger *LinkBaseExtension::_getElementCountProperty() const {
966
    auto prop = getElementCountProperty();
967
    if(prop && !linkedPlainGroup())
968
        return const_cast<App::PropertyInteger*>(prop);
969
    return nullptr;
970
}
971

972
int LinkBaseExtension::_getElementCountValue() const {
973
    auto prop = _getElementCountProperty();
974
    if(prop)
975
        return prop->getValue();
976
    return 0;
977
}
978

979
bool LinkBaseExtension::extensionHasChildElement() const {
980
    if(!_getElementListValue().empty()
981
            || (_getElementCountValue() && _getShowElementValue()))
982
        return true;
983
    if (getLinkClaimChildValue())
984
        return false;
985
    DocumentObject *linked = getTrueLinkedObject(false);
986
    if(linked) {
987
        if(linked->hasChildElement())
988
            return true;
989
    }
990
    return false;
991
}
992

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

1022
int LinkBaseExtension::extensionIsElementVisible(const char *element) {
1023
    int index = _getShowElementValue()?getElementIndex(element):getArrayIndex(element);
1024
    if(index>=0) {
1025
        auto propElementVis = getVisibilityListProperty();
1026
        if(propElementVis) {
1027
            if(propElementVis->getSize()<=index || propElementVis->getValues()[index])
1028
                return 1;
1029
            return 0;
1030
        }
1031
        return -1;
1032
    }
1033
    DocumentObject *linked = getTrueLinkedObject(false);
1034
    if(linked)
1035
        return linked->isElementVisible(element);
1036
    return -1;
1037
}
1038

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

1046
DocumentObject *LinkBaseExtension::getContainer(){
1047
    auto ext = getExtendedContainer();
1048
    if(!ext || !ext->isDerivedFrom(DocumentObject::getClassTypeId()))
1049
        LINK_THROW(Base::RuntimeError,"Link: container not derived from document object");
1050
    return static_cast<DocumentObject *>(ext);
1051
}
1052

1053
DocumentObject *LinkBaseExtension::getLink(int depth) const{
1054
    if (!GetApplication().checkLinkDepth(depth, MessageOption::Error))
1055
        return nullptr;
1056
    if(getLinkedObjectProperty())
1057
        return getLinkedObjectValue();
1058
    return nullptr;
1059
}
1060

1061
int LinkBaseExtension::getArrayIndex(const char *subname, const char **psubname) {
1062
    if(!subname || Data::isMappedElement(subname))
1063
        return -1;
1064
    const char *dot = strchr(subname,'.');
1065
    if(!dot) dot= subname+strlen(subname);
1066
    if(dot == subname)
1067
        return -1;
1068
    int idx = 0;
1069
    for(const char *c=subname;c!=dot;++c) {
1070
        if(!isdigit(*c))
1071
            return -1;
1072
        idx = idx*10 + *c -'0';
1073
    }
1074
    if(psubname) {
1075
        if(*dot)
1076
            *psubname = dot+1;
1077
        else
1078
            *psubname = dot;
1079
    }
1080
    return idx;
1081
}
1082

1083
int LinkBaseExtension::getElementIndex(const char *subname, const char **psubname) const {
1084
    if(!subname || Data::isMappedElement(subname))
1085
        return -1;
1086
    int idx = -1;
1087
    const char *dot = strchr(subname,'.');
1088
    if(!dot) dot= subname+strlen(subname);
1089

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

1200
void LinkBaseExtension::elementNameFromIndex(int idx, std::ostream &ss) const {
1201
    const auto &elements = _getElementListValue();
1202
    if(idx < 0 || idx >= (int)elements.size())
1203
        return;
1204

1205
    auto obj = elements[idx];
1206
    if(_ChildCache.getSize()) {
1207
        auto group = GroupExtension::getGroupOfObject(obj);
1208
        if(group && _ChildCache.find(group->getNameInDocument(),&idx))
1209
            elementNameFromIndex(idx,ss);
1210
    }
1211
    ss << obj->getNameInDocument() << '.';
1212
}
1213

1214
Base::Vector3d LinkBaseExtension::getScaleVector() const {
1215
    if(getScaleVectorProperty())
1216
        return getScaleVectorValue();
1217
    double s = getScaleValue();
1218
    return Base::Vector3d(s,s,s);
1219
}
1220

1221
Base::Matrix4D LinkBaseExtension::getTransform(bool transform) const {
1222
    Base::Matrix4D mat;
1223
    if(transform) {
1224
        if(getLinkPlacementProperty())
1225
            mat = getLinkPlacementValue().toMatrix();
1226
        else if(getPlacementProperty())
1227
            mat = getPlacementValue().toMatrix();
1228
    }
1229
    if(getScaleProperty() || getScaleVectorProperty()) {
1230
        Base::Matrix4D s;
1231
        s.scale(getScaleVector());
1232
        mat *= s;
1233
    }
1234
    return mat;
1235
}
1236

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

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

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

1312
    if(mat) *mat *= getTransform(transform);
1313

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

1329
        int elementCount = _getElementCountValue();
1330
        if(idx>=elementCount)
1331
            return true;
1332
        isElement = true;
1333
        if(mat) {
1334
            auto placementList = getPlacementListProperty();
1335
            if(placementList && placementList->getSize()>idx)
1336
                *mat *= (*placementList)[idx].toMatrix();
1337
            auto scaleList = getScaleListProperty();
1338
            if(scaleList && scaleList->getSize()>idx) {
1339
                Base::Matrix4D s;
1340
                s.scale((*scaleList)[idx]);
1341
                *mat *= s;
1342
            }
1343
        }
1344
    }
1345

1346
    auto linked = getTrueLinkedObject(false,mat,depth);
1347
    if(!linked || linked==obj)
1348
        return true;
1349

1350
    Base::Matrix4D matNext;
1351

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

1384
    if (!ret) {
1385
        if(mat) matNext = *mat;
1386
        ret = linked->getSubObject(subname,pyObj,mat?&matNext:nullptr,false,depth+1);
1387
    }
1388

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

1416
void LinkBaseExtension::checkGeoElementMap(const App::DocumentObject *obj,
1417
        const App::DocumentObject *linked, PyObject **pyObj, const char *postfix) const
1418
{
1419
    if(!pyObj || !*pyObj || (!postfix && obj->getDocument()==linked->getDocument()) ||
1420
       !PyObject_TypeCheck(*pyObj, &Data::ComplexGeoDataPy::Type))
1421
        return;
1422

1423
//     auto geoData = static_cast<Data::ComplexGeoDataPy*>(*pyObj)->getComplexGeoDataPtr();
1424
//     geoData->reTagElementMap(obj->getID(),obj->getDocument()->Hasher,postfix);
1425

1426
    auto geoData = static_cast<Data::ComplexGeoDataPy*>(*pyObj)->getComplexGeoDataPtr();
1427
    std::string _postfix;
1428
    if (linked && obj && linked->getDocument() != obj->getDocument()) {
1429
        _postfix = Data::POSTFIX_EXTERNAL_TAG;
1430
        if (postfix) {
1431
            if (!boost::starts_with(postfix, Data::ComplexGeoData::elementMapPrefix()))
1432
                _postfix += Data::ComplexGeoData::elementMapPrefix();
1433
            _postfix += postfix;
1434
        }
1435
        postfix = _postfix.c_str();
1436
    }
1437
    geoData->reTagElementMap(obj->getID(),obj->getDocument()->getStringHasher(),postfix);
1438

1439
}
1440

1441
void LinkBaseExtension::onExtendedUnsetupObject() {
1442
    if(!getElementListProperty())
1443
        return;
1444
    detachElements();
1445
    if (auto obj = getLinkCopyOnChangeGroupValue()) {
1446
        if(obj->isAttachedToDocument() && !obj->isRemoving())
1447
            obj->getDocument()->removeObject(obj->getNameInDocument());
1448
    }
1449
}
1450

1451
DocumentObject *LinkBaseExtension::getTrueLinkedObject(
1452
        bool recurse, Base::Matrix4D *mat, int depth, bool noElement) const
1453
{
1454
    if(noElement && extensionIsDerivedFrom(LinkElement::getExtensionClassTypeId())
1455
            && !static_cast<const LinkElement*>(this)->canDelete())
1456
    {
1457
        return nullptr;
1458
    }
1459

1460
    auto ret = getLink(depth);
1461
    if(!ret)
1462
        return nullptr;
1463
    bool transform = linkTransform();
1464
    const char *subname = getSubName();
1465
    if(subname || (mat && transform)) {
1466
        ret = ret->getSubObject(subname,nullptr,mat,transform,depth+1);
1467
        transform = false;
1468
    }
1469
    if(ret && recurse)
1470
        ret = ret->getLinkedObject(recurse,mat,transform,depth+1);
1471
    if(ret && !ret->isAttachedToDocument())
1472
        return nullptr;
1473
    return ret;
1474
}
1475

1476
bool LinkBaseExtension::extensionGetLinkedObject(DocumentObject *&ret,
1477
        bool recurse, Base::Matrix4D *mat, bool transform, int depth) const
1478
{
1479
    if(mat)
1480
        *mat *= getTransform(transform);
1481
    ret = nullptr;
1482
    if(!_getElementCountValue())
1483
        ret = getTrueLinkedObject(recurse,mat,depth);
1484
    if(!ret)
1485
        ret = const_cast<DocumentObject*>(getContainer());
1486
    // always return true to indicate we've handled getLinkObject() call
1487
    return true;
1488
}
1489

1490
void LinkBaseExtension::extensionOnChanged(const Property *prop) {
1491
    auto parent = getContainer();
1492
    if(parent && !parent->isRestoring() && prop && !prop->testStatus(Property::User3))
1493
        update(parent,prop);
1494
    inherited::extensionOnChanged(prop);
1495
}
1496

1497
void LinkBaseExtension::parseSubName() const {
1498
    // If user has ever linked to some sub-element, the Link shall always accept
1499
    // sub-element linking in the future, which affects how ViewProviderLink
1500
    // dropObjectEx() behave. So we will push an empty string later even if no
1501
    // sub-element linking this time.
1502
    bool hasSubElement = !mySubElements.empty();
1503
    mySubElements.clear();
1504
    mySubName.clear();
1505
    auto xlink = freecad_dynamic_cast<const PropertyXLink>(getLinkedObjectProperty());
1506
    if(!xlink || xlink->getSubValues().empty()) {
1507
        if(hasSubElement)
1508
            mySubElements.emplace_back("");
1509
        return;
1510
    }
1511
    const auto &subs = xlink->getSubValues();
1512
    auto subname = subs.front().c_str();
1513
    auto element = Data::findElementName(subname);
1514
    if(!element || !element[0]) {
1515
        mySubName = subs[0];
1516
        if(hasSubElement)
1517
            mySubElements.emplace_back("");
1518
        return;
1519
    }
1520
    mySubElements.emplace_back(element);
1521
    mySubName = std::string(subname,element-subname);
1522
    for(std::size_t i=1;i<subs.size();++i) {
1523
        auto &sub = subs[i];
1524
        element = Data::findElementName(sub.c_str());
1525
        if(element && element[0] && boost::starts_with(sub,mySubName))
1526
            mySubElements.emplace_back(element);
1527
    }
1528
}
1529

1530
void LinkBaseExtension::slotChangedPlainGroup(const App::DocumentObject &obj, const App::Property &prop) {
1531
    auto group = obj.getExtensionByType<GroupExtension>(true,false);
1532
    if(group && &prop == &group->Group)
1533
        updateGroup();
1534
}
1535

1536
void LinkBaseExtension::updateGroup() {
1537
    std::vector<GroupExtension*> groups;
1538
    std::unordered_set<const App::DocumentObject*> groupSet;
1539
    auto group = linkedPlainGroup();
1540
    if(group) {
1541
        groups.push_back(group);
1542
        groupSet.insert(group->getExtendedObject());
1543
    }else{
1544
        for(auto o : getElementListProperty()->getValues()) {
1545
            if(!o || !o->isAttachedToDocument())
1546
                continue;
1547
            auto ext = o->getExtensionByType<GroupExtension>(true,false);
1548
            if(ext) {
1549
                groups.push_back(ext);
1550
                groupSet.insert(o);
1551
            }
1552
        }
1553
    }
1554
    std::vector<App::DocumentObject*> children;
1555
    if(!groups.empty()) {
1556
        children = getElementListValue();
1557
        std::set<DocumentObject*> childSet(children.begin(),children.end());
1558
        for(auto ext : groups) {
1559
            auto group = ext->getExtendedObject();
1560
            auto &conn = plainGroupConns[group];
1561
            if(!conn.connected()) {
1562
                FC_LOG("new group connection " << getExtendedObject()->getFullName()
1563
                        << " -> " << group->getFullName());
1564
                //NOLINTBEGIN
1565
                conn = group->signalChanged.connect(
1566
                        std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2));
1567
                //NOLINTEND
1568
            }
1569
            std::size_t count = children.size();
1570
            ext->getAllChildren(children,childSet);
1571
            for(;count<children.size();++count) {
1572
                auto child = children[count];
1573
                if(!child->getExtensionByType<GroupExtension>(true,false))
1574
                    continue;
1575
                groupSet.insert(child);
1576
                auto &conn = plainGroupConns[child];
1577
                if(!conn.connected()) {
1578
                    FC_LOG("new group connection " << getExtendedObject()->getFullName()
1579
                            << " -> " << child->getFullName());
1580
                    //NOLINTBEGIN
1581
                    conn = child->signalChanged.connect(
1582
                            std::bind(&LinkBaseExtension::slotChangedPlainGroup,this,sp::_1,sp::_2));
1583
                    //NOLINTEND
1584
                }
1585
            }
1586
        }
1587
    }
1588
    for(auto it=plainGroupConns.begin();it!=plainGroupConns.end();) {
1589
        if(!groupSet.count(it->first))
1590
            it = plainGroupConns.erase(it);
1591
        else
1592
            ++it;
1593
    }
1594
    if(children != _ChildCache.getValues())
1595
        _ChildCache.setValue(children);
1596
}
1597

1598
void LinkBaseExtension::update(App::DocumentObject *parent, const Property *prop) {
1599
    if(!prop)
1600
        return;
1601

1602
    if(prop == getLinkPlacementProperty() || prop == getPlacementProperty()) {
1603
        auto src = getLinkPlacementProperty();
1604
        auto dst = getPlacementProperty();
1605
        if(src!=prop) std::swap(src,dst);
1606
        if(src && dst) {
1607
            dst->setStatus(Property::User3,true);
1608
            dst->setValue(src->getValue());
1609
            dst->setStatus(Property::User3,false);
1610
        }
1611
    }else if(prop == getScaleProperty()) {
1612
        if(!prop->testStatus(Property::User3) && getScaleVectorProperty()) {
1613
            auto s = getScaleValue();
1614
            auto p = getScaleVectorProperty();
1615
            p->setStatus(Property::User3,true);
1616
            p->setValue(s,s,s);
1617
            p->setStatus(Property::User3,false);
1618
        }
1619
    }else if(prop == getScaleVectorProperty()) {
1620
        if(!prop->testStatus(Property::User3) && getScaleProperty()) {
1621
            const auto &v = getScaleVectorValue();
1622
            if(v.x == v.y && v.x == v.z) {
1623
                auto p = getScaleProperty();
1624
                p->setStatus(Property::User3,true);
1625
                p->setValue(v.x);
1626
                p->setStatus(Property::User3,false);
1627
            }
1628
        }
1629
    }else if(prop == _getShowElementProperty()) {
1630
        if(_getShowElementValue())
1631
            update(parent,_getElementCountProperty());
1632
        else {
1633
            auto objs = getElementListValue();
1634

1635
            // preserve element properties in ourself
1636
            std::vector<Base::Placement> placements;
1637
            placements.reserve(objs.size());
1638
            std::vector<Base::Vector3d> scales;
1639
            scales.reserve(objs.size());
1640
            for(auto obj : objs) {
1641
                auto element = freecad_dynamic_cast<LinkElement>(obj);
1642
                if(element) {
1643
                    placements.push_back(element->Placement.getValue());
1644
                    scales.push_back(element->getScaleVector());
1645
                }else{
1646
                    placements.emplace_back();
1647
                    scales.emplace_back(1,1,1);
1648
                }
1649
            }
1650
            // touch the property again to make sure view provider has been
1651
            // signaled before clearing the elements
1652
            getShowElementProperty()->setStatus(App::Property::User3, true);
1653
            getShowElementProperty()->touch();
1654
            getShowElementProperty()->setStatus(App::Property::User3, false);
1655

1656
            getElementListProperty()->setValues(std::vector<App::DocumentObject*>());
1657

1658
            if(getPlacementListProperty()) {
1659
                getPlacementListProperty()->setStatus(Property::User3, getScaleListProperty() != nullptr);
1660
                getPlacementListProperty()->setValue(placements);
1661
                getPlacementListProperty()->setStatus(Property::User3, false);
1662
            }
1663
            if(getScaleListProperty())
1664
                getScaleListProperty()->setValue(scales);
1665

1666
            for(auto obj : objs) {
1667
                if(obj && obj->isAttachedToDocument())
1668
                    obj->getDocument()->removeObject(obj->getNameInDocument());
1669
            }
1670
        }
1671
    }else if(prop == _getElementCountProperty()) {
1672
        size_t elementCount = getElementCountValue()<0?0:(size_t)getElementCountValue();
1673

1674
        auto propVis = getVisibilityListProperty();
1675
        if(propVis) {
1676
            if(propVis->getSize()>(int)elementCount)
1677
                propVis->setSize(getElementCountValue(),true);
1678
        }
1679

1680
        if(!_getShowElementValue()) {
1681
            if(getScaleListProperty()) {
1682
                auto scales = getScaleListValue();
1683
                scales.resize(elementCount,Base::Vector3d(1,1,1));
1684
                getScaleListProperty()->setStatus(Property::User3,true);
1685
                getScaleListProperty()->setValue(scales);
1686
                getScaleListProperty()->setStatus(Property::User3,false);
1687
            }
1688
            if(getPlacementListProperty()) {
1689
                auto placements = getPlacementListValue();
1690
                if(placements.size()<elementCount) {
1691
                    for(size_t i=placements.size();i<elementCount;++i)
1692
                        placements.emplace_back(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation());
1693
                }else
1694
                    placements.resize(elementCount);
1695
                getPlacementListProperty()->setStatus(Property::User3,true);
1696
                getPlacementListProperty()->setValue(placements);
1697
                getPlacementListProperty()->setStatus(Property::User3,false);
1698
            }
1699
        }else if(getElementListProperty()) {
1700
            auto objs = getElementListValue();
1701
            if(elementCount>objs.size()) {
1702
                std::string name = parent->getNameInDocument();
1703
                auto doc = parent->getDocument();
1704
                name += "_i";
1705
                name = doc->getUniqueObjectName(name.c_str());
1706
                if(name[name.size()-1] != 'i')
1707
                    name += "_i";
1708
                auto offset = name.size();
1709
                auto placementProp = getPlacementListProperty();
1710
                auto scaleProp = getScaleListProperty();
1711
                const auto &vis = getVisibilityListValue();
1712

1713
                auto owner = getContainer();
1714
                long ownerID = owner?owner->getID():0;
1715

1716
                for(size_t i=objs.size();i<elementCount;++i) {
1717
                    name.resize(offset);
1718
                    name += std::to_string(i);
1719

1720
                    // It is possible to have orphan LinkElement here due to,
1721
                    // for example, undo and redo. So we try to re-claim the
1722
                    // children element first.
1723
                    auto obj = freecad_dynamic_cast<LinkElement>(doc->getObject(name.c_str()));
1724
                    if(obj && (!obj->_LinkOwner.getValue() || obj->_LinkOwner.getValue()==ownerID)) {
1725
                        obj->Visibility.setValue(false);
1726
                    } else {
1727
                        obj = new LinkElement;
1728
                        parent->getDocument()->addObject(obj,name.c_str());
1729
                    }
1730

1731
                    if(vis.size()>i && !vis[i])
1732
                        myHiddenElements.insert(obj);
1733

1734
                    if(placementProp && placementProp->getSize()>(int)i)
1735
                        obj->Placement.setValue(placementProp->getValues()[i]);
1736
                    else{
1737
                        Base::Placement pla(Base::Vector3d(i%10,(i/10)%10,i/100),Base::Rotation());
1738
                        obj->Placement.setValue(pla);
1739
                    }
1740
                    if(scaleProp && scaleProp->getSize()>(int)i)
1741
                        obj->Scale.setValue(scaleProp->getValues()[i].x);
1742
                    else
1743
                        obj->Scale.setValue(1);
1744
                    objs.push_back(obj);
1745
                }
1746
                if(getPlacementListProperty())
1747
                    getPlacementListProperty()->setSize(0);
1748
                if(getScaleListProperty())
1749
                    getScaleListProperty()->setSize(0);
1750

1751
                getElementListProperty()->setValue(objs);
1752

1753
            }else if(elementCount<objs.size()){
1754
                std::vector<App::DocumentObject*> tmpObjs;
1755
                auto owner = getContainer();
1756
                long ownerID = owner?owner->getID():0;
1757
                while(objs.size()>elementCount) {
1758
                    auto element = freecad_dynamic_cast<LinkElement>(objs.back());
1759
                    if(element && element->_LinkOwner.getValue()==ownerID)
1760
                        tmpObjs.push_back(objs.back());
1761
                    objs.pop_back();
1762
                }
1763
                getElementListProperty()->setValue(objs);
1764
                for(auto obj : tmpObjs) {
1765
                    if(obj && obj->isAttachedToDocument())
1766
                        obj->getDocument()->removeObject(obj->getNameInDocument());
1767
                }
1768
            }
1769
        }
1770
    }else if(prop == getVisibilityListProperty()) {
1771
        if(_getShowElementValue()) {
1772
            const auto &elements = _getElementListValue();
1773
            const auto &vis = getVisibilityListValue();
1774
            myHiddenElements.clear();
1775
            for(size_t i=0;i<vis.size();++i) {
1776
                if(i>=elements.size())
1777
                    break;
1778
                if(!vis[i])
1779
                    myHiddenElements.insert(elements[i]);
1780
            }
1781
        }
1782
    }else if(prop == getElementListProperty() || prop == &_ChildCache) {
1783

1784
        if(prop == getElementListProperty()) {
1785
            _ChildCache.setStatus(Property::User3,true);
1786
            updateGroup();
1787
            _ChildCache.setStatus(Property::User3,false);
1788
        }
1789

1790
        const auto &elements = _getElementListValue();
1791

1792
        if(enableLabelCache)
1793
            myLabelCache.clear();
1794

1795
        // Element list changed, we need to sychrnoize VisibilityList.
1796
        if(_getShowElementValue() && getVisibilityListProperty()) {
1797
            if(parent->getDocument()->isPerformingTransaction()) {
1798
                update(parent,getVisibilityListProperty());
1799
            }else{
1800
                boost::dynamic_bitset<> vis;
1801
                vis.resize(elements.size(),true);
1802
                std::unordered_set<const App::DocumentObject *> hiddenElements;
1803
                for(size_t i=0;i<elements.size();++i) {
1804
                    if(myHiddenElements.find(elements[i])!=myHiddenElements.end()) {
1805
                        hiddenElements.insert(elements[i]);
1806
                        vis[i] = false;
1807
                    }
1808
                }
1809
                myHiddenElements.swap(hiddenElements);
1810
                if(vis != getVisibilityListValue()) {
1811
                    auto propVis = getVisibilityListProperty();
1812
                    propVis->setStatus(Property::User3,true);
1813
                    propVis->setValue(vis);
1814
                    propVis->setStatus(Property::User3,false);
1815
                }
1816
            }
1817
        }
1818
        syncElementList();
1819
        if(_getShowElementValue()
1820
                && _getElementCountProperty()
1821
                && getElementListProperty()
1822
                && getElementCountValue()!=getElementListProperty()->getSize())
1823
        {
1824
            getElementCountProperty()->setValue(
1825
                    getElementListProperty()->getSize());
1826
        }
1827
    }else if(prop == getLinkedObjectProperty()) {
1828
        auto group = linkedPlainGroup();
1829
        if(getShowElementProperty())
1830
            getShowElementProperty()->setStatus(Property::Hidden, !!group);
1831
        if(getElementCountProperty())
1832
            getElementCountProperty()->setStatus(Property::Hidden, !!group);
1833
        if(group)
1834
            updateGroup();
1835
        else if(_ChildCache.getSize())
1836
            _ChildCache.setValue();
1837
        parseSubName();
1838
        syncElementList();
1839

1840
        if(getLinkCopyOnChangeValue()==CopyOnChangeOwned
1841
                && !pauseCopyOnChange
1842
                && !parent->getDocument()->isPerformingTransaction())
1843
            getLinkCopyOnChangeProperty()->setValue(CopyOnChangeEnabled);
1844
        else
1845
            setupCopyOnChange(parent, true);
1846

1847
    }else if(prop == getLinkCopyOnChangeProperty()) {
1848
        setupCopyOnChange(parent, getLinkCopyOnChangeSourceValue() == nullptr);
1849
    } else if (prop == getLinkCopyOnChangeSourceProperty()) {
1850
        if (auto source = getLinkCopyOnChangeSourceValue()) {
1851
            this->connCopyOnChangeSource = source->signalChanged.connect(
1852
                [this](const DocumentObject & obj, const Property &prop) {
1853
                    auto src = getLinkCopyOnChangeSourceValue();
1854
                    if (src != &obj || getLinkCopyOnChangeValue()==CopyOnChangeDisabled)
1855
                        return;
1856
                    if (App::Document::isAnyRestoring()
1857
                            || obj.testStatus(ObjectStatus::NoTouch)
1858
                            || (prop.getType() & Prop_Output)
1859
                            || prop.testStatus(Property::Output))
1860
                        return;
1861
                    if (auto propTouch = getLinkCopyOnChangeTouchedProperty())
1862
                        propTouch->setValue(true);
1863
                });
1864
        } else
1865
            this->connCopyOnChangeSource.disconnect();
1866

1867
    }else if(prop == getLinkTransformProperty()) {
1868
        auto linkPlacement = getLinkPlacementProperty();
1869
        auto placement = getPlacementProperty();
1870
        if(linkPlacement && placement) {
1871
            bool transform = getLinkTransformValue();
1872
            placement->setStatus(Property::Hidden,transform);
1873
            linkPlacement->setStatus(Property::Hidden,!transform);
1874
        }
1875
        syncElementList();
1876

1877
    } else {
1878
        checkCopyOnChange(parent, *prop);
1879
    }
1880
}
1881

1882
void LinkBaseExtension::cacheChildLabel(int enable) const {
1883
    enableLabelCache = enable?true:false;
1884
    myLabelCache.clear();
1885
    if(enable<=0)
1886
        return;
1887

1888
    int idx = 0;
1889
    for(auto child : _getElementListValue()) {
1890
        if(child && child->isAttachedToDocument())
1891
            myLabelCache[child->Label.getStrValue()] = idx;
1892
        ++idx;
1893
    }
1894
}
1895

1896
bool LinkBaseExtension::linkTransform() const {
1897
    if(!getLinkTransformProperty() &&
1898
       !getLinkPlacementProperty() &&
1899
       !getPlacementProperty())
1900
        return true;
1901
    return getLinkTransformValue();
1902
}
1903

1904
void LinkBaseExtension::syncElementList() {
1905
    auto transform = getLinkTransformProperty();
1906
    auto link = getLinkedObjectProperty();
1907
    auto xlink = freecad_dynamic_cast<const PropertyXLink>(link);
1908

1909
    auto owner = getContainer();
1910
    auto ownerID = owner?owner->getID():0;
1911
    auto elements = getElementListValue();
1912
    for (auto i : elements) {
1913
        auto element = freecad_dynamic_cast<LinkElement>(i);
1914
        if (!element
1915
            || (element->_LinkOwner.getValue()
1916
                && element->_LinkOwner.getValue() != ownerID))
1917
            continue;
1918

1919
        element->_LinkOwner.setValue(ownerID);
1920

1921
        element->LinkTransform.setStatus(Property::Hidden, transform != nullptr);
1922
        element->LinkTransform.setStatus(Property::Immutable, transform != nullptr);
1923
        if (transform && element->LinkTransform.getValue() != transform->getValue())
1924
            element->LinkTransform.setValue(transform->getValue());
1925

1926
        element->LinkedObject.setStatus(Property::Hidden, link != nullptr);
1927
        element->LinkedObject.setStatus(Property::Immutable, link != nullptr);
1928
        if (element->LinkCopyOnChange.getValue() == 2)
1929
            continue;
1930
        if (xlink) {
1931
            if (element->LinkedObject.getValue() != xlink->getValue()
1932
                || element->LinkedObject.getSubValues() != xlink->getSubValues()) {
1933
                element->LinkedObject.setValue(xlink->getValue(), xlink->getSubValues());
1934
            }
1935
        }
1936
        else if (element->LinkedObject.getValue() != link->getValue()
1937
                 || !element->LinkedObject.getSubValues().empty()) {
1938
            element->setLink(-1, link->getValue());
1939
        }
1940
    }
1941
}
1942

1943
void LinkBaseExtension::onExtendedDocumentRestored() {
1944
    inherited::onExtendedDocumentRestored();
1945
    myHiddenElements.clear();
1946
    auto parent = getContainer();
1947
    if(!parent)
1948
        return;
1949
    if(hasOldSubElement) {
1950
        hasOldSubElement = false;
1951
        // SubElements was stored as a PropertyStringList. It is now migrated to be
1952
        // stored inside PropertyXLink.
1953
        auto xlink = freecad_dynamic_cast<PropertyXLink>(getLinkedObjectProperty());
1954
        if(!xlink)
1955
            FC_ERR("Failed to restore SubElements for " << parent->getFullName());
1956
        else if(!xlink->getValue())
1957
            FC_ERR("Discard SubElements of " << parent->getFullName() << " due to null link");
1958
        else if(xlink->getSubValues().size() > 1)
1959
            FC_ERR("Failed to restore SubElements for " << parent->getFullName()
1960
                    << " due to conflict subnames");
1961
        else if(xlink->getSubValues().empty()) {
1962
            auto subs = xlink->getSubValues();
1963
            xlink->setSubValues(std::move(subs));
1964
        } else {
1965
            std::set<std::string> subset(mySubElements.begin(),mySubElements.end());
1966
            auto sub = xlink->getSubValues().front();
1967
            auto element = Data::findElementName(sub.c_str());
1968
            if(element && element[0]) {
1969
                subset.insert(element);
1970
                sub.resize(element - sub.c_str());
1971
            }
1972
            std::vector<std::string> subs;
1973
            for(const auto &s : subset)
1974
                subs.push_back(sub + s);
1975
            xlink->setSubValues(std::move(subs));
1976
        }
1977
    }
1978
    if(getScaleVectorProperty() && getScaleProperty()) {
1979
        // Scale vector is added later. The code here is for migration.
1980
        const auto &v = getScaleVectorValue();
1981
        double s = getScaleValue();
1982
        if(v.x == v.y && v.x == v.z && v.x != s)
1983
            getScaleVectorProperty()->setValue(s,s,s);
1984
    }
1985
    update(parent,getVisibilityListProperty());
1986
    if (auto prop = getLinkedObjectProperty()) {
1987
        Base::StateLocker guard(pauseCopyOnChange);
1988
        update(parent,prop);
1989
    }
1990
    update(parent,getLinkCopyOnChangeSourceProperty());
1991
    update(parent,getElementListProperty());
1992
    if (getLinkCopyOnChangeValue() != CopyOnChangeDisabled)
1993
        monitorOnChangeCopyObjects(getOnChangeCopyObjects());
1994
}
1995

1996
void LinkBaseExtension::_handleChangedPropertyName(
1997
        Base::XMLReader &reader, const char * TypeName, const char *PropName)
1998
{
1999
    if(strcmp(PropName,"SubElements")==0
2000
        && strcmp(TypeName,PropertyStringList::getClassTypeId().getName())==0)
2001
    {
2002
        PropertyStringList prop;
2003
        prop.setContainer(getContainer());
2004
        prop.Restore(reader);
2005
        if(prop.getSize()) {
2006
            mySubElements = prop.getValues();
2007
            hasOldSubElement = true;
2008
        }
2009
    }
2010
}
2011

2012
void LinkBaseExtension::setLink(int index, DocumentObject *obj,
2013
    const char *subname, const std::vector<std::string> &subElements)
2014
{
2015
    auto parent = getContainer();
2016
    if(!parent)
2017
        LINK_THROW(Base::RuntimeError,"No parent container");
2018

2019
    if(obj && !App::Document::isAnyRestoring()) {
2020
        auto inSet = parent->getInListEx(true);
2021
        inSet.insert(parent);
2022
        if(inSet.find(obj)!=inSet.end())
2023
            LINK_THROW(Base::RuntimeError,"Cyclic dependency");
2024
    }
2025

2026
    auto linkProp = getLinkedObjectProperty();
2027

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

2034
    if(index>=0) {
2035
        // LinkGroup assignment
2036

2037
        if(linkProp || !getElementListProperty())
2038
            LINK_THROW(Base::RuntimeError,"Cannot set link element");
2039

2040
        DocumentObject *old = nullptr;
2041
        const auto &elements = getElementListProperty()->getValues();
2042
        if(!obj) {
2043
            if(index>=(int)elements.size())
2044
                LINK_THROW(Base::ValueError,"Link element index out of bound");
2045
            std::vector<DocumentObject*> objs;
2046
            old = elements[index];
2047
            for(int i=0;i<(int)elements.size();++i) {
2048
                if(i!=index)
2049
                    objs.push_back(elements[i]);
2050
            }
2051
            getElementListProperty()->setValue(objs);
2052
        }else if(!obj->isAttachedToDocument())
2053
            LINK_THROW(Base::ValueError,"Invalid object");
2054
        else{
2055
            if(index>(int)elements.size())
2056
                LINK_THROW(Base::ValueError,"Link element index out of bound");
2057

2058
            if(index < (int)elements.size())
2059
                old = elements[index];
2060

2061
            int idx = -1;
2062
            if(getLinkModeValue()>=LinkModeAutoLink ||
2063
               (subname && subname[0]) ||
2064
               !subElements.empty() ||
2065
               obj->getDocument()!=parent->getDocument() ||
2066
               (getElementListProperty()->find(obj->getNameInDocument(),&idx) && idx!=index))
2067
            {
2068
                std::string name = parent->getDocument()->getUniqueObjectName("Link");
2069
                auto link = new Link;
2070
                link->_LinkOwner.setValue(parent->getID());
2071
                parent->getDocument()->addObject(link,name.c_str());
2072
                link->setLink(-1,obj,subname,subElements);
2073
                auto linked = link->getTrueLinkedObject(true);
2074
                if(linked)
2075
                    link->Label.setValue(linked->Label.getValue());
2076
                auto pla = freecad_dynamic_cast<PropertyPlacement>(obj->getPropertyByName("Placement"));
2077
                if(pla)
2078
                    link->Placement.setValue(pla->getValue());
2079
                link->Visibility.setValue(false);
2080
                obj = link;
2081
            }
2082

2083
            if(old == obj)
2084
                return;
2085

2086
            getElementListProperty()->set1Value(index,obj);
2087
        }
2088
        detachElement(old);
2089
        return;
2090
    }
2091

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

2096
        if(obj || !getElementListProperty())
2097
            LINK_THROW(Base::RuntimeError,"No PropertyLink or PropertyLinkList configured");
2098
        detachElements();
2099
        return;
2100
    }
2101

2102
    // Here means we are assigning a Link
2103

2104
    auto xlink = freecad_dynamic_cast<PropertyXLink>(linkProp);
2105
    if(obj) {
2106
        if(!obj->isAttachedToDocument())
2107
            LINK_THROW(Base::ValueError,"Invalid document object");
2108
        if(!xlink) {
2109
            if(parent && obj->getDocument()!=parent->getDocument())
2110
                LINK_THROW(Base::ValueError,"Cannot link to external object without PropertyXLink");
2111
        }
2112
    }
2113

2114
    if(!xlink) {
2115
        if(!subElements.empty() || (subname && subname[0]))
2116
            LINK_THROW(Base::RuntimeError,"SubName/SubElement link requires PropertyXLink");
2117
        linkProp->setValue(obj);
2118
        return;
2119
    }
2120

2121
    std::vector<std::string> subs;
2122
    if(!subElements.empty()) {
2123
        subs.reserve(subElements.size());
2124
        for(const auto &s : subElements) {
2125
            subs.emplace_back(subname?subname:"");
2126
            subs.back() += s;
2127
        }
2128
    } else if(subname && subname[0])
2129
        subs.emplace_back(subname);
2130
    xlink->setValue(obj,std::move(subs));
2131
}
2132

2133
void LinkBaseExtension::detachElements()
2134
{
2135
    std::vector<App::DocumentObjectT> objs;
2136
    for (auto obj : getElementListValue())
2137
        objs.emplace_back(obj);
2138
    getElementListProperty()->setValue();
2139
    for(const auto &objT : objs)
2140
        detachElement(objT.getObject());
2141
}
2142

2143
void LinkBaseExtension::detachElement(DocumentObject *obj) {
2144
    if(!obj || !obj->isAttachedToDocument() || obj->isRemoving())
2145
        return;
2146
    auto ext = obj->getExtensionByType<LinkBaseExtension>(true);
2147
    auto owner = getContainer();
2148
    long ownerID = owner?owner->getID():0;
2149
    if(getLinkModeValue()==LinkModeAutoUnlink) {
2150
        if(!ext || ext->_LinkOwner.getValue()!=ownerID)
2151
            return;
2152
    }else if(getLinkModeValue()!=LinkModeAutoDelete) {
2153
        if(ext && ext->_LinkOwner.getValue()==ownerID)
2154
            ext->_LinkOwner.setValue(0);
2155
        return;
2156
    }
2157
    obj->getDocument()->removeObject(obj->getNameInDocument());
2158
}
2159

2160
std::vector<App::DocumentObject*> LinkBaseExtension::getLinkedChildren(bool filter) const{
2161
    if(!filter)
2162
        return _getElementListValue();
2163
    std::vector<App::DocumentObject*> ret;
2164
    for(auto o : _getElementListValue()) {
2165
        if(!o->hasExtension(GroupExtension::getExtensionClassTypeId(),false))
2166
            ret.push_back(o);
2167
    }
2168
    return ret;
2169
}
2170

2171
const char *LinkBaseExtension::flattenSubname(const char *subname) const {
2172
    if(subname && _ChildCache.getSize()) {
2173
        const char *sub = subname;
2174
        std::string s;
2175
        for(const char* dot=strchr(sub,'.');dot;sub=dot+1,dot=strchr(sub,'.')) {
2176
            DocumentObject *obj = nullptr;
2177
            s.clear();
2178
            s.append(sub,dot+1);
2179
            extensionGetSubObject(obj,s.c_str());
2180
            if(!obj)
2181
                break;
2182
            if(!obj->hasExtension(GroupExtension::getExtensionClassTypeId(),false))
2183
                return sub;
2184
        }
2185
    }
2186
    return subname;
2187
}
2188

2189
void LinkBaseExtension::expandSubname(std::string &subname) const {
2190
    if(!_ChildCache.getSize())
2191
        return;
2192

2193
    const char *pos = nullptr;
2194
    int index = getElementIndex(subname.c_str(),&pos);
2195
    if(index<0)
2196
        return;
2197
    std::ostringstream ss;
2198
    elementNameFromIndex(index,ss);
2199
    ss << pos;
2200
    subname = ss.str();
2201
}
2202

2203
static bool isExcludedProperties(const char *name) {
2204
#define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true;
2205
    CHECK_EXCLUDE_PROP(Shape);
2206
    CHECK_EXCLUDE_PROP(Proxy);
2207
    CHECK_EXCLUDE_PROP(Placement);
2208
    return false;
2209
}
2210

2211
Property *LinkBaseExtension::extensionGetPropertyByName(const char* name) const {
2212
    if (checkingProperty)
2213
        return inherited::extensionGetPropertyByName(name);
2214
    Base::StateLocker guard(checkingProperty);
2215
    if(isExcludedProperties(name))
2216
        return nullptr;
2217
    auto owner = getContainer();
2218
    if (owner) {
2219
        App::Property *prop = owner->getPropertyByName(name);
2220
        if(prop)
2221
            return prop;
2222
        if(owner->canLinkProperties()) {
2223
            auto linked = getTrueLinkedObject(true);
2224
            if(linked)
2225
                return linked->getPropertyByName(name);
2226
        }
2227
    }
2228
    return nullptr;
2229
}
2230

2231
bool LinkBaseExtension::isLinkMutated() const
2232
{
2233
    return getLinkCopyOnChangeValue() != CopyOnChangeDisabled
2234
        && getLinkedObjectValue()
2235
        && (!getLinkCopyOnChangeSourceValue()
2236
            || (getLinkedObjectValue() != getLinkCopyOnChangeSourceValue()));
2237
}
2238

2239
///////////////////////////////////////////////////////////////////////////////////////////
2240

2241
namespace App {
2242
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkBaseExtensionPython, App::LinkBaseExtension)
2243

2244
// explicit template instantiation
2245
template class AppExport ExtensionPythonT<LinkBaseExtension>;
2246

2247
}
2248

2249
//////////////////////////////////////////////////////////////////////////////
2250

2251
EXTENSION_PROPERTY_SOURCE(App::LinkExtension, App::LinkBaseExtension)
2252

2253
LinkExtension::LinkExtension()
2254
{
2255
    initExtensionType(LinkExtension::getExtensionClassTypeId());
2256

2257
    LINK_PROPS_ADD_EXTENSION(LINK_PARAMS_EXT);
2258
}
2259

2260
///////////////////////////////////////////////////////////////////////////////////////////
2261

2262
namespace App {
2263
EXTENSION_PROPERTY_SOURCE_TEMPLATE(App::LinkExtensionPython, App::LinkExtension)
2264

2265
// explicit template instantiation
2266
template class AppExport ExtensionPythonT<App::LinkExtension>;
2267

2268
}
2269

2270
///////////////////////////////////////////////////////////////////////////////////////////
2271

2272
PROPERTY_SOURCE_WITH_EXTENSIONS(App::Link, App::DocumentObject)
2273

2274
Link::Link() {
2275
    LINK_PROPS_ADD(LINK_PARAMS_LINK);
2276
    LinkExtension::initExtension(this);
2277
    static const PropertyIntegerConstraint::Constraints s_constraints = {0,INT_MAX,1};
2278
    ElementCount.setConstraints(&s_constraints);
2279
}
2280

2281
bool Link::canLinkProperties() const {
2282
    return true;
2283
}
2284

2285
bool Link::isLink() const
2286
{
2287
    return ElementCount.getValue() == 0;
2288
}
2289

2290
bool Link::isLinkGroup() const
2291
{
2292
    return ElementCount.getValue() > 0;
2293
}
2294

2295
//////////////////////////////////////////////////////////////////////////////////////////
2296

2297
namespace App {
2298
PROPERTY_SOURCE_TEMPLATE(App::LinkPython, App::Link)
2299
template<> const char* App::LinkPython::getViewProviderName() const {
2300
    return "Gui::ViewProviderLinkPython";
2301
}
2302
template class AppExport FeaturePythonT<App::Link>;
2303
}
2304

2305
//////////////////////////////////////////////////////////////////////////////////////////
2306

2307
PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkElement, App::DocumentObject)
2308

2309
LinkElement::LinkElement() {
2310
    LINK_PROPS_ADD(LINK_PARAMS_ELEMENT);
2311
    LinkBaseExtension::initExtension(this);
2312
}
2313

2314
bool LinkElement::canDelete() const {
2315
    if(!_LinkOwner.getValue())
2316
        return true;
2317

2318
    auto owner = getContainer();
2319
    return !owner || !owner->getDocument()->getObjectByID(_LinkOwner.getValue());
2320
}
2321

2322
bool LinkElement::isLink() const
2323
{
2324
    return true;
2325
}
2326

2327
App::Link* LinkElement::getLinkGroup() const
2328
{
2329
    std::vector<App::DocumentObject*> inList = getInList();
2330
    for (auto* obj : inList) {
2331
        auto* link = dynamic_cast<App::Link*>(obj);
2332
        if (!link) {
2333
            continue;
2334
        }
2335
        std::vector<App::DocumentObject*> elts = link->ElementList.getValues();
2336
        for (auto* elt : elts) {
2337
            if (elt == this) {
2338
                return link;
2339
            }
2340
        }
2341
    }
2342
    return nullptr;
2343
}
2344

2345
//////////////////////////////////////////////////////////////////////////////////////////
2346

2347
namespace App {
2348
PROPERTY_SOURCE_TEMPLATE(App::LinkElementPython, App::LinkElement)
2349
template<> const char* App::LinkElementPython::getViewProviderName() const {
2350
    return "Gui::ViewProviderLinkPython";
2351
}
2352
template class AppExport FeaturePythonT<App::LinkElement>;
2353
}
2354

2355
//////////////////////////////////////////////////////////////////////////////////////////
2356

2357
PROPERTY_SOURCE_WITH_EXTENSIONS(App::LinkGroup, App::DocumentObject)
2358

2359
LinkGroup::LinkGroup() {
2360
    LINK_PROPS_ADD(LINK_PARAMS_GROUP);
2361
    LinkBaseExtension::initExtension(this);
2362
}
2363

2364
//////////////////////////////////////////////////////////////////////////////////////////
2365

2366
namespace App {
2367
PROPERTY_SOURCE_TEMPLATE(App::LinkGroupPython, App::LinkGroup)
2368
template<> const char* App::LinkGroupPython::getViewProviderName() const {
2369
    return "Gui::ViewProviderLinkPython";
2370
}
2371
template class AppExport FeaturePythonT<App::LinkGroup>;
2372
}
2373

2374
#if defined(__clang__)
2375
# pragma clang diagnostic pop
2376
#endif
2377

2378

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

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

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

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