FreeCAD

Форк
0
/
EditModeConstraintCoinManager.cpp 
2992 строки · 142.6 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2021 Abdullah Tahiri <abdullah.tahiri.yo@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
#ifndef _PreComp_
25
#include <QPainter>
26
#include <QRegularExpression>
27
#include <memory>
28

29
#include <Inventor/SbImage.h>
30
#include <Inventor/SbVec3f.h>
31
#include <Inventor/SoPickedPoint.h>
32
#include <Inventor/nodes/SoAnnotation.h>
33
#include <Inventor/nodes/SoDrawStyle.h>
34
#include <Inventor/nodes/SoGroup.h>
35
#include <Inventor/nodes/SoImage.h>
36
#include <Inventor/nodes/SoInfo.h>
37
#include <Inventor/nodes/SoMaterial.h>
38
#include <Inventor/nodes/SoMaterialBinding.h>
39
#include <Inventor/nodes/SoPickStyle.h>
40
#include <Inventor/nodes/SoSeparator.h>
41
#include <Inventor/nodes/SoTranslation.h>
42
#endif  // #ifndef _PreComp_
43

44
#include <Base/Converter.h>
45
#include <Base/Exception.h>
46
#include <Base/Tools.h>
47
#include <Base/UnitsApi.h>
48
#include <Base/Vector3D.h>
49
#include <Gui/BitmapFactory.h>
50
#include <Gui/Inventor/SmSwitchboard.h>
51
#include <Gui/SoDatumLabel.h>
52
#include <Gui/Tools.h>
53
#include <Gui/Utilities.h>
54
#include <Mod/Part/App/Geometry.h>
55
#include <Mod/Sketcher/App/Constraint.h>
56
#include <Mod/Sketcher/App/GeoEnum.h>
57
#include <Mod/Sketcher/App/GeoList.h>
58
#include <Mod/Sketcher/App/GeometryFacade.h>
59
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
60

61
#include "EditModeConstraintCoinManager.h"
62
#include "SoZoomTranslation.h"
63
#include "Utils.h"
64
#include "ViewProviderSketch.h"
65
#include "ViewProviderSketchCoinAttorney.h"
66

67

68
using namespace Gui;
69
using namespace SketcherGui;
70
using namespace Sketcher;
71

72
//**************************** EditModeConstraintCoinManager class ******************************
73

74
EditModeConstraintCoinManager::EditModeConstraintCoinManager(
75
    ViewProviderSketch& vp,
76
    DrawingParameters& drawingParams,
77
    GeometryLayerParameters& geometryLayerParams,
78
    ConstraintParameters& constraintParams,
79
    EditModeScenegraphNodes& editModeScenegraph,
80
    CoinMapping& coinMap)
81
    : viewProvider(vp)
82
    , drawingParameters(drawingParams)
83
    , geometryLayerParameters(geometryLayerParams)
84
    , constraintParameters(constraintParams)
85
    , editModeScenegraphNodes(editModeScenegraph)
86
    , coinMapping(coinMap)
87
{}
88

89
EditModeConstraintCoinManager::~EditModeConstraintCoinManager()
90
{}
91

92
void EditModeConstraintCoinManager::updateVirtualSpace()
93
{
94
    const std::vector<Sketcher::Constraint*>& constrlist =
95
        ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
96

97
    bool isshownvirtualspace = ViewProviderSketchCoinAttorney::isShownVirtualSpace(viewProvider);
98

99
    if (constrlist.size() == vConstrType.size()) {
100

101
        editModeScenegraphNodes.constrGroup->enable.setNum(constrlist.size());
102

103
        SbBool* sws = editModeScenegraphNodes.constrGroup->enable.startEditing();
104

105
        for (size_t i = 0; i < constrlist.size(); i++) {
106
            sws[i] = !(constrlist[i]->isInVirtualSpace
107
                       != isshownvirtualspace);  // XOR of constraint mode and VP mode
108
        }
109

110

111
        editModeScenegraphNodes.constrGroup->enable.finishEditing();
112
    }
113
}
114

115
void EditModeConstraintCoinManager::processConstraints(const GeoListFacade& geolistfacade)
116
{
117
    const auto& constrlist = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
118

119
    auto zConstrH = ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider)
120
        * drawingParameters.zConstr;
121

122
    // After an undo/redo it can happen that we have an empty geometry list but a non-empty
123
    // constraint list In this case just ignore the constraints. (See bug #0000421)
124
    if (geolistfacade.geomlist.size() <= 2 && !constrlist.empty()) {
125
        rebuildConstraintNodes(geolistfacade);
126
        return;
127
    }
128

129
    int extGeoCount = geolistfacade.getExternalCount();
130
    int intGeoCount = geolistfacade.getInternalCount();
131

132
    // reset point if the constraint type has changed
133
Restart:
134
    // check if a new constraint arrived
135
    if (constrlist.size() != vConstrType.size()) {
136
        rebuildConstraintNodes(geolistfacade);
137
    }
138

139
    assert(int(constrlist.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
140
    assert(int(vConstrType.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
141

142
    // update the virtual space
143
    updateVirtualSpace();
144

145
    auto getNormal = [](const GeoListFacade& geolistfacade,
146
                        const int geoid,
147
                        const Base::Vector3d& pointoncurve) {
148
        auto geom = geolistfacade.getGeometryFromGeoId(geoid);
149
        auto curve = dynamic_cast<const Part::GeomCurve*>(geom);
150

151
        auto line = dynamic_cast<const Part::GeomLineSegment*>(curve);
152

153
        if (line) {
154
            Base::Vector3d linedir = line->getEndPoint() - line->getStartPoint();
155
            return Base::Vector3d(-linedir.y, linedir.x, 0);
156
        }
157
        else {
158
            Base::Vector3d normal;
159
            try {
160
                if (!(curve && curve->normalAt(pointoncurve, normal))) {
161
                    normal = Base::Vector3d(1, 0, 0);
162
                }
163
            }
164
            catch (const Base::CADKernelError&) {
165
                normal = Base::Vector3d(1, 0, 0);
166
            }
167

168
            return normal;
169
        }
170
    };
171

172
    // go through the constraints and update the position
173
    int i = 0;
174
    for (std::vector<Sketcher::Constraint*>::const_iterator it = constrlist.begin();
175
         it != constrlist.end();
176
         ++it, i++) {
177
        // check if the type has changed
178
        if ((*it)->Type != vConstrType[i]) {
179
            // clearing the type vector will force a rebuild of the visual nodes
180
            vConstrType.clear();
181
            // TODO: The 'goto' here is unsafe as it can happen that we cause an endless loop (see
182
            // bug #0001956).
183
            goto Restart;
184
        }
185
        try {  // because calculateNormalAtPoint, used in there, can throw
186
            // root separator for this constraint
187
            SoSeparator* sep =
188
                static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
189
            const Constraint* Constr = *it;
190

191
            if (Constr->First < -extGeoCount || Constr->First >= intGeoCount
192
                || (Constr->Second != GeoEnum::GeoUndef
193
                    && (Constr->Second < -extGeoCount || Constr->Second >= intGeoCount))
194
                || (Constr->Third != GeoEnum::GeoUndef
195
                    && (Constr->Third < -extGeoCount || Constr->Third >= intGeoCount))) {
196
                // Constraint can refer to non-existent geometry during undo/redo
197
                continue;
198
            }
199

200
            // distinguish different constraint types to build up
201
            switch (Constr->Type) {
202
                case Block:
203
                case Horizontal:  // write the new position of the Horizontal constraint Same as
204
                                  // vertical position.
205
                case Vertical:    // write the new position of the Vertical constraint
206
                {
207
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
208
                    bool alignment = Constr->Type != Block && Constr->Second != GeoEnum::GeoUndef;
209

210
                    // get the geometry
211
                    const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
212

213
                    if (!alignment) {
214
                        // Vertical & Horiz can only be a GeomLineSegment, but Blocked can be
215
                        // anything.
216
                        Base::Vector3d midpos;
217
                        Base::Vector3d dir;
218
                        Base::Vector3d norm;
219

220
                        if (geo->is<Part::GeomLineSegment>()) {
221
                            const Part::GeomLineSegment* lineSeg =
222
                                static_cast<const Part::GeomLineSegment*>(geo);
223

224
                            // calculate the half distance between the start and endpoint
225
                            midpos = ((lineSeg->getEndPoint() + lineSeg->getStartPoint()) / 2);
226

227
                            // Get a set of vectors perpendicular and tangential to these
228
                            dir = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Normalize();
229

230
                            norm = Base::Vector3d(-dir.y, dir.x, 0);
231
                        }
232
                        else if (geo->is<Part::GeomBSplineCurve>()) {
233
                            const Part::GeomBSplineCurve* bsp =
234
                                static_cast<const Part::GeomBSplineCurve*>(geo);
235
                            midpos = Base::Vector3d(0, 0, 0);
236

237
                            std::vector<Base::Vector3d> poles = bsp->getPoles();
238

239
                            // Move center of gravity towards start not to collide with bspline
240
                            // degree information.
241
                            double ws = 1.0 / poles.size();
242
                            double w = 1.0;
243

244
                            for (std::vector<Base::Vector3d>::iterator it = poles.begin();
245
                                 it != poles.end();
246
                                 ++it) {
247
                                midpos += w * (*it);
248
                                w -= ws;
249
                            }
250

251
                            midpos /= poles.size();
252

253
                            dir = (bsp->getEndPoint() - bsp->getStartPoint()).Normalize();
254
                            norm = Base::Vector3d(-dir.y, dir.x, 0);
255
                        }
256
                        else {
257
                            double ra = 0, rb = 0;
258
                            double angle,        // rotation of object as a whole
259
                                angleplus = 0.;  // arc angle (t parameter for ellipses)
260

261
                            if (geo->is<Part::GeomCircle>()) {
262
                                const Part::GeomCircle* circle =
263
                                    static_cast<const Part::GeomCircle*>(geo);
264
                                ra = circle->getRadius();
265
                                angle = M_PI / 4;
266
                                midpos = circle->getCenter();
267
                            }
268
                            else if (geo->is<Part::GeomArcOfCircle>()) {
269
                                const Part::GeomArcOfCircle* arc =
270
                                    static_cast<const Part::GeomArcOfCircle*>(geo);
271
                                ra = arc->getRadius();
272
                                double startangle, endangle;
273
                                arc->getRange(startangle, endangle, /*emulateCCW=*/true);
274
                                angle = (startangle + endangle) / 2;
275
                                midpos = arc->getCenter();
276
                            }
277
                            else if (geo->is<Part::GeomEllipse>()) {
278
                                const Part::GeomEllipse* ellipse =
279
                                    static_cast<const Part::GeomEllipse*>(geo);
280
                                ra = ellipse->getMajorRadius();
281
                                rb = ellipse->getMinorRadius();
282
                                Base::Vector3d majdir = ellipse->getMajorAxisDir();
283
                                angle = atan2(majdir.y, majdir.x);
284
                                angleplus = M_PI / 4;
285
                                midpos = ellipse->getCenter();
286
                            }
287
                            else if (geo->is<Part::GeomArcOfEllipse>()) {
288
                                const Part::GeomArcOfEllipse* aoe =
289
                                    static_cast<const Part::GeomArcOfEllipse*>(geo);
290
                                ra = aoe->getMajorRadius();
291
                                rb = aoe->getMinorRadius();
292
                                double startangle, endangle;
293
                                aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
294
                                Base::Vector3d majdir = aoe->getMajorAxisDir();
295
                                angle = atan2(majdir.y, majdir.x);
296
                                angleplus = (startangle + endangle) / 2;
297
                                midpos = aoe->getCenter();
298
                            }
299
                            else if (geo->is<Part::GeomArcOfHyperbola>()) {
300
                                const Part::GeomArcOfHyperbola* aoh =
301
                                    static_cast<const Part::GeomArcOfHyperbola*>(geo);
302
                                ra = aoh->getMajorRadius();
303
                                rb = aoh->getMinorRadius();
304
                                double startangle, endangle;
305
                                aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
306
                                Base::Vector3d majdir = aoh->getMajorAxisDir();
307
                                angle = atan2(majdir.y, majdir.x);
308
                                angleplus = (startangle + endangle) / 2;
309
                                midpos = aoh->getCenter();
310
                            }
311
                            else if (geo->is<Part::GeomArcOfParabola>()) {
312
                                const Part::GeomArcOfParabola* aop =
313
                                    static_cast<const Part::GeomArcOfParabola*>(geo);
314
                                ra = aop->getFocal();
315
                                double startangle, endangle;
316
                                aop->getRange(startangle, endangle, /*emulateCCW=*/true);
317
                                Base::Vector3d majdir = -aop->getXAxisDir();
318
                                angle = atan2(majdir.y, majdir.x);
319
                                angleplus = (startangle + endangle) / 2;
320
                                midpos = aop->getFocus();
321
                            }
322
                            else {
323
                                break;
324
                            }
325

326
                            if (geo->is<Part::GeomEllipse>() || geo->is<Part::GeomArcOfEllipse>()
327
                                || geo->is<Part::GeomArcOfHyperbola>()) {
328

329
                                Base::Vector3d majDir, minDir, rvec;
330
                                majDir = Base::Vector3d(cos(angle),
331
                                                        sin(angle),
332
                                                        0);  // direction of major axis of ellipse
333
                                minDir = Base::Vector3d(-majDir.y,
334
                                                        majDir.x,
335
                                                        0);  // direction of minor axis of ellipse
336
                                rvec =
337
                                    (ra * cos(angleplus)) * majDir + (rb * sin(angleplus)) * minDir;
338
                                midpos += rvec;
339
                                rvec.Normalize();
340
                                norm = rvec;
341
                                dir = Base::Vector3d(
342
                                    -rvec.y,
343
                                    rvec.x,
344
                                    0);  // DeepSOIC: I'm not sure what dir is supposed to mean.
345
                            }
346
                            else {
347
                                norm = Base::Vector3d(cos(angle), sin(angle), 0);
348
                                dir = Base::Vector3d(-norm.y, norm.x, 0);
349
                                midpos += ra * norm;
350
                            }
351
                        }
352

353
                        Base::Vector3d relpos = seekConstraintPosition(
354
                            midpos,
355
                            norm,
356
                            dir,
357
                            2.5,
358
                            editModeScenegraphNodes.constrGroup->getChild(i));
359

360
                        auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
361
                            static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
362

363
                        translation->abPos =
364
                            SbVec3f(midpos.x, midpos.y, zConstrH);  // Absolute Reference
365

366
                        // Reference Position that is scaled according to zoom
367
                        translation->translation = SbVec3f(relpos.x, relpos.y, 0);
368
                    }
369
                    else {
370
                        assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
371
                        assert(Constr->FirstPos != Sketcher::PointPos::none
372
                               && Constr->SecondPos != Sketcher::PointPos::none);
373

374
                        Base::Vector3d midpos1, dir1, norm1;
375
                        Base::Vector3d midpos2, dir2, norm2;
376

377
                        midpos1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
378
                        midpos2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
379

380
                        dir1 = (midpos2 - midpos1).Normalize();
381
                        dir2 = -dir1;
382
                        norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.);
383
                        norm2 = norm1;
384

385
                        Base::Vector3d relpos1 = seekConstraintPosition(
386
                            midpos1,
387
                            norm1,
388
                            dir1,
389
                            4.0,
390
                            editModeScenegraphNodes.constrGroup->getChild(i));
391

392
                        auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
393
                            static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
394

395
                        translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
396
                        translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
397

398
                        Base::Vector3d relpos2 = seekConstraintPosition(
399
                            midpos2,
400
                            norm2,
401
                            dir2,
402
                            4.0,
403
                            editModeScenegraphNodes.constrGroup->getChild(i));
404

405
                        Base::Vector3d secondPos = midpos2 - midpos1;
406

407
                        translation = static_cast<SoZoomTranslation*>(sep->getChild(
408
                            static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
409

410
                        translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH);
411
                        translation->translation =
412
                            SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
413
                    }
414
                } break;
415
                case Perpendicular: {
416
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
417
                    assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
418
                    // get the geometry
419
                    const Part::Geometry* geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
420
                    const Part::Geometry* geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
421
                    Base::Vector3d midpos1, dir1, norm1;
422
                    Base::Vector3d midpos2, dir2, norm2;
423
                    bool twoIcons = false;  // a very local flag. It's set to true to indicate that
424
                                            // the second dir+norm are valid and should be used
425

426
                    if (Constr->Third != GeoEnum::GeoUndef ||  // perpty via point
427
                        Constr->FirstPos
428
                            != Sketcher::PointPos::none) {  // endpoint-to-curve or
429
                                                            // endpoint-to-endpoint perpty
430

431
                        int ptGeoId;
432
                        Sketcher::PointPos ptPosId;
433
                        do {  // dummy loop to use break =) Maybe goto?
434
                            ptGeoId = Constr->First;
435
                            ptPosId = Constr->FirstPos;
436
                            if (ptPosId != Sketcher::PointPos::none) {
437
                                break;
438
                            }
439
                            ptGeoId = Constr->Second;
440
                            ptPosId = Constr->SecondPos;
441
                            if (ptPosId != Sketcher::PointPos::none) {
442
                                break;
443
                            }
444
                            ptGeoId = Constr->Third;
445
                            ptPosId = Constr->ThirdPos;
446
                            if (ptPosId != Sketcher::PointPos::none) {
447
                                break;
448
                            }
449
                            assert(0);  // no point found!
450
                        } while (false);
451

452
                        midpos1 = geolistfacade.getPoint(ptGeoId, ptPosId);
453

454
                        norm1 = getNormal(geolistfacade, Constr->Second, midpos1);
455

456
                        // TODO: Check the method above. This was the old one making use of the
457
                        // solver.
458
                        // norm1 = getSolvedSketch().calculateNormalAtPoint(Constr->Second,
459
                        // midpos1.x, midpos1.y);
460

461
                        norm1.Normalize();
462
                        dir1 = norm1;
463
                        dir1.RotateZ(-M_PI / 2.0);
464
                    }
465
                    else if (Constr->FirstPos == Sketcher::PointPos::none) {
466

467
                        if (geo1->is<Part::GeomLineSegment>()) {
468
                            const Part::GeomLineSegment* lineSeg1 =
469
                                static_cast<const Part::GeomLineSegment*>(geo1);
470
                            midpos1 = ((lineSeg1->getEndPoint() + lineSeg1->getStartPoint()) / 2);
471
                            dir1 =
472
                                (lineSeg1->getEndPoint() - lineSeg1->getStartPoint()).Normalize();
473
                            norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.);
474
                        }
475
                        else if (geo1->is<Part::GeomArcOfCircle>()) {
476
                            const Part::GeomArcOfCircle* arc =
477
                                static_cast<const Part::GeomArcOfCircle*>(geo1);
478
                            double startangle, endangle, midangle;
479
                            arc->getRange(startangle, endangle, /*emulateCCW=*/true);
480
                            midangle = (startangle + endangle) / 2;
481
                            norm1 = Base::Vector3d(cos(midangle), sin(midangle), 0);
482
                            dir1 = Base::Vector3d(-norm1.y, norm1.x, 0);
483
                            midpos1 = arc->getCenter() + arc->getRadius() * norm1;
484
                        }
485
                        else if (geo1->is<Part::GeomCircle>()) {
486
                            const Part::GeomCircle* circle =
487
                                static_cast<const Part::GeomCircle*>(geo1);
488
                            norm1 = Base::Vector3d(cos(M_PI / 4), sin(M_PI / 4), 0);
489
                            dir1 = Base::Vector3d(-norm1.y, norm1.x, 0);
490
                            midpos1 = circle->getCenter() + circle->getRadius() * norm1;
491
                        }
492
                        else {
493
                            break;
494
                        }
495

496
                        if (geo2->is<Part::GeomLineSegment>()) {
497
                            const Part::GeomLineSegment* lineSeg2 =
498
                                static_cast<const Part::GeomLineSegment*>(geo2);
499
                            midpos2 = ((lineSeg2->getEndPoint() + lineSeg2->getStartPoint()) / 2);
500
                            dir2 =
501
                                (lineSeg2->getEndPoint() - lineSeg2->getStartPoint()).Normalize();
502
                            norm2 = Base::Vector3d(-dir2.y, dir2.x, 0.);
503
                        }
504
                        else if (geo2->is<Part::GeomArcOfCircle>()) {
505
                            const Part::GeomArcOfCircle* arc =
506
                                static_cast<const Part::GeomArcOfCircle*>(geo2);
507
                            double startangle, endangle, midangle;
508
                            arc->getRange(startangle, endangle, /*emulateCCW=*/true);
509
                            midangle = (startangle + endangle) / 2;
510
                            norm2 = Base::Vector3d(cos(midangle), sin(midangle), 0);
511
                            dir2 = Base::Vector3d(-norm2.y, norm2.x, 0);
512
                            midpos2 = arc->getCenter() + arc->getRadius() * norm2;
513
                        }
514
                        else if (geo2->is<Part::GeomCircle>()) {
515
                            const Part::GeomCircle* circle =
516
                                static_cast<const Part::GeomCircle*>(geo2);
517
                            norm2 = Base::Vector3d(cos(M_PI / 4), sin(M_PI / 4), 0);
518
                            dir2 = Base::Vector3d(-norm2.y, norm2.x, 0);
519
                            midpos2 = circle->getCenter() + circle->getRadius() * norm2;
520
                        }
521
                        else {
522
                            break;
523
                        }
524
                        twoIcons = true;
525
                    }
526

527
                    Base::Vector3d relpos1 =
528
                        seekConstraintPosition(midpos1,
529
                                               norm1,
530
                                               dir1,
531
                                               4.0,
532
                                               editModeScenegraphNodes.constrGroup->getChild(i));
533

534
                    auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
535
                        static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
536

537
                    translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
538
                    translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
539

540
                    if (twoIcons) {
541
                        Base::Vector3d relpos2 = seekConstraintPosition(
542
                            midpos2,
543
                            norm2,
544
                            dir2,
545
                            4.0,
546
                            editModeScenegraphNodes.constrGroup->getChild(i));
547

548
                        Base::Vector3d secondPos = midpos2 - midpos1;
549
                        auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
550
                            static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
551
                        translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH);
552
                        translation->translation =
553
                            SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
554
                    }
555

556
                } break;
557
                case Parallel:
558
                case Equal: {
559
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
560
                    assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
561
                    // get the geometry
562
                    const Part::Geometry* geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
563
                    const Part::Geometry* geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
564

565
                    Base::Vector3d midpos1, dir1, norm1;
566
                    Base::Vector3d midpos2, dir2, norm2;
567
                    if (!geo1->is<Part::GeomLineSegment>() || !geo2->is<Part::GeomLineSegment>()) {
568
                        if (Constr->Type == Equal) {
569
                            double r1a = 0, r1b = 0, r2a = 0, r2b = 0;
570
                            double angle1,
571
                                angle1plus = 0., angle2,
572
                                angle2plus =
573
                                    0.;  // angle1 = rotation of object as a whole; angle1plus = arc
574
                                         // angle (t parameter for ellipses).
575
                            if (geo1->is<Part::GeomCircle>()) {
576
                                const Part::GeomCircle* circle =
577
                                    static_cast<const Part::GeomCircle*>(geo1);
578
                                r1a = circle->getRadius();
579
                                angle1 = M_PI / 4;
580
                                midpos1 = circle->getCenter();
581
                            }
582
                            else if (geo1->is<Part::GeomArcOfCircle>()) {
583
                                const Part::GeomArcOfCircle* arc =
584
                                    static_cast<const Part::GeomArcOfCircle*>(geo1);
585
                                r1a = arc->getRadius();
586
                                double startangle, endangle;
587
                                arc->getRange(startangle, endangle, /*emulateCCW=*/true);
588
                                angle1 = (startangle + endangle) / 2;
589
                                midpos1 = arc->getCenter();
590
                            }
591
                            else if (geo1->is<Part::GeomEllipse>()) {
592
                                const Part::GeomEllipse* ellipse =
593
                                    static_cast<const Part::GeomEllipse*>(geo1);
594
                                r1a = ellipse->getMajorRadius();
595
                                r1b = ellipse->getMinorRadius();
596
                                Base::Vector3d majdir = ellipse->getMajorAxisDir();
597
                                angle1 = atan2(majdir.y, majdir.x);
598
                                angle1plus = M_PI / 4;
599
                                midpos1 = ellipse->getCenter();
600
                            }
601
                            else if (geo1->is<Part::GeomArcOfEllipse>()) {
602
                                const Part::GeomArcOfEllipse* aoe =
603
                                    static_cast<const Part::GeomArcOfEllipse*>(geo1);
604
                                r1a = aoe->getMajorRadius();
605
                                r1b = aoe->getMinorRadius();
606
                                double startangle, endangle;
607
                                aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
608
                                Base::Vector3d majdir = aoe->getMajorAxisDir();
609
                                angle1 = atan2(majdir.y, majdir.x);
610
                                angle1plus = (startangle + endangle) / 2;
611
                                midpos1 = aoe->getCenter();
612
                            }
613
                            else if (geo1->is<Part::GeomArcOfHyperbola>()) {
614
                                const Part::GeomArcOfHyperbola* aoh =
615
                                    static_cast<const Part::GeomArcOfHyperbola*>(geo1);
616
                                r1a = aoh->getMajorRadius();
617
                                r1b = aoh->getMinorRadius();
618
                                double startangle, endangle;
619
                                aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
620
                                Base::Vector3d majdir = aoh->getMajorAxisDir();
621
                                angle1 = atan2(majdir.y, majdir.x);
622
                                angle1plus = (startangle + endangle) / 2;
623
                                midpos1 = aoh->getCenter();
624
                            }
625
                            else if (geo1->is<Part::GeomArcOfParabola>()) {
626
                                const Part::GeomArcOfParabola* aop =
627
                                    static_cast<const Part::GeomArcOfParabola*>(geo1);
628
                                r1a = aop->getFocal();
629
                                double startangle, endangle;
630
                                aop->getRange(startangle, endangle, /*emulateCCW=*/true);
631
                                Base::Vector3d majdir = -aop->getXAxisDir();
632
                                angle1 = atan2(majdir.y, majdir.x);
633
                                angle1plus = (startangle + endangle) / 2;
634
                                midpos1 = aop->getFocus();
635
                            }
636
                            else {
637
                                break;
638
                            }
639

640
                            if (geo2->is<Part::GeomCircle>()) {
641
                                const Part::GeomCircle* circle =
642
                                    static_cast<const Part::GeomCircle*>(geo2);
643
                                r2a = circle->getRadius();
644
                                angle2 = M_PI / 4;
645
                                midpos2 = circle->getCenter();
646
                            }
647
                            else if (geo2->is<Part::GeomArcOfCircle>()) {
648
                                const Part::GeomArcOfCircle* arc =
649
                                    static_cast<const Part::GeomArcOfCircle*>(geo2);
650
                                r2a = arc->getRadius();
651
                                double startangle, endangle;
652
                                arc->getRange(startangle, endangle, /*emulateCCW=*/true);
653
                                angle2 = (startangle + endangle) / 2;
654
                                midpos2 = arc->getCenter();
655
                            }
656
                            else if (geo2->is<Part::GeomEllipse>()) {
657
                                const Part::GeomEllipse* ellipse =
658
                                    static_cast<const Part::GeomEllipse*>(geo2);
659
                                r2a = ellipse->getMajorRadius();
660
                                r2b = ellipse->getMinorRadius();
661
                                Base::Vector3d majdir = ellipse->getMajorAxisDir();
662
                                angle2 = atan2(majdir.y, majdir.x);
663
                                angle2plus = M_PI / 4;
664
                                midpos2 = ellipse->getCenter();
665
                            }
666
                            else if (geo2->is<Part::GeomArcOfEllipse>()) {
667
                                const Part::GeomArcOfEllipse* aoe =
668
                                    static_cast<const Part::GeomArcOfEllipse*>(geo2);
669
                                r2a = aoe->getMajorRadius();
670
                                r2b = aoe->getMinorRadius();
671
                                double startangle, endangle;
672
                                aoe->getRange(startangle, endangle, /*emulateCCW=*/true);
673
                                Base::Vector3d majdir = aoe->getMajorAxisDir();
674
                                angle2 = atan2(majdir.y, majdir.x);
675
                                angle2plus = (startangle + endangle) / 2;
676
                                midpos2 = aoe->getCenter();
677
                            }
678
                            else if (geo2->is<Part::GeomArcOfHyperbola>()) {
679
                                const Part::GeomArcOfHyperbola* aoh =
680
                                    static_cast<const Part::GeomArcOfHyperbola*>(geo2);
681
                                r2a = aoh->getMajorRadius();
682
                                r2b = aoh->getMinorRadius();
683
                                double startangle, endangle;
684
                                aoh->getRange(startangle, endangle, /*emulateCCW=*/true);
685
                                Base::Vector3d majdir = aoh->getMajorAxisDir();
686
                                angle2 = atan2(majdir.y, majdir.x);
687
                                angle2plus = (startangle + endangle) / 2;
688
                                midpos2 = aoh->getCenter();
689
                            }
690
                            else if (geo2->is<Part::GeomArcOfParabola>()) {
691
                                const Part::GeomArcOfParabola* aop =
692
                                    static_cast<const Part::GeomArcOfParabola*>(geo2);
693
                                r2a = aop->getFocal();
694
                                double startangle, endangle;
695
                                aop->getRange(startangle, endangle, /*emulateCCW=*/true);
696
                                Base::Vector3d majdir = -aop->getXAxisDir();
697
                                angle2 = atan2(majdir.y, majdir.x);
698
                                angle2plus = (startangle + endangle) / 2;
699
                                midpos2 = aop->getFocus();
700
                            }
701
                            else {
702
                                break;
703
                            }
704

705
                            if (geo1->is<Part::GeomEllipse>() || geo1->is<Part::GeomArcOfEllipse>()
706
                                || geo1->is<Part::GeomArcOfHyperbola>()) {
707

708
                                Base::Vector3d majDir, minDir, rvec;
709
                                majDir = Base::Vector3d(cos(angle1),
710
                                                        sin(angle1),
711
                                                        0);  // direction of major axis of ellipse
712
                                minDir = Base::Vector3d(-majDir.y,
713
                                                        majDir.x,
714
                                                        0);  // direction of minor axis of ellipse
715
                                rvec = (r1a * cos(angle1plus)) * majDir
716
                                    + (r1b * sin(angle1plus)) * minDir;
717
                                midpos1 += rvec;
718
                                rvec.Normalize();
719
                                norm1 = rvec;
720
                                dir1 = Base::Vector3d(
721
                                    -rvec.y,
722
                                    rvec.x,
723
                                    0);  // DeepSOIC: I'm not sure what dir is supposed to mean.
724
                            }
725
                            else {
726
                                norm1 = Base::Vector3d(cos(angle1), sin(angle1), 0);
727
                                dir1 = Base::Vector3d(-norm1.y, norm1.x, 0);
728
                                midpos1 += r1a * norm1;
729
                            }
730

731

732
                            if (geo2->is<Part::GeomEllipse>() || geo2->is<Part::GeomArcOfEllipse>()
733
                                || geo2->is<Part::GeomArcOfHyperbola>()) {
734

735
                                Base::Vector3d majDir, minDir, rvec;
736
                                majDir = Base::Vector3d(cos(angle2),
737
                                                        sin(angle2),
738
                                                        0);  // direction of major axis of ellipse
739
                                minDir = Base::Vector3d(-majDir.y,
740
                                                        majDir.x,
741
                                                        0);  // direction of minor axis of ellipse
742
                                rvec = (r2a * cos(angle2plus)) * majDir
743
                                    + (r2b * sin(angle2plus)) * minDir;
744
                                midpos2 += rvec;
745
                                rvec.Normalize();
746
                                norm2 = rvec;
747
                                dir2 = Base::Vector3d(-rvec.y, rvec.x, 0);
748
                            }
749
                            else {
750
                                norm2 = Base::Vector3d(cos(angle2), sin(angle2), 0);
751
                                dir2 = Base::Vector3d(-norm2.y, norm2.x, 0);
752
                                midpos2 += r2a * norm2;
753
                            }
754
                        }
755
                        else {  // Parallel can only apply to a GeomLineSegment
756
                            break;
757
                        }
758
                    }
759
                    else {
760
                        const Part::GeomLineSegment* lineSeg1 =
761
                            static_cast<const Part::GeomLineSegment*>(geo1);
762
                        const Part::GeomLineSegment* lineSeg2 =
763
                            static_cast<const Part::GeomLineSegment*>(geo2);
764

765
                        // calculate the half distance between the start and endpoint
766
                        midpos1 = ((lineSeg1->getEndPoint() + lineSeg1->getStartPoint()) / 2);
767
                        midpos2 = ((lineSeg2->getEndPoint() + lineSeg2->getStartPoint()) / 2);
768
                        // Get a set of vectors perpendicular and tangential to these
769
                        dir1 = (lineSeg1->getEndPoint() - lineSeg1->getStartPoint()).Normalize();
770
                        dir2 = (lineSeg2->getEndPoint() - lineSeg2->getStartPoint()).Normalize();
771
                        norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.);
772
                        norm2 = Base::Vector3d(-dir2.y, dir2.x, 0.);
773
                    }
774

775
                    Base::Vector3d relpos1 =
776
                        seekConstraintPosition(midpos1,
777
                                               norm1,
778
                                               dir1,
779
                                               4.0,
780
                                               editModeScenegraphNodes.constrGroup->getChild(i));
781
                    Base::Vector3d relpos2 =
782
                        seekConstraintPosition(midpos2,
783
                                               norm2,
784
                                               dir2,
785
                                               4.0,
786
                                               editModeScenegraphNodes.constrGroup->getChild(i));
787

788
                    auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
789
                        static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
790

791
                    translation->abPos =
792
                        SbVec3f(midpos1.x, midpos1.y, zConstrH);  // Absolute Reference
793

794
                    // Reference Position that is scaled according to zoom
795
                    translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
796

797
                    Base::Vector3d secondPos = midpos2 - midpos1;
798

799
                    translation = static_cast<SoZoomTranslation*>(sep->getChild(
800
                        static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
801

802
                    translation->abPos =
803
                        SbVec3f(secondPos.x, secondPos.y, zConstrH);  // Absolute Reference
804

805
                    // Reference Position that is scaled according to zoom
806
                    translation->translation =
807
                        SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
808

809
                } break;
810
                case Distance:
811
                case DistanceX:
812
                case DistanceY: {
813
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
814

815
                    double helperStartAngle1 = 0.;  // for arc helpers
816
                    double helperStartAngle2 = 0.;
817
                    double helperRange1 = 0.;
818
                    double helperRange2 = 0.;
819
                    double radius1 = 0.;
820
                    double radius2 = 0.;
821
                    Base::Vector3d center1(0., 0., 0.);
822
                    Base::Vector3d center2(0., 0., 0.);
823

824
                    int numPoints = 2;
825

826
                    // pnt1 will be initialized to (0,0,0) if only one point is given
827
                    auto pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
828

829
                    Base::Vector3d pnt2(0., 0., 0.);
830

831
                    if (Constr->SecondPos != Sketcher::PointPos::none) {
832
                        // point to point distance
833
                        pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
834
                    }
835
                    else if (Constr->Second != GeoEnum::GeoUndef) {
836
                        auto geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
837
                        auto geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
838
                        if (isLineSegment(*geo2)) {
839
                            // NOLINTNEXTLINE
840
                            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo2);
841
                            Base::Vector3d l2p1 = lineSeg->getStartPoint();
842
                            Base::Vector3d l2p2 = lineSeg->getEndPoint();
843

844
                            if (Constr->FirstPos != Sketcher::PointPos::none) {
845
                                // point to line distance
846
                                // calculate the projection of p1 onto lineSeg
847
                                pnt2.ProjectToLine(pnt1 - l2p1, l2p2 - l2p1);
848
                                pnt2 += pnt1;
849
                            }
850
                            else if (isCircleOrArc(*geo1)) {
851
                                // circular to line distance
852
                                auto [radius, ct] = getRadiusCenterCircleArc(geo1);
853
                                // project the center on the line (translated to origin)
854
                                pnt1.ProjectToLine(ct - l2p1, l2p2 - l2p1);
855
                                Base::Vector3d dir = pnt1;
856
                                dir.Normalize();
857
                                pnt1 += ct;
858
                                pnt2 = ct + dir * radius;
859
                            }
860
                        }
861
                        else if (isCircleOrArc(*geo2)) {
862
                            if (Constr->FirstPos != Sketcher::PointPos::none) {
863
                                // point to circular distance
864
                                auto [rad, ct] = getRadiusCenterCircleArc(geo2);
865

866
                                Base::Vector3d v = pnt1 - ct;
867
                                v = v.Normalize();
868
                                pnt2 = ct + rad * v;
869
                            }
870
                            else if (isCircleOrArc(*geo1)) {
871
                                // circular to circular distance
872
                                GetCirclesMinimalDistance(geo1, geo2, pnt1, pnt2);
873
                            }
874
                        }
875
                        else {
876
                            break;
877
                        }
878
                    }
879
                    else if (Constr->FirstPos != Sketcher::PointPos::none) {
880
                        // one point distance
881
                        pnt1 = Base::Vector3d(0., 0., 0.);
882
                        pnt2 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
883
                    }
884
                    else if (Constr->First != GeoEnum::GeoUndef) {
885
                        auto geo = geolistfacade.getGeometryFromGeoId(Constr->First);
886
                        if (isLineSegment(*geo)) {
887
                            // segment distance
888
                            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo);
889
                            pnt1 = lineSeg->getStartPoint();
890
                            pnt2 = lineSeg->getEndPoint();
891
                        }
892
                        else if (isArcOfCircle(*geo)) {
893
                            // arc length
894
                            auto arc = static_cast<const Part::GeomArcOfCircle*>(geo);
895
                            int index = static_cast<int>(ConstraintNodePosition::DatumLabelIndex);
896
                            auto* asciiText = static_cast<SoDatumLabel*>(sep->getChild(index));
897
                            center1 = arc->getCenter();
898
                            pnt1 = arc->getStartPoint();
899
                            pnt2 = arc->getEndPoint();
900

901
                            double startAngle, endAngle;
902
                            arc->getRange(startAngle, endAngle, /*emulateCCW=*/false);
903

904
                            asciiText->datumtype = SoDatumLabel::ARCLENGTH;
905
                            asciiText->param1 = Constr->LabelDistance;
906
                            asciiText->string =
907
                                SbString(std::string("◠ ")
908
                                             .append(getPresentationString(Constr).toUtf8())
909
                                             .c_str());
910

911
                            asciiText->pnts.setNum(3);
912
                            SbVec3f* verts = asciiText->pnts.startEditing();
913
                            verts[0] = SbVec3f(center1.x, center1.y, center1.z);
914
                            verts[1] = SbVec3f(pnt1.x, pnt1.y, pnt1.z);
915
                            verts[2] = SbVec3f(pnt2.x, pnt2.y, pnt2.z);
916
                            asciiText->pnts.finishEditing();
917
                            break;
918
                        }
919
                        else {
920
                            break;
921
                        }
922
                    }
923
                    else {
924
                        break;
925
                    }
926

927
                    int index = static_cast<int>(ConstraintNodePosition::DatumLabelIndex);
928
                    auto* asciiText = static_cast<SoDatumLabel*>(sep->getChild(index));  // NOLINT
929

930
                    // Get presentation string (w/o units if option is set)
931
                    asciiText->string =
932
                        SbString(getPresentationString(Constr).toUtf8().constData());
933

934
                    if (Constr->Type == Distance) {
935
                        asciiText->datumtype = SoDatumLabel::DISTANCE;
936
                    }
937
                    else if (Constr->Type == DistanceX) {
938
                        asciiText->datumtype = SoDatumLabel::DISTANCEX;
939
                    }
940
                    else if (Constr->Type == DistanceY) {
941
                        asciiText->datumtype = SoDatumLabel::DISTANCEY;
942
                    }
943

944
                    // Check if arc helpers are needed
945
                    if (Constr->Second != GeoEnum::GeoUndef) {
946
                        auto geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
947
                        auto geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
948

949
                        if (isArcOfCircle(*geo1) && Constr->FirstPos == Sketcher::PointPos::none) {
950
                            auto arc = static_cast<const Part::GeomArcOfCircle*>(geo1);  // NOLINT
951
                            radius1 = arc->getRadius();
952
                            center1 = arc->getCenter();
953

954
                            double angle =
955
                                toVector2d(isLineSegment(*geo2) ? pnt2 - center1 : pnt1 - center1)
956
                                    .Angle();
957
                            double startAngle, endAngle;
958
                            arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
959

960
                            findHelperAngles(helperStartAngle1,
961
                                             helperRange1,
962
                                             angle,
963
                                             startAngle,
964
                                             endAngle);
965

966
                            if (helperRange1 != 0.) {
967
                                // We override to draw the full helper as it does not look good
968
                                // otherwise We still use findHelperAngles before to find if helper
969
                                // is needed.
970
                                helperStartAngle1 = endAngle;
971
                                helperRange1 = 2 * M_PI - (endAngle - startAngle);
972

973
                                numPoints++;
974
                            }
975
                        }
976
                        if (isArcOfCircle(*geo2) && Constr->SecondPos == Sketcher::PointPos::none) {
977
                            auto arc = static_cast<const Part::GeomArcOfCircle*>(geo2);  // NOLINT
978
                            radius2 = arc->getRadius();
979
                            center2 = arc->getCenter();
980

981
                            double angle =
982
                                toVector2d(pnt2 - center2).Angle();  // between -pi and pi
983
                            double startAngle, endAngle;             // between 0 and 2*pi
984
                            arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
985

986
                            findHelperAngles(helperStartAngle2,
987
                                             helperRange2,
988
                                             angle,
989
                                             startAngle,
990
                                             endAngle);
991

992
                            if (helperRange2 != 0.) {
993
                                helperStartAngle2 = endAngle;
994
                                helperRange2 = 2 * M_PI - (endAngle - startAngle);
995

996
                                numPoints++;
997
                            }
998
                        }
999
                    }
1000

1001
                    // Assign the Datum Points
1002
                    asciiText->pnts.setNum(numPoints);
1003
                    SbVec3f* verts = asciiText->pnts.startEditing();
1004

1005
                    verts[0] = SbVec3f(pnt1.x, pnt1.y, zConstrH);
1006
                    verts[1] = SbVec3f(pnt2.x, pnt2.y, zConstrH);
1007

1008
                    if (numPoints > 2) {
1009
                        if (helperRange1 != 0.) {
1010
                            verts[2] = SbVec3f(center1.x, center1.y, zConstrH);
1011
                            asciiText->param3 = helperStartAngle1;
1012
                            asciiText->param4 = helperRange1;
1013
                            asciiText->param5 = radius1;
1014
                        }
1015
                        else {
1016
                            verts[2] = SbVec3f(center2.x, center2.y, zConstrH);
1017
                            asciiText->param3 = helperStartAngle2;
1018
                            asciiText->param4 = helperRange2;
1019
                            asciiText->param5 = radius2;
1020
                        }
1021
                        if (numPoints > 3) {
1022
                            verts[3] = SbVec3f(center2.x, center2.y, zConstrH);
1023
                            asciiText->param6 = helperStartAngle2;
1024
                            asciiText->param7 = helperRange2;
1025
                            asciiText->param8 = radius2;
1026
                        }
1027
                        else {
1028
                            asciiText->param6 = 0.;
1029
                            asciiText->param7 = 0.;
1030
                            asciiText->param8 = 0.;
1031
                        }
1032
                    }
1033
                    else {
1034
                        asciiText->param3 = 0.;
1035
                        asciiText->param4 = 0.;
1036
                        asciiText->param5 = 0.;
1037
                    }
1038

1039
                    asciiText->pnts.finishEditing();
1040

1041
                    // Assign the Label Distance
1042
                    asciiText->param1 = Constr->LabelDistance;
1043
                    asciiText->param2 = Constr->LabelPosition;
1044
                } break;
1045
                case PointOnObject:
1046
                case Tangent:
1047
                case SnellsLaw: {
1048
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1049
                    assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
1050

1051
                    Base::Vector3d pos, relPos;
1052
                    if (Constr->Type == PointOnObject || Constr->Type == SnellsLaw
1053
                        || (Constr->Type == Tangent && Constr->Third != GeoEnum::GeoUndef)
1054
                        ||  // Tangency via point
1055
                        (Constr->Type == Tangent
1056
                         && Constr->FirstPos
1057
                             != Sketcher::PointPos::none)  // endpoint-to-curve or
1058
                                                           // endpoint-to-endpoint tangency
1059
                    ) {
1060

1061
                        // find the point of tangency/point that is on object
1062
                        // just any point among first/second/third should be OK
1063
                        int ptGeoId;
1064
                        Sketcher::PointPos ptPosId;
1065
                        do {  // dummy loop to use break =) Maybe goto?
1066
                            ptGeoId = Constr->First;
1067
                            ptPosId = Constr->FirstPos;
1068
                            if (ptPosId != Sketcher::PointPos::none) {
1069
                                break;
1070
                            }
1071
                            ptGeoId = Constr->Second;
1072
                            ptPosId = Constr->SecondPos;
1073
                            if (ptPosId != Sketcher::PointPos::none) {
1074
                                break;
1075
                            }
1076
                            ptGeoId = Constr->Third;
1077
                            ptPosId = Constr->ThirdPos;
1078
                            if (ptPosId != Sketcher::PointPos::none) {
1079
                                break;
1080
                            }
1081
                            assert(0);  // no point found!
1082
                        } while (false);
1083

1084
                        pos = geolistfacade.getPoint(ptGeoId, ptPosId);
1085
                        auto norm = getNormal(geolistfacade, Constr->Second, pos);
1086

1087
                        // TODO: Check substitution
1088
                        // Base::Vector3d norm =
1089
                        // getSolvedSketch().calculateNormalAtPoint(Constr->Second, pos.x, pos.y);
1090
                        norm.Normalize();
1091
                        Base::Vector3d dir = norm;
1092
                        dir.RotateZ(-M_PI / 2.0);
1093

1094
                        relPos = seekConstraintPosition(
1095
                            pos,
1096
                            norm,
1097
                            dir,
1098
                            2.5,
1099
                            editModeScenegraphNodes.constrGroup->getChild(i));
1100

1101
                        auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1102
                            static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1103

1104
                        translation->abPos = SbVec3f(pos.x, pos.y, zConstrH);  // Absolute Reference
1105
                        translation->translation = SbVec3f(relPos.x, relPos.y, 0);
1106
                    }
1107
                    else if (Constr->Type == Tangent) {
1108
                        // get the geometry
1109
                        const Part::Geometry* geo1 =
1110
                            geolistfacade.getGeometryFromGeoId(Constr->First);
1111
                        const Part::Geometry* geo2 =
1112
                            geolistfacade.getGeometryFromGeoId(Constr->Second);
1113

1114
                        if (geo1->is<Part::GeomLineSegment>()
1115
                            && geo2->is<Part::GeomLineSegment>()) {
1116
                            const Part::GeomLineSegment* lineSeg1 =
1117
                                static_cast<const Part::GeomLineSegment*>(geo1);
1118
                            const Part::GeomLineSegment* lineSeg2 =
1119
                                static_cast<const Part::GeomLineSegment*>(geo2);
1120
                            // tangency between two lines
1121
                            Base::Vector3d midpos1 =
1122
                                ((lineSeg1->getEndPoint() + lineSeg1->getStartPoint()) / 2);
1123
                            Base::Vector3d midpos2 =
1124
                                ((lineSeg2->getEndPoint() + lineSeg2->getStartPoint()) / 2);
1125
                            Base::Vector3d dir1 =
1126
                                (lineSeg1->getEndPoint() - lineSeg1->getStartPoint()).Normalize();
1127
                            Base::Vector3d dir2 =
1128
                                (lineSeg2->getEndPoint() - lineSeg2->getStartPoint()).Normalize();
1129
                            Base::Vector3d norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.f);
1130
                            Base::Vector3d norm2 = Base::Vector3d(-dir2.y, dir2.x, 0.f);
1131

1132
                            Base::Vector3d relpos1 = seekConstraintPosition(
1133
                                midpos1,
1134
                                norm1,
1135
                                dir1,
1136
                                4.0,
1137
                                editModeScenegraphNodes.constrGroup->getChild(i));
1138
                            Base::Vector3d relpos2 = seekConstraintPosition(
1139
                                midpos2,
1140
                                norm2,
1141
                                dir2,
1142
                                4.0,
1143
                                editModeScenegraphNodes.constrGroup->getChild(i));
1144

1145
                            auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1146
                                static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1147

1148
                            translation->abPos =
1149
                                SbVec3f(midpos1.x, midpos1.y, zConstrH);  // Absolute Reference
1150
                            translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
1151

1152
                            Base::Vector3d secondPos = midpos2 - midpos1;
1153

1154
                            translation = static_cast<SoZoomTranslation*>(sep->getChild(
1155
                                static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
1156

1157
                            translation->abPos = SbVec3f(secondPos.x,
1158
                                                         secondPos.y,
1159
                                                         zConstrH);  // Absolute Reference
1160
                            translation->translation =
1161
                                SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
1162

1163
                            break;
1164
                        }
1165
                        else if (geo2->is<Part::GeomLineSegment>()) {
1166
                            std::swap(geo1, geo2);
1167
                        }
1168

1169
                        if (geo1->is<Part::GeomLineSegment>()) {
1170
                            const Part::GeomLineSegment* lineSeg =
1171
                                static_cast<const Part::GeomLineSegment*>(geo1);
1172
                            Base::Vector3d dir =
1173
                                (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Normalize();
1174
                            Base::Vector3d norm(-dir.y, dir.x, 0);
1175
                            if (geo2->is<Part::GeomCircle>()) {
1176
                                const Part::GeomCircle* circle =
1177
                                    static_cast<const Part::GeomCircle*>(geo2);
1178
                                // tangency between a line and a circle
1179
                                float length =
1180
                                    (circle->getCenter() - lineSeg->getStartPoint()) * dir;
1181

1182
                                pos = lineSeg->getStartPoint() + dir * length;
1183
                                relPos = norm * 1;  // TODO Huh?
1184
                            }
1185
                            else if (geo2->is<Part::GeomEllipse>()
1186
                                     || geo2->is<Part::GeomArcOfEllipse>()) {
1187

1188
                                Base::Vector3d center;
1189
                                if (geo2->is<Part::GeomEllipse>()) {
1190
                                    const Part::GeomEllipse* ellipse =
1191
                                        static_cast<const Part::GeomEllipse*>(geo2);
1192
                                    center = ellipse->getCenter();
1193
                                }
1194
                                else {
1195
                                    const Part::GeomArcOfEllipse* aoc =
1196
                                        static_cast<const Part::GeomArcOfEllipse*>(geo2);
1197
                                    center = aoc->getCenter();
1198
                                }
1199

1200
                                // tangency between a line and an ellipse
1201
                                float length = (center - lineSeg->getStartPoint()) * dir;
1202

1203
                                pos = lineSeg->getStartPoint() + dir * length;
1204
                                relPos = norm * 1;
1205
                            }
1206
                            else if (geo2->is<Part::GeomArcOfCircle>()) {
1207
                                const Part::GeomArcOfCircle* arc =
1208
                                    static_cast<const Part::GeomArcOfCircle*>(geo2);
1209
                                // tangency between a line and an arc
1210
                                float length = (arc->getCenter() - lineSeg->getStartPoint()) * dir;
1211

1212
                                pos = lineSeg->getStartPoint() + dir * length;
1213
                                relPos = norm * 1;  // TODO Huh?
1214
                            }
1215
                        }
1216

1217
                        if (geo1->is<Part::GeomCircle>() && geo2->is<Part::GeomCircle>()) {
1218
                            const Part::GeomCircle* circle1 =
1219
                                static_cast<const Part::GeomCircle*>(geo1);
1220
                            const Part::GeomCircle* circle2 =
1221
                                static_cast<const Part::GeomCircle*>(geo2);
1222
                            // tangency between two circles
1223
                            Base::Vector3d dir =
1224
                                (circle2->getCenter() - circle1->getCenter()).Normalize();
1225
                            pos = circle1->getCenter() + dir * circle1->getRadius();
1226
                            relPos = dir * 1;
1227
                        }
1228
                        else if (geo2->is<Part::GeomCircle>()) {
1229
                            std::swap(geo1, geo2);
1230
                        }
1231

1232
                        if (geo1->is<Part::GeomCircle>() && geo2->is<Part::GeomArcOfCircle>()) {
1233
                            const Part::GeomCircle* circle =
1234
                                static_cast<const Part::GeomCircle*>(geo1);
1235
                            const Part::GeomArcOfCircle* arc =
1236
                                static_cast<const Part::GeomArcOfCircle*>(geo2);
1237
                            // tangency between a circle and an arc
1238
                            Base::Vector3d dir =
1239
                                (arc->getCenter() - circle->getCenter()).Normalize();
1240
                            pos = circle->getCenter() + dir * circle->getRadius();
1241
                            relPos = dir * 1;
1242
                        }
1243
                        else if (geo1->is<Part::GeomArcOfCircle>()
1244
                                 && geo2->is<Part::GeomArcOfCircle>()) {
1245
                            const Part::GeomArcOfCircle* arc1 =
1246
                                static_cast<const Part::GeomArcOfCircle*>(geo1);
1247
                            const Part::GeomArcOfCircle* arc2 =
1248
                                static_cast<const Part::GeomArcOfCircle*>(geo2);
1249
                            // tangency between two arcs
1250
                            Base::Vector3d dir =
1251
                                (arc2->getCenter() - arc1->getCenter()).Normalize();
1252
                            pos = arc1->getCenter() + dir * arc1->getRadius();
1253
                            relPos = dir * 1;
1254
                        }
1255
                        auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1256
                            static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1257

1258
                        translation->abPos = SbVec3f(pos.x, pos.y, zConstrH);  // Absolute Reference
1259
                        translation->translation = SbVec3f(relPos.x, relPos.y, 0);
1260
                    }
1261
                } break;
1262
                case Symmetric: {
1263
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1264
                    assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
1265

1266
                    Base::Vector3d pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
1267
                    Base::Vector3d pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
1268

1269
                    SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1270
                    SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1271
                    SbVec3f dir = (p2 - p1);
1272
                    dir.normalize();
1273
                    SbVec3f norm(-dir[1], dir[0], 0);
1274

1275
                    SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1276
                        sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1277
                    asciiText->datumtype = SoDatumLabel::SYMMETRIC;
1278

1279
                    asciiText->pnts.setNum(2);
1280
                    SbVec3f* verts = asciiText->pnts.startEditing();
1281

1282
                    verts[0] = p1;
1283
                    verts[1] = p2;
1284

1285
                    asciiText->pnts.finishEditing();
1286

1287
                    auto translation = static_cast<SoTranslation*>(sep->getChild(
1288
                        static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1289

1290
                    translation->translation = (p1 + p2) / 2;
1291
                } break;
1292
                case Angle: {
1293
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1294
                    assert((Constr->Second >= -extGeoCount && Constr->Second < intGeoCount)
1295
                           || Constr->Second == GeoEnum::GeoUndef);
1296

1297
                    SbVec3f p0;
1298
                    double distance = Constr->LabelDistance;
1299
                    double startangle, range;
1300
                    double endLineLength1 = 0.0;
1301
                    double endLineLength2 = 0.0;
1302
                    if (Constr->Second != GeoEnum::GeoUndef) {
1303
                        Base::Vector3d dir1, dir2;
1304
                        if (Constr->Third == GeoEnum::GeoUndef) {  // angle between two lines
1305
                            const Part::Geometry* geo1 =
1306
                                geolistfacade.getGeometryFromGeoId(Constr->First);
1307
                            const Part::Geometry* geo2 =
1308
                                geolistfacade.getGeometryFromGeoId(Constr->Second);
1309
                            if (!isLineSegment(*geo1) || !isLineSegment(*geo2)) {
1310
                                break;
1311
                            }
1312
                            auto* line1 = static_cast<const Part::GeomLineSegment*>(geo1);
1313
                            auto* line2 = static_cast<const Part::GeomLineSegment*>(geo2);
1314

1315
                            bool flip1 = (Constr->FirstPos == PointPos::end);
1316
                            bool flip2 = (Constr->SecondPos == PointPos::end);
1317
                            dir1 = (flip1 ? -1. : 1.)
1318
                                * (line1->getEndPoint() - line1->getStartPoint()).Normalize();
1319
                            dir2 = (flip2 ? -1. : 1.)
1320
                                * (line2->getEndPoint() - line2->getStartPoint()).Normalize();
1321
                            Base::Vector3d pnt1 =
1322
                                flip1 ? line1->getEndPoint() : line1->getStartPoint();
1323
                            Base::Vector3d pnt2 =
1324
                                flip2 ? line2->getEndPoint() : line2->getStartPoint();
1325
                            Base::Vector3d pnt12 =
1326
                                flip1 ? line1->getStartPoint() : line1->getEndPoint();
1327
                            Base::Vector3d pnt22 =
1328
                                flip2 ? line2->getStartPoint() : line2->getEndPoint();
1329

1330
                            // line-line intersection
1331
                            Base::Vector3d intersection;
1332
                            {
1333
                                double det = dir1.x * dir2.y - dir1.y * dir2.x;
1334
                                if ((det > 0 ? det : -det) < 1e-10) {
1335
                                    // lines are coincident (or parallel) and in this case the
1336
                                    // center of the point pairs with the shortest distance is
1337
                                    // used
1338
                                    Base::Vector3d p1[2], p2[2];
1339
                                    p1[0] = line1->getStartPoint();
1340
                                    p1[1] = line1->getEndPoint();
1341
                                    p2[0] = line2->getStartPoint();
1342
                                    p2[1] = line2->getEndPoint();
1343
                                    double length = DBL_MAX;
1344
                                    for (int i = 0; i <= 1; i++) {
1345
                                        for (int j = 0; j <= 1; j++) {
1346
                                            double tmp = (p2[j] - p1[i]).Length();
1347
                                            if (tmp < length) {
1348
                                                length = tmp;
1349
                                                double x = (p2[j].x + p1[i].x) / 2;
1350
                                                double y = (p2[j].y + p1[i].y) / 2;
1351
                                                intersection = Base::Vector3d(x, y, 0.);
1352
                                            }
1353
                                        }
1354
                                    }
1355
                                }
1356
                                else {
1357
                                    double c1 = dir1.y * pnt1.x - dir1.x * pnt1.y;
1358
                                    double c2 = dir2.y * pnt2.x - dir2.x * pnt2.y;
1359
                                    double x = (dir1.x * c2 - dir2.x * c1) / det;
1360
                                    double y = (dir1.y * c2 - dir2.y * c1) / det;
1361
                                    intersection = Base::Vector3d(x, y, 0.);
1362
                                }
1363
                            }
1364
                            p0.setValue(intersection.x, intersection.y, 0.);
1365

1366
                            range = Constr->getValue();  // WYSIWYG
1367
                            startangle = atan2(dir1.y, dir1.x);
1368
                            Base::Vector3d vl1 = dir1 * 2 * distance - (pnt1 - intersection);
1369
                            Base::Vector3d vl2 = dir2 * 2 * distance - (pnt2 - intersection);
1370
                            Base::Vector3d vl12 = dir1 * 2 * distance - (pnt12 - intersection);
1371
                            Base::Vector3d vl22 = dir2 * 2 * distance - (pnt22 - intersection);
1372

1373
                            endLineLength1 = vl12.Dot(dir1) > 0 ? vl12.Length()
1374
                                : vl1.Dot(dir1) < 0             ? -vl1.Length()
1375
                                                                : 0.0;
1376
                            endLineLength2 = vl22.Dot(dir2) > 0 ? vl22.Length()
1377
                                : vl2.Dot(dir2) < 0             ? -vl2.Length()
1378
                                                                : 0.0;
1379
                        }
1380
                        else {  // angle-via-point
1381
                            Base::Vector3d p =
1382
                                geolistfacade.getPoint(Constr->Third, Constr->ThirdPos);
1383
                            p0 = SbVec3f(p.x, p.y, 0);
1384
                            dir1 = getNormal(geolistfacade, Constr->First, p);
1385
                            // TODO: Check
1386
                            // dir1 = getSolvedSketch().calculateNormalAtPoint(Constr->First,
1387
                            // p.x, p.y);
1388
                            dir1.RotateZ(-M_PI / 2);  // convert to vector of tangency by rotating
1389
                            dir2 = getNormal(geolistfacade, Constr->Second, p);
1390
                            // TODO: Check
1391
                            // dir2 = getSolvedSketch().calculateNormalAtPoint(Constr->Second,
1392
                            // p.x, p.y);
1393
                            dir2.RotateZ(-M_PI / 2);
1394

1395
                            startangle = atan2(dir1.y, dir1.x);
1396
                            range = atan2(dir1.x * dir2.y - dir1.y * dir2.x,
1397
                                          dir1.x * dir2.x + dir1.y * dir2.y);
1398
                        }
1399
                    }
1400
                    else if (Constr->First != GeoEnum::GeoUndef) {
1401
                        const Part::Geometry* geo =
1402
                            geolistfacade.getGeometryFromGeoId(Constr->First);
1403
                        if (geo->is<Part::GeomLineSegment>()) {
1404
                            auto* lineSeg = static_cast<const Part::GeomLineSegment*>(geo);
1405
                            p0 = Base::convertTo<SbVec3f>(
1406
                                (lineSeg->getEndPoint() + lineSeg->getStartPoint()) / 2);
1407
                            double l1 = 2 * distance
1408
                                - (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length() / 2;
1409
                            endLineLength1 = 2 * distance;
1410
                            endLineLength2 = l1 > 0. ? l1 : 0.;
1411

1412
                            Base::Vector3d dir = lineSeg->getEndPoint() - lineSeg->getStartPoint();
1413
                            startangle = 0.;
1414
                            range = atan2(dir.y, dir.x);
1415
                        }
1416
                        else if (geo->is<Part::GeomArcOfCircle>()) {
1417
                            auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
1418
                            p0 = Base::convertTo<SbVec3f>(arc->getCenter());
1419

1420
                            endLineLength1 = 2 * distance - arc->getRadius();
1421
                            endLineLength2 = endLineLength1;
1422

1423
                            double endangle;
1424
                            arc->getRange(startangle, endangle, /*emulateCCWXY=*/true);
1425
                            range = endangle - startangle;
1426
                        }
1427
                        else {
1428
                            break;
1429
                        }
1430
                    }
1431
                    else {
1432
                        break;
1433
                    }
1434

1435
                    SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1436
                        sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1437
                    asciiText->string =
1438
                        SbString(getPresentationString(Constr).toUtf8().constData());
1439
                    asciiText->datumtype = SoDatumLabel::ANGLE;
1440
                    asciiText->param1 = distance;
1441
                    asciiText->param2 = startangle;
1442
                    asciiText->param3 = range;
1443
                    asciiText->param4 = endLineLength1;
1444
                    asciiText->param5 = endLineLength2;
1445

1446
                    asciiText->pnts.setNum(2);
1447
                    SbVec3f* verts = asciiText->pnts.startEditing();
1448

1449
                    verts[0] = p0;
1450

1451
                    asciiText->pnts.finishEditing();
1452

1453
                } break;
1454
                case Diameter: {
1455
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1456

1457
                    Base::Vector3d pnt1(0., 0., 0.), pnt2(0., 0., 0.);
1458
                    double helperStartAngle = 0.;
1459
                    double helperRange = 0.;
1460

1461
                    if (Constr->First == GeoEnum::GeoUndef) {
1462
                        break;
1463
                    }
1464

1465
                    const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
1466

1467
                    if (geo->is<Part::GeomArcOfCircle>()) {
1468
                        auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
1469
                        double radius = arc->getRadius();
1470
                        double angle = (double)Constr->LabelPosition;  // between -pi and pi
1471
                        double startAngle, endAngle;                   // between 0 and 2*pi
1472
                        arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
1473

1474
                        if (angle == 10) {
1475
                            angle = (startAngle + endAngle) / 2;
1476
                        }
1477

1478
                        findHelperAngles(helperStartAngle,
1479
                                         helperRange,
1480
                                         angle,
1481
                                         startAngle,
1482
                                         endAngle);
1483

1484
                        Base::Vector3d center = arc->getCenter();
1485
                        pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1486
                        pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1487
                    }
1488
                    else if (geo->is<Part::GeomCircle>()) {
1489
                        auto* circle = static_cast<const Part::GeomCircle*>(geo);
1490
                        double radius = circle->getRadius();
1491
                        double angle = (double)Constr->LabelPosition;
1492
                        if (angle == 10) {
1493
                            angle = 0;
1494
                        }
1495
                        Base::Vector3d center = circle->getCenter();
1496
                        pnt1 = center - radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1497
                        pnt2 = center + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1498
                    }
1499
                    else {
1500
                        break;
1501
                    }
1502

1503
                    SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1504
                    SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1505

1506
                    SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1507
                        sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1508

1509
                    // Get display string with units hidden if so requested
1510
                    asciiText->string =
1511
                        SbString(getPresentationString(Constr).toUtf8().constData());
1512

1513
                    asciiText->datumtype = SoDatumLabel::DIAMETER;
1514
                    asciiText->param1 = Constr->LabelDistance;
1515
                    asciiText->param2 = Constr->LabelPosition;
1516
                    asciiText->param3 = helperStartAngle;
1517
                    asciiText->param4 = helperRange;
1518

1519
                    asciiText->pnts.setNum(2);
1520
                    SbVec3f* verts = asciiText->pnts.startEditing();
1521

1522
                    verts[0] = p1;
1523
                    verts[1] = p2;
1524

1525
                    asciiText->pnts.finishEditing();
1526
                } break;
1527
                case Weight:
1528
                case Radius: {
1529
                    assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1530

1531
                    Base::Vector3d pnt1(0., 0., 0.), pnt2(0., 0., 0.);
1532
                    double helperStartAngle = 0.;
1533
                    double helperRange = 0.;
1534

1535
                    if (Constr->First == GeoEnum::GeoUndef) {
1536
                        break;
1537
                    }
1538
                    const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
1539

1540
                    if (geo->is<Part::GeomArcOfCircle>()) {
1541
                        auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
1542
                        double radius = arc->getRadius();
1543
                        double angle = (double)Constr->LabelPosition;  // between -pi and pi
1544
                        double startAngle, endAngle;                   // between 0 and 2*pi
1545
                        arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
1546

1547
                        if (angle == 10) {
1548
                            angle = (startAngle + endAngle) / 2;
1549
                        }
1550

1551
                        findHelperAngles(helperStartAngle,
1552
                                         helperRange,
1553
                                         angle,
1554
                                         startAngle,
1555
                                         endAngle);
1556

1557
                        pnt1 = arc->getCenter();
1558
                        pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1559
                    }
1560
                    else if (geo->is<Part::GeomCircle>()) {
1561
                        auto* circle = static_cast<const Part::GeomCircle*>(geo);
1562
                        auto gf = GeometryFacade::getFacade(geo);
1563

1564
                        double radius;
1565

1566
                        radius = circle->getRadius();
1567

1568
                        double angle = (double)Constr->LabelPosition;
1569
                        if (angle == 10) {
1570
                            angle = 0;
1571
                        }
1572
                        pnt1 = circle->getCenter();
1573
                        pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1574
                    }
1575
                    else {
1576
                        break;
1577
                    }
1578

1579
                    SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1580
                    SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1581

1582
                    SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1583
                        sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1584

1585
                    // Get display string with units hidden if so requested
1586
                    if (Constr->Type == Weight) {
1587
                        asciiText->string =
1588
                            SbString(QString::number(Constr->getValue()).toStdString().c_str());
1589
                    }
1590
                    else {
1591
                        asciiText->string =
1592
                            SbString(getPresentationString(Constr).toUtf8().constData());
1593
                    }
1594

1595
                    asciiText->datumtype = SoDatumLabel::RADIUS;
1596
                    asciiText->param1 = Constr->LabelDistance;
1597
                    asciiText->param2 = Constr->LabelPosition;
1598
                    asciiText->param3 = helperStartAngle;
1599
                    asciiText->param4 = helperRange;
1600

1601
                    asciiText->pnts.setNum(2);
1602
                    SbVec3f* verts = asciiText->pnts.startEditing();
1603

1604
                    verts[0] = p1;
1605
                    verts[1] = p2;
1606

1607
                    asciiText->pnts.finishEditing();
1608
                } break;
1609
                case Coincident:  // nothing to do for coincident
1610
                case None:
1611
                case InternalAlignment:
1612
                case NumConstraintTypes:
1613
                    break;
1614
            }
1615
        }
1616
        catch (Base::Exception& e) {
1617
            Base::Console().DeveloperError("EditModeConstraintCoinManager",
1618
                                           "Exception during draw: %s\n",
1619
                                           e.what());
1620
            e.ReportException();
1621
        }
1622
        catch (...) {
1623
            Base::Console().DeveloperError("EditModeConstraintCoinManager",
1624
                                           "Exception during draw: unknown\n");
1625
        }
1626
    }
1627
}
1628

1629
void EditModeConstraintCoinManager::findHelperAngles(double& helperStartAngle,
1630
                                                     double& helperRange,
1631
                                                     double angle,
1632
                                                     double startAngle,
1633
                                                     double endAngle)
1634
{
1635
    double margin = 0.2;  // about 10deg
1636
    if (angle < 0) {
1637
        angle = angle + 2 * M_PI;
1638
    }
1639
    // endAngle can be more than 2*pi as its startAngle + arcAngle
1640
    if (endAngle > 2 * M_PI && angle < endAngle - 2 * M_PI) {
1641
        angle = angle + 2 * M_PI;
1642
    }
1643
    if (!(angle > startAngle && angle < endAngle)) {
1644
        if ((angle < startAngle && startAngle - angle < angle + 2 * M_PI - endAngle)
1645
            || (angle > endAngle && startAngle + 2 * M_PI - angle < angle - endAngle)) {
1646
            if (angle > startAngle) {
1647
                angle -= 2 * M_PI;
1648
            }
1649
            helperStartAngle = angle - margin;
1650
            helperRange = startAngle - angle + margin;
1651
        }
1652
        else {
1653
            if (angle < endAngle) {
1654
                angle += 2 * M_PI;
1655
            }
1656
            helperStartAngle = endAngle;
1657
            helperRange = angle - endAngle + margin;
1658
        }
1659
    }
1660
}
1661

1662
Base::Vector3d EditModeConstraintCoinManager::seekConstraintPosition(const Base::Vector3d& origPos,
1663
                                                                     const Base::Vector3d& norm,
1664
                                                                     const Base::Vector3d& dir,
1665
                                                                     float step,
1666
                                                                     const SoNode* constraint)
1667
{
1668

1669
    auto rp = ViewProviderSketchCoinAttorney::getRayPickAction(viewProvider);
1670

1671
    float scaled_step = step * ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider);
1672

1673
    int multiplier = 0;
1674
    Base::Vector3d relPos, freePos;
1675
    bool isConstraintAtPosition = true;
1676
    while (isConstraintAtPosition && multiplier < 10) {
1677
        // Calculate new position of constraint
1678
        relPos = norm * 0.5f + dir * multiplier;
1679
        freePos = origPos + relPos * scaled_step;
1680

1681
        // Prevent crash : https://forum.freecad.org/viewtopic.php?f=8&t=65305
1682
        if (!rp) {
1683
            return relPos * step;
1684
        }
1685

1686
        rp->setRadius(0.1f);
1687
        rp->setPickAll(true);
1688
        rp->setRay(SbVec3f(freePos.x, freePos.y, -1.f), SbVec3f(0, 0, 1));
1689
        // problem
1690
        rp->apply(editModeScenegraphNodes.constrGroup);  // We could narrow it down to just the
1691
                                                         // SoGroup containing the constraints
1692

1693
        // returns a copy of the point
1694
        SoPickedPoint* pp = rp->getPickedPoint();
1695
        const SoPickedPointList ppl = rp->getPickedPointList();
1696

1697
        if (ppl.getLength() <= 1 && pp) {
1698
            SoPath* path = pp->getPath();
1699
            int length = path->getLength();
1700
            SoNode* tailFather1 = path->getNode(length - 2);
1701
            SoNode* tailFather2 = path->getNode(length - 3);
1702

1703
            // checking if a constraint is the same as the one selected
1704
            if (tailFather1 == constraint || tailFather2 == constraint) {
1705
                isConstraintAtPosition = false;
1706
            }
1707
        }
1708
        else {
1709
            isConstraintAtPosition = false;
1710
        }
1711

1712
        multiplier *= -1;  // search in both sides
1713
        if (multiplier >= 0) {
1714
            multiplier++;  // Increment the multiplier
1715
        }
1716
    }
1717
    if (multiplier == 10) {
1718
        relPos = norm * 0.5f;  // no free position found
1719
    }
1720
    return relPos * step;
1721
}
1722

1723
void EditModeConstraintCoinManager::updateConstraintColor(
1724
    const std::vector<Sketcher::Constraint*>& constraints)
1725
{
1726
    // Because coincident constraints are selected using the point color, we need to edit the point
1727
    // materials.
1728

1729
    std::vector<int> PtNum;
1730
    std::vector<SbColor*> pcolor;  // point color
1731
    std::vector<std::vector<int>> CurvNum;
1732
    std::vector<std::vector<SbColor*>> color;  // curve color
1733

1734
    for (int l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) {
1735
        PtNum.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.getNum());
1736
        pcolor.push_back(editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.startEditing());
1737
        CurvNum.emplace_back();
1738
        color.emplace_back();
1739
        for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) {
1740
            CurvNum[l].push_back(
1741
                editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.getNum());
1742
            color[l].push_back(
1743
                editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.startEditing());
1744
        }
1745
    }
1746

1747
    int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(),
1748
                                          static_cast<int>(constraints.size()));
1749

1750
    // colors of the constraints
1751
    for (int i = 0; i < maxNumberOfConstraints; i++) {
1752
        SoSeparator* s =
1753
            static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
1754

1755
        // Check Constraint Type
1756
        Sketcher::Constraint* constraint = constraints[i];
1757
        ConstraintType type = constraint->Type;
1758

1759
        // It may happen that color updates are triggered by programmatic selection changes before a
1760
        // command final update. Then constraints may have been changed and the color will be
1761
        // updated as part
1762
        if (type != vConstrType[i]) {
1763
            break;
1764
        }
1765

1766
        bool hasDatumLabel = (type == Sketcher::Angle || type == Sketcher::Radius
1767
                              || type == Sketcher::Diameter || type == Sketcher::Weight
1768
                              || type == Sketcher::Symmetric || type == Sketcher::Distance
1769
                              || type == Sketcher::DistanceX || type == Sketcher::DistanceY);
1770

1771
        // Non DatumLabel Nodes will have a material excluding coincident
1772
        bool hasMaterial = false;
1773

1774
        SoMaterial* m = nullptr;
1775
        if (!hasDatumLabel && type != Sketcher::Coincident && type != Sketcher::InternalAlignment) {
1776
            int matIndex = static_cast<int>(ConstraintNodePosition::MaterialIndex);
1777
            if (matIndex < s->getNumChildren()) {
1778
                hasMaterial = true;
1779
                m = static_cast<SoMaterial*>(s->getChild(matIndex));
1780
            }
1781
        }
1782

1783
        auto selectpoint = [this, pcolor, PtNum](int geoid, Sketcher::PointPos pos) {
1784
            if (geoid >= 0) {
1785
                auto multifieldIndex = coinMapping.getIndexLayer(geoid, pos);
1786

1787
                if (multifieldIndex != MultiFieldId::Invalid) {
1788
                    int index = multifieldIndex.fieldIndex;
1789
                    int layer = multifieldIndex.layerId;
1790
                    if (layer < static_cast<int>(PtNum.size()) && index >= 0
1791
                        && index < PtNum[layer]) {
1792
                        pcolor[layer][index] = drawingParameters.SelectColor;
1793
                    }
1794
                }
1795
            }
1796
        };
1797

1798
        auto selectline = [this, color, CurvNum](int geoid) {
1799
            if (geoid >= 0) {
1800
                auto multifieldIndex = coinMapping.getIndexLayer(geoid, Sketcher::PointPos::none);
1801

1802
                if (multifieldIndex != MultiFieldId::Invalid) {
1803
                    int index = multifieldIndex.fieldIndex;
1804
                    int layer = multifieldIndex.layerId;
1805
                    int sublayer = multifieldIndex.geoTypeId;
1806
                    if (layer < static_cast<int>(CurvNum.size())
1807
                        && sublayer < static_cast<int>(CurvNum[layer].size()) && index >= 0
1808
                        && index < CurvNum[layer][sublayer]) {
1809
                        color[layer][sublayer][index] = drawingParameters.SelectColor;
1810
                    }
1811
                }
1812
            }
1813
        };
1814

1815

1816
        if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, i)) {
1817
            if (hasDatumLabel) {
1818
                SoDatumLabel* l = static_cast<SoDatumLabel*>(
1819
                    s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1820
                l->textColor = drawingParameters.SelectColor;
1821
            }
1822
            else if (hasMaterial) {
1823
                m->diffuseColor = drawingParameters.SelectColor;
1824
            }
1825
            else if (type == Sketcher::Coincident) {
1826
                selectpoint(constraint->First, constraint->FirstPos);
1827
                selectpoint(constraint->Second, constraint->SecondPos);
1828
            }
1829
            else if (type == Sketcher::InternalAlignment) {
1830
                switch (constraint->AlignmentType) {
1831
                    case EllipseMajorDiameter:
1832
                    case EllipseMinorDiameter:
1833
                    case HyperbolaMajor:
1834
                    case HyperbolaMinor:
1835
                    case ParabolaFocalAxis: {
1836
                        selectline(constraint->First);
1837
                    } break;
1838
                    case EllipseFocus1:
1839
                    case EllipseFocus2:
1840
                    case HyperbolaFocus:
1841
                    case ParabolaFocus:
1842
                    case BSplineControlPoint:
1843
                    case BSplineKnotPoint: {
1844
                        selectpoint(constraint->First, constraint->FirstPos);
1845
                    } break;
1846
                    default:
1847
                        break;
1848
                }
1849
            }
1850
        }
1851
        else if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, i)) {
1852
            if (hasDatumLabel) {
1853
                SoDatumLabel* l = static_cast<SoDatumLabel*>(
1854
                    s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1855
                l->textColor = drawingParameters.PreselectColor;
1856
            }
1857
            else if (hasMaterial) {
1858
                m->diffuseColor = drawingParameters.PreselectColor;
1859
            }
1860
        }
1861
        else {
1862
            if (hasDatumLabel) {
1863
                SoDatumLabel* l = static_cast<SoDatumLabel*>(
1864
                    s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1865

1866
                l->textColor = constraint->isActive
1867
                    ? ViewProviderSketchCoinAttorney::constraintHasExpression(viewProvider, i)
1868
                        ? drawingParameters.ExprBasedConstrDimColor
1869
                        : (constraint->isDriving ? drawingParameters.ConstrDimColor
1870
                                                 : drawingParameters.NonDrivingConstrDimColor)
1871
                    : drawingParameters.DeactivatedConstrDimColor;
1872
            }
1873
            else if (hasMaterial) {
1874
                m->diffuseColor = constraint->isActive
1875
                    ? (constraint->isDriving ? drawingParameters.ConstrDimColor
1876
                                             : drawingParameters.NonDrivingConstrDimColor)
1877
                    : drawingParameters.DeactivatedConstrDimColor;
1878
            }
1879
        }
1880
    }
1881

1882
    for (int l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) {
1883
        editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.finishEditing();
1884
        for (int t = 0; t < geometryLayerParameters.getSubLayerCount(); t++) {
1885
            editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.finishEditing();
1886
        }
1887
    }
1888
}
1889

1890
void EditModeConstraintCoinManager::rebuildConstraintNodes()
1891
{
1892
    auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
1893

1894
    rebuildConstraintNodes(geolistfacade);
1895
}
1896

1897
void EditModeConstraintCoinManager::setConstraintSelectability(bool enabled /* = true */)
1898
{
1899
    if (enabled) {
1900
        editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::SHAPE);
1901
    }
1902
    else {
1903
        editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::UNPICKABLE);
1904
    }
1905
}
1906

1907
void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade& geolistfacade)
1908
{
1909
    const std::vector<Sketcher::Constraint*>& constrlist =
1910
        ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
1911

1912
    // clean up
1913
    Gui::coinRemoveAllChildren(editModeScenegraphNodes.constrGroup);
1914

1915
    vConstrType.clear();
1916

1917
    // Get sketch normal
1918
    Base::Vector3d RN(0, 0, 1);
1919

1920
    // move to position of Sketch
1921
    Base::Placement Plz = ViewProviderSketchCoinAttorney::getEditingPlacement(viewProvider);
1922
    Base::Rotation tmp(Plz.getRotation());
1923
    tmp.multVec(RN, RN);
1924
    Plz.setRotation(tmp);
1925

1926
    SbVec3f norm(RN.x, RN.y, RN.z);
1927

1928
    rebuildConstraintNodes(geolistfacade, constrlist, norm);
1929
}
1930

1931
void EditModeConstraintCoinManager::rebuildConstraintNodes(
1932
    const GeoListFacade& geolistfacade,
1933
    const std::vector<Sketcher::Constraint*> constrlist,
1934
    SbVec3f norm)
1935
{
1936

1937
    for (std::vector<Sketcher::Constraint*>::const_iterator it = constrlist.begin();
1938
         it != constrlist.end();
1939
         ++it) {
1940
        // root separator for one constraint
1941
        SoSeparator* sep = new SoSeparator();
1942
        sep->ref();
1943
        // no caching for frequently-changing data structures
1944
        sep->renderCaching = SoSeparator::OFF;
1945

1946
        // every constrained visual node gets its own material for preselection and selection
1947
        SoMaterial* mat = new SoMaterial;
1948
        mat->ref();
1949
        mat->diffuseColor = (*it)->isActive
1950
            ? ((*it)->isDriving ? drawingParameters.ConstrDimColor
1951
                                : drawingParameters.NonDrivingConstrDimColor)
1952
            : drawingParameters.DeactivatedConstrDimColor;
1953

1954

1955
        // distinguish different constraint types to build up
1956
        switch ((*it)->Type) {
1957
            case Distance:
1958
            case DistanceX:
1959
            case DistanceY:
1960
            case Radius:
1961
            case Diameter:
1962
            case Weight:
1963
            case Angle: {
1964
                SoDatumLabel* text = new SoDatumLabel();
1965
                text->norm.setValue(norm);
1966
                text->string = "";
1967
                text->textColor = (*it)->isActive
1968
                    ? ((*it)->isDriving ? drawingParameters.ConstrDimColor
1969
                                        : drawingParameters.NonDrivingConstrDimColor)
1970
                    : drawingParameters.DeactivatedConstrDimColor;
1971
                text->size.setValue(drawingParameters.labelFontSize);
1972
                text->lineWidth = 2 * drawingParameters.pixelScalingFactor;
1973
                text->useAntialiasing = false;
1974
                SoAnnotation* anno = new SoAnnotation();
1975
                anno->renderCaching = SoSeparator::OFF;
1976
                anno->addChild(text);
1977
                // #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
1978
                sep->addChild(text);
1979
                editModeScenegraphNodes.constrGroup->addChild(anno);
1980
                vConstrType.push_back((*it)->Type);
1981
                // nodes not needed
1982
                sep->unref();
1983
                mat->unref();
1984
                continue;  // jump to next constraint
1985
            } break;
1986
            case Horizontal:
1987
            case Vertical:
1988
            case Block: {
1989
                // #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
1990
                sep->addChild(mat);
1991
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
1992
                sep->addChild(new SoZoomTranslation());
1993
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
1994
                sep->addChild(new SoImage());
1995
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
1996
                sep->addChild(new SoInfo());
1997
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
1998
                sep->addChild(new SoZoomTranslation());
1999
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
2000
                sep->addChild(new SoImage());
2001
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
2002
                sep->addChild(new SoInfo());
2003

2004
                // remember the type of this constraint node
2005
                vConstrType.push_back((*it)->Type);
2006
            } break;
2007
            case Coincident:  // no visual for coincident so far
2008
                vConstrType.push_back(Coincident);
2009
                break;
2010
            case Parallel:
2011
            case Perpendicular:
2012
            case Equal: {
2013
                // #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
2014
                sep->addChild(mat);
2015
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
2016
                sep->addChild(new SoZoomTranslation());
2017
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
2018
                sep->addChild(new SoImage());
2019
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
2020
                sep->addChild(new SoInfo());
2021
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
2022
                sep->addChild(new SoZoomTranslation());
2023
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
2024
                sep->addChild(new SoImage());
2025
                // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
2026
                sep->addChild(new SoInfo());
2027

2028
                // remember the type of this constraint node
2029
                vConstrType.push_back((*it)->Type);
2030
            } break;
2031
            case PointOnObject:
2032
            case Tangent:
2033
            case SnellsLaw: {
2034
                // #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
2035
                sep->addChild(mat);
2036
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
2037
                sep->addChild(new SoZoomTranslation());
2038
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
2039
                sep->addChild(new SoImage());
2040
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
2041
                sep->addChild(new SoInfo());
2042

2043
                if ((*it)->Type == Tangent) {
2044
                    const Part::Geometry* geo1 = geolistfacade.getGeometryFromGeoId((*it)->First);
2045
                    const Part::Geometry* geo2 = geolistfacade.getGeometryFromGeoId((*it)->Second);
2046
                    if (!geo1 || !geo2) {
2047
                        Base::Console().DeveloperWarning(
2048
                            "EditModeConstraintCoinManager",
2049
                            "Tangent constraint references non-existing geometry\n");
2050
                    }
2051
                    else if (geo1->is<Part::GeomLineSegment>()
2052
                             && geo2->is<Part::GeomLineSegment>()) {
2053
                        // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_TRANSLATION 4
2054
                        sep->addChild(new SoZoomTranslation());
2055
                        // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_ICON 5
2056
                        sep->addChild(new SoImage());
2057
                        // #define CONSTRAINT_SEPARATOR_INDEX_SECOND_CONSTRAINTID 6
2058
                        sep->addChild(new SoInfo());
2059
                    }
2060
                }
2061

2062
                vConstrType.push_back((*it)->Type);
2063
            } break;
2064
            case Symmetric: {
2065
                SoDatumLabel* arrows = new SoDatumLabel();
2066
                arrows->norm.setValue(norm);
2067
                arrows->string = "";
2068
                arrows->textColor = drawingParameters.ConstrDimColor;
2069
                arrows->lineWidth = 2 * drawingParameters.pixelScalingFactor;
2070

2071
                // #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
2072
                sep->addChild(arrows);
2073
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_TRANSLATION 1
2074
                sep->addChild(new SoTranslation());
2075
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_ICON 2
2076
                sep->addChild(new SoImage());
2077
                // #define CONSTRAINT_SEPARATOR_INDEX_FIRST_CONSTRAINTID 3
2078
                sep->addChild(new SoInfo());
2079

2080
                vConstrType.push_back((*it)->Type);
2081
            } break;
2082
            case InternalAlignment: {
2083
                vConstrType.push_back((*it)->Type);
2084
            } break;
2085
            default:
2086
                vConstrType.push_back((*it)->Type);
2087
        }
2088

2089
        editModeScenegraphNodes.constrGroup->addChild(sep);
2090
        // decrement ref counter again
2091
        sep->unref();
2092
        mat->unref();
2093
    }
2094
}
2095

2096
QString EditModeConstraintCoinManager::getPresentationString(const Constraint* constraint)
2097
{
2098
    QString nameStr;           // name parameter string
2099
    QString valueStr;          // dimensional value string
2100
    QString presentationStr;   // final return string
2101
    QString unitStr;           // the actual unit string
2102
    QString baseUnitStr;       // the expected base unit string
2103
    double factor;             // unit scaling factor, currently not used
2104
    Base::UnitSystem unitSys;  // current unit system
2105

2106
    if (!constraint->isActive) {
2107
        return QString::fromLatin1(" ");
2108
    }
2109

2110
    // Get the current name parameter string of the constraint
2111
    nameStr = QString::fromStdString(constraint->Name);
2112

2113
    // Get the current value string including units
2114
    valueStr = constraint->getPresentationValue().getUserString(factor, unitStr);
2115

2116
    // Hide units if user has requested it, is being displayed in the base
2117
    // units, and the schema being used has a clear base unit in the first
2118
    // place. Otherwise, display units.
2119
    if (constraintParameters.bHideUnits && constraint->Type != Sketcher::Angle) {
2120
        // Only hide the default length unit. Right now there is not an easy way
2121
        // to get that from the Unit system so we have to manually add it here.
2122
        // Hopefully this can be added in the future so this code won't have to
2123
        // be updated if a new units schema is added.
2124
        unitSys = Base::UnitsApi::getSchema();
2125

2126
        // If this is a supported unit system then define what the base unit is.
2127
        switch (unitSys) {
2128
            case Base::UnitSystem::SI1:
2129
            case Base::UnitSystem::MmMin:
2130
                baseUnitStr = QString::fromLatin1("mm");
2131
                break;
2132

2133
            case Base::UnitSystem::SI2:
2134
                baseUnitStr = QString::fromLatin1("m");
2135
                break;
2136

2137
            case Base::UnitSystem::ImperialDecimal:
2138
                baseUnitStr = QString::fromLatin1("in");
2139
                break;
2140

2141
            case Base::UnitSystem::Centimeters:
2142
                baseUnitStr = QString::fromLatin1("cm");
2143
                break;
2144

2145
            default:
2146
                // Nothing to do
2147
                break;
2148
        }
2149

2150
        if (!baseUnitStr.isEmpty()) {
2151
            // expected unit string matches actual unit string. remove.
2152
            if (QString::compare(baseUnitStr, unitStr) == 0) {
2153
                // Example code from: Mod/TechDraw/App/DrawViewDimension.cpp:372
2154
                QRegularExpression rxUnits(
2155
                    QString::fromUtf8(" \\D*$"));  // space + any non digits at end of string
2156
                valueStr.remove(rxUnits);          // getUserString(defaultDecimals) without units
2157
            }
2158
        }
2159
    }
2160

2161
    if (constraint->Type == Sketcher::Diameter) {
2162
        valueStr.prepend(QChar(216));  // Diameter sign
2163
    }
2164
    else if (constraint->Type == Sketcher::Radius) {
2165
        valueStr.prepend(QChar(82));  // Capital letter R
2166
    }
2167

2168
    /**
2169
    Create the representation string from the user defined format string
2170
    Format options are:
2171
    %N - the constraint name parameter
2172
    %V - the value of the dimensional constraint, including any unit characters
2173
    */
2174
    if (constraintParameters.bShowDimensionalName && !nameStr.isEmpty()) {
2175
        if (constraintParameters.sDimensionalStringFormat.contains(QLatin1String("%V"))
2176
            || constraintParameters.sDimensionalStringFormat.contains(QLatin1String("%N"))) {
2177
            presentationStr = constraintParameters.sDimensionalStringFormat;
2178
            presentationStr.replace(QLatin1String("%N"), nameStr);
2179
            presentationStr.replace(QLatin1String("%V"), valueStr);
2180
        }
2181
        else {
2182
            // user defined format string does not contain any valid parameter, using default format
2183
            // "%N = %V"
2184
            presentationStr = nameStr + QLatin1String(" = ") + valueStr;
2185
        }
2186

2187
        return presentationStr;
2188
    }
2189

2190
    return valueStr;
2191
}
2192

2193
std::set<int> EditModeConstraintCoinManager::detectPreselectionConstr(const SoPickedPoint* Point,
2194
                                                                      const SbVec2s& cursorPos)
2195
{
2196
    std::set<int> constrIndices;
2197
    SoPath* path = Point->getPath();
2198

2199
    // Get the constraints' tail
2200
    SoNode* tailFather2 = path->getNode(path->getLength() - 3);
2201

2202
    if (tailFather2 != editModeScenegraphNodes.constrGroup) {
2203
        return constrIndices;
2204
    }
2205

2206

2207
    SoNode* tail = path->getTail();
2208
    SoNode* tailFather = path->getNode(path->getLength() - 2);
2209

2210
    for (int i = 0; i < editModeScenegraphNodes.constrGroup->getNumChildren(); ++i) {
2211
        if (editModeScenegraphNodes.constrGroup->getChild(i) == tailFather) {
2212
            SoSeparator* sep = static_cast<SoSeparator*>(tailFather);
2213
            if (sep->getNumChildren()
2214
                > static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)) {
2215
                SoInfo* constrIds = nullptr;
2216
                if (tail
2217
                    == sep->getChild(static_cast<int>(ConstraintNodePosition::FirstIconIndex))) {
2218
                    // First icon was hit
2219
                    constrIds = static_cast<SoInfo*>(sep->getChild(
2220
                        static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)));
2221
                }
2222
                else {
2223
                    // Assume second icon was hit
2224
                    if (static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)
2225
                        < sep->getNumChildren()) {
2226
                        constrIds = static_cast<SoInfo*>(sep->getChild(
2227
                            static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)));
2228
                    }
2229
                }
2230

2231
                if (constrIds) {
2232
                    QString constrIdsStr =
2233
                        QString::fromLatin1(constrIds->string.getValue().getString());
2234
                    if (combinedConstrBoxes.count(constrIdsStr) && dynamic_cast<SoImage*>(tail)) {
2235
                        // If it's a combined constraint icon
2236

2237
                        // Screen dimensions of the icon
2238
                        SbVec3s iconSize = getDisplayedSize(static_cast<SoImage*>(tail));
2239
                        // Center of the icon
2240
                        // SbVec2f iconCoords = viewer->screenCoordsOfPath(path);
2241

2242
                        // The use of the Path to get the screen coordinates to get the icon center
2243
                        // coordinates does not work.
2244
                        //
2245
                        // This implementation relies on the use of ZoomTranslation to get the
2246
                        // absolute and relative positions of the icons.
2247
                        //
2248
                        // In the case of second icons (the same constraint has two icons at two
2249
                        // different positions), the translation vectors have to be added, as the
2250
                        // second ZoomTranslation operates on top of the first.
2251
                        //
2252
                        // Coordinates are projected on the sketch plane and then to the screen in
2253
                        // the interval [0 1] Then this result is converted to pixels using the
2254
                        // scale factor.
2255

2256
                        SbVec3f absPos;
2257
                        SbVec3f trans;
2258
                        float scaleFactor;
2259

2260
                        auto translation = static_cast<SoZoomTranslation*>(
2261
                            static_cast<SoSeparator*>(tailFather)
2262
                                ->getChild(static_cast<int>(
2263
                                    ConstraintNodePosition::FirstTranslationIndex)));
2264

2265
                        absPos = translation->abPos.getValue();
2266

2267
                        trans = translation->translation.getValue();
2268

2269
                        scaleFactor = translation->getScaleFactor();
2270

2271
                        if (tail
2272
                            != sep->getChild(
2273
                                static_cast<int>(ConstraintNodePosition::FirstIconIndex))) {
2274
                            Base::Console().Log("SecondIcon\n");
2275

2276
                            auto translation2 = static_cast<SoZoomTranslation*>(
2277
                                static_cast<SoSeparator*>(tailFather)
2278
                                    ->getChild(static_cast<int>(
2279
                                        ConstraintNodePosition::SecondTranslationIndex)));
2280

2281
                            absPos += translation2->abPos.getValue();
2282

2283
                            trans += translation2->translation.getValue();
2284

2285
                            scaleFactor = translation2->getScaleFactor();
2286
                        }
2287

2288
                        // Only the translation is scaled because this is how SoZoomTranslation
2289
                        // works
2290
                        SbVec3f constrPos = absPos + scaleFactor * trans;
2291

2292
                        SbVec2f iconCoords = ViewProviderSketchCoinAttorney::getScreenCoordinates(
2293
                            viewProvider,
2294
                            SbVec2f(constrPos[0], constrPos[1]));
2295

2296
                        // cursorPos is SbVec2s in screen coordinates coming from SoEvent in
2297
                        // mousemove
2298
                        //
2299
                        // Coordinates of the mouse cursor on the icon, origin at top-left for Qt
2300
                        // but bottom-left for OIV.
2301
                        // The coordinates are needed in Qt format, i.e. from top to bottom.
2302
                        int iconX = cursorPos[0] - iconCoords[0] + iconSize[0] / 2,
2303
                            iconY = cursorPos[1] - iconCoords[1] + iconSize[1] / 2;
2304
                        iconY = iconSize[1] - iconY;
2305

2306
                        for (ConstrIconBBVec::iterator b =
2307
                                 combinedConstrBoxes[constrIdsStr].begin();
2308
                             b != combinedConstrBoxes[constrIdsStr].end();
2309
                             ++b) {
2310

2311
#ifdef FC_DEBUG
2312
                            // Useful code to debug coordinates and bounding boxes that does
2313
                            // not need to be compiled in for any debug operations.
2314

2315
                            /*Base::Console().Log("Abs(%f,%f),Trans(%f,%f),Coords(%d,%d),iCoords(%f,%f),icon(%d,%d),isize(%d,%d),boundingbox([%d,%d],[%d,%d])\n",
2316
                             * absPos[0],absPos[1],trans[0], trans[1], cursorPos[0],
2317
                             * cursorPos[1], iconCoords[0], iconCoords[1], iconX, iconY,
2318
                             * iconSize[0], iconSize[1],
2319
                             * b->first.topLeft().x(),b->first.topLeft().y(),b->first.bottomRight().x(),b->first.bottomRight().y());*/
2320
#endif
2321

2322
                            if (b->first.contains(iconX, iconY)) {
2323
                                // We've found a bounding box that contains the mouse pointer!
2324
                                for (std::set<int>::iterator k = b->second.begin();
2325
                                     k != b->second.end();
2326
                                     ++k) {
2327
                                    constrIndices.insert(*k);
2328
                                }
2329
                            }
2330
                        }
2331
                    }
2332
                    else {
2333
                        // It's a constraint icon, not a combined one
2334
                        QStringList constrIdStrings = constrIdsStr.split(QString::fromLatin1(","));
2335
                        while (!constrIdStrings.empty()) {
2336
                            auto constraintid = constrIdStrings.takeAt(0).toInt();
2337
                            constrIndices.insert(constraintid);
2338
                        }
2339
                    }
2340
                }
2341
            }
2342
            else {
2343
                // other constraint icons - eg radius...
2344
                constrIndices.clear();
2345
                constrIndices.insert(i);
2346
            }
2347
            break;
2348
        }
2349
    }
2350

2351
    return constrIndices;
2352
}
2353

2354
SbVec3s EditModeConstraintCoinManager::getDisplayedSize(const SoImage* iconPtr) const
2355
{
2356
    SbVec3s iconSize = iconPtr->image.getValue().getSize();
2357

2358
    if (iconPtr->width.getValue() != -1) {
2359
        iconSize[0] = iconPtr->width.getValue();
2360
    }
2361
    if (iconPtr->height.getValue() != -1) {
2362
        iconSize[1] = iconPtr->height.getValue();
2363
    }
2364
    return iconSize;
2365
}
2366

2367
// public function that triggers drawing of most constraint icons
2368
void EditModeConstraintCoinManager::drawConstraintIcons()
2369
{
2370
    auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
2371

2372
    drawConstraintIcons(geolistfacade);
2373
}
2374

2375
void EditModeConstraintCoinManager::drawConstraintIcons(const GeoListFacade& geolistfacade)
2376
{
2377
    const std::vector<Sketcher::Constraint*>& constraints =
2378
        ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
2379

2380
    std::vector<constrIconQueueItem> iconQueue;
2381

2382
    int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(),
2383
                                          static_cast<int>(constraints.size()));
2384

2385
    for (int constrId = 0; constrId < maxNumberOfConstraints; ++constrId) {
2386
        Sketcher::Constraint* constraint = constraints[constrId];
2387

2388
        // Check if Icon Should be created
2389
        bool multipleIcons = false;
2390

2391
        QString icoType = iconTypeFromConstraint(constraint);
2392
        if (icoType.isEmpty()) {
2393
            continue;
2394
        }
2395

2396
        if (constraint->Type != vConstrType[constrId]) {
2397
            break;
2398
        }
2399

2400
        switch (constraint->Type) {
2401

2402
            case Tangent: {  // second icon is available only for collinear line segments
2403
                const Part::Geometry* geo1 = geolistfacade.getGeometryFromGeoId(constraint->First);
2404
                const Part::Geometry* geo2 = geolistfacade.getGeometryFromGeoId(constraint->Second);
2405
                if (geo1 && geo1->is<Part::GeomLineSegment>() && geo2
2406
                    && geo2->is<Part::GeomLineSegment>()) {
2407
                    multipleIcons = true;
2408
                }
2409
            } break;
2410
            case Horizontal:
2411
            case Vertical: {  // second icon is available only for point alignment
2412
                if (constraint->Second != GeoEnum::GeoUndef
2413
                    && constraint->FirstPos != Sketcher::PointPos::none
2414
                    && constraint->SecondPos != Sketcher::PointPos::none) {
2415
                    multipleIcons = true;
2416
                }
2417
            } break;
2418
            case Parallel:
2419
                multipleIcons = true;
2420
                break;
2421
            case Perpendicular:
2422
                // second icon is available only when there is no common point
2423
                if (constraint->FirstPos == Sketcher::PointPos::none
2424
                    && constraint->Third == GeoEnum::GeoUndef) {
2425
                    multipleIcons = true;
2426
                }
2427
                break;
2428
            case Equal:
2429
                multipleIcons = true;
2430
                break;
2431
            default:
2432
                break;
2433
        }
2434

2435
        // Double-check that we can safely access the Inventor nodes
2436
        if (constrId >= editModeScenegraphNodes.constrGroup->getNumChildren()) {
2437
            Base::Console().DeveloperWarning(
2438
                "EditModeConstraintManager",
2439
                "Can't update constraint icons because view is not in sync with sketch\n");
2440
            break;
2441
        }
2442

2443
        // Find the Constraint Icon SoImage Node
2444
        SoSeparator* sep =
2445
            static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(constrId));
2446
        int numChildren = sep->getNumChildren();
2447

2448
        SbVec3f absPos;
2449
        // Somewhat hacky - we use SoZoomTranslations for most types of icon,
2450
        // but symmetry icons use SoTranslations...
2451
        SoTranslation* translationPtr = static_cast<SoTranslation*>(
2452
            sep->getChild(static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
2453

2454
        if (dynamic_cast<SoZoomTranslation*>(translationPtr)) {
2455
            absPos = static_cast<SoZoomTranslation*>(translationPtr)->abPos.getValue();
2456
        }
2457
        else {
2458
            absPos = translationPtr->translation.getValue();
2459
        }
2460

2461
        SoImage* coinIconPtr = dynamic_cast<SoImage*>(
2462
            sep->getChild(static_cast<int>(ConstraintNodePosition::FirstIconIndex)));
2463
        SoInfo* infoPtr = static_cast<SoInfo*>(
2464
            sep->getChild(static_cast<int>(ConstraintNodePosition::FirstConstraintIdIndex)));
2465

2466
        constrIconQueueItem thisIcon;
2467
        thisIcon.type = icoType;
2468
        thisIcon.constraintId = constrId;
2469
        thisIcon.position = absPos;
2470
        thisIcon.destination = coinIconPtr;
2471
        thisIcon.infoPtr = infoPtr;
2472
        thisIcon.visible = constraint->isInVirtualSpace
2473
            == ViewProviderSketchCoinAttorney::isShownVirtualSpace(viewProvider);
2474

2475
        if (constraint->Type == Symmetric) {
2476
            Base::Vector3d startingpoint =
2477
                geolistfacade.getPoint(constraint->First, constraint->FirstPos);
2478
            Base::Vector3d endpoint =
2479
                geolistfacade.getPoint(constraint->Second, constraint->SecondPos);
2480

2481
            SbVec3f pos0(startingpoint.x, startingpoint.y, startingpoint.z);
2482
            SbVec3f pos1(endpoint.x, endpoint.y, endpoint.z);
2483

2484
            thisIcon.iconRotation =
2485
                ViewProviderSketchCoinAttorney::getRotation(viewProvider, pos0, pos1);
2486
        }
2487
        else {
2488
            thisIcon.iconRotation = 0;
2489
        }
2490

2491
        if (multipleIcons) {
2492
            if (constraint->Name.empty()) {
2493
                thisIcon.label = QString::number(constrId + 1);
2494
            }
2495
            else {
2496
                thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
2497
            }
2498
            iconQueue.push_back(thisIcon);
2499

2500
            // Note that the second translation is meant to be applied after the first.
2501
            // So, to get the position of the second icon, we add the two translations together
2502
            //
2503
            // See note ~30 lines up.
2504
            if (numChildren > static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)) {
2505
                translationPtr = static_cast<SoTranslation*>(sep->getChild(
2506
                    static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
2507
                if (dynamic_cast<SoZoomTranslation*>(translationPtr)) {
2508
                    thisIcon.position +=
2509
                        static_cast<SoZoomTranslation*>(translationPtr)->abPos.getValue();
2510
                }
2511
                else {
2512
                    thisIcon.position += translationPtr->translation.getValue();
2513
                }
2514

2515
                thisIcon.destination = dynamic_cast<SoImage*>(
2516
                    sep->getChild(static_cast<int>(ConstraintNodePosition::SecondIconIndex)));
2517
                thisIcon.infoPtr = static_cast<SoInfo*>(sep->getChild(
2518
                    static_cast<int>(ConstraintNodePosition::SecondConstraintIdIndex)));
2519
            }
2520
        }
2521
        else {
2522
            if (constraint->Name.empty()) {
2523
                thisIcon.label = QString();
2524
            }
2525
            else {
2526
                thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
2527
            }
2528
        }
2529

2530
        iconQueue.push_back(thisIcon);
2531
    }
2532

2533
    combineConstraintIcons(iconQueue);
2534
}
2535

2536
void EditModeConstraintCoinManager::combineConstraintIcons(IconQueue iconQueue)
2537
{
2538
    // getScaleFactor gives us a ratio of pixels per some kind of real units
2539
    float maxDistSquared = pow(ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider), 2);
2540

2541
    // There's room for optimisation here; we could reuse the combined icons...
2542
    combinedConstrBoxes.clear();
2543

2544
    while (!iconQueue.empty()) {
2545
        // A group starts with an item popped off the back of our initial queue
2546
        IconQueue thisGroup;
2547
        thisGroup.push_back(iconQueue.back());
2548
        constrIconQueueItem init = iconQueue.back();
2549
        iconQueue.pop_back();
2550

2551
        // we group only icons not being Symmetry icons, because we want those on the line
2552
        // and only icons that are visible
2553
        if (init.type != QString::fromLatin1("Constraint_Symmetric") && init.visible) {
2554

2555
            IconQueue::iterator i = iconQueue.begin();
2556

2557

2558
            while (i != iconQueue.end()) {
2559
                if ((*i).visible) {
2560
                    bool addedToGroup = false;
2561

2562
                    for (IconQueue::iterator j = thisGroup.begin(); j != thisGroup.end(); ++j) {
2563
                        float distSquared = pow(i->position[0] - j->position[0], 2)
2564
                            + pow(i->position[1] - j->position[1], 2);
2565
                        if (distSquared <= maxDistSquared
2566
                            && (*i).type != QString::fromLatin1("Constraint_Symmetric")) {
2567
                            // Found an icon in iconQueue that's close enough to
2568
                            // a member of thisGroup, so move it into thisGroup
2569
                            thisGroup.push_back(*i);
2570
                            i = iconQueue.erase(i);
2571
                            addedToGroup = true;
2572
                            break;
2573
                        }
2574
                    }
2575

2576
                    if (addedToGroup) {
2577
                        if (i == iconQueue.end()) {
2578
                            // We just got the last icon out of iconQueue
2579
                            break;
2580
                        }
2581
                        else {
2582
                            // Start looking through the iconQueue again, in case
2583
                            // we have an icon that's now close enough to thisGroup
2584
                            i = iconQueue.begin();
2585
                        }
2586
                    }
2587
                    else {
2588
                        ++i;
2589
                    }
2590
                }
2591
                else {  // if !visible we skip it
2592
                    i++;
2593
                }
2594
            }
2595
        }
2596

2597
        if (thisGroup.size() == 1) {
2598
            drawTypicalConstraintIcon(thisGroup[0]);
2599
        }
2600
        else {
2601
            drawMergedConstraintIcons(thisGroup);
2602
        }
2603
    }
2604
}
2605

2606
void EditModeConstraintCoinManager::drawMergedConstraintIcons(IconQueue iconQueue)
2607
{
2608
    for (IconQueue::iterator i = iconQueue.begin(); i != iconQueue.end(); ++i) {
2609
        clearCoinImage(i->destination);
2610
    }
2611

2612
    QImage compositeIcon;
2613
    SoImage* thisDest = iconQueue[0].destination;
2614
    SoInfo* thisInfo = iconQueue[0].infoPtr;
2615

2616
    // Tracks all constraint IDs that are combined into this icon
2617
    QString idString;
2618
    int lastVPad = 0;
2619

2620
    QStringList labels;
2621
    std::vector<int> ids;
2622
    QString thisType;
2623
    QColor iconColor;
2624
    QList<QColor> labelColors;
2625
    int maxColorPriority;
2626
    double iconRotation;
2627

2628
    ConstrIconBBVec boundingBoxes;
2629
    while (!iconQueue.empty()) {
2630
        IconQueue::iterator i = iconQueue.begin();
2631

2632
        labels.clear();
2633
        labels.append(i->label);
2634

2635
        ids.clear();
2636
        ids.push_back(i->constraintId);
2637

2638
        thisType = i->type;
2639
        iconColor = constrColor(i->constraintId);
2640
        labelColors.clear();
2641
        labelColors.append(iconColor);
2642
        iconRotation = i->iconRotation;
2643

2644
        maxColorPriority = constrColorPriority(i->constraintId);
2645

2646
        if (idString.length()) {
2647
            idString.append(QString::fromLatin1(","));
2648
        }
2649
        idString.append(QString::number(i->constraintId));
2650

2651
        i = iconQueue.erase(i);
2652
        while (i != iconQueue.end()) {
2653
            if (i->type != thisType) {
2654
                ++i;
2655
                continue;
2656
            }
2657

2658
            labels.append(i->label);
2659
            ids.push_back(i->constraintId);
2660
            labelColors.append(constrColor(i->constraintId));
2661

2662
            if (constrColorPriority(i->constraintId) > maxColorPriority) {
2663
                maxColorPriority = constrColorPriority(i->constraintId);
2664
                iconColor = constrColor(i->constraintId);
2665
            }
2666

2667
            idString.append(QString::fromLatin1(",") + QString::number(i->constraintId));
2668

2669
            i = iconQueue.erase(i);
2670
        }
2671

2672
        // To be inserted into edit->combinedConstBoxes
2673
        std::vector<QRect> boundingBoxesVec;
2674
        int oldHeight = 0;
2675

2676
        // Render the icon here.
2677
        if (compositeIcon.isNull()) {
2678
            compositeIcon = renderConstrIcon(thisType,
2679
                                             iconColor,
2680
                                             labels,
2681
                                             labelColors,
2682
                                             iconRotation,
2683
                                             &boundingBoxesVec,
2684
                                             &lastVPad);
2685
        }
2686
        else {
2687
            int thisVPad;
2688
            QImage partialIcon = renderConstrIcon(thisType,
2689
                                                  iconColor,
2690
                                                  labels,
2691
                                                  labelColors,
2692
                                                  iconRotation,
2693
                                                  &boundingBoxesVec,
2694
                                                  &thisVPad);
2695

2696
            // Stack vertically for now.  Down the road, it might make sense
2697
            // to figure out the best orientation automatically.
2698
            oldHeight = compositeIcon.height();
2699

2700
            // This is overkill for the currently used (20 July 2014) font,
2701
            // since it always seems to have the same vertical pad, but this
2702
            // might not always be the case.  The 3 pixel buffer might need
2703
            // to vary depending on font size too...
2704
            oldHeight -= std::max(lastVPad - 3, 0);
2705

2706
            compositeIcon = compositeIcon.copy(0,
2707
                                               0,
2708
                                               std::max(partialIcon.width(), compositeIcon.width()),
2709
                                               partialIcon.height() + compositeIcon.height());
2710

2711
            QPainter qp(&compositeIcon);
2712
            qp.drawImage(0, oldHeight, partialIcon);
2713

2714
            lastVPad = thisVPad;
2715
        }
2716

2717
        // Add bounding boxes for the icon we just rendered to boundingBoxes
2718
        std::vector<int>::iterator id = ids.begin();
2719
        std::set<int> nextIds;
2720
        for (std::vector<QRect>::iterator bb = boundingBoxesVec.begin();
2721
             bb != boundingBoxesVec.end();
2722
             ++bb) {
2723
            nextIds.clear();
2724

2725
            if (bb == boundingBoxesVec.begin()) {
2726
                // The first bounding box is for the icon at left, so assign
2727
                // all IDs for that type of constraint to the icon.
2728
                for (std::vector<int>::iterator j = ids.begin(); j != ids.end(); ++j) {
2729
                    nextIds.insert(*j);
2730
                }
2731
            }
2732
            else {
2733
                nextIds.insert(*(id++));
2734
            }
2735

2736
            ConstrIconBB newBB(bb->adjusted(0, oldHeight, 0, oldHeight), nextIds);
2737

2738
            boundingBoxes.push_back(newBB);
2739
        }
2740
    }
2741

2742
    combinedConstrBoxes[idString] = boundingBoxes;
2743
    thisInfo->string.setValue(idString.toLatin1().data());
2744
    sendConstraintIconToCoin(compositeIcon, thisDest);
2745
}
2746

2747

2748
/// Note: labels, labelColors, and boundingBoxes are all
2749
/// assumed to be the same length.
2750
QImage EditModeConstraintCoinManager::renderConstrIcon(const QString& type,
2751
                                                       const QColor& iconColor,
2752
                                                       const QStringList& labels,
2753
                                                       const QList<QColor>& labelColors,
2754
                                                       double iconRotation,
2755
                                                       std::vector<QRect>* boundingBoxes,
2756
                                                       int* vPad)
2757
{
2758
    // Constants to help create constraint icons
2759
    QString joinStr = QString::fromLatin1(", ");
2760

2761
    QPixmap pxMap;
2762
    std::stringstream constraintName;
2763
    constraintName << type.toLatin1().data()
2764
                   << drawingParameters.constraintIconSize;  // allow resizing by embedding size
2765
    if (!Gui::BitmapFactory().findPixmapInCache(constraintName.str().c_str(), pxMap)) {
2766
        pxMap = Gui::BitmapFactory().pixmapFromSvg(
2767
            type.toLatin1().data(),
2768
            QSizeF(drawingParameters.constraintIconSize, drawingParameters.constraintIconSize));
2769
        Gui::BitmapFactory().addPixmapToCache(constraintName.str().c_str(),
2770
                                              pxMap);  // Cache for speed, avoiding pixmapFromSvg
2771
    }
2772
    QImage icon = pxMap.toImage();
2773

2774
    QFont font = ViewProviderSketchCoinAttorney::getApplicationFont(viewProvider);
2775
    font.setPixelSize(static_cast<int>(1.0 * drawingParameters.constraintIconSize));
2776
    font.setBold(true);
2777
    QFontMetrics qfm = QFontMetrics(font);
2778

2779
    int labelWidth = qfm.boundingRect(labels.join(joinStr)).width();
2780
    // See Qt docs on qRect::bottom() for explanation of the +1
2781
    int pxBelowBase = qfm.boundingRect(labels.join(joinStr)).bottom() + 1;
2782

2783
    if (vPad) {
2784
        *vPad = pxBelowBase;
2785
    }
2786

2787
    QTransform rotation;
2788
    rotation.rotate(iconRotation);
2789

2790
    QImage roticon = icon.transformed(rotation);
2791
    QImage image = roticon.copy(0, 0, roticon.width() + labelWidth, roticon.height() + pxBelowBase);
2792

2793
    // Make a bounding box for the icon
2794
    if (boundingBoxes) {
2795
        boundingBoxes->push_back(QRect(0, 0, roticon.width(), roticon.height()));
2796
    }
2797

2798
    // Render the Icons
2799
    QPainter qp(&image);
2800
    qp.setCompositionMode(QPainter::CompositionMode_SourceIn);
2801
    qp.fillRect(roticon.rect(), iconColor);
2802

2803
    // Render constraint label if necessary
2804
    if (!labels.join(QString()).isEmpty()) {
2805
        qp.setCompositionMode(QPainter::CompositionMode_SourceOver);
2806
        qp.setFont(font);
2807

2808
        int cursorOffset = 0;
2809

2810
        // In Python: "for label, color in zip(labels, labelColors):"
2811
        QStringList::const_iterator labelItr;
2812
        QString labelStr;
2813
        QList<QColor>::const_iterator colorItr;
2814
        QRect labelBB;
2815
        for (labelItr = labels.begin(), colorItr = labelColors.begin();
2816
             labelItr != labels.end() && colorItr != labelColors.end();
2817
             ++labelItr, ++colorItr) {
2818

2819
            qp.setPen(*colorItr);
2820

2821
            if (labelItr + 1 == labels.end()) {  // if this is the last label
2822
                labelStr = *labelItr;
2823
            }
2824
            else {
2825
                labelStr = *labelItr + joinStr;
2826
            }
2827

2828
            // Note: text can sometimes draw to the left of the starting
2829
            //       position, eg italic fonts.  Check QFontMetrics
2830
            //       documentation for more info, but be mindful if the
2831
            //       icon.width() is ever very small (or removed).
2832
            qp.drawText(icon.width() + cursorOffset, icon.height(), labelStr);
2833

2834
            if (boundingBoxes) {
2835
                labelBB = qfm.boundingRect(labelStr);
2836
                labelBB.moveTo(icon.width() + cursorOffset,
2837
                               icon.height() - qfm.height() + pxBelowBase);
2838
                boundingBoxes->push_back(labelBB);
2839
            }
2840

2841
            cursorOffset += Gui::QtTools::horizontalAdvance(qfm, labelStr);
2842
        }
2843
    }
2844

2845
    return image;
2846
}
2847

2848
void EditModeConstraintCoinManager::drawTypicalConstraintIcon(const constrIconQueueItem& i)
2849
{
2850
    QColor color = constrColor(i.constraintId);
2851

2852
    QImage image = renderConstrIcon(i.type,
2853
                                    color,
2854
                                    QStringList(i.label),
2855
                                    QList<QColor>() << color,
2856
                                    i.iconRotation);
2857

2858
    i.infoPtr->string.setValue(QString::number(i.constraintId).toLatin1().data());
2859
    sendConstraintIconToCoin(image, i.destination);
2860
}
2861

2862
QString EditModeConstraintCoinManager::iconTypeFromConstraint(Constraint* constraint)
2863
{
2864
    /*! TODO: Consider pushing this functionality up into Constraint
2865
     *
2866
     Abdullah: Please, don't. An icon is visualisation information and
2867
     does not belong in App, but in Gui. Rather consider refactoring it
2868
     in a separate class dealing with visualisation of constraints.*/
2869

2870
    switch (constraint->Type) {
2871
        case Horizontal:
2872
            return QString::fromLatin1("Constraint_Horizontal");
2873
        case Vertical:
2874
            return QString::fromLatin1("Constraint_Vertical");
2875
        case PointOnObject:
2876
            return QString::fromLatin1("Constraint_PointOnObject");
2877
        case Tangent:
2878
            return QString::fromLatin1("Constraint_Tangent");
2879
        case Parallel:
2880
            return QString::fromLatin1("Constraint_Parallel");
2881
        case Perpendicular:
2882
            return QString::fromLatin1("Constraint_Perpendicular");
2883
        case Equal:
2884
            return QString::fromLatin1("Constraint_EqualLength");
2885
        case Symmetric:
2886
            return QString::fromLatin1("Constraint_Symmetric");
2887
        case SnellsLaw:
2888
            return QString::fromLatin1("Constraint_SnellsLaw");
2889
        case Block:
2890
            return QString::fromLatin1("Constraint_Block");
2891
        default:
2892
            return QString();
2893
    }
2894
}
2895

2896
void EditModeConstraintCoinManager::sendConstraintIconToCoin(const QImage& icon,
2897
                                                             SoImage* soImagePtr)
2898
{
2899
    SoSFImage icondata = SoSFImage();
2900

2901
    Gui::BitmapFactory().convert(icon, icondata);
2902

2903
    SbVec2s iconSize(icon.width(), icon.height());
2904

2905
    int four = 4;
2906
    soImagePtr->image.setValue(iconSize, 4, icondata.getValue(iconSize, four));
2907

2908
    // Set Image Alignment to Center
2909
    soImagePtr->vertAlignment = SoImage::HALF;
2910
    soImagePtr->horAlignment = SoImage::CENTER;
2911
}
2912

2913
void EditModeConstraintCoinManager::clearCoinImage(SoImage* soImagePtr)
2914
{
2915
    soImagePtr->setToDefaults();
2916
}
2917

2918
QColor EditModeConstraintCoinManager::constrColor(int constraintId)
2919
{
2920
    auto toQColor = [](auto sbcolor) -> QColor {
2921
        return QColor((int)(sbcolor[0] * 255.0f),
2922
                      (int)(sbcolor[1] * 255.0f),
2923
                      (int)(sbcolor[2] * 255.0f));
2924
    };
2925

2926
    const auto constraints = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
2927

2928
    if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, constraintId)) {
2929
        return toQColor(drawingParameters.PreselectColor);
2930
    }
2931
    else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId)) {
2932
        return toQColor(drawingParameters.SelectColor);
2933
    }
2934
    else if (!constraints[constraintId]->isActive) {
2935
        return toQColor(drawingParameters.DeactivatedConstrDimColor);
2936
    }
2937
    else if (!constraints[constraintId]->isDriving) {
2938
        return toQColor(drawingParameters.NonDrivingConstrDimColor);
2939
    }
2940
    else {
2941
        return toQColor(drawingParameters.ConstrIcoColor);
2942
    }
2943
}
2944

2945
int EditModeConstraintCoinManager::constrColorPriority(int constraintId)
2946
{
2947
    if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, constraintId)) {
2948
        return 3;
2949
    }
2950
    else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId)) {
2951
        return 2;
2952
    }
2953
    else {
2954
        return 1;
2955
    }
2956
}
2957

2958
SoSeparator* EditModeConstraintCoinManager::getConstraintIdSeparator(int i)
2959
{
2960
    return dynamic_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
2961
}
2962

2963
void EditModeConstraintCoinManager::createEditModeInventorNodes()
2964
{
2965
    // group node for the Constraint visual +++++++++++++++++++++++++++++++++++
2966
    SoMaterialBinding* MtlBind = new SoMaterialBinding;
2967
    MtlBind->setName("ConstraintMaterialBinding");
2968
    MtlBind->value = SoMaterialBinding::OVERALL;
2969
    editModeScenegraphNodes.EditRoot->addChild(MtlBind);
2970

2971
    // use small line width for the Constraints
2972
    editModeScenegraphNodes.ConstraintDrawStyle = new SoDrawStyle;
2973
    editModeScenegraphNodes.ConstraintDrawStyle->setName("ConstraintDrawStyle");
2974
    editModeScenegraphNodes.ConstraintDrawStyle->lineWidth =
2975
        1 * drawingParameters.pixelScalingFactor;
2976
    editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.ConstraintDrawStyle);
2977

2978
    // add the group where all the constraints has its SoSeparator
2979
    editModeScenegraphNodes.constrGrpSelect =
2980
        new SoPickStyle();  // used to toggle constraints selectability
2981
    editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::SHAPE);
2982
    editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.constrGrpSelect);
2983
    setConstraintSelectability();  // Ensure default value;
2984

2985
    editModeScenegraphNodes.constrGroup = new SmSwitchboard();
2986
    editModeScenegraphNodes.constrGroup->setName("ConstraintGroup");
2987
    editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.constrGroup);
2988

2989
    SoPickStyle* ps = new SoPickStyle();  // used to following nodes aren't impacted
2990
    ps->style.setValue(SoPickStyle::SHAPE);
2991
    editModeScenegraphNodes.EditRoot->addChild(ps);
2992
}
2993

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

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

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

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