FreeCAD

Форк
0
/
SoFCUnifiedSelection.cpp 
1826 строк · 63.7 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2005 Jürgen Riegel <juergen.riegel@web.de>              *
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 <Inventor/SoFullPath.h>
27
# include <Inventor/SoPickedPoint.h>
28
# include <Inventor/actions/SoCallbackAction.h>
29
# include <Inventor/actions/SoGetBoundingBoxAction.h>
30
# include <Inventor/actions/SoGetPrimitiveCountAction.h>
31
# include <Inventor/actions/SoGLRenderAction.h>
32
# include <Inventor/actions/SoHandleEventAction.h>
33
# include <Inventor/actions/SoWriteAction.h>
34
# include <Inventor/bundles/SoMaterialBundle.h>
35
# include <Inventor/details/SoFaceDetail.h>
36
# include <Inventor/details/SoLineDetail.h>
37
# include <Inventor/elements/SoCacheElement.h>
38
# include <Inventor/elements/SoCoordinateElement.h>
39
# include <Inventor/elements/SoDrawStyleElement.h>
40
# include <Inventor/elements/SoGLCacheContextElement.h>
41
# include <Inventor/elements/SoLazyElement.h>
42
# include <Inventor/elements/SoLineWidthElement.h>
43
# include <Inventor/elements/SoMaterialBindingElement.h>
44
# include <Inventor/elements/SoModelMatrixElement.h>
45
# include <Inventor/elements/SoOverrideElement.h>
46
# include <Inventor/elements/SoShapeStyleElement.h>
47
# include <Inventor/elements/SoSwitchElement.h>
48
# include <Inventor/elements/SoTextureEnabledElement.h>
49
# include <Inventor/events/SoLocation2Event.h>
50
# include <Inventor/events/SoMouseButtonEvent.h>
51
# include <Inventor/misc/SoChildList.h>
52
# include <Inventor/misc/SoState.h>
53
# include <Inventor/nodes/SoCoordinate3.h>
54
# include <Inventor/nodes/SoCube.h>
55
# include <Inventor/nodes/SoIndexedFaceSet.h>
56
# include <Inventor/nodes/SoIndexedLineSet.h>
57
# include <Inventor/nodes/SoMaterial.h>
58
# include <Inventor/nodes/SoMaterialBinding.h>
59
# include <Inventor/nodes/SoNormalBinding.h>
60
# include <Inventor/nodes/SoPointSet.h>
61
# include <Inventor/threads/SbStorage.h>
62
#endif
63

64
#ifdef FC_OS_MACOSX
65
# include <OpenGL/gl.h>
66
#else
67
# ifdef FC_OS_WIN32
68
#  include <windows.h>
69
# endif
70
# include <GL/gl.h>
71
#endif
72

73
#include <App/Document.h>
74
#include <App/ElementNamingUtils.h>
75
#include <Base/Tools.h>
76
#include <Base/UnitsApi.h>
77

78
#include "SoFCUnifiedSelection.h"
79
#include "Application.h"
80
#include "Document.h"
81
#include "MainWindow.h"
82
#include "SoFCInteractiveElement.h"
83
#include "SoFCSelectionAction.h"
84
#include "ViewParams.h"
85
#include "ViewProvider.h"
86
#include "ViewProviderDocumentObject.h"
87

88

89
FC_LOG_LEVEL_INIT("SoFCUnifiedSelection",false,true,true)
90

91
using namespace Gui;
92

93
namespace Gui {
94
void printPreselectionInfo(const char* documentName,
95
                           const char* objectName,
96
                           const char* subElementName,
97
                           float x, float y, float z,
98
                           double precision);
99
}
100

101
SoFullPath * Gui::SoFCUnifiedSelection::currenthighlight = nullptr;
102

103
// *************************************************************************
104

105
SO_NODE_SOURCE(SoFCUnifiedSelection)
106

107
/*!
108
  Constructor.
109
*/
110
SoFCUnifiedSelection::SoFCUnifiedSelection()
111
{
112
    SO_NODE_CONSTRUCTOR(SoFCUnifiedSelection);
113

114
    SO_NODE_ADD_FIELD(colorHighlight, (SbColor(1.0f, 0.6f, 0.0f)));
115
    SO_NODE_ADD_FIELD(colorSelection, (SbColor(0.1f, 0.8f, 0.1f)));
116
    SO_NODE_ADD_FIELD(highlightMode,  (AUTO));
117
    SO_NODE_ADD_FIELD(selectionMode,  (ON));
118
    SO_NODE_ADD_FIELD(selectionRole,  (true));
119
    SO_NODE_ADD_FIELD(useNewSelection, (true));
120

121
    SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, AUTO);
122
    SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, ON);
123
    SO_NODE_DEFINE_ENUM_VALUE(HighlightModes, OFF);
124
    SO_NODE_SET_SF_ENUM_TYPE (highlightMode, HighlightModes);
125

126
    // Documentation of SoFullPath:
127
    // Since the SoFullPath is derived from SoPath and contains no private data, you can cast SoPath instances to the SoFullPath type.
128
    // This will allow you to examine hidden children. Actually, you are not supposed to allocate instances of this class at all.
129
    // It is only available as an "extended interface" into the superclass SoPath.
130
    detailPath = static_cast<SoFullPath*>(new SoPath(20));
131
    detailPath->ref();
132

133
    setPreSelection = false;
134
    preSelection = -1;
135
    useNewSelection = ViewParams::instance()->getUseNewSelection();
136
}
137

138
/*!
139
  Destructor.
140
*/
141
SoFCUnifiedSelection::~SoFCUnifiedSelection()
142
{
143
    // If we're being deleted and we're the current highlight,
144
    // NULL out that variable
145
    if (currenthighlight) {
146
        currenthighlight->unref();
147
        currenthighlight = nullptr;
148
    }
149
    if (detailPath) {
150
        detailPath->unref();
151
        detailPath = nullptr;
152
    }
153
}
154

155
// doc from parent
156
void
157
SoFCUnifiedSelection::initClass()
158
{
159
    SO_NODE_INIT_CLASS(SoFCUnifiedSelection,SoSeparator,"Separator");
160
}
161

162
void SoFCUnifiedSelection::finish()
163
{
164
    atexit_cleanup();
165
}
166

167
bool SoFCUnifiedSelection::hasHighlight() {
168
    return currenthighlight != nullptr;
169
}
170

171
void SoFCUnifiedSelection::applySettings()
172
{
173
    float transparency;
174
    ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View");
175
    bool enablePre = hGrp->GetBool("EnablePreselection", true);
176
    bool enableSel = hGrp->GetBool("EnableSelection", true);
177
    if (!enablePre) {
178
        this->highlightMode = SoFCUnifiedSelection::OFF;
179
    }
180
    else {
181
        // Search for a user defined value with the current color as default
182
        SbColor highlightColor = this->colorHighlight.getValue();
183
        auto highlight = (unsigned long)(highlightColor.getPackedValue());
184
        highlight = hGrp->GetUnsigned("HighlightColor", highlight);
185
        highlightColor.setPackedValue((uint32_t)highlight, transparency);
186
        this->colorHighlight.setValue(highlightColor);
187
    }
188

189
    if (!enableSel) {
190
        this->selectionMode = SoFCUnifiedSelection::OFF;
191
    }
192
    else {
193
        // Do the same with the selection color
194
        SbColor selectionColor = this->colorSelection.getValue();
195
        auto selection = (unsigned long)(selectionColor.getPackedValue());
196
        selection = hGrp->GetUnsigned("SelectionColor", selection);
197
        selectionColor.setPackedValue((uint32_t)selection, transparency);
198
        this->colorSelection.setValue(selectionColor);
199
    }
200
}
201

202
const char* SoFCUnifiedSelection::getFileFormatName() const
203
{
204
    return "Separator";
205
}
206

207
void SoFCUnifiedSelection::write(SoWriteAction * action)
208
{
209
    SoOutput * out = action->getOutput();
210
    if (out->getStage() == SoOutput::WRITE) {
211
        // Do not write out the fields of this class
212
        if (this->writeHeader(out, true, false))
213
            return;
214
        SoGroup::doAction(static_cast<SoAction *>(action));
215
        this->writeFooter(out);
216
    }
217
    else {
218
        inherited::write(action);
219
    }
220
}
221

222
int SoFCUnifiedSelection::getPriority(const SoPickedPoint* p)
223
{
224
    const SoDetail* detail = p->getDetail();
225
    if (!detail)
226
        return 0;
227
    if (detail->isOfType(SoFaceDetail::getClassTypeId()))
228
        return 1;
229
    if (detail->isOfType(SoLineDetail::getClassTypeId()))
230
        return 2;
231
    if (detail->isOfType(SoPointDetail::getClassTypeId()))
232
        return 3;
233
    return 0;
234
}
235

236
std::vector<SoFCUnifiedSelection::PickedInfo>
237
SoFCUnifiedSelection::getPickedList(SoHandleEventAction* action, bool singlePick) const
238
{
239
    ViewProvider *last_vp = nullptr;
240
    std::vector<PickedInfo> ret;
241
    const SoPickedPointList & points = action->getPickedPointList();
242
    for(int i=0,count=points.getLength();i<count;++i) {
243
        PickedInfo info;
244
        info.pp = points[i];
245
        info.vpd = nullptr;
246
        ViewProvider *vp = nullptr;
247
        auto path = static_cast<SoFullPath *>(info.pp->getPath());
248
        if (this->pcDocument && path && path->containsPath(action->getCurPath())) {
249
            vp = this->pcDocument->getViewProviderByPathFromHead(path);
250
            if(singlePick && last_vp && last_vp!=vp)
251
                return ret;
252
        }
253
        if(!vp || !vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) {
254
            if(!singlePick) continue;
255
            if(ret.empty())
256
                ret.push_back(info);
257
            break;
258
        }
259
        info.vpd = static_cast<ViewProviderDocumentObject*>(vp);
260
        if(!(useNewSelection.getValue()||info.vpd->useNewSelectionModel()) || !info.vpd->isSelectable()) {
261
            if(!singlePick) continue;
262
            if(ret.empty()) {
263
                info.vpd = nullptr;
264
                ret.push_back(info);
265
            }
266
            break;
267
        }
268
        if(!info.vpd->getElementPicked(info.pp,info.element))
269
            continue;
270

271
        if(singlePick)
272
            last_vp = vp;
273
        ret.push_back(info);
274
    }
275

276
    if(ret.size()<=1)
277
        return ret;
278

279
    // To identify the picking of lines in a concave area we have to
280
    // get all intersection points. If we have two or more intersection
281
    // points where the first is of a face and the second of a line with
282
    // almost similar coordinates we use the second point, instead.
283

284
    int picked_prio = getPriority(ret[0].pp);
285
    auto last_vpd = ret[0].vpd;
286
    const SbVec3f& picked_pt = ret.front().pp->getPoint();
287
    auto itPicked = ret.begin();
288
    for(auto it=ret.begin()+1;it!=ret.end();++it) {
289
        auto &info = *it;
290
        if(last_vpd != info.vpd)
291
            break;
292

293
        int cur_prio = getPriority(info.pp);
294
        const SbVec3f& cur_pt = info.pp->getPoint();
295

296
        if ((cur_prio > picked_prio) && picked_pt.equals(cur_pt, 0.2F)) {
297
            itPicked = it;
298
            picked_prio = cur_prio;
299
        }
300
    }
301

302
    if(singlePick) {
303
        std::vector<PickedInfo> sret(itPicked,itPicked+1);
304
        return sret;
305
    }
306
    if(itPicked != ret.begin())
307
        std::swap(*itPicked, *ret.begin());
308
    return ret;
309
}
310

311
void SoFCUnifiedSelection::doAction(SoAction *action)
312
{
313
    if (action->getTypeId() == SoFCEnableHighlightAction::getClassTypeId()) {
314
        auto preaction = static_cast<SoFCEnableHighlightAction*>(action);
315
        if (preaction->highlight) {
316
            this->highlightMode = SoFCUnifiedSelection::AUTO;
317
        }
318
        else {
319
            this->highlightMode = SoFCUnifiedSelection::OFF;
320
        }
321
    }
322

323
    if (action->getTypeId() == SoFCEnableSelectionAction::getClassTypeId()) {
324
        auto selaction = static_cast<SoFCEnableSelectionAction*>(action);
325
        if (selaction->selection) {
326
            this->selectionMode = SoFCUnifiedSelection::ON;
327
        }
328
        else {
329
            this->selectionMode = SoFCUnifiedSelection::OFF;
330
        }
331
    }
332

333
    if (action->getTypeId() == SoFCSelectionColorAction::getClassTypeId()) {
334
        auto colaction = static_cast<SoFCSelectionColorAction*>(action);
335
        this->colorSelection = colaction->selectionColor;
336
    }
337

338
    if (action->getTypeId() == SoFCHighlightColorAction::getClassTypeId()) {
339
        auto colaction = static_cast<SoFCHighlightColorAction*>(action);
340
        this->colorHighlight = colaction->highlightColor;
341
    }
342

343
    if (action->getTypeId() == SoFCHighlightAction::getClassTypeId()) {
344
        auto hilaction = static_cast<SoFCHighlightAction*>(action);
345
        // Do not clear currently highlighted object when setting new pre-selection
346
        if (!setPreSelection && hilaction->SelChange.Type == SelectionChanges::RmvPreselect) {
347
            if (currenthighlight) {
348
                SoHighlightElementAction hlAction;
349
                hlAction.apply(currenthighlight);
350
                currenthighlight->unref();
351
                currenthighlight = nullptr;
352
            }
353
        }
354
        else if (highlightMode.getValue() != OFF
355
                    && hilaction->SelChange.Type == SelectionChanges::SetPreselect) {
356
            if (currenthighlight) {
357
                SoHighlightElementAction hlAction;
358
                hlAction.apply(currenthighlight);
359
                currenthighlight->unref();
360
                currenthighlight = nullptr;
361
            }
362

363
            App::Document* doc = App::GetApplication().getDocument(hilaction->SelChange.pDocName);
364
            App::DocumentObject* obj = doc->getObject(hilaction->SelChange.pObjectName);
365
            ViewProvider*vp = Application::Instance->getViewProvider(obj);
366
            SoDetail* detail = vp->getDetail(hilaction->SelChange.pSubName);
367

368
            SoHighlightElementAction hlAction;
369
            hlAction.setHighlighted(true);
370
            hlAction.setColor(this->colorHighlight.getValue());
371
            hlAction.setElement(detail);
372
            hlAction.apply(vp->getRoot());
373
            delete detail;
374

375
            SoSearchAction sa;
376
            sa.setNode(vp->getRoot());
377
            sa.apply(vp->getRoot());
378
            currenthighlight = static_cast<SoFullPath*>(sa.getPath()->copy());
379
            currenthighlight->ref();
380
        }
381

382
        if (useNewSelection.getValue())
383
            return;
384
    }
385

386
    if (action->getTypeId() == SoFCSelectionAction::getClassTypeId()) {
387
        auto selaction = static_cast<SoFCSelectionAction*>(action);
388
        if(selectionMode.getValue() == ON
389
            && (selaction->SelChange.Type == SelectionChanges::AddSelection
390
                || selaction->SelChange.Type == SelectionChanges::RmvSelection))
391
        {
392
            // selection changes inside the 3d view are handled in handleEvent()
393
            App::Document* doc = App::GetApplication().getDocument(selaction->SelChange.pDocName);
394
            App::DocumentObject* obj = doc->getObject(selaction->SelChange.pObjectName);
395
            ViewProvider*vp = Application::Instance->getViewProvider(obj);
396
            if (vp && (useNewSelection.getValue()||vp->useNewSelectionModel()) && vp->isSelectable()) {
397
                SoDetail *detail = nullptr;
398
                detailPath->truncate(0);
399
                if(!selaction->SelChange.pSubName || !selaction->SelChange.pSubName[0] ||
400
                    vp->getDetailPath(selaction->SelChange.pSubName,detailPath,true,detail))
401
                {
402
                    SoSelectionElementAction::Type type = SoSelectionElementAction::None;
403
                    if (selaction->SelChange.Type == SelectionChanges::AddSelection) {
404
                        if (detail)
405
                            type = SoSelectionElementAction::Append;
406
                        else
407
                            type = SoSelectionElementAction::All;
408
                    }
409
                    else {
410
                        if (detail)
411
                            type = SoSelectionElementAction::Remove;
412
                        else
413
                            type = SoSelectionElementAction::None;
414
                    }
415

416
                    SoSelectionElementAction selectionAction(type);
417
                    selectionAction.setColor(this->colorSelection.getValue());
418
                    selectionAction.setElement(detail);
419
                    if(detailPath->getLength())
420
                        selectionAction.apply(detailPath);
421
                    else
422
                        selectionAction.apply(vp->getRoot());
423
                }
424
                detailPath->truncate(0);
425
                delete detail;
426
            }
427
        }
428
        else if (selaction->SelChange.Type == SelectionChanges::ClrSelection) {
429
            SoSelectionElementAction selectionAction(SoSelectionElementAction::None);
430
            for(int i=0;i<this->getNumChildren();++i)
431
                selectionAction.apply(this->getChild(i));
432
        }
433
        else if(selectionMode.getValue() == ON
434
                    && selaction->SelChange.Type == SelectionChanges::SetSelection) {
435
            std::vector<ViewProvider*> vps;
436
            if (this->pcDocument)
437
                vps = this->pcDocument->getViewProvidersOfType(ViewProviderDocumentObject::getClassTypeId());
438
            for (const auto & vp : vps) {
439
                auto vpd = static_cast<ViewProviderDocumentObject*>(vp);
440
                if (useNewSelection.getValue() || vpd->useNewSelectionModel()) {
441
                    SoSelectionElementAction::Type type;
442
                    if(Selection().isSelected(vpd->getObject()) && vpd->isSelectable())
443
                        type = SoSelectionElementAction::All;
444
                    else
445
                        type = SoSelectionElementAction::None;
446

447
                    SoSelectionElementAction selectionAction(type);
448
                    selectionAction.setColor(this->colorSelection.getValue());
449
                    selectionAction.apply(vpd->getRoot());
450
                }
451
            }
452
        }
453
        else if (selaction->SelChange.Type == SelectionChanges::SetPreselectSignal) {
454
            // selection changes inside the 3d view are handled in handleEvent()
455
            App::Document* doc = App::GetApplication().getDocument(selaction->SelChange.pDocName);
456
            App::DocumentObject* obj = doc->getObject(selaction->SelChange.pObjectName);
457
            ViewProvider*vp = Application::Instance->getViewProvider(obj);
458
            if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) &&
459
                (useNewSelection.getValue()||vp->useNewSelectionModel()) && vp->isSelectable())
460
            {
461
                detailPath->truncate(0);
462
                SoDetail *det = nullptr;
463
                if(vp->getDetailPath(selaction->SelChange.pSubName,detailPath,true,det)) {
464
                    setHighlight(detailPath,det,static_cast<ViewProviderDocumentObject*>(vp),
465
                            selaction->SelChange.pSubName,
466
                            selaction->SelChange.x,
467
                            selaction->SelChange.y,
468
                            selaction->SelChange.z);
469
                }
470
                delete det;
471
            }
472
        }
473

474
        if (useNewSelection.getValue())
475
            return;
476
    }
477

478
    inherited::doAction( action );
479
}
480

481
bool SoFCUnifiedSelection::setHighlight(const PickedInfo &info) {
482
    if(!info.pp)
483
        return setHighlight(nullptr,nullptr,nullptr,nullptr,0.0,0.0,0.0);
484
    const auto &pt = info.pp->getPoint();
485
    return setHighlight(static_cast<SoFullPath*>(info.pp->getPath()),
486
            info.pp->getDetail(), info.vpd, info.element.c_str(), pt[0],pt[1],pt[2]);
487
}
488

489
bool SoFCUnifiedSelection::setHighlight(SoFullPath *path, const SoDetail *det,
490
        ViewProviderDocumentObject *vpd, const char *element, float x, float y, float z)
491
{
492
    Base::FlagToggler<SbBool> flag(setPreSelection);
493

494
    bool highlighted = false;
495
    if(path && path->getLength() &&
496
       vpd && vpd->getObject() && vpd->getObject()->isAttachedToDocument())
497
    {
498
        const char *docname = vpd->getObject()->getDocument()->getName();
499
        const char *objname = vpd->getObject()->getNameInDocument();
500

501
        this->preSelection = 1;
502

503
        printPreselectionInfo(docname, objname, element, x, y, z, 1e-7);
504

505

506
        int ret = Gui::Selection().setPreselect(docname,objname,element,x,y,z);
507
        if(ret<0 && currenthighlight)
508
            return true;
509

510
        if(ret) {
511
            if (currenthighlight) {
512
                SoHighlightElementAction action;
513
                action.setHighlighted(false);
514
                action.apply(currenthighlight);
515
                currenthighlight->unref();
516
                currenthighlight = nullptr;
517
            }
518
            currenthighlight = static_cast<SoFullPath*>(path->copy());
519
            currenthighlight->ref();
520
            highlighted = true;
521
        }
522
    }
523

524
    if(currenthighlight) {
525
        SoHighlightElementAction action;
526
        action.setHighlighted(highlighted);
527
        action.setColor(this->colorHighlight.getValue());
528
        action.setElement(det);
529
        action.apply(currenthighlight);
530
        if(!highlighted) {
531
            currenthighlight->unref();
532
            currenthighlight = nullptr;
533
            Selection().rmvPreselect();
534
        }
535
        this->touch();
536
    }
537
    return highlighted;
538
}
539

540
bool SoFCUnifiedSelection::setSelection(const std::vector<PickedInfo> &infos, bool ctrlDown) {
541
    if(infos.empty() || !infos[0].vpd)
542
        return false;
543

544
    std::vector<SelectionSingleton::SelObj> sels;
545
    if(infos.size()>1) {
546
        for(auto &info : infos) {
547
            if(!info.vpd) continue;
548

549
            SelectionSingleton::SelObj sel;
550
            sel.pResolvedObject = nullptr;
551
            sel.pObject = info.vpd->getObject();
552
            sel.pDoc = sel.pObject->getDocument();
553
            sel.DocName = sel.pDoc->getName();
554
            sel.FeatName = sel.pObject->getNameInDocument();
555
            sel.TypeName = sel.pObject->getTypeId().getName();
556
            sel.SubName = info.element.c_str();
557
            const auto &pt = info.pp->getPoint();
558
            sel.x = pt[0];
559
            sel.y = pt[1];
560
            sel.z = pt[2];
561
            sels.push_back(sel);
562
        }
563
    }
564

565
    const auto &info = infos[0];
566
    auto vpd = info.vpd;
567
    if(!vpd)
568
        return false;
569
    if(!vpd->getObject()->isAttachedToDocument())
570
        return false;
571
    const char *objname = vpd->getObject()->getNameInDocument();
572
    const char *docname = vpd->getObject()->getDocument()->getName();
573

574
    bool hasNext = false;
575
    const SoPickedPoint * pp = info.pp;
576
    const SoDetail *det = pp->getDetail();
577
    SoDetail *detNext = nullptr;
578
    auto pPath = static_cast<SoFullPath*>(pp->getPath());
579
    const auto &pt = pp->getPoint();
580
    SoSelectionElementAction::Type type = SoSelectionElementAction::None;
581
    auto mymode = static_cast<HighlightModes>(this->highlightMode.getValue());
582
    static char buf[513];
583

584
    if (ctrlDown) {
585
        if(Gui::Selection().isSelected(docname, objname, info.element.c_str(), ResolveMode::NoResolve))
586
            Gui::Selection().rmvSelection(docname, objname,info.element.c_str(), &sels);
587
        else {
588
            bool ok = Gui::Selection().addSelection(docname,objname,
589
                    info.element.c_str(), pt[0] ,pt[1] ,pt[2], &sels);
590
            if (ok && mymode == OFF) {
591
                snprintf(buf,512,"Selected: %s.%s.%s (%g, %g, %g)",
592
                        docname,objname,info.element.c_str()
593
                        ,fabs(pt[0])>1e-7?pt[0]:0.0
594
                        ,fabs(pt[1])>1e-7?pt[1]:0.0
595
                        ,fabs(pt[2])>1e-7?pt[2]:0.0);
596

597
                getMainWindow()->showMessage(QString::fromLatin1(buf));
598
            }
599
        }
600
        return true;
601
    }
602

603
    // Hierarchy ascending
604
    //
605
    // If the clicked subelement is already selected, check if there is an
606
    // upper hierarchy, and select that hierarchy instead.
607
    //
608
    // For example, let's suppose PickedInfo above reports
609
    // 'link.link2.box.Face1', and below Selection().getSelectedElement returns
610
    // 'link.link2.box.', meaning that 'box' is the current selected hierarchy,
611
    // and the user is clicking the box again.  So we shall go up one level,
612
    // and select 'link.link2.'
613
    //
614

615
    std::string subName = info.element;
616
    std::string objectName = objname;
617

618
    const char *subSelected = Gui::Selection().getSelectedElement(
619
                                vpd->getObject(),subName.c_str());
620

621
    FC_TRACE("select " << (subSelected?subSelected:"'null'") << ", " <<
622
            objectName << ", " << subName);
623
    std::string newElement;
624
    if(subSelected) {
625
        newElement = Data::newElementName(subSelected);
626
        subSelected = newElement.c_str();
627
        std::string nextsub;
628
        const char *next = strrchr(subSelected,'.');
629
        if(next && next!=subSelected) {
630
            if(next[1]==0) {
631
                // The convention of dot separated SubName demands a mandatory
632
                // ending dot for every object name reference inside SubName.
633
                // The non-object sub-element, however, must not end with a dot.
634
                // So, next[1]==0 here means current selection is a whole object
635
                // selection (because no sub-element), so we shall search
636
                // upwards for the second last dot, which is the end of the
637
                // parent name of the current selected object
638
                for(--next;next!=subSelected;--next) {
639
                    if(*next == '.') break;
640
                }
641
            }
642
            if(*next == '.')
643
                nextsub = std::string(subSelected,next-subSelected+1);
644
        }
645
        if(nextsub.length() || *subSelected!=0) {
646
            hasNext = true;
647
            subName = nextsub;
648
            detailPath->truncate(0);
649
            if(vpd->getDetailPath(subName.c_str(),detailPath,true,detNext) &&
650
               detailPath->getLength())
651
            {
652
                pPath = detailPath;
653
                det = detNext;
654
                FC_TRACE("select next " << objectName << ", " << subName);
655
            }
656
        }
657
    }
658

659
    FC_TRACE("clearing selection");
660
    Gui::Selection().clearSelection();
661
    FC_TRACE("add selection");
662
    bool ok = Gui::Selection().addSelection(docname, objectName.c_str() ,subName.c_str(),
663
            pt[0] ,pt[1] ,pt[2], &sels);
664
    if (ok)
665
        type = hasNext?SoSelectionElementAction::All:SoSelectionElementAction::Append;
666

667
    if (mymode == OFF) {
668
        snprintf(buf,512,"Selected: %s.%s.%s (%g, %g, %g)",
669
                docname, objectName.c_str() ,subName.c_str()
670
                ,fabs(pt[0])>1e-7?pt[0]:0.0
671
                ,fabs(pt[1])>1e-7?pt[1]:0.0
672
                ,fabs(pt[2])>1e-7?pt[2]:0.0);
673

674
        getMainWindow()->showMessage(QString::fromLatin1(buf));
675
    }
676

677
    if (pPath) {
678
        FC_TRACE("applying action");
679
        SoSelectionElementAction action(type);
680
        action.setColor(this->colorSelection.getValue());
681
        action.setElement(det);
682
        action.apply(pPath);
683
        FC_TRACE("applied action");
684
        this->touch();
685
    }
686

687
    if(detNext) delete detNext;
688
    return true;
689
}
690

691
// doc from parent
692
void
693
SoFCUnifiedSelection::handleEvent(SoHandleEventAction * action)
694
{
695
    // If off then don't handle this event
696
    if (!selectionRole.getValue()) {
697
        inherited::handleEvent(action);
698
        return;
699
    }
700

701
    auto mymode = static_cast<HighlightModes>(this->highlightMode.getValue());
702
    const SoEvent * event = action->getEvent();
703

704
    // If we don't need to pick for locate highlighting,
705
    // then just behave as separator and return.
706
    // NOTE: we still have to pick for ON even though we don't have
707
    // to re-render, because the app needs to be notified as the mouse
708
    // goes over locate highlight nodes.
709
    //if (highlightMode.getValue() == OFF) {
710
    //    inherited::handleEvent( action );
711
    //    return;
712
    //}
713

714
    //
715
    // If this is a mouseMotion event, then check for locate highlighting
716
    //
717
    if (event->isOfType(SoLocation2Event::getClassTypeId())) {
718
        // NOTE: If preselection is off then we do not check for a picked point because otherwise this search may slow
719
        // down extremely the system on really big data sets. In this case we just check for a picked point if the data
720
        // set has been selected.
721
        if (mymode == AUTO || mymode == ON) {
722
            // check to see if the mouse is over our geometry...
723
            auto infos = this->getPickedList(action,true);
724
            if(!infos.empty())
725
                setHighlight(infos[0]);
726
            else {
727
                setHighlight(PickedInfo());
728
                if (this->preSelection > 0) {
729
                    this->preSelection = 0;
730
                    // touch() makes sure to call GLRenderBelowPath so that the cursor can be updated
731
                    // because only from there the SoGLWidgetElement delivers the OpenGL window
732
                    this->touch();
733
                }
734
            }
735
        }
736
    }
737
    // mouse press events for (de)selection
738
    else if (event->isOfType(SoMouseButtonEvent::getClassTypeId()) &&
739
             selectionMode.getValue() == SoFCUnifiedSelection::ON) {
740
        const auto e = static_cast<const SoMouseButtonEvent *>(event);
741
        if (SoMouseButtonEvent::isButtonReleaseEvent(e,SoMouseButtonEvent::BUTTON1)) {
742
            // check to see if the mouse is over a geometry...
743
            auto infos = this->getPickedList(action,!Selection().needPickedList());
744
            bool greedySel = Gui::Selection().getSelectionStyle() == Gui::SelectionSingleton::SelectionStyle::GreedySelection;
745
            if(setSelection(infos, event->wasCtrlDown() || greedySel))
746
                action->setHandled();
747
        } // mouse release
748
    }
749

750
    inherited::handleEvent(action);
751
}
752

753
void SoFCUnifiedSelection::GLRenderBelowPath(SoGLRenderAction * action)
754
{
755
    inherited::GLRenderBelowPath(action);
756

757
    // nothing picked, so restore the arrow cursor if needed
758
    if (this->preSelection == 0) {
759
        // this is called when a selection gate forbade to select an object
760
        // and the user moved the mouse to an empty area
761
        this->preSelection = -1;
762
        QtGLWidget* window;
763
        SoState *state = action->getState();
764
        SoGLWidgetElement::get(state, window);
765
        QWidget* parent = window ? window->parentWidget() : nullptr;
766
        if (parent) {
767
            QCursor c = parent->cursor();
768
            if (c.shape() == Qt::ForbiddenCursor) {
769
                c.setShape(Qt::ArrowCursor);
770
                parent->setCursor(c);
771
            }
772
        }
773
    }
774
}
775

776
// ---------------------------------------------------------------
777

778
SO_ACTION_SOURCE(SoHighlightElementAction)
779

780
void SoHighlightElementAction::initClass()
781
{
782
    SO_ACTION_INIT_CLASS(SoHighlightElementAction,SoAction);
783

784
    SO_ENABLE(SoHighlightElementAction, SoSwitchElement);
785
    SO_ENABLE(SoHighlightElementAction, SoModelMatrixElement);
786

787
    SO_ACTION_ADD_METHOD(SoNode,nullAction);
788

789
    SO_ENABLE(SoHighlightElementAction, SoCoordinateElement);
790

791
    SO_ACTION_ADD_METHOD(SoGroup,callDoAction);
792
    SO_ACTION_ADD_METHOD(SoIndexedLineSet,callDoAction);
793
    SO_ACTION_ADD_METHOD(SoIndexedFaceSet,callDoAction);
794
    SO_ACTION_ADD_METHOD(SoPointSet,callDoAction);
795
}
796

797
SoHighlightElementAction::SoHighlightElementAction ()
798
{
799
    SO_ACTION_CONSTRUCTOR(SoHighlightElementAction);
800
}
801

802
SoHighlightElementAction::~SoHighlightElementAction() = default;
803

804
void SoHighlightElementAction::beginTraversal(SoNode *node)
805
{
806
    traverse(node);
807
}
808

809
void SoHighlightElementAction::callDoAction(SoAction *action,SoNode *node)
810
{
811
    node->doAction(action);
812
}
813

814
void SoHighlightElementAction::setHighlighted(SbBool ok)
815
{
816
    this->_highlight = ok;
817
}
818

819
SbBool SoHighlightElementAction::isHighlighted() const
820
{
821
    return this->_highlight;
822
}
823

824
void SoHighlightElementAction::setColor(const SbColor& c)
825
{
826
    this->_color = c;
827
}
828

829
const SbColor& SoHighlightElementAction::getColor() const
830
{
831
    return this->_color;
832
}
833

834
void SoHighlightElementAction::setElement(const SoDetail* det)
835
{
836
    this->_det = det;
837
}
838

839
const SoDetail* SoHighlightElementAction::getElement() const
840
{
841
    return this->_det;
842
}
843

844
// ---------------------------------------------------------------
845

846
SO_ACTION_SOURCE(SoSelectionElementAction)
847

848
void SoSelectionElementAction::initClass()
849
{
850
    SO_ACTION_INIT_CLASS(SoSelectionElementAction,SoAction);
851

852
    SO_ENABLE(SoSelectionElementAction, SoSwitchElement);
853
    SO_ENABLE(SoSelectionElementAction, SoModelMatrixElement);
854

855
    SO_ACTION_ADD_METHOD(SoNode,nullAction);
856

857
    SO_ENABLE(SoSelectionElementAction, SoCoordinateElement);
858

859
    SO_ACTION_ADD_METHOD(SoCoordinate3,callDoAction);
860
    SO_ACTION_ADD_METHOD(SoGroup,callDoAction);
861
    SO_ACTION_ADD_METHOD(SoIndexedLineSet,callDoAction);
862
    SO_ACTION_ADD_METHOD(SoIndexedFaceSet,callDoAction);
863
    SO_ACTION_ADD_METHOD(SoPointSet,callDoAction);
864
}
865

866
SoSelectionElementAction::SoSelectionElementAction (Type t, bool secondary)
867
    : _type(t), _secondary(secondary)
868
{
869
    SO_ACTION_CONSTRUCTOR(SoSelectionElementAction);
870
}
871

872
SoSelectionElementAction::~SoSelectionElementAction() = default;
873

874
void SoSelectionElementAction::beginTraversal(SoNode *node)
875
{
876
    traverse(node);
877
}
878

879
void SoSelectionElementAction::callDoAction(SoAction *action,SoNode *node)
880
{
881
    node->doAction(action);
882
}
883

884
SoSelectionElementAction::Type
885
SoSelectionElementAction::getType() const
886
{
887
    return this->_type;
888
}
889

890
void SoSelectionElementAction::setColor(const SbColor& c)
891
{
892
    this->_color = c;
893
}
894

895
const SbColor& SoSelectionElementAction::getColor() const
896
{
897
    return this->_color;
898
}
899

900
void SoSelectionElementAction::setElement(const SoDetail* det)
901
{
902
    this->_det = det;
903
}
904

905
const SoDetail* SoSelectionElementAction::getElement() const
906
{
907
    return this->_det;
908
}
909

910
// ---------------------------------------------------------------
911

912
SO_ACTION_SOURCE(SoVRMLAction)
913

914
void SoVRMLAction::initClass()
915
{
916
    SO_ACTION_INIT_CLASS(SoVRMLAction,SoAction);
917

918
    SO_ENABLE(SoVRMLAction, SoSwitchElement);
919

920
    SO_ACTION_ADD_METHOD(SoNode,nullAction);
921

922
    SO_ENABLE(SoVRMLAction, SoCoordinateElement);
923
    SO_ENABLE(SoVRMLAction, SoMaterialBindingElement);
924
    SO_ENABLE(SoVRMLAction, SoLazyElement);
925
    SO_ENABLE(SoVRMLAction, SoShapeStyleElement);
926

927
    SO_ACTION_ADD_METHOD(SoCoordinate3,callDoAction);
928
    SO_ACTION_ADD_METHOD(SoMaterialBinding,callDoAction);
929
    SO_ACTION_ADD_METHOD(SoMaterial,callDoAction);
930
    SO_ACTION_ADD_METHOD(SoNormalBinding,callDoAction);
931
    SO_ACTION_ADD_METHOD(SoGroup,callDoAction);
932
    SO_ACTION_ADD_METHOD(SoIndexedLineSet,callDoAction);
933
    SO_ACTION_ADD_METHOD(SoIndexedFaceSet,callDoAction);
934
    SO_ACTION_ADD_METHOD(SoPointSet,callDoAction);
935
}
936

937
SoVRMLAction::SoVRMLAction()
938
{
939
    SO_ACTION_CONSTRUCTOR(SoVRMLAction);
940
}
941

942
SoVRMLAction::~SoVRMLAction() = default;
943

944
void SoVRMLAction::setOverrideMode(SbBool on)
945
{
946
    overrideMode = on;
947
}
948

949
SbBool SoVRMLAction::isOverrideMode() const
950
{
951
    return overrideMode;
952
}
953

954
void SoVRMLAction::callDoAction(SoAction *action, SoNode *node)
955
{
956
    if (node->getTypeId().isDerivedFrom(SoNormalBinding::getClassTypeId()) && action->isOfType(SoVRMLAction::getClassTypeId())) {
957
        auto vrmlAction = static_cast<SoVRMLAction*>(action);
958
        if (vrmlAction->overrideMode) {
959
            auto bind = static_cast<SoNormalBinding*>(node);
960
            vrmlAction->bindList.push_back(bind->value.getValue());
961
            // this normal binding causes some problems for the part view provider
962
            // See also #0002222: Number of normals in exported VRML is wrong
963
            if (bind->value.getValue() == static_cast<int>(SoNormalBinding::PER_VERTEX_INDEXED))
964
                bind->value = SoNormalBinding::OVERALL;
965
        }
966
        else if (!vrmlAction->bindList.empty()) {
967
            static_cast<SoNormalBinding*>(node)->value = static_cast<SoNormalBinding::Binding>(vrmlAction->bindList.front());
968
            vrmlAction->bindList.pop_front();
969
        }
970
    }
971

972
    node->doAction(action);
973
}
974

975
// ---------------------------------------------------------------------------------
976

977
bool SoFCSelectionRoot::StackComp::operator()(const Stack &a, const Stack &b) const {
978
    if(a.size()-a.offset < b.size()-b.offset)
979
        return true;
980
    if(a.size()-a.offset > b.size()-b.offset)
981
        return false;
982
    auto it1=a.rbegin(), end1=a.rend()-a.offset;
983
    auto it2=b.rbegin();
984
    for(;it1!=end1;++it1,++it2) {
985
        if(*it1 < *it2)
986
            return true;
987
        if(*it1 > *it2)
988
            return false;
989
    }
990
    return false;
991
}
992
// ---------------------------------------------------------------------------------
993
SoSeparator::CacheEnabled SoFCSeparator::CacheMode = SoSeparator::AUTO;
994
SO_NODE_SOURCE(SoFCSeparator)
995

996
SoFCSeparator::SoFCSeparator(bool trackCacheMode)
997
    :trackCacheMode(trackCacheMode)
998
{
999
    SO_NODE_CONSTRUCTOR(SoFCSeparator);
1000
    if(!trackCacheMode) {
1001
        renderCaching = SoSeparator::OFF;
1002
        boundingBoxCaching = SoSeparator::OFF;
1003
    }
1004
}
1005

1006
void SoFCSeparator::GLRenderBelowPath(SoGLRenderAction * action) {
1007
    if(trackCacheMode && renderCaching.getValue()!=CacheMode) {
1008
        renderCaching = CacheMode;
1009
        boundingBoxCaching = CacheMode;
1010
    }
1011
    inherited::GLRenderBelowPath(action);
1012
}
1013

1014
void SoFCSeparator::initClass()
1015
{
1016
    SO_NODE_INIT_CLASS(SoFCSeparator,SoSeparator,"FCSeparator");
1017
}
1018

1019
void SoFCSeparator::finish()
1020
{
1021
    atexit_cleanup();
1022
}
1023

1024
// ---------------------------------------------------------------------------------
1025
// Thread local data for bounding box rendering
1026
//
1027
// The code is inspred by Coin SoLevelOfDetails.cpp.
1028
using SoFCBBoxRenderInfo = struct {
1029
    SoGetBoundingBoxAction * bboxaction;
1030
    SoCube *cube;
1031
    SoColorPacker *packer;
1032
};
1033

1034
static void so_bbox_construct_data(void * closure)
1035
{
1036
    auto data = static_cast<SoFCBBoxRenderInfo*>(closure);
1037
    data->bboxaction = nullptr;
1038
    data->cube = nullptr;
1039
    data->packer = nullptr;
1040
}
1041

1042
static void so_bbox_destruct_data(void * closure)
1043
{
1044
    auto data = static_cast<SoFCBBoxRenderInfo*>(closure);
1045
    delete data->bboxaction;
1046
    if(data->cube)
1047
        data->cube->unref();
1048
    delete data->packer;
1049
}
1050

1051
static SbStorage * so_bbox_storage = nullptr;
1052

1053
// called from atexit
1054
static void so_bbox_cleanup()
1055
{
1056
    delete so_bbox_storage;
1057
}
1058

1059
// ---------------------------------------------------------------------------------
1060

1061
SoFCSelectionRoot::Stack SoFCSelectionRoot::SelStack;
1062
std::unordered_map<SoAction*,SoFCSelectionRoot::Stack> SoFCSelectionRoot::ActionStacks;
1063
SoFCSelectionRoot::ColorStack SoFCSelectionRoot::SelColorStack;
1064
SoFCSelectionRoot::ColorStack SoFCSelectionRoot::HlColorStack;
1065
SoFCSelectionRoot* SoFCSelectionRoot::ShapeColorNode;
1066

1067
SO_NODE_SOURCE(SoFCSelectionRoot)
1068

1069
SoFCSelectionRoot::SoFCSelectionRoot(bool trackCacheMode)
1070
    :SoFCSeparator(trackCacheMode)
1071
{
1072
    SO_NODE_CONSTRUCTOR(SoFCSelectionRoot);
1073
    SO_NODE_ADD_FIELD(selectionStyle,(Full));
1074
    SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, Full);
1075
    SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, Box);
1076
    SO_NODE_DEFINE_ENUM_VALUE(SelectStyles, PassThrough);
1077
    SO_NODE_SET_SF_ENUM_TYPE(selectionStyle, SelectStyles);
1078
}
1079

1080
SoFCSelectionRoot::~SoFCSelectionRoot() = default;
1081

1082
void SoFCSelectionRoot::initClass()
1083
{
1084
    SO_NODE_INIT_CLASS(SoFCSelectionRoot,SoFCSeparator,"FCSelectionRoot");
1085

1086
    so_bbox_storage = new SbStorage(sizeof(SoFCBBoxRenderInfo),
1087
            so_bbox_construct_data, so_bbox_destruct_data);
1088

1089
    // cc_coin_atexit((coin_atexit_f*) so_bbox_cleanup);
1090
}
1091

1092
void SoFCSelectionRoot::finish()
1093
{
1094
    so_bbox_cleanup();
1095
    atexit_cleanup();
1096
}
1097

1098
SoNode *SoFCSelectionRoot::getCurrentRoot(bool front, SoNode *def) {
1099
    if(!SelStack.empty())
1100
        return front?SelStack.front():SelStack.back();
1101
    return def;
1102
}
1103

1104
SoFCSelectionContextBasePtr SoFCSelectionRoot::getNodeContext(
1105
        Stack &stack, SoNode *node, SoFCSelectionContextBasePtr def)
1106
{
1107
    if(stack.empty())
1108
        return def;
1109

1110
    auto front = dynamic_cast<SoFCSelectionRoot *>(stack.front());
1111
    if (front == nullptr) {
1112
        return SoFCSelectionContextBasePtr();
1113
    }
1114

1115
    stack.front() = node;
1116

1117
    auto it = front->contextMap.find(stack);
1118
    stack.front() = front;
1119
    if(it!=front->contextMap.end())
1120
        return it->second;
1121
    return {};
1122
}
1123

1124
SoFCSelectionContextBasePtr
1125
SoFCSelectionRoot::getNodeContext2(Stack &stack, SoNode *node, SoFCSelectionContextBase::MergeFunc *merge)
1126
{
1127
    SoFCSelectionContextBasePtr ret;
1128
    if (stack.empty()) {
1129
        return ret;
1130
    }
1131

1132
    auto *back = dynamic_cast<SoFCSelectionRoot*>(stack.back());
1133
    if (back == nullptr || back->contextMap2.empty()) {
1134
        return ret;
1135
    }
1136

1137
    int status = 0;
1138
    auto &map = back->contextMap2;
1139
    stack.back() = node;
1140
    for(stack.offset=0;stack.offset<stack.size();++stack.offset) {
1141
        auto it = map.find(stack);
1142
        SoFCSelectionContextBasePtr ctx;
1143
        if(it!=map.end())
1144
            ctx = it->second;
1145
        status = merge(status,ret,ctx,stack.offset==stack.size()-1?nullptr:stack[stack.offset]);
1146
        if(status<0)
1147
            break;
1148
    }
1149
    stack.offset = 0;
1150
    stack.back() = back;
1151
    return ret;
1152
}
1153

1154
std::pair<bool,SoFCSelectionContextBasePtr*> SoFCSelectionRoot::findActionContext(
1155
        SoAction *action, SoNode *_node, bool create, bool erase)
1156
{
1157
    std::pair<bool,SoFCSelectionContextBasePtr*> res(false,0);
1158

1159
    if(action->isOfType(SoSelectionElementAction::getClassTypeId()))
1160
        res.first = static_cast<SoSelectionElementAction*>(action)->isSecondary();
1161

1162
    auto it = ActionStacks.find(action);
1163
    if(it==ActionStacks.end() || it->second.empty())
1164
        return res;
1165

1166
    auto &stack = it->second;
1167

1168
    if(res.first) {
1169
        auto back = dynamic_cast<SoFCSelectionRoot*>(stack.back());
1170
        if (back != nullptr) {
1171
            stack.back() = _node;
1172
            if(create)
1173
                res.second = &back->contextMap2[stack];
1174
            else {
1175
                auto it = back->contextMap2.find(stack);
1176
                if(it!=back->contextMap2.end()) {
1177
                    res.second = &it->second;
1178
                    if(erase)
1179
                        back->contextMap2.erase(it);
1180
                }
1181
            }
1182
            stack.back() = back;
1183
        }
1184
    }else{
1185
        auto front = dynamic_cast<SoFCSelectionRoot*>(stack.front());
1186
        if (front != nullptr) {
1187
            stack.front() = _node;
1188
            if(create)
1189
                res.second = &front->contextMap[stack];
1190
            else {
1191
                auto it = front->contextMap.find(stack);
1192
                if(it!=front->contextMap.end()) {
1193
                    res.second = &it->second;
1194
                    if(erase)
1195
                        front->contextMap.erase(it);
1196
                }
1197
            }
1198
            stack.front() = front;
1199
        }
1200
    }
1201
    return res;
1202
}
1203

1204
bool SoFCSelectionRoot::renderBBox(SoGLRenderAction *action, SoNode *node, SbColor color)
1205
{
1206
    auto data = static_cast<SoFCBBoxRenderInfo*>(so_bbox_storage->get());
1207
    if (!data->bboxaction) {
1208
        // The viewport region will be replaced every time the action is
1209
        // used, so we can just feed it a dummy here.
1210
        data->bboxaction = new SoGetBoundingBoxAction(SbViewportRegion());
1211
        data->cube = new SoCube;
1212
        data->cube->ref();
1213
        data->packer = new SoColorPacker;
1214
    }
1215

1216
    SbBox3f bbox;
1217
    data->bboxaction->setViewportRegion(action->getViewportRegion());
1218
    data->bboxaction->apply(node);
1219
    bbox = data->bboxaction->getBoundingBox();
1220
    if(bbox.isEmpty())
1221
        return false;
1222

1223
    auto state = action->getState();
1224

1225
    state->push();
1226

1227
    SoMaterialBindingElement::set(state,SoMaterialBindingElement::OVERALL);
1228
    SoLazyElement::setEmissive(state, &color);
1229
    SoLazyElement::setDiffuse(state, node,1, &color, data->packer);
1230
    SoDrawStyleElement::set(state, node, SoDrawStyleElement::LINES);
1231
    SoLineWidthElement::set(state, node, 1.0f);
1232

1233
    const static float trans = 0.0;
1234
    SoLazyElement::setTransparency(state, node, 1, &trans, data->packer);
1235

1236
    float x, y, z;
1237
    bbox.getSize(x, y, z);
1238
    data->cube->width  = x+0.001;
1239
    data->cube->height  = y+0.001;
1240
    data->cube->depth = z+0.001;
1241

1242
    SoModelMatrixElement::translateBy(state,node,bbox.getCenter());
1243

1244
    SoMaterialBundle mb(action);
1245
    mb.sendFirst();
1246

1247
    data->cube->GLRender(action);
1248

1249
    state->pop();
1250
    return true;
1251
}
1252

1253
static std::time_t _CyclicLastReported;
1254

1255
void SoFCSelectionRoot::renderPrivate(SoGLRenderAction * action, bool inPath) {
1256
    if(ViewParams::instance()->getCoinCycleCheck()
1257
            && !SelStack.nodeSet.insert(this).second)
1258
    {
1259
        std::time_t t = std::time(nullptr);
1260
        if(_CyclicLastReported < t) {
1261
            _CyclicLastReported = t+5;
1262
            FC_ERR("Cyclic scene graph: " << getName());
1263
        }
1264
        return;
1265
    }
1266
    SelStack.push_back(this);
1267
    if(_renderPrivate(action,inPath)) {
1268
        if(inPath)
1269
            SoSeparator::GLRenderInPath(action);
1270
        else
1271
            SoSeparator::GLRenderBelowPath(action);
1272
    }
1273
    SelStack.pop_back();
1274
    SelStack.nodeSet.erase(this);
1275
}
1276

1277
bool SoFCSelectionRoot::_renderPrivate(SoGLRenderAction * action, bool inPath) {
1278
    auto ctx2 = std::static_pointer_cast<SelContext>(getNodeContext2(SelStack,this,SelContext::merge));
1279
    if(ctx2 && ctx2->hideAll)
1280
        return false;
1281

1282
    auto state = action->getState();
1283
    SelContextPtr ctx = getRenderContext<SelContext>(this);
1284
    int style = selectionStyle.getValue();
1285
    if((style==SoFCSelectionRoot::Box || ViewParams::instance()->getShowSelectionBoundingBox())
1286
       && ctx && !ctx->hideAll && (ctx->selAll || ctx->hlAll))
1287
    {
1288
        if (style==SoFCSelectionRoot::PassThrough) {
1289
            style = SoFCSelectionRoot::Box;
1290
        }
1291
        else {
1292
            renderBBox(action,this,ctx->hlAll?ctx->hlColor:ctx->selColor);
1293
            return true;
1294
        }
1295
    }
1296

1297
    // Here, we are not setting (pre)selection color override here.
1298
    // Instead, we are checking and setting up for any secondary context
1299
    // color override.
1300
    //
1301
    // When the current selection style is full highlight, we should ignore any
1302
    // secondary override. If the style is bounding box, however, we should
1303
    // honour the secondary color override.
1304

1305
    bool colorPushed = false;
1306
    if(!ShapeColorNode && overrideColor &&
1307
        !SoOverrideElement::getDiffuseColorOverride(state) &&
1308
        (style==SoFCSelectionRoot::Box || !ctx || (!ctx->selAll && !ctx->hideAll)))
1309
    {
1310
        ShapeColorNode = this;
1311
        colorPushed = true;
1312
        state->push();
1313
        auto &packer = ShapeColorNode->shapeColorPacker;
1314
        auto &trans = ShapeColorNode->transOverride;
1315
        auto &color = ShapeColorNode->colorOverride;
1316
        if(!SoOverrideElement::getTransparencyOverride(state) && trans) {
1317
            SoLazyElement::setTransparency(state, ShapeColorNode, 1, &trans, &packer);
1318
            SoOverrideElement::setTransparencyOverride(state,ShapeColorNode,true);
1319
        }
1320
        SoLazyElement::setDiffuse(state, ShapeColorNode, 1, &color, &packer);
1321
        SoOverrideElement::setDiffuseColorOverride(state,ShapeColorNode,true);
1322
        SoMaterialBindingElement::set(state, ShapeColorNode, SoMaterialBindingElement::OVERALL);
1323
        SoOverrideElement::setMaterialBindingOverride(state,ShapeColorNode,true);
1324

1325
        SoTextureEnabledElement::set(state,ShapeColorNode,false);
1326
    }
1327

1328
    if(!ctx) {
1329
        if(inPath)
1330
            SoSeparator::GLRenderInPath(action);
1331
        else
1332
            SoSeparator::GLRenderBelowPath(action);
1333
    } else {
1334
        bool selPushed;
1335
        bool hlPushed;
1336
        if((selPushed = ctx->selAll)) {
1337
            SelColorStack.push_back(ctx->selColor);
1338

1339
            if(style != SoFCSelectionRoot::Box) {
1340
                state->push();
1341
                auto &color = SelColorStack.back();
1342
                SoLazyElement::setEmissive(state, &color);
1343
                SoOverrideElement::setEmissiveColorOverride(state,this,true);
1344
                if (SoLazyElement::getLightModel(state) == SoLazyElement::BASE_COLOR) {
1345
                    auto &packer = shapeColorPacker;
1346
                    SoLazyElement::setDiffuse(state, this, 1, &color, &packer);
1347
                    SoOverrideElement::setDiffuseColorOverride(state,this,true);
1348
                    SoMaterialBindingElement::set(state, this, SoMaterialBindingElement::OVERALL);
1349
                    SoOverrideElement::setMaterialBindingOverride(state,this,true);
1350
                }
1351
            }
1352
        }
1353

1354
        if((hlPushed = ctx->hlAll))
1355
            HlColorStack.push_back(ctx->hlColor);
1356

1357
        if(inPath)
1358
            SoSeparator::GLRenderInPath(action);
1359
        else
1360
            SoSeparator::GLRenderBelowPath(action);
1361

1362
        if(selPushed) {
1363
            SelColorStack.pop_back();
1364

1365
            if(style != SoFCSelectionRoot::Box)
1366
                state->pop();
1367
        }
1368
        if(hlPushed)
1369
            HlColorStack.pop_back();
1370
    }
1371

1372
    if(colorPushed) {
1373
        ShapeColorNode = nullptr;
1374
        state->pop();
1375
    }
1376

1377
    return false;
1378
}
1379

1380
void SoFCSelectionRoot::GLRenderBelowPath(SoGLRenderAction * action) {
1381
    renderPrivate(action,false);
1382
}
1383

1384
void SoFCSelectionRoot::GLRenderInPath(SoGLRenderAction * action) {
1385
    if(action->getCurPathCode() == SoAction::BELOW_PATH)
1386
        return GLRenderBelowPath(action);
1387
    renderPrivate(action,true);
1388
}
1389

1390
bool SoFCSelectionRoot::checkColorOverride(SoState *state) {
1391
    if(ShapeColorNode) {
1392
        if(!SoOverrideElement::getDiffuseColorOverride(state)) {
1393
            state->push();
1394
            auto &packer = ShapeColorNode->shapeColorPacker;
1395
            auto &trans = ShapeColorNode->transOverride;
1396
            auto &color = ShapeColorNode->colorOverride;
1397
            if(!SoOverrideElement::getTransparencyOverride(state) && trans) {
1398
                SoLazyElement::setTransparency(state, ShapeColorNode, 1, &trans, &packer);
1399
                SoOverrideElement::setTransparencyOverride(state,ShapeColorNode,true);
1400
            }
1401
            SoLazyElement::setDiffuse(state, ShapeColorNode, 1, &color, &packer);
1402
            SoOverrideElement::setDiffuseColorOverride(state,ShapeColorNode,true);
1403
            SoMaterialBindingElement::set(state, ShapeColorNode, SoMaterialBindingElement::OVERALL);
1404
            SoOverrideElement::setMaterialBindingOverride(state,ShapeColorNode,true);
1405

1406
            SoTextureEnabledElement::set(state,ShapeColorNode,false);
1407
            return true;
1408
        }
1409
    }
1410
    return false;
1411
}
1412

1413
void SoFCSelectionRoot::checkSelection(bool &sel, SbColor &selColor, bool &hl, SbColor &hlColor) {
1414
    sel = false;
1415
    hl = false;
1416
    if((sel = !SelColorStack.empty()))
1417
        selColor = SelColorStack.back();
1418
    if((hl = !HlColorStack.empty()))
1419
        hlColor = HlColorStack.back();
1420
}
1421

1422
void SoFCSelectionRoot::resetContext() {
1423
    contextMap.clear();
1424
}
1425

1426
void SoFCSelectionRoot::moveActionStack(SoAction *from, SoAction *to, bool erase) {
1427
    auto it = ActionStacks.find(from);
1428
    if(it == ActionStacks.end())
1429
        return;
1430
    auto &stack = ActionStacks[to];
1431
    assert(stack.empty());
1432
    stack.swap(it->second);
1433
    if(erase)
1434
        ActionStacks.erase(it);
1435
}
1436

1437
#define BEGIN_ACTION \
1438
    auto &stack = ActionStacks[action];\
1439
    if(ViewParams::instance()->getCoinCycleCheck() \
1440
        && !stack.nodeSet.insert(this).second) \
1441
    {\
1442
        std::time_t t = std::time(0);\
1443
        if(_CyclicLastReported < t) {\
1444
            _CyclicLastReported = t+5;\
1445
            FC_ERR("Cyclic scene graph: " << getName());\
1446
        }\
1447
        return;\
1448
    }\
1449
    stack.push_back(this);\
1450
    auto size = stack.size();
1451

1452
#define END_ACTION \
1453
    if(stack.size()!=size || stack.back()!=this)\
1454
        FC_ERR("action stack fault");\
1455
    else {\
1456
        stack.nodeSet.erase(this);\
1457
        stack.pop_back();\
1458
        if(stack.empty())\
1459
            ActionStacks.erase(action);\
1460
    }
1461

1462
void SoFCSelectionRoot::pick(SoPickAction * action) {
1463
    BEGIN_ACTION;
1464
    if(doActionPrivate(stack,action))
1465
        inherited::pick(action);
1466
    END_ACTION;
1467
}
1468

1469
void SoFCSelectionRoot::rayPick(SoRayPickAction * action) {
1470
    BEGIN_ACTION;
1471
    if(doActionPrivate(stack,action))
1472
        inherited::rayPick(action);
1473
    END_ACTION;
1474
}
1475

1476
void SoFCSelectionRoot::handleEvent(SoHandleEventAction * action) {
1477
    BEGIN_ACTION;
1478
    inherited::handleEvent(action);
1479
    END_ACTION;
1480
}
1481

1482
void SoFCSelectionRoot::search(SoSearchAction * action) {
1483
    BEGIN_ACTION;
1484
    inherited::search(action);
1485
    END_ACTION;
1486
}
1487

1488
void SoFCSelectionRoot::getPrimitiveCount(SoGetPrimitiveCountAction * action) {
1489
    BEGIN_ACTION;
1490
    inherited::getPrimitiveCount(action);
1491
    END_ACTION;
1492
}
1493

1494
void SoFCSelectionRoot::getBoundingBox(SoGetBoundingBoxAction * action)
1495
{
1496
    BEGIN_ACTION;
1497
    if(doActionPrivate(stack,action))
1498
        inherited::getBoundingBox(action);
1499
    END_ACTION;
1500
}
1501

1502
void SoFCSelectionRoot::getMatrix(SoGetMatrixAction * action) {
1503
    BEGIN_ACTION;
1504
    if(doActionPrivate(stack,action))
1505
        inherited::getMatrix(action);
1506
    END_ACTION;
1507
}
1508

1509
void SoFCSelectionRoot::callback(SoCallbackAction *action) {
1510
    BEGIN_ACTION;
1511
    inherited::callback(action);
1512
    END_ACTION;
1513
}
1514

1515
void SoFCSelectionRoot::doAction(SoAction *action) {
1516
    BEGIN_ACTION
1517
    if(doActionPrivate(stack,action))
1518
        inherited::doAction(action);
1519
    END_ACTION
1520
}
1521

1522
bool SoFCSelectionRoot::doActionPrivate(Stack &stack, SoAction *action) {
1523
    // Selection action short-circuit optimization. In case of whole object
1524
    // selection/pre-selection, we shall store a SelContext keyed by ourself.
1525
    // And the action traversal can be short-curcuited once the first targeted
1526
    // SoFCSelectionRoot is found here. New function checkSelection() is exposed
1527
    // to check for whole object selection. This greatly improve performance on
1528
    // large group.
1529

1530
    SelContextPtr ctx2;
1531
    bool ctx2Searched = false;
1532
    bool isTail = false;
1533
    if(action->getCurPathCode()==SoAction::IN_PATH) {
1534
        auto path = action->getPathAppliedTo();
1535
        if(path) {
1536
            isTail = path->getTail()==this ||
1537
                     (path->getLength()>1
1538
                      && path->getNodeFromTail(1)==this
1539
                      && path->getTail()->isOfType(SoSwitch::getClassTypeId()));
1540
        }
1541

1542
        if(!action->isOfType(SoSelectionElementAction::getClassTypeId())) {
1543
            ctx2Searched = true;
1544
            ctx2 = std::static_pointer_cast<SelContext>(getNodeContext2(stack,this,SelContext::merge));
1545
            if(ctx2 && ctx2->hideAll)
1546
                return false;
1547
        }
1548
        if(!isTail)
1549
            return true;
1550
    }else if(action->getWhatAppliedTo()!=SoAction::NODE && action->getCurPathCode()!=SoAction::BELOW_PATH)
1551
        return true;
1552

1553
    if(action->isOfType(SoSelectionElementAction::getClassTypeId())) {
1554
        auto selAction = static_cast<SoSelectionElementAction*>(action);
1555
        if(selAction->isSecondary()) {
1556
            if(selAction->getType() == SoSelectionElementAction::Show ||
1557
               (selAction->getType() == SoSelectionElementAction::Color &&
1558
                selAction->getColors().empty() &&
1559
                action->getWhatAppliedTo()==SoAction::NODE))
1560
            {
1561
                auto ctx = getActionContext(action,this,SelContextPtr(),false);
1562
                if(ctx && ctx->hideAll) {
1563
                    ctx->hideAll = false;
1564
                    if(!ctx->hlAll && !ctx->selAll)
1565
                        removeActionContext(action,this);
1566
                    touch();
1567
                }
1568
                // applied to a node means clear all visibility setting, so
1569
                // return true to propagate the action
1570
                return selAction->getType()==SoSelectionElementAction::Color ||
1571
                       action->getWhatAppliedTo()==SoAction::NODE;
1572

1573
            }else if(selAction->getType() == SoSelectionElementAction::Hide) {
1574
                if(action->getCurPathCode()==SoAction::BELOW_PATH || isTail) {
1575
                    auto ctx = getActionContext(action,this,SelContextPtr());
1576
                    if(ctx && !ctx->hideAll) {
1577
                        ctx->hideAll = true;
1578
                        touch();
1579
                    }
1580
                    return false;
1581
                }
1582
            }
1583
            return true;
1584
        }
1585

1586
        if(selAction->getType() == SoSelectionElementAction::None) {
1587
            if(action->getWhatAppliedTo() == SoAction::NODE) {
1588
                // Here the 'select none' action is applied to a node, and we
1589
                // are the first SoFCSelectionRoot encountered (which means all
1590
                // children stores selection context here, both whole object
1591
                // and element selection), then we can simply perform the
1592
                // action by clearing the selection context here, and save the
1593
                // time for traversing a potentially large amount of children
1594
                // nodes.
1595
                resetContext();
1596
                touch();
1597
                return false;
1598
            }
1599

1600
            auto ctx = getActionContext(action,this,SelContextPtr(),false);
1601
            if(ctx && ctx->selAll) {
1602
                ctx->selAll = false;
1603
                touch();
1604
                return false;
1605
            }
1606
        } else if(selAction->getType() == SoSelectionElementAction::All) {
1607
            auto ctx = getActionContext(action,this,SelContextPtr());
1608
            assert(ctx);
1609
            ctx->selAll = true;
1610
            ctx->selColor = selAction->getColor();
1611
            touch();
1612
            return false;
1613
        }
1614
        return true;
1615
    }
1616

1617
    if(action->isOfType(SoHighlightElementAction::getClassTypeId())) {
1618
        auto hlAction = static_cast<SoHighlightElementAction*>(action);
1619
        if(hlAction->isHighlighted()) {
1620
            if(hlAction->getElement()) {
1621
                auto ctx = getActionContext(action,this,SelContextPtr(),false);
1622
                if(ctx && ctx->hlAll) {
1623
                    ctx->hlAll = false;
1624
                    touch();
1625
                }
1626
            } else {
1627
                auto ctx = getActionContext(action,this,SelContextPtr());
1628
                assert(ctx);
1629
                ctx->hlAll = true;
1630
                ctx->hlColor = hlAction->getColor();
1631
                touch();
1632
                return false;
1633
            }
1634
        } else {
1635
            auto ctx = getActionContext(action,this,SelContextPtr(),false);
1636
            if(ctx && ctx->hlAll) {
1637
                ctx->hlAll = false;
1638
                touch();
1639
                return false;
1640
            }
1641
        }
1642
        return true;
1643
    }
1644

1645
    if(!ctx2Searched) {
1646
        ctx2 = std::static_pointer_cast<SelContext>(getNodeContext2(stack,this,SelContext::merge));
1647
        if(ctx2 && ctx2->hideAll)
1648
            return false;
1649
    }
1650
    return true;
1651
}
1652

1653
int SoFCSelectionRoot::SelContext::merge(int status, SoFCSelectionContextBasePtr &output,
1654
        SoFCSelectionContextBasePtr input, SoNode *)
1655
{
1656
    auto ctx = std::dynamic_pointer_cast<SelContext>(input);
1657
    if(ctx && ctx->hideAll) {
1658
        output = ctx;
1659
        return -1;
1660
    }
1661
    return status;
1662
}
1663

1664
/////////////////////////////////////////////////////////////////////////////
1665

1666
SO_NODE_SOURCE(SoFCPathAnnotation)
1667

1668
SoFCPathAnnotation::SoFCPathAnnotation()
1669
{
1670
    SO_NODE_CONSTRUCTOR(SoFCPathAnnotation);
1671
    path = nullptr;
1672
    tmpPath = nullptr;
1673
    det = nullptr;
1674
}
1675

1676
SoFCPathAnnotation::~SoFCPathAnnotation()
1677
{
1678
    if(path) path->unref();
1679
    if(tmpPath) tmpPath->unref();
1680
    delete det;
1681
}
1682

1683
void SoFCPathAnnotation::finish()
1684
{
1685
    atexit_cleanup();
1686
}
1687

1688
void SoFCPathAnnotation::initClass()
1689
{
1690
    SO_NODE_INIT_CLASS(SoFCPathAnnotation,SoSeparator,"Separator");
1691
}
1692

1693
void SoFCPathAnnotation::GLRender(SoGLRenderAction * action)
1694
{
1695
    switch (action->getCurPathCode()) {
1696
    case SoAction::NO_PATH:
1697
    case SoAction::BELOW_PATH:
1698
        this->GLRenderBelowPath(action);
1699
        break;
1700
    case SoAction::OFF_PATH:
1701
        break;
1702
    case SoAction::IN_PATH:
1703
        this->GLRenderInPath(action);
1704
        break;
1705
    }
1706
}
1707

1708
void SoFCPathAnnotation::GLRenderBelowPath(SoGLRenderAction * action)
1709
{
1710
    if(!path || !path->getLength() || !tmpPath || !tmpPath->getLength())
1711
        return;
1712

1713
    if(path->getLength() != tmpPath->getLength()) {
1714
        // The auditing SoPath may be truncated due to harmless things such as
1715
        // flipping a SoSwitch sibling node. So we keep an unauditing SoTempPath
1716
        // around to try to restore the path.
1717
        for(int i=path->getLength()-1;i<tmpPath->getLength()-1;++i) {
1718
            auto children = path->getNode(i)->getChildren();
1719
            if(children) {
1720
                int idx = children->find(tmpPath->getNode(i+1));
1721
                if(idx >= 0) {
1722
                    path->append(idx);
1723
                    continue;
1724
                }
1725
            }
1726
            tmpPath->unref();
1727
            tmpPath = nullptr;
1728
            return;
1729
        }
1730
    }
1731

1732
    SoState * state = action->getState();
1733
    SoGLCacheContextElement::shouldAutoCache(state, SoGLCacheContextElement::DONT_AUTO_CACHE);
1734

1735
    if (action->isRenderingDelayedPaths()) {
1736
        SbBool zbenabled = glIsEnabled(GL_DEPTH_TEST);
1737
        if (zbenabled) glDisable(GL_DEPTH_TEST);
1738

1739
        if(det)
1740
            inherited::GLRenderInPath(action);
1741
        else {
1742
            bool bbox = ViewParams::instance()->getShowSelectionBoundingBox();
1743
            if(!bbox) {
1744
                for(int i=0,count=path->getLength();i<count;++i) {
1745
                    if(!path->getNode(i)->isOfType(SoFCSelectionRoot::getClassTypeId()))
1746
                        continue;
1747
                    auto node = dynamic_cast<SoFCSelectionRoot*>(path->getNode(i));
1748
                    if (node != nullptr
1749
                        && node->selectionStyle.getValue() == SoFCSelectionRoot::Box) {
1750
                        bbox = true;
1751
                        break;
1752
                    }
1753
                }
1754
            }
1755
            if(!bbox)
1756
                inherited::GLRenderInPath(action);
1757
            else {
1758
                bool sel = false;
1759
                bool hl = false;
1760
                SbColor selColor,hlColor;
1761
                SoFCSelectionRoot::checkSelection(sel,selColor,hl,hlColor);
1762
                if(sel || hl)
1763
                    SoFCSelectionRoot::renderBBox(action,this,hl?hlColor:selColor);
1764
                else
1765
                    inherited::GLRenderInPath(action);
1766
            }
1767
        }
1768

1769
        if (zbenabled) glEnable(GL_DEPTH_TEST);
1770

1771
    } else {
1772
        SoCacheElement::invalidate(action->getState());
1773
        auto curPath = action->getCurPath();
1774
        auto newPath = new SoPath(curPath->getLength()+path->getLength());
1775
        newPath->append(curPath);
1776
        newPath->append(path);
1777
        action->addDelayedPath(newPath);
1778
    }
1779
}
1780

1781
void SoFCPathAnnotation::GLRenderInPath(SoGLRenderAction * action)
1782
{
1783
    GLRenderBelowPath(action);
1784
}
1785

1786
void SoFCPathAnnotation::setDetail(SoDetail *d) {
1787
    if(d!=det) {
1788
        delete det;
1789
        det = d;
1790
    }
1791
}
1792

1793
void SoFCPathAnnotation::setPath(SoPath *newPath) {
1794
    if(path) {
1795
        path->unref();
1796
        coinRemoveAllChildren(this);
1797
        path = nullptr;
1798
        if(tmpPath) {
1799
            tmpPath->unref();
1800
            tmpPath = nullptr;
1801
        }
1802
    }
1803
    if(!newPath || !newPath->getLength())
1804
        return;
1805

1806
    tmpPath = new SoTempPath(newPath->getLength());
1807
    tmpPath->ref();
1808
    for(int i=0;i<newPath->getLength();++i)
1809
        tmpPath->append(newPath->getNode(i));
1810
    path = newPath->copy();
1811
    path->ref();
1812
    addChild(path->getNode(0));
1813
}
1814

1815
void SoFCPathAnnotation::getBoundingBox(SoGetBoundingBoxAction * action)
1816
{
1817
    if(path) {
1818
        SoGetBoundingBoxAction bboxAction(action->getViewportRegion());
1819
        SoFCSelectionRoot::moveActionStack(action,&bboxAction,false);
1820
        bboxAction.apply(path);
1821
        SoFCSelectionRoot::moveActionStack(&bboxAction,action,true);
1822
        auto bbox = bboxAction.getBoundingBox();
1823
        if(!bbox.isEmpty())
1824
            action->extendBy(bbox);
1825
    }
1826
}
1827

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

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

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

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