FreeCAD

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

23
#include "PreCompiled.h"
24

25
#ifndef _PreComp_
26
# include <atomic>
27
# include <cctype>
28
# include <boost/algorithm/string/predicate.hpp>
29
# include <Inventor/SoPickedPoint.h>
30
# include <Inventor/actions/SoGetBoundingBoxAction.h>
31
# include <Inventor/details/SoDetail.h>
32
# include <Inventor/draggers/SoCenterballDragger.h>
33
# include <Inventor/misc/SoChildList.h>
34
# include <Inventor/nodes/SoAnnotation.h>
35
# include <Inventor/nodes/SoCube.h>
36
# include <Inventor/nodes/SoDrawStyle.h>
37
# include <Inventor/nodes/SoPickStyle.h>
38
# include <Inventor/nodes/SoSeparator.h>
39
# include <Inventor/nodes/SoShapeHints.h>
40
# include <Inventor/nodes/SoSurroundScale.h>
41
# include <Inventor/nodes/SoSwitch.h>
42
# include <Inventor/nodes/SoTransform.h>
43
# include <Inventor/sensors/SoNodeSensor.h>
44
# include <Inventor/SoPickedPoint.h>
45
# include <QApplication>
46
# include <QMenu>
47
# include <QCheckBox>
48
#endif
49

50
#include <boost/range.hpp>
51
#include <App/ElementNamingUtils.h>
52
#include <App/Document.h>
53
#include <Base/BoundBoxPy.h>
54
#include <Base/MatrixPy.h>
55
#include <Base/PlacementPy.h>
56
#include <Base/Tools.h>
57

58
#include "Action.h"
59
#include "MainWindow.h"
60
#include "ViewProviderLink.h"
61
#include "ViewProviderLinkPy.h"
62
#include "Application.h"
63
#include "BitmapFactory.h"
64
#include "Control.h"
65
#include "LinkViewPy.h"
66
#include "Selection.h"
67
#include "SoFCCSysDragger.h"
68
#include "SoFCUnifiedSelection.h"
69
#include "TaskCSysDragger.h"
70
#include "TaskElementColors.h"
71
#include "View3DInventor.h"
72
#include "ViewParams.h"
73
#include "ViewProviderGeometryObject.h"
74

75
#include "ActionFunction.h"
76
#include "Command.h"
77
#include "DlgObjectSelection.h"
78

79
FC_LOG_LEVEL_INIT("App::Link", true, true)
80

81
using namespace Gui;
82
using namespace Base;
83

84
using CharRange = boost::iterator_range<const char*>;
85
////////////////////////////////////////////////////////////////////////////
86

87
static inline bool appendPathSafe(SoPath *path, SoNode *node) {
88
    if(path->getLength()) {
89
        SoNode * tail = path->getTail();
90
        const SoChildList * children = tail->getChildren();
91
        if(!children || children->find((void *)node)<0)
92
            return false;
93
    }
94
    path->append(node);
95
    return true;
96
}
97

98
#ifdef FC_DEBUG
99
#define appendPath(_path,_node)  \
100
do{\
101
    if(!appendPathSafe(_path,_node))\
102
        FC_ERR("LinkView: coin path error");\
103
}while(0)
104
#else
105
#define appendPath(_path, _node) (_path)->append(_node)
106
#endif
107

108
////////////////////////////////////////////////////////////////////////////
109
class Gui::LinkInfo {
110

111
public:
112
    std::atomic<int> ref;
113

114
    using Connection = boost::signals2::scoped_connection;
115
    Connection connChangeIcon;
116

117
    ViewProviderDocumentObject *pcLinked;
118
    std::unordered_set<Gui::LinkOwner*> links;
119

120
    using Pointer = LinkInfoPtr;
121

122
    SoNodeSensor sensor;
123
    SoNodeSensor switchSensor;
124
    SoNodeSensor childSensor;
125
    SoNodeSensor transformSensor;
126

127
    std::array<CoinPtr<SoSeparator>,LinkView::SnapshotMax> pcSnapshots;
128
    std::array<CoinPtr<SoSwitch>,LinkView::SnapshotMax> pcSwitches;
129
    CoinPtr<SoSwitch> pcLinkedSwitch;
130

131
    // for group type view providers
132
    CoinPtr<SoGroup> pcChildGroup;
133
    using NodeMap = std::unordered_map<SoNode *, Pointer>;
134
    NodeMap nodeMap;
135

136
    std::map<qint64, QIcon> iconMap;
137

138
    static ViewProviderDocumentObject *getView(App::DocumentObject *obj) {
139
        if(obj && obj->isAttachedToDocument()) {
140
            Document *pDoc = Application::Instance->getDocument(obj->getDocument());
141
            if(pDoc) {
142
                ViewProvider *vp = pDoc->getViewProvider(obj);
143
                if(vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()))
144
                    return static_cast<ViewProviderDocumentObject*>(vp);
145
            }
146
        }
147
        return nullptr;
148
    }
149

150
    static Pointer get(App::DocumentObject *obj, Gui::LinkOwner *owner) {
151
        return get(getView(obj),owner);
152
    }
153

154
    static Pointer get(ViewProviderDocumentObject *vp, LinkOwner *owner) {
155
        if(!vp)
156
            return Pointer();
157

158
        auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
159
        if(!ext) {
160
            ext = new ViewProviderLinkObserver();
161
            ext->initExtension(vp);
162
        }
163
        if(!ext->linkInfo) {
164
            // extension can be created automatically when restored from document,
165
            // with an empty linkInfo. So we need to check here.
166
            ext->linkInfo = Pointer(new LinkInfo(vp));
167
            ext->linkInfo->update();
168
        }
169
        if(owner)
170
            ext->linkInfo->links.insert(owner);
171
        return ext->linkInfo;
172
    }
173

174
    static void sensorCB(void *data, SoSensor *) {
175
        static_cast<LinkInfo*>(data)->update();
176
    }
177

178
    static void switchSensorCB(void *data, SoSensor *) {
179
        static_cast<LinkInfo*>(data)->updateSwitch();
180
    }
181

182
    static void childSensorCB(void *data, SoSensor *) {
183
        static_cast<LinkInfo*>(data)->updateChildren();
184
    }
185

186
    static void transformSensorCB(void *data, SoSensor *) {
187
        auto self = static_cast<LinkInfo*>(data);
188
        for(size_t i=0;i<self->pcSnapshots.size();++i)  {
189
            if(self->pcSnapshots[i] && i!=LinkView::SnapshotTransform)
190
                self->getSnapshot(i,true);
191
        }
192
    }
193

194
    LinkInfo(ViewProviderDocumentObject *vp)
195
        :ref(0),pcLinked(vp)
196
    {
197
        FC_LOG("new link to " << pcLinked->getObject()->getFullName());
198
        //NOLINTBEGIN
199
        connChangeIcon = vp->signalChangeIcon.connect(
200
                std::bind(&LinkInfo::slotChangeIcon,this));
201
        //NOLINTEND
202
        vp->forceUpdate(true);
203
        sensor.setFunction(sensorCB);
204
        sensor.setData(this);
205
        switchSensor.setFunction(switchSensorCB);
206
        switchSensor.setData(this);
207
        childSensor.setFunction(childSensorCB);
208
        childSensor.setData(this);
209
        transformSensor.setFunction(transformSensorCB);
210
        transformSensor.setData(this);
211
    }
212

213
    ~LinkInfo() = default;
214

215
    bool checkName(const char *name) const {
216
        return isLinked() && strcmp(name,getLinkedName())==0;
217
    }
218

219
    void remove(LinkOwner *owner) {
220
        links.erase(owner);
221
    }
222

223
    bool isLinked() const {
224
        return pcLinked && pcLinked->getObject() &&
225
           pcLinked->getObject()->isAttachedToDocument();
226
    }
227

228
    const char *getLinkedName() const {
229
        return pcLinked->getObject()->getDagKey();
230
    }
231

232
    const char *getLinkedLabel() const {
233
        return pcLinked->getObject()->Label.getValue();
234
    }
235

236
    const char *getLinkedNameSafe() const {
237
        if(isLinked())
238
            return getLinkedName();
239
        return "<nil>";
240
    }
241

242
    const char *getDocName() const {
243
        return pcLinked->getDocument()->getDocument()->getName();
244
    }
245

246
    void detach(bool unlink) {
247
        FC_LOG("link detach " << getLinkedNameSafe());
248
        auto me = LinkInfoPtr(this);
249
        if(unlink) {
250
            while(!links.empty()) {
251
                auto link = *links.begin();
252
                links.erase(links.begin());
253
                link->unlink(me);
254
            }
255
        }
256
        sensor.detach();
257
        switchSensor.detach();
258
        childSensor.detach();
259
        transformSensor.detach();
260
        for(const auto &node : pcSnapshots) {
261
            if(node)
262
                coinRemoveAllChildren(node);
263
        }
264
        for(const auto &node : pcSwitches) {
265
            if(node)
266
                coinRemoveAllChildren(node);
267
        }
268
        pcLinkedSwitch.reset();
269
        if(pcChildGroup) {
270
            coinRemoveAllChildren(pcChildGroup);
271
            pcChildGroup.reset();
272
        }
273
        pcLinked = nullptr;
274
        connChangeIcon.disconnect();
275
    }
276

277
    void updateSwitch(SoSwitch *node=nullptr) {
278
        if(!isLinked() || !pcLinkedSwitch)
279
            return;
280
        int index = pcLinkedSwitch->whichChild.getValue();
281
        for(size_t i=0;i<pcSwitches.size();++i) {
282
            if(!pcSwitches[i] || (node && node!=pcSwitches[i]))
283
                continue;
284
            int count = pcSwitches[i]->getNumChildren();
285
            if((index<0 && i==LinkView::SnapshotChild) || !count)
286
                pcSwitches[i]->whichChild = -1;
287
            else if(count>pcLinked->getDefaultMode())
288
                pcSwitches[i]->whichChild = pcLinked->getDefaultMode();
289
            else
290
                pcSwitches[i]->whichChild = 0;
291
        }
292
    }
293

294
    inline void addref() {
295
        ++ref;
296
    }
297

298
    inline void release(){
299
        int r = --ref;
300
        assert(r>=0);
301
        if(r==0)
302
            delete this;
303
        else if(r==1) {
304
            if(pcLinked) {
305
                FC_LOG("link release " << getLinkedNameSafe());
306
                auto ext = pcLinked->getExtensionByType<ViewProviderLinkObserver>(true);
307
                if(ext && ext->linkInfo == this) {
308
                    pcLinked->forceUpdate(false);
309
                    detach(true);
310
                    ext->linkInfo.reset();
311
                }
312
            }
313
        }
314
    }
315

316
    // VC2013 has trouble with template argument dependent lookup in
317
    // namespace. Have to put the below functions in global namespace.
318
    //
319
    // However, gcc seems to behave the opposite, hence the conditional
320
    // compilation  here.
321
    //
322
#if defined(_MSC_VER)
323
    friend void Gui::intrusive_ptr_add_ref(LinkInfo *px);
324
    friend void Gui::intrusive_ptr_release(LinkInfo *px);
325
#else
326
    friend inline void intrusive_ptr_add_ref(LinkInfo *px) { px->addref(); }
327
    friend inline void intrusive_ptr_release(LinkInfo *px) { px->release(); }
328
#endif
329

330
    bool isVisible() const {
331
        if(!isLinked())
332
            return true;
333
        int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
334
        for(int idx : indices) {
335
            if(!pcSwitches[idx])
336
                continue;
337
            if(pcSwitches[idx]->whichChild.getValue()==-1)
338
                return false;
339
        }
340
        return true;
341
    }
342

343
    void setVisible(bool visible) {
344
        if(!isLinked())
345
            return;
346
        int indices[] = {LinkView::SnapshotTransform, LinkView::SnapshotVisible};
347
        for(int idx : indices) {
348
            if(!pcSwitches[idx])
349
                continue;
350
            if(!visible)
351
                pcSwitches[idx]->whichChild = -1;
352
            else if(pcSwitches[idx]->getNumChildren()>pcLinked->getDefaultMode())
353
                pcSwitches[idx]->whichChild = pcLinked->getDefaultMode();
354
        }
355
    }
356

357
    SoSeparator *getSnapshot(int type, bool update=false) {
358
        if(type<0 || type>=LinkView::SnapshotMax)
359
            return nullptr;
360

361
        SoSeparator *root;
362
        if(!isLinked() || !(root=pcLinked->getRoot()))
363
            return nullptr;
364

365
        if(sensor.getAttachedNode()!=root) {
366
            sensor.detach();
367
            sensor.attach(root);
368
        }
369

370
        auto &pcSnapshot = pcSnapshots[type];
371
        auto &pcModeSwitch = pcSwitches[type];
372
        if(pcSnapshot) {
373
            if(!update)
374
                return pcSnapshot;
375
        }else{
376
            if(ViewParams::instance()->getUseSelectionRoot())
377
                pcSnapshot = new SoFCSelectionRoot;
378
            else
379
                pcSnapshot = new SoSeparator;
380
            pcSnapshot->boundingBoxCaching = SoSeparator::OFF;
381
            pcSnapshot->renderCaching = SoSeparator::OFF;
382
            std::ostringstream ss;
383
            ss << pcLinked->getObject()->getNameInDocument()
384
                << "(" << type << ')';
385
            pcSnapshot->setName(ss.str().c_str());
386
            pcModeSwitch = new SoSwitch;
387
        }
388

389
        pcLinkedSwitch.reset();
390

391
        coinRemoveAllChildren(pcSnapshot);
392
        pcModeSwitch->whichChild = -1;
393
        coinRemoveAllChildren(pcModeSwitch);
394

395
        SoSwitch *pcUpdateSwitch = pcModeSwitch;
396

397
        auto childRoot = pcLinked->getChildRoot();
398

399
        for(int i=0,count=root->getNumChildren();i<count;++i) {
400
            SoNode *node = root->getChild(i);
401
            if(node==pcLinked->getTransformNode()) {
402
                if(type!=LinkView::SnapshotTransform)
403
                    pcSnapshot->addChild(node);
404
                else {
405
                    auto transform = pcLinked->getTransformNode();
406
                    const auto &scale = transform->scaleFactor.getValue();
407
                    if(scale[0]!=1.0 || scale[1]!=1.0 || scale[2]!=1.0) {
408
                        auto trans = new SoTransform;
409
                        pcSnapshot->addChild(trans);
410
                        trans->scaleFactor.setValue(scale);
411
                        trans->scaleOrientation = transform->scaleOrientation;
412
                        if(transformSensor.getAttachedNode()!=transform) {
413
                            transformSensor.detach();
414
                            transformSensor.attach(transform);
415
                        }
416
                    }
417
                }
418
                continue;
419
            } else if(node!=pcLinked->getModeSwitch()) {
420
                pcSnapshot->addChild(node);
421
                continue;
422
            }
423

424
            pcLinkedSwitch = static_cast<SoSwitch*>(node);
425
            if(switchSensor.getAttachedNode() != pcLinkedSwitch) {
426
                switchSensor.detach();
427
                switchSensor.attach(pcLinkedSwitch);
428
                pcUpdateSwitch = nullptr;
429
            }
430

431
            pcSnapshot->addChild(pcModeSwitch);
432
            for(int i=0,count=pcLinkedSwitch->getNumChildren();i<count;++i) {
433
                auto child = pcLinkedSwitch->getChild(i);
434
                if(pcChildGroup && child==childRoot)
435
                    pcModeSwitch->addChild(pcChildGroup);
436
                else
437
                    pcModeSwitch->addChild(child);
438
            }
439
        }
440
        updateSwitch(pcUpdateSwitch);
441
        return pcSnapshot;
442
    }
443

444
    void updateData(const App::Property *prop) {
445
        LinkInfoPtr me(this);
446
        for(auto link : links)
447
            link->onLinkedUpdateData(me,prop);
448
        // update();
449
    }
450

451
    void update() {
452
        if(!isLinked() || pcLinked->isRestoring())
453
            return;
454

455
        updateChildren();
456

457
        for(size_t i=0;i<pcSnapshots.size();++i)
458
            if(pcSnapshots[i])
459
                getSnapshot(i,true);
460
    }
461

462
    void updateChildren() {
463
        if(!isLinked())
464
            return;
465

466
        if(!pcLinked->getChildRoot()) {
467
            childSensor.detach();
468
            if(pcChildGroup)
469
                coinRemoveAllChildren(pcChildGroup);
470
            return;
471
        }
472

473
        if(childSensor.getAttachedNode() != pcLinked->getChildRoot()) {
474
            childSensor.detach();
475
            childSensor.attach(pcLinked->getChildRoot());
476
        }
477
        if(!pcChildGroup)
478
            pcChildGroup = new SoGroup;
479
        else
480
            coinRemoveAllChildren(pcChildGroup);
481

482
        NodeMap nodeMap;
483

484
        for(auto child : pcLinked->claimChildren3D()) {
485
            Pointer info = get(child,nullptr);
486
            if(!info) continue;
487
            SoNode *node = info->getSnapshot(LinkView::SnapshotChild);
488
            if(!node) continue;
489
            nodeMap[node] = info;
490
            pcChildGroup->addChild(node);
491
        }
492

493
        // Use swap instead of clear() here to avoid potential link
494
        // destruction
495
        this->nodeMap.swap(nodeMap);
496
    }
497

498
    bool getElementPicked(bool addname, int type,
499
            const SoPickedPoint *pp, std::ostream &str) const
500
    {
501
        if(!pp || !isLinked() || !pcLinked->isSelectable())
502
            return false;
503

504
        if(addname)
505
            str << getLinkedName() <<'.';
506

507
        auto pcSwitch = pcSwitches[type];
508
        if(pcChildGroup && pcSwitch && pcSwitch->whichChild.getValue()>=0 &&
509
            pcSwitch->getChild(pcSwitch->whichChild.getValue())==pcChildGroup)
510
        {
511
            SoPath *path = pp->getPath();
512
            int index = path->findNode(pcChildGroup);
513
            if(index<=0)
514
                return false;
515
            auto it = nodeMap.find(path->getNode(index+1));
516
            if(it==nodeMap.end())
517
                return false;
518
            return it->second->getElementPicked(true,LinkView::SnapshotChild,pp,str);
519
        }else{
520
            std::string subname;
521
            if(!pcLinked->getElementPicked(pp,subname))
522
                return false;
523
            str<<subname;
524
        }
525
        return true;
526
    }
527

528
    static const char *checkSubname(App::DocumentObject *obj, const char *subname) {
529
#define CHECK_NAME(_name,_end) do{\
530
            if(!_name) return 0;\
531
            const char *_n = _name;\
532
            for(;*subname && *_n; ++subname,++_n)\
533
                if(*subname != *_n) break;\
534
            if(*_n || (*subname!=0 && *subname!=_end))\
535
                    return 0;\
536
            if(*subname == _end) ++subname;\
537
        }while(0)
538

539
        // if(subname[0] == '*') {
540
        //     ++subname;
541
        //     CHECK_NAME(obj->getDocument()->getName(),'*');
542
        // }
543
        CHECK_NAME(obj->getNameInDocument(),'.');
544
        return subname;
545
    }
546

547
    bool getDetail(bool checkname, int type, const char* subname,
548
            SoDetail *&det, SoFullPath *path) const
549
    {
550
        if(!isLinked())
551
            return false;
552

553
        if(checkname) {
554
            subname = checkSubname(pcLinked->getObject(),subname);
555
            if(!subname)
556
                return false;
557
        }
558

559
        if(pcSnapshots[type]->findChild(pcSwitches[type]) < 0) {
560
            if(path) {
561
                if(!appendPathSafe(path,pcSnapshots[type]))
562
                    return false;
563
            }
564
            // this is possible in case of editing, where the switch node
565
            // of the linked view object is temparaly removed from its root
566
            return true;
567
        }
568
        int len = 0;
569
        if(path) {
570
            len = path->getLength();
571
            if(!appendPathSafe(path,pcSnapshots[type]))
572
                return false;
573
            appendPath(path,pcSwitches[type]);
574
        }
575
        if(*subname == 0)
576
            return true;
577

578
        auto pcSwitch = pcSwitches[type];
579
        if(!pcChildGroup || !pcSwitch || pcSwitch->whichChild.getValue()<0 ||
580
            pcSwitch->getChild(pcSwitch->whichChild.getValue())!=pcChildGroup)
581
        {
582
            return pcLinked->getDetailPath(subname,path,false,det);
583
        }
584
        if(path){
585
            appendPath(path,pcChildGroup);
586
            if(pcLinked->getChildRoot())
587
                type = LinkView::SnapshotChild;
588
            else
589
                type = LinkView::SnapshotVisible;
590
        }
591

592
        // Special handling of nodes with childRoot, especially geo feature
593
        // group. It's object hierarchy in the tree view (i.e. in subname) is
594
        // difference from its coin hierarchy. All objects under a geo feature
595
        // group is visually grouped directly under the group's childRoot,
596
        // even though some object has secondary hierarchy in subname. E.g.
597
        //
598
        // Body
599
        //   |--Pad
600
        //       |--Sketch
601
        //
602
        //  Both Sketch and Pad's coin nodes are grouped directly under Body as,
603
        //
604
        // Body
605
        //   |--Pad
606
        //   |--Sketch
607

608
        const char *dot = strchr(subname,'.');
609
        const char *nextsub = subname;
610
        if(!dot)
611
            return false;
612
        auto geoGroup = pcLinked->getObject();
613
        auto sobj = geoGroup;
614
        while(true) {
615
            std::string objname = std::string(nextsub,dot-nextsub+1);
616
            if(!geoGroup->getSubObject(objname.c_str())) {
617
                // this object is not found under the geo group, abort.
618
                break;
619
            }
620
            // Object found under geo group, remember this subname
621
            subname = nextsub;
622

623
            auto ssobj = sobj->getSubObject(objname.c_str());
624
            if(!ssobj) {
625
                FC_ERR("invalid sub name " << nextsub << " of object " << sobj->getFullName());
626
                return false;
627
            }
628
            sobj = ssobj;
629
            auto vp = Application::Instance->getViewProvider(sobj);
630
            if(!vp) {
631
                FC_ERR("cannot find view provider of " << sobj->getFullName());
632
                return false;
633
            }
634
            if(vp->getChildRoot()) {
635
                // In case the children is also a geo group, it will visually
636
                // hold all of its own children, so stop going further down.
637
                break;
638
            }
639
            // new style mapped sub-element
640
            if(Data::isMappedElement(dot+1))
641
                break;
642
            auto next = strchr(dot+1,'.');
643
            if(!next) {
644
                // no dot any more, the following must be a sub-element
645
                break;
646
            }
647
            nextsub = dot+1;
648
            dot = next;
649
        }
650

651
        for(const auto& v : nodeMap) {
652
            if(v.second->getDetail(true,type,subname,det,path))
653
                return true;
654
        }
655
        if(path)
656
            path->truncate(len);
657
        return false;
658
    }
659

660
    void slotChangeIcon() {
661
        iconMap.clear();
662
        if(!isLinked())
663
            return;
664
        LinkInfoPtr me(this);
665
        for(auto link : links)
666
            link->onLinkedIconChange(me);
667
    }
668

669
    QIcon getIcon(QPixmap px) {
670
        static int iconSize = -1;
671
        if(iconSize < 0)
672
            iconSize = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width();
673

674
        if(!isLinked())
675
            return QIcon();
676

677
        if(px.isNull())
678
            return pcLinked->getIcon();
679
        QIcon &iconLink = iconMap[px.cacheKey()];
680
        if(iconLink.isNull()) {
681
            QIcon icon = pcLinked->getIcon();
682
            iconLink = QIcon();
683
            iconLink.addPixmap(BitmapFactory().merge(icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::Off),
684
                px,BitmapFactoryInst::BottomLeft), QIcon::Normal, QIcon::Off);
685
            iconLink.addPixmap(BitmapFactory().merge(icon.pixmap(iconSize, iconSize, QIcon::Normal, QIcon::On ),
686
                px,BitmapFactoryInst::BottomLeft), QIcon::Normal, QIcon::On);
687
        }
688
        return iconLink;
689
    }
690
};
691

692
#if defined(_MSC_VER)
693
namespace Gui
694
{
695
    void intrusive_ptr_add_ref(Gui::LinkInfo* px)
696
    {
697
        px->addref();
698
    }
699

700
    void intrusive_ptr_release(Gui::LinkInfo* px)
701
    {
702
        px->release();
703
    }
704
}
705
#endif
706

707
////////////////////////////////////////////////////////////////////////////////////
708

709
EXTENSION_TYPESYSTEM_SOURCE(Gui::ViewProviderLinkObserver,Gui::ViewProviderExtension)
710

711
ViewProviderLinkObserver::ViewProviderLinkObserver() {
712
    // TODO: any better way to get deleted automatically?
713
    m_isPythonExtension = true;
714
    initExtensionType(ViewProviderLinkObserver::getExtensionClassTypeId());
715
}
716

717
ViewProviderLinkObserver::~ViewProviderLinkObserver() {
718
    if(linkInfo) {
719
        linkInfo->detach(true);
720
        linkInfo.reset();
721
    }
722
}
723

724
bool ViewProviderLinkObserver::isLinkVisible() const {
725
    if(linkInfo)
726
        return linkInfo->isVisible();
727
    return true;
728
}
729

730
void ViewProviderLinkObserver::setLinkVisible(bool visible) {
731
    if(linkInfo)
732
        linkInfo->setVisible(visible);
733
}
734

735
void ViewProviderLinkObserver::extensionBeforeDelete() {
736
    if(linkInfo)
737
        linkInfo->detach(false);
738
}
739

740
void ViewProviderLinkObserver::extensionReattach(App::DocumentObject *) {
741
    if(linkInfo) {
742
        linkInfo->pcLinked =
743
            Base::freecad_dynamic_cast<ViewProviderDocumentObject>(getExtendedContainer());
744
        linkInfo->update();
745
    }
746
}
747

748
void ViewProviderLinkObserver::extensionOnChanged(const App::Property *prop) {
749
    (void)prop;
750
}
751

752
void ViewProviderLinkObserver::extensionModeSwitchChange() {
753
    auto owner = freecad_dynamic_cast<ViewProviderDocumentObject>(getExtendedContainer());
754
    if(owner && linkInfo)
755
        linkInfo->updateSwitch();
756
}
757

758
void ViewProviderLinkObserver::extensionUpdateData(const App::Property *prop) {
759
    if(linkInfo && linkInfo->pcLinked && linkInfo->pcLinked->getObject() &&
760
       prop != &linkInfo->pcLinked->getObject()->Visibility)
761
        linkInfo->updateData(prop);
762
}
763

764
void ViewProviderLinkObserver::extensionFinishRestoring() {
765
    if(linkInfo) {
766
        FC_TRACE("linked finish restoing");
767
        linkInfo->update();
768
    }
769
}
770

771
class LinkView::SubInfo : public LinkOwner {
772
public:
773
    LinkInfoPtr linkInfo;
774
    LinkView &handle;
775
    CoinPtr<SoSeparator> pcNode;
776
    CoinPtr<SoTransform> pcTransform;
777
    std::set<std::string> subElements;
778

779
    friend LinkView;
780

781
    SubInfo(LinkView &handle):handle(handle) {
782
        pcNode = new SoFCSelectionRoot(true);
783
        pcTransform = new SoTransform;
784
        pcNode->addChild(pcTransform);
785
    }
786

787
    ~SubInfo() override {
788
        unlink();
789
        auto root = handle.getLinkRoot();
790
        if(root) {
791
            int idx = root->findChild(pcNode);
792
            if(idx>=0)
793
                root->removeChild(idx);
794
        }
795
    }
796

797
    void onLinkedIconChange(LinkInfoPtr) override {
798
        if(handle.autoSubLink && handle.subInfo.size()==1)
799
            handle.onLinkedIconChange(handle.linkInfo);
800
    }
801

802
    void unlink(LinkInfoPtr info=LinkInfoPtr()) override {
803
        (void)info;
804
        if(linkInfo) {
805
            linkInfo->remove(this);
806
            linkInfo.reset();
807
        }
808
        coinRemoveAllChildren(pcNode);
809
        pcNode->addChild(pcTransform);
810
    }
811

812
    void link(App::DocumentObject *obj) {
813
        if(isLinked() && linkInfo->pcLinked->getObject()==obj)
814
            return;
815
        unlink();
816
        linkInfo = LinkInfo::get(obj,this);
817
        if(linkInfo)
818
            pcNode->addChild(linkInfo->getSnapshot(LinkView::SnapshotTransform));
819
    }
820

821
    bool isLinked() const{
822
        return linkInfo && linkInfo->isLinked();
823
    }
824
};
825

826
//////////////////////////////////////////////////////////////////////////////////
827

828
class LinkView::Element : public LinkOwner {
829
public:
830
    LinkInfoPtr linkInfo;
831
    LinkView &handle;
832
    CoinPtr<SoSwitch> pcSwitch;
833
    CoinPtr<SoFCSelectionRoot> pcRoot;
834
    CoinPtr<SoTransform> pcTransform;
835
    int groupIndex = -1;
836
    bool isGroup = false;
837

838
    friend LinkView;
839

840
    Element(LinkView &handle):handle(handle) {
841
        pcTransform = new SoTransform;
842
        pcRoot = new SoFCSelectionRoot(true);
843
        pcSwitch = new SoSwitch;
844
        pcSwitch->addChild(pcRoot);
845
        pcSwitch->whichChild = 0;
846
    }
847

848
    ~Element() override {
849
        unlink();
850
        auto root = handle.getLinkRoot();
851
        if(root) {
852
            int idx = root->findChild(pcRoot);
853
            if(idx>=0)
854
                root->removeChild(idx);
855
        }
856
    }
857

858
    void unlink(LinkInfoPtr info=LinkInfoPtr()) override{
859
        (void)info;
860
        if(linkInfo) {
861
            linkInfo->remove(this);
862
            linkInfo.reset();
863
        }
864
        coinRemoveAllChildren(pcRoot);
865
    }
866

867
    void link(App::DocumentObject *obj) {
868
        if(isLinked() && linkInfo->pcLinked->getObject()==obj)
869
            return;
870
        unlink();
871
        linkInfo = LinkInfo::get(obj,this);
872
        if(isLinked())
873
            pcRoot->addChild(linkInfo->getSnapshot(handle.childType));
874
    }
875

876
    bool isLinked() const{
877
        return linkInfo && linkInfo->isLinked();
878
    }
879
};
880

881
///////////////////////////////////////////////////////////////////////////////////
882

883
TYPESYSTEM_SOURCE(Gui::LinkView,Base::BaseClass)
884

885
LinkView::LinkView()
886
    :nodeType(SnapshotTransform)
887
    ,childType((SnapshotType)-1),autoSubLink(true)
888
{
889
    pcLinkRoot = new SoFCSelectionRoot;
890
}
891

892
LinkView::~LinkView() {
893
    unlink(linkInfo);
894
    unlink(linkOwner);
895
}
896

897
PyObject *LinkView::getPyObject()
898
{
899
    if (PythonObject.is(Py::_None()))
900
        PythonObject = Py::Object(new LinkViewPy(this),true);
901
    return Py::new_reference_to(PythonObject);
902
}
903

904
void LinkView::setInvalid() {
905
    if (!PythonObject.is(Py::_None())){
906
        auto obj = static_cast<Base::PyObjectBase*>(PythonObject.ptr());
907
        obj->setInvalid();
908
        obj->DecRef();
909
    }else
910
        delete this;
911
}
912

913
Base::BoundBox3d _getBoundBox(ViewProviderDocumentObject *vpd, SoNode *rootNode) {
914
    auto doc = vpd->getDocument();
915
    if(!doc)
916
        LINK_THROW(Base::RuntimeError,"no document");
917
    Gui::MDIView* view = doc->getViewOfViewProvider(vpd);
918
    if(!view)
919
        LINK_THROW(Base::RuntimeError,"no view");
920

921
    Gui::View3DInventorViewer* viewer = static_cast<Gui::View3DInventor*>(view)->getViewer();
922
    SoGetBoundingBoxAction bboxAction(viewer->getSoRenderManager()->getViewportRegion());
923
    bboxAction.apply(rootNode);
924
    auto bbox = bboxAction.getBoundingBox();
925
    float minX,minY,minZ,maxX,maxY,maxZ;
926
    bbox.getMax().getValue(maxX,maxY,maxZ);
927
    bbox.getMin().getValue(minX,minY,minZ);
928
    return Base::BoundBox3d(minX,minY,minZ,maxX,maxY,maxZ);
929
}
930

931
Base::BoundBox3d LinkView::getBoundBox(ViewProviderDocumentObject *vpd) const {
932
    if(!vpd) {
933
        if(!linkOwner || !linkOwner->isLinked())
934
            LINK_THROW(Base::ValueError,"no ViewProvider");
935
        vpd = linkOwner->pcLinked;
936
    }
937
    return _getBoundBox(vpd,pcLinkRoot);
938
}
939

940
ViewProviderDocumentObject *LinkView::getOwner() const {
941
    if(linkOwner && linkOwner->isLinked())
942
        return linkOwner->pcLinked;
943
    return nullptr;
944
}
945

946
void LinkView::setOwner(ViewProviderDocumentObject *vpd) {
947
    unlink(linkOwner);
948
    linkOwner = LinkInfo::get(vpd,this);
949
}
950

951
bool LinkView::isLinked() const{
952
    return linkInfo && linkInfo->isLinked();
953
}
954

955
void LinkView::setDrawStyle(int style, double lineWidth, double pointSize) {
956
    if(!pcDrawStyle) {
957
        if(!style)
958
            return;
959
        pcDrawStyle = new SoDrawStyle;
960
        pcDrawStyle->style = SoDrawStyle::FILLED;
961
        pcLinkRoot->insertChild(pcDrawStyle,0);
962
    }
963
    if(!style) {
964
        pcDrawStyle->setOverride(false);
965
        return;
966
    }
967
    pcDrawStyle->lineWidth = lineWidth;
968
    pcDrawStyle->pointSize = pointSize;
969
    switch(style) {
970
    case 2:
971
        pcDrawStyle->linePattern = 0xf00f;
972
        break;
973
    case 3:
974
        pcDrawStyle->linePattern = 0x0f0f;
975
        break;
976
    case 4:
977
        pcDrawStyle->linePattern = 0xff88;
978
        break;
979
    default:
980
        pcDrawStyle->linePattern = 0xffff;
981
    }
982
    pcDrawStyle->setOverride(true);
983
}
984

985
void LinkView::renderDoubleSide(bool enable) {
986
    if(enable) {
987
        if(!pcShapeHints) {
988
            pcShapeHints = new SoShapeHints;
989
            pcShapeHints->vertexOrdering = SoShapeHints::CLOCKWISE;
990
            pcShapeHints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE;
991
            pcLinkRoot->insertChild(pcShapeHints,0);
992
        }
993
        pcShapeHints->setOverride(true);
994
    }else if(pcShapeHints)
995
        pcShapeHints->setOverride(false);
996
}
997

998
void LinkView::setMaterial(int index, const App::Material *material) {
999
    if(index < 0) {
1000
        if(!material) {
1001
            pcLinkRoot->removeColorOverride();
1002
            return;
1003
        }
1004
        App::Color c = material->diffuseColor;
1005
        c.a = material->transparency;
1006
        pcLinkRoot->setColorOverride(c);
1007
        for(int i=0;i<getSize();++i)
1008
            setMaterial(i,nullptr);
1009
    }else if(index >= (int)nodeArray.size())
1010
        LINK_THROW(Base::ValueError,"LinkView: material index out of range");
1011
    else {
1012
        auto &info = *nodeArray[index];
1013
        if(!material) {
1014
            info.pcRoot->removeColorOverride();
1015
            return;
1016
        }
1017
        App::Color c = material->diffuseColor;
1018
        c.a = material->transparency;
1019
        info.pcRoot->setColorOverride(c);
1020
    }
1021
}
1022

1023
void LinkView::setLink(App::DocumentObject *obj, const std::vector<std::string> &subs) {
1024
    setLinkViewObject(Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
1025
            Application::Instance->getViewProvider(obj)),subs);
1026

1027
}
1028

1029
void LinkView::setLinkViewObject(ViewProviderDocumentObject *vpd,
1030
        const std::vector<std::string> &subs)
1031
{
1032
    if(!isLinked() || linkInfo->pcLinked != vpd) {
1033
        unlink(linkInfo);
1034
        linkInfo = LinkInfo::get(vpd,this);
1035
        if(!linkInfo)
1036
            return;
1037
    }
1038
    subInfo.clear();
1039
    for(const auto &sub : subs) {
1040
        if(sub.empty()) continue;
1041
        const char *subelement = Data::findElementName(sub.c_str());
1042
        std::string subname = sub.substr(0,subelement-sub.c_str());
1043
        auto it = subInfo.find(subname);
1044
        if(it == subInfo.end()) {
1045
            it = subInfo.insert(std::make_pair(subname,std::unique_ptr<SubInfo>())).first;
1046
            it->second = std::make_unique<SubInfo>(*this);
1047
        }
1048
        if(subelement[0])
1049
            it->second->subElements.insert(subelement);
1050
    }
1051
    updateLink();
1052
}
1053

1054
void LinkView::setTransform(SoTransform *pcTransform, const Base::Matrix4D &mat) {
1055
    double dMtrx[16];
1056
    mat.getGLMatrix(dMtrx);
1057
    pcTransform->setMatrix(SbMatrix(dMtrx[0], dMtrx[1], dMtrx[2],  dMtrx[3],
1058
                                    dMtrx[4], dMtrx[5], dMtrx[6],  dMtrx[7],
1059
                                    dMtrx[8], dMtrx[9], dMtrx[10], dMtrx[11],
1060
                                    dMtrx[12],dMtrx[13],dMtrx[14], dMtrx[15]));
1061
}
1062

1063
void LinkView::setSize(int _size) {
1064
    size_t size = _size<0?0:(size_t)_size;
1065
    if(childType<0 && size==nodeArray.size())
1066
        return;
1067
    resetRoot();
1068
    if(!size || childType>=0) {
1069
        nodeArray.clear();
1070
        nodeMap.clear();
1071
        if(!size && childType<0) {
1072
            if(pcLinkedRoot)
1073
                pcLinkRoot->addChild(pcLinkedRoot);
1074
            return;
1075
        }
1076
        childType = SnapshotContainer;
1077
    }
1078
    if(size<nodeArray.size()) {
1079
        for(size_t i=size;i<nodeArray.size();++i)
1080
            nodeMap.erase(nodeArray[i]->pcSwitch);
1081
        nodeArray.resize(size);
1082
    }
1083
    for(const auto &info : nodeArray)
1084
        pcLinkRoot->addChild(info->pcSwitch);
1085

1086
    while(nodeArray.size()<size) {
1087
        nodeArray.push_back(std::make_unique<Element>(*this));
1088
        auto &info = *nodeArray.back();
1089
        info.pcRoot->addChild(info.pcTransform);
1090
        if(pcLinkedRoot)
1091
            info.pcRoot->addChild(pcLinkedRoot);
1092
        pcLinkRoot->addChild(info.pcSwitch);
1093
        nodeMap.emplace(info.pcSwitch,(int)nodeArray.size()-1);
1094
    }
1095
}
1096

1097
void LinkView::resetRoot() {
1098
    coinRemoveAllChildren(pcLinkRoot);
1099
    if(pcTransform)
1100
        pcLinkRoot->addChild(pcTransform);
1101
    if(pcShapeHints)
1102
        pcLinkRoot->addChild(pcShapeHints);
1103
    if(pcDrawStyle)
1104
        pcLinkRoot->addChild(pcDrawStyle);
1105
}
1106

1107
void LinkView::setChildren(const std::vector<App::DocumentObject*> &children,
1108
        const boost::dynamic_bitset<> &vis, SnapshotType type)
1109
{
1110
    if(children.empty()) {
1111
        if(!nodeArray.empty()) {
1112
            nodeArray.clear();
1113
            nodeMap.clear();
1114
            childType = SnapshotContainer;
1115
            resetRoot();
1116
            if(pcLinkedRoot)
1117
                pcLinkRoot->addChild(pcLinkedRoot);
1118
        }
1119
        return;
1120
    }
1121

1122
    if(type<0 || type>=SnapshotMax)
1123
        LINK_THROW(Base::ValueError,"invalid children type");
1124

1125
    resetRoot();
1126

1127
    if(childType<0)
1128
        nodeArray.clear();
1129
    childType = type;
1130

1131
    if(nodeArray.size() > children.size())
1132
        nodeArray.resize(children.size());
1133
    else
1134
        nodeArray.reserve(children.size());
1135

1136
    std::map<App::DocumentObject*, size_t> groups;
1137
    for(size_t i=0;i<children.size();++i) {
1138
        auto obj = children[i];
1139
        if(nodeArray.size()<=i) {
1140
            nodeArray.push_back(std::make_unique<Element>(*this));
1141
        }
1142
        auto &info = *nodeArray[i];
1143
        info.isGroup = false;
1144
        info.groupIndex = -1;
1145
        info.pcSwitch->whichChild = (vis.size()<=i||vis[i])?0:-1;
1146
        info.link(obj);
1147
        if(obj->hasExtension(App::GroupExtension::getExtensionClassTypeId(),false)) {
1148
            info.isGroup = true;
1149
            coinRemoveAllChildren(info.pcRoot);
1150
            groups.emplace(obj,i);
1151
        }
1152
    }
1153
    nodeMap.clear();
1154
    for(size_t i=0;i<nodeArray.size();++i) {
1155
        auto &info = *nodeArray[i];
1156
        nodeMap.emplace(info.pcSwitch,i);
1157
        if(info.isLinked() && !groups.empty()) {
1158
            auto iter = groups.find(App::GroupExtension::getGroupOfObject(
1159
                            info.linkInfo->pcLinked->getObject()));
1160
            if(iter != groups.end()) {
1161
                info.groupIndex = iter->second;
1162
                auto &groupInfo = *nodeArray[iter->second];
1163
                groupInfo.pcRoot->addChild(info.pcSwitch);
1164
                continue;
1165
            }
1166
        }
1167
        pcLinkRoot->addChild(info.pcSwitch);
1168
    }
1169
}
1170

1171
std::vector<ViewProviderDocumentObject*> LinkView::getChildren() const {
1172
    std::vector<ViewProviderDocumentObject*> ret;
1173
    for(const auto &info : nodeArray) {
1174
        if(info->isLinked())
1175
            ret.push_back(info->linkInfo->pcLinked);
1176
    }
1177
    return ret;
1178
}
1179

1180
void LinkView::setTransform(int index, const Base::Matrix4D &mat) {
1181
    if(index<0) {
1182
        if(!pcTransform) {
1183
            pcTransform = new SoTransform;
1184
            pcLinkRoot->insertChild(pcTransform,0);
1185
        }
1186
        setTransform(pcTransform,mat);
1187
        return;
1188
    }
1189
    if(index<0 || index>=(int)nodeArray.size())
1190
        LINK_THROW(Base::ValueError,"LinkView: index out of range");
1191
    setTransform(nodeArray[index]->pcTransform,mat);
1192
}
1193

1194
void LinkView::setElementVisible(int idx, bool visible) {
1195
    if(idx>=0 && idx<(int)nodeArray.size())
1196
        nodeArray[idx]->pcSwitch->whichChild = visible?0:-1;
1197
}
1198

1199
bool LinkView::isElementVisible(int idx) const {
1200
    if(idx>=0 && idx<(int)nodeArray.size())
1201
        return nodeArray[idx]->pcSwitch->whichChild.getValue()>=0;
1202
    return false;
1203
}
1204

1205
ViewProviderDocumentObject *LinkView::getLinkedView() const {
1206
    auto link = linkInfo;
1207
    if(autoSubLink && subInfo.size()==1)
1208
        link = subInfo.begin()->second->linkInfo;
1209
    return link?link->pcLinked:nullptr;
1210
}
1211

1212
std::vector<std::string> LinkView::getSubNames() const {
1213
    std::vector<std::string> ret;
1214
    for(const auto &v : subInfo) {
1215
        if(v.second->subElements.empty()) {
1216
            ret.push_back(v.first);
1217
            continue;
1218
        }
1219
        for(const auto &s : v.second->subElements)
1220
            ret.push_back(v.first+s);
1221
    }
1222
    return ret;
1223
}
1224

1225
void LinkView::setNodeType(SnapshotType type, bool sublink) {
1226
    autoSubLink = sublink;
1227
    if(nodeType==type)
1228
        return;
1229
    if(type>=SnapshotMax ||
1230
       (type<0 && type!=SnapshotContainer && type!=SnapshotContainerTransform))
1231
        LINK_THROW(Base::ValueError,"LinkView: invalid node type");
1232

1233
    if(nodeType>=0 && type<0) {
1234
        if(pcLinkedRoot) {
1235
            SoSelectionElementAction action(SoSelectionElementAction::None,true);
1236
            action.apply(pcLinkedRoot);
1237
        }
1238
        replaceLinkedRoot(CoinPtr<SoSeparator>(new SoFCSelectionRoot));
1239
    }else if(nodeType<0 && type>=0) {
1240
        if(isLinked())
1241
            replaceLinkedRoot(linkInfo->getSnapshot(type));
1242
        else
1243
            replaceLinkedRoot(nullptr);
1244
    }
1245
    nodeType = type;
1246
    updateLink();
1247
}
1248

1249
void LinkView::replaceLinkedRoot(SoSeparator *root) {
1250
    if(root==pcLinkedRoot)
1251
        return;
1252
    if(nodeArray.empty()) {
1253
        if(pcLinkedRoot && root)
1254
            pcLinkRoot->replaceChild(pcLinkedRoot,root);
1255
        else if(root)
1256
            pcLinkRoot->addChild(root);
1257
        else
1258
            resetRoot();
1259
    }else if(childType<0) {
1260
        if(pcLinkedRoot && root) {
1261
            for(const auto &info : nodeArray)
1262
                info->pcRoot->replaceChild(pcLinkedRoot,root);
1263
        }else if(root) {
1264
            for(const auto &info : nodeArray)
1265
                info->pcRoot->addChild(root);
1266
        }else{
1267
            for(const auto &info : nodeArray)
1268
                info->pcRoot->removeChild(pcLinkedRoot);
1269
        }
1270
    }
1271
    pcLinkedRoot = root;
1272
}
1273

1274
void LinkView::onLinkedIconChange(LinkInfoPtr info) {
1275
    if(info==linkInfo && info!=linkOwner && linkOwner && linkOwner->isLinked())
1276
        linkOwner->pcLinked->signalChangeIcon();
1277
}
1278

1279
void LinkView::onLinkedUpdateData(LinkInfoPtr info, const App::Property *prop) {
1280
    if(info!=linkInfo || !linkOwner || !linkOwner->isLinked() || info==linkOwner)
1281
        return;
1282
    auto ext = linkOwner->pcLinked->getObject()->getExtensionByType<App::LinkBaseExtension>(true);
1283
    if (ext && !(prop->getType() & App::Prop_Output) &&
1284
            !prop->testStatus(App::Property::Output))
1285
    {
1286
        // propagate the signalChangedObject to potentially multiple levels
1287
        // of links, to inform tree view of children change, and other
1288
        // parent objects about the change. But we need to be careful to not
1289
        // touch the object if the property of change is marked as output.
1290
        ext->_LinkTouched.touch();
1291
    }else{
1292
        // In case the owner object does not have link extension, here is a
1293
        // trick to link the signalChangedObject from linked object to the
1294
        // owner
1295
        linkOwner->pcLinked->getDocument()->signalChangedObject(
1296
                *linkOwner->pcLinked,linkOwner->pcLinked->getObject()->Label);
1297
    }
1298
}
1299

1300
void LinkView::updateLink() {
1301
    if(!isLinked())
1302
        return;
1303

1304
    if(linkOwner && linkOwner->isLinked() && linkOwner->pcLinked->isRestoring()) {
1305
        FC_TRACE("restoring '" << linkOwner->pcLinked->getObject()->getFullName() << "'");
1306
        return;
1307
    }
1308

1309
    // TODO: is it a good idea to clear any selection here?
1310
    pcLinkRoot->resetContext();
1311

1312
    if(nodeType >= 0) {
1313
        replaceLinkedRoot(linkInfo->getSnapshot(nodeType));
1314
        return;
1315
    }
1316

1317
    // rebuild link sub objects tree
1318
    CoinPtr<SoSeparator> linkedRoot = pcLinkedRoot;
1319
    if(!linkedRoot)
1320
        linkedRoot = new SoFCSelectionRoot;
1321
    else{
1322
        SoSelectionElementAction action(SoSelectionElementAction::None,true);
1323
        action.apply(linkedRoot);
1324
        coinRemoveAllChildren(linkedRoot);
1325
    }
1326

1327
    SoTempPath path(10);
1328
    path.ref();
1329
    appendPath(&path,linkedRoot);
1330
    auto obj = linkInfo->pcLinked->getObject();
1331
    for(const auto &v : subInfo) {
1332
        auto &sub = *v.second;
1333
        Base::Matrix4D mat;
1334
        App::DocumentObject *sobj = obj->getSubObject(
1335
                v.first.c_str(), nullptr, &mat, nodeType==SnapshotContainer);
1336
        if(!sobj) {
1337
            sub.unlink();
1338
            continue;
1339
        }
1340
        sub.link(sobj);
1341
        linkedRoot->addChild(sub.pcNode);
1342
        setTransform(sub.pcTransform,mat);
1343

1344
        if(!sub.subElements.empty()) {
1345
            path.truncate(1);
1346
            appendPath(&path,sub.pcNode);
1347
            SoSelectionElementAction action(SoSelectionElementAction::Append,true);
1348
            for(const auto &subelement : sub.subElements) {
1349
                path.truncate(2);
1350
                SoDetail *det = nullptr;
1351
                if(!sub.linkInfo->getDetail(false,SnapshotTransform,subelement.c_str(),det,&path))
1352
                    continue;
1353
                action.setElement(det);
1354
                action.apply(&path);
1355
                delete det;
1356
            }
1357
        }
1358
    }
1359
    path.unrefNoDelete();
1360
    replaceLinkedRoot(linkedRoot);
1361
}
1362

1363
bool LinkView::linkGetElementPicked(const SoPickedPoint *pp, std::string &subname) const
1364
{
1365
    std::ostringstream ss;
1366
    CoinPtr<SoPath> path = pp->getPath();
1367
    if(!nodeArray.empty()) {
1368
        auto idx = path->findNode(pcLinkRoot);
1369
        if (idx < 0 || idx + 2 >= path->getLength()) {
1370
            return false;
1371
        }
1372
        auto node = path->getNode(idx+1);
1373
        auto it = nodeMap.find(node);
1374
        if(it == nodeMap.end() || !isElementVisible(it->second)) {
1375
            return false;
1376
        }
1377
        int nodeIdx = it->second;
1378
        ++idx;
1379
        while(nodeArray[nodeIdx]->isGroup) {
1380
            auto &info = *nodeArray[nodeIdx];
1381
            if(!info.isLinked()) {
1382
                return false;
1383
            }
1384
            ss << info.linkInfo->getLinkedName() << '.';
1385
            idx += 2;
1386
            if(idx>=path->getLength()) {
1387
                return false;
1388
            }
1389
            auto iter = nodeMap.find(path->getNode(idx));
1390
            if(iter == nodeMap.end() || !isElementVisible(iter->second)) {
1391
                return false;
1392
            }
1393
            nodeIdx = iter->second;
1394
        }
1395

1396
        auto &info = *nodeArray[nodeIdx];
1397
        if (!info.linkInfo) {
1398
            ss << it->second << '.';
1399
        }
1400
        else {
1401
            ss << info.linkInfo->getLinkedName() << '.';
1402
        }
1403

1404
        if(info.isLinked()) {
1405
            if (!info.linkInfo->getElementPicked(false, childType, pp, ss)) {
1406
                return false;
1407
            }
1408
            subname = ss.str();
1409
            return true;
1410
        }
1411
    }
1412

1413
    if(!isLinked()) {
1414
        return false;
1415
    }
1416

1417
    if(nodeType >= 0) {
1418
        if(linkInfo->getElementPicked(false,nodeType,pp,ss)) {
1419
            subname = ss.str();
1420
            return true;
1421
        }
1422
        return false;
1423
    }
1424
    auto idx = path->findNode(pcLinkedRoot);
1425
    if(idx<0 || idx+1>=path->getLength()) {
1426
        return false;
1427
    }
1428
    auto node = path->getNode(idx+1);
1429
    for(const auto &v : subInfo) {
1430
        auto &sub = *v.second;
1431
        if (node != sub.pcNode) {
1432
            continue;
1433
        }
1434

1435
        std::ostringstream ss2;
1436
        if(!sub.linkInfo->getElementPicked(false,SnapshotTransform,pp,ss2)) {
1437
            return false;
1438
        }
1439
        const std::string &element = ss2.str();
1440
        if(!sub.subElements.empty()) {
1441
            if(sub.subElements.find(element)==sub.subElements.end()) {
1442
                auto pos = element.find('.');
1443
                if (pos == std::string::npos ||
1444
                    sub.subElements.find(element.c_str() + pos + 1) == sub.subElements.end()) {
1445
                    return false;
1446
                }
1447
            }
1448
        }
1449
        if (!autoSubLink || subInfo.size() > 1) {
1450
            ss << v.first;
1451
        }
1452
        ss << element;
1453
        subname = ss.str();
1454
        return true;
1455
    }
1456
    return false;
1457
}
1458

1459
bool LinkView::getGroupHierarchy(int index, SoFullPath *path) const {
1460
    if(index > (int)nodeArray.size())
1461
        return false;
1462
    auto &info = *nodeArray[index];
1463
    if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
1464
        return false;
1465
    appendPath(path,info.pcSwitch);
1466
    appendPath(path,info.pcRoot);
1467
    return true;
1468
}
1469

1470
bool LinkView::linkGetDetailPath(const char *subname, SoFullPath *path, SoDetail *&det) const
1471
{
1472
    if(!subname || *subname==0)
1473
        return true;
1474
    auto len = path->getLength();
1475
    if(nodeArray.empty()) {
1476
        if(!appendPathSafe(path,pcLinkRoot))
1477
            return false;
1478
    } else {
1479
        int idx = -1;
1480
        if (subname[0]>='0' && subname[0]<='9') {
1481
            idx = App::LinkBaseExtension::getArrayIndex(subname,&subname);
1482
        } else {
1483
            while(true) {
1484
                const char *dot = strchr(subname,'.');
1485
                if(!dot)
1486
                    break;
1487
                int i = 0;
1488
                if (subname[0] == '$') {
1489
                    CharRange name(subname+1,dot);
1490
                    for(const auto &info : nodeArray) {
1491
                        if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedLabel())) {
1492
                            idx = i;
1493
                            break;
1494
                        }
1495
                        ++i;
1496
                    }
1497
                } else {
1498
                    CharRange name(subname,dot);
1499
                    for(const auto &info : nodeArray) {
1500
                        if(info->isLinked() && boost::equals(name,info->linkInfo->getLinkedName())) {
1501
                            idx = i;
1502
                            break;
1503
                        }
1504
                        ++i;
1505
                    }
1506
                }
1507

1508
                if(idx<0)
1509
                    return false;
1510

1511
                subname = dot+1;
1512
                if(!subname[0] || nodeArray[idx]->isGroup==0)
1513
                    break;
1514
                idx = -1;
1515
            }
1516
        }
1517

1518
        if(idx<0 || idx>=(int)nodeArray.size())
1519
            return false;
1520
        auto &info = *nodeArray[idx];
1521
        if(!appendPathSafe(path,pcLinkRoot))
1522
            return false;
1523
        if(info.groupIndex>=0 && !getGroupHierarchy(info.groupIndex,path))
1524
            return false;
1525
        appendPath(path,info.pcSwitch);
1526
        appendPath(path,info.pcRoot);
1527

1528
        if(*subname == 0)
1529
            return true;
1530

1531
        if(info.isLinked()) {
1532
            info.linkInfo->getDetail(false,childType,subname,det,path);
1533
            return true;
1534
        }
1535
    }
1536
    if(isLinked()) {
1537
        if(nodeType >= 0) {
1538
            if(linkInfo->getDetail(false,nodeType,subname,det,path))
1539
                return true;
1540
        }else {
1541
            appendPath(path,pcLinkedRoot);
1542
            for(const auto &v : subInfo) {
1543
                auto &sub = *v.second;
1544
                if(!sub.isLinked())
1545
                    continue;
1546
                const char *nextsub;
1547
                if(autoSubLink && subInfo.size()==1)
1548
                    nextsub = subname;
1549
                else{
1550
                    if(!boost::algorithm::starts_with(subname,v.first))
1551
                        continue;
1552
                    nextsub = subname+v.first.size();
1553
                    if(*nextsub != '.')
1554
                        continue;
1555
                    ++nextsub;
1556
                }
1557
                if(*nextsub && !sub.subElements.empty() &&
1558
                   sub.subElements.find(nextsub)==sub.subElements.end())
1559
                    break;
1560
                appendPath(path,sub.pcNode);
1561
                len = path->getLength();
1562
                if(sub.linkInfo->getDetail(false,SnapshotTransform,nextsub,det,path))
1563
                    return true;
1564
                break;
1565
            }
1566
        }
1567
    }
1568
    path->truncate(len);
1569
    return false;
1570
}
1571

1572
void LinkView::unlink(LinkInfoPtr info) {
1573
    if(!info)
1574
        return;
1575
    if(info == linkOwner) {
1576
        linkOwner->remove(this);
1577
        linkOwner.reset();
1578
    }
1579
    if(info != linkInfo)
1580
        return;
1581
    if(linkInfo) {
1582
        linkInfo->remove(this);
1583
        linkInfo.reset();
1584
    }
1585
    pcLinkRoot->resetContext();
1586
    if(pcLinkedRoot) {
1587
        if(nodeArray.empty())
1588
            resetRoot();
1589
        else {
1590
            for(const auto &info : nodeArray) {
1591
                int idx;
1592
                if(info->isLinked() &&
1593
                   (idx=info->pcRoot->findChild(pcLinkedRoot))>=0)
1594
                    info->pcRoot->removeChild(idx);
1595
            }
1596
        }
1597
        pcLinkedRoot.reset();
1598
    }
1599
    subInfo.clear();
1600
    return;
1601
}
1602

1603
QIcon LinkView::getLinkedIcon(QPixmap px) const {
1604
    auto link = linkInfo;
1605
    if(autoSubLink && subInfo.size()==1)
1606
        link = subInfo.begin()->second->linkInfo;
1607
    if(!link || !link->isLinked())
1608
        return QIcon();
1609
    return link->getIcon(px);
1610
}
1611

1612
bool LinkView::hasSubs() const {
1613
    return isLinked() && !subInfo.empty();
1614
}
1615

1616
///////////////////////////////////////////////////////////////////////////////////
1617

1618
PROPERTY_SOURCE(Gui::ViewProviderLink, Gui::ViewProviderDocumentObject)
1619

1620
static const char *_LinkIcon = "Link";
1621
// static const char *_LinkArrayIcon = "LinkArray";
1622
static const char *_LinkGroupIcon = "LinkGroup";
1623
static const char *_LinkElementIcon = "LinkElement";
1624

1625
ViewProviderLink::ViewProviderLink()
1626
    :linkType(LinkTypeNone),hasSubName(false),hasSubElement(false)
1627
    ,useCenterballDragger(false),childVp(nullptr),overlayCacheKey(0)
1628
{
1629
    sPixmap = _LinkIcon;
1630

1631
    ADD_PROPERTY_TYPE(Selectable, (true), " Link", App::Prop_None, 0);
1632

1633
    ADD_PROPERTY_TYPE(OverrideMaterial, (false), " Link", App::Prop_None, "Override linked object's material");
1634

1635
    App::Material mat(App::Material::DEFAULT);
1636
    mat.diffuseColor.setPackedValue(ViewParams::instance()->getDefaultLinkColor());
1637
    ADD_PROPERTY_TYPE(ShapeMaterial, (mat), " Link", App::Prop_None, 0);
1638
    ShapeMaterial.setStatus(App::Property::MaterialEdit, true);
1639

1640
    ADD_PROPERTY_TYPE(DrawStyle,((long int)0), " Link", App::Prop_None, "");
1641
    static const char* DrawStyleEnums[]= {"None","Solid","Dashed","Dotted","Dashdot",nullptr};
1642
    DrawStyle.setEnums(DrawStyleEnums);
1643

1644
    int lwidth = ViewParams::instance()->getDefaultShapeLineWidth();
1645
    ADD_PROPERTY_TYPE(LineWidth,(lwidth), " Link", App::Prop_None, "");
1646

1647
    static App::PropertyFloatConstraint::Constraints sizeRange = {1.0,64.0,1.0};
1648
    LineWidth.setConstraints(&sizeRange);
1649

1650
    ADD_PROPERTY_TYPE(PointSize,(lwidth), " Link", App::Prop_None, "");
1651
    PointSize.setConstraints(&sizeRange);
1652

1653
    ADD_PROPERTY(MaterialList,());
1654
    MaterialList.setStatus(App::Property::NoMaterialListEdit, true);
1655

1656
    ADD_PROPERTY(OverrideMaterialList,());
1657
    ADD_PROPERTY(OverrideColorList,());
1658

1659
    ADD_PROPERTY(ChildViewProvider, (""));
1660
    ChildViewProvider.setStatus(App::Property::Hidden,true);
1661

1662
    DisplayMode.setStatus(App::Property::Status::Hidden, true);
1663

1664
    linkView = new LinkView;
1665
}
1666

1667
ViewProviderLink::~ViewProviderLink()
1668
{
1669
    linkView->setInvalid();
1670
}
1671

1672
bool ViewProviderLink::isSelectable() const {
1673
    return !pcDragger && Selectable.getValue();
1674
}
1675

1676
void ViewProviderLink::attach(App::DocumentObject *pcObj) {
1677
    SoNode *node = linkView->getLinkRoot();
1678
    node->setName(pcObj->getFullName().c_str());
1679
    addDisplayMaskMode(node,"Link");
1680
    if(childVp) {
1681
        childVpLink = LinkInfo::get(childVp,nullptr);
1682
        node = childVpLink->getSnapshot(LinkView::SnapshotTransform);
1683
    }
1684
    addDisplayMaskMode(node,"ChildView");
1685
    setDisplayMaskMode("Link");
1686
    inherited::attach(pcObj);
1687
    checkIcon();
1688
    if(pcObj->isDerivedFrom(App::LinkElement::getClassTypeId()))
1689
        hide();
1690
    linkView->setOwner(this);
1691

1692
}
1693

1694
void ViewProviderLink::reattach(App::DocumentObject *obj) {
1695
    linkView->setOwner(this);
1696
    if(childVp)
1697
        childVp->reattach(obj);
1698
    ViewProviderDocumentObject::reattach(obj);
1699
}
1700

1701
std::vector<std::string> ViewProviderLink::getDisplayModes() const
1702
{
1703
    std::vector<std::string> StrList = inherited::getDisplayModes();
1704
    StrList.emplace_back("Link");
1705
    StrList.emplace_back("ChildView");
1706
    return StrList;
1707
}
1708

1709
QIcon ViewProviderLink::getIcon() const {
1710
    auto ext = getLinkExtension();
1711
    if(ext) {
1712
        auto link = ext->getLinkedObjectValue();
1713
        if(link && link!=getObject()) {
1714
            QPixmap overlay = getOverlayPixmap();
1715
            overlayCacheKey = overlay.cacheKey();
1716
            QIcon icon = linkView->getLinkedIcon(overlay);
1717
            if(!icon.isNull())
1718
                return icon;
1719
        }
1720
    }
1721
    overlayCacheKey = 0;
1722
    return Gui::BitmapFactory().pixmap(sPixmap);
1723
}
1724

1725
QPixmap ViewProviderLink::getOverlayPixmap() const {
1726
    auto ext = getLinkExtension();
1727
    int px = 12 * getMainWindow()->devicePixelRatioF();
1728
    if(ext && ext->getLinkedObjectProperty() && ext->_getElementCountValue())
1729
        return BitmapFactory().pixmapFromSvg("LinkArrayOverlay", QSizeF(px,px));
1730
    else if(hasSubElement)
1731
        return BitmapFactory().pixmapFromSvg("LinkSubElement", QSizeF(px,px));
1732
    else if(hasSubName)
1733
        return BitmapFactory().pixmapFromSvg("LinkSubOverlay", QSizeF(px,px));
1734
    else
1735
        return BitmapFactory().pixmapFromSvg("LinkOverlay", QSizeF(px,px));
1736
}
1737

1738
void ViewProviderLink::onChanged(const App::Property* prop) {
1739
    if(prop==&ChildViewProvider) {
1740
        childVp = freecad_dynamic_cast<ViewProviderDocumentObject>(ChildViewProvider.getObject().get());
1741
        if(childVp && getObject()) {
1742
            if(strcmp(childVp->getTypeId().getName(),getObject()->getViewProviderName())!=0
1743
                    && !childVp->allowOverride(*getObject()))
1744
            {
1745
                FC_ERR("Child view provider type '" << childVp->getTypeId().getName()
1746
                        << "' does not support " << getObject()->getFullName());
1747
            } else {
1748
                childVp->setPropertyPrefix("ChildViewProvider.");
1749
                childVp->Visibility.setValue(getObject()->Visibility.getValue());
1750
                childVp->attach(getObject());
1751
                childVp->updateView();
1752
                childVp->setActiveMode();
1753
                if(pcModeSwitch->getNumChildren()>1){
1754
                    childVpLink = LinkInfo::get(childVp,nullptr);
1755
                    pcModeSwitch->replaceChild(1,childVpLink->getSnapshot(LinkView::SnapshotTransform));
1756
                }
1757
            }
1758
        }
1759
    }else if(!isRestoring()) {
1760
        if (prop == &OverrideMaterial || prop == &ShapeMaterial ||
1761
            prop == &MaterialList || prop == &OverrideMaterialList)
1762
        {
1763
            applyMaterial();
1764
        }else if(prop == &OverrideColorList) {
1765
            applyColors();
1766
        }else if(prop==&DrawStyle || prop==&PointSize || prop==&LineWidth) {
1767
            if(!DrawStyle.getValue())
1768
                linkView->setDrawStyle(0);
1769
            else
1770
                linkView->setDrawStyle(DrawStyle.getValue(),LineWidth.getValue(),PointSize.getValue());
1771
        }
1772
    }
1773

1774
    inherited::onChanged(prop);
1775
}
1776

1777
bool ViewProviderLink::setLinkType(App::LinkBaseExtension *ext) {
1778
    auto propLink = ext->getLinkedObjectProperty();
1779
    if(!propLink)
1780
        return false;
1781
    LinkType type;
1782
    if(hasSubName)
1783
        type = LinkTypeSubs;
1784
    else
1785
        type = LinkTypeNormal;
1786
    if(linkType != type)
1787
        linkType = type;
1788
    switch(type) {
1789
    case LinkTypeSubs:
1790
        linkView->setNodeType(ext->linkTransform()?LinkView::SnapshotContainer:
1791
                LinkView::SnapshotContainerTransform);
1792
        break;
1793
    case LinkTypeNormal:
1794
        linkView->setNodeType(ext->linkTransform()?LinkView::SnapshotVisible:
1795
                LinkView::SnapshotTransform);
1796
        break;
1797
    default:
1798
        break;
1799
    }
1800
    return true;
1801
}
1802

1803
App::LinkBaseExtension *ViewProviderLink::getLinkExtension() {
1804
    if(!pcObject || !pcObject->isAttachedToDocument())
1805
        return nullptr;
1806
    return pcObject->getExtensionByType<App::LinkBaseExtension>(true);
1807
}
1808

1809
const App::LinkBaseExtension *ViewProviderLink::getLinkExtension() const{
1810
    if(!pcObject || !pcObject->isAttachedToDocument())
1811
        return nullptr;
1812
    return const_cast<App::DocumentObject*>(pcObject)->getExtensionByType<App::LinkBaseExtension>(true);
1813
}
1814

1815
void ViewProviderLink::updateData(const App::Property *prop) {
1816
    if(childVp)
1817
        childVp->updateData(prop);
1818
    if(!isRestoring() && !pcObject->isRestoring()) {
1819
        auto ext = getLinkExtension();
1820
        if(ext) updateDataPrivate(getLinkExtension(),prop);
1821
    }
1822
    return inherited::updateData(prop);
1823
}
1824

1825
static inline bool canScale(const Base::Vector3d &v) {
1826
    return fabs(v.x)>1e-7 && fabs(v.y)>1e-7 && fabs(v.z)>1e-7;
1827
}
1828

1829
void ViewProviderLink::updateDataPrivate(App::LinkBaseExtension *ext, const App::Property *prop) {
1830
    if(!prop)
1831
        return;
1832
    if(prop == &ext->_ChildCache) {
1833
        updateElementList(ext);
1834
    } else if(prop == &ext->_LinkTouched) {
1835
        if(linkView->hasSubs())
1836
            linkView->updateLink();
1837
        applyColors();
1838
        checkIcon(ext);
1839
    }else if(prop==ext->getColoredElementsProperty()) {
1840
        if(!prop->testStatus(App::Property::User3))
1841
            applyColors();
1842
    }else if(prop==ext->getScaleProperty() || prop==ext->getScaleVectorProperty()) {
1843
        if(!prop->testStatus(App::Property::User3)) {
1844
            const auto &v = ext->getScaleVector();
1845
            if(canScale(v))
1846
                pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
1847
            SbMatrix matrix = convert(ext->getTransform(false));
1848
            linkView->renderDoubleSide(matrix.det3() < 1e-7);
1849
        }
1850
    }else if(prop == ext->getPlacementProperty() || prop == ext->getLinkPlacementProperty()) {
1851
        auto propLinkPlacement = ext->getLinkPlacementProperty();
1852
        if(!propLinkPlacement || propLinkPlacement == prop) {
1853
            const auto &pla = static_cast<const App::PropertyPlacement*>(prop)->getValue();
1854
            ViewProviderGeometryObject::updateTransform(pla, pcTransform);
1855
            const auto &v = ext->getScaleVector();
1856
            if(canScale(v))
1857
                pcTransform->scaleFactor.setValue(v.x,v.y,v.z);
1858
            SbMatrix matrix = convert(ext->getTransform(false));
1859
            linkView->renderDoubleSide(matrix.det3() < 1e-7);
1860
        }
1861
    }else if(prop == ext->getLinkCopyOnChangeGroupProperty()) {
1862
        if (auto group = ext->getLinkCopyOnChangeGroupValue()) {
1863
            auto vp = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
1864
                    Application::Instance->getViewProvider(group));
1865
            if (vp) {
1866
                vp->hide();
1867
                vp->ShowInTree.setValue(false);
1868
            }
1869
        }
1870
    }else if(prop == ext->getLinkedObjectProperty()) {
1871

1872
        if(!prop->testStatus(App::Property::User3)) {
1873
            std::vector<std::string> subs;
1874
            const char *subname = ext->getSubName();
1875
            std::string sub;
1876
            if(subname)
1877
                sub = subname;
1878
            hasSubElement = false;
1879
            for(const auto &s : ext->getSubElements()) {
1880
                if(s.empty()) continue;
1881
                hasSubElement = true;
1882
                subs.push_back(sub+s);
1883
            }
1884

1885
            if(subs.empty() && !sub.empty())
1886
                subs.push_back(sub);
1887

1888
            hasSubName = !subs.empty();
1889
            setLinkType(ext);
1890

1891
            auto obj = ext->getLinkedObjectValue();
1892
            linkView->setLink(obj,subs);
1893

1894
            if(ext->_getShowElementValue())
1895
                updateElementList(ext);
1896
            else
1897
                updateDataPrivate(ext,ext->_getElementCountProperty());
1898

1899
            // applyColors();
1900
            signalChangeIcon();
1901
        }
1902
    }else if(prop == ext->getLinkTransformProperty()) {
1903
        setLinkType(ext);
1904
        applyColors();
1905
    }else if(prop==ext->_getElementCountProperty()) {
1906
        if(!ext->_getShowElementValue()) {
1907
            linkView->setSize(ext->_getElementCountValue());
1908
            updateDataPrivate(ext,ext->getVisibilityListProperty());
1909
            updateDataPrivate(ext,ext->getPlacementListProperty());
1910
        }
1911
    }else if(prop == ext->_getShowElementProperty()) {
1912
        if(!ext->_getShowElementValue()) {
1913

1914
            auto linked = freecad_dynamic_cast<ViewProviderDocumentObject>(getLinkedView(true,ext));
1915
            if(linked && linked->getDocument()==getDocument())
1916
                linked->hide();
1917

1918
            const auto &elements = ext->_getElementListValue();
1919
            // elements is about to be collapsed, preserve the materials
1920
            if(!elements.empty()) {
1921
                std::vector<App::Material> materials;
1922
                boost::dynamic_bitset<> overrideMaterials;
1923
                overrideMaterials.resize(elements.size(),false);
1924
                bool overrideMaterial = false;
1925
                bool hasMaterial = false;
1926
                materials.reserve(elements.size());
1927
                for(size_t i=0;i<elements.size();++i) {
1928
                    auto element = freecad_dynamic_cast<App::LinkElement>(elements[i]);
1929
                    if(!element) continue;
1930
                    auto vp = freecad_dynamic_cast<ViewProviderLink>(
1931
                            Application::Instance->getViewProvider(element));
1932
                    if(!vp) continue;
1933
                    overrideMaterial = overrideMaterial || vp->OverrideMaterial.getValue();
1934
                    hasMaterial = overrideMaterial || hasMaterial
1935
                        || vp->ShapeMaterial.getValue()!=ShapeMaterial.getValue();
1936
                    materials.push_back(vp->ShapeMaterial.getValue());
1937
                    overrideMaterials[i] = vp->OverrideMaterial.getValue();
1938
                }
1939
                if(!overrideMaterial)
1940
                    overrideMaterials.clear();
1941
                OverrideMaterialList.setStatus(App::Property::User3,true);
1942
                OverrideMaterialList.setValue(overrideMaterials);
1943
                OverrideMaterialList.setStatus(App::Property::User3,false);
1944
                if(!hasMaterial)
1945
                    materials.clear();
1946
                MaterialList.setStatus(App::Property::User3,true);
1947
                MaterialList.setValue(materials);
1948
                MaterialList.setStatus(App::Property::User3,false);
1949

1950
                linkView->setSize(ext->_getElementCountValue());
1951
                updateDataPrivate(ext,ext->getVisibilityListProperty());
1952
                applyMaterial();
1953
                applyColors();
1954
            }
1955
        }
1956
    }else if(prop==ext->getScaleListProperty() || prop==ext->getPlacementListProperty()) {
1957
        if(!prop->testStatus(App::Property::User3) &&
1958
            linkView->getSize() &&
1959
            !ext->_getShowElementValue())
1960
        {
1961
            auto propPlacements = ext->getPlacementListProperty();
1962
            auto propScales = ext->getScaleListProperty();
1963
            if(propPlacements && linkView->getSize()) {
1964
                const auto &touched =
1965
                    prop==propScales?propScales->getTouchList():propPlacements->getTouchList();
1966
                if(touched.empty()) {
1967
                    for(int i=0;i<linkView->getSize();++i) {
1968
                        Base::Matrix4D mat;
1969
                        if(propPlacements && propPlacements->getSize()>i)
1970
                            mat = (*propPlacements)[i].toMatrix();
1971
                        if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
1972
                            Base::Matrix4D s;
1973
                            s.scale((*propScales)[i]);
1974
                            mat *= s;
1975
                        }
1976
                        linkView->setTransform(i,mat);
1977
                    }
1978
                }else{
1979
                    for(int i : touched) {
1980
                        if(i<0 || i>=linkView->getSize())
1981
                            continue;
1982
                        Base::Matrix4D mat;
1983
                        if(propPlacements && propPlacements->getSize()>i)
1984
                            mat = (*propPlacements)[i].toMatrix();
1985
                        if(propScales && propScales->getSize()>i && canScale((*propScales)[i])) {
1986
                            Base::Matrix4D s;
1987
                            s.scale((*propScales)[i]);
1988
                            mat *= s;
1989
                        }
1990
                        linkView->setTransform(i,mat);
1991
                    }
1992
                }
1993
            }
1994
        }
1995
    }else if(prop == ext->getVisibilityListProperty()) {
1996
        const auto &vis = ext->getVisibilityListValue();
1997
        for(size_t i=0;i<(size_t)linkView->getSize();++i) {
1998
            if(vis.size()>i)
1999
                linkView->setElementVisible(i,vis[i]);
2000
            else
2001
                linkView->setElementVisible(i,true);
2002
        }
2003
    }else if(prop == ext->_getElementListProperty()) {
2004
        if(ext->_getShowElementValue())
2005
            updateElementList(ext);
2006
    }
2007
}
2008

2009
void ViewProviderLink::updateElementList(App::LinkBaseExtension *ext) {
2010
    const auto &elements = ext->_getElementListValue();
2011
    if(OverrideMaterialList.getSize() || MaterialList.getSize()) {
2012
        int i=-1;
2013
        for(auto obj : elements) {
2014
            ++i;
2015
            auto vp = freecad_dynamic_cast<ViewProviderLink>(
2016
                    Application::Instance->getViewProvider(obj));
2017
            if(!vp) continue;
2018
            if(OverrideMaterialList.getSize()>i)
2019
                vp->OverrideMaterial.setValue(OverrideMaterialList[i]);
2020
            if(MaterialList.getSize()>i)
2021
                vp->ShapeMaterial.setValue(MaterialList[i]);
2022
        }
2023
        OverrideMaterialList.setSize(0);
2024
        MaterialList.setSize(0);
2025
    }
2026
    linkView->setChildren(elements, ext->getVisibilityListValue());
2027
    applyColors();
2028
}
2029

2030
void ViewProviderLink::checkIcon(const App::LinkBaseExtension *ext) {
2031
    if(!ext) {
2032
        ext = getLinkExtension();
2033
        if(!ext)
2034
            return;
2035
    }
2036
    const char *icon;
2037
    auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
2038
    if(element)
2039
        icon = _LinkElementIcon;
2040
    else if(!ext->getLinkedObjectProperty() && ext->getElementListProperty())
2041
        icon = _LinkGroupIcon;
2042
    // else if(ext->_getElementCountValue())
2043
    //     icon = _LinkArrayIcon;
2044
    else
2045
        icon = _LinkIcon;
2046
    qint64 cacheKey = 0;
2047
    if(getObject()->getLinkedObject(false)!=getObject())
2048
        cacheKey = getOverlayPixmap().cacheKey();
2049
    if(icon!=sPixmap || cacheKey!=overlayCacheKey) {
2050
        sPixmap = icon;
2051
        signalChangeIcon();
2052
    }
2053
}
2054

2055
void ViewProviderLink::applyMaterial() {
2056
    if(OverrideMaterial.getValue())
2057
        linkView->setMaterial(-1,&ShapeMaterial.getValue());
2058
    else {
2059
        for(int i=0;i<linkView->getSize();++i) {
2060
            if(MaterialList.getSize()>i &&
2061
               OverrideMaterialList.getSize()>i && OverrideMaterialList[i])
2062
                linkView->setMaterial(i,&MaterialList[i]);
2063
            else
2064
                linkView->setMaterial(i,nullptr);
2065
        }
2066
        linkView->setMaterial(-1,nullptr);
2067
    }
2068
}
2069

2070
void ViewProviderLink::finishRestoring() {
2071
    FC_TRACE("finish restoring");
2072
    auto ext = getLinkExtension();
2073
    if(!ext)
2074
        return;
2075
    linkView->setDrawStyle(DrawStyle.getValue(),LineWidth.getValue(),PointSize.getValue());
2076
    updateDataPrivate(ext,ext->getLinkedObjectProperty());
2077
    if(ext->getLinkPlacementProperty())
2078
        updateDataPrivate(ext,ext->getLinkPlacementProperty());
2079
    else
2080
        updateDataPrivate(ext,ext->getPlacementProperty());
2081
    updateDataPrivate(ext,ext->_getElementCountProperty());
2082
    if(ext->getPlacementListProperty())
2083
        updateDataPrivate(ext,ext->getPlacementListProperty());
2084
    else
2085
        updateDataPrivate(ext,ext->getScaleListProperty());
2086
    updateDataPrivate(ext,ext->_getElementListProperty());
2087
    applyMaterial();
2088
    applyColors();
2089

2090
    // TODO: notify the tree. This is ugly, any other way?
2091
    getDocument()->signalChangedObject(*this,ext->_LinkTouched);
2092

2093
    if(childVp)
2094
        childVp->finishRestoring();
2095
}
2096

2097
bool ViewProviderLink::hasElements(const App::LinkBaseExtension *ext) const {
2098
    if(!ext) {
2099
        ext = getLinkExtension();
2100
        if(!ext)
2101
            return false;
2102
    }
2103
    const auto &elements = ext->getElementListValue();
2104
    return !elements.empty() && (int)elements.size()==ext->_getElementCountValue();
2105
}
2106

2107
bool ViewProviderLink::isGroup(const App::LinkBaseExtension *ext, bool plainGroup) const {
2108
    if(!ext) {
2109
        ext = getLinkExtension();
2110
        if(!ext)
2111
            return false;
2112
    }
2113
    return (plainGroup && ext->linkedPlainGroup())
2114
        || (ext->getElementListProperty() && !ext->getLinkedObjectProperty());
2115
}
2116

2117
ViewProvider *ViewProviderLink::getLinkedView(
2118
        bool real,const App::LinkBaseExtension *ext) const
2119
{
2120
    if(!ext)
2121
        ext = getLinkExtension();
2122
    auto obj = ext&&real?ext->getTrueLinkedObject(true):
2123
        getObject()->getLinkedObject(true);
2124
    if(obj && obj!=getObject())
2125
        return Application::Instance->getViewProvider(obj);
2126
    return nullptr;
2127
}
2128

2129
std::vector<App::DocumentObject*> ViewProviderLink::claimChildren() const {
2130
    auto ext = getLinkExtension();
2131
    std::vector<App::DocumentObject*> ret;
2132

2133
    if(ext && !ext->_getShowElementValue() && ext->_getElementCountValue()) {
2134
        // in array mode without element objects, we'd better not show the
2135
        // linked object's children to avoid inconsistent behavior on selection.
2136
        // We claim the linked object instead
2137
        if(ext) {
2138
            auto obj = ext->getLinkedObjectValue();
2139
            if(obj) ret.push_back(obj);
2140
        }
2141
    } else if(hasElements(ext) || isGroup(ext)) {
2142
        ret = ext->getElementListValue();
2143
        if (ext->_getElementCountValue()
2144
                && ext->getLinkClaimChildValue()
2145
                && ext->getLinkedObjectValue())
2146
            ret.insert(ret.begin(), ext->getLinkedObjectValue());
2147
    } else if(!hasSubName) {
2148
        auto linked = getLinkedView(true);
2149
        if(linked) {
2150
            ret = linked->claimChildren();
2151
            if (ext->getLinkClaimChildValue() && ext->getLinkedObjectValue())
2152
                ret.insert(ret.begin(), ext->getLinkedObjectValue());
2153
        }
2154
    }
2155
    if (ext && ext->getLinkCopyOnChangeGroupValue())
2156
        ret.insert(ret.begin(), ext->getLinkCopyOnChangeGroupValue());
2157
    return ret;
2158
}
2159

2160
bool ViewProviderLink::canDragObject(App::DocumentObject* obj) const {
2161
    auto ext = getLinkExtension();
2162
    if(isGroup(ext))
2163
        return true;
2164
    if(hasElements(ext))
2165
        return false;
2166
    auto linked = getLinkedView(false,ext);
2167
    if(linked)
2168
        return linked->canDragObject(obj);
2169
    return false;
2170
}
2171

2172
bool ViewProviderLink::canDragObjects() const {
2173
    auto ext = getLinkExtension();
2174
    if(isGroup(ext))
2175
        return true;
2176
    if(hasElements(ext))
2177
        return false;
2178
    auto linked = getLinkedView(false,ext);
2179
    if(linked)
2180
        return linked->canDragObjects();
2181
    return false;
2182
}
2183

2184
void ViewProviderLink::dragObject(App::DocumentObject* obj) {
2185
    auto ext = getLinkExtension();
2186
    if(isGroup(ext)) {
2187
        const auto &objs = ext->getElementListValue();
2188
        for(size_t i=0;i<objs.size();++i) {
2189
            if(obj==objs[i]) {
2190
                ext->setLink(i,nullptr);
2191
                break;
2192
            }
2193
        }
2194
        return;
2195
    }
2196
    if(hasElements(ext))
2197
        return;
2198
    auto linked = getLinkedView(false);
2199
    if(linked)
2200
        linked->dragObject(obj);
2201
}
2202

2203
bool ViewProviderLink::canDropObjects() const {
2204
    auto ext = getLinkExtension();
2205
    if(isGroup(ext))
2206
        return true;
2207
    if(hasElements(ext))
2208
        return false;
2209
    if(hasSubElement)
2210
        return true;
2211
    else if(hasSubName)
2212
        return false;
2213
    auto linked = getLinkedView(false,ext);
2214
    if(linked)
2215
        return linked->canDropObjects();
2216
    return true;
2217
}
2218

2219
bool ViewProviderLink::canDropObjectEx(App::DocumentObject *obj,
2220
        App::DocumentObject *owner, const char *subname,
2221
        const std::vector<std::string> &subElements) const
2222
{
2223
    if(pcObject == obj || pcObject == owner)
2224
        return false;
2225
    auto ext = getLinkExtension();
2226
    if(isGroup(ext))
2227
        return true;
2228
    if(!ext || !ext->getLinkedObjectProperty() || hasElements(ext))
2229
        return false;
2230
    if(!hasSubName && linkView->isLinked()) {
2231
        auto linked = getLinkedView(false,ext);
2232
        if(linked) {
2233
            auto linkedVdp = freecad_dynamic_cast<ViewProviderDocumentObject>(linked);
2234
            if(linkedVdp) {
2235
                if(linkedVdp->getObject()==obj || linkedVdp->getObject()==owner)
2236
                    return false;
2237
            }
2238
            return linked->canDropObjectEx(obj,owner,subname,subElements);
2239
        }
2240
    }
2241
    if(obj->getDocument() != getObject()->getDocument() &&
2242
       !freecad_dynamic_cast<App::PropertyXLink>(ext->getLinkedObjectProperty()))
2243
        return false;
2244

2245
    return true;
2246
}
2247

2248
std::string ViewProviderLink::dropObjectEx(App::DocumentObject* obj,
2249
    App::DocumentObject *owner, const char *subname,
2250
    const std::vector<std::string> &subElements)
2251
{
2252
    auto ext = getLinkExtension();
2253
    if (!ext)
2254
        return std::string();
2255

2256
    if(isGroup(ext)) {
2257
        size_t size = ext->getElementListValue().size();
2258
        ext->setLink(size,obj);
2259
        return std::to_string(size)+".";
2260
    }
2261

2262
    if(!ext->getLinkedObjectProperty() || hasElements(ext))
2263
        return std::string();
2264

2265
    if(!hasSubName) {
2266
        auto linked = getLinkedView(false,ext);
2267
        if(linked)
2268
            return linked->dropObjectEx(obj,owner,subname,subElements);
2269
    }
2270
    if(owner) {
2271
        if(!ext->getSubElements().empty())
2272
            ext->setLink(-1,owner,subname,subElements);
2273
        else
2274
            ext->setLink(-1,owner,subname);
2275
    } else if(!ext->getSubElements().empty())
2276
        ext->setLink(-1,obj,nullptr,subElements);
2277
    else
2278
        ext->setLink(-1,obj,nullptr);
2279
    return std::string();
2280
}
2281

2282
bool ViewProviderLink::canDragAndDropObject(App::DocumentObject* obj) const {
2283
    auto ext = getLinkExtension();
2284
    if(!ext)
2285
        return true;
2286
    if(isGroup(ext)) {
2287
        return ext->getLinkModeValue()<App::LinkBaseExtension::LinkModeAutoLink &&
2288
               obj->getDocument()==getObject()->getDocument();
2289
    }
2290
    if(!ext->getLinkedObjectProperty() || hasElements(ext))
2291
        return false;
2292
    if(!hasSubName) {
2293
        auto linked = getLinkedView(false,ext);
2294
        if(linked)
2295
            return linked->canDragAndDropObject(obj);
2296
    }
2297
    return false;
2298
}
2299

2300
bool ViewProviderLink::getElementPicked(const SoPickedPoint *pp, std::string &subname) const {
2301
    if(!isSelectable()) {
2302
        return false;
2303
    }
2304
    auto ext = getLinkExtension();
2305
    if (!ext) {
2306
        return false;
2307
    }
2308
    if(childVpLink && childVp) {
2309
        auto path = pp->getPath();
2310
        int idx = path->findNode(childVpLink->getSnapshot(LinkView::SnapshotTransform));
2311
        if(idx>=0) {
2312
            return childVp->getElementPicked(pp, subname);
2313
        }
2314
    }
2315
    bool ret = linkView->linkGetElementPicked(pp,subname);
2316
    if(!ret) {
2317
        return ret;
2318
    }
2319
    if(isGroup(ext,true)) {
2320
        const char *sub = nullptr;
2321
        int idx = App::LinkBaseExtension::getArrayIndex(subname.c_str(),&sub);
2322
        if(idx>=0 ) {
2323
            --sub;
2324
            assert(*sub == '.');
2325
            const auto &elements = ext->_getElementListValue();
2326
            subname.replace(0,sub-subname.c_str(),elements[idx]->getNameInDocument());
2327
        }
2328
    }
2329
    return ret;
2330
}
2331

2332
bool ViewProviderLink::getDetailPath(
2333
        const char *subname, SoFullPath *pPath, bool append, SoDetail *&det) const
2334
{
2335
    auto ext = getLinkExtension();
2336
    if(!ext)
2337
        return false;
2338

2339
    auto len = pPath->getLength();
2340
    if(append) {
2341
        appendPath(pPath,pcRoot);
2342
        appendPath(pPath,pcModeSwitch);
2343
    }
2344
    if(childVpLink && getDefaultMode()==1) {
2345
        if(childVpLink->getDetail(false,LinkView::SnapshotTransform,subname,det,pPath))
2346
            return true;
2347
        pPath->truncate(len);
2348
        return false;
2349
    }
2350
    std::string _subname;
2351
    if(subname && subname[0]) {
2352
        if (auto linked = ext->getLinkedObjectValue()) {
2353
            if (const char *dot = strchr(subname,'.')) {
2354
                if(subname[0]=='$') {
2355
                    CharRange sub(subname+1, dot);
2356
                    if (!boost::equals(sub, linked->Label.getValue()))
2357
                        dot = nullptr;
2358
                } else {
2359
                    CharRange sub(subname, dot);
2360
                    if (!boost::equals(sub, linked->getNameInDocument()))
2361
                        dot = nullptr;
2362
                }
2363
                if (dot && linked->getSubObject(dot+1))
2364
                    subname = dot+1;
2365
            }
2366
        }
2367

2368
        if (isGroup(ext,true) || hasElements(ext) || ext->getElementCountValue()) {
2369
            int index = ext->getElementIndex(subname,&subname);
2370
            if(index>=0) {
2371
                _subname = std::to_string(index)+'.'+subname;
2372
                subname = _subname.c_str();
2373
            }
2374
        }
2375
    }
2376
    if(linkView->linkGetDetailPath(subname,pPath,det))
2377
        return true;
2378
    pPath->truncate(len);
2379
    return false;
2380
}
2381

2382
bool ViewProviderLink::onDelete(const std::vector<std::string> &) {
2383
    auto element = freecad_dynamic_cast<App::LinkElement>(getObject());
2384
    if (element && !element->canDelete())
2385
        return false;
2386
    auto ext = getLinkExtension();
2387
    if (ext->isLinkMutated()) {
2388
        auto linked = ext->getLinkedObjectValue();
2389
        auto doc = ext->getContainer()->getDocument();
2390
        if (linked->getDocument() == doc) {
2391
            std::deque<std::string> objs;
2392
            for (auto obj : ext->getOnChangeCopyObjects(nullptr, linked)) {
2393
                if (obj->getDocument() == doc) {
2394
                    // getOnChangeCopyObjects() returns object in depending
2395
                    // order. So we delete it in reverse to avoid error
2396
                    // reported by some parent object failing to find child
2397
                    objs.emplace_front(obj->getNameInDocument());
2398
                }
2399
            }
2400
            for (const auto &name : objs)
2401
                doc->removeObject(name.c_str());
2402
        }
2403
    }
2404
    return true;
2405
}
2406

2407
bool ViewProviderLink::canDelete(App::DocumentObject *obj) const {
2408
    auto ext = getLinkExtension();
2409
    if(isGroup(ext) || hasElements(ext) || hasSubElement)
2410
        return true;
2411
    auto linked = getLinkedView(false,ext);
2412
    if(linked)
2413
        return linked->canDelete(obj);
2414
    return false;
2415
}
2416

2417
bool ViewProviderLink::linkEdit(const App::LinkBaseExtension *ext) const {
2418
    if(!ext)
2419
        ext = getLinkExtension();
2420
    if(!ext ||
2421
       (!ext->_getShowElementValue() && ext->_getElementCountValue()) ||
2422
       hasElements(ext) ||
2423
       isGroup(ext) ||
2424
       hasSubName)
2425
    {
2426
        return false;
2427
    }
2428
    return linkView->isLinked();
2429
}
2430

2431
bool ViewProviderLink::doubleClicked() {
2432
    if(linkEdit())
2433
        return linkView->getLinkedView()->doubleClicked();
2434
    return getDocument()->setEdit(this,ViewProvider::Transform);
2435
}
2436

2437
void ViewProviderLink::setupContextMenu(QMenu* menu, QObject* receiver, const char* member)
2438
{
2439
    auto ext = getLinkExtension();
2440
    if (!ext)
2441
        return;
2442

2443
    _setupContextMenu(ext, menu, receiver, member);
2444
    Gui::ActionFunction* func = nullptr;
2445

2446
    if (ext->isLinkedToConfigurableObject()) {
2447
        auto src = ext->getLinkCopyOnChangeSourceValue();
2448
        if (!src) src = ext->getLinkedObjectValue();
2449
        if (src && !ext->getOnChangeCopyObjects(nullptr, src).empty()) {
2450
            QAction *act = menu->addAction(
2451
                    QObject::tr("Setup configurable object"));
2452
            act->setToolTip(QObject::tr(
2453
                        "Select which object to copy or exclude when configuration changes. "
2454
                        "All external linked objects are excluded by default."));
2455
            act->setData(-1);
2456
            if (!func) func = new Gui::ActionFunction(menu);
2457
            func->trigger(act, [ext](){
2458
                try {
2459
                    std::vector<App::DocumentObject*> excludes;
2460
                    auto src = ext->getLinkCopyOnChangeSourceValue();
2461
                    if (!src)
2462
                        src = ext->getLinkedObjectValue();
2463
                    auto objs = ext->getOnChangeCopyObjects(&excludes, src);
2464
                    if (objs.empty())
2465
                        return;
2466
                    DlgObjectSelection dlg({src}, excludes, getMainWindow());
2467
                    dlg.setMessage(QObject::tr(
2468
                                "Please select which objects to copy when the configuration is changed"));
2469
                    auto box = new QCheckBox(QObject::tr("Apply to all"), &dlg);
2470
                    box->setToolTip(QObject::tr("Apply the setting to all links. Or, uncheck this\n"
2471
                                                "option to apply only to this link."));
2472
                    box->setChecked(App::LinkParams::getCopyOnChangeApplyToAll());
2473
                    dlg.addCheckBox(box);
2474
                    if(dlg.exec()!=QDialog::Accepted)
2475
                        return;
2476

2477
                    bool applyAll = box->isChecked();
2478
                    App::LinkParams::setCopyOnChangeApplyToAll(applyAll);
2479

2480
                    App::Link::OnChangeCopyOptions options {App::Link::OnChangeCopyOptions::None};
2481
                    if (applyAll)
2482
                        options |= App::Link::OnChangeCopyOptions::ApplyAll;
2483

2484
                    App::AutoTransaction guard("Setup configurable object");
2485
                    auto sels = dlg.getSelections(DlgObjectSelection::SelectionOptions::InvertSort);
2486
                    for (const auto & exclude : excludes) {
2487
                        auto iter = std::lower_bound(sels.begin(), sels.end(), exclude);
2488
                        if (iter == sels.end() || *iter != exclude) {
2489
                            ext->setOnChangeCopyObject(exclude, options);
2490
                        } else
2491
                            sels.erase(iter);
2492
                    }
2493
                    options |= App::Link::OnChangeCopyOptions::Exclude;
2494
                    for (auto obj : sels)
2495
                        ext->setOnChangeCopyObject(obj, options);
2496
                    if (!applyAll)
2497
                        ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
2498
                    else {
2499
                        std::set<App::LinkBaseExtension*> exts;
2500
                        for (auto o : App::Document::getDependencyList(objs)) {
2501
                            if (auto ext = o->getExtensionByType<App::LinkBaseExtension>(true))
2502
                                exts.insert(ext);
2503
                        }
2504
                        for (auto ext : exts)
2505
                            ext->monitorOnChangeCopyObjects(ext->getOnChangeCopyObjects());
2506
                    }
2507
                    Command::updateActive();
2508
                } catch (Base::Exception &e) {
2509
                    e.ReportException();
2510
                }
2511
            });
2512
        }
2513

2514
        if (ext->getLinkCopyOnChangeValue() == 0) {
2515
            auto submenu = menu->addMenu(QObject::tr("Copy on change"));
2516
            auto act = submenu->addAction(QObject::tr("Enable"));
2517
            act->setToolTip(QObject::tr(
2518
                        "Enable auto copy of linked object when its configuration is changed"));
2519
            act->setData(-1);
2520
            if (!func) func = new Gui::ActionFunction(menu);
2521
            func->trigger(act, [ext](){
2522
                try {
2523
                    App::AutoTransaction guard("Enable Link copy on change");
2524
                    ext->getLinkCopyOnChangeProperty()->setValue(1);
2525
                    Command::updateActive();
2526
                } catch (Base::Exception &e) {
2527
                    e.ReportException();
2528
                }
2529
            });
2530
            act = submenu->addAction(QObject::tr("Tracking"));
2531
            act->setToolTip(QObject::tr(
2532
                        "Copy the linked object when its configuration is changed.\n"
2533
                        "Also auto redo the copy if the original linked object is changed.\n"));
2534
            act->setData(-1);
2535
            func->trigger(act, [ext](){
2536
                try {
2537
                    App::AutoTransaction guard("Enable Link tracking");
2538
                    ext->getLinkCopyOnChangeProperty()->setValue(3);
2539
                    Command::updateActive();
2540
                } catch (Base::Exception &e) {
2541
                    e.ReportException();
2542
                }
2543
            });
2544
        }
2545
    }
2546

2547
    if (ext->getLinkCopyOnChangeValue() != 2
2548
            && ext->getLinkCopyOnChangeValue() != 0) {
2549
        QAction *act = menu->addAction(
2550
                QObject::tr("Disable copy on change"));
2551
        act->setData(-1);
2552
        if (!func) func = new Gui::ActionFunction(menu);
2553
        func->trigger(act, [ext](){
2554
            try {
2555
                App::AutoTransaction guard("Disable copy on change");
2556
                ext->getLinkCopyOnChangeProperty()->setValue((long)0);
2557
                Command::updateActive();
2558
            } catch (Base::Exception &e) {
2559
                e.ReportException();
2560
            }
2561
        });
2562
    }
2563

2564
    if (ext->isLinkMutated()) {
2565
        QAction* act = menu->addAction(QObject::tr("Refresh configurable object"));
2566
        act->setToolTip(QObject::tr(
2567
                    "Synchronize the original configurable source object by\n"
2568
                    "creating a new deep copy. Note that any changes made to\n"
2569
                    "the current copy will be lost.\n"));
2570
        act->setData(-1);
2571
        if (!func) func = new Gui::ActionFunction(menu);
2572
        func->trigger(act, [ext](){
2573
            try {
2574
                App::AutoTransaction guard("Link refresh");
2575
                ext->syncCopyOnChange();
2576
                Command::updateActive();
2577
            } catch (Base::Exception &e) {
2578
                e.ReportException();
2579
            }
2580
        });
2581
    }
2582
}
2583

2584
void ViewProviderLink::_setupContextMenu(
2585
        App::LinkBaseExtension *ext, QMenu* menu, QObject* receiver, const char* member)
2586
{
2587
    if(linkEdit(ext)) {
2588
        if (auto linkvp = Base::freecad_dynamic_cast<ViewProviderLink>(linkView->getLinkedView()))
2589
            linkvp->_setupContextMenu(ext, menu, receiver, member);
2590
        else
2591
            linkView->getLinkedView()->setupContextMenu(menu,receiver,member);
2592
    }
2593

2594
    if(ext->getLinkedObjectProperty()
2595
            && ext->_getShowElementProperty()
2596
            && ext->_getElementCountValue() > 1)
2597
    {
2598
        auto action = menu->addAction(QObject::tr("Toggle array elements"), [ext] {
2599
            try {
2600
                App::AutoTransaction guard(QT_TRANSLATE_NOOP("Command", "Toggle array elements"));
2601
                ext->getShowElementProperty()->setValue(!ext->getShowElementValue());
2602
                Command::updateActive();
2603
            } catch (Base::Exception &e) {
2604
                e.ReportException();
2605
            }
2606
        });
2607
        action->setToolTip(QObject::tr(
2608
                    "Change whether show each link array element as individual objects"));
2609
    }
2610

2611
    if((ext->getPlacementProperty() && !ext->getPlacementProperty()->isReadOnly())
2612
            || (ext->getLinkPlacementProperty() && !ext->getLinkPlacementProperty()->isReadOnly()))
2613
    {
2614
        bool found = false;
2615
        const auto actions = menu->actions();
2616
        for(auto action : actions) {
2617
            if(action->data().toInt() == ViewProvider::Transform) {
2618
                found = true;
2619
                break;
2620
            }
2621
        }
2622
        if (!found) {
2623
            QIcon iconObject = mergeGreyableOverlayIcons(Gui::BitmapFactory().pixmap("Std_TransformManip.svg"));
2624
            QAction* act = menu->addAction(iconObject, QObject::tr("Transform"), receiver, member);
2625
            act->setToolTip(QObject::tr("Transform at the origin of the placement"));
2626
            act->setData(QVariant((int)ViewProvider::Transform));
2627
        }
2628
    }
2629

2630
    if(ext->getColoredElementsProperty()) {
2631
        bool found = false;
2632
        const auto actions = menu->actions();
2633
        for(auto action : actions) {
2634
            if(action->data().toInt() == ViewProvider::Color) {
2635
                action->setText(QObject::tr("Override colors..."));
2636
                found = true;
2637
                break;
2638
            }
2639
        }
2640
        if(!found) {
2641
            QAction* act = menu->addAction(QObject::tr("Override colors..."), receiver, member);
2642
            act->setData(QVariant((int)ViewProvider::Color));
2643
        }
2644
    }
2645

2646
    auto cmd = Application::Instance->commandManager().getCommandByName("Std_LinkSelectLinked");
2647
    menu->addAction(cmd->getAction()->action());
2648
}
2649

2650
bool ViewProviderLink::initDraggingPlacement() {
2651
    Base::PyGILStateLocker lock;
2652
    try {
2653
        auto* proxy = getPropertyByName("Proxy");
2654
        if (proxy && proxy->is<App::PropertyPythonObject>()) {
2655
            Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
2656
            const char *fname = "initDraggingPlacement";
2657
            if (feature.hasAttr(fname)) {
2658
                Py::Callable method(feature.getAttr(fname));
2659
                Py::Tuple arg;
2660
                Py::Object ret(method.apply(arg));
2661
                if(ret.isTuple()) {
2662
                    PyObject *pymat,*pypla,*pybbox;
2663
                    if(!PyArg_ParseTuple(ret.ptr(),"O!O!O!",&Base::MatrixPy::Type, &pymat,
2664
                                &Base::PlacementPy::Type, &pypla,
2665
                                &Base::BoundBoxPy::Type, &pybbox)) {
2666
                        FC_ERR("initDraggingPlacement() expects return of type tuple(matrix,placement,boundbox)");
2667
                        return false;
2668
                    }
2669
                    dragCtx = std::make_unique<DraggerContext>();
2670
                    dragCtx->initialPlacement = *static_cast<Base::PlacementPy*>(pypla)->getPlacementPtr();
2671
                    dragCtx->preTransform = *static_cast<Base::MatrixPy*>(pymat)->getMatrixPtr();
2672
                    dragCtx->bbox = *static_cast<Base::BoundBoxPy*>(pybbox)->getBoundBoxPtr();
2673
                    return true;
2674
                }else if(!ret.isTrue())
2675
                    return false;
2676
            }
2677
        }
2678
    } catch (Py::Exception&) {
2679
        Base::PyException e;
2680
        e.ReportException();
2681
        return false;
2682
    }
2683

2684
    auto ext = getLinkExtension();
2685
    if(!ext) {
2686
        FC_ERR("no link extension");
2687
        return false;
2688
    }
2689
    if(!ext->hasPlacement()) {
2690
        FC_ERR("no placement");
2691
        return false;
2692
    }
2693
    auto doc = Application::Instance->editDocument();
2694
    if(!doc) {
2695
        FC_ERR("no editing document");
2696
        return false;
2697
    }
2698

2699
    dragCtx = std::make_unique<DraggerContext>();
2700

2701
    dragCtx->preTransform = doc->getEditingTransform();
2702
    doc->setEditingTransform(dragCtx->preTransform);
2703

2704
    const auto &pla = ext->getPlacementProperty()?
2705
        ext->getPlacementValue():ext->getLinkPlacementValue();
2706

2707
    // Cancel out our own transformation from the editing transform, because
2708
    // the dragger is meant to change our transformation.
2709
    dragCtx->preTransform *= pla.inverse().toMatrix();
2710

2711
    dragCtx->bbox = getBoundingBox(nullptr,false);
2712
    // The returned bounding box is before our own transform, but we still need
2713
    // to scale it to get the correct center.
2714
    auto scale = ext->getScaleVector();
2715
    dragCtx->bbox.ScaleX(scale.x);
2716
    dragCtx->bbox.ScaleY(scale.y);
2717
    dragCtx->bbox.ScaleZ(scale.z);
2718

2719
    auto modifier = QApplication::queryKeyboardModifiers();
2720
    // Determine the dragger base position
2721
    // if CTRL key is down, force to use bound box center,
2722
    // if SHIFT key is down, force to use origine,
2723
    // if not a sub link, use origine,
2724
    // else (e.g. group, array, sub link), use bound box center
2725
    if(modifier != Qt::ShiftModifier
2726
            && ((ext->getLinkedObjectValue() && !linkView->hasSubs())
2727
                || modifier == Qt::ControlModifier))
2728
    {
2729
        App::PropertyPlacement *propPla = nullptr;
2730
        if(ext->getLinkTransformValue() && ext->getLinkedObjectValue()) {
2731
            propPla = Base::freecad_dynamic_cast<App::PropertyPlacement>(
2732
                    ext->getLinkedObjectValue()->getPropertyByName("Placement"));
2733
        }
2734
        if(propPla) {
2735
            dragCtx->initialPlacement = pla * propPla->getValue();
2736
            dragCtx->mat *= propPla->getValue().inverse().toMatrix();
2737
        } else
2738
            dragCtx->initialPlacement = pla;
2739

2740
    } else {
2741
        auto offset = dragCtx->bbox.GetCenter();
2742

2743
        // This determines the initial placement of the dragger. We place it at the
2744
        // center of our bounding box.
2745
        dragCtx->initialPlacement = pla * Base::Placement(offset, Base::Rotation());
2746

2747
        // dragCtx->mat is to transform the dragger placement to our own placement.
2748
        // Since the dragger is placed at the center, we set the transformation by
2749
        // moving the same amount in reverse direction.
2750
        dragCtx->mat.move(Vector3d() - offset);
2751
    }
2752

2753
    return true;
2754
}
2755

2756
ViewProvider *ViewProviderLink::startEditing(int mode) {
2757
    if(mode==ViewProvider::Color) {
2758
        auto ext = getLinkExtension();
2759
        if(!ext || !ext->getColoredElementsProperty()) {
2760
            if(linkEdit(ext))
2761
                return linkView->getLinkedView()->startEditing(mode);
2762
        }
2763
        return inherited::startEditing(mode);
2764
    }
2765

2766
    static thread_local bool _pendingTransform;
2767
    static thread_local Base::Matrix4D  _editingTransform;
2768

2769
    auto doc = Application::Instance->editDocument();
2770

2771
    if(mode==ViewProvider::Transform) {
2772
        if(_pendingTransform && doc)
2773
            doc->setEditingTransform(_editingTransform);
2774

2775
        if(!initDraggingPlacement())
2776
            return nullptr;
2777
        if(useCenterballDragger)
2778
            pcDragger = CoinPtr<SoCenterballDragger>(new SoCenterballDragger);
2779
        else
2780
            pcDragger = CoinPtr<SoFCCSysDragger>(new SoFCCSysDragger);
2781
        updateDraggingPlacement(dragCtx->initialPlacement,true);
2782
        pcDragger->addStartCallback(dragStartCallback, this);
2783
        pcDragger->addFinishCallback(dragFinishCallback, this);
2784
        pcDragger->addMotionCallback(dragMotionCallback, this);
2785
        return inherited::startEditing(mode);
2786
    }
2787

2788
    if(!linkEdit()) {
2789
        FC_ERR("unsupported edit mode " << mode);
2790
        return nullptr;
2791
    }
2792

2793
    // TODO: the 0x8000 mask here is for caller to disambiguate the intention
2794
    // here, whether they want to, say transform the link itself or the linked
2795
    // object. Use of a mask here will allow forwarding those editing modes that
2796
    // are supported by both the link and the linked object, such as transform
2797
    // and set color. We need to find a better place to declare this constant.
2798
    mode &= ~0x8000;
2799

2800
    if(!doc) {
2801
        FC_ERR("no editing document");
2802
        return nullptr;
2803
    }
2804

2805
    // We are forwarding the editing request to linked object. We need to
2806
    // adjust the editing transformation.
2807
    Base::Matrix4D mat;
2808
    auto linked = getObject()->getLinkedObject(true,&mat,false);
2809
    if(!linked || linked==getObject()) {
2810
        FC_ERR("no linked object");
2811
        return nullptr;
2812
    }
2813
    auto vpd = freecad_dynamic_cast<ViewProviderDocumentObject>(
2814
                Application::Instance->getViewProvider(linked));
2815
    if(!vpd) {
2816
        FC_ERR("no linked viewprovider");
2817
        return nullptr;
2818
    }
2819
    // Amend the editing transformation with the link transformation.
2820
    // But save it first in case the linked object reroute the editing request
2821
    // back to us.
2822
    _editingTransform = doc->getEditingTransform();
2823
    doc->setEditingTransform(doc->getEditingTransform()*mat);
2824
    Base::FlagToggler<> guard(_pendingTransform);
2825
    return vpd->startEditing(mode);
2826
}
2827

2828
bool ViewProviderLink::setEdit(int ModNum)
2829
{
2830
    if (ModNum == ViewProvider::Color) {
2831
        auto ext = getLinkExtension();
2832
        if(!ext || !ext->getColoredElementsProperty())
2833
            return false;
2834
        TaskView::TaskDialog *dlg = Control().activeDialog();
2835
        if (dlg) {
2836
            Control().showDialog(dlg);
2837
            return false;
2838
        }
2839
        Selection().clearSelection();
2840
        return true;
2841
    }
2842
    return inherited::setEdit(ModNum);
2843
}
2844

2845
void ViewProviderLink::setEditViewer(Gui::View3DInventorViewer* viewer, int ModNum)
2846
{
2847
    if (ModNum == ViewProvider::Color) {
2848
        Gui::Control().showDialog(new TaskElementColors(this));
2849
        return;
2850
    }
2851

2852
    if (pcDragger && viewer)
2853
    {
2854
        auto rootPickStyle = new SoPickStyle();
2855
        rootPickStyle->style = SoPickStyle::UNPICKABLE;
2856
        static_cast<SoFCUnifiedSelection*>(
2857
                viewer->getSceneGraph())->insertChild(rootPickStyle, 0);
2858

2859
        if(useCenterballDragger) {
2860
            auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
2861
            auto group = new SoAnnotation;
2862
            auto pickStyle = new SoPickStyle;
2863
            pickStyle->setOverride(true);
2864
            group->addChild(pickStyle);
2865
            group->addChild(pcDragger);
2866

2867
            // Because the dragger is not grouped with the actual geometry,
2868
            // we use an invisible cube sized by the bounding box obtained from
2869
            // initDraggingPlacement() to scale the centerball dragger properly
2870

2871
            auto * ss = static_cast<SoSurroundScale*>(dragger->getPart("surroundScale", TRUE));
2872
            ss->numNodesUpToContainer = 3;
2873
            ss->numNodesUpToReset = 2;
2874

2875
            auto *geoGroup = new SoGroup;
2876
            group->addChild(geoGroup);
2877
            auto *style = new SoDrawStyle;
2878
            style->style.setValue(SoDrawStyle::INVISIBLE);
2879
            style->setOverride(TRUE);
2880
            geoGroup->addChild(style);
2881
            auto *cube = new SoCube;
2882
            geoGroup->addChild(cube);
2883
            auto length = std::max(std::max(dragCtx->bbox.LengthX(),
2884
                        dragCtx->bbox.LengthY()), dragCtx->bbox.LengthZ());
2885
            cube->width = length;
2886
            cube->height = length;
2887
            cube->depth = length;
2888

2889
            viewer->setupEditingRoot(group,&dragCtx->preTransform);
2890
        } else {
2891
            auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
2892
            dragger->draggerSize.setValue(ViewParams::instance()->getDraggerScale());
2893
            dragger->setUpAutoScale(viewer->getSoRenderManager()->getCamera());
2894
            viewer->setupEditingRoot(pcDragger,&dragCtx->preTransform);
2895

2896
            auto task = new TaskCSysDragger(this, dragger);
2897
            Gui::Control().showDialog(task);
2898
        }
2899
    }
2900
}
2901

2902
void ViewProviderLink::unsetEditViewer(Gui::View3DInventorViewer* viewer)
2903
{
2904
    SoNode *child = static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->getChild(0);
2905
    if (child && child->isOfType(SoPickStyle::getClassTypeId()))
2906
        static_cast<SoFCUnifiedSelection*>(viewer->getSceneGraph())->removeChild(child);
2907
    pcDragger.reset();
2908
    dragCtx.reset();
2909
    Gui::Control().closeDialog();
2910
}
2911

2912
Base::Placement ViewProviderLink::currentDraggingPlacement() const
2913
{
2914
    // if there isn't an active dragger return a default placement
2915
    if (!pcDragger)
2916
        return Base::Placement();
2917

2918
    SbVec3f v;
2919
    SbRotation r;
2920
    if (useCenterballDragger) {
2921
        auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
2922
        v = dragger->center.getValue();
2923
        r = dragger->rotation.getValue();
2924
    }
2925
    else {
2926
        auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
2927
        v = dragger->translation.getValue();
2928
        r = dragger->rotation.getValue();
2929
    }
2930

2931
    float q1,q2,q3,q4;
2932
    r.getValue(q1,q2,q3,q4);
2933
    return Base::Placement(Base::Vector3d(v[0],v[1],v[2]),Base::Rotation(q1,q2,q3,q4));
2934
}
2935

2936
void ViewProviderLink::enableCenterballDragger(bool enable) {
2937
    if(enable == useCenterballDragger)
2938
        return;
2939
    if(pcDragger)
2940
        LINK_THROW(Base::RuntimeError,"Cannot change dragger during dragging");
2941
    useCenterballDragger = enable;
2942
}
2943

2944
void ViewProviderLink::updateDraggingPlacement(const Base::Placement &pla, bool force) {
2945
    if(pcDragger && (force || currentDraggingPlacement()!=pla)) {
2946
        const auto &pos = pla.getPosition();
2947
        const auto &rot = pla.getRotation();
2948
        FC_LOG("updating dragger placement (" << pos.x << ", " << pos.y << ", " << pos.z << ')');
2949
        if(useCenterballDragger) {
2950
            auto dragger = static_cast<SoCenterballDragger*>(pcDragger.get());
2951
            SbBool wasenabled = dragger->enableValueChangedCallbacks(FALSE);
2952
            SbMatrix matrix;
2953
            matrix = convert(pla.toMatrix());
2954
            dragger->center.setValue(SbVec3f(0,0,0));
2955
            dragger->setMotionMatrix(matrix);
2956
            if (wasenabled) {
2957
                dragger->enableValueChangedCallbacks(TRUE);
2958
                dragger->valueChanged();
2959
            }
2960
        }else{
2961
            auto dragger = static_cast<SoFCCSysDragger*>(pcDragger.get());
2962
            dragger->translation.setValue(SbVec3f(pos.x,pos.y,pos.z));
2963
            dragger->rotation.setValue(rot[0],rot[1],rot[2],rot[3]);
2964
        }
2965
    }
2966
}
2967

2968
bool ViewProviderLink::callDraggerProxy(const char *fname, bool update) {
2969
    if(!pcDragger)
2970
        return false;
2971
    Base::PyGILStateLocker lock;
2972
    try {
2973
        auto* proxy = getPropertyByName("Proxy");
2974
        if (proxy && proxy->is<App::PropertyPythonObject>()) {
2975
            Py::Object feature = static_cast<App::PropertyPythonObject*>(proxy)->getValue();
2976
            if (feature.hasAttr(fname)) {
2977
                Py::Callable method(feature.getAttr(fname));
2978
                Py::Tuple args;
2979
                if(method.apply(args).isTrue())
2980
                    return true;
2981
            }
2982
        }
2983
    } catch (Py::Exception&) {
2984
        Base::PyException e;
2985
        e.ReportException();
2986
        return true;
2987
    }
2988

2989
    if(update) {
2990
        auto ext = getLinkExtension();
2991
        if(ext) {
2992
            const auto &pla = currentDraggingPlacement();
2993
            auto prop = ext->getLinkPlacementProperty();
2994
            if(!prop)
2995
                prop = ext->getPlacementProperty();
2996
            if(prop) {
2997
                auto plaNew = pla * Base::Placement(dragCtx->mat);
2998
                if(prop->getValue()!=plaNew)
2999
                    prop->setValue(plaNew);
3000
            }
3001
            updateDraggingPlacement(pla);
3002
        }
3003
    }
3004
    return false;
3005
}
3006

3007
void ViewProviderLink::dragStartCallback(void *data, SoDragger *) {
3008
    auto me = static_cast<ViewProviderLink*>(data);
3009
    me->dragCtx->initialPlacement = me->currentDraggingPlacement();
3010
    if(!me->callDraggerProxy("onDragStart",false)) {
3011
        me->dragCtx->cmdPending = true;
3012
        me->getDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Link Transform"));
3013
    }else
3014
        me->dragCtx->cmdPending = false;
3015
}
3016

3017
void ViewProviderLink::dragFinishCallback(void *data, SoDragger *) {
3018
    auto me = static_cast<ViewProviderLink*>(data);
3019
    me->callDraggerProxy("onDragEnd",true);
3020
    if(me->dragCtx->cmdPending) {
3021
        if(me->currentDraggingPlacement() == me->dragCtx->initialPlacement)
3022
            me->getDocument()->abortCommand();
3023
        else
3024
            me->getDocument()->commitCommand();
3025
    }
3026
}
3027

3028
void ViewProviderLink::dragMotionCallback(void *data, SoDragger *) {
3029
    auto me = static_cast<ViewProviderLink*>(data);
3030
    me->callDraggerProxy("onDragMotion",true);
3031
}
3032

3033
void ViewProviderLink::updateLinks(ViewProvider *vp) {
3034
    try {
3035
        auto ext = vp->getExtensionByType<ViewProviderLinkObserver>(true);
3036
        if (ext && ext->linkInfo)
3037
            ext->linkInfo->update();
3038
    }
3039
    catch (const Base::TypeError &e) {
3040
        e.ReportException();
3041
    }
3042
    catch (const Base::ValueError &e) {
3043
        e.ReportException();
3044
    }
3045
}
3046

3047
PyObject *ViewProviderLink::getPyObject() {
3048
    if (!pyViewObject)
3049
        pyViewObject = new ViewProviderLinkPy(this);
3050
    pyViewObject->IncRef();
3051
    return pyViewObject;
3052
}
3053

3054
PyObject *ViewProviderLink::getPyLinkView() {
3055
    return linkView->getPyObject();
3056
}
3057

3058
std::map<std::string, App::Color> ViewProviderLink::getElementColors(const char *subname) const {
3059
    bool isPrefix = true;
3060
    if(!subname)
3061
        subname = "";
3062
    else {
3063
        auto len = strlen(subname);
3064
        isPrefix = !len || subname[len-1]=='.';
3065
    }
3066
    std::map<std::string, App::Color> colors;
3067
    auto ext = getLinkExtension();
3068
    if(!ext || ! ext->getColoredElementsProperty())
3069
        return colors;
3070
    const auto &subs = ext->getColoredElementsProperty()->getShadowSubs();
3071
    int size = OverrideColorList.getSize();
3072

3073
    std::string wildcard(subname);
3074
    if(wildcard == "Face" || wildcard == "Face*" || wildcard.empty()) {
3075
        if(wildcard.size()==4 || OverrideMaterial.getValue()) {
3076
            App::Color c = ShapeMaterial.getValue().diffuseColor;
3077
            c.a = ShapeMaterial.getValue().transparency;
3078
            colors["Face"] = c;
3079
            if(wildcard.size()==4)
3080
                return colors;
3081
        }
3082
        if(!wildcard.empty())
3083
            wildcard.resize(4);
3084
    }else if(wildcard == "Edge*")
3085
        wildcard.resize(4);
3086
    else if(wildcard == "Vertex*")
3087
        wildcard.resize(5);
3088
    else if(wildcard == ViewProvider::hiddenMarker()+"*")
3089
        wildcard.resize(ViewProvider::hiddenMarker().size());
3090
    else
3091
        wildcard.clear();
3092

3093
    int i=-1;
3094
    if(!wildcard.empty()) {
3095
        for(const auto &sub : subs) {
3096
            if(++i >= size)
3097
                break;
3098
            auto pos = sub.oldName.rfind('.');
3099
            if(pos == std::string::npos)
3100
                pos = 0;
3101
            else
3102
                ++pos;
3103
            const char *element = sub.oldName.c_str()+pos;
3104
            if(boost::starts_with(element,wildcard))
3105
                colors[sub.oldName] = OverrideColorList[i];
3106
            else if(!element[0] && wildcard=="Face")
3107
                colors[sub.oldName.substr(0,element-sub.oldName.c_str())+wildcard] = OverrideColorList[i];
3108
        }
3109

3110
        // In case of multi-level linking, we recursively call into each level,
3111
        // and merge the colors
3112
        auto vp = this;
3113
        while(true) {
3114
            if(wildcard!=ViewProvider::hiddenMarker() && vp->OverrideMaterial.getValue()) {
3115
                auto color = ShapeMaterial.getValue().diffuseColor;
3116
                color.a = ShapeMaterial.getValue().transparency;
3117
                colors.emplace(wildcard,color);
3118
            }
3119
            auto link = vp->getObject()->getLinkedObject(false);
3120
            if(!link || link==vp->getObject())
3121
                break;
3122
            auto next = freecad_dynamic_cast<ViewProviderLink>(
3123
                    Application::Instance->getViewProvider(link));
3124
            if(!next)
3125
                break;
3126
            for(const auto &v : next->getElementColors(subname))
3127
                colors.insert(v);
3128
            vp = next;
3129
        }
3130
        if(wildcard!=ViewProvider::hiddenMarker()) {
3131
            // Get collapsed array color override.
3132
            auto ext = vp->getLinkExtension();
3133
            if(ext->_getElementCountValue() && !ext->_getShowElementValue()) {
3134
                const auto &overrides = vp->OverrideMaterialList.getValues();
3135
                int i=-1;
3136
                for(const auto &mat : vp->MaterialList.getValues()) {
3137
                    if(++i>=(int)overrides.size())
3138
                        break;
3139
                    if(!overrides[i])
3140
                        continue;
3141
                    auto color = mat.diffuseColor;
3142
                    color.a = mat.transparency;
3143
                    colors.emplace(std::to_string(i)+"."+wildcard,color);
3144
                }
3145
            }
3146
        }
3147
        return colors;
3148
    }
3149

3150
    int element_count = ext->getElementCountValue();
3151

3152
    for(const auto &sub : subs) {
3153
        if(++i >= size)
3154
            break;
3155

3156
        int offset = 0;
3157

3158
        if(!sub.oldName.empty() && element_count && !std::isdigit(sub.oldName[0])) {
3159
            // For checking and expanding color override of array base
3160
            if(!subname[0]) {
3161
                std::ostringstream ss;
3162
                ss << "0." << sub.oldName;
3163
                if(getObject()->getSubObject(ss.str().c_str())) {
3164
                    for(int j=0;j<element_count;++j) {
3165
                        ss.str("");
3166
                        ss << j << '.' << sub.oldName;
3167
                        colors.emplace(ss.str(),OverrideColorList[i]);
3168
                    }
3169
                    continue;
3170
                }
3171
            } else if (std::isdigit(subname[0])) {
3172
                const char *dot = strchr(subname,'.');
3173
                if(dot)
3174
                    offset = dot-subname+1;
3175
            }
3176
        }
3177

3178
        if(isPrefix) {
3179
            if(!boost::starts_with(sub.newName,subname+offset)
3180
                    && !boost::starts_with(sub.oldName,subname+offset))
3181
                continue;
3182
        }else if(sub.newName!=subname+offset && sub.oldName!=subname+offset)
3183
            continue;
3184

3185
        if(offset)
3186
            colors.emplace(std::string(subname,offset)+sub.oldName, OverrideColorList[i]);
3187
        else
3188
            colors[sub.oldName] = OverrideColorList[i];
3189
    }
3190

3191
    if(!subname[0])
3192
        return colors;
3193

3194
    bool found = true;
3195
    if(colors.empty()) {
3196
        found = false;
3197
        colors.emplace(subname,App::Color());
3198
    }
3199
    std::map<std::string, App::Color> ret;
3200
    for(const auto &v : colors) {
3201
        const char *pos = nullptr;
3202
        auto sobj = getObject()->resolve(v.first.c_str(),nullptr,nullptr,&pos);
3203
        if(!sobj || !pos)
3204
            continue;
3205
        auto link = sobj->getLinkedObject(true);
3206
        if(!link || link==getObject())
3207
            continue;
3208
        auto vp = Application::Instance->getViewProvider(sobj->getLinkedObject(true));
3209
        if(!vp)
3210
            continue;
3211
        for(const auto &v2 : vp->getElementColors(!pos[0]?"Face":pos)) {
3212
            std::string name;
3213
            if(pos[0])
3214
                name = v.first.substr(0,pos-v.first.c_str())+v2.first;
3215
            else
3216
                name = v.first;
3217
            ret[name] = found?v.second:v2.second;
3218
        }
3219
    }
3220
    return ret;
3221
}
3222

3223
void ViewProviderLink::setElementColors(const std::map<std::string, App::Color> &colorMap) {
3224
    auto ext = getLinkExtension();
3225
    if(!ext || ! ext->getColoredElementsProperty())
3226
        return;
3227

3228
    // For checking and collapsing array element color
3229
    std::map<std::string,std::map<int,App::Color> > subMap;
3230
    int element_count = ext->getElementCountValue();
3231

3232
    std::vector<std::string> subs;
3233
    std::vector<App::Color> colors;
3234
    App::Color faceColor;
3235
    bool hasFaceColor = false;
3236
    for(const auto &v : colorMap) {
3237
        if(!hasFaceColor && v.first == "Face") {
3238
            hasFaceColor = true;
3239
            faceColor = v.second;
3240
            continue;
3241
        }
3242

3243
        if(element_count && !v.first.empty() && std::isdigit(v.first[0])) {
3244
            // In case of array, check if there are override of the same
3245
            // sub-element for every array element. And collapse those overrides
3246
            // into one without the index.
3247
            const char *dot = strchr(v.first.c_str(),'.');
3248
            if(dot) {
3249
                subMap[dot+1][std::atoi(v.first.c_str())] = v.second;
3250
                continue;
3251
            }
3252
        }
3253
        subs.push_back(v.first);
3254
        colors.push_back(v.second);
3255
    }
3256
    for(auto &v : subMap) {
3257
        if(element_count == (int)v.second.size()) {
3258
            App::Color firstColor = v.second.begin()->second;
3259
            subs.push_back(v.first);
3260
            colors.push_back(firstColor);
3261
            for(auto it=v.second.begin();it!=v.second.end();) {
3262
                if(it->second==firstColor)
3263
                    it = v.second.erase(it);
3264
                else
3265
                    ++it;
3266
            }
3267
        }
3268
        std::ostringstream ss;
3269
        for(const auto &colorInfo : v.second) {
3270
            ss.str("");
3271
            ss << colorInfo.first << '.' << v.first;
3272
            subs.push_back(ss.str());
3273
            colors.push_back(colorInfo.second);
3274
        }
3275
    }
3276

3277
    auto prop = ext->getColoredElementsProperty();
3278
    if(subs!=prop->getSubValues() || colors!=OverrideColorList.getValues()) {
3279
        prop->setStatus(App::Property::User3,true);
3280
        prop->setValue(getObject(),subs);
3281
        prop->setStatus(App::Property::User3,false);
3282
        OverrideColorList.setValues(colors);
3283
    }
3284
    if(hasFaceColor) {
3285
        auto mat = ShapeMaterial.getValue();
3286
        mat.diffuseColor = faceColor;
3287
        mat.transparency = faceColor.a;
3288
        ShapeMaterial.setStatus(App::Property::User3,true);
3289
        ShapeMaterial.setValue(mat);
3290
        ShapeMaterial.setStatus(App::Property::User3,false);
3291
    }
3292
    OverrideMaterial.setValue(hasFaceColor);
3293
}
3294

3295
void ViewProviderLink::applyColors() {
3296
    auto ext = getLinkExtension();
3297
    if(!ext || ! ext->getColoredElementsProperty())
3298
        return;
3299

3300
    SoSelectionElementAction action(SoSelectionElementAction::Color,true);
3301
    // reset color and visibility first
3302
    action.apply(linkView->getLinkRoot());
3303

3304
    std::map<std::string, std::map<std::string,App::Color> > colorMap;
3305
    std::set<std::string> hideList;
3306
    auto colors = getElementColors();
3307
    colors.erase("Face");
3308
    for(const auto &v : colors) {
3309
        const char *subname = v.first.c_str();
3310
        const char *element = nullptr;
3311
        auto sobj = getObject()->resolve(subname,nullptr,nullptr,&element);
3312
        if(!sobj || !element)
3313
            continue;
3314
        if(ViewProvider::hiddenMarker() == element)
3315
            hideList.emplace(subname,element-subname);
3316
        else
3317
            colorMap[std::string(subname,element-subname)][element] = v.second;
3318
    }
3319

3320
    SoTempPath path(10);
3321
    path.ref();
3322
    for(auto &v : colorMap) {
3323
        action.swapColors(v.second);
3324
        if(v.first.empty()) {
3325
            action.apply(linkView->getLinkRoot());
3326
            continue;
3327
        }
3328
        SoDetail *det=nullptr;
3329
        path.truncate(0);
3330
        if(getDetailPath(v.first.c_str(), &path, false, det))
3331
            action.apply(&path);
3332
        delete det;
3333
    }
3334

3335
    action.setType(SoSelectionElementAction::Hide);
3336
    for(const auto &sub : hideList) {
3337
        SoDetail *det=nullptr;
3338
        path.truncate(0);
3339
        if(!sub.empty() && getDetailPath(sub.c_str(), &path, false, det))
3340
            action.apply(&path);
3341
        delete det;
3342
    }
3343
    path.unrefNoDelete();
3344
}
3345

3346
void ViewProviderLink::setOverrideMode(const std::string &mode) {
3347
    auto ext = getLinkExtension();
3348
    if(!ext)
3349
        return;
3350
    auto obj = ext->getTrueLinkedObject(false);
3351
    if(obj && obj!=getObject()) {
3352
        auto vp = Application::Instance->getViewProvider(obj);
3353
        vp->setOverrideMode(mode);
3354
    }
3355
    if(childVp)
3356
        childVp->setOverrideMode(mode);
3357
}
3358

3359
void ViewProviderLink::onBeforeChange(const App::Property *prop) {
3360
    if(prop == &ChildViewProvider) {
3361
        if(childVp) {
3362
            childVp->beforeDelete();
3363
            pcModeSwitch->replaceChild(1,linkView->getLinkRoot());
3364
            childVpLink.reset();
3365
            childVp = nullptr;
3366
        }
3367
    }
3368
    inherited::onBeforeChange(prop);
3369
}
3370

3371
static bool isExcludedProperties(const char *name) {
3372
#define CHECK_EXCLUDE_PROP(_name) if(strcmp(name,#_name)==0) return true;
3373
    CHECK_EXCLUDE_PROP(Proxy);
3374
    return false;
3375
}
3376

3377
App::Property *ViewProviderLink::getPropertyByName(const char *name) const {
3378
    auto prop = inherited::getPropertyByName(name);
3379
    if(prop || isExcludedProperties(name))
3380
        return prop;
3381
    if(childVp) {
3382
        prop = childVp->getPropertyByName(name);
3383
        if(prop && !prop->testStatus(App::Property::Hidden))
3384
            return prop;
3385
        prop = nullptr;
3386
    }
3387
    if(pcObject && pcObject->canLinkProperties()) {
3388
        auto linked = getLinkedViewProvider(nullptr,true);
3389
        if(linked && linked!=this)
3390
            prop = linked->getPropertyByName(name);
3391
    }
3392
    return prop;
3393
}
3394

3395
void ViewProviderLink::getPropertyMap(std::map<std::string,App::Property*> &Map) const {
3396
    inherited::getPropertyMap(Map);
3397
    if(!childVp)
3398
        return;
3399
    std::map<std::string,App::Property*> childMap;
3400
    childVp->getPropertyMap(childMap);
3401
    for(const auto &v : childMap) {
3402
        auto ret = Map.insert(v);
3403
        if(!ret.second) {
3404
            auto myProp = ret.first->second;
3405
            if(myProp->testStatus(App::Property::Hidden))
3406
                ret.first->second = v.second;
3407
        }
3408
    }
3409
}
3410

3411
void ViewProviderLink::getPropertyList(std::vector<App::Property*> &List) const {
3412
    std::map<std::string,App::Property*> Map;
3413
    getPropertyMap(Map);
3414
    List.reserve(List.size()+Map.size());
3415
    for(const auto &v:Map)
3416
        List.push_back(v.second);
3417
}
3418

3419
ViewProviderDocumentObject *ViewProviderLink::getLinkedViewProvider(
3420
        std::string *subname, bool recursive) const
3421
{
3422
    auto self = const_cast<ViewProviderLink*>(this);
3423
    auto ext = getLinkExtension();
3424
    if(!ext)
3425
        return self;
3426
    App::DocumentObject *linked = nullptr;
3427
    if(!recursive) {
3428
        linked = ext->getLink();
3429
        const char *s = ext->getSubName();
3430
        if(subname && s)
3431
            *subname = s;
3432
    } else
3433
        linked = ext->getTrueLinkedObject(recursive);
3434
    if(!linked)
3435
        return self;
3436
    auto res = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(
3437
            Application::Instance->getViewProvider(linked));
3438
    if(res)
3439
        return res;
3440
    return self;
3441
}
3442

3443
void ViewProviderLink::setTransformation(const Base::Matrix4D &rcMatrix)
3444
{
3445
    inherited::setTransformation(rcMatrix);
3446
    auto ext = getLinkExtension();
3447
    if(ext) {
3448
        if (ext->getScaleVectorProperty())
3449
            updateDataPrivate(getLinkExtension(),ext->getScaleVectorProperty());
3450
        else
3451
            updateDataPrivate(getLinkExtension(),ext->getScaleProperty());
3452
    }
3453
}
3454

3455
void ViewProviderLink::setTransformation(const SbMatrix &rcMatrix)
3456
{
3457
    inherited::setTransformation(rcMatrix);
3458
    auto ext = getLinkExtension();
3459
    if(ext) {
3460
        if (ext->getScaleVectorProperty())
3461
            updateDataPrivate(getLinkExtension(),ext->getScaleVectorProperty());
3462
        else
3463
            updateDataPrivate(getLinkExtension(),ext->getScaleProperty());
3464
    }
3465
}
3466

3467
////////////////////////////////////////////////////////////////////////////////////////
3468

3469
namespace Gui {
3470
PROPERTY_SOURCE_TEMPLATE(Gui::ViewProviderLinkPython, Gui::ViewProviderLink)
3471
template class GuiExport ViewProviderFeaturePythonT<ViewProviderLink>;
3472
}
3473

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

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

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

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