1
/***************************************************************************
2
* Copyright (c) 2021 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
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. *
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. *
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 *
21
***************************************************************************/
23
#include "PreCompiled.h"
26
#include <QRegularExpression>
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_
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>
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>
61
#include "EditModeConstraintCoinManager.h"
62
#include "SoZoomTranslation.h"
64
#include "ViewProviderSketch.h"
65
#include "ViewProviderSketchCoinAttorney.h"
69
using namespace SketcherGui;
70
using namespace Sketcher;
72
//**************************** EditModeConstraintCoinManager class ******************************
74
EditModeConstraintCoinManager::EditModeConstraintCoinManager(
75
ViewProviderSketch& vp,
76
DrawingParameters& drawingParams,
77
GeometryLayerParameters& geometryLayerParams,
78
ConstraintParameters& constraintParams,
79
EditModeScenegraphNodes& editModeScenegraph,
82
, drawingParameters(drawingParams)
83
, geometryLayerParameters(geometryLayerParams)
84
, constraintParameters(constraintParams)
85
, editModeScenegraphNodes(editModeScenegraph)
86
, coinMapping(coinMap)
89
EditModeConstraintCoinManager::~EditModeConstraintCoinManager()
92
void EditModeConstraintCoinManager::updateVirtualSpace()
94
const std::vector<Sketcher::Constraint*>& constrlist =
95
ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
97
bool isshownvirtualspace = ViewProviderSketchCoinAttorney::isShownVirtualSpace(viewProvider);
99
if (constrlist.size() == vConstrType.size()) {
101
editModeScenegraphNodes.constrGroup->enable.setNum(constrlist.size());
103
SbBool* sws = editModeScenegraphNodes.constrGroup->enable.startEditing();
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
111
editModeScenegraphNodes.constrGroup->enable.finishEditing();
115
void EditModeConstraintCoinManager::processConstraints(const GeoListFacade& geolistfacade)
117
const auto& constrlist = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
119
auto zConstrH = ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider)
120
* drawingParameters.zConstr;
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);
129
int extGeoCount = geolistfacade.getExternalCount();
130
int intGeoCount = geolistfacade.getInternalCount();
132
// reset point if the constraint type has changed
134
// check if a new constraint arrived
135
if (constrlist.size() != vConstrType.size()) {
136
rebuildConstraintNodes(geolistfacade);
139
assert(int(constrlist.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
140
assert(int(vConstrType.size()) == editModeScenegraphNodes.constrGroup->getNumChildren());
142
// update the virtual space
143
updateVirtualSpace();
145
auto getNormal = [](const GeoListFacade& geolistfacade,
147
const Base::Vector3d& pointoncurve) {
148
auto geom = geolistfacade.getGeometryFromGeoId(geoid);
149
auto curve = dynamic_cast<const Part::GeomCurve*>(geom);
151
auto line = dynamic_cast<const Part::GeomLineSegment*>(curve);
154
Base::Vector3d linedir = line->getEndPoint() - line->getStartPoint();
155
return Base::Vector3d(-linedir.y, linedir.x, 0);
158
Base::Vector3d normal;
160
if (!(curve && curve->normalAt(pointoncurve, normal))) {
161
normal = Base::Vector3d(1, 0, 0);
164
catch (const Base::CADKernelError&) {
165
normal = Base::Vector3d(1, 0, 0);
172
// go through the constraints and update the position
174
for (std::vector<Sketcher::Constraint*>::const_iterator it = constrlist.begin();
175
it != constrlist.end();
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
181
// TODO: The 'goto' here is unsafe as it can happen that we cause an endless loop (see
185
try { // because calculateNormalAtPoint, used in there, can throw
186
// root separator for this constraint
188
static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
189
const Constraint* Constr = *it;
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
200
// distinguish different constraint types to build up
201
switch (Constr->Type) {
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
207
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
208
bool alignment = Constr->Type != Block && Constr->Second != GeoEnum::GeoUndef;
211
const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
214
// Vertical & Horiz can only be a GeomLineSegment, but Blocked can be
216
Base::Vector3d midpos;
220
if (geo->is<Part::GeomLineSegment>()) {
221
const Part::GeomLineSegment* lineSeg =
222
static_cast<const Part::GeomLineSegment*>(geo);
224
// calculate the half distance between the start and endpoint
225
midpos = ((lineSeg->getEndPoint() + lineSeg->getStartPoint()) / 2);
227
// Get a set of vectors perpendicular and tangential to these
228
dir = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Normalize();
230
norm = Base::Vector3d(-dir.y, dir.x, 0);
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);
237
std::vector<Base::Vector3d> poles = bsp->getPoles();
239
// Move center of gravity towards start not to collide with bspline
240
// degree information.
241
double ws = 1.0 / poles.size();
244
for (std::vector<Base::Vector3d>::iterator it = poles.begin();
251
midpos /= poles.size();
253
dir = (bsp->getEndPoint() - bsp->getStartPoint()).Normalize();
254
norm = Base::Vector3d(-dir.y, dir.x, 0);
257
double ra = 0, rb = 0;
258
double angle, // rotation of object as a whole
259
angleplus = 0.; // arc angle (t parameter for ellipses)
261
if (geo->is<Part::GeomCircle>()) {
262
const Part::GeomCircle* circle =
263
static_cast<const Part::GeomCircle*>(geo);
264
ra = circle->getRadius();
266
midpos = circle->getCenter();
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();
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();
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();
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();
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();
326
if (geo->is<Part::GeomEllipse>() || geo->is<Part::GeomArcOfEllipse>()
327
|| geo->is<Part::GeomArcOfHyperbola>()) {
329
Base::Vector3d majDir, minDir, rvec;
330
majDir = Base::Vector3d(cos(angle),
332
0); // direction of major axis of ellipse
333
minDir = Base::Vector3d(-majDir.y,
335
0); // direction of minor axis of ellipse
337
(ra * cos(angleplus)) * majDir + (rb * sin(angleplus)) * minDir;
341
dir = Base::Vector3d(
344
0); // DeepSOIC: I'm not sure what dir is supposed to mean.
347
norm = Base::Vector3d(cos(angle), sin(angle), 0);
348
dir = Base::Vector3d(-norm.y, norm.x, 0);
353
Base::Vector3d relpos = seekConstraintPosition(
358
editModeScenegraphNodes.constrGroup->getChild(i));
360
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
361
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
364
SbVec3f(midpos.x, midpos.y, zConstrH); // Absolute Reference
366
// Reference Position that is scaled according to zoom
367
translation->translation = SbVec3f(relpos.x, relpos.y, 0);
370
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
371
assert(Constr->FirstPos != Sketcher::PointPos::none
372
&& Constr->SecondPos != Sketcher::PointPos::none);
374
Base::Vector3d midpos1, dir1, norm1;
375
Base::Vector3d midpos2, dir2, norm2;
377
midpos1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
378
midpos2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
380
dir1 = (midpos2 - midpos1).Normalize();
382
norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.);
385
Base::Vector3d relpos1 = seekConstraintPosition(
390
editModeScenegraphNodes.constrGroup->getChild(i));
392
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
393
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
395
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
396
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
398
Base::Vector3d relpos2 = seekConstraintPosition(
403
editModeScenegraphNodes.constrGroup->getChild(i));
405
Base::Vector3d secondPos = midpos2 - midpos1;
407
translation = static_cast<SoZoomTranslation*>(sep->getChild(
408
static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
410
translation->abPos = SbVec3f(secondPos.x, secondPos.y, zConstrH);
411
translation->translation =
412
SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
415
case Perpendicular: {
416
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
417
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
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
426
if (Constr->Third != GeoEnum::GeoUndef || // perpty via point
428
!= Sketcher::PointPos::none) { // endpoint-to-curve or
429
// endpoint-to-endpoint perpty
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) {
439
ptGeoId = Constr->Second;
440
ptPosId = Constr->SecondPos;
441
if (ptPosId != Sketcher::PointPos::none) {
444
ptGeoId = Constr->Third;
445
ptPosId = Constr->ThirdPos;
446
if (ptPosId != Sketcher::PointPos::none) {
449
assert(0); // no point found!
452
midpos1 = geolistfacade.getPoint(ptGeoId, ptPosId);
454
norm1 = getNormal(geolistfacade, Constr->Second, midpos1);
456
// TODO: Check the method above. This was the old one making use of the
458
// norm1 = getSolvedSketch().calculateNormalAtPoint(Constr->Second,
459
// midpos1.x, midpos1.y);
463
dir1.RotateZ(-M_PI / 2.0);
465
else if (Constr->FirstPos == Sketcher::PointPos::none) {
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);
472
(lineSeg1->getEndPoint() - lineSeg1->getStartPoint()).Normalize();
473
norm1 = Base::Vector3d(-dir1.y, dir1.x, 0.);
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;
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;
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);
501
(lineSeg2->getEndPoint() - lineSeg2->getStartPoint()).Normalize();
502
norm2 = Base::Vector3d(-dir2.y, dir2.x, 0.);
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;
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;
527
Base::Vector3d relpos1 =
528
seekConstraintPosition(midpos1,
532
editModeScenegraphNodes.constrGroup->getChild(i));
534
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
535
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
537
translation->abPos = SbVec3f(midpos1.x, midpos1.y, zConstrH);
538
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
541
Base::Vector3d relpos2 = seekConstraintPosition(
546
editModeScenegraphNodes.constrGroup->getChild(i));
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);
559
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
560
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
562
const Part::Geometry* geo1 = geolistfacade.getGeometryFromGeoId(Constr->First);
563
const Part::Geometry* geo2 = geolistfacade.getGeometryFromGeoId(Constr->Second);
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;
571
angle1plus = 0., angle2,
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();
580
midpos1 = circle->getCenter();
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();
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();
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();
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();
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();
640
if (geo2->is<Part::GeomCircle>()) {
641
const Part::GeomCircle* circle =
642
static_cast<const Part::GeomCircle*>(geo2);
643
r2a = circle->getRadius();
645
midpos2 = circle->getCenter();
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();
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();
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();
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();
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();
705
if (geo1->is<Part::GeomEllipse>() || geo1->is<Part::GeomArcOfEllipse>()
706
|| geo1->is<Part::GeomArcOfHyperbola>()) {
708
Base::Vector3d majDir, minDir, rvec;
709
majDir = Base::Vector3d(cos(angle1),
711
0); // direction of major axis of ellipse
712
minDir = Base::Vector3d(-majDir.y,
714
0); // direction of minor axis of ellipse
715
rvec = (r1a * cos(angle1plus)) * majDir
716
+ (r1b * sin(angle1plus)) * minDir;
720
dir1 = Base::Vector3d(
723
0); // DeepSOIC: I'm not sure what dir is supposed to mean.
726
norm1 = Base::Vector3d(cos(angle1), sin(angle1), 0);
727
dir1 = Base::Vector3d(-norm1.y, norm1.x, 0);
728
midpos1 += r1a * norm1;
732
if (geo2->is<Part::GeomEllipse>() || geo2->is<Part::GeomArcOfEllipse>()
733
|| geo2->is<Part::GeomArcOfHyperbola>()) {
735
Base::Vector3d majDir, minDir, rvec;
736
majDir = Base::Vector3d(cos(angle2),
738
0); // direction of major axis of ellipse
739
minDir = Base::Vector3d(-majDir.y,
741
0); // direction of minor axis of ellipse
742
rvec = (r2a * cos(angle2plus)) * majDir
743
+ (r2b * sin(angle2plus)) * minDir;
747
dir2 = Base::Vector3d(-rvec.y, rvec.x, 0);
750
norm2 = Base::Vector3d(cos(angle2), sin(angle2), 0);
751
dir2 = Base::Vector3d(-norm2.y, norm2.x, 0);
752
midpos2 += r2a * norm2;
755
else { // Parallel can only apply to a GeomLineSegment
760
const Part::GeomLineSegment* lineSeg1 =
761
static_cast<const Part::GeomLineSegment*>(geo1);
762
const Part::GeomLineSegment* lineSeg2 =
763
static_cast<const Part::GeomLineSegment*>(geo2);
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.);
775
Base::Vector3d relpos1 =
776
seekConstraintPosition(midpos1,
780
editModeScenegraphNodes.constrGroup->getChild(i));
781
Base::Vector3d relpos2 =
782
seekConstraintPosition(midpos2,
786
editModeScenegraphNodes.constrGroup->getChild(i));
788
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
789
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
792
SbVec3f(midpos1.x, midpos1.y, zConstrH); // Absolute Reference
794
// Reference Position that is scaled according to zoom
795
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
797
Base::Vector3d secondPos = midpos2 - midpos1;
799
translation = static_cast<SoZoomTranslation*>(sep->getChild(
800
static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
803
SbVec3f(secondPos.x, secondPos.y, zConstrH); // Absolute Reference
805
// Reference Position that is scaled according to zoom
806
translation->translation =
807
SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
813
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
815
double helperStartAngle1 = 0.; // for arc helpers
816
double helperStartAngle2 = 0.;
817
double helperRange1 = 0.;
818
double helperRange2 = 0.;
821
Base::Vector3d center1(0., 0., 0.);
822
Base::Vector3d center2(0., 0., 0.);
826
// pnt1 will be initialized to (0,0,0) if only one point is given
827
auto pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
829
Base::Vector3d pnt2(0., 0., 0.);
831
if (Constr->SecondPos != Sketcher::PointPos::none) {
832
// point to point distance
833
pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
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)) {
840
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo2);
841
Base::Vector3d l2p1 = lineSeg->getStartPoint();
842
Base::Vector3d l2p2 = lineSeg->getEndPoint();
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);
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;
858
pnt2 = ct + dir * radius;
861
else if (isCircleOrArc(*geo2)) {
862
if (Constr->FirstPos != Sketcher::PointPos::none) {
863
// point to circular distance
864
auto [rad, ct] = getRadiusCenterCircleArc(geo2);
866
Base::Vector3d v = pnt1 - ct;
870
else if (isCircleOrArc(*geo1)) {
871
// circular to circular distance
872
GetCirclesMinimalDistance(geo1, geo2, pnt1, pnt2);
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);
884
else if (Constr->First != GeoEnum::GeoUndef) {
885
auto geo = geolistfacade.getGeometryFromGeoId(Constr->First);
886
if (isLineSegment(*geo)) {
888
auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo);
889
pnt1 = lineSeg->getStartPoint();
890
pnt2 = lineSeg->getEndPoint();
892
else if (isArcOfCircle(*geo)) {
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();
901
double startAngle, endAngle;
902
arc->getRange(startAngle, endAngle, /*emulateCCW=*/false);
904
asciiText->datumtype = SoDatumLabel::ARCLENGTH;
905
asciiText->param1 = Constr->LabelDistance;
907
SbString(std::string("◠ ")
908
.append(getPresentationString(Constr).toUtf8())
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();
927
int index = static_cast<int>(ConstraintNodePosition::DatumLabelIndex);
928
auto* asciiText = static_cast<SoDatumLabel*>(sep->getChild(index)); // NOLINT
930
// Get presentation string (w/o units if option is set)
932
SbString(getPresentationString(Constr).toUtf8().constData());
934
if (Constr->Type == Distance) {
935
asciiText->datumtype = SoDatumLabel::DISTANCE;
937
else if (Constr->Type == DistanceX) {
938
asciiText->datumtype = SoDatumLabel::DISTANCEX;
940
else if (Constr->Type == DistanceY) {
941
asciiText->datumtype = SoDatumLabel::DISTANCEY;
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);
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();
955
toVector2d(isLineSegment(*geo2) ? pnt2 - center1 : pnt1 - center1)
957
double startAngle, endAngle;
958
arc->getRange(startAngle, endAngle, /*emulateCCW=*/true);
960
findHelperAngles(helperStartAngle1,
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
970
helperStartAngle1 = endAngle;
971
helperRange1 = 2 * M_PI - (endAngle - startAngle);
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();
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);
986
findHelperAngles(helperStartAngle2,
992
if (helperRange2 != 0.) {
993
helperStartAngle2 = endAngle;
994
helperRange2 = 2 * M_PI - (endAngle - startAngle);
1001
// Assign the Datum Points
1002
asciiText->pnts.setNum(numPoints);
1003
SbVec3f* verts = asciiText->pnts.startEditing();
1005
verts[0] = SbVec3f(pnt1.x, pnt1.y, zConstrH);
1006
verts[1] = SbVec3f(pnt2.x, pnt2.y, zConstrH);
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;
1016
verts[2] = SbVec3f(center2.x, center2.y, zConstrH);
1017
asciiText->param3 = helperStartAngle2;
1018
asciiText->param4 = helperRange2;
1019
asciiText->param5 = radius2;
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;
1028
asciiText->param6 = 0.;
1029
asciiText->param7 = 0.;
1030
asciiText->param8 = 0.;
1034
asciiText->param3 = 0.;
1035
asciiText->param4 = 0.;
1036
asciiText->param5 = 0.;
1039
asciiText->pnts.finishEditing();
1041
// Assign the Label Distance
1042
asciiText->param1 = Constr->LabelDistance;
1043
asciiText->param2 = Constr->LabelPosition;
1048
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1049
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
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
1057
!= Sketcher::PointPos::none) // endpoint-to-curve or
1058
// endpoint-to-endpoint tangency
1061
// find the point of tangency/point that is on object
1062
// just any point among first/second/third should be OK
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) {
1071
ptGeoId = Constr->Second;
1072
ptPosId = Constr->SecondPos;
1073
if (ptPosId != Sketcher::PointPos::none) {
1076
ptGeoId = Constr->Third;
1077
ptPosId = Constr->ThirdPos;
1078
if (ptPosId != Sketcher::PointPos::none) {
1081
assert(0); // no point found!
1084
pos = geolistfacade.getPoint(ptGeoId, ptPosId);
1085
auto norm = getNormal(geolistfacade, Constr->Second, pos);
1087
// TODO: Check substitution
1088
// Base::Vector3d norm =
1089
// getSolvedSketch().calculateNormalAtPoint(Constr->Second, pos.x, pos.y);
1091
Base::Vector3d dir = norm;
1092
dir.RotateZ(-M_PI / 2.0);
1094
relPos = seekConstraintPosition(
1099
editModeScenegraphNodes.constrGroup->getChild(i));
1101
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1102
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1104
translation->abPos = SbVec3f(pos.x, pos.y, zConstrH); // Absolute Reference
1105
translation->translation = SbVec3f(relPos.x, relPos.y, 0);
1107
else if (Constr->Type == Tangent) {
1109
const Part::Geometry* geo1 =
1110
geolistfacade.getGeometryFromGeoId(Constr->First);
1111
const Part::Geometry* geo2 =
1112
geolistfacade.getGeometryFromGeoId(Constr->Second);
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);
1132
Base::Vector3d relpos1 = seekConstraintPosition(
1137
editModeScenegraphNodes.constrGroup->getChild(i));
1138
Base::Vector3d relpos2 = seekConstraintPosition(
1143
editModeScenegraphNodes.constrGroup->getChild(i));
1145
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1146
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1148
translation->abPos =
1149
SbVec3f(midpos1.x, midpos1.y, zConstrH); // Absolute Reference
1150
translation->translation = SbVec3f(relpos1.x, relpos1.y, 0);
1152
Base::Vector3d secondPos = midpos2 - midpos1;
1154
translation = static_cast<SoZoomTranslation*>(sep->getChild(
1155
static_cast<int>(ConstraintNodePosition::SecondTranslationIndex)));
1157
translation->abPos = SbVec3f(secondPos.x,
1159
zConstrH); // Absolute Reference
1160
translation->translation =
1161
SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
1165
else if (geo2->is<Part::GeomLineSegment>()) {
1166
std::swap(geo1, geo2);
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
1180
(circle->getCenter() - lineSeg->getStartPoint()) * dir;
1182
pos = lineSeg->getStartPoint() + dir * length;
1183
relPos = norm * 1; // TODO Huh?
1185
else if (geo2->is<Part::GeomEllipse>()
1186
|| geo2->is<Part::GeomArcOfEllipse>()) {
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();
1195
const Part::GeomArcOfEllipse* aoc =
1196
static_cast<const Part::GeomArcOfEllipse*>(geo2);
1197
center = aoc->getCenter();
1200
// tangency between a line and an ellipse
1201
float length = (center - lineSeg->getStartPoint()) * dir;
1203
pos = lineSeg->getStartPoint() + dir * length;
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;
1212
pos = lineSeg->getStartPoint() + dir * length;
1213
relPos = norm * 1; // TODO Huh?
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();
1228
else if (geo2->is<Part::GeomCircle>()) {
1229
std::swap(geo1, geo2);
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();
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();
1255
auto translation = static_cast<SoZoomTranslation*>(sep->getChild(
1256
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1258
translation->abPos = SbVec3f(pos.x, pos.y, zConstrH); // Absolute Reference
1259
translation->translation = SbVec3f(relPos.x, relPos.y, 0);
1263
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1264
assert(Constr->Second >= -extGeoCount && Constr->Second < intGeoCount);
1266
Base::Vector3d pnt1 = geolistfacade.getPoint(Constr->First, Constr->FirstPos);
1267
Base::Vector3d pnt2 = geolistfacade.getPoint(Constr->Second, Constr->SecondPos);
1269
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1270
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1271
SbVec3f dir = (p2 - p1);
1273
SbVec3f norm(-dir[1], dir[0], 0);
1275
SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1276
sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1277
asciiText->datumtype = SoDatumLabel::SYMMETRIC;
1279
asciiText->pnts.setNum(2);
1280
SbVec3f* verts = asciiText->pnts.startEditing();
1285
asciiText->pnts.finishEditing();
1287
auto translation = static_cast<SoTranslation*>(sep->getChild(
1288
static_cast<int>(ConstraintNodePosition::FirstTranslationIndex)));
1290
translation->translation = (p1 + p2) / 2;
1293
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1294
assert((Constr->Second >= -extGeoCount && Constr->Second < intGeoCount)
1295
|| Constr->Second == GeoEnum::GeoUndef);
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)) {
1312
auto* line1 = static_cast<const Part::GeomLineSegment*>(geo1);
1313
auto* line2 = static_cast<const Part::GeomLineSegment*>(geo2);
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();
1330
// line-line intersection
1331
Base::Vector3d intersection;
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
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();
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.);
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.);
1364
p0.setValue(intersection.x, intersection.y, 0.);
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);
1373
endLineLength1 = vl12.Dot(dir1) > 0 ? vl12.Length()
1374
: vl1.Dot(dir1) < 0 ? -vl1.Length()
1376
endLineLength2 = vl22.Dot(dir2) > 0 ? vl22.Length()
1377
: vl2.Dot(dir2) < 0 ? -vl2.Length()
1380
else { // angle-via-point
1382
geolistfacade.getPoint(Constr->Third, Constr->ThirdPos);
1383
p0 = SbVec3f(p.x, p.y, 0);
1384
dir1 = getNormal(geolistfacade, Constr->First, p);
1386
// dir1 = getSolvedSketch().calculateNormalAtPoint(Constr->First,
1388
dir1.RotateZ(-M_PI / 2); // convert to vector of tangency by rotating
1389
dir2 = getNormal(geolistfacade, Constr->Second, p);
1391
// dir2 = getSolvedSketch().calculateNormalAtPoint(Constr->Second,
1393
dir2.RotateZ(-M_PI / 2);
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);
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.;
1412
Base::Vector3d dir = lineSeg->getEndPoint() - lineSeg->getStartPoint();
1414
range = atan2(dir.y, dir.x);
1416
else if (geo->is<Part::GeomArcOfCircle>()) {
1417
auto* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
1418
p0 = Base::convertTo<SbVec3f>(arc->getCenter());
1420
endLineLength1 = 2 * distance - arc->getRadius();
1421
endLineLength2 = endLineLength1;
1424
arc->getRange(startangle, endangle, /*emulateCCWXY=*/true);
1425
range = endangle - startangle;
1435
SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1436
sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
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;
1446
asciiText->pnts.setNum(2);
1447
SbVec3f* verts = asciiText->pnts.startEditing();
1451
asciiText->pnts.finishEditing();
1455
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1457
Base::Vector3d pnt1(0., 0., 0.), pnt2(0., 0., 0.);
1458
double helperStartAngle = 0.;
1459
double helperRange = 0.;
1461
if (Constr->First == GeoEnum::GeoUndef) {
1465
const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
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);
1475
angle = (startAngle + endAngle) / 2;
1478
findHelperAngles(helperStartAngle,
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.);
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;
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.);
1503
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1504
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1506
SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1507
sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1509
// Get display string with units hidden if so requested
1511
SbString(getPresentationString(Constr).toUtf8().constData());
1513
asciiText->datumtype = SoDatumLabel::DIAMETER;
1514
asciiText->param1 = Constr->LabelDistance;
1515
asciiText->param2 = Constr->LabelPosition;
1516
asciiText->param3 = helperStartAngle;
1517
asciiText->param4 = helperRange;
1519
asciiText->pnts.setNum(2);
1520
SbVec3f* verts = asciiText->pnts.startEditing();
1525
asciiText->pnts.finishEditing();
1529
assert(Constr->First >= -extGeoCount && Constr->First < intGeoCount);
1531
Base::Vector3d pnt1(0., 0., 0.), pnt2(0., 0., 0.);
1532
double helperStartAngle = 0.;
1533
double helperRange = 0.;
1535
if (Constr->First == GeoEnum::GeoUndef) {
1538
const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(Constr->First);
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);
1548
angle = (startAngle + endAngle) / 2;
1551
findHelperAngles(helperStartAngle,
1557
pnt1 = arc->getCenter();
1558
pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1560
else if (geo->is<Part::GeomCircle>()) {
1561
auto* circle = static_cast<const Part::GeomCircle*>(geo);
1562
auto gf = GeometryFacade::getFacade(geo);
1566
radius = circle->getRadius();
1568
double angle = (double)Constr->LabelPosition;
1572
pnt1 = circle->getCenter();
1573
pnt2 = pnt1 + radius * Base::Vector3d(cos(angle), sin(angle), 0.);
1579
SbVec3f p1(pnt1.x, pnt1.y, zConstrH);
1580
SbVec3f p2(pnt2.x, pnt2.y, zConstrH);
1582
SoDatumLabel* asciiText = static_cast<SoDatumLabel*>(
1583
sep->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1585
// Get display string with units hidden if so requested
1586
if (Constr->Type == Weight) {
1588
SbString(QString::number(Constr->getValue()).toStdString().c_str());
1592
SbString(getPresentationString(Constr).toUtf8().constData());
1595
asciiText->datumtype = SoDatumLabel::RADIUS;
1596
asciiText->param1 = Constr->LabelDistance;
1597
asciiText->param2 = Constr->LabelPosition;
1598
asciiText->param3 = helperStartAngle;
1599
asciiText->param4 = helperRange;
1601
asciiText->pnts.setNum(2);
1602
SbVec3f* verts = asciiText->pnts.startEditing();
1607
asciiText->pnts.finishEditing();
1609
case Coincident: // nothing to do for coincident
1611
case InternalAlignment:
1612
case NumConstraintTypes:
1616
catch (Base::Exception& e) {
1617
Base::Console().DeveloperError("EditModeConstraintCoinManager",
1618
"Exception during draw: %s\n",
1620
e.ReportException();
1623
Base::Console().DeveloperError("EditModeConstraintCoinManager",
1624
"Exception during draw: unknown\n");
1629
void EditModeConstraintCoinManager::findHelperAngles(double& helperStartAngle,
1630
double& helperRange,
1635
double margin = 0.2; // about 10deg
1637
angle = angle + 2 * M_PI;
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;
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) {
1649
helperStartAngle = angle - margin;
1650
helperRange = startAngle - angle + margin;
1653
if (angle < endAngle) {
1656
helperStartAngle = endAngle;
1657
helperRange = angle - endAngle + margin;
1662
Base::Vector3d EditModeConstraintCoinManager::seekConstraintPosition(const Base::Vector3d& origPos,
1663
const Base::Vector3d& norm,
1664
const Base::Vector3d& dir,
1666
const SoNode* constraint)
1669
auto rp = ViewProviderSketchCoinAttorney::getRayPickAction(viewProvider);
1671
float scaled_step = step * ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider);
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;
1681
// Prevent crash : https://forum.freecad.org/viewtopic.php?f=8&t=65305
1683
return relPos * step;
1686
rp->setRadius(0.1f);
1687
rp->setPickAll(true);
1688
rp->setRay(SbVec3f(freePos.x, freePos.y, -1.f), SbVec3f(0, 0, 1));
1690
rp->apply(editModeScenegraphNodes.constrGroup); // We could narrow it down to just the
1691
// SoGroup containing the constraints
1693
// returns a copy of the point
1694
SoPickedPoint* pp = rp->getPickedPoint();
1695
const SoPickedPointList ppl = rp->getPickedPointList();
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);
1703
// checking if a constraint is the same as the one selected
1704
if (tailFather1 == constraint || tailFather2 == constraint) {
1705
isConstraintAtPosition = false;
1709
isConstraintAtPosition = false;
1712
multiplier *= -1; // search in both sides
1713
if (multiplier >= 0) {
1714
multiplier++; // Increment the multiplier
1717
if (multiplier == 10) {
1718
relPos = norm * 0.5f; // no free position found
1720
return relPos * step;
1723
void EditModeConstraintCoinManager::updateConstraintColor(
1724
const std::vector<Sketcher::Constraint*>& constraints)
1726
// Because coincident constraints are selected using the point color, we need to edit the point
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
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());
1743
editModeScenegraphNodes.CurvesMaterials[l][t]->diffuseColor.startEditing());
1747
int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(),
1748
static_cast<int>(constraints.size()));
1750
// colors of the constraints
1751
for (int i = 0; i < maxNumberOfConstraints; i++) {
1753
static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
1755
// Check Constraint Type
1756
Sketcher::Constraint* constraint = constraints[i];
1757
ConstraintType type = constraint->Type;
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
1762
if (type != vConstrType[i]) {
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);
1771
// Non DatumLabel Nodes will have a material excluding coincident
1772
bool hasMaterial = false;
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()) {
1779
m = static_cast<SoMaterial*>(s->getChild(matIndex));
1783
auto selectpoint = [this, pcolor, PtNum](int geoid, Sketcher::PointPos pos) {
1785
auto multifieldIndex = coinMapping.getIndexLayer(geoid, pos);
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;
1798
auto selectline = [this, color, CurvNum](int geoid) {
1800
auto multifieldIndex = coinMapping.getIndexLayer(geoid, Sketcher::PointPos::none);
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;
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;
1822
else if (hasMaterial) {
1823
m->diffuseColor = drawingParameters.SelectColor;
1825
else if (type == Sketcher::Coincident) {
1826
selectpoint(constraint->First, constraint->FirstPos);
1827
selectpoint(constraint->Second, constraint->SecondPos);
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);
1840
case HyperbolaFocus:
1842
case BSplineControlPoint:
1843
case BSplineKnotPoint: {
1844
selectpoint(constraint->First, constraint->FirstPos);
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;
1857
else if (hasMaterial) {
1858
m->diffuseColor = drawingParameters.PreselectColor;
1862
if (hasDatumLabel) {
1863
SoDatumLabel* l = static_cast<SoDatumLabel*>(
1864
s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex)));
1866
l->textColor = constraint->isActive
1867
? ViewProviderSketchCoinAttorney::constraintHasExpression(viewProvider, i)
1868
? drawingParameters.ExprBasedConstrDimColor
1869
: (constraint->isDriving ? drawingParameters.ConstrDimColor
1870
: drawingParameters.NonDrivingConstrDimColor)
1871
: drawingParameters.DeactivatedConstrDimColor;
1873
else if (hasMaterial) {
1874
m->diffuseColor = constraint->isActive
1875
? (constraint->isDriving ? drawingParameters.ConstrDimColor
1876
: drawingParameters.NonDrivingConstrDimColor)
1877
: drawingParameters.DeactivatedConstrDimColor;
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();
1890
void EditModeConstraintCoinManager::rebuildConstraintNodes()
1892
auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
1894
rebuildConstraintNodes(geolistfacade);
1897
void EditModeConstraintCoinManager::setConstraintSelectability(bool enabled /* = true */)
1900
editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::SHAPE);
1903
editModeScenegraphNodes.constrGrpSelect->style.setValue(SoPickStyle::UNPICKABLE);
1907
void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade& geolistfacade)
1909
const std::vector<Sketcher::Constraint*>& constrlist =
1910
ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
1913
Gui::coinRemoveAllChildren(editModeScenegraphNodes.constrGroup);
1915
vConstrType.clear();
1917
// Get sketch normal
1918
Base::Vector3d RN(0, 0, 1);
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);
1926
SbVec3f norm(RN.x, RN.y, RN.z);
1928
rebuildConstraintNodes(geolistfacade, constrlist, norm);
1931
void EditModeConstraintCoinManager::rebuildConstraintNodes(
1932
const GeoListFacade& geolistfacade,
1933
const std::vector<Sketcher::Constraint*> constrlist,
1937
for (std::vector<Sketcher::Constraint*>::const_iterator it = constrlist.begin();
1938
it != constrlist.end();
1940
// root separator for one constraint
1941
SoSeparator* sep = new SoSeparator();
1943
// no caching for frequently-changing data structures
1944
sep->renderCaching = SoSeparator::OFF;
1946
// every constrained visual node gets its own material for preselection and selection
1947
SoMaterial* mat = new SoMaterial;
1949
mat->diffuseColor = (*it)->isActive
1950
? ((*it)->isDriving ? drawingParameters.ConstrDimColor
1951
: drawingParameters.NonDrivingConstrDimColor)
1952
: drawingParameters.DeactivatedConstrDimColor;
1955
// distinguish different constraint types to build up
1956
switch ((*it)->Type) {
1964
SoDatumLabel* text = new SoDatumLabel();
1965
text->norm.setValue(norm);
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);
1984
continue; // jump to next constraint
1989
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
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());
2004
// remember the type of this constraint node
2005
vConstrType.push_back((*it)->Type);
2007
case Coincident: // no visual for coincident so far
2008
vConstrType.push_back(Coincident);
2013
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
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());
2028
// remember the type of this constraint node
2029
vConstrType.push_back((*it)->Type);
2034
// #define CONSTRAINT_SEPARATOR_INDEX_MATERIAL_OR_DATUMLABEL 0
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());
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");
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());
2062
vConstrType.push_back((*it)->Type);
2065
SoDatumLabel* arrows = new SoDatumLabel();
2066
arrows->norm.setValue(norm);
2067
arrows->string = "";
2068
arrows->textColor = drawingParameters.ConstrDimColor;
2069
arrows->lineWidth = 2 * drawingParameters.pixelScalingFactor;
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());
2080
vConstrType.push_back((*it)->Type);
2082
case InternalAlignment: {
2083
vConstrType.push_back((*it)->Type);
2086
vConstrType.push_back((*it)->Type);
2089
editModeScenegraphNodes.constrGroup->addChild(sep);
2090
// decrement ref counter again
2096
QString EditModeConstraintCoinManager::getPresentationString(const Constraint* constraint)
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
2106
if (!constraint->isActive) {
2107
return QString::fromLatin1(" ");
2110
// Get the current name parameter string of the constraint
2111
nameStr = QString::fromStdString(constraint->Name);
2113
// Get the current value string including units
2114
valueStr = constraint->getPresentationValue().getUserString(factor, unitStr);
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();
2126
// If this is a supported unit system then define what the base unit is.
2128
case Base::UnitSystem::SI1:
2129
case Base::UnitSystem::MmMin:
2130
baseUnitStr = QString::fromLatin1("mm");
2133
case Base::UnitSystem::SI2:
2134
baseUnitStr = QString::fromLatin1("m");
2137
case Base::UnitSystem::ImperialDecimal:
2138
baseUnitStr = QString::fromLatin1("in");
2141
case Base::UnitSystem::Centimeters:
2142
baseUnitStr = QString::fromLatin1("cm");
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
2161
if (constraint->Type == Sketcher::Diameter) {
2162
valueStr.prepend(QChar(216)); // Diameter sign
2164
else if (constraint->Type == Sketcher::Radius) {
2165
valueStr.prepend(QChar(82)); // Capital letter R
2169
Create the representation string from the user defined format string
2171
%N - the constraint name parameter
2172
%V - the value of the dimensional constraint, including any unit characters
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);
2182
// user defined format string does not contain any valid parameter, using default format
2184
presentationStr = nameStr + QLatin1String(" = ") + valueStr;
2187
return presentationStr;
2193
std::set<int> EditModeConstraintCoinManager::detectPreselectionConstr(const SoPickedPoint* Point,
2194
const SbVec2s& cursorPos)
2196
std::set<int> constrIndices;
2197
SoPath* path = Point->getPath();
2199
// Get the constraints' tail
2200
SoNode* tailFather2 = path->getNode(path->getLength() - 3);
2202
if (tailFather2 != editModeScenegraphNodes.constrGroup) {
2203
return constrIndices;
2207
SoNode* tail = path->getTail();
2208
SoNode* tailFather = path->getNode(path->getLength() - 2);
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;
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)));
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)));
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
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);
2242
// The use of the Path to get the screen coordinates to get the icon center
2243
// coordinates does not work.
2245
// This implementation relies on the use of ZoomTranslation to get the
2246
// absolute and relative positions of the icons.
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.
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
2260
auto translation = static_cast<SoZoomTranslation*>(
2261
static_cast<SoSeparator*>(tailFather)
2262
->getChild(static_cast<int>(
2263
ConstraintNodePosition::FirstTranslationIndex)));
2265
absPos = translation->abPos.getValue();
2267
trans = translation->translation.getValue();
2269
scaleFactor = translation->getScaleFactor();
2273
static_cast<int>(ConstraintNodePosition::FirstIconIndex))) {
2274
Base::Console().Log("SecondIcon\n");
2276
auto translation2 = static_cast<SoZoomTranslation*>(
2277
static_cast<SoSeparator*>(tailFather)
2278
->getChild(static_cast<int>(
2279
ConstraintNodePosition::SecondTranslationIndex)));
2281
absPos += translation2->abPos.getValue();
2283
trans += translation2->translation.getValue();
2285
scaleFactor = translation2->getScaleFactor();
2288
// Only the translation is scaled because this is how SoZoomTranslation
2290
SbVec3f constrPos = absPos + scaleFactor * trans;
2292
SbVec2f iconCoords = ViewProviderSketchCoinAttorney::getScreenCoordinates(
2294
SbVec2f(constrPos[0], constrPos[1]));
2296
// cursorPos is SbVec2s in screen coordinates coming from SoEvent in
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;
2306
for (ConstrIconBBVec::iterator b =
2307
combinedConstrBoxes[constrIdsStr].begin();
2308
b != combinedConstrBoxes[constrIdsStr].end();
2312
// Useful code to debug coordinates and bounding boxes that does
2313
// not need to be compiled in for any debug operations.
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());*/
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();
2327
constrIndices.insert(*k);
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);
2343
// other constraint icons - eg radius...
2344
constrIndices.clear();
2345
constrIndices.insert(i);
2351
return constrIndices;
2354
SbVec3s EditModeConstraintCoinManager::getDisplayedSize(const SoImage* iconPtr) const
2356
SbVec3s iconSize = iconPtr->image.getValue().getSize();
2358
if (iconPtr->width.getValue() != -1) {
2359
iconSize[0] = iconPtr->width.getValue();
2361
if (iconPtr->height.getValue() != -1) {
2362
iconSize[1] = iconPtr->height.getValue();
2367
// public function that triggers drawing of most constraint icons
2368
void EditModeConstraintCoinManager::drawConstraintIcons()
2370
auto geolistfacade = ViewProviderSketchCoinAttorney::getGeoListFacade(viewProvider);
2372
drawConstraintIcons(geolistfacade);
2375
void EditModeConstraintCoinManager::drawConstraintIcons(const GeoListFacade& geolistfacade)
2377
const std::vector<Sketcher::Constraint*>& constraints =
2378
ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
2380
std::vector<constrIconQueueItem> iconQueue;
2382
int maxNumberOfConstraints = std::min(editModeScenegraphNodes.constrGroup->getNumChildren(),
2383
static_cast<int>(constraints.size()));
2385
for (int constrId = 0; constrId < maxNumberOfConstraints; ++constrId) {
2386
Sketcher::Constraint* constraint = constraints[constrId];
2388
// Check if Icon Should be created
2389
bool multipleIcons = false;
2391
QString icoType = iconTypeFromConstraint(constraint);
2392
if (icoType.isEmpty()) {
2396
if (constraint->Type != vConstrType[constrId]) {
2400
switch (constraint->Type) {
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;
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;
2419
multipleIcons = true;
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;
2429
multipleIcons = true;
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");
2443
// Find the Constraint Icon SoImage Node
2445
static_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(constrId));
2446
int numChildren = sep->getNumChildren();
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)));
2454
if (dynamic_cast<SoZoomTranslation*>(translationPtr)) {
2455
absPos = static_cast<SoZoomTranslation*>(translationPtr)->abPos.getValue();
2458
absPos = translationPtr->translation.getValue();
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)));
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);
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);
2481
SbVec3f pos0(startingpoint.x, startingpoint.y, startingpoint.z);
2482
SbVec3f pos1(endpoint.x, endpoint.y, endpoint.z);
2484
thisIcon.iconRotation =
2485
ViewProviderSketchCoinAttorney::getRotation(viewProvider, pos0, pos1);
2488
thisIcon.iconRotation = 0;
2491
if (multipleIcons) {
2492
if (constraint->Name.empty()) {
2493
thisIcon.label = QString::number(constrId + 1);
2496
thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
2498
iconQueue.push_back(thisIcon);
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
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();
2512
thisIcon.position += translationPtr->translation.getValue();
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)));
2522
if (constraint->Name.empty()) {
2523
thisIcon.label = QString();
2526
thisIcon.label = QString::fromUtf8(constraint->Name.c_str());
2530
iconQueue.push_back(thisIcon);
2533
combineConstraintIcons(iconQueue);
2536
void EditModeConstraintCoinManager::combineConstraintIcons(IconQueue iconQueue)
2538
// getScaleFactor gives us a ratio of pixels per some kind of real units
2539
float maxDistSquared = pow(ViewProviderSketchCoinAttorney::getScaleFactor(viewProvider), 2);
2541
// There's room for optimisation here; we could reuse the combined icons...
2542
combinedConstrBoxes.clear();
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();
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) {
2555
IconQueue::iterator i = iconQueue.begin();
2558
while (i != iconQueue.end()) {
2560
bool addedToGroup = false;
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;
2577
if (i == iconQueue.end()) {
2578
// We just got the last icon out of iconQueue
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();
2591
else { // if !visible we skip it
2597
if (thisGroup.size() == 1) {
2598
drawTypicalConstraintIcon(thisGroup[0]);
2601
drawMergedConstraintIcons(thisGroup);
2606
void EditModeConstraintCoinManager::drawMergedConstraintIcons(IconQueue iconQueue)
2608
for (IconQueue::iterator i = iconQueue.begin(); i != iconQueue.end(); ++i) {
2609
clearCoinImage(i->destination);
2612
QImage compositeIcon;
2613
SoImage* thisDest = iconQueue[0].destination;
2614
SoInfo* thisInfo = iconQueue[0].infoPtr;
2616
// Tracks all constraint IDs that are combined into this icon
2621
std::vector<int> ids;
2624
QList<QColor> labelColors;
2625
int maxColorPriority;
2626
double iconRotation;
2628
ConstrIconBBVec boundingBoxes;
2629
while (!iconQueue.empty()) {
2630
IconQueue::iterator i = iconQueue.begin();
2633
labels.append(i->label);
2636
ids.push_back(i->constraintId);
2639
iconColor = constrColor(i->constraintId);
2640
labelColors.clear();
2641
labelColors.append(iconColor);
2642
iconRotation = i->iconRotation;
2644
maxColorPriority = constrColorPriority(i->constraintId);
2646
if (idString.length()) {
2647
idString.append(QString::fromLatin1(","));
2649
idString.append(QString::number(i->constraintId));
2651
i = iconQueue.erase(i);
2652
while (i != iconQueue.end()) {
2653
if (i->type != thisType) {
2658
labels.append(i->label);
2659
ids.push_back(i->constraintId);
2660
labelColors.append(constrColor(i->constraintId));
2662
if (constrColorPriority(i->constraintId) > maxColorPriority) {
2663
maxColorPriority = constrColorPriority(i->constraintId);
2664
iconColor = constrColor(i->constraintId);
2667
idString.append(QString::fromLatin1(",") + QString::number(i->constraintId));
2669
i = iconQueue.erase(i);
2672
// To be inserted into edit->combinedConstBoxes
2673
std::vector<QRect> boundingBoxesVec;
2676
// Render the icon here.
2677
if (compositeIcon.isNull()) {
2678
compositeIcon = renderConstrIcon(thisType,
2688
QImage partialIcon = renderConstrIcon(thisType,
2696
// Stack vertically for now. Down the road, it might make sense
2697
// to figure out the best orientation automatically.
2698
oldHeight = compositeIcon.height();
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);
2706
compositeIcon = compositeIcon.copy(0,
2708
std::max(partialIcon.width(), compositeIcon.width()),
2709
partialIcon.height() + compositeIcon.height());
2711
QPainter qp(&compositeIcon);
2712
qp.drawImage(0, oldHeight, partialIcon);
2714
lastVPad = thisVPad;
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();
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) {
2733
nextIds.insert(*(id++));
2736
ConstrIconBB newBB(bb->adjusted(0, oldHeight, 0, oldHeight), nextIds);
2738
boundingBoxes.push_back(newBB);
2742
combinedConstrBoxes[idString] = boundingBoxes;
2743
thisInfo->string.setValue(idString.toLatin1().data());
2744
sendConstraintIconToCoin(compositeIcon, thisDest);
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,
2758
// Constants to help create constraint icons
2759
QString joinStr = QString::fromLatin1(", ");
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
2772
QImage icon = pxMap.toImage();
2774
QFont font = ViewProviderSketchCoinAttorney::getApplicationFont(viewProvider);
2775
font.setPixelSize(static_cast<int>(1.0 * drawingParameters.constraintIconSize));
2777
QFontMetrics qfm = QFontMetrics(font);
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;
2784
*vPad = pxBelowBase;
2787
QTransform rotation;
2788
rotation.rotate(iconRotation);
2790
QImage roticon = icon.transformed(rotation);
2791
QImage image = roticon.copy(0, 0, roticon.width() + labelWidth, roticon.height() + pxBelowBase);
2793
// Make a bounding box for the icon
2794
if (boundingBoxes) {
2795
boundingBoxes->push_back(QRect(0, 0, roticon.width(), roticon.height()));
2799
QPainter qp(&image);
2800
qp.setCompositionMode(QPainter::CompositionMode_SourceIn);
2801
qp.fillRect(roticon.rect(), iconColor);
2803
// Render constraint label if necessary
2804
if (!labels.join(QString()).isEmpty()) {
2805
qp.setCompositionMode(QPainter::CompositionMode_SourceOver);
2808
int cursorOffset = 0;
2810
// In Python: "for label, color in zip(labels, labelColors):"
2811
QStringList::const_iterator labelItr;
2813
QList<QColor>::const_iterator colorItr;
2815
for (labelItr = labels.begin(), colorItr = labelColors.begin();
2816
labelItr != labels.end() && colorItr != labelColors.end();
2817
++labelItr, ++colorItr) {
2819
qp.setPen(*colorItr);
2821
if (labelItr + 1 == labels.end()) { // if this is the last label
2822
labelStr = *labelItr;
2825
labelStr = *labelItr + joinStr;
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);
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);
2841
cursorOffset += Gui::QtTools::horizontalAdvance(qfm, labelStr);
2848
void EditModeConstraintCoinManager::drawTypicalConstraintIcon(const constrIconQueueItem& i)
2850
QColor color = constrColor(i.constraintId);
2852
QImage image = renderConstrIcon(i.type,
2854
QStringList(i.label),
2855
QList<QColor>() << color,
2858
i.infoPtr->string.setValue(QString::number(i.constraintId).toLatin1().data());
2859
sendConstraintIconToCoin(image, i.destination);
2862
QString EditModeConstraintCoinManager::iconTypeFromConstraint(Constraint* constraint)
2864
/*! TODO: Consider pushing this functionality up into Constraint
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.*/
2870
switch (constraint->Type) {
2872
return QString::fromLatin1("Constraint_Horizontal");
2874
return QString::fromLatin1("Constraint_Vertical");
2876
return QString::fromLatin1("Constraint_PointOnObject");
2878
return QString::fromLatin1("Constraint_Tangent");
2880
return QString::fromLatin1("Constraint_Parallel");
2882
return QString::fromLatin1("Constraint_Perpendicular");
2884
return QString::fromLatin1("Constraint_EqualLength");
2886
return QString::fromLatin1("Constraint_Symmetric");
2888
return QString::fromLatin1("Constraint_SnellsLaw");
2890
return QString::fromLatin1("Constraint_Block");
2896
void EditModeConstraintCoinManager::sendConstraintIconToCoin(const QImage& icon,
2897
SoImage* soImagePtr)
2899
SoSFImage icondata = SoSFImage();
2901
Gui::BitmapFactory().convert(icon, icondata);
2903
SbVec2s iconSize(icon.width(), icon.height());
2906
soImagePtr->image.setValue(iconSize, 4, icondata.getValue(iconSize, four));
2908
// Set Image Alignment to Center
2909
soImagePtr->vertAlignment = SoImage::HALF;
2910
soImagePtr->horAlignment = SoImage::CENTER;
2913
void EditModeConstraintCoinManager::clearCoinImage(SoImage* soImagePtr)
2915
soImagePtr->setToDefaults();
2918
QColor EditModeConstraintCoinManager::constrColor(int constraintId)
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));
2926
const auto constraints = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
2928
if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, constraintId)) {
2929
return toQColor(drawingParameters.PreselectColor);
2931
else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId)) {
2932
return toQColor(drawingParameters.SelectColor);
2934
else if (!constraints[constraintId]->isActive) {
2935
return toQColor(drawingParameters.DeactivatedConstrDimColor);
2937
else if (!constraints[constraintId]->isDriving) {
2938
return toQColor(drawingParameters.NonDrivingConstrDimColor);
2941
return toQColor(drawingParameters.ConstrIcoColor);
2945
int EditModeConstraintCoinManager::constrColorPriority(int constraintId)
2947
if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, constraintId)) {
2950
else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId)) {
2958
SoSeparator* EditModeConstraintCoinManager::getConstraintIdSeparator(int i)
2960
return dynamic_cast<SoSeparator*>(editModeScenegraphNodes.constrGroup->getChild(i));
2963
void EditModeConstraintCoinManager::createEditModeInventorNodes()
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);
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);
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;
2985
editModeScenegraphNodes.constrGroup = new SmSwitchboard();
2986
editModeScenegraphNodes.constrGroup->setName("ConstraintGroup");
2987
editModeScenegraphNodes.EditRoot->addChild(editModeScenegraphNodes.constrGroup);
2989
SoPickStyle* ps = new SoPickStyle(); // used to following nodes aren't impacted
2990
ps->style.setValue(SoPickStyle::SHAPE);
2991
editModeScenegraphNodes.EditRoot->addChild(ps);