FreeCAD

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

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <Precision.hxx>
26
#include <QPainter>
27
#include <cfloat>
28
#endif
29

30
#include <boost/range/adaptor/reversed.hpp>
31

32
#include <App/Application.h>
33
#include <Base/Tools.h>
34
#include <Base/Tools2D.h>
35
#include <Gui/Action.h>
36
#include <Gui/Application.h>
37
#include <Gui/BitmapFactory.h>
38
#include <Gui/CommandT.h>
39
#include <Gui/DlgCheckableMessageBox.h>
40
#include <Gui/Document.h>
41
#include <Gui/MainWindow.h>
42
#include <Gui/Notifications.h>
43
#include <Gui/Selection.h>
44
#include <Gui/SelectionFilter.h>
45
#include <Gui/SelectionObject.h>
46
#include <Mod/Sketcher/App/GeometryFacade.h>
47
#include <Mod/Sketcher/App/SketchObject.h>
48
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
49

50
#include "CommandConstraints.h"
51
#include "DrawSketchHandler.h"
52
#include "EditDatumDialog.h"
53
#include "Utils.h"
54
#include "ViewProviderSketch.h"
55
#include "ui_InsertDatum.h"
56
#include <Inventor/events/SoKeyboardEvent.h>
57

58
// Remove this after pre-commit hook is activated
59
// clang-format off
60
using namespace std;
61
using namespace SketcherGui;
62
using namespace Sketcher;
63

64
/***** Creation Mode ************/
65
namespace SketcherGui
66
{
67
enum ConstraintCreationMode
68
{
69
    Driving,
70
    Reference
71
};
72
}
73

74
ConstraintCreationMode constraintCreationMode = Driving;
75

76
bool isCreateConstraintActive(Gui::Document* doc)
77
{
78
    if (doc) {
79
        // checks if a Sketch View provider is in Edit and is in no special mode
80
        if (doc->getInEdit()
81
            && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) {
82
            if (static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit())->getSketchMode()
83
                == ViewProviderSketch::STATUS_NONE) {
84
                if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId())
85
                    > 0) {
86
                    return true;
87
                }
88
            }
89
        }
90
    }
91
    return false;
92
}
93

94
// Utility method to avoid repeating the same code over and over again
95
void finishDatumConstraint(Gui::Command* cmd,
96
                           Sketcher::SketchObject* sketch,
97
                           bool isDriving = true,
98
                           unsigned int numberofconstraints = 1)
99
{
100
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
101
        "User parameter:BaseApp/Preferences/Mod/Sketcher");
102

103
    // Get the latest constraint
104
    const std::vector<Sketcher::Constraint*>& ConStr = sketch->Constraints.getValues();
105
    auto lastConstraintIndex = ConStr.size() - 1;
106
    Sketcher::Constraint* constr = ConStr[lastConstraintIndex];
107
    auto lastConstraintType = constr->Type;
108

109
    // Guess some reasonable distance for placing the datum text
110
    Gui::Document* doc = cmd->getActiveGuiDocument();
111
    float scaleFactor = 1.0;
112
    double labelPosition = 0.0;
113
    float labelPositionRandomness = 0.0;
114

115
    if (lastConstraintType == Radius || lastConstraintType == Diameter) {
116
        labelPosition = hGrp->GetFloat("RadiusDiameterConstraintDisplayBaseAngle", 15.0)
117
            * (M_PI / 180);// Get radius/diameter constraint display angle
118
        labelPositionRandomness =
119
            hGrp->GetFloat("RadiusDiameterConstraintDisplayAngleRandomness", 0.0)
120
            * (M_PI / 180);// Get randomness
121

122
        // Adds a random value around the base angle, so that possibly overlapping labels get likely
123
        // a different position.
124
        if (labelPositionRandomness != 0.0) {
125
            labelPosition = labelPosition
126
                + labelPositionRandomness
127
                    * (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) - 0.5);
128
        }
129
    }
130

131
    if (doc && doc->getInEdit()
132
        && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) {
133
        SketcherGui::ViewProviderSketch* vp =
134
            static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
135
        scaleFactor = vp->getScaleFactor();
136

137
        int firstConstraintIndex = lastConstraintIndex - numberofconstraints + 1;
138

139
        for (int i = lastConstraintIndex; i >= firstConstraintIndex; i--) {
140
            ConStr[i]->LabelDistance = 2. * scaleFactor;
141

142
            if (lastConstraintType == Radius || lastConstraintType == Diameter) {
143
                const Part::Geometry* geo = sketch->getGeometry(ConStr[i]->First);
144

145
                if (geo && isCircle(*geo)) {
146
                    ConStr[i]->LabelPosition = labelPosition;
147
                }
148
            }
149
        }
150
        vp->draw(false, false);// Redraw
151
    }
152

153
    bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true);
154

155
    // Ask for the value of the distance immediately
156
    if (show && isDriving) {
157
        EditDatumDialog editDatumDialog(sketch, ConStr.size() - 1);
158
        editDatumDialog.exec();
159
    }
160
    else {
161
        // no dialog was shown so commit the command
162
        cmd->commitCommand();
163
    }
164

165
    tryAutoRecompute(sketch);
166
    cmd->getSelection().clearSelection();
167
}
168

169
void showNoConstraintBetweenExternal(const App::DocumentObject* obj)
170
{
171
    Gui::TranslatedUserWarning(
172
        obj,
173
        QObject::tr("Wrong selection"),
174
        QObject::tr("Cannot add a constraint between two external geometries."));
175
}
176

177
void showNoConstraintBetweenFixedGeometry(const App::DocumentObject* obj)
178
{
179
    Gui::TranslatedUserWarning(obj,
180
                               QObject::tr("Wrong selection"),
181
                               QObject::tr("Cannot add a constraint between two fixed geometries. "
182
                                           "Fixed geometries include external geometry, "
183
                                           "blocked geometry, and special points "
184
                                           "such as B-spline knot points."));
185
}
186

187
bool isGeoConcentricCompatible(const Part::Geometry* geo)
188
{
189
    return (isEllipse(*geo) || isArcOfEllipse(*geo) || isCircle(*geo) || isArcOfCircle(*geo));
190
}
191

192
// Removes point-on-object constraints made redundant with certain constraints
193
// under certain conditions. Currently, that happens only when the constraint is on
194
// a B-spline, for 3-selection tangent, perpendicular, and angle constraints.
195
// Returns true if constraints were removed.
196
// GeoId3 HAS to be the point, and the other two are the curves.
197
bool removeRedundantPointOnObject(SketchObject* Obj, int GeoId1, int GeoId2, int GeoId3)
198
{
199
    const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
200

201
    std::vector<int> cidsToBeRemoved;
202

203
    int cid = 0;
204
    for (auto it = cvals.begin(); it != cvals.end(); ++it, ++cid) {
205
        if ((*it)->Type == Sketcher::PointOnObject &&
206
            (((*it)->First == GeoId3 && (*it)->Second == GeoId1) ||
207
             ((*it)->First == GeoId3 && (*it)->Second == GeoId2))) {
208

209
            // ONLY do this if it is a B-spline (or any other where point
210
            // on object is implied).
211
            const Part::Geometry* geom = Obj->getGeometry((*it)->Second);
212
            if (isBSplineCurve(*geom))
213
                cidsToBeRemoved.push_back(cid);
214
        }
215
    }
216

217
    if (!cidsToBeRemoved.empty()) {
218
        for (auto it = cidsToBeRemoved.rbegin(); it != cidsToBeRemoved.rend(); ++it) {
219
            Gui::cmdAppObjectArgs(Obj,
220
                                  "delConstraint(%d)",
221
                                  *it);// remove the preexisting point on object constraint.
222
        }
223

224
        // A substitution requires a solve() so that the autoremove redundants works when
225
        // Autorecompute not active. However, delConstraint includes such solve() internally. So
226
        // at this point it is already solved.
227
        tryAutoRecomputeIfNotSolve(Obj);
228

229
        notifyConstraintSubstitutions(QObject::tr("One or two point on object constraint(s) was/were deleted, "
230
                                                  "since the latest constraint being applied internally applies point-on-object as well."));
231

232
        // TODO: find way to get selection here, or clear elsewhere
233
        // getSelection().clearSelection();
234
        return true;
235
    }
236

237
    return false;
238
}
239

240
/// Makes an angle constraint between 2 lines
241
void SketcherGui::makeAngleBetweenTwoLines(Sketcher::SketchObject* Obj,
242
    Gui::Command* cmd,
243
    int geoId1,
244
    int geoId2)
245
{
246
    Sketcher::PointPos posId1 = Sketcher::PointPos::none;
247
    Sketcher::PointPos posId2 = Sketcher::PointPos::none;
248
    double actAngle;
249

250
    if (!calculateAngle(Obj, geoId1, geoId2, posId1, posId2, actAngle)) {
251
        return;
252
    }
253

254
    if (actAngle == 0.0) {
255
        Gui::TranslatedUserWarning(
256
            Obj,
257
            QObject::tr("Parallel lines"),
258
            QObject::tr("An angle constraint cannot be set for two parallel lines."));
259

260
        return;
261
    }
262

263
    Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
264
    Gui::cmdAppObjectArgs(Obj,
265
        "addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f))",
266
        geoId1,
267
        static_cast<int>(posId1),
268
        geoId2,
269
        static_cast<int>(posId2),
270
        actAngle);
271

272
    if (areBothPointsOrSegmentsFixed(Obj, geoId1, geoId2)
273
        || constraintCreationMode == Reference) {
274
        // it is a constraint on a external line, make it non-driving
275
        const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
276

277
        Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
278
        finishDatumConstraint(cmd, Obj, false);
279
    }
280
    else {
281
        finishDatumConstraint(cmd, Obj, true);
282
    }
283
}
284

285
bool SketcherGui::calculateAngle(Sketcher::SketchObject* Obj, int& GeoId1, int& GeoId2, Sketcher::PointPos& PosId1, Sketcher::PointPos& PosId2, double& ActAngle)
286
{
287
    const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
288
    const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
289

290
    if (!(geom1->is<Part::GeomLineSegment>()) ||
291
        !(geom2->is<Part::GeomLineSegment>())) {
292
        return false;
293
    }
294

295
    const Part::GeomLineSegment* lineSeg1 = static_cast<const Part::GeomLineSegment*>(geom1);
296
    const Part::GeomLineSegment* lineSeg2 = static_cast<const Part::GeomLineSegment*>(geom2);
297

298
    // find the two closest line ends
299
    Base::Vector3d p1[2], p2[2];
300
    p1[0] = lineSeg1->getStartPoint();
301
    p1[1] = lineSeg1->getEndPoint();
302
    p2[0] = lineSeg2->getStartPoint();
303
    p2[1] = lineSeg2->getEndPoint();
304

305
    // Get the intersection point in 2d of the two lines if possible
306
    Base::Line2d line1(Base::Vector2d(p1[0].x, p1[0].y), Base::Vector2d(p1[1].x, p1[1].y));
307
    Base::Line2d line2(Base::Vector2d(p2[0].x, p2[0].y), Base::Vector2d(p2[1].x, p2[1].y));
308
    Base::Vector2d s;
309
    if (line1.Intersect(line2, s)) {
310
        // get the end points of the line segments that are closest to the intersection point
311
        Base::Vector3d s3d(s.x, s.y, p1[0].z);
312
        if (Base::DistanceP2(s3d, p1[0]) < Base::DistanceP2(s3d, p1[1]))
313
            PosId1 = Sketcher::PointPos::start;
314
        else
315
            PosId1 = Sketcher::PointPos::end;
316
        if (Base::DistanceP2(s3d, p2[0]) < Base::DistanceP2(s3d, p2[1]))
317
            PosId2 = Sketcher::PointPos::start;
318
        else
319
            PosId2 = Sketcher::PointPos::end;
320
    }
321
    else {
322
        // if all points are collinear
323
        double length = DBL_MAX;
324
        for (int i = 0; i <= 1; i++) {
325
            for (int j = 0; j <= 1; j++) {
326
                double tmp = Base::DistanceP2(p2[j], p1[i]);
327
                if (tmp < length) {
328
                    length = tmp;
329
                    PosId1 = i ? Sketcher::PointPos::end : Sketcher::PointPos::start;
330
                    PosId2 = j ? Sketcher::PointPos::end : Sketcher::PointPos::start;
331
                }
332
            }
333
        }
334
    }
335

336
    Base::Vector3d dir1 = ((PosId1 == Sketcher::PointPos::start) ? 1. : -1.) *
337
        (lineSeg1->getEndPoint() - lineSeg1->getStartPoint());
338
    Base::Vector3d dir2 = ((PosId2 == Sketcher::PointPos::start) ? 1. : -1.) *
339
        (lineSeg2->getEndPoint() - lineSeg2->getStartPoint());
340

341
    // check if the two lines are parallel
342
    Base::Vector3d dir3 = dir1 % dir2;
343
    if (dir3.Length() < Precision::Intersection()) {
344
        Base::Vector3d dist = (p1[0] - p2[0]) % dir1;
345
        if (dist.Sqr() > Precision::Intersection()) {
346
            ActAngle = 0.0;
347
            return true;
348
        }
349
    }
350

351
    ActAngle = atan2(dir1.x * dir2.y - dir1.y * dir2.x,
352
        dir1.y * dir2.y + dir1.x * dir2.x);
353

354
    if (ActAngle < 0) {
355
        ActAngle *= -1;
356
        std::swap(GeoId1, GeoId2);
357
        std::swap(PosId1, PosId2);
358
    }
359

360
    return true;
361
}
362

363
/// Makes a simple tangency constraint using extra point + tangent via point
364
/// ellipse => an ellipse
365
/// geom2 => any of an ellipse, an arc of ellipse, a circle, or an arc (of circle)
366
/// geoId1 => geoid of the ellipse
367
/// geoId2 => geoid of geom2
368
/// NOTE: A command must be opened before calling this function, which this function
369
/// commits or aborts as appropriate. The reason is for compatibility reasons with
370
/// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp
371
void SketcherGui::makeTangentToEllipseviaNewPoint(Sketcher::SketchObject* Obj,
372
                                                  const Part::GeomEllipse* ellipse,
373
                                                  const Part::Geometry* geom2,
374
                                                  int geoId1,
375
                                                  int geoId2)
376
{
377

378
    Base::Vector3d center = ellipse->getCenter();
379
    double majord = ellipse->getMajorRadius();
380
    double minord = ellipse->getMinorRadius();
381
    double phi = atan2(ellipse->getMajorAxisDir().y, ellipse->getMajorAxisDir().x);
382

383
    Base::Vector3d center2;
384

385
    if (isEllipse(*geom2)) {
386
        center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
387
    }
388
    else if (isArcOfEllipse(*geom2)) {
389
        center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
390
    }
391
    else if (isCircle(*geom2)) {
392
        center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
393
    }
394
    else if (isArcOfCircle(*geom2)) {
395
        center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
396
    }
397

398
    Base::Vector3d direction = center2 - center;
399
    double tapprox =
400
        atan2(direction.y, direction.x) - phi;// we approximate the eccentric anomaly by the polar
401

402
    Base::Vector3d PoE = Base::Vector3d(
403
        center.x + majord * cos(tapprox) * cos(phi) - minord * sin(tapprox) * sin(phi),
404
        center.y + majord * cos(tapprox) * sin(phi) + minord * sin(tapprox) * cos(phi),
405
        0);
406

407
    try {
408
        // Add a point
409
        Gui::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)", PoE.x, PoE.y);
410
        int GeoIdPoint = Obj->getHighestCurveIndex();
411

412
        // Point on first object
413
        Gui::cmdAppObjectArgs(Obj,
414
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
415
                              GeoIdPoint,
416
                              static_cast<int>(Sketcher::PointPos::start),
417
                              geoId1);// constrain major axis
418
        // Point on second object
419
        Gui::cmdAppObjectArgs(Obj,
420
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
421
                              GeoIdPoint,
422
                              static_cast<int>(Sketcher::PointPos::start),
423
                              geoId2);// constrain major axis
424
        // tangent via point
425
        Gui::cmdAppObjectArgs(Obj,
426
                              "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
427
                              geoId1,
428
                              geoId2,
429
                              GeoIdPoint,
430
                              static_cast<int>(Sketcher::PointPos::start));
431
    }
432
    catch (const Base::Exception& e) {
433
        Gui::NotifyUserError(Obj,
434
                             QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
435
                             e.what());
436
        Gui::Command::abortCommand();
437

438
        tryAutoRecompute(Obj);
439
        return;
440
    }
441

442
    Gui::Command::commitCommand();
443
    tryAutoRecompute(Obj);
444
}
445

446
/// Makes a simple tangency constraint using extra point + tangent via point
447
/// aoe => an arc of ellipse
448
/// geom2 => any of an arc of ellipse, a circle, or an arc (of circle)
449
/// geoId1 => geoid of the arc of ellipse
450
/// geoId2 => geoid of geom2
451
/// NOTE: A command must be opened before calling this function, which this function
452
/// commits or aborts as appropriate. The reason is for compatibility reasons with
453
/// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp
454
void SketcherGui::makeTangentToArcOfEllipseviaNewPoint(Sketcher::SketchObject* Obj,
455
                                                       const Part::GeomArcOfEllipse* aoe,
456
                                                       const Part::Geometry* geom2,
457
                                                       int geoId1,
458
                                                       int geoId2)
459
{
460

461
    Base::Vector3d center = aoe->getCenter();
462
    double majord = aoe->getMajorRadius();
463
    double minord = aoe->getMinorRadius();
464
    double phi = atan2(aoe->getMajorAxisDir().y, aoe->getMajorAxisDir().x);
465

466
    Base::Vector3d center2;
467

468
    if (isArcOfEllipse(*geom2)) {
469
        center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
470
    }
471
    else if (isCircle(*geom2)) {
472
        center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
473
    }
474
    else if (isArcOfCircle(*geom2)) {
475
        center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
476
    }
477

478
    Base::Vector3d direction = center2 - center;
479
    double tapprox =
480
        atan2(direction.y, direction.x) - phi;// we approximate the eccentric anomaly by the polar
481

482
    Base::Vector3d PoE = Base::Vector3d(
483
        center.x + majord * cos(tapprox) * cos(phi) - minord * sin(tapprox) * sin(phi),
484
        center.y + majord * cos(tapprox) * sin(phi) + minord * sin(tapprox) * cos(phi),
485
        0);
486

487
    try {
488
        // Add a point
489
        Gui::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)", PoE.x, PoE.y);
490
        int GeoIdPoint = Obj->getHighestCurveIndex();
491

492
        // Point on first object
493
        Gui::cmdAppObjectArgs(Obj,
494
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
495
                              GeoIdPoint,
496
                              static_cast<int>(Sketcher::PointPos::start),
497
                              geoId1);// constrain major axis
498
        // Point on second object
499
        Gui::cmdAppObjectArgs(Obj,
500
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
501
                              GeoIdPoint,
502
                              static_cast<int>(Sketcher::PointPos::start),
503
                              geoId2);// constrain major axis
504
        // tangent via point
505
        Gui::cmdAppObjectArgs(Obj,
506
                              "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
507
                              geoId1,
508
                              geoId2,
509
                              GeoIdPoint,
510
                              static_cast<int>(Sketcher::PointPos::start));
511
    }
512
    catch (const Base::Exception& e) {
513
        Gui::NotifyUserError(Obj,
514
                             QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
515
                             e.what());
516
        Gui::Command::abortCommand();
517

518
        tryAutoRecompute(Obj);
519
        return;
520
    }
521

522
    Gui::Command::commitCommand();
523
    tryAutoRecompute(Obj);
524
}
525

526
/// Makes a simple tangency constraint using extra point + tangent via point
527
/// aoh => an arc of hyperbola
528
/// geom2 => any of an arc of hyperbola, an arc of ellipse, a circle, or an arc (of circle)
529
/// geoId1 => geoid of the arc of hyperbola
530
/// geoId2 => geoid of geom2
531
/// NOTE: A command must be opened before calling this function, which this function
532
/// commits or aborts as appropriate. The reason is for compatibility reasons with
533
/// other code e.g. "Autoconstraints" in DrawSketchHandler.cpp
534
void SketcherGui::makeTangentToArcOfHyperbolaviaNewPoint(Sketcher::SketchObject* Obj,
535
                                                         const Part::GeomArcOfHyperbola* aoh,
536
                                                         const Part::Geometry* geom2,
537
                                                         int geoId1,
538
                                                         int geoId2)
539
{
540

541
    Base::Vector3d center = aoh->getCenter();
542
    double majord = aoh->getMajorRadius();
543
    double minord = aoh->getMinorRadius();
544
    Base::Vector3d dirmaj = aoh->getMajorAxisDir();
545
    double phi = atan2(dirmaj.y, dirmaj.x);
546
    double df = sqrt(majord * majord + minord * minord);
547
    Base::Vector3d focus = center + df * dirmaj;// positive focus
548

549
    Base::Vector3d center2;
550

551
    if (isArcOfHyperbola(*geom2)) {
552
        auto aoh2 = static_cast<const Part::GeomArcOfHyperbola*>(geom2);
553
        Base::Vector3d dirmaj2 = aoh2->getMajorAxisDir();
554
        double majord2 = aoh2->getMajorRadius();
555
        double minord2 = aoh2->getMinorRadius();
556
        double df2 = sqrt(majord2 * majord2 + minord2 * minord2);
557
        center2 = aoh2->getCenter() + df2 * dirmaj2;// positive focus
558
    }
559
    else if (isArcOfEllipse(*geom2)) {
560
        center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
561
    }
562
    else if (isEllipse(*geom2)) {
563
        center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
564
    }
565
    else if (isCircle(*geom2)) {
566
        center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
567
    }
568
    else if (isArcOfCircle(*geom2)) {
569
        center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
570
    }
571
    else if (isLineSegment(*geom2)) {
572
        auto l2 = static_cast<const Part::GeomLineSegment*>(geom2);
573
        center2 = (l2->getStartPoint() + l2->getEndPoint()) / 2;
574
    }
575

576
    Base::Vector3d direction = center2 - focus;
577
    double tapprox = atan2(direction.y, direction.x) - phi;
578

579
    Base::Vector3d PoH = Base::Vector3d(
580
        center.x + majord * cosh(tapprox) * cos(phi) - minord * sinh(tapprox) * sin(phi),
581
        center.y + majord * cosh(tapprox) * sin(phi) + minord * sinh(tapprox) * cos(phi),
582
        0);
583

584
    try {
585
        // Add a point
586
        Gui::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)", PoH.x, PoH.y);
587
        int GeoIdPoint = Obj->getHighestCurveIndex();
588

589
        // Point on first object
590
        Gui::cmdAppObjectArgs(Obj,
591
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
592
                              GeoIdPoint,
593
                              static_cast<int>(Sketcher::PointPos::start),
594
                              geoId1);// constrain major axis
595
        // Point on second object
596
        Gui::cmdAppObjectArgs(Obj,
597
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
598
                              GeoIdPoint,
599
                              static_cast<int>(Sketcher::PointPos::start),
600
                              geoId2);// constrain major axis
601
        // tangent via point
602
        Gui::cmdAppObjectArgs(Obj,
603
                              "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
604
                              geoId1,
605
                              geoId2,
606
                              GeoIdPoint,
607
                              static_cast<int>(Sketcher::PointPos::start));
608
    }
609
    catch (const Base::Exception& e) {
610
        Gui::NotifyUserError(Obj,
611
                             QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
612
                             e.what());
613
        Gui::Command::abortCommand();
614

615
        tryAutoRecompute(Obj);
616
        return;
617
    }
618

619
    Gui::Command::commitCommand();
620

621
    tryAutoRecompute(Obj);
622
}
623

624
/// Makes a simple tangency constraint using extra point + tangent via point
625
/// aop => an arc of parabola
626
/// geom2 => any of an arc of parabola, an arc of hyperbola an arc of ellipse, a circle, or an arc
627
/// (of circle) geoId1 => geoid of the arc of parabola geoId2 => geoid of geom2 NOTE: A command must
628
/// be opened before calling this function, which this function commits or aborts as appropriate.
629
/// The reason is for compatibility reasons with other code e.g. "Autoconstraints" in
630
/// DrawSketchHandler.cpp
631
void SketcherGui::makeTangentToArcOfParabolaviaNewPoint(Sketcher::SketchObject* Obj,
632
                                                        const Part::GeomArcOfParabola* aop,
633
                                                        const Part::Geometry* geom2,
634
                                                        int geoId1,
635
                                                        int geoId2)
636
{
637

638
    Base::Vector3d focus = aop->getFocus();
639

640
    Base::Vector3d center2;
641

642
    if (isArcOfParabola(*geom2)) {
643
        center2 = (static_cast<const Part::GeomArcOfParabola*>(geom2))->getFocus();
644
    }
645
    else if (isArcOfHyperbola(*geom2)) {
646
        auto aoh2 = static_cast<const Part::GeomArcOfHyperbola*>(geom2);
647
        Base::Vector3d dirmaj2 = aoh2->getMajorAxisDir();
648
        double majord2 = aoh2->getMajorRadius();
649
        double minord2 = aoh2->getMinorRadius();
650
        double df2 = sqrt(majord2 * majord2 + minord2 * minord2);
651
        center2 = aoh2->getCenter() + df2 * dirmaj2;// positive focus
652
    }
653
    else if (isArcOfEllipse(*geom2)) {
654
        center2 = (static_cast<const Part::GeomArcOfEllipse*>(geom2))->getCenter();
655
    }
656
    else if (isEllipse(*geom2)) {
657
        center2 = (static_cast<const Part::GeomEllipse*>(geom2))->getCenter();
658
    }
659
    else if (isCircle(*geom2)) {
660
        center2 = (static_cast<const Part::GeomCircle*>(geom2))->getCenter();
661
    }
662
    else if (isArcOfCircle(*geom2)) {
663
        center2 = (static_cast<const Part::GeomArcOfCircle*>(geom2))->getCenter();
664
    }
665
    else if (isLineSegment(*geom2)) {
666
        auto l2 = static_cast<const Part::GeomLineSegment*>(geom2);
667
        center2 = (l2->getStartPoint() + l2->getEndPoint()) / 2;
668
    }
669

670
    Base::Vector3d direction = center2 - focus;
671

672
    Base::Vector3d PoP = focus + direction / 2;
673

674
    try {
675
        // Add a point
676
        Gui::cmdAppObjectArgs(Obj, "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)", PoP.x, PoP.y);
677
        int GeoIdPoint = Obj->getHighestCurveIndex();
678

679
        // Point on first object
680
        Gui::cmdAppObjectArgs(Obj,
681
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
682
                              GeoIdPoint,
683
                              static_cast<int>(Sketcher::PointPos::start),
684
                              geoId1);// constrain major axis
685
        // Point on second object
686
        Gui::cmdAppObjectArgs(Obj,
687
                              "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
688
                              GeoIdPoint,
689
                              static_cast<int>(Sketcher::PointPos::start),
690
                              geoId2);// constrain major axis
691
        // tangent via point
692
        Gui::cmdAppObjectArgs(Obj,
693
                              "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
694
                              geoId1,
695
                              geoId2,
696
                              GeoIdPoint,
697
                              static_cast<int>(Sketcher::PointPos::start));
698
    }
699
    catch (const Base::Exception& e) {
700
        Gui::NotifyUserError(Obj,
701
                             QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
702
                             e.what());
703

704
        Gui::Command::abortCommand();
705

706
        tryAutoRecompute(Obj);
707
        return;
708
    }
709

710
    Gui::Command::commitCommand();
711
    tryAutoRecompute(Obj);
712
}
713

714
void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj,
715
                                     int GeoId1,
716
                                     int GeoId2,
717
                                     PointPos PosId1,
718
                                     PointPos PosId2)
719
{
720
    // This code supports simple B-spline endpoint tangency to any other geometric curve
721
    const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
722
    const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
723

724
    if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
725
        if (! isBSplineCurve(*geom1)) {
726
            std::swap(GeoId1, GeoId2);
727
            std::swap(PosId1, PosId2);
728
        }
729
        // GeoId1 is the B-spline now
730
    }// end of code supports simple B-spline endpoint tangency
731

732
    Gui::cmdAppObjectArgs(Obj,
733
                          "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d))",
734
                          GeoId1,
735
                          static_cast<int>(PosId1),
736
                          GeoId2,
737
                          static_cast<int>(PosId2));
738
}
739

740
void SketcherGui::doEndpointToEdgeTangency(Sketcher::SketchObject* Obj,
741
                                           int GeoId1,
742
                                           PointPos PosId1,
743
                                           int GeoId2)
744
{
745
    Gui::cmdAppObjectArgs(Obj,
746
                          "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))",
747
                          GeoId1,
748
                          static_cast<int>(PosId1),
749
                          GeoId2);
750
}
751

752
void SketcherGui::notifyConstraintSubstitutions(const QString& message)
753
{
754
    Gui::Dialog::DlgCheckableMessageBox::showMessage(
755
        QObject::tr("Sketcher Constraint Substitution"),
756
        message,
757
        QLatin1String("User parameter:BaseApp/Preferences/Mod/Sketcher/General"),
758
        QLatin1String("NotifyConstraintSubstitutions"),
759
        true,// Default ParamEntry
760
        true,// checkbox state
761
        QObject::tr("Keep notifying me of constraint substitutions"));
762
}
763

764
// returns true if successful, false otherwise
765
bool addConstraintSafely(SketchObject* obj, std::function<void()> constraintadditionfunction)
766
{
767
    try {
768
        constraintadditionfunction();
769
    }
770
    catch (const Base::IndexError& e) {
771
        // Exceptions originating in Python have already been reported by the Interpreter as
772
        // Untranslated developer intended messages (i.e. to the Report View)
773
        // Example of exception carrying a static string with a pair in the "Notifications" context
774
        Gui::NotifyUserError(obj,
775
                             QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
776
                             e.what());
777

778
        Gui::Command::abortCommand();
779

780
        tryAutoRecompute(obj);
781
        return false;
782
    }
783
    catch (const Base::Exception&) {
784
        Gui::TranslatedUserError(
785
            obj,
786
            QObject::tr("Error"),
787
            QObject::tr("Unexpected error. More information may be available in the Report View."));
788

789
        Gui::Command::abortCommand();
790

791
        tryAutoRecompute(obj);
792
        return false;
793
    }
794

795
    return true;
796
}
797

798
namespace SketcherGui
799
{
800

801
struct SelIdPair
802
{
803
    int GeoId;
804
    Sketcher::PointPos PosId;
805
};
806

807
struct SketchSelection
808
{
809
    enum GeoType
810
    {
811
        Point,
812
        Line,
813
        Circle,
814
        Arc
815
    };
816
    int setUp();
817
    struct SketchSelectionItem
818
    {
819
        GeoType type;
820
        int GeoId;
821
        bool Extern;
822
    };
823
    std::list<SketchSelectionItem> Items;
824
    QString ErrorMsg;
825
};
826

827
int SketchSelection::setUp()
828
{
829
    std::vector<Gui::SelectionObject> selection = Gui::Selection().getSelectionEx();
830

831
    Sketcher::SketchObject* SketchObj = nullptr;
832
    std::vector<std::string> SketchSubNames;
833
    std::vector<std::string> SupportSubNames;
834

835
    // only one sketch with its subelements are allowed to be selected
836
    if (selection.size() == 1) {
837
        // if one selectetd, only sketch allowed. should be done by activity of command
838
        if (!selection[0].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
839
            ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
840
            return -1;
841
        }
842

843
        SketchSubNames = selection[0].getSubNames();
844
    }
845
    else if (selection.size() == 2) {
846
        if (selection[0].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
847
            SketchObj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
848
            // check if the none sketch object is the support of the sketch
849
            if (selection[1].getObject() != SketchObj->AttachmentSupport.getValue()) {
850
                ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
851
                return -1;
852
            }
853
            // assume always a Part::Feature derived object as support
854
            assert(selection[1].getObject()->isDerivedFrom<Part::Feature>());
855
            SketchSubNames = selection[0].getSubNames();
856
            SupportSubNames = selection[1].getSubNames();
857
        }
858
        else if (selection[1].getObject()->isDerivedFrom<Sketcher::SketchObject>()) {
859
            SketchObj = static_cast<Sketcher::SketchObject*>(selection[1].getObject());
860
            // check if the none sketch object is the support of the sketch
861
            if (selection[0].getObject() != SketchObj->AttachmentSupport.getValue()) {
862
                ErrorMsg = QObject::tr("Only sketch and its support are allowed to be selected.");
863
                return -1;
864
            }
865
            // assume always a Part::Feature derived object as support
866
            assert(selection[0].getObject()->isDerivedFrom<Part::Feature>());
867
            SketchSubNames = selection[1].getSubNames();
868
            SupportSubNames = selection[0].getSubNames();
869
        }
870
        else {
871
            ErrorMsg = QObject::tr("One of the selected has to be on the sketch.");
872
            return -1;
873
        }
874
    }
875

876
    return Items.size();
877
}
878

879
}// namespace SketcherGui
880

881
/* Constrain commands =======================================================*/
882

883
namespace SketcherGui
884
{
885
/**
886
 * @brief The SelType enum
887
 * Types of sketch elements that can be (pre)selected. The root/origin and the
888
 * axes are put up separately so that they can be specifically disallowed, for
889
 * example, when in lock, horizontal, or vertical constraint modes.
890
 */
891
enum SelType
892
{
893
    SelUnknown = 0,
894
    SelVertex = 1,
895
    SelVertexOrRoot = 64,
896
    SelRoot = 2,
897
    SelEdge = 4,
898
    SelEdgeOrAxis = 128,
899
    SelHAxis = 8,
900
    SelVAxis = 16,
901
    SelExternalEdge = 32
902
};
903

904
/**
905
 * @brief The GenericConstraintSelection class
906
 * SelectionFilterGate with changeable filters. In a constraint creation mode
907
 * like point on object, if the first selection object can be a point, the next
908
 * has to be a curve for the constraint to make sense. Thus filters are
909
 * changeable so that same filter can be kept on while in one mode.
910
 */
911
class GenericConstraintSelection: public Gui::SelectionFilterGate
912
{
913
    App::DocumentObject* object;
914

915
public:
916
    explicit GenericConstraintSelection(App::DocumentObject* obj)
917
        : Gui::SelectionFilterGate(nullPointer())
918
        , object(obj)
919
        , allowedSelTypes(0)
920
    {}
921

922
    bool allow(App::Document*, App::DocumentObject* pObj, const char* sSubName) override
923
    {
924
        if (pObj != this->object) {
925
            return false;
926
        }
927
        if (!sSubName || sSubName[0] == '\0') {
928
            return false;
929
        }
930
        std::string element(sSubName);
931
        if ((allowedSelTypes & (SelRoot | SelVertexOrRoot) && element.substr(0, 9) == "RootPoint")
932
            || (allowedSelTypes & (SelVertex | SelVertexOrRoot) && element.substr(0, 6) == "Vertex")
933
            || (allowedSelTypes & (SelEdge | SelEdgeOrAxis) && element.substr(0, 4) == "Edge")
934
            || (allowedSelTypes & (SelHAxis | SelEdgeOrAxis) && element.substr(0, 6) == "H_Axis")
935
            || (allowedSelTypes & (SelVAxis | SelEdgeOrAxis) && element.substr(0, 6) == "V_Axis")
936
            || (allowedSelTypes & SelExternalEdge && element.substr(0, 12) == "ExternalEdge")) {
937
            return true;
938
        }
939

940
        return false;
941
    }
942

943
    void setAllowedSelTypes(unsigned int types)
944
    {
945
        if (types < 256) {
946
            allowedSelTypes = types;
947
        }
948
    }
949

950
protected:
951
    int allowedSelTypes;
952
};
953
}// namespace SketcherGui
954

955
/**
956
 * @brief The CmdSketcherConstraint class
957
 * Superclass for all sketcher constraints to ease generation of constraint
958
 * creation modes.
959
 */
960
class CmdSketcherConstraint: public Gui::Command
961
{
962
    friend class DrawSketchHandlerGenConstraint;
963

964
public:
965
    explicit CmdSketcherConstraint(const char* name)
966
        : Command(name)
967
    {}
968

969
    ~CmdSketcherConstraint() override
970
    {}
971

972
    const char* className() const override
973
    {
974
        return "CmdSketcherConstraint";
975
    }
976

977
protected:
978
    /**
979
     * @brief allowedSelSequences
980
     * Each element is a vector representing sequence of selections allowable.
981
     * DrawSketchHandlerGenConstraint will use these to filter elements and
982
     * generate sequences to be passed to applyConstraint().
983
     * Whenever any sequence is completed, applyConstraint() is called, so it's
984
     * best to keep them prefix-free.
985
     * Be mindful that when SelVertex and SelRoot are given preference over
986
     * SelVertexOrRoot, and similar for edges/axes. Thus if a vertex is selected
987
     * when SelVertex and SelVertexOrRoot are both applicable, only sequences with
988
     * SelVertex will be continue.
989
     *
990
     * TODO: Introduce structs to allow keeping first selection
991
     */
992
    std::vector<std::vector<SketcherGui::SelType>> allowedSelSequences;
993

994
    virtual void applyConstraint(std::vector<SelIdPair>&, int)
995
    {}
996
    void activated(int /*iMsg*/) override;
997
    bool isActive() override
998
    {
999
        return isCommandActive(getActiveGuiDocument());
1000
    }
1001
};
1002

1003
class DrawSketchHandlerGenConstraint: public DrawSketchHandler
1004
{
1005
public:
1006
    explicit DrawSketchHandlerGenConstraint(CmdSketcherConstraint* _cmd)
1007
        : cmd(_cmd)
1008
        , seqIndex(0)
1009
    {}
1010
    ~DrawSketchHandlerGenConstraint() override
1011
    {
1012
        Gui::Selection().rmvSelectionGate();
1013
    }
1014

1015
    void mouseMove(Base::Vector2d /*onSketchPos*/) override
1016
    {}
1017

1018
    bool pressButton(Base::Vector2d /*onSketchPos*/) override
1019
    {
1020
        return true;
1021
    }
1022

1023
    bool releaseButton(Base::Vector2d onSketchPos) override
1024
    {
1025
        SelIdPair selIdPair;
1026
        selIdPair.GeoId = GeoEnum::GeoUndef;
1027
        selIdPair.PosId = Sketcher::PointPos::none;
1028
        std::stringstream ss;
1029
        SelType newSelType = SelUnknown;
1030

1031
        // For each SelType allowed, check if button is released there and assign it to selIdPair
1032
        int VtId = getPreselectPoint();
1033
        int CrvId = getPreselectCurve();
1034
        int CrsId = getPreselectCross();
1035
        if (allowedSelTypes & (SelRoot | SelVertexOrRoot) && CrsId == 0) {
1036
            selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
1037
            selIdPair.PosId = Sketcher::PointPos::start;
1038
            newSelType = (allowedSelTypes & SelRoot) ? SelRoot : SelVertexOrRoot;
1039
            ss << "RootPoint";
1040
        }
1041
        else if (allowedSelTypes & (SelVertex | SelVertexOrRoot) && VtId >= 0) {
1042
            sketchgui->getSketchObject()->getGeoVertexIndex(VtId, selIdPair.GeoId, selIdPair.PosId);
1043
            newSelType = (allowedSelTypes & SelVertex) ? SelVertex : SelVertexOrRoot;
1044
            ss << "Vertex" << VtId + 1;
1045
        }
1046
        else if (allowedSelTypes & (SelEdge | SelEdgeOrAxis) && CrvId >= 0) {
1047
            selIdPair.GeoId = CrvId;
1048
            newSelType = (allowedSelTypes & SelEdge) ? SelEdge : SelEdgeOrAxis;
1049
            ss << "Edge" << CrvId + 1;
1050
        }
1051
        else if (allowedSelTypes & (SelHAxis | SelEdgeOrAxis) && CrsId == 1) {
1052
            selIdPair.GeoId = Sketcher::GeoEnum::HAxis;
1053
            newSelType = (allowedSelTypes & SelHAxis) ? SelHAxis : SelEdgeOrAxis;
1054
            ss << "H_Axis";
1055
        }
1056
        else if (allowedSelTypes & (SelVAxis | SelEdgeOrAxis) && CrsId == 2) {
1057
            selIdPair.GeoId = Sketcher::GeoEnum::VAxis;
1058
            newSelType = (allowedSelTypes & SelVAxis) ? SelVAxis : SelEdgeOrAxis;
1059
            ss << "V_Axis";
1060
        }
1061
        else if (allowedSelTypes & SelExternalEdge && CrvId <= Sketcher::GeoEnum::RefExt) {
1062
            // TODO: Figure out how this works
1063
            selIdPair.GeoId = CrvId;
1064
            newSelType = SelExternalEdge;
1065
            ss << "ExternalEdge" << Sketcher::GeoEnum::RefExt + 1 - CrvId;
1066
        }
1067

1068
        if (selIdPair.GeoId == GeoEnum::GeoUndef) {
1069
            // If mouse is released on "blank" space, start over
1070
            selSeq.clear();
1071
            resetOngoingSequences();
1072
            Gui::Selection().clearSelection();
1073
        }
1074
        else {
1075
            // If mouse is released on something allowed, select it and move forward
1076
            selSeq.push_back(selIdPair);
1077
            Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName(),
1078
                                          sketchgui->getSketchObject()->getNameInDocument(),
1079
                                          ss.str().c_str(),
1080
                                          onSketchPos.x,
1081
                                          onSketchPos.y,
1082
                                          0.f);
1083
            _tempOnSequences.clear();
1084
            allowedSelTypes = 0;
1085
            for (std::set<int>::iterator token = ongoingSequences.begin();
1086
                 token != ongoingSequences.end();
1087
                 ++token) {
1088
                if ((cmd->allowedSelSequences).at(*token).at(seqIndex) == newSelType) {
1089
                    if (seqIndex == (cmd->allowedSelSequences).at(*token).size() - 1) {
1090
                        // One of the sequences is completed. Pass to cmd->applyConstraint
1091
                        cmd->applyConstraint(selSeq, *token);// replace arg 2 by ongoingToken
1092

1093
                        selSeq.clear();
1094
                        resetOngoingSequences();
1095

1096
                        return true;
1097
                    }
1098
                    _tempOnSequences.insert(*token);
1099
                    allowedSelTypes =
1100
                        allowedSelTypes | (cmd->allowedSelSequences).at(*token).at(seqIndex + 1);
1101
                }
1102
            }
1103

1104
            // Progress to next seqIndex
1105
            std::swap(_tempOnSequences, ongoingSequences);
1106
            seqIndex++;
1107
            selFilterGate->setAllowedSelTypes(allowedSelTypes);
1108
        }
1109

1110
        return true;
1111
    }
1112

1113
private:
1114
    void activated() override
1115
    {
1116
        selFilterGate = new GenericConstraintSelection(sketchgui->getObject());
1117

1118
        resetOngoingSequences();
1119

1120
        selSeq.clear();
1121

1122
        Gui::Selection().rmvSelectionGate();
1123
        Gui::Selection().addSelectionGate(selFilterGate);
1124

1125
        // Constrain icon size in px
1126
        qreal pixelRatio = devicePixelRatio();
1127
        const unsigned long defaultCrosshairColor = 0xFFFFFF;
1128
        unsigned long color = getCrosshairColor();
1129
        auto colorMapping = std::map<unsigned long, unsigned long>();
1130
        colorMapping[defaultCrosshairColor] = color;
1131

1132
        qreal fullIconWidth = 32 * pixelRatio;
1133
        qreal iconWidth = 16 * pixelRatio;
1134
        QPixmap cursorPixmap =
1135
                    Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair",
1136
                                                       QSizeF(fullIconWidth, fullIconWidth),
1137
                                                       colorMapping),
1138
                icon = Gui::BitmapFactory().pixmapFromSvg(cmd->getPixmap(),
1139
                                                          QSizeF(iconWidth, iconWidth));
1140
        QPainter cursorPainter;
1141
        cursorPainter.begin(&cursorPixmap);
1142
        cursorPainter.drawPixmap(16 * pixelRatio, 16 * pixelRatio, icon);
1143
        cursorPainter.end();
1144
        int hotX = 8;
1145
        int hotY = 8;
1146
        cursorPixmap.setDevicePixelRatio(pixelRatio);
1147
        // only X11 needs hot point coordinates to be scaled
1148
        if (qGuiApp->platformName() == QLatin1String("xcb")) {
1149
            hotX *= pixelRatio;
1150
            hotY *= pixelRatio;
1151
        }
1152
        setCursor(cursorPixmap, hotX, hotY, false);
1153
    }
1154

1155
protected:
1156
    CmdSketcherConstraint* cmd;
1157

1158
    GenericConstraintSelection* selFilterGate = nullptr;
1159

1160
    std::vector<SelIdPair> selSeq;
1161
    unsigned int allowedSelTypes = 0;
1162

1163
    /// indices of currently ongoing sequences in cmd->allowedSequences
1164
    std::set<int> ongoingSequences, _tempOnSequences;
1165
    /// Index within the selection sequences active
1166
    unsigned int seqIndex;
1167

1168
    void resetOngoingSequences()
1169
    {
1170
        ongoingSequences.clear();
1171
        for (unsigned int i = 0; i < cmd->allowedSelSequences.size(); i++) {
1172
            ongoingSequences.insert(i);
1173
        }
1174
        seqIndex = 0;
1175

1176
        // Estimate allowed selections from the first types in allowedSelTypes
1177
        allowedSelTypes = 0;
1178
        for (std::vector<std::vector<SelType>>::const_iterator it =
1179
                 cmd->allowedSelSequences.begin();
1180
             it != cmd->allowedSelSequences.end();
1181
             ++it) {
1182
            allowedSelTypes = allowedSelTypes | (*it).at(seqIndex);
1183
        }
1184
        selFilterGate->setAllowedSelTypes(allowedSelTypes);
1185

1186
        Gui::Selection().clearSelection();
1187
    }
1188
};
1189

1190
void CmdSketcherConstraint::activated(int /*iMsg*/)
1191
{
1192
    ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
1193
    getSelection().clearSelection();
1194
}
1195

1196
// Comp for dimension tools =============================================
1197

1198
class CmdSketcherCompDimensionTools : public Gui::GroupCommand
1199
{
1200
public:
1201
    CmdSketcherCompDimensionTools()
1202
        : GroupCommand("Sketcher_CompDimensionTools")
1203
    {
1204
        sAppModule = "Sketcher";
1205
        sGroup = "Sketcher";
1206
        sMenuText = QT_TR_NOOP("Dimension");
1207
        sToolTipText = QT_TR_NOOP("Dimension tools.");
1208
        sWhatsThis = "Sketcher_CompDimensionTools";
1209
        sStatusTip = sToolTipText;
1210
        eType = ForEdit;
1211

1212
        setCheckable(false);
1213
        setRememberLast(false);
1214

1215
        addCommand("Sketcher_Dimension");
1216
        addCommand(); //separator
1217
        addCommand("Sketcher_ConstrainDistanceX");
1218
        addCommand("Sketcher_ConstrainDistanceY");
1219
        addCommand("Sketcher_ConstrainDistance");
1220
        addCommand("Sketcher_ConstrainRadiam");
1221
        addCommand("Sketcher_ConstrainRadius");
1222
        addCommand("Sketcher_ConstrainDiameter");
1223
        addCommand("Sketcher_ConstrainAngle");
1224
        addCommand("Sketcher_ConstrainLock");
1225
    }
1226

1227
    void updateAction(int mode) override
1228
    {
1229
        Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
1230
        if (!pcAction) {
1231
            return;
1232
        }
1233

1234
        QList<QAction*> al = pcAction->actions();
1235
        int index = pcAction->property("defaultAction").toInt();
1236
        switch (mode) {
1237
        case Reference:
1238
            al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension_Driven"));
1239
            //al[1] is the separator
1240
            al[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance_Driven"));
1241
            al[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance_Driven"));
1242
            al[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length_Driven"));
1243
            al[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
1244
            al[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
1245
            al[7]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
1246
            al[8]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle_Driven"));
1247
            al[9]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock_Driven"));
1248
            getAction()->setIcon(al[index]->icon());
1249
            break;
1250
        case Driving:
1251
            al[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension"));
1252
            //al[1] is the separator
1253
            al[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance"));
1254
            al[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance"));
1255
            al[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length"));
1256
            al[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
1257
            al[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
1258
            al[7]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
1259
            al[8]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle"));
1260
            al[9]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock"));
1261
            getAction()->setIcon(al[index]->icon());
1262
            break;
1263
        }
1264
    }
1265

1266
    const char* className() const override { return "CmdSketcherCompDimensionTools"; }
1267
};
1268

1269
// Comp for constrain tools =============================================
1270

1271
class CmdSketcherCompConstrainTools : public Gui::GroupCommand
1272
{
1273
public:
1274
    CmdSketcherCompConstrainTools()
1275
        : GroupCommand("Sketcher_CompConstrainTools")
1276
    {
1277
        sAppModule = "Sketcher";
1278
        sGroup = "Sketcher";
1279
        sMenuText = QT_TR_NOOP("Constrain");
1280
        sToolTipText = QT_TR_NOOP("Constrain tools.");
1281
        sWhatsThis = "Sketcher_CompConstrainTools";
1282
        sStatusTip = sToolTipText;
1283
        eType = ForEdit;
1284

1285
        setCheckable(false);
1286
        setRememberLast(false);
1287

1288
        addCommand("Sketcher_ConstrainCoincidentUnified");
1289
        addCommand("Sketcher_ConstrainHorVer");
1290
        addCommand("Sketcher_ConstrainParallel");
1291
        addCommand("Sketcher_ConstrainPerpendicular");
1292
        addCommand("Sketcher_ConstrainTangent");
1293
        addCommand("Sketcher_ConstrainEqual");
1294
        addCommand("Sketcher_ConstrainSymmetric");
1295
        addCommand("Sketcher_ConstrainBlock");
1296
    }
1297
    const char* className() const override { return "CmdSketcherCompConstrainTools"; }
1298
};
1299

1300
// Comp for toggle constraint tools =============================================
1301

1302
class CmdSketcherCompToggleConstraints : public Gui::GroupCommand
1303
{
1304
public:
1305
    CmdSketcherCompToggleConstraints()
1306
        : GroupCommand("Sketcher_CompToggleConstraints")
1307
    {
1308
        sAppModule = "Sketcher";
1309
        sGroup = "Sketcher";
1310
        sMenuText = QT_TR_NOOP("Toggle constraints");
1311
        sToolTipText = QT_TR_NOOP("Toggle constrain tools.");
1312
        sWhatsThis = "Sketcher_CompToggleConstraints";
1313
        sStatusTip = sToolTipText;
1314
        eType = ForEdit;
1315

1316
        setCheckable(false);
1317
        setRememberLast(false);
1318

1319
        addCommand("Sketcher_ToggleDrivingConstraint");
1320
        addCommand("Sketcher_ToggleActiveConstraint");
1321

1322
    }
1323
    const char* className() const override
1324
    {
1325
        return "CmdSketcherCompToggleConstraints";
1326
    }
1327
    bool isActive() override
1328
    {
1329
        return isCommandActive(getActiveGuiDocument());
1330
    }
1331
};
1332

1333
// Dimension tool =======================================================
1334

1335
class GeomSelectionSizes
1336
{
1337
public:
1338
    GeomSelectionSizes(size_t s_pts, size_t s_lns, size_t s_cir, size_t s_ell, size_t s_spl) :
1339
        s_pts(s_pts), s_lns(s_lns), s_cir(s_cir), s_ell(s_ell), s_spl(s_spl) {}
1340
    ~GeomSelectionSizes() {}
1341

1342
    bool hasPoints()        const { return s_pts > 0; }
1343
    bool hasLines()         const { return s_lns > 0; }
1344
    bool hasCirclesOrArcs() const { return s_cir > 0; }
1345
    bool hasEllipseAndCo()  const { return s_ell > 0; }
1346
    bool hasSplineAndCo()   const { return s_spl > 0; }
1347

1348
    bool has1Point()             const { return s_pts == 1 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1349
    bool has2Points()            const { return s_pts == 2 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1350
    bool has1Point1Line()        const { return s_pts == 1 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1351
    bool has3Points()            const { return s_pts == 3 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1352
    bool has4MorePoints()        const { return s_pts >= 4 && s_lns == 0 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1353
    bool has2Points1Line()       const { return s_pts == 2 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1354
    bool has3MorePoints1Line()   const { return s_pts >= 3 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1355
    bool has1Point1Circle()      const { return s_pts == 1 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
1356
    bool has1MorePoint1Ellipse() const { return s_pts >= 1 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
1357

1358
    bool has1Line()              const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1359
    bool has2Lines()             const { return s_pts == 0 && s_lns == 2 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1360
    bool has3MoreLines()         const { return s_pts == 0 && s_lns >= 3 && s_cir == 0 && s_ell == 0 && s_spl == 0; }
1361
    bool has1Line1Circle()       const { return s_pts == 0 && s_lns == 1 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
1362
    bool has1Line2Circles()      const { return s_pts == 0 && s_lns == 1 && s_cir == 2 && s_ell == 0 && s_spl == 0; }
1363
    bool has1Line1Ellipse()      const { return s_pts == 0 && s_lns == 1 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
1364

1365
    bool has1Circle()            const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 0 && s_spl == 0; }
1366
    bool has2Circles()           const { return s_pts == 0 && s_lns == 0 && s_cir == 2 && s_ell == 0 && s_spl == 0; }
1367
    bool has3MoreCircles()       const { return s_pts == 0 && s_lns == 0 && s_cir >= 3 && s_ell == 0 && s_spl == 0; }
1368
    bool has1Circle1Ellipse()    const { return s_pts == 0 && s_lns == 0 && s_cir == 1 && s_ell == 1 && s_spl == 0; }
1369

1370
    bool has1Ellipse()           const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell == 1 && s_spl == 0; }
1371
    bool has2MoreEllipses()      const { return s_pts == 0 && s_lns == 0 && s_cir == 0 && s_ell >= 2 && s_spl == 0; }
1372
    bool has1Point1Spline1MoreEdge()   const { return s_pts == 1 && s_spl >= 1 && (s_lns + s_cir + s_ell + s_spl) == 2; }
1373

1374
    size_t s_pts, s_lns, s_cir, s_ell, s_spl;
1375
};
1376

1377
class DrawSketchHandlerDimension : public DrawSketchHandler
1378
{
1379
public:
1380
    explicit DrawSketchHandlerDimension(std::vector<std::string> SubNames)
1381
        : specialConstraint(SpecialConstraint::None)
1382
        , availableConstraint(AvailableConstraint::FIRST)
1383
        , previousOnSketchPos(Base::Vector2d(0.f, 0.f))
1384
        , selPoints({})
1385
        , selLine({})
1386
        , selCircleArc({})
1387
        , selEllipseAndCo({})
1388
        , selSplineAndCo({})
1389
        , initialSelection(std::move(SubNames))
1390
        , cstrIndexes({})
1391
    {
1392
    }
1393
    ~DrawSketchHandlerDimension() override
1394
    {
1395
    }
1396

1397
    enum class AvailableConstraint {
1398
        FIRST,
1399
        SECOND,
1400
        THIRD,
1401
        FOURTH,
1402
        FIFTH,
1403
        RESET
1404
    };
1405

1406
    enum class SpecialConstraint {
1407
        LineOr2PointsDistance,
1408
        Block,
1409
        None
1410
    };
1411

1412
    void activated() override
1413
    {
1414
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
1415

1416
        Obj = sketchgui->getSketchObject();
1417

1418
        // Constrain icon size in px
1419
        qreal pixelRatio = devicePixelRatio();
1420
        const unsigned long defaultCrosshairColor = 0xFFFFFF;
1421
        unsigned long color = getCrosshairColor();
1422
        auto colorMapping = std::map<unsigned long, unsigned long>();
1423
        colorMapping[defaultCrosshairColor] = color;
1424

1425
        qreal fullIconWidth = 32 * pixelRatio;
1426
        qreal iconWidth = 16 * pixelRatio;
1427
        QPixmap cursorPixmap = Gui::BitmapFactory().pixmapFromSvg("Sketcher_Crosshair", QSizeF(fullIconWidth, fullIconWidth), colorMapping),
1428
            icon = Gui::BitmapFactory().pixmapFromSvg("Constraint_Dimension", QSizeF(iconWidth, iconWidth));
1429
        QPainter cursorPainter;
1430
        cursorPainter.begin(&cursorPixmap);
1431
        cursorPainter.drawPixmap(16 * pixelRatio, 16 * pixelRatio, icon);
1432
        cursorPainter.end();
1433
        int hotX = 8;
1434
        int hotY = 8;
1435
        cursorPixmap.setDevicePixelRatio(pixelRatio);
1436
        // only X11 needs hot point coordinates to be scaled
1437
        if (qGuiApp->platformName() == QLatin1String("xcb")) {
1438
            hotX *= pixelRatio;
1439
            hotY *= pixelRatio;
1440
        }
1441
        setCursor(cursorPixmap, hotX, hotY, false);
1442

1443
        handleInitialSelection();
1444
    }
1445

1446
    void deactivated() override
1447
    {
1448
        Gui::Command::abortCommand();
1449
        Obj->solve();
1450
        sketchgui->draw(false, false); // Redraw
1451
    }
1452

1453
    void registerPressedKey(bool pressed, int key) override
1454
    {
1455
        if (key == SoKeyboardEvent::M && pressed) {
1456
            if (availableConstraint == AvailableConstraint::FIRST) {
1457
                availableConstraint = AvailableConstraint::SECOND;
1458
            }
1459
            else if (availableConstraint == AvailableConstraint::SECOND) {
1460
                availableConstraint = AvailableConstraint::THIRD;
1461
            }
1462
            else if (availableConstraint == AvailableConstraint::THIRD) {
1463
                availableConstraint = AvailableConstraint::FOURTH;
1464
            }
1465
            else if (availableConstraint == AvailableConstraint::FOURTH) {
1466
                availableConstraint = AvailableConstraint::FIFTH;
1467
            }
1468
            else if (availableConstraint == AvailableConstraint::FIFTH || availableConstraint == AvailableConstraint::RESET) {
1469
                availableConstraint = AvailableConstraint::FIRST;
1470
            }
1471
            makeAppropriateConstraint(previousOnSketchPos);
1472
        }
1473
        else {
1474
            DrawSketchHandler::registerPressedKey(pressed, key);
1475
        }
1476
    }
1477

1478
    void mouseMove(Base::Vector2d onSketchPos) override
1479
    {
1480
        if (hasBeenAborted()) {
1481
            resetTool();
1482
            return;
1483
        }
1484

1485
        previousOnSketchPos = onSketchPos;
1486

1487
        //Change distance constraint based on position of mouse.
1488
        if (specialConstraint == SpecialConstraint::LineOr2PointsDistance)
1489
            updateDistanceType(onSketchPos);
1490

1491
        //Move constraints
1492
        if (!cstrIndexes.empty()) {
1493
            bool oneMoved = false;
1494
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
1495
            int lastConstrIndex = static_cast<int>(ConStr.size()) - 1;
1496
            for (int index : cstrIndexes) {
1497
                if (ConStr[index]->isDimensional()) {
1498
                    Base::Vector2d pointWhereToMove = onSketchPos;
1499

1500
                    if (specialConstraint == SpecialConstraint::Block) {
1501
                        if (index == lastConstrIndex)
1502
                            pointWhereToMove.y = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId).y;
1503
                        else
1504
                            pointWhereToMove.x = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId).x;
1505
                    }
1506
                    moveConstraint(index, pointWhereToMove);
1507
                    oneMoved = true;
1508
                }
1509
            }
1510
            if (oneMoved)
1511
                sketchgui->draw(false, false); // Redraw
1512
        }
1513
    }
1514

1515
    bool pressButton(Base::Vector2d onSketchPos) override
1516
    {
1517
        Q_UNUSED(onSketchPos)
1518
        return true;
1519
    }
1520

1521
    bool releaseButton(Base::Vector2d onSketchPos) override
1522
    {
1523
        Q_UNUSED(onSketchPos);
1524
        availableConstraint = AvailableConstraint::FIRST;
1525
        SelIdPair selIdPair;
1526
        selIdPair.GeoId = GeoEnum::GeoUndef;
1527
        selIdPair.PosId = Sketcher::PointPos::none;
1528
        std::stringstream ss;
1529
        Base::Type newselGeoType = Base::Type::badType();
1530

1531
        int VtId = getPreselectPoint();
1532
        int CrvId = getPreselectCurve();
1533
        int CrsId = getPreselectCross();
1534

1535
        if (VtId >= 0) { //Vertex
1536
            Obj->getGeoVertexIndex(VtId,
1537
                selIdPair.GeoId, selIdPair.PosId);
1538
            newselGeoType = Part::GeomPoint::getClassTypeId();
1539
            ss << "Vertex" << VtId + 1;
1540
        }
1541
        else if (CrsId == 0) { //RootPoint
1542
            selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
1543
            selIdPair.PosId = Sketcher::PointPos::start;
1544
            newselGeoType = Part::GeomPoint::getClassTypeId();
1545
            ss << "RootPoint";
1546
        }
1547
        else if (CrsId == 1) { //H_Axis
1548
            selIdPair.GeoId = Sketcher::GeoEnum::HAxis;
1549
            newselGeoType = Part::GeomLineSegment::getClassTypeId();
1550
            ss << "H_Axis";
1551
        }
1552
        else if (CrsId == 2) { //V_Axis
1553
            selIdPair.GeoId = Sketcher::GeoEnum::VAxis;
1554
            newselGeoType = Part::GeomLineSegment::getClassTypeId();
1555
            ss << "V_Axis";
1556
        }
1557
        else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { //Curves
1558
            selIdPair.GeoId = CrvId;
1559
            const Part::Geometry* geo = Obj->getGeometry(CrvId);
1560

1561
            newselGeoType = geo->getTypeId();
1562

1563
            if (CrvId >= 0) {
1564
                ss << "Edge" << CrvId + 1;
1565
            }
1566
            else {
1567
                ss << "ExternalEdge" << Sketcher::GeoEnum::RefExt + 1 - CrvId;
1568
            }
1569
        }
1570

1571
        if (selIdPair.GeoId == GeoEnum::GeoUndef) {
1572
            // If mouse is released on "blank" space, finalize and start over
1573
            finalizeCommand();
1574
            return true;
1575
        }
1576

1577
        std::vector<SelIdPair>& selVector = getSelectionVector(newselGeoType);
1578

1579
        if (notSelectedYet(selIdPair)) {
1580
            //add the geometry to its type vector. Temporarily if not selAllowed
1581
            selVector.push_back(selIdPair);
1582

1583
            bool selAllowed = makeAppropriateConstraint(onSketchPos);
1584

1585
            if (selAllowed) {
1586
                // If mouse is released on something allowed, select it
1587
                Gui::Selection().addSelection(Obj->getDocument()->getName(),
1588
                    Obj->getNameInDocument(),
1589
                    ss.str().c_str(), onSketchPos.x, onSketchPos.y, 0.f);
1590
                sketchgui->draw(false, false); // Redraw
1591
            }
1592
            else {
1593
                selVector.pop_back();
1594
            }
1595
        }
1596
        else {
1597
            //if it is already selected we unselect it.
1598
            selVector.pop_back();
1599
            if (!selectionEmpty()) {
1600
                makeAppropriateConstraint(onSketchPos);
1601
            }
1602
            else {
1603
                restartCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
1604
            }
1605

1606
            Gui::Selection().rmvSelection(Obj->getDocument()->getName(),
1607
                Obj->getNameInDocument(),
1608
                ss.str().c_str());
1609
            sketchgui->draw(false, false); // Redraw
1610
        }
1611
        return true;
1612
    }
1613

1614
    void quit() override
1615
    {
1616
        if (!cstrIndexes.empty()) {
1617
            // if a constraint is being made, then we cancel the dimension but not the tool.
1618
            resetTool();
1619
            sketchgui->draw(false, false); // Redraw
1620
        }
1621
        else {
1622
            DrawSketchHandler::quit();
1623
        }
1624
    }
1625
protected:
1626
    SpecialConstraint specialConstraint;
1627
    AvailableConstraint availableConstraint;
1628

1629
    Base::Vector2d previousOnSketchPos;
1630

1631
    std::vector<SelIdPair> selPoints;
1632
    std::vector<SelIdPair> selLine;
1633
    std::vector<SelIdPair> selCircleArc;
1634
    std::vector<SelIdPair> selEllipseAndCo;
1635
    std::vector<SelIdPair> selSplineAndCo;
1636

1637
    std::vector<std::string> initialSelection;
1638

1639
    std::vector<int> cstrIndexes;
1640

1641
    Sketcher::SketchObject* Obj;
1642

1643
    void clearRefVectors()
1644
    {
1645
        selPoints.clear();
1646
        selLine.clear();
1647
        selCircleArc.clear();
1648
        selEllipseAndCo.clear();
1649
        selSplineAndCo.clear();
1650
    }
1651

1652
    void handleInitialSelection()
1653
    {
1654
        if (initialSelection.size() == 0) {
1655
            return;
1656
        }
1657

1658
        availableConstraint = AvailableConstraint::FIRST;
1659

1660
        // Add the selected elements to their corresponding selection vectors
1661
        for (auto& selElement : initialSelection) {
1662
            SelIdPair selIdPair;
1663
            getIdsFromName(selElement, Obj, selIdPair.GeoId, selIdPair.PosId);
1664

1665
            Base::Type newselGeoType = Base::Type::badType();
1666
            if (isEdge(selIdPair.GeoId, selIdPair.PosId)) {
1667
                const Part::Geometry* geo = Obj->getGeometry(selIdPair.GeoId);
1668
                newselGeoType = geo->getTypeId();
1669
            }
1670
            else if (isVertex(selIdPair.GeoId, selIdPair.PosId)) {
1671
                newselGeoType = Part::GeomPoint::getClassTypeId();
1672
            }
1673

1674
            std::vector<SelIdPair>& selVector = getSelectionVector(newselGeoType);
1675

1676
            //add the geometry to its type vector. Temporarily if not selAllowed
1677
            selVector.push_back(selIdPair);
1678
        }
1679

1680
        // See if the selection is valid
1681
        bool selAllowed = makeAppropriateConstraint(Base::Vector2d(0.,0.));
1682

1683
        if (!selAllowed) {
1684
            clearRefVectors();
1685
        }
1686
    }
1687

1688
    void finalizeCommand()
1689
    {
1690
        if (hasBeenAborted()) {
1691
            resetTool();
1692
            return;
1693
        }
1694

1695
        // Ask for the value of datum constraints
1696
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher");
1697
        bool show = hGrp->GetBool("ShowDialogOnDistanceConstraint", true);
1698
        const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
1699

1700
        bool commandHandledInEditDatum = false;
1701
        for (int index : cstrIndexes | boost::adaptors::reversed) {
1702
            if (show && ConStr[index]->isDimensional() && ConStr[index]->isDriving) {
1703
                commandHandledInEditDatum = true;
1704
                EditDatumDialog editDatumDialog(sketchgui, index);
1705
                editDatumDialog.exec();
1706
                if (!editDatumDialog.isSuccess()) {
1707
                    break;
1708
                }
1709
            }
1710
        }
1711

1712
        if (!commandHandledInEditDatum)
1713
            Gui::Command::commitCommand();
1714

1715
        // This code enables the continuous creation mode.
1716
        bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true);
1717
        if (continuousMode) {
1718
            resetTool();
1719
        }
1720
        else {
1721
            sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
1722
        }
1723
    }
1724

1725
    std::vector<SelIdPair>& getSelectionVector(Base::Type selGeoType)
1726
    {
1727
        if (selGeoType == Part::GeomPoint::getClassTypeId()) {
1728
            return selPoints;
1729
        }
1730
        else if (selGeoType == Part::GeomLineSegment::getClassTypeId()) {
1731
            return selLine;
1732
        }
1733
        else if (selGeoType == Part::GeomArcOfCircle::getClassTypeId() ||
1734
            selGeoType == Part::GeomCircle::getClassTypeId()) {
1735
            return selCircleArc;
1736
        }
1737
        else if (selGeoType == Part::GeomEllipse::getClassTypeId() ||
1738
            selGeoType == Part::GeomArcOfEllipse::getClassTypeId() ||
1739
            selGeoType == Part::GeomArcOfHyperbola::getClassTypeId() ||
1740
            selGeoType == Part::GeomArcOfParabola::getClassTypeId()) {
1741
            return selEllipseAndCo;
1742
        }
1743
        else if (selGeoType == Part::GeomBSplineCurve::getClassTypeId()) {
1744
            return selSplineAndCo;
1745
        }
1746

1747
        static std::vector<SelIdPair> emptyVector;
1748
        return emptyVector;
1749
    }
1750

1751
    bool notSelectedYet(const SelIdPair& elem)
1752
    {
1753
        auto contains = [&](const std::vector<SelIdPair>& vec, const SelIdPair& elem) {
1754
            for (const auto& x : vec)
1755
            {
1756
                if (x.GeoId == elem.GeoId && x.PosId == elem.PosId)
1757
                    return true;
1758
            }
1759
            return false;
1760
        };
1761

1762
        return !contains(selPoints, elem)
1763
            && !contains(selLine, elem)
1764
            && !contains(selCircleArc, elem)
1765
            && !contains(selEllipseAndCo, elem);
1766
    }
1767

1768
    bool selectionEmpty()
1769
    {
1770
        return selPoints.empty() && selLine.empty() && selCircleArc.empty() && selEllipseAndCo.empty();
1771
    }
1772

1773
    bool makeAppropriateConstraint(Base::Vector2d onSketchPos) {
1774
        bool selAllowed = false;
1775

1776
        GeomSelectionSizes selection(selPoints.size(), selLine.size(), selCircleArc.size(), selEllipseAndCo.size(), selSplineAndCo.size());
1777

1778
        if (selection.hasPoints()) {
1779
            if (selection.has1Point()) { makeCts_1Point(selAllowed, onSketchPos); }
1780
            else if (selection.has2Points()) { makeCts_2Point(selAllowed, onSketchPos); }
1781
            else if (selection.has1Point1Line()) { makeCts_1Point1Line(selAllowed, onSketchPos); }
1782
            else if (selection.has1Point1Spline1MoreEdge()) { makeCts_1Point1Spline1MoreEdge(selAllowed);}
1783
            else if (selection.has3Points()) { makeCts_3Point(selAllowed, selection.s_pts); }
1784
            else if (selection.has4MorePoints()) { makeCts_4MorePoint(selAllowed, selection.s_pts); }
1785
            else if (selection.has2Points1Line()) { makeCts_2Point1Line(selAllowed, onSketchPos, selection.s_pts); }
1786
            else if (selection.has3MorePoints1Line()) { makeCts_3MorePoint1Line(selAllowed, onSketchPos, selection.s_pts); }
1787
            else if (selection.has1Point1Circle()) { makeCts_1Point1Circle(selAllowed, onSketchPos); }
1788
            else if (selection.has1MorePoint1Ellipse()) { makeCts_1MorePoint1Ellipse(selAllowed); }
1789
        }
1790
        else if (selection.hasLines()) {
1791
            if (selection.has1Line()) { makeCts_1Line(selAllowed, onSketchPos); }
1792
            else if (selection.has2Lines()) { makeCts_2Line(selAllowed, onSketchPos); }
1793
            else if (selection.has3MoreLines()) { makeCts_3MoreLine(selAllowed, selection.s_lns); }
1794
            else if (selection.has1Line1Circle()) { makeCts_1Line1Circle(selAllowed, onSketchPos); }
1795
            else if (selection.has1Line2Circles()) { makeCts_1Line2Circle(selAllowed); }
1796
            else if (selection.has1Line1Ellipse()) { makeCts_1Line1Ellipse(selAllowed); }
1797
        }
1798
        else if (selection.hasCirclesOrArcs()) {
1799
            if (selection.has1Circle()) { makeCts_1Circle(selAllowed, onSketchPos); }
1800
            else if (selection.has2Circles()) { makeCts_2Circle(selAllowed, onSketchPos); }
1801
            else if (selection.has3MoreCircles()) { makeCts_3MoreCircle(selAllowed, selection.s_cir); }
1802
            else if (selection.has1Circle1Ellipse()) { makeCts_1Circle1Ellipse(selAllowed); }
1803
        }
1804
        else if (selection.hasEllipseAndCo()) {
1805
            if (selection.has1Ellipse()) { makeCts_1Ellipse(selAllowed); }
1806
            else if (selection.has2MoreEllipses()) { makeCts_2MoreEllipse(selAllowed, selection.s_ell); }
1807
        }
1808
        return selAllowed;
1809
    }
1810

1811
    void makeCts_1Point(bool& selAllowed, Base::Vector2d onSketchPos)
1812
    {
1813
        //distance, lock
1814
        if (availableConstraint == AvailableConstraint::FIRST) {
1815
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Distance to origin' constraint"));
1816
            createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
1817
            selAllowed = true;
1818
        }
1819
        if (availableConstraint == AvailableConstraint::SECOND) {
1820
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add lock constraint"));
1821
            specialConstraint = SpecialConstraint::Block;
1822
            createDistanceXYConstrain(Sketcher::DistanceX, selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
1823
            createDistanceXYConstrain(Sketcher::DistanceY, selPoints[0].GeoId, selPoints[0].PosId, Sketcher::GeoEnum::RtPnt, Sketcher::PointPos::start, onSketchPos);
1824
            availableConstraint = AvailableConstraint::RESET;
1825
        }
1826
    }
1827

1828
    void makeCts_2Point(bool& selAllowed, Base::Vector2d onSketchPos)
1829
    {
1830
        //distance, horizontal, vertical
1831
        if (availableConstraint == AvailableConstraint::FIRST) {
1832
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
1833
            createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
1834
            selAllowed = true;
1835
        }
1836
        if (availableConstraint == AvailableConstraint::SECOND) {
1837
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
1838
            createHorizontalConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId);
1839
        }
1840
        if (availableConstraint == AvailableConstraint::THIRD) {
1841
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
1842
            createVerticalConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId);
1843
            availableConstraint = AvailableConstraint::RESET;
1844
        }
1845
    }
1846

1847
    void makeCts_1Point1Line(bool& selAllowed, Base::Vector2d onSketchPos)
1848
    {
1849
        //distance, Symmetry
1850
        if (availableConstraint == AvailableConstraint::FIRST) {
1851
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
1852
            createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos); // point to be on first parameter
1853
            selAllowed = true;
1854
        }
1855
        if (availableConstraint == AvailableConstraint::SECOND) {
1856
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraint"));
1857
            createSymmetryConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, selPoints[0].GeoId, selPoints[0].PosId);
1858
            availableConstraint = AvailableConstraint::RESET;
1859
        }
1860
    }
1861

1862
    void makeCts_3Point(bool& selAllowed, size_t s_pts)
1863
    {
1864
        //Horizontal, vertical, symmetry
1865
        if (s_pts > 0 && availableConstraint == AvailableConstraint::FIRST) {
1866
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
1867
            for (size_t i = 0; i < s_pts - 1; i++) {
1868
                createHorizontalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
1869
            }
1870
            selAllowed = true;
1871
        }
1872
        if (s_pts > 0 && availableConstraint == AvailableConstraint::SECOND) {
1873
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
1874
            for (size_t i = 0; i < s_pts - 1; i++) {
1875
                createVerticalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
1876
            }
1877
        }
1878
        if (availableConstraint == AvailableConstraint::THIRD) {
1879
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraints"));
1880
            createSymmetryConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, selPoints[2].GeoId, selPoints[2].PosId);
1881
            availableConstraint = AvailableConstraint::RESET;
1882
        }
1883
    }
1884

1885
    void makeCts_1Point1Spline1MoreEdge(bool& /*selAllowed*/)
1886
    {
1887
        //angle
1888
        if (availableConstraint == AvailableConstraint::FIRST) {
1889
            // FIXME: Once everything is implemented uncomment restartCommand and setAllowed
1890
            // restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Angle' constraint"));
1891
            // TODO: Find the appropriate geoids and call createAngleConstrain
1892
            // selAllowed = true;
1893
        }
1894
    }
1895

1896
    void makeCts_4MorePoint(bool& selAllowed, size_t s_pts)
1897
    {
1898
        //Horizontal, vertical
1899
        if (s_pts > 0 && availableConstraint == AvailableConstraint::FIRST) {
1900
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Horizontal' constraints"));
1901
            for (size_t i = 0; i < s_pts - 1; i++) {
1902
                createHorizontalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
1903
            }
1904
            selAllowed = true;
1905
        }
1906
        if (s_pts > 0 && availableConstraint == AvailableConstraint::SECOND) {
1907
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add 'Vertical' constraints"));
1908
            for (size_t i = 0; i < s_pts - 1; i++) {
1909
                createVerticalConstrain(selPoints[i].GeoId, selPoints[i].PosId, selPoints[i + 1].GeoId, selPoints[i + 1].PosId);
1910
            }
1911
            availableConstraint = AvailableConstraint::RESET;
1912
        }
1913
    }
1914

1915
    void makeCts_2Point1Line(bool& selAllowed, Base::Vector2d onSketchPos, size_t s_pts)
1916
    {
1917
        //symmetry, distances
1918
        if (availableConstraint == AvailableConstraint::FIRST) {
1919
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraint"));
1920
            createSymmetryConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, selLine[0].GeoId, selLine[0].PosId);
1921
            selAllowed = true;
1922
        }
1923
        if (availableConstraint == AvailableConstraint::SECOND) {
1924
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraints"));
1925
            for (size_t i = 0; i < s_pts; i++) {
1926
                createDistanceConstrain(selPoints[i].GeoId, selPoints[i].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
1927
            }
1928
            availableConstraint = AvailableConstraint::RESET;
1929
        }
1930
    }
1931

1932
    void makeCts_3MorePoint1Line(bool& selAllowed, Base::Vector2d onSketchPos, size_t s_pts)
1933
    {
1934
        //distances
1935
        if (availableConstraint == AvailableConstraint::FIRST) {
1936
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraints"));
1937
            for (size_t i = 0; i < s_pts; i++) {
1938
                createDistanceConstrain(selPoints[i].GeoId, selPoints[i].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
1939
            }
1940
            selAllowed = true;
1941
            availableConstraint = AvailableConstraint::RESET;
1942
        }
1943
    }
1944

1945
    void makeCts_1Point1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
1946
    {
1947
        //Distance
1948
        if (availableConstraint == AvailableConstraint::FIRST) {
1949
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
1950
            createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selCircleArc[0].GeoId, selCircleArc[0].PosId, onSketchPos);
1951
            selAllowed = true;
1952
            availableConstraint = AvailableConstraint::RESET;
1953
        }
1954
    }
1955

1956
    void makeCts_1MorePoint1Ellipse(bool& selAllowed)
1957
    {
1958
        Q_UNUSED(selAllowed)
1959
        //distance between 1 point and ellipse/arc of... not supported yet.
1960
        if (availableConstraint == AvailableConstraint::FIRST) {
1961
            //nothing yet
1962
            //availableConstraint = AvailableConstraint::RESET;
1963
        }
1964
    }
1965

1966
    void makeCts_1Line(bool& selAllowed, Base::Vector2d onSketchPos)
1967
    {
1968
        //axis can be selected but we don't want distance on axis!
1969
        if ((selLine[0].GeoId != Sketcher::GeoEnum::VAxis && selLine[0].GeoId != Sketcher::GeoEnum::HAxis)) {
1970
            //distance, horizontal, vertical, block
1971
            if (availableConstraint == AvailableConstraint::FIRST) {
1972
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
1973
                createDistanceConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
1974
                selAllowed = true;
1975
            }
1976
            if (availableConstraint == AvailableConstraint::SECOND) {
1977
                if (isHorizontalVerticalBlock(selLine[0].GeoId)) {
1978
                    //if the line has a vertical horizontal or block constraint then we don't switch to other modes as they are horizontal, vertical and block.
1979
                    availableConstraint = AvailableConstraint::RESET;
1980
                }
1981
                else {
1982
                    restartCommand(QT_TRANSLATE_NOOP("Command", "Add Horizontal constraint"));
1983
                    createHorizontalConstrain(selLine[0].GeoId, Sketcher::PointPos::none, GeoEnum::GeoUndef, Sketcher::PointPos::none);
1984
                }
1985
            }
1986
            if (availableConstraint == AvailableConstraint::THIRD) {
1987
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Vertical constraint"));
1988
                createVerticalConstrain(selLine[0].GeoId, Sketcher::PointPos::none, GeoEnum::GeoUndef, Sketcher::PointPos::none);
1989
            }
1990
            if (availableConstraint == AvailableConstraint::FOURTH) {
1991
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Block constraint"));
1992
                createBlockConstrain(selLine[0].GeoId);
1993
                availableConstraint = AvailableConstraint::RESET;
1994
            }
1995
        }
1996
        else {
1997
            //But axis can still be selected
1998
            selAllowed = true;
1999
        }
2000
    }
2001

2002
    void makeCts_2Line(bool& selAllowed, Base::Vector2d onSketchPos)
2003
    {
2004
        //angle (if parallel: Distance (see in createAngleConstrain)), equal.
2005
        if (availableConstraint == AvailableConstraint::FIRST) {
2006
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Angle constraint"));
2007
            createAngleConstrain(selLine[0].GeoId, selLine[1].GeoId, onSketchPos);
2008
            selAllowed = true;
2009
        }
2010
        if (availableConstraint == AvailableConstraint::SECOND) {
2011
            if (selLine[0].GeoId == Sketcher::GeoEnum::VAxis || selLine[1].GeoId == Sketcher::GeoEnum::VAxis
2012
                || selLine[0].GeoId == Sketcher::GeoEnum::HAxis || selLine[1].GeoId == Sketcher::GeoEnum::HAxis) {
2013
                //if one line is axis, then can't equal..
2014
            }
2015
            else {
2016
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
2017
                createEqualityConstrain(selLine[0].GeoId, selLine[1].GeoId);
2018
            }
2019
            availableConstraint = AvailableConstraint::RESET;
2020
        }
2021
    }
2022

2023
    void makeCts_3MoreLine(bool& selAllowed, size_t s_lns)
2024
    {
2025
        //equality.
2026
        if (s_lns > 0 && availableConstraint == AvailableConstraint::FIRST) {
2027
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraints"));
2028
            for (size_t i = 0; i < s_lns - 1; i++) {
2029
                createEqualityConstrain(selLine[i].GeoId, selLine[i + 1].GeoId);
2030
            }
2031
            selAllowed = true;
2032
            availableConstraint = AvailableConstraint::RESET;
2033
        }
2034
    }
2035

2036
    void makeCts_1Line1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
2037
    {
2038
        //Distance
2039
        if (availableConstraint == AvailableConstraint::FIRST) {
2040
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
2041
            createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selLine[0].GeoId, selLine[0].PosId, onSketchPos); //Line second parameter
2042
            selAllowed = true;
2043
            availableConstraint = AvailableConstraint::RESET;
2044
        }
2045
    }
2046

2047
    void makeCts_1Line2Circle(bool& selAllowed)
2048
    {
2049
        //symmetry.
2050
        if (availableConstraint == AvailableConstraint::FIRST) {
2051
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Symmetry constraints"));
2052
            createSymmetryConstrain(selCircleArc[0].GeoId, Sketcher::PointPos::mid, selCircleArc[1].GeoId, Sketcher::PointPos::mid, selLine[0].GeoId, selLine[0].PosId);
2053
            selAllowed = true;
2054
            availableConstraint = AvailableConstraint::RESET;
2055
        }
2056
    }
2057

2058
    void makeCts_1Line1Ellipse(bool& selAllowed)
2059
    {
2060
        Q_UNUSED(selAllowed)
2061
        //TODO distance between line and ellipse/arc of... not supported yet.
2062
        if (availableConstraint == AvailableConstraint::FIRST) {
2063
            //selAllowed = true;
2064
            //availableConstraint = AvailableConstraint::RESET;
2065
        }
2066
    }
2067

2068
    void makeCts_1Circle(bool& selAllowed, Base::Vector2d onSketchPos)
2069
    {
2070
        int geoId = selCircleArc[0].GeoId;
2071
        bool reverseOrder = isRadiusDoF(geoId);
2072

2073
        if (reverseOrder) {
2074
            if (availableConstraint == AvailableConstraint::FIRST) {
2075
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc angle constraint"));
2076
                createArcAngleConstrain(geoId, onSketchPos);
2077
                selAllowed = true;
2078
            }
2079
            if (availableConstraint == AvailableConstraint::SECOND) {
2080
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc length constraint"));
2081
                createArcLengthConstrain(geoId, onSketchPos);
2082
            }
2083
            if (availableConstraint == AvailableConstraint::THIRD) {
2084
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
2085
                createRadiusDiameterConstrain(geoId, onSketchPos, true);
2086
            }
2087
            if (availableConstraint == AvailableConstraint::FOURTH) {
2088
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
2089
                createRadiusDiameterConstrain(geoId, onSketchPos, false);
2090
                availableConstraint = AvailableConstraint::RESET;
2091
            }
2092
        }
2093
        else {
2094
            if (availableConstraint == AvailableConstraint::FIRST) {
2095
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
2096
                createRadiusDiameterConstrain(geoId, onSketchPos, true);
2097
                selAllowed = true;
2098
            }
2099
            if (availableConstraint == AvailableConstraint::SECOND) {
2100
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add Radius constraint"));
2101
                createRadiusDiameterConstrain(geoId, onSketchPos, false);
2102
                if (!isArcOfCircle(*Obj->getGeometry(geoId))) {
2103
                    //This way if key is pressed again it goes back to FIRST
2104
                    availableConstraint = AvailableConstraint::RESET;
2105
                }
2106
            }
2107
            if (availableConstraint == AvailableConstraint::THIRD) {
2108
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc angle constraint"));
2109
                createArcAngleConstrain(geoId, onSketchPos);
2110
            }
2111
            if (availableConstraint == AvailableConstraint::FOURTH) {
2112
                restartCommand(QT_TRANSLATE_NOOP("Command", "Add arc length constraint"));
2113
                createArcLengthConstrain(geoId, onSketchPos);
2114
                availableConstraint = AvailableConstraint::RESET;
2115
            }
2116
        }
2117

2118
    }
2119

2120
    void makeCts_2Circle(bool& selAllowed, Base::Vector2d onSketchPos)
2121
    {
2122
        //Distance, radial distance, equality
2123
        if (availableConstraint == AvailableConstraint::FIRST) {
2124
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
2125
            createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selCircleArc[1].GeoId, selCircleArc[1].PosId, onSketchPos);
2126
            selAllowed = true;
2127
        }
2128
        if (availableConstraint == AvailableConstraint::SECOND) {
2129
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add concentric and length constraint"));
2130
            bool created = createCoincidenceConstrain(selCircleArc[0].GeoId, Sketcher::PointPos::mid, selCircleArc[1].GeoId, Sketcher::PointPos::mid);
2131
            if (!created) { //Already concentric, so skip to next
2132
                availableConstraint = AvailableConstraint::THIRD;
2133
            }
2134
            else {
2135
                createDistanceConstrain(selCircleArc[0].GeoId, selCircleArc[0].PosId, selCircleArc[1].GeoId, selCircleArc[1].PosId, onSketchPos);
2136
            }
2137
        }
2138
        if (availableConstraint == AvailableConstraint::THIRD) {
2139
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
2140
            createEqualityConstrain(selCircleArc[0].GeoId, selCircleArc[1].GeoId);
2141
            availableConstraint = AvailableConstraint::RESET;
2142
        }
2143
    }
2144

2145
    void makeCts_3MoreCircle(bool& selAllowed, size_t s_cir)
2146
    {
2147
        //equality.
2148
        if (s_cir > 0 && availableConstraint == AvailableConstraint::FIRST) {
2149
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
2150
            for (size_t i = 0; i < s_cir - 1; i++) {
2151
                createEqualityConstrain(selCircleArc[i].GeoId, selCircleArc[i + 1].GeoId);
2152
            }
2153
            selAllowed = true;
2154
            availableConstraint = AvailableConstraint::RESET;
2155
        }
2156
    }
2157

2158
    void makeCts_1Circle1Ellipse(bool& selAllowed)
2159
    {
2160
        Q_UNUSED(selAllowed)
2161
        //TODO distance between circle and ellipse/arc of... not supported yet.
2162
        if (availableConstraint == AvailableConstraint::FIRST) {
2163
            //selAllowed = true;
2164
            //availableConstraint = AvailableConstraint::RESET;
2165
        }
2166
    }
2167

2168
    void makeCts_1Ellipse(bool& selAllowed)
2169
    {
2170
        //One ellipse or arc of ellipse/hyperbola/parabola - no constrain to attribute
2171
        selAllowed = true;
2172
    }
2173

2174
    void makeCts_2MoreEllipse(bool& selAllowed, size_t s_ell)
2175
    {
2176
        //only ellipse or arc of of same kind, then equality of all radius.
2177
        bool allTheSame = 1;
2178
        const Part::Geometry* geom = Obj->getGeometry(selEllipseAndCo[0].GeoId);
2179
        Base::Type typeOf = geom->getTypeId();
2180
        for (size_t i = 1; i < s_ell; i++) {
2181
            const Part::Geometry* geomi = Obj->getGeometry(selEllipseAndCo[i].GeoId);
2182
            if (typeOf != geomi->getTypeId()) {
2183
                allTheSame = 0;
2184
            }
2185
        }
2186
        if (allTheSame) {
2187
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Equality constraint"));
2188
            for (size_t i = 1; i < s_ell; i++) {
2189
                createEqualityConstrain(selEllipseAndCo[0].GeoId, selEllipseAndCo[i].GeoId);
2190
            }
2191
            selAllowed = true;
2192
        }
2193
    }
2194

2195
    void createDistanceConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, Base::Vector2d onSketchPos) {
2196
        // If there's a point, it must be GeoId1. We could add a swap to make sure but as it's hardcoded it's not necessary.
2197

2198
        if (GeoId1 == GeoId2 || (PosId1 != Sketcher::PointPos::none && PosId2 != Sketcher::PointPos::none)) {
2199
            specialConstraint = SpecialConstraint::LineOr2PointsDistance;
2200
        }
2201

2202
        // Point-line case and point-circle/arc
2203
        if (PosId1 != Sketcher::PointPos::none && PosId2 == Sketcher::PointPos::none) {
2204
            Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
2205
            double ActDist = 0.;
2206
            const Part::Geometry* geom = Obj->getGeometry(GeoId2);
2207

2208
            if (isLineSegment(*geom)) {
2209
                auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
2210
                Base::Vector3d pnt1 = lineSeg->getStartPoint();
2211
                Base::Vector3d pnt2 = lineSeg->getEndPoint();
2212
                Base::Vector3d d = pnt2 - pnt1;
2213
                ActDist = std::abs(-pnt.x * d.y + pnt.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y) / d.Length();
2214
            }
2215
            else if (isCircle(*geom)) {
2216
                auto circle = static_cast<const Part::GeomCircle*>(geom);
2217
                Base::Vector3d ct = circle->getCenter();
2218
                Base::Vector3d di = ct - pnt;
2219
                ActDist = std::abs(di.Length() - circle->getRadius());
2220
            }
2221
            else if (isArcOfCircle(*geom)) {
2222
                auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
2223
                Base::Vector3d ct = arc->getCenter();
2224
                Base::Vector3d di = ct - pnt;
2225
                ActDist = std::abs(di.Length() - arc->getRadius());
2226
            }
2227

2228
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f)) ",
2229
                GeoId1, static_cast<int>(PosId1), GeoId2, ActDist);
2230
        }
2231
        // Circle/arc - line, circle/arc - circle/arc cases
2232
        else if (PosId1 == Sketcher::PointPos::none && PosId2 == Sketcher::PointPos::none) {
2233
            const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
2234
            const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
2235
            double radius1 {};
2236
            double radius2 {};
2237
            Base::Vector3d center1, center2;
2238
            if (isCircle(*geo1)) {
2239
                auto conic = static_cast<const Part::GeomCircle*>(geo1);
2240
                radius1 = conic->getRadius();
2241
                center1 = conic->getCenter();
2242
            }
2243
            else if (isArcOfCircle(*geo1)) {
2244
                auto conic = static_cast<const Part::GeomArcOfCircle*>(geo1);
2245
                radius1 = conic->getRadius();
2246
                center1 = conic->getCenter();
2247
            }
2248
            if (isCircle(*geo2)) {
2249
                auto conic = static_cast<const Part::GeomCircle*>(geo2);
2250
                radius2 = conic->getRadius();
2251
                center2 = conic->getCenter();
2252
            }
2253
            else if (isArcOfCircle(*geo2)){
2254
                auto conic = static_cast<const Part::GeomArcOfCircle*>(geo2);
2255
                radius2 = conic->getRadius();
2256
                center2 = conic->getCenter();
2257
            }
2258
            // Circle/arc - line case
2259
            if ((isCircle(*geo1) || isArcOfCircle(*geo1)) && isLineSegment(*geo2)) {
2260
                auto lineSeg = static_cast<const Part::GeomLineSegment*>(geo2);
2261
                Base::Vector3d pnt1 = lineSeg->getStartPoint();
2262
                Base::Vector3d pnt2 = lineSeg->getEndPoint();
2263
                Base::Vector3d d = pnt2 - pnt1;
2264
                double ActDist =
2265
                    std::abs(-center1.x * d.y + center1.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
2266
                        / d.Length()
2267
                    - radius1;
2268

2269
                Gui::cmdAppObjectArgs(Obj,
2270
                                      "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
2271
                                      GeoId1,
2272
                                      GeoId2,
2273
                                      ActDist);
2274
            }
2275
            // Circle/arc - circle/arc case
2276
            else if ((isCircle(*geo1) || isArcOfCircle(*geo1))
2277
                     && (isCircle(*geo2) || isArcOfCircle(*geo2))) {
2278
                double ActDist = 0.;
2279

2280
                Base::Vector3d intercenter = center1 - center2;
2281
                double intercenterdistance = intercenter.Length();
2282

2283
                if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
2284

2285
                    ActDist = intercenterdistance - radius1 - radius2;
2286
                }
2287
                else {
2288
                    double bigradius = std::max(radius1, radius2);
2289
                    double smallradius = std::min(radius1, radius2);
2290

2291
                    ActDist = bigradius - smallradius - intercenterdistance;
2292
                }
2293

2294
                Gui::cmdAppObjectArgs(Obj,
2295
                                      "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
2296
                                      GeoId1,
2297
                                      GeoId2,
2298
                                      ActDist);
2299
            }
2300
        }
2301
        else {  // both points
2302
            Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
2303
            Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
2304

2305
            Gui::cmdAppObjectArgs(Obj,
2306
                                  "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ",
2307
                                  GeoId1,
2308
                                  static_cast<int>(PosId1),
2309
                                  GeoId2,
2310
                                  static_cast<int>(PosId2),
2311
                                  (pnt2 - pnt1).Length());
2312
        }
2313

2314
        finishDimensionCreation(GeoId1, GeoId2, onSketchPos);
2315
    }
2316

2317
    void createDistanceXYConstrain(Sketcher::ConstraintType type, int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, Base::Vector2d onSketchPos) {
2318
        Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
2319
        Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
2320
        double ActLength = pnt2.x - pnt1.x;
2321

2322
        if (type == Sketcher::DistanceY) {
2323
            ActLength = pnt2.y - pnt1.y;
2324
        }
2325

2326
        //negative sign avoidance: swap the points to make value positive
2327
        if (ActLength < -Precision::Confusion()) {
2328
            std::swap(GeoId1, GeoId2);
2329
            std::swap(PosId1, PosId2);
2330
            std::swap(pnt1, pnt2);
2331
            ActLength = -ActLength;
2332
        }
2333

2334
        if (type == Sketcher::DistanceY) {
2335
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f)) ",
2336
                GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActLength);
2337
        }
2338
        else {
2339
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f)) ",
2340
                GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActLength);
2341
        }
2342

2343
        finishDimensionCreation(GeoId1, GeoId2, onSketchPos);
2344
    }
2345

2346
    void createRadiusDiameterConstrain(int GeoId, Base::Vector2d onSketchPos, bool firstCstr) {
2347
        double radius = 0.0;
2348
        bool isCircleGeom = true;
2349

2350
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
2351

2352
        if(!geom)
2353
            return;
2354

2355
        if (geom && isArcOfCircle(*geom)) {
2356
            auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
2357
            radius = arc->getRadius();
2358
            isCircleGeom = false;
2359
        }
2360
        else if (geom && isCircle(*geom)) {
2361
            auto circle = static_cast<const Part::GeomCircle*>(geom);
2362
            radius = circle->getRadius();
2363
        }
2364

2365
        if (isBsplinePole(geom)) {
2366
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ",
2367
                GeoId, radius);
2368
        }
2369
        else {
2370
            ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher/dimensioning");
2371
            bool dimensioningDiameter = hGrp->GetBool("DimensioningDiameter", true);
2372
            bool dimensioningRadius = hGrp->GetBool("DimensioningRadius", true);
2373

2374
            if ((firstCstr && dimensioningRadius && !dimensioningDiameter) ||
2375
                (!firstCstr && !dimensioningRadius && dimensioningDiameter) ||
2376
                (firstCstr && dimensioningRadius && dimensioningDiameter && !isCircleGeom) ||
2377
                (!firstCstr && dimensioningRadius && dimensioningDiameter && isCircleGeom) ) {
2378
                Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Radius',%d,%f)) ",
2379
                    GeoId, radius);
2380
            }
2381
            else {
2382
                Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Diameter',%d,%f)) ",
2383
                    GeoId, radius * 2);
2384
            }
2385
        }
2386

2387
        finishDimensionCreation(GeoId, GeoEnum::GeoUndef, onSketchPos);
2388
    }
2389

2390
    bool createCoincidenceConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
2391
        // check if the edge already has a Block constraint
2392
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
2393
            return false;
2394
        }
2395

2396
        // check if this coincidence is already enforced (even indirectly)
2397
        bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
2398
        if (!constraintExists && (GeoId1 != GeoId2)) {
2399
            Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Coincident', %d, %d, %d, %d)) ",
2400
                GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
2401

2402
            addConstraintIndex();
2403
            return true;
2404
        }
2405
        return false;
2406
    }
2407

2408
    void createEqualityConstrain(int GeoId1, int GeoId2) {
2409
        // check if the edge already has a Block constraint
2410
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
2411
            return;
2412
        }
2413

2414
        const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
2415
        const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
2416

2417
        if ((isLineSegment(*geo1) && ! isLineSegment(*geo2))
2418
            || (isArcOfHyperbola(*geo1) && ! isArcOfHyperbola(*geo2))
2419
            || (isArcOfParabola(*geo1) && ! isArcOfParabola(*geo2))
2420
            || (isBsplinePole(geo1) && !isBsplinePole(geo2))
2421
            || ((isCircle(*geo1) || isArcOfCircle(*geo1)) && !(isCircle(*geo2) || isArcOfCircle(*geo2)))
2422
            || ((isEllipse(*geo1) || isArcOfEllipse(*geo1)) && !(isEllipse(*geo2) || isArcOfEllipse(*geo2)))) {
2423

2424
            Gui::TranslatedUserWarning(Obj,
2425
                QObject::tr("Wrong selection"),
2426
                QObject::tr("Select two or more edges of similar type."));
2427
            return;
2428
        }
2429

2430
        Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ",
2431
            GeoId1, GeoId2);
2432
        addConstraintIndex();
2433
    }
2434

2435
    void createAngleConstrain(int GeoId1, int GeoId2, Base::Vector2d onSketchPos) {
2436
        Sketcher::PointPos PosId1 = Sketcher::PointPos::none;
2437
        Sketcher::PointPos PosId2 = Sketcher::PointPos::none;
2438
        double ActAngle;
2439

2440
        if (!calculateAngle(Obj, GeoId1, GeoId2, PosId1, PosId2, ActAngle)) {
2441
            return;
2442
        }
2443

2444
        if (ActAngle == 0.0) {
2445
            //Here we are sure that GeoIds are lines. So 0.0 means that lines are parallel, we change to distance
2446
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
2447
            createDistanceConstrain(selLine[1].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, selLine[0].PosId, onSketchPos);
2448
            return;
2449
        }
2450

2451
        Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ",
2452
            GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), ActAngle);
2453

2454
        finishDimensionCreation(GeoId1, GeoId2, onSketchPos);
2455
    }
2456

2457
    void createArcLengthConstrain(int GeoId, Base::Vector2d onSketchPos) {
2458
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
2459
        if (!isArcOfCircle(*geom)) {
2460
            return;
2461
        }
2462

2463
        const auto* arc = static_cast<const Part::GeomArcOfCircle*>(geom);
2464
        double ActLength = arc->getAngle(false) * arc->getRadius();
2465

2466
        Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Distance',%d,%f))",
2467
            GeoId, ActLength);
2468

2469
        finishDimensionCreation(GeoId, GeoEnum::GeoUndef, onSketchPos);
2470
    }
2471

2472
    void createArcAngleConstrain(int GeoId, Base::Vector2d onSketchPos) {
2473
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
2474
        if (!isArcOfCircle(*geom)) {
2475
            return;
2476
        }
2477

2478
        const auto* arc = static_cast<const Part::GeomArcOfCircle*>(geom);
2479
        double angle = arc->getAngle(/*EmulateCCWXY=*/true);
2480

2481
        Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Angle',%d,%f))",
2482
            GeoId, angle);
2483

2484
        finishDimensionCreation(GeoId, GeoEnum::GeoUndef, onSketchPos);
2485
    }
2486

2487
    void createVerticalConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
2488
        if (selLine.size() == 1) {
2489
            Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%d)) ", GeoId1);
2490
        }
2491
        else { //2points
2492
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
2493
                return;
2494
            }
2495
            Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Vertical',%d,%d,%d,%d)) "
2496
                , GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
2497
        }
2498
        addConstraintIndex();
2499
        tryAutoRecompute(Obj);
2500
    }
2501
    void createHorizontalConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2) {
2502
        if (selLine.size() == 1) {
2503
            Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%d)) ", GeoId1);
2504
        }
2505
        else { //2points
2506
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
2507
                return;
2508
            }
2509
            Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Horizontal',%d,%d,%d,%d)) "
2510
                , GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2));
2511
        }
2512
        addConstraintIndex();
2513
        tryAutoRecompute(Obj);
2514
    }
2515

2516
    void createBlockConstrain(int GeoId) {
2517
        Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Block',%d)) ", GeoId);
2518

2519
        addConstraintIndex();
2520
        tryAutoRecompute(Obj);
2521
    }
2522

2523
    bool isHorizontalVerticalBlock(int GeoId) {
2524
        const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
2525

2526
        // check if the edge already has a Horizontal/Vertical/Block constraint
2527
        for (const auto& constraint : vals) {
2528
            if ((constraint->Type == Sketcher::Horizontal || constraint->Type == Sketcher::Vertical || constraint->Type == Sketcher::Block)
2529
                && constraint->First == GeoId) {
2530
                return true;
2531
            }
2532
        }
2533
        return false;
2534
    }
2535

2536
    void createSymmetryConstrain(int GeoId1, Sketcher::PointPos PosId1, int GeoId2, Sketcher::PointPos PosId2, int GeoId3, Sketcher::PointPos PosId3) {
2537
        if (selPoints.size() == 2 && selLine.size() == 1) {
2538
            if (isEdge(GeoId1, PosId1) && isVertex(GeoId3, PosId3)) {
2539
                std::swap(GeoId1, GeoId3);
2540
                std::swap(PosId1, PosId3);
2541
            }
2542
            else if (isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
2543
                std::swap(GeoId2, GeoId3);
2544
                std::swap(PosId2, PosId3);
2545
            }
2546

2547
            if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
2548
                return;
2549
            }
2550

2551
            const Part::Geometry* geom = Obj->getGeometry(GeoId3);
2552

2553
            if (isLineSegment(*geom)) {
2554
                if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
2555
                    Gui::TranslatedUserWarning(Obj,
2556
                        QObject::tr("Wrong selection"),
2557
                        QObject::tr("Cannot add a symmetry constraint between a line and its end points!"));
2558
                    return;
2559
                }
2560

2561
                Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d)) ",
2562
                    GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), GeoId3);
2563

2564
                addConstraintIndex();
2565
                tryAutoRecompute(Obj);
2566
            }
2567
        }
2568
        else {
2569
            if (selPoints.size() == 1 && selLine.size() == 1) { //1line 1 point
2570
                if (GeoId1 == GeoId3) {
2571
                    Gui::TranslatedUserWarning(Obj,
2572
                        QObject::tr("Wrong selection"),
2573
                        QObject::tr("Cannot add a symmetry constraint between a line and its end points!"));
2574
                    return;
2575
                }
2576
                if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
2577
                    return;
2578
                }
2579
            }
2580
            else {
2581
                if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
2582
                    return;
2583
                }
2584
            }
2585
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d)) ",
2586
                GeoId1, static_cast<int>(PosId1), GeoId2, static_cast<int>(PosId2), GeoId3, static_cast<int>(PosId3));
2587

2588
            addConstraintIndex();
2589
            tryAutoRecompute(Obj);
2590
        }
2591
    }
2592

2593
    void updateDistanceType(Base::Vector2d onSketchPos)
2594
    {
2595
        const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
2596
        Sketcher::ConstraintType type = vals[vals.size() - 1]->Type;
2597

2598
        Base::Vector3d pnt1, pnt2;
2599
        bool addedOrigin = false;
2600
        if (selPoints.size() == 1) {
2601
            //Case of single point selected, for distance constraint. We add temporarily the origin in the vector.
2602
            addedOrigin = true;
2603
            SelIdPair selIdPair;
2604
            selIdPair.GeoId = Sketcher::GeoEnum::RtPnt;
2605
            selIdPair.PosId = Sketcher::PointPos::start;
2606
            selPoints.push_back(selIdPair);
2607
        }
2608

2609
        if (selLine.size() == 1) {
2610
            pnt1 = Obj->getPoint(selLine[0].GeoId, Sketcher::PointPos::start);
2611
            pnt2 = Obj->getPoint(selLine[0].GeoId, Sketcher::PointPos::end);
2612
        }
2613
        else {
2614
            pnt1 = Obj->getPoint(selPoints[0].GeoId, selPoints[0].PosId);
2615
            pnt2 = Obj->getPoint(selPoints[1].GeoId, selPoints[1].PosId);
2616
        }
2617

2618
        double minX, minY, maxX, maxY;
2619
        minX = min(pnt1.x, pnt2.x);
2620
        maxX = max(pnt1.x, pnt2.x);
2621
        minY = min(pnt1.y, pnt2.y);
2622
        maxY = max(pnt1.y, pnt2.y);
2623
        if (onSketchPos.x > minX && onSketchPos.x < maxX
2624
            && (onSketchPos.y < minY || onSketchPos.y > maxY) && type != Sketcher::DistanceX) {
2625
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add DistanceX constraint"));
2626
            specialConstraint = SpecialConstraint::LineOr2PointsDistance;
2627
            if (selLine.size() == 1) {
2628
                createDistanceXYConstrain(Sketcher::DistanceX, selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
2629
            }
2630
            else {
2631
                createDistanceXYConstrain(Sketcher::DistanceX, selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
2632
            }
2633
        }
2634
        else if (onSketchPos.y > minY && onSketchPos.y < maxY
2635
            && (onSketchPos.x < minX || onSketchPos.x > maxX) && type != Sketcher::DistanceY) {
2636
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add DistanceY constraint"));
2637
            specialConstraint = SpecialConstraint::LineOr2PointsDistance;
2638
            if (selLine.size() == 1) {
2639
                createDistanceXYConstrain(Sketcher::DistanceY, selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
2640
            }
2641
            else {
2642
                createDistanceXYConstrain(Sketcher::DistanceY, selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
2643
            }
2644
        }
2645
        else if ((((onSketchPos.y < minY || onSketchPos.y > maxY) && (onSketchPos.x < minX || onSketchPos.x > maxX))
2646
            || (onSketchPos.y > minY && onSketchPos.y < maxY && onSketchPos.x > minX && onSketchPos.x < maxX)) && type != Sketcher::Distance) {
2647
            restartCommand(QT_TRANSLATE_NOOP("Command", "Add Distance constraint"));
2648
            if (selLine.size() == 1) {
2649
                createDistanceConstrain(selLine[0].GeoId, Sketcher::PointPos::start, selLine[0].GeoId, Sketcher::PointPos::end, onSketchPos);
2650
            }
2651
            else {
2652
                createDistanceConstrain(selPoints[0].GeoId, selPoints[0].PosId, selPoints[1].GeoId, selPoints[1].PosId, onSketchPos);
2653
            }
2654
        }
2655

2656
        if (addedOrigin) {
2657
            //remove origin
2658
            selPoints.pop_back();
2659
        }
2660
    }
2661

2662
    bool isRadiusDoF(int geoId)
2663
    {
2664
        const Part::Geometry* geo = Obj->getGeometry(geoId);
2665
        if (!isArcOfCircle(*geo)) {
2666
            return false;
2667
        }
2668

2669
        //make sure we are not taking into account the constraint created in previous mode.
2670
        Gui::Command::abortCommand();
2671
        Obj->solve();
2672

2673
        auto solvext = Obj->getSolvedSketch().getSolverExtension(geoId);
2674

2675
        if (solvext) {
2676
            auto arcInfo = solvext->getArc();
2677

2678
            return !arcInfo.isRadiusDoF();
2679
        }
2680

2681
        return false;
2682
    }
2683

2684
    void finishDimensionCreation(int GeoId1, int GeoId2, Base::Vector2d onSketchPos)
2685
    {
2686
        bool fixed = GeoId2 == GeoEnum::GeoUndef ? isPointOrSegmentFixed(Obj, GeoId1) : areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
2687

2688
        int index = Obj->Constraints.getValues().size() - 1;
2689
        if (fixed || constraintCreationMode == Reference) {
2690
            Gui::cmdAppObjectArgs(Obj, "setDriving(%i,%s)", index, "False");
2691
        }
2692

2693
        addConstraintIndex();
2694
        moveConstraint(index, onSketchPos);
2695
    }
2696

2697
    void addConstraintIndex()
2698
    {
2699
        cstrIndexes.push_back(Obj->Constraints.getValues().size() - 1);
2700
    }
2701

2702
    bool hasBeenAborted()
2703
    {
2704
        // User can abort the command with Undo (ctrl-Z)
2705
        if (!cstrIndexes.empty()) {
2706
            int lastConstrIndex = Obj->Constraints.getSize() - 1;
2707
            if (cstrIndexes.back() != lastConstrIndex) {
2708
                return true;
2709
            }
2710
        }
2711

2712
        return false;
2713
    }
2714

2715
    void restartCommand(const char* cstrName) {
2716
        specialConstraint = SpecialConstraint::None;
2717
        Gui::Command::abortCommand();
2718
        Obj->solve();
2719
        sketchgui->draw(false, false); // Redraw
2720
        Gui::Command::openCommand(cstrName);
2721

2722
        cstrIndexes.clear();
2723
    }
2724

2725
    void resetTool()
2726
    {
2727
        Gui::Command::abortCommand();
2728
        Gui::Selection().clearSelection();
2729
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Dimension"));
2730
        cstrIndexes.clear();
2731
        specialConstraint = SpecialConstraint::None;
2732
        previousOnSketchPos = Base::Vector2d(0.f, 0.f);
2733
        clearRefVectors();
2734
    }
2735
};
2736

2737
DEF_STD_CMD_AU(CmdSketcherDimension)
2738

2739
CmdSketcherDimension::CmdSketcherDimension()
2740
    : Command("Sketcher_Dimension")
2741
{
2742
    sAppModule = "Sketcher";
2743
    sGroup = "Sketcher";
2744
    sMenuText = QT_TR_NOOP("Dimension");
2745
    sToolTipText = QT_TR_NOOP("Constrain contextually based on your selection.\n"
2746
        "Depending on your selection you might have several constraints available. You can cycle through them using M key.\n"
2747
        "Left clicking on empty space will validate the current constraint. Right clicking or pressing Esc will cancel.");
2748
    sWhatsThis = "Sketcher_Dimension";
2749
    sStatusTip = sToolTipText;
2750
    sPixmap = "Constraint_Dimension";
2751
    sAccel = "D";
2752
    eType = ForEdit;
2753
}
2754

2755
void CmdSketcherDimension::activated(int iMsg)
2756
{
2757
    Q_UNUSED(iMsg);
2758
    App::AutoTransaction::setEnable(false);
2759

2760
    // get the selection
2761
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
2762
    std::vector<std::string> SubNames = {};
2763

2764
    // only one sketch with its subelements are allowed to be selected
2765
    if (selection.size() == 1 && selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
2766
        SubNames = selection[0].getSubNames();
2767
    }
2768

2769
    ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerDimension>(SubNames));
2770
}
2771

2772
void CmdSketcherDimension::updateAction(int mode)
2773
{
2774
    switch (mode) {
2775
    case Reference:
2776
        if (getAction())
2777
            getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension_Driven"));
2778
        break;
2779
    case Driving:
2780
        if (getAction())
2781
            getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Dimension"));
2782
        break;
2783
    }
2784
}
2785

2786
bool CmdSketcherDimension::isActive(void)
2787
{
2788
    return isCommandActive(getActiveGuiDocument());
2789
}
2790

2791
// Comp for horizontal/vertical =============================================
2792

2793
class CmdSketcherCompHorizontalVertical : public Gui::GroupCommand
2794
{
2795
public:
2796
    CmdSketcherCompHorizontalVertical()
2797
        : GroupCommand("Sketcher_CompHorVer")
2798
    {
2799
        sAppModule = "Sketcher";
2800
        sGroup = "Sketcher";
2801
        sMenuText = QT_TR_NOOP("Constrain horizontal/vertical");
2802
        sToolTipText = QT_TR_NOOP("Constrains a single line to either horizontal or vertical.");
2803
        sWhatsThis = "Sketcher_CompHorVer";
2804
        sStatusTip = sToolTipText;
2805
        eType = ForEdit;
2806

2807
        setCheckable(false);
2808
        setRememberLast(false);
2809

2810
        addCommand("Sketcher_ConstrainHorVer");
2811
        addCommand("Sketcher_ConstrainHorizontal");
2812
        addCommand("Sketcher_ConstrainVertical");
2813
    }
2814

2815
    const char* className() const override
2816
    {
2817
        return "CmdSketcherCompHorizontalVertical";
2818
    }
2819

2820
    bool isActive() override
2821
    {
2822
        return isCommandActive(getActiveGuiDocument());
2823
    }
2824
};
2825

2826
// ============================================================================
2827
bool canHorVerBlock(Sketcher::SketchObject* Obj, int geoId)
2828
{
2829
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
2830

2831
    // check if the edge already has a Horizontal/Vertical/Block constraint
2832
    for (auto& constr : vals) {
2833
        if (constr->Type == Sketcher::Horizontal && constr->First == geoId
2834
            && constr->FirstPos == Sketcher::PointPos::none) {
2835
            Gui::TranslatedUserWarning(
2836
                Obj,
2837
                QObject::tr("Double constraint"),
2838
                QObject::tr("The selected edge already has a horizontal constraint!"));
2839
            return false;
2840
        }
2841
        if (constr->Type == Sketcher::Vertical && constr->First == geoId
2842
            && constr->FirstPos == Sketcher::PointPos::none) {
2843
            Gui::TranslatedUserWarning(
2844
                Obj,
2845
                QObject::tr("Impossible constraint"),
2846
                QObject::tr("The selected edge already has a vertical constraint!"));
2847
            return false;
2848
        }
2849
        // check if the edge already has a Block constraint
2850
        if (constr->Type == Sketcher::Block && constr->First == geoId
2851
            && constr->FirstPos == Sketcher::PointPos::none) {
2852
            Gui::TranslatedUserWarning(
2853
                Obj,
2854
                QObject::tr("Impossible constraint"),
2855
                QObject::tr("The selected edge already has a Block constraint!"));
2856
            return false;
2857
        }
2858
    }
2859
    return true;
2860
}
2861

2862
void horVerActivated(CmdSketcherConstraint* cmd, std::string type)
2863
{
2864
    // get the selection
2865
    std::vector<Gui::SelectionObject> selection = Gui::Command::getSelection().getSelectionEx();
2866

2867
    // only one sketch with its subelements are allowed to be selected
2868
    if (selection.size() != 1
2869
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
2870
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
2871
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
2872
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
2873

2874
        if (constraintMode) {
2875
            ActivateHandler(cmd->getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(cmd));
2876
            Gui::Command::getSelection().clearSelection();
2877
        }
2878
        else {
2879
            Gui::TranslatedUserWarning(cmd->getActiveGuiDocument(),
2880
                QObject::tr("Wrong selection"),
2881
                QObject::tr("Select an edge from the sketch."));
2882
        }
2883
        return;
2884
    }
2885

2886
    // get the needed lists and objects
2887
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
2888
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
2889

2890
    std::vector<int> edgegeoids;
2891
    std::vector<int> pointgeoids;
2892
    std::vector<Sketcher::PointPos> pointpos;
2893

2894
    int fixedpoints = 0;
2895

2896
    for (auto& name : SubNames) {
2897
        int GeoId;
2898
        Sketcher::PointPos PosId;
2899
        getIdsFromName(name, Obj, GeoId, PosId);
2900

2901
        if (isEdge(GeoId, PosId)) {// it is an edge
2902
            const Part::Geometry* geo = Obj->getGeometry(GeoId);
2903

2904
            if (!isLineSegment(*geo)) {
2905
                Gui::TranslatedUserWarning(Obj,
2906
                    QObject::tr("Impossible constraint"),
2907
                    QObject::tr("The selected edge is not a line segment."));
2908
                return;
2909
            }
2910

2911
            if (canHorVerBlock(Obj, GeoId)) {
2912
                edgegeoids.push_back(GeoId);
2913
            }
2914
        }
2915
        else if (isVertex(GeoId, PosId)) {
2916
            // can be a point, a construction point, an external point or root
2917

2918
            if (isPointOrSegmentFixed(Obj, GeoId)) {
2919
                fixedpoints++;
2920
            }
2921

2922
            pointgeoids.push_back(GeoId);
2923
            pointpos.push_back(PosId);
2924
        }
2925
    }
2926

2927
    if (edgegeoids.empty() && pointgeoids.size() < 2) {
2928
        Gui::TranslatedUserWarning(
2929
            Obj,
2930
            QObject::tr("Impossible constraint"),
2931
            QObject::tr("The selected item(s) can't accept a horizontal or vertical constraint!"));
2932
        return;
2933
    }
2934

2935
    // if there is at least one edge selected, ignore the point alignment functionality
2936
    if (!edgegeoids.empty()) {
2937
        // undo command open
2938
        const char* cmdName = type == "Horizontal" ? "Add horizontal constraint" : type == "Vertical" ? "Add vertical constraint" : "Add horizontal/vertical constraint";
2939
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
2940
        for (auto& geoId : edgegeoids) {
2941
            std::string typeToApply = type;
2942
            if (type == "HorVer") {
2943
                const Part::Geometry* geo = Obj->getGeometry(geoId);
2944
                auto* line = static_cast<const Part::GeomLineSegment*>(geo);
2945
                double angle = toVector2d(line->getEndPoint() - line->getStartPoint()).Angle();
2946
                typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
2947
            }
2948

2949
            Gui::cmdAppObjectArgs(selection[0].getObject(),
2950
                "addConstraint(Sketcher.Constraint('%s',%d))",
2951
                typeToApply,
2952
                geoId);
2953
        }
2954
    }
2955
    else if (fixedpoints <= 1) {// pointgeoids
2956
        // undo command open
2957
        const char* cmdName = type == "Horizontal" ? "Add horizontal alignment" : type == "Vertical" ? "Add vertical alignment" : "Add horizontal/vertical alignment";
2958
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
2959
        std::vector<int>::iterator it;
2960
        std::vector<Sketcher::PointPos>::iterator itp;
2961
        for (it = pointgeoids.begin(), itp = pointpos.begin();
2962
            it != std::prev(pointgeoids.end()) && itp != std::prev(pointpos.end());
2963
            it++, itp++) {
2964

2965
            std::string typeToApply = type;
2966
            if (type == "HorVer") {
2967
                auto point1 = Obj->getPoint(*it, *itp);
2968
                auto point2 = Obj->getPoint(*std::next(it), *std::next(itp));
2969
                double angle = toVector2d(point2 - point1).Angle();
2970
                typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
2971
            }
2972

2973
            Gui::cmdAppObjectArgs(selection[0].getObject(),
2974
                "addConstraint(Sketcher.Constraint('%s',%d,%d,%d,%d))",
2975
                typeToApply,
2976
                *it,
2977
                static_cast<int>(*itp),
2978
                *std::next(it),
2979
                static_cast<int>(*std::next(itp)));
2980
        }
2981
    }
2982
    else {// vertex mode, fixedpoints > 1
2983
        Gui::TranslatedUserWarning(Obj,
2984
            QObject::tr("Impossible constraint"),
2985
            QObject::tr("There are more than one fixed points selected. "
2986
                "Select a maximum of one fixed point!"));
2987
        return;
2988
    }
2989
    // finish the transaction and update
2990
    Gui::Command::commitCommand();
2991

2992
    tryAutoRecompute(Obj);
2993

2994
    // clear the selection (convenience)
2995
    Gui::Command::getSelection().clearSelection();
2996
}
2997

2998
void horVerApplyConstraint(CmdSketcherConstraint* cmd, std::string type, std::vector<SelIdPair>& selSeq, int seqIndex)
2999
{
3000
    auto* sketchgui =
3001
        static_cast<SketcherGui::ViewProviderSketch*>(cmd->getActiveGuiDocument()->getInEdit());
3002
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
3003

3004
    switch (seqIndex) {
3005
    case 0:// {Edge}
3006
    {
3007
        if (selSeq.empty()) {
3008
            return;
3009
        }
3010

3011
        int CrvId = selSeq.front().GeoId;
3012
        if (CrvId != -1) {
3013
            const Part::Geometry* geo = Obj->getGeometry(CrvId);
3014

3015
            if (!isLineSegment(*geo)) {
3016
                Gui::TranslatedUserWarning(
3017
                    Obj,
3018
                    QObject::tr("Impossible constraint"),
3019
                    QObject::tr("The selected edge is not a line segment."));
3020
                return;
3021
            }
3022

3023
            // check if the edge already has a Horizontal/Vertical/Block constraint
3024
            if (!canHorVerBlock(Obj, CrvId)) {
3025
                return;
3026
            }
3027

3028
            std::string typeToApply = type;
3029
            if (type == "HorVer") {
3030
                const Part::Geometry* geo = Obj->getGeometry(CrvId);
3031
                auto* line = static_cast<const Part::GeomLineSegment*>(geo);
3032
                double angle = toVector2d(line->getEndPoint() - line->getStartPoint()).Angle();
3033
                typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
3034
            }
3035

3036
            const char* cmdName = typeToApply == "Horizontal" ? "Add horizontal constraint" : "Add vertical constraint";
3037
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
3038

3039
            // issue the actual commands to create the constraint
3040
            Gui::cmdAppObjectArgs(sketchgui->getObject(),
3041
                "addConstraint(Sketcher.Constraint('%s',%d))",
3042
                typeToApply,
3043
                CrvId);
3044
            // finish the transaction and update
3045
            Gui::Command::commitCommand();
3046

3047
            tryAutoRecompute(Obj);
3048
        }
3049

3050
        break;
3051
    }
3052

3053
    case 1:// {SelVertex, SelVertexOrRoot}
3054
    case 2:// {SelRoot, SelVertex}
3055
    {
3056
        int GeoId1, GeoId2;
3057
        Sketcher::PointPos PosId1, PosId2;
3058
        GeoId1 = selSeq.at(0).GeoId;
3059
        GeoId2 = selSeq.at(1).GeoId;
3060
        PosId1 = selSeq.at(0).PosId;
3061
        PosId2 = selSeq.at(1).PosId;
3062

3063
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
3064
            showNoConstraintBetweenFixedGeometry(Obj);
3065
            return;
3066
        }
3067

3068
        std::string typeToApply = type;
3069
        if (type == "HorVer") {
3070
            auto point1 = Obj->getPoint(GeoId1, PosId1);
3071
            auto point2 = Obj->getPoint(GeoId2, PosId2);
3072
            double angle = toVector2d(point2 - point1).Angle();
3073
            typeToApply = fabs(sin(angle)) < fabs(cos(angle)) ? "Horizontal" : "Vertical";
3074
        }
3075

3076
        // undo command open
3077
        const char* cmdName = type == "Horizontal" ? "Add horizontal alignment" : "Add vertical alignment";
3078
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", cmdName));
3079

3080
        // issue the actual commands to create the constraint
3081
        Gui::cmdAppObjectArgs(sketchgui->getObject(),
3082
            "addConstraint(Sketcher.Constraint('%s',%d,%d,%d,%d))",
3083
            typeToApply,
3084
            GeoId1,
3085
            static_cast<int>(PosId1),
3086
            GeoId2,
3087
            static_cast<int>(PosId2));
3088
        // finish the transaction and update
3089
        Gui::Command::commitCommand();
3090

3091
        tryAutoRecompute(Obj);
3092

3093
        break;
3094
    }
3095
    }
3096
}
3097

3098
class CmdSketcherConstrainHorVer : public CmdSketcherConstraint
3099
{
3100
public:
3101
    CmdSketcherConstrainHorVer();
3102
    ~CmdSketcherConstrainHorVer() override
3103
    {}
3104
    const char* className() const override
3105
    {
3106
        return "CmdSketcherConstrainHorVer";
3107
    }
3108

3109
protected:
3110
    void activated(int iMsg) override;
3111
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3112
};
3113

3114
CmdSketcherConstrainHorVer::CmdSketcherConstrainHorVer()
3115
    : CmdSketcherConstraint("Sketcher_ConstrainHorVer")
3116
{
3117
    sAppModule = "Sketcher";
3118
    sGroup = "Sketcher";
3119
    sMenuText = QT_TR_NOOP("Constrain horizontal/vertical");
3120
    sToolTipText = QT_TR_NOOP("Constrains a single line to either horizontal or vertical, whichever is closer to current alignment.");
3121
    sWhatsThis = "Sketcher_ConstrainHorVer";
3122
    sStatusTip = sToolTipText;
3123
    sPixmap = "Constraint_HorVer";
3124
    sAccel = "A";
3125
    eType = ForEdit;
3126

3127
    allowedSelSequences = { {SelEdge}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex} };
3128
}
3129

3130
void CmdSketcherConstrainHorVer::activated(int iMsg)
3131
{
3132
    Q_UNUSED(iMsg);
3133
    horVerActivated(this, "HorVer");
3134
}
3135

3136
void CmdSketcherConstrainHorVer::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
3137
{
3138
    horVerApplyConstraint(this, "HorVer", selSeq, seqIndex);
3139
}
3140

3141

3142
// ============================================================================
3143

3144
class CmdSketcherConstrainHorizontal: public CmdSketcherConstraint
3145
{
3146
public:
3147
    CmdSketcherConstrainHorizontal();
3148
    ~CmdSketcherConstrainHorizontal() override
3149
    {}
3150
    const char* className() const override
3151
    {
3152
        return "CmdSketcherConstrainHorizontal";
3153
    }
3154

3155
protected:
3156
    void activated(int iMsg) override;
3157
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3158
};
3159

3160
CmdSketcherConstrainHorizontal::CmdSketcherConstrainHorizontal()
3161
    : CmdSketcherConstraint("Sketcher_ConstrainHorizontal")
3162
{
3163
    sAppModule = "Sketcher";
3164
    sGroup = "Sketcher";
3165
    sMenuText = QT_TR_NOOP("Constrain horizontal");
3166
    sToolTipText = QT_TR_NOOP("Create a horizontal constraint on the selected item");
3167
    sWhatsThis = "Sketcher_ConstrainHorizontal";
3168
    sStatusTip = sToolTipText;
3169
    sPixmap = "Constraint_Horizontal";
3170
    sAccel = "H";
3171
    eType = ForEdit;
3172

3173
    allowedSelSequences = {{SelEdge}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}};
3174
}
3175

3176
void CmdSketcherConstrainHorizontal::activated(int iMsg)
3177
{
3178
    Q_UNUSED(iMsg);
3179
    horVerActivated(this, "Horizontal");
3180
}
3181

3182
void CmdSketcherConstrainHorizontal::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
3183
{
3184
    horVerApplyConstraint(this, "Horizontal", selSeq, seqIndex);
3185
}
3186

3187
// ================================================================================
3188

3189
class CmdSketcherConstrainVertical: public CmdSketcherConstraint
3190
{
3191
public:
3192
    CmdSketcherConstrainVertical();
3193
    ~CmdSketcherConstrainVertical() override
3194
    {}
3195
    const char* className() const override
3196
    {
3197
        return "CmdSketcherConstrainVertical";
3198
    }
3199

3200
protected:
3201
    void activated(int iMsg) override;
3202
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3203
};
3204

3205
CmdSketcherConstrainVertical::CmdSketcherConstrainVertical()
3206
    : CmdSketcherConstraint("Sketcher_ConstrainVertical")
3207
{
3208
    sAppModule = "Sketcher";
3209
    sGroup = "Sketcher";
3210
    sMenuText = QT_TR_NOOP("Constrain vertical");
3211
    sToolTipText = QT_TR_NOOP("Create a vertical constraint on the selected item");
3212
    sWhatsThis = "Sketcher_ConstrainVertical";
3213
    sStatusTip = sToolTipText;
3214
    sPixmap = "Constraint_Vertical";
3215
    sAccel = "V";
3216
    eType = ForEdit;
3217

3218
    allowedSelSequences = {{SelEdge}, {SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}};
3219
}
3220

3221
void CmdSketcherConstrainVertical::activated(int iMsg)
3222
{
3223
    Q_UNUSED(iMsg);
3224
    horVerActivated(this, "Vertical");
3225
}
3226

3227
void CmdSketcherConstrainVertical::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
3228
{
3229
    horVerApplyConstraint(this, "Vertical", selSeq, seqIndex);
3230
}
3231

3232
// ======================================================================================
3233

3234
class CmdSketcherConstrainLock: public CmdSketcherConstraint
3235
{
3236
public:
3237
    CmdSketcherConstrainLock();
3238
    ~CmdSketcherConstrainLock() override
3239
    {}
3240
    void updateAction(int mode) override;
3241
    const char* className() const override
3242
    {
3243
        return "CmdSketcherConstrainLock";
3244
    }
3245

3246
protected:
3247
    void activated(int iMsg) override;
3248
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3249
};
3250

3251
CmdSketcherConstrainLock::CmdSketcherConstrainLock()
3252
    : CmdSketcherConstraint("Sketcher_ConstrainLock")
3253
{
3254
    sAppModule = "Sketcher";
3255
    sGroup = "Sketcher";
3256
    sMenuText = QT_TR_NOOP("Constrain lock");
3257
    sToolTipText = QT_TR_NOOP("Create both a horizontal "
3258
                              "and a vertical distance constraint\n"
3259
                              "on the selected vertex");
3260
    sWhatsThis = "Sketcher_ConstrainLock";
3261
    sStatusTip = sToolTipText;
3262
    sPixmap = "Constraint_Lock";
3263
    sAccel = "K, L";
3264
    eType = ForEdit;
3265

3266
    allowedSelSequences = {{SelVertex}};
3267
}
3268

3269
void CmdSketcherConstrainLock::activated(int iMsg)
3270
{
3271
    Q_UNUSED(iMsg);
3272

3273
    // get the selection
3274
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
3275

3276
    // only one sketch with its subelements are allowed to be selected
3277
    if (selection.size() != 1
3278
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
3279
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3280
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
3281
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
3282

3283
        if (constraintMode) {
3284
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
3285
            getSelection().clearSelection();
3286
        }
3287
        else {
3288
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
3289
                                       QObject::tr("Wrong selection"),
3290
                                       QObject::tr("Select vertices from the sketch."));
3291
        }
3292
        return;
3293
    }
3294

3295
    // get the needed lists and objects
3296
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
3297
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
3298

3299
    std::vector<int> GeoId;
3300
    std::vector<Sketcher::PointPos> PosId;
3301

3302
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
3303
         ++it) {
3304
        int GeoIdt;
3305
        Sketcher::PointPos PosIdt;
3306
        getIdsFromName((*it), Obj, GeoIdt, PosIdt);
3307
        GeoId.push_back(GeoIdt);
3308
        PosId.push_back(PosIdt);
3309

3310
        if ((it != std::prev(SubNames.end())
3311
             && (isEdge(GeoIdt, PosIdt) || (GeoIdt < 0 && GeoIdt >= Sketcher::GeoEnum::VAxis)))
3312
            || (it == std::prev(SubNames.end()) && isEdge(GeoIdt, PosIdt))) {
3313
            if (selection.size() == 1) {
3314
                Gui::TranslatedUserWarning(
3315
                    Obj,
3316
                    QObject::tr("Wrong selection"),
3317
                    QObject::tr("Select one vertex from the sketch other than the origin."));
3318
            }
3319
            else {
3320
                Gui::TranslatedUserWarning(Obj,
3321
                                           QObject::tr("Wrong selection"),
3322
                                           QObject::tr("Select only vertices from the sketch. The "
3323
                                                       "last selected vertex may be the origin."));
3324
            }
3325
            // clear the selection (convenience)
3326
            getSelection().clearSelection();
3327
            return;
3328
        }
3329
    }
3330

3331
    int lastconstraintindex = Obj->Constraints.getSize() - 1;
3332

3333
    if (GeoId.size() == 1) {// absolute mode
3334
        // check if the edge already has a Block constraint
3335
        bool edgeisblocked = false;
3336

3337
        if (isPointOrSegmentFixed(Obj, GeoId[0])) {
3338
            edgeisblocked = true;
3339
        }
3340

3341
        Base::Vector3d pnt = Obj->getPoint(GeoId[0], PosId[0]);
3342

3343
        // undo command open
3344
        openCommand(QT_TRANSLATE_NOOP("Command", "Add 'Lock' constraint"));
3345
        Gui::cmdAppObjectArgs(selection[0].getObject(),
3346
                              "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f))",
3347
                              GeoId[0],
3348
                              static_cast<int>(PosId[0]),
3349
                              pnt.x);
3350
        Gui::cmdAppObjectArgs(selection[0].getObject(),
3351
                              "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f))",
3352
                              GeoId[0],
3353
                              static_cast<int>(PosId[0]),
3354
                              pnt.y);
3355

3356
        lastconstraintindex += 2;
3357

3358
        if (edgeisblocked || GeoId[0] <= Sketcher::GeoEnum::RefExt
3359
            || constraintCreationMode == Reference) {
3360
            // it is a constraint on a external line, make it non-driving
3361

3362
            Gui::cmdAppObjectArgs(selection[0].getObject(),
3363
                                  "setDriving(%d,%s)",
3364
                                  lastconstraintindex - 1,
3365
                                  "False");
3366

3367
            Gui::cmdAppObjectArgs(selection[0].getObject(),
3368
                                  "setDriving(%d,%s)",
3369
                                  lastconstraintindex,
3370
                                  "False");
3371
        }
3372
    }
3373
    else {
3374
        std::vector<int>::const_iterator itg;
3375
        std::vector<Sketcher::PointPos>::const_iterator itp;
3376

3377
        Base::Vector3d pntr = Obj->getPoint(GeoId.back(), PosId.back());
3378

3379
        // check if the edge already has a Block constraint
3380
        bool refpointfixed = false;
3381

3382
        if (isPointOrSegmentFixed(Obj, GeoId.back())) {
3383
            refpointfixed = true;
3384
        }
3385

3386
        for (itg = GeoId.begin(), itp = PosId.begin();
3387
             itg != std::prev(GeoId.end()) && itp != std::prev(PosId.end());
3388
             ++itp, ++itg) {
3389
            bool pointfixed = false;
3390

3391
            if (isPointOrSegmentFixed(Obj, *itg)) {
3392
                pointfixed = true;
3393
            }
3394

3395
            Base::Vector3d pnt = Obj->getPoint(*itg, *itp);
3396

3397
            // undo command open
3398
            openCommand(QT_TRANSLATE_NOOP("Command", "Add relative 'Lock' constraint"));
3399
            Gui::cmdAppObjectArgs(selection[0].getObject(),
3400
                                  "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
3401
                                  *itg,
3402
                                  static_cast<int>(*itp),
3403
                                  GeoId.back(),
3404
                                  static_cast<int>(PosId.back()),
3405
                                  pntr.x - pnt.x);
3406

3407
            Gui::cmdAppObjectArgs(selection[0].getObject(),
3408
                                  "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
3409
                                  *itg,
3410
                                  static_cast<int>(*itp),
3411
                                  GeoId.back(),
3412
                                  static_cast<int>(PosId.back()),
3413
                                  pntr.y - pnt.y);
3414
            lastconstraintindex += 2;
3415

3416
            if ((refpointfixed && pointfixed) || constraintCreationMode == Reference) {
3417
                // it is a constraint on a external line, make it non-driving
3418

3419
                Gui::cmdAppObjectArgs(selection[0].getObject(),
3420
                                      "setDriving(%d,%s)",
3421
                                      lastconstraintindex - 1,
3422
                                      "False");
3423

3424
                Gui::cmdAppObjectArgs(selection[0].getObject(),
3425
                                      "setDriving(%d,%s)",
3426
                                      lastconstraintindex,
3427
                                      "False");
3428
            }
3429
        }
3430
    }
3431

3432
    // finish the transaction and update
3433
    commitCommand();
3434
    tryAutoRecompute(Obj);
3435

3436
    // clear the selection (convenience)
3437
    getSelection().clearSelection();
3438
}
3439

3440
void CmdSketcherConstrainLock::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
3441
{
3442
    switch (seqIndex) {
3443
        case 0:// {Vertex}
3444
            // Create the constraints
3445
            SketcherGui::ViewProviderSketch* sketchgui =
3446
                static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
3447
            Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
3448

3449
            // check if the edge already has a Block constraint
3450
            bool pointfixed = false;
3451

3452
            if (selSeq.empty()) {
3453
                return;
3454
            }
3455

3456
            if (isPointOrSegmentFixed(Obj, selSeq.front().GeoId)) {
3457
                pointfixed = true;
3458
            }
3459

3460
            Base::Vector3d pnt = Obj->getPoint(selSeq.front().GeoId, selSeq.front().PosId);
3461

3462
            // undo command open
3463
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add fixed constraint"));
3464
            Gui::cmdAppObjectArgs(sketchgui->getObject(),
3465
                                  "addConstraint(Sketcher.Constraint('DistanceX', %d, %d, %f))",
3466
                                  selSeq.front().GeoId,
3467
                                  static_cast<int>(selSeq.front().PosId),
3468
                                  pnt.x);
3469
            Gui::cmdAppObjectArgs(sketchgui->getObject(),
3470
                                  "addConstraint(Sketcher.Constraint('DistanceY', %d, %d, %f))",
3471
                                  selSeq.front().GeoId,
3472
                                  static_cast<int>(selSeq.front().PosId),
3473
                                  pnt.y);
3474

3475
            if (pointfixed || constraintCreationMode == Reference) {
3476
                // it is a constraint on a external line, make it non-driving
3477
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
3478

3479
                Gui::cmdAppObjectArgs(sketchgui->getObject(),
3480
                                      "setDriving(%d, %s)",
3481
                                      ConStr.size() - 2,
3482
                                      "False");
3483

3484
                Gui::cmdAppObjectArgs(sketchgui->getObject(),
3485
                                      "setDriving(%d, %s)",
3486
                                      ConStr.size() - 1,
3487
                                      "False");
3488
            }
3489

3490
            // finish the transaction and update
3491
            Gui::Command::commitCommand();
3492

3493
            ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3494
                "User parameter:BaseApp/Preferences/Mod/Sketcher");
3495
            bool autoRecompute = hGrp->GetBool("AutoRecompute", false);
3496

3497
            if (autoRecompute) {
3498
                Gui::Command::updateActive();
3499
            }
3500
            break;
3501
    }
3502
}
3503

3504
void CmdSketcherConstrainLock::updateAction(int mode)
3505
{
3506
    switch (mode) {
3507
        case Reference:
3508
            if (getAction()) {
3509
                getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock_Driven"));
3510
            }
3511
            break;
3512
        case Driving:
3513
            if (getAction()) {
3514
                getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Lock"));
3515
            }
3516
            break;
3517
    }
3518
}
3519

3520
// ======================================================================================
3521

3522
class CmdSketcherConstrainBlock: public CmdSketcherConstraint
3523
{
3524
public:
3525
    CmdSketcherConstrainBlock();
3526
    ~CmdSketcherConstrainBlock() override
3527
    {}
3528
    const char* className() const override
3529
    {
3530
        return "CmdSketcherConstrainBlock";
3531
    }
3532

3533
protected:
3534
    void activated(int iMsg) override;
3535
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3536
};
3537

3538
CmdSketcherConstrainBlock::CmdSketcherConstrainBlock()
3539
    : CmdSketcherConstraint("Sketcher_ConstrainBlock")
3540
{
3541
    sAppModule = "Sketcher";
3542
    sGroup = "Sketcher";
3543
    sMenuText = QT_TR_NOOP("Constrain block");
3544
    sToolTipText = QT_TR_NOOP("Block the selected edge from moving");
3545
    sWhatsThis = "Sketcher_ConstrainBlock";
3546
    sStatusTip = sToolTipText;
3547
    sPixmap = "Constraint_Block";
3548
    sAccel = "K, B";
3549
    eType = ForEdit;
3550

3551
    allowedSelSequences = {{SelEdge}};
3552
}
3553

3554
void CmdSketcherConstrainBlock::activated(int iMsg)
3555
{
3556
    Q_UNUSED(iMsg);
3557

3558
    // get the selection
3559
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
3560

3561
    // only one sketch with its subelements are allowed to be selected
3562
    if (selection.size() != 1
3563
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
3564
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3565
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
3566
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
3567

3568
        if (constraintMode) {
3569
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
3570
            getSelection().clearSelection();
3571
        }
3572
        else {
3573
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
3574
                                       QObject::tr("Wrong selection"),
3575
                                       QObject::tr("Select vertices from the sketch."));
3576
        }
3577
        return;
3578
    }
3579

3580
    // get the needed lists and objects
3581
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
3582
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
3583

3584
    // Check that the solver does not report redundant/conflicting constraints
3585
    if (Obj->getLastSolverStatus() != GCS::Success || Obj->getLastHasConflicts()
3586
        || Obj->getLastHasRedundancies()) {
3587
        Gui::TranslatedUserWarning(Obj,
3588
                                   QObject::tr("Wrong solver status"),
3589
                                   QObject::tr("A Block constraint cannot be added "
3590
                                               "if the sketch is unsolved "
3591
                                               "or there are redundant and "
3592
                                               "conflicting constraints."));
3593
        return;
3594
    }
3595

3596
    std::vector<int> GeoId;
3597
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
3598

3599
    for (auto& subname : SubNames) {
3600
        int GeoIdt;
3601
        Sketcher::PointPos PosIdt;
3602
        getIdsFromName(subname, Obj, GeoIdt, PosIdt);
3603

3604
        if (isVertex(GeoIdt, PosIdt) || GeoIdt < 0) {
3605
            if (selection.size() == 1) {
3606
                Gui::TranslatedUserWarning(Obj,
3607
                                           QObject::tr("Wrong selection"),
3608
                                           QObject::tr("Select one edge from the sketch."));
3609
            }
3610
            else {
3611
                Gui::TranslatedUserWarning(Obj,
3612
                                           QObject::tr("Wrong selection"),
3613
                                           QObject::tr("Select only edges from the sketch."));
3614
            }
3615
            // clear the selection
3616
            getSelection().clearSelection();
3617
            return;
3618
        }
3619

3620
        // check if the edge already has a Block constraint
3621
        if (checkConstraint(vals, Sketcher::Block, GeoIdt, Sketcher::PointPos::none)) {
3622
            Gui::TranslatedUserWarning(
3623
                Obj,
3624
                QObject::tr("Double constraint"),
3625
                QObject::tr("The selected edge already has a Block constraint!"));
3626
            return;
3627
        }
3628

3629
        GeoId.push_back(GeoIdt);
3630
    }
3631

3632
    for (std::vector<int>::iterator itg = GeoId.begin(); itg != GeoId.end(); ++itg) {
3633
        // undo command open
3634
        openCommand(QT_TRANSLATE_NOOP("Command", "Add 'Block' constraint"));
3635

3636
        bool safe = addConstraintSafely(Obj, [&]() {
3637
            Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Block',%d))", (*itg));
3638
        });
3639

3640
        if (!safe) {
3641
            return;
3642
        }
3643
        else {
3644
            commitCommand();
3645
            tryAutoRecompute(Obj);
3646
        }
3647
    }
3648

3649
    // clear the selection (convenience)
3650
    getSelection().clearSelection();
3651
}
3652

3653
void CmdSketcherConstrainBlock::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
3654
{
3655
    switch (seqIndex) {
3656
        case 0:// {Edge}
3657
        {
3658
            // Create the constraints
3659
            SketcherGui::ViewProviderSketch* sketchgui =
3660
                static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
3661

3662
            auto Obj = static_cast<Sketcher::SketchObject*>(sketchgui->getObject());
3663

3664
            // check if the edge already has a Block constraint
3665
            const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
3666

3667
            if (selSeq.empty()) {
3668
                return;
3669
            }
3670

3671
            if (checkConstraint(vals,
3672
                                Sketcher::Block,
3673
                                selSeq.front().GeoId,
3674
                                Sketcher::PointPos::none)) {
3675
                Gui::TranslatedUserWarning(
3676
                    Obj,
3677
                    QObject::tr("Double constraint"),
3678
                    QObject::tr("The selected edge already has a Block constraint!"));
3679
                return;
3680
            }
3681

3682
            // undo command open
3683
            openCommand(QT_TRANSLATE_NOOP("Command", "Add block constraint"));
3684

3685
            bool safe = addConstraintSafely(Obj, [&]() {
3686
                Gui::cmdAppObjectArgs(sketchgui->getObject(),
3687
                                      "addConstraint(Sketcher.Constraint('Block',%d))",
3688
                                      selSeq.front().GeoId);
3689
            });
3690

3691
            if (!safe) {
3692
                return;
3693
            }
3694
            else {
3695
                commitCommand();
3696
                tryAutoRecompute(Obj);
3697
            }
3698
        } break;
3699
        default:
3700
            break;
3701
    }
3702
}
3703

3704
// ======================================================================================
3705

3706
class CmdSketcherConstrainCoincidentUnified : public CmdSketcherConstraint
3707
{
3708
public:
3709
    CmdSketcherConstrainCoincidentUnified(const char* initName = "Sketcher_ConstrainCoincidentUnified");
3710
    ~CmdSketcherConstrainCoincidentUnified() override
3711
    {}
3712
    const char* className() const override
3713
    {
3714
        return "CmdSketcherConstrainCoincidentUnified";
3715
    }
3716

3717
protected:
3718
    enum class CoincicenceType {
3719
        Coincident,
3720
        PointOnObject,
3721
        Both
3722
    };
3723

3724
    void activated(int iMsg) override;
3725
    void onActivated(CoincicenceType type);
3726
    void activatedCoincident(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves);
3727
    void activatedPointOnObject(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves);
3728

3729
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
3730
    void applyConstraintCoincident(std::vector<SelIdPair>& selSeq, int seqIndex);
3731
    void applyConstraintPointOnObject(std::vector<SelIdPair>& selSeq, int seqIndex);
3732

3733
    // returns true if a substitution took place
3734
    static bool substituteConstraintCombinationsPointOnObject(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2);
3735
    static bool substituteConstraintCombinationsCoincident(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2, PointPos PosId2);
3736
};
3737

3738
CmdSketcherConstrainCoincidentUnified::CmdSketcherConstrainCoincidentUnified(const char* initName)
3739
    : CmdSketcherConstraint(initName)
3740
{
3741
    sAppModule = "Sketcher";
3742
    sGroup = "Sketcher";
3743
    sMenuText = QT_TR_NOOP("Constrain coincident");
3744
    sToolTipText = QT_TR_NOOP("Create a coincident constraint between points, or fix a point on an edge, "
3745
        "or a concentric constraint between circles, arcs, and ellipses");
3746
    sWhatsThis = "Sketcher_ConstrainCoincidentUnified";
3747
    sStatusTip = sToolTipText;
3748
    sPixmap = "Constraint_Coincident";
3749

3750
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3751
        "User parameter:BaseApp/Preferences/Mod/Sketcher/Constraints");
3752
    sAccel = hGrp->GetBool("UnifiedCoincident", true) ? "C" :"C,O";
3753

3754
    eType = ForEdit;
3755

3756
    allowedSelSequences = { {SelVertex, SelEdgeOrAxis},
3757
                           {SelRoot, SelEdge},
3758
                           {SelVertex, SelExternalEdge},
3759
                           {SelEdge, SelVertexOrRoot},
3760
                           {SelEdgeOrAxis, SelVertex},
3761
                           {SelExternalEdge, SelVertex},
3762

3763
                           {SelVertex, SelVertexOrRoot},
3764
                           {SelRoot, SelVertex},
3765
                           {SelEdge, SelEdge},
3766
                           {SelEdge, SelExternalEdge},
3767
                           {SelExternalEdge, SelEdge} };
3768
}
3769

3770
bool CmdSketcherConstrainCoincidentUnified::substituteConstraintCombinationsPointOnObject(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2)
3771
{
3772
    const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
3773

3774
    int cid = 0;
3775
    for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
3776
        ++it, ++cid) {
3777
        if ((*it)->Type == Sketcher::Tangent && (*it)->FirstPos == Sketcher::PointPos::none
3778
            && (*it)->SecondPos == Sketcher::PointPos::none && (*it)->Third == GeoEnum::GeoUndef
3779
            && (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
3780
                || ((*it)->Second == GeoId1 && (*it)->First == GeoId2))
3781
            && (PosId1 == Sketcher::PointPos::start
3782
                || PosId1 == Sketcher::PointPos::end)) {
3783

3784
            // NOTE: This function does not either open or commit a command as it is used for group
3785
            // addition it relies on such infrastructure being provided by the caller.
3786

3787
            Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", cid);
3788

3789
            doEndpointToEdgeTangency(Obj, GeoId1, PosId1, GeoId2);
3790

3791
            notifyConstraintSubstitutions(
3792
                QObject::tr("Endpoint to edge tangency was applied instead."));
3793

3794
            getSelection().clearSelection();
3795
            return true;
3796
        }
3797
    }
3798

3799
    return false;
3800
}
3801

3802
bool CmdSketcherConstrainCoincidentUnified::substituteConstraintCombinationsCoincident(SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2, PointPos PosId2)
3803
{
3804
    // checks for direct and indirect coincidence constraints
3805
    bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
3806

3807
    const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
3808

3809
    // NOTE: This function does not either open or commit a command as it is used for group addition
3810
    // it relies on such infrastructure being provided by the caller.
3811

3812
    int j = 0;
3813
    for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
3814
        ++it, ++j) {
3815
        if ((*it)->Type == Sketcher::Tangent && (*it)->Third == GeoEnum::GeoUndef
3816
            && (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
3817
                || ((*it)->Second == GeoId1 && (*it)->First == GeoId2))) {
3818
            if (!(PosId1 == Sketcher::PointPos::start
3819
                  || PosId1 == Sketcher::PointPos::end)
3820
                || !(PosId2 == Sketcher::PointPos::start
3821
                     || PosId2 == Sketcher::PointPos::end)) {
3822
                continue;
3823
            }
3824
            if ((*it)->FirstPos == Sketcher::PointPos::none
3825
                && (*it)->SecondPos == Sketcher::PointPos::none) {
3826

3827
                if (constraintExists) {
3828
                    // try to remove any pre-existing direct coincident constraints
3829
                    Gui::cmdAppObjectArgs(Obj,
3830
                        "delConstraintOnPoint(%d,%d)",
3831
                        GeoId1,
3832
                        static_cast<int>(PosId1));
3833
                }
3834

3835
                Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", j);
3836

3837
                doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
3838

3839
                notifyConstraintSubstitutions(
3840
                    QObject::tr("Endpoint to endpoint tangency was applied instead."));
3841

3842
                getSelection().clearSelection();
3843
                return true;
3844
            }
3845
            else if (isBsplineKnot(Obj, GeoId1) != isBsplineKnot(Obj, GeoId2)) {
3846
                // Replace with knot-to-endpoint tangency
3847

3848
                if (isBsplineKnot(Obj, GeoId2)) {
3849
                    std::swap(GeoId1, GeoId2);
3850
                    std::swap(PosId1, PosId2);
3851
                }
3852

3853
                // if a similar tangency already exists this must result in bad constraints
3854
                if ((*it)->SecondPos == Sketcher::PointPos::none) {
3855
                    Gui::cmdAppObjectArgs(Obj, "delConstraint(%d)", j);
3856

3857
                    doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
3858

3859
                    notifyConstraintSubstitutions(
3860
                        QObject::tr("B-spline knot to endpoint tangency was applied instead."));
3861

3862
                    getSelection().clearSelection();
3863
                    return true;
3864
                }
3865
            }
3866
        }
3867
    }
3868

3869
    return false;
3870
}
3871

3872
void CmdSketcherConstrainCoincidentUnified::activated(int iMsg)
3873
{
3874
    Q_UNUSED(iMsg);
3875
    onActivated(CoincicenceType::Both);
3876
}
3877

3878
void CmdSketcherConstrainCoincidentUnified::onActivated(CoincicenceType type)
3879
{
3880
    QString errorMess;
3881
    if (type == CoincicenceType::Coincident) {
3882
        errorMess = QObject::tr("Select either several points, or several conics for concentricity.");
3883
    }
3884
    else if (type == CoincicenceType::PointOnObject) {
3885
        errorMess = QObject::tr("Select either one point and several curves, or one curve and several points");
3886
    }
3887
    else {
3888
        errorMess = QObject::tr("Select either one point and several curves or one curve and several"
3889
            " points for pointOnObject, or several points for coincidence, or several conics for concentricity.");
3890
    }
3891

3892
    // get the selection
3893
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
3894

3895
    // only one sketch with its subelements are allowed to be selected
3896
    if (selection.size() != 1
3897
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
3898
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
3899
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
3900
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
3901

3902
        if (constraintMode) {
3903
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
3904
            getSelection().clearSelection();
3905
        }
3906
        else {
3907
            Gui::TranslatedUserWarning(getActiveGuiDocument(), QObject::tr("Wrong selection"), errorMess);
3908
        }
3909
        return;
3910
    }
3911

3912
    // get the needed lists and objects
3913
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
3914
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
3915

3916
    // count curves and points
3917
    std::vector<SelIdPair> points;
3918
    std::vector<SelIdPair> curves;
3919
    for (std::size_t i = 0; i < SubNames.size(); i++) {
3920
        SelIdPair id;
3921
        getIdsFromName(SubNames[i], Obj, id.GeoId, id.PosId);
3922
        if (isEdge(id.GeoId, id.PosId)) {
3923
            curves.push_back(id);
3924
        }
3925
        if (isVertex(id.GeoId, id.PosId)) {
3926
            points.push_back(id);
3927
        }
3928
    }
3929

3930
    if (type != CoincicenceType::Coincident && ((points.size() == 1 && !curves.empty()) || (!points.empty() && curves.size() == 1))) {
3931
        activatedPointOnObject(Obj, points, curves);
3932
    }
3933
    else if (type != CoincicenceType::PointOnObject && ((!points.empty() && curves.empty()) || (points.empty() && !curves.empty()))) {
3934
        activatedCoincident(Obj, points, curves);
3935
    }
3936
    else {
3937
        Gui::TranslatedUserWarning(Obj, QObject::tr("Wrong selection"), errorMess);
3938
    }
3939
}
3940

3941
void CmdSketcherConstrainCoincidentUnified::activatedPointOnObject(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves)
3942
{
3943
    openCommand(QT_TRANSLATE_NOOP("Command", "Add point on object constraint"));
3944
    int cnt = 0;
3945
    for (std::size_t iPnt = 0; iPnt < points.size(); iPnt++) {
3946
        for (std::size_t iCrv = 0; iCrv < curves.size(); iCrv++) {
3947
            if (areBothPointsOrSegmentsFixed(obj, points[iPnt].GeoId, curves[iCrv].GeoId)) {
3948
                showNoConstraintBetweenFixedGeometry(obj);
3949
                continue;
3950
            }
3951
            if (points[iPnt].GeoId == curves[iCrv].GeoId) {
3952
                continue;// constraining a point of an element onto the element is a bad idea...
3953
            }
3954

3955
            const Part::Geometry* geom = obj->getGeometry(curves[iCrv].GeoId);
3956

3957
            if (geom && isBsplinePole(geom)) {
3958
                Gui::TranslatedUserWarning(
3959
                    obj,
3960
                    QObject::tr("Wrong selection"),
3961
                    QObject::tr("Select an edge that is not a B-spline weight."));
3962
                abortCommand();
3963

3964
                continue;
3965
            }
3966

3967
            if (substituteConstraintCombinationsPointOnObject(obj,
3968
                points[iPnt].GeoId,
3969
                points[iPnt].PosId,
3970
                curves[iCrv].GeoId)) {
3971
                cnt++;
3972
                continue;
3973
            }
3974

3975
            cnt++;
3976
            Gui::cmdAppObjectArgs(
3977
                obj,
3978
                "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
3979
                points[iPnt].GeoId,
3980
                static_cast<int>(points[iPnt].PosId),
3981
                curves[iCrv].GeoId);
3982
        }
3983
    }
3984
    if (cnt) {
3985
        commitCommand();
3986
        getSelection().clearSelection();
3987
    }
3988
    else {
3989
        abortCommand();
3990
        Gui::TranslatedUserWarning(obj,
3991
            QObject::tr("Wrong selection"),
3992
            QObject::tr("None of the selected points were constrained onto the respective curves, because they are part of the same element, they are both external geometry, or the edge is not eligible."));
3993
    }
3994
    return;
3995
}
3996

3997
void CmdSketcherConstrainCoincidentUnified::activatedCoincident(SketchObject* obj, std::vector<SelIdPair> points, std::vector<SelIdPair> curves)
3998
{
3999
    bool allConicsEdges = true;// If user selects only conics (circle, ellipse, arc, arcOfEllipse)
4000
                               // then we make concentric constraint.
4001
    for (auto& curve : curves) {
4002
        if (!isGeoConcentricCompatible(obj->getGeometry(curve.GeoId))) {
4003
            allConicsEdges = false;
4004
        }
4005

4006
        if (!allConicsEdges) {
4007
            Gui::TranslatedUserWarning(
4008
                obj,
4009
                QObject::tr("Wrong selection"),
4010
                QObject::tr("Select two or more vertices from the sketch for a coincident "
4011
                    "constraint, or two or more circles, ellipses, arcs or arcs of ellipse "
4012
                    "for a concentric constraint."));
4013
            return;
4014
        }
4015
        curve.PosId = Sketcher::PointPos::mid;
4016
    }
4017

4018
    std::vector<SelIdPair> vecOfSelIdToUse = curves.empty() ? points : curves;
4019

4020
    int GeoId1 = vecOfSelIdToUse[0].GeoId;
4021
    Sketcher::PointPos PosId1 = vecOfSelIdToUse[0].PosId;
4022

4023
    // undo command open
4024
    bool constraintsAdded = false;
4025
    openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint"));
4026

4027
    for (std::size_t i = 1; i < vecOfSelIdToUse.size(); i++) {
4028
        int GeoId2 = vecOfSelIdToUse[i].GeoId;
4029
        Sketcher::PointPos PosId2 = vecOfSelIdToUse[i].PosId;
4030

4031
        // check if the edge already has a Block constraint
4032
        if (areBothPointsOrSegmentsFixed(obj, GeoId1, GeoId2)) {
4033
            showNoConstraintBetweenFixedGeometry(obj);
4034
            return;
4035
        }
4036

4037
        // check if as a consequence of this command undesirable combinations of constraints would
4038
        // arise and substitute them with more appropriate counterparts, examples:
4039
        // - coincidence + tangency on edge
4040
        // - point on object + tangency on edge
4041
        if (substituteConstraintCombinationsCoincident(obj, GeoId1, PosId1, GeoId2, PosId2)) {
4042
            constraintsAdded = true;
4043
            break;
4044
        }
4045

4046
        // check if this coincidence is already enforced (even indirectly)
4047
        bool constraintExists = obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
4048

4049
        if (!constraintExists) {
4050
            constraintsAdded = true;
4051
            Gui::cmdAppObjectArgs(obj,
4052
                "addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d))",
4053
                GeoId1,
4054
                static_cast<int>(PosId1),
4055
                GeoId2,
4056
                static_cast<int>(PosId2));
4057
        }
4058
    }
4059

4060
    // finish or abort the transaction and update
4061
    if (constraintsAdded) {
4062
        commitCommand();
4063
    }
4064
    else {
4065
        abortCommand();
4066
    }
4067

4068
    tryAutoRecompute(obj);
4069

4070
    // clear the selection (convenience)
4071
    getSelection().clearSelection();
4072
}
4073

4074
void CmdSketcherConstrainCoincidentUnified::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
4075
{
4076
    switch (seqIndex) {
4077
    case 0:// {SelVertex, SelEdgeOrAxis}
4078
    case 1:// {SelRoot, SelEdge}
4079
    case 2:// {SelVertex, SelExternalEdge}
4080
    case 3:// {SelEdge, SelVertexOrRoot}
4081
    case 4:// {SelEdgeOrAxis, SelVertex}
4082
    case 5:// {SelExternalEdge, SelVertex}
4083
        applyConstraintPointOnObject(selSeq, seqIndex);
4084
        break;
4085
    case 6:// {SelVertex, SelVertexOrRoot}
4086
    case 7:// {SelRoot, SelVertex}
4087
    case 8:// {SelEdge, SelEdge}
4088
    case 9:// {SelEdge, SelExternalEdge}
4089
    case 10:// {SelExternalEdge, SelEdge}
4090
        seqIndex -= 6;
4091
        applyConstraintCoincident(selSeq, seqIndex);
4092
        break;
4093
    default:
4094
        return;
4095
    }
4096
}
4097

4098
void CmdSketcherConstrainCoincidentUnified::applyConstraintPointOnObject(std::vector<SelIdPair>& selSeq, int seqIndex)
4099
{
4100
    int GeoIdVt, GeoIdCrv;
4101
    Sketcher::PointPos PosIdVt;
4102

4103
    switch (seqIndex) {
4104
    case 0:// {SelVertex, SelEdgeOrAxis}
4105
    case 1:// {SelRoot, SelEdge}
4106
    case 2:// {SelVertex, SelExternalEdge}
4107
        GeoIdVt = selSeq.at(0).GeoId;
4108
        GeoIdCrv = selSeq.at(1).GeoId;
4109
        PosIdVt = selSeq.at(0).PosId;
4110

4111
        break;
4112
    case 3:// {SelEdge, SelVertexOrRoot}
4113
    case 4:// {SelEdgeOrAxis, SelVertex}
4114
    case 5:// {SelExternalEdge, SelVertex}
4115
        GeoIdVt = selSeq.at(1).GeoId;
4116
        GeoIdCrv = selSeq.at(0).GeoId;
4117
        PosIdVt = selSeq.at(1).PosId;
4118

4119
        break;
4120
    default:
4121
        return;
4122
    }
4123

4124
    SketcherGui::ViewProviderSketch* sketchgui =
4125
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
4126
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
4127

4128
    openCommand(QT_TRANSLATE_NOOP("Command", "Add point on object constraint"));
4129
    bool allOK = true;
4130

4131
    if (areBothPointsOrSegmentsFixed(Obj, GeoIdVt, GeoIdCrv)) {
4132
        showNoConstraintBetweenFixedGeometry(Obj);
4133
        allOK = false;
4134
    }
4135
    if (GeoIdVt == GeoIdCrv) {
4136
        allOK = false;// constraining a point of an element onto the element is a bad idea...
4137
    }
4138

4139
    const Part::Geometry* geom = Obj->getGeometry(GeoIdCrv);
4140

4141
    if (geom && isBsplinePole(geom)) {
4142
        Gui::TranslatedUserWarning(Obj,
4143
            QObject::tr("Wrong selection"),
4144
            QObject::tr("Select an edge that is not a B-spline weight."));
4145
        abortCommand();
4146

4147
        return;
4148
    }
4149

4150
    if (allOK) {
4151
        if (!substituteConstraintCombinationsPointOnObject(Obj, GeoIdVt, PosIdVt, GeoIdCrv)) {
4152
            Gui::cmdAppObjectArgs(sketchgui->getObject(),
4153
                "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
4154
                GeoIdVt,
4155
                static_cast<int>(PosIdVt),
4156
                GeoIdCrv);
4157
        }
4158

4159
        commitCommand();
4160
        tryAutoRecompute(Obj);
4161
    }
4162
    else {
4163
        abortCommand();
4164
        Gui::TranslatedUserWarning(Obj,
4165
            QObject::tr("Wrong selection"),
4166
            QObject::tr("None of the selected points "
4167
                "were constrained onto the respective curves, "
4168
                "either because they are parts of the same element, "
4169
                "or because they are both external geometry."));
4170
    }
4171
    return;
4172
}
4173

4174
void CmdSketcherConstrainCoincidentUnified::applyConstraintCoincident(std::vector<SelIdPair>& selSeq, int seqIndex)
4175
{
4176
    SketcherGui::ViewProviderSketch* sketchgui =
4177
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
4178
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
4179

4180
    int GeoId1 = selSeq.at(0).GeoId, GeoId2 = selSeq.at(1).GeoId;
4181
    Sketcher::PointPos PosId1 = selSeq.at(0).PosId, PosId2 = selSeq.at(1).PosId;
4182

4183
    switch (seqIndex) {
4184
    case 0:// {SelVertex, SelVertexOrRoot}
4185
    case 1:// {SelRoot, SelVertex}
4186
        // Nothing specific.
4187
        break;
4188
    case 2:// {SelEdge, SelEdge}
4189
    case 3:// {SelEdge, SelExternalEdge}
4190
    case 4:// {SelExternalEdge, SelEdge}
4191
        // Concentric for circles, ellipse, arc, arcofEllipse only.
4192
        if (!isGeoConcentricCompatible(Obj->getGeometry(GeoId1))
4193
            || !isGeoConcentricCompatible(Obj->getGeometry(GeoId2))) {
4194
            Gui::TranslatedUserWarning(
4195
                Obj,
4196
                QObject::tr("Wrong selection"),
4197
                QObject::tr(
4198
                    "Select two vertices from the sketch for a coincident constraint, or two "
4199
                    "circles, ellipses, arcs or arcs of ellipse for a concentric constraint."));
4200
            return;
4201
        }
4202
        PosId1 = Sketcher::PointPos::mid;
4203
        PosId2 = Sketcher::PointPos::mid;
4204
        break;
4205
    }
4206

4207
    // check if the edge already has a Block constraint
4208
    if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
4209
        showNoConstraintBetweenFixedGeometry(Obj);
4210
        return;
4211
    }
4212

4213
    // undo command open
4214
    Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint"));
4215

4216
    // check if this coincidence is already enforced (even indirectly)
4217
    bool constraintExists = Obj->arePointsCoincident(GeoId1, PosId1, GeoId2, PosId2);
4218
    if (substituteConstraintCombinationsCoincident(Obj, GeoId1, PosId1, GeoId2, PosId2)) {}
4219
    else if (!constraintExists && (GeoId1 != GeoId2)) {
4220
        Gui::cmdAppObjectArgs(sketchgui->getObject(),
4221
            "addConstraint(Sketcher.Constraint('Coincident', %d, %d, %d, %d))",
4222
            GeoId1,
4223
            static_cast<int>(PosId1),
4224
            GeoId2,
4225
            static_cast<int>(PosId2));
4226
    }
4227
    else {
4228
        Gui::Command::abortCommand();
4229
        return;
4230
    }
4231
    Gui::Command::commitCommand();
4232
    tryAutoRecompute(Obj);
4233
}
4234

4235

4236
// ======================================================================================
4237

4238
class CmdSketcherConstrainCoincident: public CmdSketcherConstrainCoincidentUnified
4239
{
4240
public:
4241
    CmdSketcherConstrainCoincident();
4242
    ~CmdSketcherConstrainCoincident() override
4243
    {}
4244
    const char* className() const override
4245
    {
4246
        return "CmdSketcherConstrainCoincident";
4247
    }
4248

4249
protected:
4250
    void activated(int iMsg) override;
4251
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
4252
};
4253

4254
CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident()
4255
    : CmdSketcherConstrainCoincidentUnified("Sketcher_ConstrainCoincident")
4256
{
4257
    sAppModule = "Sketcher";
4258
    sGroup = "Sketcher";
4259
    sMenuText = QT_TR_NOOP("Constrain coincident");
4260
    sToolTipText = QT_TR_NOOP("Create a coincident constraint between points, or a concentric "
4261
                              "constraint between circles, arcs, and ellipses");
4262
    sWhatsThis = "Sketcher_ConstrainCoincident";
4263
    sStatusTip = sToolTipText;
4264
    sPixmap = "Constraint_PointOnPoint";
4265
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
4266
        "User parameter:BaseApp/Preferences/Mod/Sketcher/Constraints");
4267
    sAccel = hGrp->GetBool("UnifiedCoincident", true) ? "C,C" : "C";
4268
    eType = ForEdit;
4269

4270
    allowedSelSequences = {{SelVertex, SelVertexOrRoot},
4271
                           {SelRoot, SelVertex},
4272
                           {SelEdge, SelEdge},
4273
                           {SelEdge, SelExternalEdge},
4274
                           {SelExternalEdge, SelEdge}};
4275
}
4276

4277
void CmdSketcherConstrainCoincident::activated(int iMsg)
4278
{
4279
    Q_UNUSED(iMsg);
4280
    onActivated(CoincicenceType::Coincident);
4281
}
4282

4283
void CmdSketcherConstrainCoincident::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
4284
{
4285
    applyConstraintCoincident(selSeq, seqIndex);
4286
}
4287

4288
// ======================================================================================
4289

4290
class CmdSketcherConstrainPointOnObject: public CmdSketcherConstrainCoincidentUnified
4291
{
4292
public:
4293
    CmdSketcherConstrainPointOnObject();
4294
    ~CmdSketcherConstrainPointOnObject() override
4295
    {}
4296
    const char* className() const override
4297
    {
4298
        return "CmdSketcherConstrainPointOnObject";
4299
    }
4300

4301
protected:
4302
    void activated(int iMsg) override;
4303
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
4304
};
4305

4306
CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject()
4307
    : CmdSketcherConstrainCoincidentUnified("Sketcher_ConstrainPointOnObject")
4308
{
4309
    sAppModule = "Sketcher";
4310
    sGroup = "Sketcher";
4311
    sMenuText = QT_TR_NOOP("Constrain point on object");
4312
    sToolTipText = QT_TR_NOOP("Fix a point onto an object");
4313
    sWhatsThis = "Sketcher_ConstrainPointOnObject";
4314
    sStatusTip = sToolTipText;
4315
    sPixmap = "Constraint_PointOnObject";
4316
    sAccel = "O";
4317
    eType = ForEdit;
4318

4319
    allowedSelSequences = {{SelVertex, SelEdgeOrAxis},
4320
                           {SelRoot, SelEdge},
4321
                           {SelVertex, SelExternalEdge},
4322
                           {SelEdge, SelVertexOrRoot},
4323
                           {SelEdgeOrAxis, SelVertex},
4324
                           {SelExternalEdge, SelVertex}};
4325
}
4326

4327
void CmdSketcherConstrainPointOnObject::activated(int iMsg)
4328
{
4329
    Q_UNUSED(iMsg);
4330
    onActivated(CoincicenceType::PointOnObject);
4331
}
4332

4333
void CmdSketcherConstrainPointOnObject::applyConstraint(std::vector<SelIdPair>& selSeq,
4334
                                                        int seqIndex)
4335
{
4336
    applyConstraintPointOnObject(selSeq, seqIndex);
4337
}
4338

4339
// ======================================================================================
4340

4341
class CmdSketcherConstrainDistance : public CmdSketcherConstraint
4342
{
4343
public:
4344
    CmdSketcherConstrainDistance();
4345
    ~CmdSketcherConstrainDistance() override
4346
    {}
4347
    void updateAction(int mode) override;
4348
    const char* className() const override
4349
    {
4350
        return "CmdSketcherConstrainDistance";
4351
    }
4352

4353
protected:
4354
    void activated(int iMsg) override;
4355
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
4356
};
4357

4358
CmdSketcherConstrainDistance::CmdSketcherConstrainDistance()
4359
    : CmdSketcherConstraint("Sketcher_ConstrainDistance")
4360
{
4361
    sAppModule = "Sketcher";
4362
    sGroup = "Sketcher";
4363
    sMenuText = QT_TR_NOOP("Constrain distance");
4364
    sToolTipText = QT_TR_NOOP("Fix a length of a line or the distance between a line and a vertex "
4365
        "or between two circles");
4366
    sWhatsThis = "Sketcher_ConstrainDistance";
4367
    sStatusTip = sToolTipText;
4368
    sPixmap = "Constraint_Length";
4369
    sAccel = "K, D";
4370
    eType = ForEdit;
4371

4372
    allowedSelSequences = { {SelVertex, SelVertexOrRoot},
4373
                           {SelRoot, SelVertex},
4374
                           {SelEdge},
4375
                           {SelExternalEdge},
4376
                           {SelVertex, SelEdgeOrAxis},
4377
                           {SelRoot, SelEdge},
4378
                           {SelVertex, SelExternalEdge},
4379
                           {SelRoot, SelExternalEdge},
4380
                           {SelEdge, SelEdge} };
4381
}
4382

4383
void CmdSketcherConstrainDistance::activated(int iMsg)
4384
{
4385
    Q_UNUSED(iMsg);
4386
    // get the selection
4387
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
4388

4389
    // only one sketch with its subelements are allowed to be selected
4390
    if (selection.size() != 1
4391
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
4392
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
4393
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
4394
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
4395

4396
        if (constraintMode) {
4397
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
4398

4399
            getSelection().clearSelection();
4400
        }
4401
        else {
4402
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
4403
                QObject::tr("Wrong selection"),
4404
                QObject::tr("Select vertices from the sketch."));
4405
        }
4406
        return;
4407
    }
4408

4409
    // get the needed lists and objects
4410
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
4411
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
4412

4413
    if (SubNames.empty() || SubNames.size() > 2) {
4414
        Gui::TranslatedUserWarning(Obj,
4415
            QObject::tr("Wrong selection"),
4416
            QObject::tr("Select exactly one line or one point and one line "
4417
                "or two points from the sketch."));
4418
        return;
4419
    }
4420

4421
    int GeoId1, GeoId2 = GeoEnum::GeoUndef;
4422
    Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::none;
4423
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
4424
    if (SubNames.size() == 2) {
4425
        getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
4426
    }
4427

4428
    bool arebothpointsorsegmentsfixed = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
4429

4430
    if (isVertex(GeoId1, PosId1)
4431
        && (GeoId2 == Sketcher::GeoEnum::VAxis || GeoId2 == Sketcher::GeoEnum::HAxis)) {
4432
        std::swap(GeoId1, GeoId2);
4433
        std::swap(PosId1, PosId2);
4434
    }
4435

4436
    if ((isVertex(GeoId1, PosId1) || GeoId1 == Sketcher::GeoEnum::VAxis
4437
        || GeoId1 == Sketcher::GeoEnum::HAxis)
4438
        && isVertex(GeoId2, PosId2)) {// point to point distance
4439

4440
        Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
4441

4442
        if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::PointPos::none) {
4443
            PosId1 = Sketcher::PointPos::start;
4444

4445
            openCommand(
4446
                QT_TRANSLATE_NOOP("Command", "Add distance from horizontal axis constraint"));
4447
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4448
                "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
4449
                GeoId1,
4450
                static_cast<int>(PosId1),
4451
                GeoId2,
4452
                static_cast<int>(PosId2),
4453
                pnt2.y);
4454
        }
4455
        else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
4456
            PosId1 = Sketcher::PointPos::start;
4457

4458
            openCommand(QT_TRANSLATE_NOOP("Command", "Add distance from vertical axis constraint"));
4459
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4460
                "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
4461
                GeoId1,
4462
                static_cast<int>(PosId1),
4463
                GeoId2,
4464
                static_cast<int>(PosId2),
4465
                pnt2.x);
4466
        }
4467
        else {
4468
            Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
4469

4470
            openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point distance constraint"));
4471
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4472
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f))",
4473
                GeoId1,
4474
                static_cast<int>(PosId1),
4475
                GeoId2,
4476
                static_cast<int>(PosId2),
4477
                (pnt2 - pnt1).Length());
4478
        }
4479

4480
        if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
4481
            // it is a constraint on a external line, make it non-driving
4482
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4483

4484
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4485
                "setDriving(%d,%s)",
4486
                ConStr.size() - 1,
4487
                "False");
4488
            finishDatumConstraint(this, Obj, false);
4489
        }
4490
        else {
4491
            finishDatumConstraint(this, Obj, true);
4492
        }
4493
        return;
4494
    }
4495
    else if ((isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2))
4496
        || (isEdge(GeoId1, PosId1) && isVertex(GeoId2, PosId2))) {// point to line distance
4497
        if (isVertex(GeoId2, PosId2)) {
4498
            std::swap(GeoId1, GeoId2);
4499
            std::swap(PosId1, PosId2);
4500
        }
4501
        Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
4502
        const Part::Geometry* geom = Obj->getGeometry(GeoId2);
4503

4504
        if (isLineSegment(*geom)) {
4505
            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
4506
            Base::Vector3d pnt1 = lineSeg->getStartPoint();
4507
            Base::Vector3d pnt2 = lineSeg->getEndPoint();
4508
            Base::Vector3d d = pnt2 - pnt1;
4509
            double ActDist =
4510
                std::abs(-pnt.x * d.y + pnt.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
4511
                / d.Length();
4512

4513
            openCommand(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
4514
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4515
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
4516
                GeoId1,
4517
                static_cast<int>(PosId1),
4518
                GeoId2,
4519
                ActDist);
4520

4521
            if (arebothpointsorsegmentsfixed
4522
                || constraintCreationMode
4523
                == Reference) {// it is a constraint on a external line, make it non-driving
4524
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4525

4526
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4527
                    "setDriving(%d,%s)",
4528
                    ConStr.size() - 1,
4529
                    "False");
4530
                finishDatumConstraint(this, Obj, false);
4531
            }
4532
            else {
4533
                finishDatumConstraint(this, Obj, true);
4534
            }
4535

4536
            return;
4537
        }
4538
        else if (isCircleOrArc(*geom)) {    // point to circle distance
4539
            auto [radius, center] = getRadiusCenterCircleArc(geom);
4540
            Base::Vector3d d = center - pnt;
4541
            double ActDist = std::abs(d.Length() - radius);
4542

4543
            openCommand(QT_TRANSLATE_NOOP("Command", "Add point to circle Distance constraint"));
4544
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4545
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
4546
                GeoId1,
4547
                static_cast<int>(PosId1),
4548
                GeoId2,
4549
                ActDist);
4550

4551
            if (arebothpointsorsegmentsfixed
4552
                || constraintCreationMode
4553
                == Reference) {// it is a constraint on a external line, make it non-driving
4554
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4555

4556
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4557
                    "setDriving(%d,%s)",
4558
                    ConStr.size() - 1,
4559
                    "False");
4560
                finishDatumConstraint(this, Obj, false);
4561
            }
4562
            else {
4563
                finishDatumConstraint(this, Obj, true);
4564
            }
4565

4566
            return;
4567
        }
4568
    }
4569
    else if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2)) {
4570
        const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
4571
        const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
4572

4573
        if(isCircleOrArc(*geom1) && isCircleOrArc(*geom2)) {
4574

4575
            auto [radius1, center1] = getRadiusCenterCircleArc(geom1);
4576
            auto [radius2, center2] = getRadiusCenterCircleArc(geom2);
4577

4578
            double ActDist = 0.0;
4579

4580
            Base::Vector3d intercenter = center1 - center2;
4581
            double intercenterdistance = intercenter.Length();
4582

4583
            if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
4584

4585
                ActDist = intercenterdistance - radius1 - radius2;
4586
            }
4587
            else {
4588
                double bigradius = std::max(radius1, radius2);
4589
                double smallradius = std::min(radius1, radius2);
4590

4591
                ActDist = bigradius - smallradius - intercenterdistance;
4592
            }
4593

4594
            openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint"));
4595
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4596
                                  "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
4597
                                  GeoId1,
4598
                                  GeoId2,
4599
                                  ActDist);
4600

4601
            if (arebothpointsorsegmentsfixed
4602
                || constraintCreationMode
4603
                    == Reference) {// it is a constraint on a external line, make it non-driving
4604
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4605

4606
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4607
                                      "setDriving(%d,%s)",
4608
                                      ConStr.size() - 1,
4609
                                      "False");
4610
                finishDatumConstraint(this, Obj, false);
4611
            }
4612
            else {
4613
                finishDatumConstraint(this, Obj, true);
4614
            }
4615

4616
            return;
4617
        }
4618
        else if ((isCircleOrArc(*geom1) && isLineSegment(*geom2))
4619
            || (isLineSegment(*geom1) && isCircleOrArc(*geom2))) {// circle to line distance
4620

4621
            if (isLineSegment(*geom1)) {
4622
                std::swap(geom1, geom2);// Assume circle is first
4623
                std::swap(GeoId1, GeoId2);
4624
            }
4625

4626
            auto [radius, center] = getRadiusCenterCircleArc(geom1);
4627

4628
            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom2);
4629
            Base::Vector3d pnt1 = lineSeg->getStartPoint();
4630
            Base::Vector3d pnt2 = lineSeg->getEndPoint();
4631
            Base::Vector3d d = pnt2 - pnt1;
4632
            double ActDist =
4633
                std::abs(-center.x * d.y + center.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
4634
                / d.Length()
4635
                - radius;
4636

4637
            openCommand(QT_TRANSLATE_NOOP("Command", "Add circle to line distance constraint"));
4638
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4639
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f)) ",
4640
                GeoId1,
4641
                GeoId2,
4642
                ActDist);
4643

4644
            if (arebothpointsorsegmentsfixed
4645
                || constraintCreationMode
4646
                == Reference) {// it is a constraint on a external line, make it non-driving
4647
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4648

4649
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4650
                    "setDriving(%i,%s)",
4651
                    ConStr.size() - 1,
4652
                    "False");
4653
                finishDatumConstraint(this, Obj, false);
4654
            }
4655
            else {
4656
                finishDatumConstraint(this, Obj, true);
4657
            }
4658

4659
            return;
4660
        }
4661
        else {
4662
            Gui::TranslatedNotification(
4663
                Obj,
4664
                QObject::tr("Wrong selection"),
4665
                QObject::tr("Cannot add a length constraint on this selection!"));
4666
            return;
4667
        }
4668
    }
4669
    else if (isEdge(GeoId1, PosId1)) {// line length
4670
        if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
4671
            Gui::TranslatedUserWarning(Obj,
4672
                QObject::tr("Wrong selection"),
4673
                QObject::tr("Cannot add a length constraint on an axis!"));
4674
            return;
4675
        }
4676

4677
        arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
4678

4679
        const Part::Geometry* geom = Obj->getGeometry(GeoId1);
4680

4681
        if (isLineSegment(*geom)) {
4682
            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
4683
            double ActLength = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length();
4684

4685
            openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
4686
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4687
                "addConstraint(Sketcher.Constraint('Distance',%d,%f))",
4688
                GeoId1,
4689
                ActLength);
4690

4691
            // it is a constraint on a external line, make it non-driving
4692
            if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
4693
                || constraintCreationMode == Reference) {
4694
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4695

4696
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4697
                    "setDriving(%d,%s)",
4698
                    ConStr.size() - 1,
4699
                    "False");
4700
                finishDatumConstraint(this, Obj, false);
4701
            }
4702
            else {
4703
                finishDatumConstraint(this, Obj, true);
4704
            }
4705

4706
            return;
4707
        }
4708
        else if (isArcOfCircle(*geom)) {
4709
            auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
4710
            double ActLength = arc->getAngle(false) * arc->getRadius();
4711

4712
            openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
4713
            Gui::cmdAppObjectArgs(selection[0].getObject(),
4714
                "addConstraint(Sketcher.Constraint('Distance',%d,%f))",
4715
                GeoId1,
4716
                ActLength);
4717

4718
            // it is a constraint on a external line, make it non-driving
4719
            if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
4720
                || constraintCreationMode == Reference) {
4721
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4722

4723
                Gui::cmdAppObjectArgs(selection[0].getObject(),
4724
                    "setDriving(%d,%s)",
4725
                    ConStr.size() - 1,
4726
                    "False");
4727
                finishDatumConstraint(this, Obj, false);
4728
            }
4729
            else {
4730
                finishDatumConstraint(this, Obj, true);
4731
            }
4732

4733
            return;
4734
        }
4735
    }
4736

4737
    Gui::TranslatedUserWarning(Obj,
4738
        QObject::tr("Wrong selection"),
4739
        QObject::tr("Select exactly one line or one point and one line or "
4740
            "two points or two circles from the sketch."));
4741
    return;
4742
}
4743

4744
void CmdSketcherConstrainDistance::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
4745
{
4746
    SketcherGui::ViewProviderSketch* sketchgui =
4747
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
4748
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
4749

4750
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
4751
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none;
4752

4753
    bool arebothpointsorsegmentsfixed = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
4754

4755
    switch (seqIndex) {
4756
    case 0:// {SelVertex, SelVertexOrRoot}
4757
    case 1:// {SelRoot, SelVertex}
4758
    {
4759
        GeoId1 = selSeq.at(0).GeoId;
4760
        GeoId2 = selSeq.at(1).GeoId;
4761
        PosId1 = selSeq.at(0).PosId;
4762
        PosId2 = selSeq.at(1).PosId;
4763

4764
        Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
4765

4766
        if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::PointPos::none) {
4767
            PosId1 = Sketcher::PointPos::start;
4768

4769
            openCommand(
4770
                QT_TRANSLATE_NOOP("Command", "Add distance from horizontal axis constraint"));
4771
            Gui::cmdAppObjectArgs(
4772
                Obj,
4773
                "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
4774
                GeoId1,
4775
                static_cast<int>(PosId1),
4776
                GeoId2,
4777
                static_cast<int>(PosId2),
4778
                pnt2.y);
4779
        }
4780
        else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
4781
            PosId1 = Sketcher::PointPos::start;
4782

4783
            openCommand(
4784
                QT_TRANSLATE_NOOP("Command", "Add distance from vertical axis constraint"));
4785
            Gui::cmdAppObjectArgs(
4786
                Obj,
4787
                "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
4788
                GeoId1,
4789
                static_cast<int>(PosId1),
4790
                GeoId2,
4791
                static_cast<int>(PosId2),
4792
                pnt2.x);
4793
        }
4794
        else {
4795
            Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
4796

4797
            openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point distance constraint"));
4798
            Gui::cmdAppObjectArgs(
4799
                Obj,
4800
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f))",
4801
                GeoId1,
4802
                static_cast<int>(PosId1),
4803
                GeoId2,
4804
                static_cast<int>(PosId2),
4805
                (pnt2 - pnt1).Length());
4806
        }
4807

4808
        if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
4809
            // it is a constraint on a external line, make it non-driving
4810
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4811

4812
            Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
4813
            finishDatumConstraint(this, Obj, false);
4814
        }
4815
        else {
4816
            finishDatumConstraint(this, Obj, true);
4817
        }
4818

4819
        return;
4820
    }
4821
    case 2:// {SelEdge}
4822
    case 3:// {SelExternalEdge}
4823
    {
4824
        GeoId1 = selSeq.at(0).GeoId;
4825

4826
        arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
4827

4828
        const Part::Geometry* geom = Obj->getGeometry(GeoId1);
4829

4830
        if (isLineSegment(*geom)) {
4831
            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
4832
            double ActLength = (lineSeg->getEndPoint() - lineSeg->getStartPoint()).Length();
4833

4834
            openCommand(QT_TRANSLATE_NOOP("Command", "Add length constraint"));
4835
            Gui::cmdAppObjectArgs(Obj,
4836
                "addConstraint(Sketcher.Constraint('Distance',%d,%f))",
4837
                GeoId1,
4838
                ActLength);
4839

4840
            if (arebothpointsorsegmentsfixed || GeoId1 <= Sketcher::GeoEnum::RefExt
4841
                || constraintCreationMode == Reference) {
4842
                // it is a constraint on a external line, make it non-driving
4843
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4844

4845
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
4846
                finishDatumConstraint(this, Obj, false);
4847
            }
4848
            else {
4849
                finishDatumConstraint(this, Obj, true);
4850
            }
4851
        }
4852
        else if (isCircle(*geom)) {
4853
            // allow this selection but do nothing as it needs 2 circles or 1 circle and 1 line
4854
        }
4855
        else {
4856
            Gui::TranslatedUserWarning(
4857
                Obj,
4858
                QObject::tr("Wrong selection"),
4859
                QObject::tr("This constraint does not make sense for non-linear curves."));
4860
        }
4861

4862
        return;
4863
    }
4864
    case 4:// {SelVertex, SelEdgeOrAxis}
4865
    case 5:// {SelRoot, SelEdge}
4866
    case 6:// {SelVertex, SelExternalEdge}
4867
    case 7:// {SelRoot, SelExternalEdge}
4868
    {
4869
        GeoId1 = selSeq.at(0).GeoId;
4870
        GeoId2 = selSeq.at(1).GeoId;
4871
        PosId1 = selSeq.at(0).PosId;
4872

4873
        Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
4874
        const Part::Geometry* geom = Obj->getGeometry(GeoId2);
4875

4876
        if (isLineSegment(*geom)) {
4877
            auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
4878
            Base::Vector3d pnt1 = lineSeg->getStartPoint();
4879
            Base::Vector3d pnt2 = lineSeg->getEndPoint();
4880
            Base::Vector3d d = pnt2 - pnt1;
4881
            double ActDist =
4882
                std::abs(-pnt.x * d.y + pnt.y * d.x + pnt1.x * pnt2.y - pnt2.x * pnt1.y)
4883
                / d.Length();
4884

4885
            openCommand(QT_TRANSLATE_NOOP("Command", "Add point to line Distance constraint"));
4886
            Gui::cmdAppObjectArgs(Obj,
4887
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f))",
4888
                GeoId1,
4889
                static_cast<int>(PosId1),
4890
                GeoId2,
4891
                ActDist);
4892

4893
            if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
4894
                // it is a constraint on a external line, make it non-driving
4895
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4896

4897
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
4898
                finishDatumConstraint(this, Obj, false);
4899
            }
4900
            else {
4901
                finishDatumConstraint(this, Obj, true);
4902
            }
4903
        }
4904

4905
        return;
4906
    }
4907
    case 8:// {SelEdge, SelEdge}
4908
    {
4909
        GeoId1 = selSeq.at(0).GeoId;
4910
        GeoId2 = selSeq.at(1).GeoId;
4911
        const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
4912
        const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
4913

4914
        if (isCircle(*geom1) && isCircle(*geom2)) {// circle to circle distance
4915
            auto circleSeg1 = static_cast<const Part::GeomCircle*>(geom1);
4916
            double radius1 = circleSeg1->getRadius();
4917
            Base::Vector3d center1 = circleSeg1->getCenter();
4918

4919
            auto circleSeg2 = static_cast<const Part::GeomCircle*>(geom2);
4920
            double radius2 = circleSeg2->getRadius();
4921
            Base::Vector3d center2 = circleSeg2->getCenter();
4922

4923
            double ActDist = 0.;
4924

4925
            Base::Vector3d intercenter = center1 - center2;
4926
            double intercenterdistance = intercenter.Length();
4927

4928
            if (intercenterdistance >= radius1 && intercenterdistance >= radius2) {
4929

4930
                ActDist = intercenterdistance - radius1 - radius2;
4931
            }
4932
            else {
4933
                double bigradius = std::max(radius1, radius2);
4934
                double smallradius = std::min(radius1, radius2);
4935

4936
                ActDist = bigradius - smallradius - intercenterdistance;
4937
            }
4938

4939
            openCommand(
4940
                QT_TRANSLATE_NOOP("Command", "Add circle to circle distance constraint"));
4941
            Gui::cmdAppObjectArgs(Obj,
4942
                "addConstraint(Sketcher.Constraint('Distance',%d,%d,%f))",
4943
                GeoId1,
4944
                GeoId2,
4945
                ActDist);
4946

4947
            if (arebothpointsorsegmentsfixed
4948
                || constraintCreationMode
4949
                == Reference) {// it is a constraint on a external line, make it non-driving
4950
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
4951

4952
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
4953
                finishDatumConstraint(this, Obj, false);
4954
            }
4955
            else {
4956
                finishDatumConstraint(this, Obj, true);
4957
            }
4958

4959
            return;
4960
        }
4961
        else {
4962
            Gui::TranslatedUserWarning(
4963
                Obj,
4964
                QObject::tr("Wrong selection"),
4965
                QObject::tr("Select exactly one line or one point and one line or two points "
4966
                    "or two circles from the sketch."));
4967
        }
4968
    }
4969
    default:
4970
        break;
4971
    }
4972
}
4973

4974
void CmdSketcherConstrainDistance::updateAction(int mode)
4975
{
4976
    switch (mode) {
4977
    case Reference:
4978
        if (getAction()) {
4979
            getAction()->setIcon(
4980
                Gui::BitmapFactory().iconFromTheme("Constraint_Length_Driven"));
4981
        }
4982
        break;
4983
    case Driving:
4984
        if (getAction()) {
4985
            getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Length"));
4986
        }
4987
        break;
4988
    }
4989
}
4990

4991
// ======================================================================================
4992

4993
class CmdSketcherConstrainDistanceX: public CmdSketcherConstraint
4994
{
4995
public:
4996
    CmdSketcherConstrainDistanceX();
4997
    ~CmdSketcherConstrainDistanceX() override
4998
    {}
4999
    void updateAction(int mode) override;
5000
    const char* className() const override
5001
    {
5002
        return "CmdSketcherConstrainDistanceX";
5003
    }
5004

5005
protected:
5006
    void activated(int iMsg) override;
5007
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
5008
};
5009

5010
CmdSketcherConstrainDistanceX::CmdSketcherConstrainDistanceX()
5011
    : CmdSketcherConstraint("Sketcher_ConstrainDistanceX")
5012
{
5013
    sAppModule = "Sketcher";
5014
    sGroup = "Sketcher";
5015
    sMenuText = QT_TR_NOOP("Constrain horizontal distance");
5016
    sToolTipText = QT_TR_NOOP("Fix the horizontal distance "
5017
                              "between two points or line ends");
5018
    sWhatsThis = "Sketcher_ConstrainDistanceX";
5019
    sStatusTip = sToolTipText;
5020
    sPixmap = "Constraint_HorizontalDistance";
5021
    sAccel = "L";
5022
    eType = ForEdit;
5023

5024
    // Can't do single vertex because its a prefix for 2 vertices
5025
    allowedSelSequences = {{SelVertex, SelVertexOrRoot},
5026
                           {SelRoot, SelVertex},
5027
                           {SelEdge},
5028
                           {SelExternalEdge}};
5029
}
5030

5031
void CmdSketcherConstrainDistanceX::activated(int iMsg)
5032
{
5033
    Q_UNUSED(iMsg);
5034
    // get the selection
5035
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
5036

5037
    // only one sketch with its subelements are allowed to be selected
5038
    if (selection.size() != 1
5039
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
5040
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
5041
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
5042
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
5043

5044
        if (constraintMode) {
5045
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
5046
            getSelection().clearSelection();
5047
        }
5048
        else {
5049
            // TODO: Get the exact message from git history and put it here
5050
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
5051
                                       QObject::tr("Wrong selection"),
5052
                                       QObject::tr("Select the right things from the sketch."));
5053
        }
5054
        return;
5055
    }
5056

5057
    // get the needed lists and objects
5058
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
5059
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
5060

5061
    if (SubNames.empty() || SubNames.size() > 2) {
5062
        Gui::TranslatedUserWarning(
5063
            Obj,
5064
            QObject::tr("Wrong selection"),
5065
            QObject::tr("Select exactly one line or up to two points from the sketch."));
5066
        return;
5067
    }
5068

5069
    int GeoId1, GeoId2 = GeoEnum::GeoUndef;
5070
    Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::none;
5071
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
5072
    if (SubNames.size() == 2) {
5073
        getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
5074
    }
5075

5076
    bool arebothpointsorsegmentsfixed = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
5077

5078
    if (GeoId2 == Sketcher::GeoEnum::HAxis || GeoId2 == Sketcher::GeoEnum::VAxis) {
5079
        std::swap(GeoId1, GeoId2);
5080
        std::swap(PosId1, PosId2);
5081
    }
5082

5083
    if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::PointPos::none) {
5084
        // reject horizontal axis from selection
5085
        GeoId1 = GeoEnum::GeoUndef;
5086
    }
5087
    else if (GeoId1 == Sketcher::GeoEnum::VAxis && PosId1 == Sketcher::PointPos::none) {
5088
        GeoId1 = Sketcher::GeoEnum::HAxis;
5089
        PosId1 = Sketcher::PointPos::start;
5090
    }
5091

5092
    if (isEdge(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
5093
        // horizontal length of a line
5094
        if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
5095
            Gui::TranslatedUserWarning(
5096
                Obj,
5097
                QObject::tr("Wrong selection"),
5098
                QObject::tr("Cannot add a horizontal length constraint on an axis!"));
5099
            return;
5100
        }
5101

5102
        arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
5103

5104
        const Part::Geometry* geom = Obj->getGeometry(GeoId1);
5105

5106
        if (isLineSegment(*geom)) {
5107
            // convert to as if two endpoints of the line have been selected
5108
            PosId1 = Sketcher::PointPos::start;
5109
            GeoId2 = GeoId1;
5110
            PosId2 = Sketcher::PointPos::end;
5111
        }
5112
    }
5113
    if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {
5114
        // point to point horizontal distance
5115
        Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
5116
        Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
5117
        double ActLength = pnt2.x - pnt1.x;
5118

5119
        // negative sign avoidance: swap the points to make value positive
5120
        if (ActLength < -Precision::Confusion()) {
5121
            std::swap(GeoId1, GeoId2);
5122
            std::swap(PosId1, PosId2);
5123
            std::swap(pnt1, pnt2);
5124
            ActLength = -ActLength;
5125
        }
5126

5127
        openCommand(
5128
            QT_TRANSLATE_NOOP("Command", "Add point to point horizontal distance constraint"));
5129
        Gui::cmdAppObjectArgs(selection[0].getObject(),
5130
                              "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
5131
                              GeoId1,
5132
                              static_cast<int>(PosId1),
5133
                              GeoId2,
5134
                              static_cast<int>(PosId2),
5135
                              ActLength);
5136

5137
        if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
5138
            // it is a constraint on a external line, make it non-driving
5139
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5140

5141
            Gui::cmdAppObjectArgs(selection[0].getObject(),
5142
                                  "setDriving(%d,%s)",
5143
                                  ConStr.size() - 1,
5144
                                  "False");
5145
            finishDatumConstraint(this, Obj, false);
5146
        }
5147
        else {
5148
            finishDatumConstraint(this, Obj, true);
5149
        }
5150

5151
        return;
5152
    }
5153
    else if (isVertex(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
5154
        // point on fixed x-coordinate
5155

5156
        if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
5157
            Gui::TranslatedUserWarning(
5158
                Obj,
5159
                QObject::tr("Wrong selection"),
5160
                QObject::tr("Cannot add a fixed x-coordinate constraint on the origin point!"));
5161
            return;
5162
        }
5163

5164
        Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
5165
        double ActX = pnt.x;
5166

5167
        arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
5168

5169
        openCommand(QT_TRANSLATE_NOOP("Command", "Add fixed x-coordinate constraint"));
5170
        Gui::cmdAppObjectArgs(selection[0].getObject(),
5171
                              "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%f))",
5172
                              GeoId1,
5173
                              static_cast<int>(PosId1),
5174
                              ActX);
5175

5176
        if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
5177
            // it is a constraint on a external line, make it non-driving
5178
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5179

5180
            Gui::cmdAppObjectArgs(selection[0].getObject(),
5181
                                  "setDriving(%d,%s)",
5182
                                  ConStr.size() - 1,
5183
                                  "False");
5184
            finishDatumConstraint(this, Obj, false);
5185
        }
5186
        else {
5187
            finishDatumConstraint(this, Obj, true);
5188
        }
5189

5190
        return;
5191
    }
5192

5193
    Gui::TranslatedUserWarning(
5194
        Obj,
5195
        QObject::tr("Wrong selection"),
5196
        QObject::tr("Select exactly one line or up to two points from the sketch."));
5197
    return;
5198
}
5199

5200
void CmdSketcherConstrainDistanceX::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
5201
{
5202
    SketcherGui::ViewProviderSketch* sketchgui =
5203
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
5204
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
5205

5206
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
5207
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none;
5208

5209
    switch (seqIndex) {
5210
        case 0:// {SelVertex, SelVertexOrRoot}
5211
        case 1:// {SelRoot, SelVertex}
5212
        {
5213
            GeoId1 = selSeq.at(0).GeoId;
5214
            GeoId2 = selSeq.at(1).GeoId;
5215
            PosId1 = selSeq.at(0).PosId;
5216
            PosId2 = selSeq.at(1).PosId;
5217
            break;
5218
        }
5219
        case 2:// {SelEdge}
5220
        case 3:// {SelExternalEdge}
5221
        {
5222
            GeoId1 = GeoId2 = selSeq.at(0).GeoId;
5223
            PosId1 = Sketcher::PointPos::start;
5224
            PosId2 = Sketcher::PointPos::end;
5225

5226
            const Part::Geometry* geom = Obj->getGeometry(GeoId1);
5227

5228
            if (! isLineSegment(*geom)) {
5229
                Gui::TranslatedUserWarning(
5230
                    Obj,
5231
                    QObject::tr("Wrong selection"),
5232
                    QObject::tr(
5233
                        "This constraint only makes sense on a line segment or a pair of points."));
5234
                return;
5235
            }
5236

5237
            break;
5238
        }
5239
        default:
5240
            break;
5241
    }
5242

5243
    Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
5244
    Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
5245
    double ActLength = pnt2.x - pnt1.x;
5246

5247
    // negative sign avoidance: swap the points to make value positive
5248
    if (ActLength < -Precision::Confusion()) {
5249
        std::swap(GeoId1, GeoId2);
5250
        std::swap(PosId1, PosId2);
5251
        std::swap(pnt1, pnt2);
5252
        ActLength = -ActLength;
5253
    }
5254

5255
    openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point horizontal distance constraint"));
5256
    Gui::cmdAppObjectArgs(Obj,
5257
                          "addConstraint(Sketcher.Constraint('DistanceX',%d,%d,%d,%d,%f))",
5258
                          GeoId1,
5259
                          static_cast<int>(PosId1),
5260
                          GeoId2,
5261
                          static_cast<int>(PosId2),
5262
                          ActLength);
5263

5264
    if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2) || constraintCreationMode == Reference) {
5265
        // it is a constraint on a external line, make it non-driving
5266
        const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5267

5268
        Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
5269
        finishDatumConstraint(this, Obj, false);
5270
    }
5271
    else {
5272
        finishDatumConstraint(this, Obj, true);
5273
    }
5274
}
5275

5276
void CmdSketcherConstrainDistanceX::updateAction(int mode)
5277
{
5278
    switch (mode) {
5279
        case Reference:
5280
            if (getAction()) {
5281
                getAction()->setIcon(
5282
                    Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance_Driven"));
5283
            }
5284
            break;
5285
        case Driving:
5286
            if (getAction()) {
5287
                getAction()->setIcon(
5288
                    Gui::BitmapFactory().iconFromTheme("Constraint_HorizontalDistance"));
5289
            }
5290
            break;
5291
    }
5292
}
5293

5294

5295
// ======================================================================================
5296

5297
class CmdSketcherConstrainDistanceY: public CmdSketcherConstraint
5298
{
5299
public:
5300
    CmdSketcherConstrainDistanceY();
5301
    ~CmdSketcherConstrainDistanceY() override
5302
    {}
5303
    void updateAction(int mode) override;
5304
    const char* className() const override
5305
    {
5306
        return "CmdSketcherConstrainDistanceY";
5307
    }
5308

5309
protected:
5310
    void activated(int iMsg) override;
5311
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
5312
};
5313

5314
CmdSketcherConstrainDistanceY::CmdSketcherConstrainDistanceY()
5315
    : CmdSketcherConstraint("Sketcher_ConstrainDistanceY")
5316
{
5317
    sAppModule = "Sketcher";
5318
    sGroup = "Sketcher";
5319
    sMenuText = QT_TR_NOOP("Constrain vertical distance");
5320
    sToolTipText = QT_TR_NOOP("Fix the vertical distance between two points or line ends");
5321
    sWhatsThis = "Sketcher_ConstrainDistanceY";
5322
    sStatusTip = sToolTipText;
5323
    sPixmap = "Constraint_VerticalDistance";
5324
    sAccel = "I";
5325
    eType = ForEdit;
5326

5327
    // Can't do single vertex because its a prefix for 2 vertices
5328
    allowedSelSequences = {{SelVertex, SelVertexOrRoot},
5329
                           {SelRoot, SelVertex},
5330
                           {SelEdge},
5331
                           {SelExternalEdge}};
5332
}
5333

5334
void CmdSketcherConstrainDistanceY::activated(int iMsg)
5335
{
5336
    Q_UNUSED(iMsg);
5337
    // get the selection
5338
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
5339

5340
    // only one sketch with its subelements are allowed to be selected
5341
    if (selection.size() != 1
5342
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
5343
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
5344
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
5345
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
5346

5347
        if (constraintMode) {
5348
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
5349
            getSelection().clearSelection();
5350
        }
5351
        else {
5352
            // TODO: Get the exact message from git history and put it here
5353
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
5354
                                       QObject::tr("Wrong selection"),
5355
                                       QObject::tr("Select the right things from the sketch."));
5356
        }
5357
        return;
5358
    }
5359

5360
    // get the needed lists and objects
5361
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
5362
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
5363

5364
    if (SubNames.empty() || SubNames.size() > 2) {
5365
        Gui::TranslatedUserWarning(
5366
            Obj,
5367
            QObject::tr("Wrong selection"),
5368
            QObject::tr("Select exactly one line or up to two points from the sketch."));
5369
        return;
5370
    }
5371

5372
    int GeoId1, GeoId2 = GeoEnum::GeoUndef;
5373
    Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::none;
5374
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
5375
    if (SubNames.size() == 2) {
5376
        getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
5377
    }
5378

5379
    bool arebothpointsorsegmentsfixed = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
5380

5381
    if (GeoId2 == Sketcher::GeoEnum::HAxis || GeoId2 == Sketcher::GeoEnum::VAxis) {
5382
        std::swap(GeoId1, GeoId2);
5383
        std::swap(PosId1, PosId2);
5384
    }
5385

5386
    if (GeoId1 == Sketcher::GeoEnum::VAxis
5387
        && PosId1 == Sketcher::PointPos::none) {// reject vertical axis from selection
5388
        GeoId1 = GeoEnum::GeoUndef;
5389
    }
5390
    else if (GeoId1 == Sketcher::GeoEnum::HAxis && PosId1 == Sketcher::PointPos::none) {
5391
        PosId1 = Sketcher::PointPos::start;
5392
    }
5393

5394
    if (isEdge(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {// vertical length of a line
5395
        if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
5396
            Gui::TranslatedUserWarning(
5397
                Obj,
5398
                QObject::tr("Wrong selection"),
5399
                QObject::tr("Cannot add a vertical length constraint on an axis!"));
5400
            return;
5401
        }
5402

5403
        arebothpointsorsegmentsfixed = isPointOrSegmentFixed(Obj, GeoId1);
5404

5405
        const Part::Geometry* geom = Obj->getGeometry(GeoId1);
5406

5407
        if (isLineSegment(*geom)) {
5408
            // convert to as if two endpoints of the line have been selected
5409
            PosId1 = Sketcher::PointPos::start;
5410
            GeoId2 = GeoId1;
5411
            PosId2 = Sketcher::PointPos::end;
5412
        }
5413
    }
5414

5415
    if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {
5416
        // point to point vertical distance
5417
        Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
5418
        Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
5419
        double ActLength = pnt2.y - pnt1.y;
5420

5421
        // negative sign avoidance: swap the points to make value positive
5422
        if (ActLength < -Precision::Confusion()) {
5423
            std::swap(GeoId1, GeoId2);
5424
            std::swap(PosId1, PosId2);
5425
            std::swap(pnt1, pnt2);
5426
            ActLength = -ActLength;
5427
        }
5428

5429
        openCommand(
5430
            QT_TRANSLATE_NOOP("Command", "Add point to point vertical distance constraint"));
5431
        Gui::cmdAppObjectArgs(selection[0].getObject(),
5432
                              "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
5433
                              GeoId1,
5434
                              static_cast<int>(PosId1),
5435
                              GeoId2,
5436
                              static_cast<int>(PosId2),
5437
                              ActLength);
5438

5439
        if (arebothpointsorsegmentsfixed || constraintCreationMode == Reference) {
5440
            // it is a constraint on a external line, make it non-driving
5441
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5442

5443
            Gui::cmdAppObjectArgs(selection[0].getObject(),
5444
                                  "setDriving(%d,%s)",
5445
                                  ConStr.size() - 1,
5446
                                  "False");
5447
            finishDatumConstraint(this, Obj, false);
5448
        }
5449
        else {
5450
            finishDatumConstraint(this, Obj, true);
5451
        }
5452

5453
        return;
5454
    }
5455
    else if (isVertex(GeoId1, PosId1) && GeoId2 == GeoEnum::GeoUndef) {
5456
        // point on fixed y-coordinate
5457
        if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
5458
            Gui::TranslatedUserWarning(
5459
                Obj,
5460
                QObject::tr("Wrong selection"),
5461
                QObject::tr("Cannot add a fixed y-coordinate constraint on the origin point!"));
5462
            return;
5463
        }
5464

5465
        Base::Vector3d pnt = Obj->getPoint(GeoId1, PosId1);
5466
        double ActY = pnt.y;
5467

5468
        openCommand(QT_TRANSLATE_NOOP("Command", "Add fixed y-coordinate constraint"));
5469
        Gui::cmdAppObjectArgs(selection[0].getObject(),
5470
                              "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%f))",
5471
                              GeoId1,
5472
                              static_cast<int>(PosId1),
5473
                              ActY);
5474

5475
        if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
5476
            // it is a constraint on a external line, make it non-driving
5477
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5478

5479
            Gui::cmdAppObjectArgs(selection[0].getObject(),
5480
                                  "setDriving(%d,%s)",
5481
                                  ConStr.size() - 1,
5482
                                  "False");
5483
            finishDatumConstraint(this, Obj, false);
5484
        }
5485
        else {
5486
            finishDatumConstraint(this, Obj, true);
5487
        }
5488

5489
        return;
5490
    }
5491

5492
    Gui::TranslatedUserWarning(
5493
        Obj,
5494
        QObject::tr("Wrong selection"),
5495
        QObject::tr("Select exactly one line or up to two points from the sketch."));
5496
    return;
5497
}
5498

5499
void CmdSketcherConstrainDistanceY::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
5500
{
5501
    SketcherGui::ViewProviderSketch* sketchgui =
5502
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
5503
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
5504

5505
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
5506
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none;
5507

5508
    switch (seqIndex) {
5509
        case 0:// {SelVertex, SelVertexOrRoot}
5510
        case 1:// {SelRoot, SelVertex}
5511
        {
5512
            GeoId1 = selSeq.at(0).GeoId;
5513
            GeoId2 = selSeq.at(1).GeoId;
5514
            PosId1 = selSeq.at(0).PosId;
5515
            PosId2 = selSeq.at(1).PosId;
5516
            break;
5517
        }
5518
        case 2:// {SelEdge}
5519
        case 3:// {SelExternalEdge}
5520
        {
5521
            GeoId1 = GeoId2 = selSeq.at(0).GeoId;
5522
            PosId1 = Sketcher::PointPos::start;
5523
            PosId2 = Sketcher::PointPos::end;
5524

5525
            const Part::Geometry* geom = Obj->getGeometry(GeoId1);
5526

5527
            if (! isLineSegment(*geom)) {
5528
                Gui::TranslatedUserWarning(
5529
                    Obj,
5530
                    QObject::tr("Wrong selection"),
5531
                    QObject::tr(
5532
                        "This constraint only makes sense on a line segment or a pair of points."));
5533
                return;
5534
            }
5535

5536
            break;
5537
        }
5538
        default:
5539
            break;
5540
    }
5541

5542
    Base::Vector3d pnt1 = Obj->getPoint(GeoId1, PosId1);
5543
    Base::Vector3d pnt2 = Obj->getPoint(GeoId2, PosId2);
5544
    double ActLength = pnt2.y - pnt1.y;
5545

5546
    // negative sign avoidance: swap the points to make value positive
5547
    if (ActLength < -Precision::Confusion()) {
5548
        std::swap(GeoId1, GeoId2);
5549
        std::swap(PosId1, PosId2);
5550
        std::swap(pnt1, pnt2);
5551
        ActLength = -ActLength;
5552
    }
5553

5554
    openCommand(QT_TRANSLATE_NOOP("Command", "Add point to point vertical distance constraint"));
5555
    Gui::cmdAppObjectArgs(Obj,
5556
                          "addConstraint(Sketcher.Constraint('DistanceY',%d,%d,%d,%d,%f))",
5557
                          GeoId1,
5558
                          static_cast<int>(PosId1),
5559
                          GeoId2,
5560
                          static_cast<int>(PosId2),
5561
                          ActLength);
5562

5563
    if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)
5564
        || constraintCreationMode
5565
            == Reference) {// it is a constraint on a external line, make it non-driving
5566
        const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
5567

5568
        Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
5569
        finishDatumConstraint(this, Obj, false);
5570
    }
5571
    else {
5572
        finishDatumConstraint(this, Obj, true);
5573
    }
5574
}
5575

5576
void CmdSketcherConstrainDistanceY::updateAction(int mode)
5577
{
5578
    switch (mode) {
5579
        case Reference:
5580
            if (getAction()) {
5581
                getAction()->setIcon(
5582
                    Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance_Driven"));
5583
            }
5584
            break;
5585
        case Driving:
5586
            if (getAction()) {
5587
                getAction()->setIcon(
5588
                    Gui::BitmapFactory().iconFromTheme("Constraint_VerticalDistance"));
5589
            }
5590
            break;
5591
    }
5592
}
5593

5594
//=================================================================================
5595

5596
class CmdSketcherConstrainParallel: public CmdSketcherConstraint
5597
{
5598
public:
5599
    CmdSketcherConstrainParallel();
5600
    ~CmdSketcherConstrainParallel() override
5601
    {}
5602
    const char* className() const override
5603
    {
5604
        return "CmdSketcherConstrainParallel";
5605
    }
5606

5607
protected:
5608
    void activated(int iMsg) override;
5609
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
5610
};
5611

5612
CmdSketcherConstrainParallel::CmdSketcherConstrainParallel()
5613
    : CmdSketcherConstraint("Sketcher_ConstrainParallel")
5614
{
5615
    sAppModule = "Sketcher";
5616
    sGroup = "Sketcher";
5617
    sMenuText = QT_TR_NOOP("Constrain parallel");
5618
    sToolTipText = QT_TR_NOOP("Create a parallel constraint between two lines");
5619
    sWhatsThis = "Sketcher_ConstrainParallel";
5620
    sStatusTip = sToolTipText;
5621
    sPixmap = "Constraint_Parallel";
5622
    sAccel = "P";
5623
    eType = ForEdit;
5624

5625
    // TODO: Also needed: ExternalEdges
5626
    allowedSelSequences = {{SelEdge, SelEdgeOrAxis},
5627
                           {SelEdgeOrAxis, SelEdge},
5628
                           {SelEdge, SelExternalEdge},
5629
                           {SelExternalEdge, SelEdge}};
5630
}
5631

5632
void CmdSketcherConstrainParallel::activated(int iMsg)
5633
{
5634
    Q_UNUSED(iMsg);
5635
    // get the selection
5636
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
5637

5638
    // only one sketch with its subelements are allowed to be selected
5639
    if (selection.size() != 1
5640
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
5641
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
5642
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
5643
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
5644

5645
        if (constraintMode) {
5646
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
5647
            getSelection().clearSelection();
5648
        }
5649
        else {
5650
            // TODO: Get the exact message from git history and put it here
5651
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
5652
                                       QObject::tr("Wrong selection"),
5653
                                       QObject::tr("Select two or more lines from the sketch."));
5654
        }
5655
        return;
5656
    }
5657

5658
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
5659

5660
    // go through the selected subelements
5661
    std::vector<int> ids;
5662
    bool hasAlreadyExternal = false;
5663
    for (auto& subname : selection[0].getSubNames()) {
5664

5665
        int GeoId;
5666
        Sketcher::PointPos PosId;
5667
        getIdsFromName(subname, Obj, GeoId, PosId);
5668

5669
        if (!isEdge(GeoId, PosId)) {
5670
            continue;
5671
        }
5672
        else if (isPointOrSegmentFixed(Obj, GeoId)) {
5673
            if (hasAlreadyExternal) {
5674
                showNoConstraintBetweenFixedGeometry(Obj);
5675
                return;
5676
            }
5677
            else {
5678
                hasAlreadyExternal = true;
5679
            }
5680
        }
5681

5682
        // Check that the curve is a line segment
5683
        const Part::Geometry* geo = Obj->getGeometry(GeoId);
5684

5685
        if (!isLineSegment(*geo)) {
5686
            Gui::TranslatedUserWarning(Obj,
5687
                QObject::tr("Wrong selection"),
5688
                QObject::tr("One selected edge is not a valid line."));
5689
            return;
5690
        }
5691
        ids.push_back(GeoId);
5692
    }
5693

5694
    if (ids.size() < 2) {
5695
        Gui::TranslatedUserWarning(Obj,
5696
            QObject::tr("Wrong selection"),
5697
            QObject::tr("Select at least two lines from the sketch."));
5698
        return;
5699
    }
5700

5701
    // undo command open
5702
    openCommand(QT_TRANSLATE_NOOP("Command", "Add parallel constraint"));
5703
    for (int i = 0; i < int(ids.size() - 1); i++) {
5704
        Gui::cmdAppObjectArgs(selection[0].getObject(),
5705
                              "addConstraint(Sketcher.Constraint('Parallel',%d,%d))",
5706
                              ids[i],
5707
                              ids[i + 1]);
5708
    }
5709
    // finish the transaction and update
5710
    commitCommand();
5711

5712
    tryAutoRecompute(Obj);
5713

5714
    // clear the selection (convenience)
5715
    getSelection().clearSelection();
5716
}
5717

5718
void CmdSketcherConstrainParallel::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
5719
{
5720
    switch (seqIndex) {
5721
        case 0:// {SelEdge, SelEdgeOrAxis}
5722
        case 1:// {SelEdgeOrAxis, SelEdge}
5723
        case 2:// {SelEdge, SelExternalEdge}
5724
        case 3:// {SelExternalEdge, SelEdge}
5725
            // create the constraint
5726
            SketcherGui::ViewProviderSketch* sketchgui =
5727
                static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
5728
            Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
5729

5730
            int GeoId1 = selSeq.at(0).GeoId, GeoId2 = selSeq.at(1).GeoId;
5731

5732
            // Check that the curves are line segments
5733
            if (! isLineSegment(*(Obj->getGeometry(GeoId1))) || ! isLineSegment(*(Obj->getGeometry(GeoId2)))) {
5734
                Gui::TranslatedUserWarning(Obj,
5735
                                           QObject::tr("Wrong selection"),
5736
                                           QObject::tr("The selected edge is not a valid line."));
5737
                return;
5738
            }
5739

5740
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
5741
                showNoConstraintBetweenFixedGeometry(Obj);
5742
                return;
5743
            }
5744

5745
            // undo command open
5746
            openCommand(QT_TRANSLATE_NOOP("Command", "Add parallel constraint"));
5747
            Gui::cmdAppObjectArgs(sketchgui->getObject(),
5748
                                  "addConstraint(Sketcher.Constraint('Parallel',%d,%d))",
5749
                                  GeoId1,
5750
                                  GeoId2);
5751
            // finish the transaction and update
5752
            commitCommand();
5753
            tryAutoRecompute(Obj);
5754
    }
5755
}
5756

5757
// ======================================================================================
5758

5759
class CmdSketcherConstrainPerpendicular: public CmdSketcherConstraint
5760
{
5761
public:
5762
    CmdSketcherConstrainPerpendicular();
5763
    ~CmdSketcherConstrainPerpendicular() override
5764
    {}
5765
    const char* className() const override
5766
    {
5767
        return "CmdSketcherConstrainPerpendicular";
5768
    }
5769

5770
protected:
5771
    void activated(int iMsg) override;
5772
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
5773
};
5774

5775
CmdSketcherConstrainPerpendicular::CmdSketcherConstrainPerpendicular()
5776
    : CmdSketcherConstraint("Sketcher_ConstrainPerpendicular")
5777
{
5778
    sAppModule = "Sketcher";
5779
    sGroup = "Sketcher";
5780
    sMenuText = QT_TR_NOOP("Constrain perpendicular");
5781
    sToolTipText = QT_TR_NOOP("Create a perpendicular constraint between two lines");
5782
    sWhatsThis = "Sketcher_ConstrainPerpendicular";
5783
    sStatusTip = sToolTipText;
5784
    sPixmap = "Constraint_Perpendicular";
5785
    sAccel = "N";
5786
    eType = ForEdit;
5787

5788
    // TODO: there are two more combos: endpoint then curve and endpoint then endpoint
5789
    allowedSelSequences = {{SelEdge, SelEdgeOrAxis},
5790
                           {SelEdgeOrAxis, SelEdge},
5791
                           {SelEdge, SelExternalEdge},
5792
                           {SelExternalEdge, SelEdge},
5793
                           {SelVertexOrRoot, SelEdge, SelEdgeOrAxis},
5794
                           {SelVertexOrRoot, SelEdgeOrAxis, SelEdge},
5795
                           {SelVertexOrRoot, SelEdge, SelExternalEdge},
5796
                           {SelVertexOrRoot, SelExternalEdge, SelEdge},
5797
                           {SelEdge, SelVertexOrRoot, SelEdgeOrAxis},
5798
                           {SelEdgeOrAxis, SelVertexOrRoot, SelEdge},
5799
                           {SelEdge, SelVertexOrRoot, SelExternalEdge},
5800
                           {SelExternalEdge, SelVertexOrRoot, SelEdge}};
5801
    ;
5802
}
5803

5804
void CmdSketcherConstrainPerpendicular::activated(int iMsg)
5805
{
5806
    Q_UNUSED(iMsg);
5807

5808
    // get the selection
5809
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
5810

5811
    // only one sketch with its subelements are allowed to be selected
5812
    if (selection.size() != 1
5813
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
5814
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
5815
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
5816
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
5817

5818
        if (constraintMode) {
5819
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
5820
            getSelection().clearSelection();
5821
        }
5822
        else {
5823
            // TODO: Get the exact message from git history and put it here
5824
            QString strBasicHelp =
5825
                QObject::tr("There is a number of ways this constraint can be applied.\n\n"
5826
                            "Accepted combinations: two curves; an endpoint and a curve; two "
5827
                            "endpoints; two curves and a point.",
5828
                            /*disambig.:*/ "perpendicular constraint");
5829
            QString strError =
5830
                QObject::tr("Select some geometry from the sketch.", "perpendicular constraint");
5831
            strError.append(QString::fromLatin1("\n\n"));
5832
            strError.append(strBasicHelp);
5833
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
5834
                                       QObject::tr("Wrong selection"),
5835
                                       std::move(strError));
5836
        }
5837
        return;
5838
    }
5839

5840
    // get the needed lists and objects
5841
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
5842
    auto* Obj = dynamic_cast<Sketcher::SketchObject*>(selection[0].getObject());
5843

5844
    if (!Obj || (SubNames.size() != 2 && SubNames.size() != 3)) {
5845
        Gui::TranslatedUserWarning(Obj,
5846
                                   QObject::tr("Wrong selection"),
5847
                                   QObject::tr("Wrong number of selected objects!"));
5848
        return;
5849
    }
5850

5851
    int GeoId1, GeoId2, GeoId3;
5852
    Sketcher::PointPos PosId1, PosId2, PosId3;
5853
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
5854
    getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
5855

5856
    if (areBothPointsOrSegmentsFixed(Obj,
5857
                                     GeoId1,
5858
                                     GeoId2)) {// checkBothExternal displays error message
5859
        showNoConstraintBetweenFixedGeometry(Obj);
5860
        return;
5861
    }
5862

5863
    if (SubNames.size() == 3) {// perpendicular via point
5864
        getIdsFromName(SubNames[2], Obj, GeoId3, PosId3);
5865
        // let's sink the point to be GeoId3. We want to keep the order the two curves have been
5866
        // selected in.
5867
        if (isVertex(GeoId1, PosId1)) {
5868
            std::swap(GeoId1, GeoId2);
5869
            std::swap(PosId1, PosId2);
5870
        }
5871
        if (isVertex(GeoId2, PosId2)) {
5872
            std::swap(GeoId2, GeoId3);
5873
            std::swap(PosId2, PosId3);
5874
        }
5875

5876
        if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
5877

5878
            if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
5879
                Gui::TranslatedUserWarning(
5880
                    Obj,
5881
                    QObject::tr("Wrong selection"),
5882
                    QObject::tr("Select an edge that is not a B-spline weight."));
5883
                return;
5884
            }
5885

5886
            openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
5887

5888
            bool safe = addConstraintSafely(Obj, [&]() {
5889
                // add missing point-on-object constraints
5890
                if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
5891
                    const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
5892
                    if (!(geom1 && isBSplineCurve(*geom1))) {
5893
                        Gui::cmdAppObjectArgs(
5894
                            selection[0].getObject(),
5895
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
5896
                            GeoId3,
5897
                            static_cast<int>(PosId3),
5898
                            GeoId1);
5899
                    }
5900
                }
5901

5902
                if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
5903
                    const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
5904
                    if (!(geom2 && isBSplineCurve(*geom2))) {
5905
                        Gui::cmdAppObjectArgs(
5906
                            selection[0].getObject(),
5907
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
5908
                            GeoId3,
5909
                            static_cast<int>(PosId3),
5910
                            GeoId2);
5911
                    }
5912
                }
5913

5914
                if (!IsPointAlreadyOnCurve(
5915
                        GeoId1,
5916
                        GeoId3,
5917
                        PosId3,
5918
                        Obj)) {
5919
                    // FIXME: it's a good idea to add a check if the sketch is solved
5920
                    const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
5921
                    if (!(geom1 && isBSplineCurve(*geom1))) {
5922
                        Gui::cmdAppObjectArgs(
5923
                            selection[0].getObject(),
5924
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
5925
                            GeoId3,
5926
                            static_cast<int>(PosId3),
5927
                            GeoId1);
5928
                    }
5929
                }
5930

5931
                Gui::cmdAppObjectArgs(
5932
                    selection[0].getObject(),
5933
                    "addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
5934
                    GeoId1,
5935
                    GeoId2,
5936
                    GeoId3,
5937
                    static_cast<int>(PosId3));
5938

5939
                removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
5940
            });
5941

5942
            if (!safe) {
5943
                return;
5944
            }
5945
            else {
5946
                commitCommand();
5947
                tryAutoRecompute(Obj);
5948
            }
5949

5950
            getSelection().clearSelection();
5951

5952
            return;
5953
        }
5954

5955
        Gui::TranslatedUserWarning(
5956
            Obj,
5957
            QObject::tr("Wrong selection"),
5958
            QObject::tr("With 3 objects, there must be 2 curves and 1 point."));
5959
    }
5960
    else if (SubNames.size() == 2) {
5961
        if (isVertex(GeoId1, PosId1)
5962
            && isVertex(GeoId2, PosId2)) {// endpoint-to-endpoint perpendicularity
5963

5964
            if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) {
5965
                Gui::TranslatedUserWarning(
5966
                    Obj,
5967
                    QObject::tr("Wrong selection"),
5968
                    QObject::tr(
5969
                        "Cannot add a perpendicularity constraint at an unconnected point!"));
5970
                return;
5971
            }
5972

5973
            // This code supports simple B-spline endpoint perp to any other geometric curve
5974
            const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
5975
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
5976

5977
            if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
5978
                if (! isBSplineCurve(*geom1)) {
5979
                    std::swap(GeoId1, GeoId2);
5980
                    std::swap(PosId1, PosId2);
5981
                }
5982
                // GeoId1 is the B-spline now
5983
            }// end of code supports simple B-spline endpoint tangency
5984

5985
            openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
5986
            Gui::cmdAppObjectArgs(selection[0].getObject(),
5987
                                  "addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d,%d))",
5988
                                  GeoId1,
5989
                                  static_cast<int>(PosId1),
5990
                                  GeoId2,
5991
                                  static_cast<int>(PosId2));
5992
            commitCommand();
5993
            tryAutoRecompute(Obj);
5994

5995
            getSelection().clearSelection();
5996
            return;
5997
        }
5998
        else if ((isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2))
5999
                 || (isEdge(GeoId1, PosId1) && isVertex(GeoId2, PosId2))) {// endpoint-to-curve
6000
            if (isVertex(GeoId2, PosId2)) {
6001
                std::swap(GeoId1, GeoId2);
6002
                std::swap(PosId1, PosId2);
6003
            }
6004

6005
            if (isSimpleVertex(Obj, GeoId1, PosId1)) {
6006
                Gui::TranslatedUserWarning(
6007
                    Obj,
6008
                    QObject::tr("Wrong selection"),
6009
                    QObject::tr(
6010
                        "Cannot add a perpendicularity constraint at an unconnected point!"));
6011
                return;
6012
            }
6013

6014
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
6015

6016
            if (isBsplinePole(geom2)) {
6017
                Gui::TranslatedUserWarning(
6018
                    Obj,
6019
                    QObject::tr("Wrong selection"),
6020
                    QObject::tr("Select an edge that is not a B-spline weight."));
6021
                return;
6022
            }
6023

6024
            openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicularity constraint"));
6025
            Gui::cmdAppObjectArgs(selection[0].getObject(),
6026
                                  "addConstraint(Sketcher.Constraint('Perpendicular',%d,%d,%d))",
6027
                                  GeoId1,
6028
                                  static_cast<int>(PosId1),
6029
                                  GeoId2);
6030
            commitCommand();
6031
            tryAutoRecompute(Obj);
6032

6033
            getSelection().clearSelection();
6034
            return;
6035
        }
6036
        else if (isEdge(GeoId1, PosId1)
6037
                 && isEdge(GeoId2, PosId2)) {// simple perpendicularity between GeoId1 and GeoId2
6038

6039
            const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
6040
            const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
6041
            if (!geo1 || !geo2) {
6042
                return;
6043
            }
6044

6045
            if (! isLineSegment(*geo1) && ! isLineSegment(*geo2)) {
6046
                Gui::TranslatedUserWarning(
6047
                    Obj,
6048
                    QObject::tr("Wrong selection"),
6049
                    QObject::tr("One of the selected edges should be a line."));
6050
                return;
6051
            }
6052

6053
            // if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) {
6054
            //     // unsupported until tangent to B-spline at any point implemented.
6055
            //     Gui::TranslatedUserWarning(
6056
            //         Obj,
6057
            //         QObject::tr("Wrong selection"),
6058
            //         QObject::tr("Perpendicular to B-spline edge currently unsupported."));
6059
            //     return;
6060
            // }
6061

6062
            if (isLineSegment(*geo1)) {
6063
                std::swap(GeoId1, GeoId2);
6064
            }
6065

6066
            if (isBsplinePole(Obj, GeoId1)) {
6067
                Gui::TranslatedUserWarning(
6068
                    Obj,
6069
                    QObject::tr("Wrong selection"),
6070
                    QObject::tr("Select an edge that is not a B-spline weight."));
6071
                return;
6072
            }
6073

6074
            // GeoId2 is the line
6075
            geo1 = Obj->getGeometry(GeoId1);
6076
            geo2 = Obj->getGeometry(GeoId2);
6077

6078
            if (isEllipse(*geo1) || isArcOfEllipse(*geo1) || isArcOfHyperbola(*geo1) || isArcOfParabola(*geo1)) {
6079
                Base::Vector3d center;
6080
                Base::Vector3d majdir;
6081
                Base::Vector3d focus;
6082
                double majord = 0;
6083
                double minord = 0;
6084
                double phi = 0;
6085

6086
                if (isEllipse(*geo1)) {
6087
                    auto ellipse = static_cast<const Part::GeomEllipse*>(geo1);
6088
                    center = ellipse->getCenter();
6089
                    majord = ellipse->getMajorRadius();
6090
                    minord = ellipse->getMinorRadius();
6091
                    majdir = ellipse->getMajorAxisDir();
6092
                    phi = atan2(majdir.y, majdir.x);
6093
                }
6094
                else if (isArcOfEllipse(*geo1)) {
6095
                    auto aoe = static_cast<const Part::GeomArcOfEllipse*>(geo1);
6096
                    center = aoe->getCenter();
6097
                    majord = aoe->getMajorRadius();
6098
                    minord = aoe->getMinorRadius();
6099
                    majdir = aoe->getMajorAxisDir();
6100
                    phi = atan2(majdir.y, majdir.x);
6101
                }
6102
                else if (isArcOfHyperbola(*geo1)) {
6103
                    auto aoh = static_cast<const Part::GeomArcOfHyperbola*>(geo1);
6104
                    center = aoh->getCenter();
6105
                    majord = aoh->getMajorRadius();
6106
                    minord = aoh->getMinorRadius();
6107
                    majdir = aoh->getMajorAxisDir();
6108
                    phi = atan2(majdir.y, majdir.x);
6109
                }
6110
                else if (isArcOfParabola(*geo1)) {
6111
                    auto aop = static_cast<const Part::GeomArcOfParabola*>(geo1);
6112
                    center = aop->getCenter();
6113
                    focus = aop->getFocus();
6114
                }
6115

6116
                const Part::GeomLineSegment* line = static_cast<const Part::GeomLineSegment*>(geo2);
6117

6118
                Base::Vector3d point1 = line->getStartPoint();
6119
                Base::Vector3d PoO;
6120

6121
                if (isArcOfHyperbola(*geo1)) {
6122
                    double df = sqrt(majord * majord + minord * minord);
6123
                    Base::Vector3d direction = point1 - (center + majdir * df);// towards the focus
6124
                    double tapprox = atan2(direction.y, direction.x) - phi;
6125

6126
                    PoO = Base::Vector3d(center.x + majord * cosh(tapprox) * cos(phi)
6127
                                             - minord * sinh(tapprox) * sin(phi),
6128
                                         center.y + majord * cosh(tapprox) * sin(phi)
6129
                                             + minord * sinh(tapprox) * cos(phi),
6130
                                         0);
6131
                }
6132
                else if (isArcOfParabola(*geo1)) {
6133
                    Base::Vector3d direction = point1 - focus;// towards the focus
6134

6135
                    PoO = point1 + direction / 2;
6136
                }
6137
                else {
6138
                    Base::Vector3d direction = point1 - center;
6139
                    double tapprox = atan2(direction.y, direction.x)
6140
                        - phi;// we approximate the eccentric anomaly by the polar
6141

6142
                    PoO = Base::Vector3d(center.x + majord * cos(tapprox) * cos(phi)
6143
                                             - minord * sin(tapprox) * sin(phi),
6144
                                         center.y + majord * cos(tapprox) * sin(phi)
6145
                                             + minord * sin(tapprox) * cos(phi),
6146
                                         0);
6147
                }
6148
                openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
6149

6150
                try {
6151
                    // Add a point
6152
                    Gui::cmdAppObjectArgs(Obj,
6153
                                          "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)",
6154
                                          PoO.x,
6155
                                          PoO.y);
6156
                    int GeoIdPoint = Obj->getHighestCurveIndex();
6157

6158
                    // Point on first object (ellipse, arc of ellipse)
6159
                    Gui::cmdAppObjectArgs(
6160
                        selection[0].getObject(),
6161
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6162
                        GeoIdPoint,
6163
                        static_cast<int>(Sketcher::PointPos::start),
6164
                        GeoId1);
6165
                    // Point on second object
6166
                    Gui::cmdAppObjectArgs(
6167
                        selection[0].getObject(),
6168
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6169
                        GeoIdPoint,
6170
                        static_cast<int>(Sketcher::PointPos::start),
6171
                        GeoId2);
6172
                    // add constraint: Perpendicular-via-point
6173
                    Gui::cmdAppObjectArgs(
6174
                        Obj,
6175
                        "addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
6176
                        GeoId1,
6177
                        GeoId2,
6178
                        GeoIdPoint,
6179
                        static_cast<int>(Sketcher::PointPos::start));
6180
                }
6181
                catch (const Base::Exception& e) {
6182
                    Gui::NotifyUserError(Obj,
6183
                                         QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
6184
                                         e.what());
6185
                    Gui::Command::abortCommand();
6186

6187
                    tryAutoRecompute(Obj);
6188
                    return;
6189
                }
6190

6191
                commitCommand();
6192
                tryAutoRecompute(Obj);
6193

6194
                getSelection().clearSelection();
6195
                return;
6196
            }
6197

6198
            openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
6199
            Gui::cmdAppObjectArgs(selection[0].getObject(),
6200
                                  "addConstraint(Sketcher.Constraint('Perpendicular',%d,%d))",
6201
                                  GeoId1,
6202
                                  GeoId2);
6203
            commitCommand();
6204
            tryAutoRecompute(Obj);
6205

6206
            getSelection().clearSelection();
6207
            return;
6208
        }
6209
    }
6210

6211
    return;
6212
}
6213

6214
void CmdSketcherConstrainPerpendicular::applyConstraint(std::vector<SelIdPair>& selSeq,
6215
                                                        int seqIndex)
6216
{
6217
    SketcherGui::ViewProviderSketch* sketchgui =
6218
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
6219
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
6220

6221
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
6222
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
6223
                       PosId3 = Sketcher::PointPos::none;
6224

6225
    switch (seqIndex) {
6226
        case 0:// {SelEdge, SelEdgeOrAxis}
6227
        case 1:// {SelEdgeOrAxis, SelEdge}
6228
        case 2:// {SelEdge, SelExternalEdge}
6229
        case 3:// {SelExternalEdge, SelEdge}
6230
        {
6231
            GeoId1 = selSeq.at(0).GeoId;
6232
            GeoId2 = selSeq.at(1).GeoId;
6233

6234
            // check if the edge already has a Block constraint
6235
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
6236
                showNoConstraintBetweenFixedGeometry(Obj);
6237
                return;
6238
            }
6239

6240
            const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
6241
            const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
6242
            if (!geo1 || !geo2) {
6243
                return;
6244
            }
6245

6246
            if (! isLineSegment(*geo1) && ! isLineSegment(*geo2)) {
6247
                Gui::TranslatedUserWarning(
6248
                    Obj,
6249
                    QObject::tr("Wrong selection"),
6250
                    QObject::tr("One of the selected edges should be a line."));
6251
                return;
6252
            }
6253

6254
            // if (isBSplineCurve(*geo1) || isBSplineCurve(*geo2)) {
6255
            //     // unsupported until tangent to B-spline at any point implemented.
6256
            //     Gui::TranslatedUserWarning(
6257
            //         Obj,
6258
            //         QObject::tr("Wrong selection"),
6259
            //         QObject::tr("Perpendicular to B-spline edge currently unsupported."));
6260
            //     return;
6261
            // }
6262

6263
            if (isLineSegment(*geo1)) {
6264
                std::swap(GeoId1, GeoId2);
6265
            }
6266

6267
            if (isBsplinePole(Obj, GeoId1)) {
6268
                Gui::TranslatedUserWarning(
6269
                    Obj,
6270
                    QObject::tr("Wrong selection"),
6271
                    QObject::tr("Select an edge that is not a B-spline weight."));
6272
                return;
6273
            }
6274

6275
            // GeoId2 is the line
6276
            geo1 = Obj->getGeometry(GeoId1);
6277
            geo2 = Obj->getGeometry(GeoId2);
6278

6279
            if (isEllipse(*geo1) || isArcOfEllipse(*geo1) || isArcOfHyperbola(*geo1) || isArcOfParabola(*geo1)) {
6280
                Base::Vector3d center;
6281
                Base::Vector3d majdir;
6282
                Base::Vector3d focus;
6283
                double majord = 0;
6284
                double minord = 0;
6285
                double phi = 0;
6286

6287
                if (isEllipse(*geo1)) {
6288
                    auto ellipse = static_cast<const Part::GeomEllipse*>(geo1);
6289
                    center = ellipse->getCenter();
6290
                    majord = ellipse->getMajorRadius();
6291
                    minord = ellipse->getMinorRadius();
6292
                    majdir = ellipse->getMajorAxisDir();
6293
                    phi = atan2(majdir.y, majdir.x);
6294
                }
6295
                else if (isArcOfEllipse(*geo1)) {
6296
                    auto aoe = static_cast<const Part::GeomArcOfEllipse*>(geo1);
6297
                    center = aoe->getCenter();
6298
                    majord = aoe->getMajorRadius();
6299
                    minord = aoe->getMinorRadius();
6300
                    majdir = aoe->getMajorAxisDir();
6301
                    phi = atan2(majdir.y, majdir.x);
6302
                }
6303
                else if (isArcOfHyperbola(*geo1)) {
6304
                    auto aoh = static_cast<const Part::GeomArcOfHyperbola*>(geo1);
6305
                    center = aoh->getCenter();
6306
                    majord = aoh->getMajorRadius();
6307
                    minord = aoh->getMinorRadius();
6308
                    majdir = aoh->getMajorAxisDir();
6309
                    phi = atan2(majdir.y, majdir.x);
6310
                }
6311
                else if (isArcOfParabola(*geo1)) {
6312
                    auto aop = static_cast<const Part::GeomArcOfParabola*>(geo1);
6313
                    center = aop->getCenter();
6314
                    focus = aop->getFocus();
6315
                }
6316

6317
                const Part::GeomLineSegment* line = static_cast<const Part::GeomLineSegment*>(geo2);
6318

6319
                Base::Vector3d point1 = line->getStartPoint();
6320
                Base::Vector3d PoO;
6321

6322
                if (isArcOfHyperbola(*geo1)) {
6323
                    double df = sqrt(majord * majord + minord * minord);
6324
                    Base::Vector3d direction = point1 - (center + majdir * df);// towards the focus
6325
                    double tapprox = atan2(direction.y, direction.x) - phi;
6326

6327
                    PoO = Base::Vector3d(center.x + majord * cosh(tapprox) * cos(phi)
6328
                                             - minord * sinh(tapprox) * sin(phi),
6329
                                         center.y + majord * cosh(tapprox) * sin(phi)
6330
                                             + minord * sinh(tapprox) * cos(phi),
6331
                                         0);
6332
                }
6333
                else if (isArcOfParabola(*geo1)) {
6334
                    Base::Vector3d direction = point1 - focus;// towards the focus
6335

6336
                    PoO = point1 + direction / 2;
6337
                }
6338
                else {
6339
                    Base::Vector3d direction = point1 - center;
6340
                    double tapprox = atan2(direction.y, direction.x)
6341
                        - phi;// we approximate the eccentric anomaly by the polar
6342

6343
                    PoO = Base::Vector3d(center.x + majord * cos(tapprox) * cos(phi)
6344
                                             - minord * sin(tapprox) * sin(phi),
6345
                                         center.y + majord * cos(tapprox) * sin(phi)
6346
                                             + minord * sin(tapprox) * cos(phi),
6347
                                         0);
6348
                }
6349
                openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
6350

6351
                try {
6352
                    // Add a point
6353
                    Gui::cmdAppObjectArgs(Obj,
6354
                                          "addGeometry(Part.Point(App.Vector(%f,%f,0)), True)",
6355
                                          PoO.x,
6356
                                          PoO.y);
6357
                    int GeoIdPoint = Obj->getHighestCurveIndex();
6358

6359
                    // Point on first object (ellipse, arc of ellipse)
6360
                    Gui::cmdAppObjectArgs(
6361
                        Obj,
6362
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6363
                        GeoIdPoint,
6364
                        static_cast<int>(Sketcher::PointPos::start),
6365
                        GeoId1);
6366
                    // Point on second object
6367
                    Gui::cmdAppObjectArgs(
6368
                        Obj,
6369
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6370
                        GeoIdPoint,
6371
                        static_cast<int>(Sketcher::PointPos::start),
6372
                        GeoId2);
6373

6374
                    // add constraint: Perpendicular-via-point
6375
                    Gui::cmdAppObjectArgs(
6376
                        Obj,
6377
                        "addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
6378
                        GeoId1,
6379
                        GeoId2,
6380
                        GeoIdPoint,
6381
                        static_cast<int>(Sketcher::PointPos::start));
6382

6383
                    commitCommand();
6384
                }
6385
                catch (const Base::Exception& e) {
6386
                    Gui::NotifyUserError(Obj,
6387
                                         QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"),
6388
                                         e.what());
6389
                    Gui::Command::abortCommand();
6390
                }
6391

6392
                tryAutoRecompute(Obj);
6393

6394
                getSelection().clearSelection();
6395
                return;
6396
            }
6397

6398
            openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
6399
            Gui::cmdAppObjectArgs(Obj,
6400
                                  "addConstraint(Sketcher.Constraint('Perpendicular',%d,%d))",
6401
                                  GeoId1,
6402
                                  GeoId2);
6403
            commitCommand();
6404

6405
            tryAutoRecompute(Obj);
6406
            return;
6407
        }
6408
        case 4:// {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}
6409
        case 5:// {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}
6410
        case 6:// {SelVertexOrRoot, SelEdge, SelExternalEdge}
6411
        case 7:// {SelVertexOrRoot, SelExternalEdge, SelEdge}
6412
        {
6413
            // let's sink the point to be GeoId3.
6414
            GeoId1 = selSeq.at(1).GeoId;
6415
            GeoId2 = selSeq.at(2).GeoId;
6416
            GeoId3 = selSeq.at(0).GeoId;
6417
            PosId3 = selSeq.at(0).PosId;
6418

6419
            break;
6420
        }
6421
        case 8: // {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}
6422
        case 9: // {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}
6423
        case 10:// {SelEdge, SelVertexOrRoot, SelExternalEdge}
6424
        case 11:// {SelExternalEdge, SelVertexOrRoot, SelEdge}
6425
        {
6426
            // let's sink the point to be GeoId3.
6427
            GeoId1 = selSeq.at(0).GeoId;
6428
            GeoId2 = selSeq.at(2).GeoId;
6429
            GeoId3 = selSeq.at(1).GeoId;
6430
            PosId3 = selSeq.at(1).PosId;
6431

6432
            break;
6433
        }
6434
        default:
6435
            return;
6436
    }
6437

6438
    if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
6439

6440
        // check if the edge already has a Block constraint
6441
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
6442
            showNoConstraintBetweenFixedGeometry(Obj);
6443
            return;
6444
        }
6445

6446
        if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
6447
            Gui::TranslatedUserWarning(
6448
                Obj,
6449
                QObject::tr("Wrong selection"),
6450
                QObject::tr("Select an edge that is not a B-spline weight."));
6451
            return;
6452
        }
6453

6454
        openCommand(QT_TRANSLATE_NOOP("Command", "Add perpendicular constraint"));
6455

6456
        bool safe = addConstraintSafely(Obj, [&]() {
6457
            // add missing point-on-object constraints
6458
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
6459
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
6460
                if (!(geom1 && isBSplineCurve(*geom1))) {
6461
                    Gui::cmdAppObjectArgs(
6462
                        Obj,
6463
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6464
                        GeoId3,
6465
                        static_cast<int>(PosId3),
6466
                        GeoId1);
6467
                }
6468
            }
6469

6470
            if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
6471
                const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
6472
                if (!(geom2 && isBSplineCurve(*geom2))) {
6473
                    Gui::cmdAppObjectArgs(
6474
                        Obj,
6475
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6476
                        GeoId3,
6477
                        static_cast<int>(PosId3),
6478
                        GeoId2);
6479
                }
6480
            }
6481

6482
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
6483
                // FIXME: it's a good idea to add a check if the sketch is solved
6484
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
6485
                if (!(geom1 && isBSplineCurve(*geom1))) {
6486
                    Gui::cmdAppObjectArgs(
6487
                        Obj,
6488
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6489
                        GeoId3,
6490
                        static_cast<int>(PosId3),
6491
                        GeoId1);
6492
                }
6493
            }
6494

6495
            Gui::cmdAppObjectArgs(
6496
                Obj,
6497
                "addConstraint(Sketcher.Constraint('PerpendicularViaPoint',%d,%d,%d,%d))",
6498
                GeoId1,
6499
                GeoId2,
6500
                GeoId3,
6501
                static_cast<int>(PosId3));
6502

6503
            removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
6504
        });
6505

6506
        if (!safe) {
6507
            return;
6508
        }
6509
        else {
6510
            commitCommand();
6511
            tryAutoRecompute(Obj);
6512
        }
6513

6514
        getSelection().clearSelection();
6515

6516
        return;
6517
    }
6518
}
6519

6520
// ======================================================================================
6521

6522
class CmdSketcherConstrainTangent: public CmdSketcherConstraint
6523
{
6524
public:
6525
    CmdSketcherConstrainTangent();
6526
    ~CmdSketcherConstrainTangent() override
6527
    {}
6528
    const char* className() const override
6529
    {
6530
        return "CmdSketcherConstrainTangent";
6531
    }
6532

6533
protected:
6534
    void activated(int iMsg) override;
6535
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
6536
    // returns true if a substitution took place
6537
    static bool substituteConstraintCombinations(SketchObject* Obj, int GeoId1, int GeoId2);
6538
};
6539

6540
CmdSketcherConstrainTangent::CmdSketcherConstrainTangent()
6541
    : CmdSketcherConstraint("Sketcher_ConstrainTangent")
6542
{
6543
    sAppModule = "Sketcher";
6544
    sGroup = "Sketcher";
6545
    sMenuText = QT_TR_NOOP("Constrain tangent or collinear");
6546
    sToolTipText = QT_TR_NOOP("Create a tangent or collinear constraint between two entities");
6547
    sWhatsThis = "Sketcher_ConstrainTangent";
6548
    sStatusTip = sToolTipText;
6549
    sPixmap = "Constraint_Tangent";
6550
    sAccel = "T";
6551
    eType = ForEdit;
6552

6553
    allowedSelSequences = {
6554
        {SelEdge, SelEdgeOrAxis},
6555
        {SelEdgeOrAxis, SelEdge},
6556
        {SelEdge, SelExternalEdge},
6557
        {SelExternalEdge, SelEdge}, /* Two Curves */
6558
        {SelVertexOrRoot, SelEdge, SelEdgeOrAxis},
6559
        {SelVertexOrRoot, SelEdgeOrAxis, SelEdge},
6560
        {SelVertexOrRoot, SelEdge, SelExternalEdge},
6561
        {SelVertexOrRoot, SelExternalEdge, SelEdge},
6562
        {SelEdge, SelVertexOrRoot, SelEdgeOrAxis},
6563
        {SelEdgeOrAxis, SelVertexOrRoot, SelEdge},
6564
        {SelEdge, SelVertexOrRoot, SelExternalEdge},
6565
        {SelExternalEdge, SelVertexOrRoot, SelEdge}, /* Two Curves and a Point */
6566
        {SelVertexOrRoot, SelVertex} /*Two Endpoints*/ /*No Place for One Endpoint and One Curve*/};
6567
}
6568

6569
bool CmdSketcherConstrainTangent::substituteConstraintCombinations(SketchObject* Obj,
6570
                                                                   int GeoId1,
6571
                                                                   int GeoId2)
6572
{
6573
    const std::vector<Constraint*>& cvals = Obj->Constraints.getValues();
6574

6575
    int cid = 0;
6576
    for (std::vector<Constraint*>::const_iterator it = cvals.begin(); it != cvals.end();
6577
         ++it, ++cid) {
6578
        if ((*it)->Type == Sketcher::Coincident
6579
            && (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
6580
                || ((*it)->Second == GeoId1 && (*it)->First == GeoId2))
6581
            && ((*it)->FirstPos == Sketcher::PointPos::start
6582
                || (*it)->FirstPos == Sketcher::PointPos::end)
6583
            && ((*it)->SecondPos == Sketcher::PointPos::start
6584
                || (*it)->SecondPos == Sketcher::PointPos::end)) {
6585
            // save values because 'doEndpointTangency' changes the
6586
            // constraint property and thus invalidates this iterator
6587
            int first = (*it)->First;
6588
            int firstpos = static_cast<int>((*it)->FirstPos);
6589

6590
            Gui::Command::openCommand(
6591
                QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency"));
6592

6593
            doEndpointTangency(Obj, (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos);
6594

6595
            Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%d,%d)", first, firstpos);
6596

6597
            commitCommand();
6598
            Obj->solve();// The substitution requires a solve() so that the autoremove redundants
6599
                         // works when Autorecompute not active.
6600
            tryAutoRecomputeIfNotSolve(Obj);
6601

6602
            notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied. "
6603
                                                      "The coincident constraint was deleted."));
6604

6605
            getSelection().clearSelection();
6606
            return true;
6607
        }
6608
        else if ((*it)->Type == Sketcher::PointOnObject
6609
                 && (((*it)->First == GeoId1 && (*it)->Second == GeoId2)
6610
                     || ((*it)->Second == GeoId1 && (*it)->First == GeoId2))
6611
                 && ((*it)->FirstPos == Sketcher::PointPos::start
6612
                     || (*it)->FirstPos == Sketcher::PointPos::end)) {
6613
            Gui::Command::openCommand(
6614
                QT_TRANSLATE_NOOP("Command",
6615
                                  "Swap point on object and tangency with point to curve tangency"));
6616

6617
            doEndpointToEdgeTangency(Obj, (*it)->First, (*it)->FirstPos, (*it)->Second);
6618

6619
            Gui::cmdAppObjectArgs(Obj,
6620
                                  "delConstraint(%d)",
6621
                                  cid);// remove the preexisting point on object constraint.
6622

6623
            commitCommand();
6624

6625
            // A substitution requires a solve() so that the autoremove redundants works when
6626
            // Autorecompute not active. However, delConstraint includes such solve() internally. So
6627
            // at this point it is already solved.
6628
            tryAutoRecomputeIfNotSolve(Obj);
6629

6630
            notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied. The "
6631
                                                      "point on object constraint was deleted."));
6632

6633
            getSelection().clearSelection();
6634
            return true;
6635
        }
6636
    }
6637

6638
    return false;
6639
}
6640

6641
void CmdSketcherConstrainTangent::activated(int iMsg)
6642
{
6643
    Q_UNUSED(iMsg);
6644

6645
    // get the selection
6646
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
6647

6648
    // only one sketch with its subelements are allowed to be selected
6649
    if (selection.size() != 1
6650
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
6651
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
6652
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
6653
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
6654

6655
        if (constraintMode) {
6656
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
6657
            getSelection().clearSelection();
6658
        }
6659
        else {
6660
            QString strBasicHelp =
6661
                QObject::tr("There are a number of ways this constraint can be applied.\n\n"
6662
                            "Accepted combinations: two curves; an endpoint and a curve; two "
6663
                            "endpoints; two curves and a point.",
6664
                            /*disambig.:*/ "tangent constraint");
6665
            QString strError =
6666
                QObject::tr("Select some geometry from the sketch.", "tangent constraint");
6667
            strError.append(QString::fromLatin1("\n\n"));
6668
            strError.append(strBasicHelp);
6669
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
6670
                                       QObject::tr("Wrong selection"),
6671
                                       std::move(strError));
6672
        }
6673
        return;
6674
    }
6675

6676
    // get the needed lists and objects
6677
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
6678
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
6679

6680
    if (SubNames.size() != 2 && SubNames.size() != 3) {
6681
        Gui::TranslatedUserWarning(Obj,
6682
                                   QObject::tr("Wrong selection"),
6683
                                   QObject::tr("Wrong number of selected objects!"));
6684
        return;
6685
    }
6686

6687
    int GeoId1, GeoId2, GeoId3;
6688
    Sketcher::PointPos PosId1, PosId2, PosId3;
6689
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
6690
    getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
6691

6692
    if (areBothPointsOrSegmentsFixed(Obj,
6693
                                     GeoId1,
6694
                                     GeoId2)) {// checkBothExternal displays error message
6695
        showNoConstraintBetweenFixedGeometry(Obj);
6696
        return;
6697
    }
6698
    if (SubNames.size() == 3) {// tangent via point
6699
        getIdsFromName(SubNames[2], Obj, GeoId3, PosId3);
6700
        // let's sink the point to be GeoId3. We want to keep the order the two curves have been
6701
        // selected in.
6702
        if (isVertex(GeoId1, PosId1)) {
6703
            std::swap(GeoId1, GeoId2);
6704
            std::swap(PosId1, PosId2);
6705
        }
6706
        if (isVertex(GeoId2, PosId2)) {
6707
            std::swap(GeoId2, GeoId3);
6708
            std::swap(PosId2, PosId3);
6709
        }
6710

6711
        if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
6712

6713
            if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
6714
                Gui::TranslatedUserWarning(
6715
                    Obj,
6716
                    QObject::tr("Wrong selection"),
6717
                    QObject::tr("Select an edge that is not a B-spline weight."));
6718
                return;
6719
            }
6720

6721
            openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
6722

6723
            bool safe = addConstraintSafely(Obj, [&]() {
6724
                // add missing point-on-object constraints
6725
                if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
6726
                    const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
6727
                    if (!(geom1 && isBSplineCurve(*geom1))) {
6728
                        Gui::cmdAppObjectArgs(
6729
                            selection[0].getObject(),
6730
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6731
                            GeoId3,
6732
                            static_cast<int>(PosId3),
6733
                            GeoId1);
6734
                    }
6735
                }
6736

6737
                if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
6738
                    const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
6739
                    if (!(geom2 && isBSplineCurve(*geom2))) {
6740
                        Gui::cmdAppObjectArgs(
6741
                            selection[0].getObject(),
6742
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6743
                            GeoId3,
6744
                            static_cast<int>(PosId3),
6745
                            GeoId2);
6746
                    }
6747
                }
6748

6749
                if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
6750
                    // FIXME: it's a good idea to add a check if the sketch is solved
6751
                    const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
6752
                    if (!(geom1 && isBSplineCurve(*geom1))) {
6753
                        Gui::cmdAppObjectArgs(
6754
                            selection[0].getObject(),
6755
                            "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
6756
                            GeoId3,
6757
                            static_cast<int>(PosId3),
6758
                            GeoId1);
6759
                    }
6760
                }
6761

6762
                Gui::cmdAppObjectArgs(
6763
                    selection[0].getObject(),
6764
                    "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
6765
                    GeoId1,
6766
                    GeoId2,
6767
                    GeoId3,
6768
                    static_cast<int>(PosId3));
6769

6770
                removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
6771
            });
6772

6773
            if (!safe) {
6774
                return;
6775
            }
6776
            else {
6777
                commitCommand();
6778
                tryAutoRecompute(Obj);
6779
            }
6780

6781
            getSelection().clearSelection();
6782

6783
            return;
6784
        }
6785

6786
        Gui::TranslatedUserWarning(
6787
            Obj,
6788
            QObject::tr("Wrong selection"),
6789
            QObject::tr("With 3 objects, there must be 2 curves and 1 point."));
6790
    }
6791
    else if (SubNames.size() == 2) {
6792

6793
        if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {// endpoint-to-endpoint tangency
6794

6795
            if (isBsplineKnot(Obj, GeoId2)) {
6796
                std::swap(GeoId1, GeoId2);
6797
                std::swap(PosId1, PosId2);
6798
            }
6799

6800
            if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) {
6801

6802
                if (isBsplineKnot(Obj, GeoId1)) {
6803
                    const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
6804

6805
                    if (! geom2 || ! isLineSegment(*geom2)) {
6806
                        Gui::TranslatedUserWarning(
6807
                            Obj,
6808
                            QObject::tr("Wrong selection"),
6809
                            QObject::tr("Tangent constraint at B-spline knot is only supported "
6810
                                        "with lines!"));
6811
                        return;
6812
                    }
6813
                }
6814
                else {
6815
                    Gui::TranslatedUserWarning(
6816
                        Obj,
6817
                        QObject::tr("Wrong selection"),
6818
                        QObject::tr("Cannot add a tangency constraint at an unconnected point!"));
6819
                    return;
6820
                }
6821
            }
6822

6823
            openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
6824
            doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2);
6825
            commitCommand();
6826
            tryAutoRecompute(Obj);
6827

6828
            getSelection().clearSelection();
6829
            return;
6830
        }
6831
        else if ((isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2))
6832
                 || (isEdge(GeoId1, PosId1)
6833
                     && isVertex(GeoId2, PosId2))) {// endpoint-to-curve/knot-to-curve tangency
6834
            if (isVertex(GeoId2, PosId2)) {
6835
                std::swap(GeoId1, GeoId2);
6836
                std::swap(PosId1, PosId2);
6837
            }
6838

6839
            if (isSimpleVertex(Obj, GeoId1, PosId1)) {
6840
                if (isBsplineKnot(Obj, GeoId1)) {
6841
                    const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
6842

6843
                    if (!geom2 || ! isLineSegment(*geom2)) {
6844
                        Gui::TranslatedUserWarning(
6845
                            Obj,
6846
                            QObject::tr("Wrong selection"),
6847
                            QObject::tr("Tangent constraint at B-spline knot is only supported "
6848
                                        "with lines!"));
6849
                        return;
6850
                    }
6851
                }
6852
                else {
6853
                    Gui::TranslatedUserWarning(
6854
                        Obj,
6855
                        QObject::tr("Wrong selection"),
6856
                        QObject::tr("Cannot add a tangency constraint at an unconnected point!"));
6857
                    return;
6858
                }
6859
            }
6860

6861
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
6862

6863
            if (isBsplinePole(geom2)) {
6864
                Gui::TranslatedUserWarning(
6865
                    Obj,
6866
                    QObject::tr("Wrong selection"),
6867
                    QObject::tr("Select an edge that is not a B-spline weight."));
6868
                return;
6869
            }
6870

6871
            if (!substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
6872
                openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
6873
                Gui::cmdAppObjectArgs(selection[0].getObject(),
6874
                                      "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d))",
6875
                                      GeoId1,
6876
                                      static_cast<int>(PosId1),
6877
                                      GeoId2);
6878
                commitCommand();
6879
                tryAutoRecompute(Obj);
6880

6881
                getSelection().clearSelection();
6882
            }
6883
            return;
6884
        }
6885
        else if (isEdge(GeoId1, PosId1)
6886
                 && isEdge(GeoId2, PosId2)) {// simple tangency between GeoId1 and GeoId2
6887

6888
            const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
6889
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
6890

6891
            if (isBsplinePole(geom1) || isBsplinePole(geom2)) {
6892
                Gui::TranslatedUserWarning(
6893
                    Obj,
6894
                    QObject::tr("Wrong selection"),
6895
                    QObject::tr("Select an edge that is not a B-spline weight."));
6896
                return;
6897
            }
6898

6899
            // check if as a consequence of this command undesirable combinations of constraints
6900
            // would arise and substitute them with more appropriate counterparts, examples:
6901
            // - coincidence + tangency on edge
6902
            // - point on object + tangency on edge
6903
            if (substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
6904
                return;
6905
            }
6906

6907
            if (geom1 && geom2 && (isEllipse(*geom1) || isEllipse(*geom2))) {
6908
                if (! isEllipse(*geom1)) {
6909
                    std::swap(GeoId1, GeoId2);
6910
                }
6911

6912
                // GeoId1 is the ellipse
6913
                geom1 = Obj->getGeometry(GeoId1);
6914
                geom2 = Obj->getGeometry(GeoId2);
6915

6916
                if (isEllipse(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2) || isArcOfCircle(*geom2)) {
6917
                    Gui::Command::openCommand(
6918
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
6919
                    makeTangentToEllipseviaNewPoint(Obj,
6920
                                                    static_cast<const Part::GeomEllipse*>(geom1),
6921
                                                    geom2,
6922
                                                    GeoId1,
6923
                                                    GeoId2);
6924
                    getSelection().clearSelection();
6925
                    return;
6926
                }
6927
                else if (isArcOfHyperbola(*geom2)) {
6928
                    Gui::Command::openCommand(
6929
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
6930
                    makeTangentToArcOfHyperbolaviaNewPoint(
6931
                        Obj,
6932
                        static_cast<const Part::GeomArcOfHyperbola*>(geom2),
6933
                        geom1,
6934
                        GeoId2,
6935
                        GeoId1);
6936
                    getSelection().clearSelection();
6937
                    return;
6938
                }
6939
                else if (isArcOfParabola(*geom2)) {
6940
                    Gui::Command::openCommand(
6941
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
6942
                    makeTangentToArcOfParabolaviaNewPoint(
6943
                        Obj,
6944
                        static_cast<const Part::GeomArcOfParabola*>(geom2),
6945
                        geom1,
6946
                        GeoId2,
6947
                        GeoId1);
6948
                    getSelection().clearSelection();
6949
                    return;
6950
                }
6951
            }
6952
            else if (geom1 && geom2 && (isArcOfEllipse(*geom1) || isArcOfEllipse(*geom2))) {
6953
                if (! isArcOfEllipse(*geom1)) {
6954
                    std::swap(GeoId1, GeoId2);
6955
                }
6956

6957
                // GeoId1 is the arc of ellipse
6958
                geom1 = Obj->getGeometry(GeoId1);
6959
                geom2 = Obj->getGeometry(GeoId2);
6960

6961
                if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2)
6962
                    || isCircle(*geom2) || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
6963

6964
                    Gui::Command::openCommand(
6965
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
6966
                    makeTangentToArcOfEllipseviaNewPoint(
6967
                        Obj,
6968
                        static_cast<const Part::GeomArcOfEllipse*>(geom1),
6969
                        geom2,
6970
                        GeoId1,
6971
                        GeoId2);
6972

6973
                    getSelection().clearSelection();
6974
                    return;
6975
                }
6976
                else if (isArcOfParabola(*geom2)) {
6977
                    Gui::Command::openCommand(
6978
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
6979
                    makeTangentToArcOfParabolaviaNewPoint(
6980
                        Obj,
6981
                        static_cast<const Part::GeomArcOfParabola*>(geom2),
6982
                        geom1,
6983
                        GeoId2,
6984
                        GeoId1);
6985
                    getSelection().clearSelection();
6986
                    return;
6987
                }
6988
            }
6989
            else if (geom1 && geom2 && (isArcOfHyperbola(*geom1) || isArcOfHyperbola(*geom2))) {
6990
                if (! isArcOfHyperbola(*geom1)) {
6991
                    std::swap(GeoId1, GeoId2);
6992
                }
6993

6994
                // GeoId1 is the arc of hyperbola
6995
                geom1 = Obj->getGeometry(GeoId1);
6996
                geom2 = Obj->getGeometry(GeoId2);
6997

6998
                if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2)
6999
                    || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
7000

7001
                    Gui::Command::openCommand(
7002
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7003
                    makeTangentToArcOfHyperbolaviaNewPoint(
7004
                        Obj,
7005
                        static_cast<const Part::GeomArcOfHyperbola*>(geom1),
7006
                        geom2,
7007
                        GeoId1,
7008
                        GeoId2);
7009
                    getSelection().clearSelection();
7010
                    return;
7011
                }
7012
                else if (isArcOfParabola(*geom2)) {
7013
                    Gui::Command::openCommand(
7014
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7015
                    makeTangentToArcOfParabolaviaNewPoint(
7016
                        Obj,
7017
                        static_cast<const Part::GeomArcOfParabola*>(geom2),
7018
                        geom1,
7019
                        GeoId2,
7020
                        GeoId1);
7021
                    getSelection().clearSelection();
7022
                    return;
7023
                }
7024
            }
7025
            else if (geom1 && geom2 && (isArcOfParabola(*geom1) || isArcOfParabola(*geom2))) {
7026
                if (! isArcOfParabola(*geom1)) {
7027
                    std::swap(GeoId1, GeoId2);
7028
                }
7029

7030
                // GeoId1 is the arc of hyperbola
7031
                geom1 = Obj->getGeometry(GeoId1);
7032
                geom2 = Obj->getGeometry(GeoId2);
7033

7034
                if (isArcOfParabola(*geom2) || isArcOfHyperbola(*geom2)
7035
                    || isArcOfEllipse(*geom2) || isCircle(*geom2)
7036
                    || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
7037

7038
                    Gui::Command::openCommand(
7039
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7040
                    makeTangentToArcOfParabolaviaNewPoint(
7041
                        Obj,
7042
                        static_cast<const Part::GeomArcOfParabola*>(geom1),
7043
                        geom2,
7044
                        GeoId1,
7045
                        GeoId2);
7046
                    getSelection().clearSelection();
7047
                    return;
7048
                }
7049
            }
7050
            else if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
7051
                Gui::TranslatedUserWarning(
7052
                    Obj,
7053
                    QObject::tr("Wrong selection"),
7054
                    QObject::tr("Only tangent-via-point is supported with a B-spline."));
7055
                getSelection().clearSelection();
7056
                return;
7057
            }
7058

7059
            openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
7060
            Gui::cmdAppObjectArgs(selection[0].getObject(),
7061
                                  "addConstraint(Sketcher.Constraint('Tangent',%d,%d))",
7062
                                  GeoId1,
7063
                                  GeoId2);
7064
            commitCommand();
7065
            tryAutoRecompute(Obj);
7066

7067
            getSelection().clearSelection();
7068
            return;
7069
        }
7070
    }
7071

7072
    return;
7073
}
7074

7075
void CmdSketcherConstrainTangent::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
7076
{
7077
    SketcherGui::ViewProviderSketch* sketchgui =
7078
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
7079
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
7080

7081
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
7082
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
7083
                       PosId3 = Sketcher::PointPos::none;
7084

7085
    switch (seqIndex) {
7086
        case 0:// {SelEdge, SelEdgeOrAxis}
7087
        case 1:// {SelEdgeOrAxis, SelEdge}
7088
        case 2:// {SelEdge, SelExternalEdge}
7089
        case 3:// {SelExternalEdge, SelEdge}
7090
        {
7091
            GeoId1 = selSeq.at(0).GeoId;
7092
            GeoId2 = selSeq.at(1).GeoId;
7093

7094
            // check if the edge already has a Block constraint
7095
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
7096
                showNoConstraintBetweenFixedGeometry(Obj);
7097
                return;
7098
            }
7099

7100
            const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
7101
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
7102

7103
            if (isBsplinePole(geom1) || isBsplinePole(geom2)) {
7104
                Gui::TranslatedUserWarning(
7105
                    Obj,
7106
                    QObject::tr("Wrong selection"),
7107
                    QObject::tr("Select an edge that is not a B-spline weight."));
7108
                return;
7109
            }
7110

7111
            // check if as a consequence of this command undesirable combinations of constraints
7112
            // would arise and substitute them with more appropriate counterparts, examples:
7113
            // - coincidence + tangency on edge
7114
            // - point on object + tangency on edge
7115
            if (substituteConstraintCombinations(Obj, GeoId1, GeoId2)) {
7116
                return;
7117
            }
7118

7119
            if (geom1 && geom2 && (isEllipse(*geom1) || isEllipse(*geom2))) {
7120
                if (! isEllipse(*geom1)) {
7121
                    std::swap(GeoId1, GeoId2);
7122
                }
7123

7124
                // GeoId1 is the ellipse
7125
                geom1 = Obj->getGeometry(GeoId1);
7126
                geom2 = Obj->getGeometry(GeoId2);
7127

7128
                if (isEllipse(*geom2) || isArcOfEllipse(*geom2)
7129
                    || isCircle(*geom2) || isArcOfCircle(*geom2)) {
7130

7131
                    Gui::Command::openCommand(
7132
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7133
                    makeTangentToEllipseviaNewPoint(Obj,
7134
                                                    static_cast<const Part::GeomEllipse*>(geom1),
7135
                                                    geom2,
7136
                                                    GeoId1,
7137
                                                    GeoId2);
7138
                    getSelection().clearSelection();
7139
                    return;
7140
                }
7141
                else if (isArcOfHyperbola(*geom2)) {
7142
                    Gui::Command::openCommand(
7143
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7144
                    makeTangentToArcOfHyperbolaviaNewPoint(
7145
                        Obj,
7146
                        static_cast<const Part::GeomArcOfHyperbola*>(geom2),
7147
                        geom1,
7148
                        GeoId2,
7149
                        GeoId1);
7150
                    getSelection().clearSelection();
7151
                    return;
7152
                }
7153
                else if (isArcOfParabola(*geom2)) {
7154
                    Gui::Command::openCommand(
7155
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7156
                    makeTangentToArcOfParabolaviaNewPoint(
7157
                        Obj,
7158
                        static_cast<const Part::GeomArcOfParabola*>(geom2),
7159
                        geom1,
7160
                        GeoId2,
7161
                        GeoId1);
7162
                    getSelection().clearSelection();
7163
                    return;
7164
                }
7165
            }
7166
            else if (geom1 && geom2 && (isArcOfHyperbola(*geom1) || isArcOfHyperbola(*geom2))) {
7167
                if (! isArcOfHyperbola(*geom1)) {
7168
                    std::swap(GeoId1, GeoId2);
7169
                }
7170

7171
                // GeoId1 is the arc of hyperbola
7172
                geom1 = Obj->getGeometry(GeoId1);
7173
                geom2 = Obj->getGeometry(GeoId2);
7174

7175
                if (isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2) || isCircle(*geom2)
7176
                   || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
7177

7178
                    Gui::Command::openCommand(
7179
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7180
                    makeTangentToArcOfHyperbolaviaNewPoint(
7181
                        Obj,
7182
                        static_cast<const Part::GeomArcOfHyperbola*>(geom1),
7183
                        geom2,
7184
                        GeoId1,
7185
                        GeoId2);
7186
                    getSelection().clearSelection();
7187
                    return;
7188
                }
7189
                else if (isArcOfParabola(*geom2)) {
7190
                    Gui::Command::openCommand(
7191
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7192
                    makeTangentToArcOfParabolaviaNewPoint(
7193
                        Obj,
7194
                        static_cast<const Part::GeomArcOfParabola*>(geom2),
7195
                        geom1,
7196
                        GeoId2,
7197
                        GeoId1);
7198
                    getSelection().clearSelection();
7199
                    return;
7200
                }
7201
            }
7202
            else if (geom1 && geom2 && (isArcOfParabola(*geom1) || isArcOfParabola(*geom2))) {
7203
                if (! isArcOfParabola(*geom1)) {
7204
                    std::swap(GeoId1, GeoId2);
7205
                }
7206

7207
                // GeoId1 is the arc of hyperbola
7208
                geom1 = Obj->getGeometry(GeoId1);
7209
                geom2 = Obj->getGeometry(GeoId2);
7210

7211
                if (isArcOfParabola(*geom2) || isArcOfHyperbola(*geom2) || isArcOfEllipse(*geom2)
7212
                   || isCircle(*geom2) || isArcOfCircle(*geom2) || isLineSegment(*geom2)) {
7213

7214
                    Gui::Command::openCommand(
7215
                        QT_TRANSLATE_NOOP("Command", "Add tangent constraint point"));
7216
                    makeTangentToArcOfParabolaviaNewPoint(
7217
                        Obj,
7218
                        static_cast<const Part::GeomArcOfParabola*>(geom1),
7219
                        geom2,
7220
                        GeoId1,
7221
                        GeoId2);
7222
                    getSelection().clearSelection();
7223
                    return;
7224
                }
7225
            }
7226

7227
            openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
7228
            Gui::cmdAppObjectArgs(Obj,
7229
                                  "addConstraint(Sketcher.Constraint('Tangent',%d,%d))",
7230
                                  GeoId1,
7231
                                  GeoId2);
7232
            commitCommand();
7233
            tryAutoRecompute(Obj);
7234

7235
            return;
7236
        }
7237
        case 4:// {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}
7238
        case 5:// {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}
7239
        case 6:// {SelVertexOrRoot, SelEdge, SelExternalEdge}
7240
        case 7:// {SelVertexOrRoot, SelExternalEdge, SelEdge}
7241
        {
7242
            // let's sink the point to be GeoId3.
7243
            GeoId1 = selSeq.at(1).GeoId;
7244
            GeoId2 = selSeq.at(2).GeoId;
7245
            GeoId3 = selSeq.at(0).GeoId;
7246
            PosId3 = selSeq.at(0).PosId;
7247

7248
            break;
7249
        }
7250
        case 8: // {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}
7251
        case 9: // {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}
7252
        case 10:// {SelEdge, SelVertexOrRoot, SelExternalEdge}
7253
        case 11:// {SelExternalEdge, SelVertexOrRoot, SelEdge}
7254
        {
7255
            // let's sink the point to be GeoId3.
7256
            GeoId1 = selSeq.at(0).GeoId;
7257
            GeoId2 = selSeq.at(2).GeoId;
7258
            GeoId3 = selSeq.at(1).GeoId;
7259
            PosId3 = selSeq.at(1).PosId;
7260

7261
            break;
7262
        }
7263
        case 12:// {SelVertexOrRoot, SelVertex}
7264
        {
7265
            // Different notation than the previous places
7266
            GeoId1 = selSeq.at(0).GeoId;
7267
            GeoId2 = selSeq.at(1).GeoId;
7268
            PosId1 = selSeq.at(0).PosId;
7269
            PosId2 = selSeq.at(1).PosId;
7270

7271
            // check if the edge already has a Block constraint
7272
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
7273
                showNoConstraintBetweenFixedGeometry(Obj);
7274
                return;
7275
            }
7276

7277
            if (isSimpleVertex(Obj, GeoId1, PosId1) || isSimpleVertex(Obj, GeoId2, PosId2)) {
7278
                Gui::TranslatedUserWarning(
7279
                    Obj,
7280
                    QObject::tr("Wrong selection"),
7281
                    QObject::tr("Cannot add a tangency constraint at an unconnected point!"));
7282
                return;
7283
            }
7284

7285
            // This code supports simple B-spline endpoint tangency to any other geometric curve
7286
            const Part::Geometry* geom1 = Obj->getGeometry(GeoId1);
7287
            const Part::Geometry* geom2 = Obj->getGeometry(GeoId2);
7288

7289
            if (geom1 && geom2 && (isBSplineCurve(*geom1) || isBSplineCurve(*geom2))) {
7290
                if (! isBSplineCurve(*geom1)) {
7291
                    std::swap(GeoId1, GeoId2);
7292
                    std::swap(PosId1, PosId2);
7293
                }
7294
                // GeoId1 is the B-spline now
7295
            }// end of code supports simple B-spline endpoint tangency
7296

7297
            openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
7298
            Gui::cmdAppObjectArgs(Obj,
7299
                                  "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d))",
7300
                                  GeoId1,
7301
                                  static_cast<int>(PosId1),
7302
                                  GeoId2,
7303
                                  static_cast<int>(PosId2));
7304
            commitCommand();
7305
            tryAutoRecompute(Obj);
7306

7307
            getSelection().clearSelection();
7308
            return;
7309
        }
7310
        default:
7311
            return;
7312
    }
7313

7314
    if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
7315

7316
        // check if the edge already has a Block constraint
7317
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
7318
            showNoConstraintBetweenFixedGeometry(Obj);
7319
            return;
7320
        }
7321

7322
        if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
7323
            Gui::TranslatedUserWarning(
7324
                Obj,
7325
                QObject::tr("Wrong selection"),
7326
                QObject::tr("Select an edge that is not a B-spline weight."));
7327
            return;
7328
        }
7329

7330
        openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint"));
7331

7332
        bool safe = addConstraintSafely(Obj, [&]() {
7333
            // add missing point-on-object constraints
7334
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
7335
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
7336
                if (!(geom1 && isBSplineCurve(*geom1))) {
7337
                    Gui::cmdAppObjectArgs(
7338
                        Obj,
7339
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
7340
                        GeoId3,
7341
                        static_cast<int>(PosId3),
7342
                        GeoId1);
7343
                }
7344
            }
7345

7346
            if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
7347
                const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
7348
                if (!(geom2 && isBSplineCurve(*geom2))) {
7349
                    Gui::cmdAppObjectArgs(
7350
                        Obj,
7351
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
7352
                        GeoId3,
7353
                        static_cast<int>(PosId3),
7354
                        GeoId2);
7355
                }
7356
            }
7357

7358
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
7359
                // FIXME: it's a good idea to add a check if the sketch is solved
7360
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
7361
                if (!(geom1 && isBSplineCurve(*geom1))) {
7362
                    Gui::cmdAppObjectArgs(
7363
                        Obj,
7364
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
7365
                        GeoId3,
7366
                        static_cast<int>(PosId3),
7367
                        GeoId1);
7368
                }
7369
            }
7370

7371
            Gui::cmdAppObjectArgs(
7372
                Obj,
7373
                "addConstraint(Sketcher.Constraint('TangentViaPoint',%d,%d,%d,%d))",
7374
                GeoId1,
7375
                GeoId2,
7376
                GeoId3,
7377
                static_cast<int>(PosId3));
7378

7379
            removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
7380
        });
7381

7382
        if (!safe) {
7383
            return;
7384
        }
7385
        else {
7386
            commitCommand();
7387
            tryAutoRecompute(Obj);
7388
        }
7389

7390
        getSelection().clearSelection();
7391

7392
        return;
7393
    }
7394
}
7395

7396
// ======================================================================================
7397

7398
class CmdSketcherConstrainRadius: public CmdSketcherConstraint
7399
{
7400
public:
7401
    CmdSketcherConstrainRadius();
7402
    ~CmdSketcherConstrainRadius() override
7403
    {}
7404
    void updateAction(int mode) override;
7405
    const char* className() const override
7406
    {
7407
        return "CmdSketcherConstrainRadius";
7408
    }
7409

7410
protected:
7411
    void activated(int iMsg) override;
7412
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
7413
};
7414

7415
CmdSketcherConstrainRadius::CmdSketcherConstrainRadius()
7416
    : CmdSketcherConstraint("Sketcher_ConstrainRadius")
7417
{
7418
    sAppModule = "Sketcher";
7419
    sGroup = "Sketcher";
7420
    sMenuText = QT_TR_NOOP("Constrain radius");
7421
    sToolTipText = QT_TR_NOOP("Fix the radius of a circle or an arc");
7422
    sWhatsThis = "Sketcher_ConstrainRadius";
7423
    sStatusTip = sToolTipText;
7424
    sPixmap = "Constraint_Radius";
7425
    sAccel = "K, R";
7426
    eType = ForEdit;
7427

7428
    allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
7429
}
7430

7431
void CmdSketcherConstrainRadius::activated(int iMsg)
7432
{
7433
    Q_UNUSED(iMsg);
7434
    // get the selection
7435
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
7436

7437
    // only one sketch with its subelements are allowed to be selected
7438
    if (selection.size() != 1
7439
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
7440
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
7441
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
7442
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
7443

7444
        if (constraintMode) {
7445
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
7446
            getSelection().clearSelection();
7447
        }
7448
        else {
7449
            // TODO: Get the exact message from git history and put it here
7450
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
7451
                                       QObject::tr("Wrong selection"),
7452
                                       QObject::tr("Select the right things from the sketch."));
7453
        }
7454
        return;
7455
    }
7456

7457
    // get the needed lists and objects
7458
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
7459
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
7460

7461
    if (SubNames.empty()) {
7462
        Gui::TranslatedUserWarning(
7463
            Obj,
7464
            QObject::tr("Wrong selection"),
7465
            QObject::tr("Select one or more arcs or circles from the sketch."));
7466
        return;
7467
    }
7468

7469
    // check for which selected geometry the constraint can be applied
7470
    std::vector<std::pair<int, double>> geoIdRadiusMap;
7471
    std::vector<std::pair<int, double>> externalGeoIdRadiusMap;
7472

7473
    bool poles = false;
7474
    bool nonpoles = false;
7475

7476
    for (auto& subname : SubNames) {
7477
        bool issegmentfixed = false;
7478
        int GeoId;
7479

7480
        if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
7481
            GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
7482
            issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
7483
        }
7484
        else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
7485
            GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
7486
            issegmentfixed = true;
7487
        }
7488
        else {
7489
            continue;
7490
        }
7491

7492
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
7493

7494
        if (geom && isArcOfCircle(*geom)) {
7495
            auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
7496
            double radius = arc->getRadius();
7497

7498
            if (issegmentfixed) {
7499
                externalGeoIdRadiusMap.emplace_back(GeoId, radius);
7500
            }
7501
            else {
7502
                geoIdRadiusMap.emplace_back(GeoId, radius);
7503
            }
7504

7505
            nonpoles = true;
7506
        }
7507
        else if (geom && isCircle(*geom)) {
7508
            auto circle = static_cast<const Part::GeomCircle*>(geom);
7509
            double radius = circle->getRadius();
7510

7511
            if (issegmentfixed) {
7512
                externalGeoIdRadiusMap.emplace_back(GeoId, radius);
7513
            }
7514
            else {
7515
                geoIdRadiusMap.emplace_back(GeoId, radius);
7516
            }
7517

7518
            if (isBsplinePole(geom)) {
7519
                poles = true;
7520
            }
7521
            else {
7522
                nonpoles = true;
7523
            }
7524
        }
7525
    }
7526

7527
    if (geoIdRadiusMap.empty() && externalGeoIdRadiusMap.empty()) {
7528
        Gui::TranslatedUserWarning(
7529
            Obj,
7530
            QObject::tr("Wrong selection"),
7531
            QObject::tr("Select one or more arcs or circles from the sketch."));
7532
        return;
7533
    }
7534

7535
    if (poles && nonpoles) {
7536
        Gui::TranslatedUserWarning(
7537
            Obj,
7538
            QObject::tr("Wrong selection"),
7539
            QObject::tr("Select either only one or more B-spline poles or only one or more arcs or "
7540
                        "circles from the sketch, but not mixed."));
7541
        return;
7542
    }
7543

7544
    bool commitNeeded = false;
7545
    bool updateNeeded = false;
7546
    bool commandopened = false;
7547

7548
    if (!externalGeoIdRadiusMap.empty()) {
7549
        // Create the non-driving radius constraints now
7550
        openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
7551
        commandopened = true;
7552
        unsigned int constrSize = 0;
7553

7554
        for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdRadiusMap.begin();
7555
             it != externalGeoIdRadiusMap.end();
7556
             ++it) {
7557

7558
            if (nonpoles) {
7559
                Gui::cmdAppObjectArgs(selection[0].getObject(),
7560
                                      "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
7561
                                      it->first,
7562
                                      it->second);
7563
            }
7564
            else {
7565
                Gui::cmdAppObjectArgs(selection[0].getObject(),
7566
                                      "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
7567
                                      it->first,
7568
                                      it->second);
7569
            }
7570

7571
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
7572

7573
            constrSize = ConStr.size();
7574

7575
            Gui::cmdAppObjectArgs(selection[0].getObject(),
7576
                                  "setDriving(%d,%s)",
7577
                                  constrSize - 1,
7578
                                  "False");
7579
        }
7580

7581
        finishDatumConstraint(this, Obj, false, externalGeoIdRadiusMap.size());
7582

7583
        commitNeeded = true;
7584
        updateNeeded = true;
7585
    }
7586

7587
    if (!geoIdRadiusMap.empty()) {
7588
        if (geoIdRadiusMap.size() > 1 && constraintCreationMode == Driving) {
7589

7590
            int refGeoId = geoIdRadiusMap.front().first;
7591
            double radius = geoIdRadiusMap.front().second;
7592

7593
            if (!commandopened) {
7594
                openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
7595
            }
7596

7597
            // Add the equality constraints
7598
            for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiusMap.begin() + 1;
7599
                 it != geoIdRadiusMap.end();
7600
                 ++it) {
7601
                Gui::cmdAppObjectArgs(selection[0].getObject(),
7602
                                      "addConstraint(Sketcher.Constraint('Equal',%d,%d))",
7603
                                      refGeoId,
7604
                                      it->first);
7605
            }
7606

7607
            if (nonpoles) {
7608
                Gui::cmdAppObjectArgs(selection[0].getObject(),
7609
                                      "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
7610
                                      refGeoId,
7611
                                      radius);
7612
            }
7613
            else {
7614
                Gui::cmdAppObjectArgs(selection[0].getObject(),
7615
                                      "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
7616
                                      refGeoId,
7617
                                      radius);
7618
            }
7619
        }
7620
        else {
7621
            // Create the radius constraints now
7622
            if (!commandopened) {
7623
                openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
7624
            }
7625
            for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiusMap.begin();
7626
                 it != geoIdRadiusMap.end();
7627
                 ++it) {
7628
                if (nonpoles) {
7629
                    Gui::cmdAppObjectArgs(selection[0].getObject(),
7630
                                          "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
7631
                                          it->first,
7632
                                          it->second);
7633
                }
7634
                else {
7635
                    Gui::cmdAppObjectArgs(selection[0].getObject(),
7636
                                          "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
7637
                                          it->first,
7638
                                          it->second);
7639
                }
7640

7641
                if (constraintCreationMode == Reference) {
7642
                    const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
7643
                    Gui::cmdAppObjectArgs(selection[0].getObject(),
7644
                                          "setDriving(%d,%s)",
7645
                                          ConStr.size() - 1,
7646
                                          "False");
7647
                }
7648
            }
7649
        }
7650

7651
        finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
7652

7653
        // updateActive();
7654
        getSelection().clearSelection();
7655
    }
7656

7657
    if (commitNeeded) {
7658
        commitCommand();
7659
    }
7660

7661
    if (updateNeeded) {
7662
        tryAutoRecomputeIfNotSolve(Obj);// we have to update the solver after this aborted addition.
7663
    }
7664
}
7665

7666
void CmdSketcherConstrainRadius::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
7667
{
7668
    SketcherGui::ViewProviderSketch* sketchgui =
7669
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
7670
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
7671

7672
    int GeoId = selSeq.at(0).GeoId;
7673
    double radius = 0.0;
7674

7675
    bool updateNeeded = false;
7676

7677
    switch (seqIndex) {
7678
        case 0:// {SelEdge}
7679
        case 1:// {SelExternalEdge}
7680
        {
7681
            const Part::Geometry* geom = Obj->getGeometry(GeoId);
7682

7683
            if (geom && isArcOfCircle(*geom)) {
7684
                auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
7685
                radius = arc->getRadius();
7686
            }
7687
            else if (geom && isCircle(*geom)) {
7688
                auto circle = static_cast<const Part::GeomCircle*>(geom);
7689
                radius = circle->getRadius();
7690
            }
7691
            else {
7692
                Gui::TranslatedUserWarning(
7693
                    Obj,
7694
                    QObject::tr("Wrong selection"),
7695
                    QObject::tr("Constraint only applies to arcs or circles."));
7696
                return;
7697
            }
7698

7699
            // Create the radius constraint now
7700
            openCommand(QT_TRANSLATE_NOOP("Command", "Add radius constraint"));
7701

7702
            bool ispole = isBsplinePole(geom);
7703

7704
            if (ispole) {
7705
                Gui::cmdAppObjectArgs(Obj,
7706
                                      "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
7707
                                      GeoId,
7708
                                      radius);
7709
            }
7710
            else {
7711
                Gui::cmdAppObjectArgs(Obj,
7712
                                      "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
7713
                                      GeoId,
7714
                                      radius);
7715
            }
7716

7717
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
7718

7719
            bool fixed = isPointOrSegmentFixed(Obj, GeoId);
7720
            if (fixed || constraintCreationMode == Reference) {
7721
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
7722

7723
                updateNeeded = true;// We do need to update the solver DoF after setting the
7724
                                    // constraint driving.
7725
            }
7726

7727
            finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
7728

7729
            // updateActive();
7730
            getSelection().clearSelection();
7731

7732
            commitCommand();
7733

7734
            if (updateNeeded) {
7735
                tryAutoRecomputeIfNotSolve(
7736
                    Obj);// we have to update the solver after this aborted addition.
7737
            }
7738
        }
7739
    }
7740
}
7741

7742
void CmdSketcherConstrainRadius::updateAction(int mode)
7743
{
7744
    switch (mode) {
7745
        case Reference:
7746
            if (getAction()) {
7747
                getAction()->setIcon(
7748
                    Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
7749
            }
7750
            break;
7751
        case Driving:
7752
            if (getAction()) {
7753
                getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
7754
            }
7755
            break;
7756
    }
7757
}
7758

7759
// ======================================================================================
7760

7761
class CmdSketcherConstrainDiameter: public CmdSketcherConstraint
7762
{
7763
public:
7764
    CmdSketcherConstrainDiameter();
7765
    ~CmdSketcherConstrainDiameter() override
7766
    {}
7767
    void updateAction(int mode) override;
7768
    const char* className() const override
7769
    {
7770
        return "CmdSketcherConstrainDiameter";
7771
    }
7772

7773
protected:
7774
    void activated(int iMsg) override;
7775
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
7776
};
7777

7778
CmdSketcherConstrainDiameter::CmdSketcherConstrainDiameter()
7779
    : CmdSketcherConstraint("Sketcher_ConstrainDiameter")
7780
{
7781
    sAppModule = "Sketcher";
7782
    sGroup = "Sketcher";
7783
    sMenuText = QT_TR_NOOP("Constrain diameter");
7784
    sToolTipText = QT_TR_NOOP("Fix the diameter of a circle or an arc");
7785
    sWhatsThis = "Sketcher_ConstrainDiameter";
7786
    sStatusTip = sToolTipText;
7787
    sPixmap = "Constraint_Diameter";
7788
    sAccel = "K, O";
7789
    eType = ForEdit;
7790

7791
    allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
7792
}
7793

7794
void CmdSketcherConstrainDiameter::activated(int iMsg)
7795
{
7796
    Q_UNUSED(iMsg);
7797
    // get the selection
7798
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
7799

7800
    // only one sketch with its subelements are allowed to be selected
7801
    if (selection.size() != 1
7802
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
7803
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
7804
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
7805
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
7806

7807
        if (constraintMode) {
7808
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
7809
            getSelection().clearSelection();
7810
        }
7811
        else {
7812
            // TODO: Get the exact message from git history and put it here
7813
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
7814
                                       QObject::tr("Wrong selection"),
7815
                                       QObject::tr("Select the right things from the sketch."));
7816
        }
7817
        return;
7818
    }
7819

7820
    // get the needed lists and objects
7821
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
7822
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
7823

7824
    if (SubNames.empty()) {
7825
        Gui::TranslatedUserWarning(
7826
            Obj,
7827
            QObject::tr("Wrong selection"),
7828
            QObject::tr("Select one or more arcs or circles from the sketch."));
7829
        return;
7830
    }
7831

7832
    // check for which selected geometry the constraint can be applied
7833
    std::vector<std::pair<int, double>> geoIdDiameterMap;
7834
    std::vector<std::pair<int, double>> externalGeoIdDiameterMap;
7835

7836
    for (auto& subname : SubNames) {
7837
        bool issegmentfixed = false;
7838
        int GeoId;
7839

7840
        if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
7841
            GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
7842
            issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
7843
        }
7844
        else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
7845
            GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
7846
            issegmentfixed = true;
7847
        }
7848
        else {
7849
            continue;
7850
        }
7851

7852
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
7853

7854
        if (geom && isArcOfCircle(*geom)) {
7855
            auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
7856
            double radius = arc->getRadius();
7857

7858
            if (issegmentfixed) {
7859
                externalGeoIdDiameterMap.emplace_back(GeoId, 2 * radius);
7860
            }
7861
            else {
7862
                geoIdDiameterMap.emplace_back(GeoId, 2 * radius);
7863
            }
7864
        }
7865
        else if (geom && isCircle(*geom)) {
7866
            auto circle = static_cast<const Part::GeomCircle*>(geom);
7867
            double radius = circle->getRadius();
7868

7869
            if (isBsplinePole(geom)) {
7870
                Gui::TranslatedUserWarning(
7871
                    Obj,
7872
                    QObject::tr("Wrong selection"),
7873
                    QObject::tr("Select an edge that is not a B-spline weight."));
7874

7875
                continue;
7876
            }
7877

7878
            if (issegmentfixed) {
7879
                externalGeoIdDiameterMap.emplace_back(GeoId, 2 * radius);
7880
            }
7881
            else {
7882
                geoIdDiameterMap.emplace_back(GeoId, 2 * radius);
7883
            }
7884
        }
7885
    }
7886

7887
    if (geoIdDiameterMap.empty() && externalGeoIdDiameterMap.empty()) {
7888
        Gui::TranslatedUserWarning(
7889
            Obj,
7890
            QObject::tr("Wrong selection"),
7891
            QObject::tr("Select one or more arcs or circles from the sketch."));
7892
        return;
7893
    }
7894

7895
    bool commitNeeded = false;
7896
    bool updateNeeded = false;
7897
    bool commandopened = false;
7898

7899
    if (!externalGeoIdDiameterMap.empty()) {
7900
        // Create the non-driving radius constraints now
7901
        openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
7902
        commandopened = true;
7903
        unsigned int constrSize = 0;
7904

7905
        for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdDiameterMap.begin();
7906
             it != externalGeoIdDiameterMap.end();
7907
             ++it) {
7908
            Gui::cmdAppObjectArgs(Obj,
7909
                                  "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
7910
                                  it->first,
7911
                                  it->second);
7912

7913
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
7914

7915
            constrSize = ConStr.size();
7916

7917
            Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", constrSize - 1, "False");
7918
        }
7919

7920
        finishDatumConstraint(this, Obj, false, externalGeoIdDiameterMap.size());
7921

7922
        commitNeeded = true;
7923
        updateNeeded = true;
7924
    }
7925

7926
    if (!geoIdDiameterMap.empty()) {
7927
        if (geoIdDiameterMap.size() > 1 && constraintCreationMode == Driving) {
7928

7929
            int refGeoId = geoIdDiameterMap.front().first;
7930
            double diameter = geoIdDiameterMap.front().second;
7931

7932
            if (!commandopened) {
7933
                openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
7934
            }
7935

7936
            // Add the equality constraints
7937
            for (std::vector<std::pair<int, double>>::iterator it = geoIdDiameterMap.begin() + 1;
7938
                 it != geoIdDiameterMap.end();
7939
                 ++it) {
7940
                Gui::cmdAppObjectArgs(Obj,
7941
                                      "addConstraint(Sketcher.Constraint('Equal',%d,%d))",
7942
                                      refGeoId,
7943
                                      it->first);
7944
            }
7945

7946
            Gui::cmdAppObjectArgs(Obj,
7947
                                  "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
7948
                                  refGeoId,
7949
                                  diameter);
7950
        }
7951
        else {
7952
            // Create the diameter constraints now
7953
            if (!commandopened) {
7954
                openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
7955
            }
7956
            for (std::vector<std::pair<int, double>>::iterator it = geoIdDiameterMap.begin();
7957
                 it != geoIdDiameterMap.end();
7958
                 ++it) {
7959
                Gui::cmdAppObjectArgs(Obj,
7960
                                      "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
7961
                                      it->first,
7962
                                      it->second);
7963

7964
                if (constraintCreationMode == Reference) {
7965

7966
                    const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
7967

7968
                    Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
7969
                }
7970
            }
7971
        }
7972

7973
        finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
7974

7975
        // updateActive();
7976
        getSelection().clearSelection();
7977
    }
7978

7979
    if (commitNeeded) {
7980
        commitCommand();
7981
    }
7982

7983
    if (updateNeeded) {
7984
        tryAutoRecomputeIfNotSolve(Obj);// we have to update the solver after this aborted addition.
7985
    }
7986
}
7987

7988
void CmdSketcherConstrainDiameter::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
7989
{
7990
    SketcherGui::ViewProviderSketch* sketchgui =
7991
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
7992
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
7993

7994
    int GeoId = selSeq.at(0).GeoId;
7995
    double diameter = 0.0;
7996

7997
    bool updateNeeded = false;
7998

7999
    switch (seqIndex) {
8000
        case 0:// {SelEdge}
8001
        case 1:// {SelExternalEdge}
8002
        {
8003
            const Part::Geometry* geom = Obj->getGeometry(GeoId);
8004

8005
            if (geom && isArcOfCircle(*geom)) {
8006
                auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
8007
                diameter = 2 * arc->getRadius();
8008
            }
8009
            else if (geom && isCircle(*geom)) {
8010
                auto circle = static_cast<const Part::GeomCircle*>(geom);
8011
                diameter = 2 * circle->getRadius();
8012
            }
8013
            else {
8014
                Gui::TranslatedUserWarning(
8015
                    Obj,
8016
                    QObject::tr("Wrong selection"),
8017
                    QObject::tr("Constraint only applies to arcs or circles."));
8018
                return;
8019
            }
8020

8021
            if (isBsplinePole(geom)) {
8022
                Gui::TranslatedUserWarning(
8023
                    Obj,
8024
                    QObject::tr("Wrong selection"),
8025
                    QObject::tr("Select an edge that is not a B-spline weight."));
8026
                return;
8027
            }
8028

8029
            // Create the diameter constraint now
8030
            openCommand(QT_TRANSLATE_NOOP("Command", "Add diameter constraint"));
8031
            Gui::cmdAppObjectArgs(Obj,
8032
                                  "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
8033
                                  GeoId,
8034
                                  diameter);
8035

8036
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8037

8038
            bool fixed = isPointOrSegmentFixed(Obj, GeoId);
8039
            if (fixed || constraintCreationMode == Reference) {
8040
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
8041
                updateNeeded = true;// We do need to update the solver DoF after setting the
8042
                                    // constraint driving.
8043
            }
8044

8045
            finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
8046

8047
            // updateActive();
8048
            getSelection().clearSelection();
8049

8050
            commitCommand();
8051

8052
            if (updateNeeded) {
8053
                tryAutoRecomputeIfNotSolve(
8054
                    Obj);// we have to update the solver after this aborted addition.
8055
            }
8056
        }
8057
    }
8058
}
8059

8060
void CmdSketcherConstrainDiameter::updateAction(int mode)
8061
{
8062
    switch (mode) {
8063
        case Reference:
8064
            if (getAction()) {
8065
                getAction()->setIcon(
8066
                    Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
8067
            }
8068
            break;
8069
        case Driving:
8070
            if (getAction()) {
8071
                getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
8072
            }
8073
            break;
8074
    }
8075
}
8076

8077
// ======================================================================================
8078

8079
class CmdSketcherConstrainRadiam: public CmdSketcherConstraint
8080
{
8081
public:
8082
    CmdSketcherConstrainRadiam();
8083
    ~CmdSketcherConstrainRadiam() override
8084
    {}
8085
    void updateAction(int mode) override;
8086
    const char* className() const override
8087
    {
8088
        return "CmdSketcherConstrainRadiam";
8089
    }
8090

8091
protected:
8092
    void activated(int iMsg) override;
8093
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
8094
};
8095

8096
CmdSketcherConstrainRadiam::CmdSketcherConstrainRadiam()
8097
    : CmdSketcherConstraint("Sketcher_ConstrainRadiam")
8098
{
8099
    sAppModule = "Sketcher";
8100
    sGroup = "Sketcher";
8101
    sMenuText = QT_TR_NOOP("Constrain auto radius/diameter");
8102
    sToolTipText = QT_TR_NOOP(
8103
        "Fix the diameter if a circle is chosen, or the radius if an arc/spline pole is chosen");
8104
    sWhatsThis = "Sketcher_ConstrainRadiam";
8105
    sStatusTip = sToolTipText;
8106
    sPixmap = "Constraint_Radiam";
8107
    sAccel = "K, S";
8108
    eType = ForEdit;
8109

8110
    allowedSelSequences = {{SelEdge}, {SelExternalEdge}};
8111
}
8112

8113
void CmdSketcherConstrainRadiam::activated(int iMsg)
8114
{
8115
    Q_UNUSED(iMsg);
8116
    // get the selection
8117
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
8118

8119
    // only one sketch with its subelements are allowed to be selected
8120
    if (selection.size() != 1
8121
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
8122
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
8123
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
8124
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
8125

8126
        if (constraintMode) {
8127
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
8128
            getSelection().clearSelection();
8129
        }
8130
        else {
8131
            // TODO: Get the exact message from git history and put it here
8132
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
8133
                                       QObject::tr("Wrong selection"),
8134
                                       QObject::tr("Select the right things from the sketch."));
8135
        }
8136
        return;
8137
    }
8138

8139
    // get the needed lists and objects
8140
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
8141
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
8142

8143
    if (SubNames.empty()) {
8144
        Gui::TranslatedUserWarning(
8145
            Obj,
8146
            QObject::tr("Wrong selection"),
8147
            QObject::tr("Select one or more arcs or circles from the sketch."));
8148
        return;
8149
    }
8150

8151
    // check for which selected geometry the constraint can be applied
8152
    std::vector<std::pair<int, double>> geoIdRadiamMap;
8153
    std::vector<std::pair<int, double>> externalGeoIdRadiamMap;
8154

8155
    bool poles = false;
8156
    bool nonpoles = false;
8157

8158
    for (auto& subname : SubNames) {
8159
        bool issegmentfixed = false;
8160
        int GeoId;
8161

8162
        if (subname.size() > 4 && subname.substr(0, 4) == "Edge") {
8163
            GeoId = std::atoi(subname.substr(4, 4000).c_str()) - 1;
8164
            issegmentfixed = isPointOrSegmentFixed(Obj, GeoId);
8165
        }
8166
        else if (subname.size() > 4 && subname.substr(0, 12) == "ExternalEdge") {
8167
            GeoId = -std::atoi(subname.substr(12, 4000).c_str()) - 2;
8168
            issegmentfixed = true;
8169
        }
8170
        else {
8171
            continue;
8172
        }
8173

8174
        const Part::Geometry* geom = Obj->getGeometry(GeoId);
8175
        double radius;
8176

8177
        if (geom && isArcOfCircle(*geom)) {
8178
            auto arcir = static_cast<const Part::GeomArcOfCircle*>(geom);
8179
            radius = arcir->getRadius();
8180
            nonpoles = true;
8181
        }
8182
        else if (geom && isCircle(*geom)) {
8183
            auto arcir = static_cast<const Part::GeomCircle*>(geom);
8184
            radius = arcir->getRadius();
8185
            if (isBsplinePole(geom)) {
8186
                poles = true;
8187
            }
8188
            else {
8189
                nonpoles = true;
8190
            }
8191
        }
8192
        else {
8193
            continue;
8194
        }
8195

8196
        if (issegmentfixed) {
8197
            externalGeoIdRadiamMap.emplace_back(GeoId, radius);
8198
        }
8199
        else {
8200
            geoIdRadiamMap.emplace_back(GeoId, radius);
8201
        }
8202
    }
8203

8204
    if (geoIdRadiamMap.empty() && externalGeoIdRadiamMap.empty()) {
8205
        Gui::TranslatedUserWarning(
8206
            Obj,
8207
            QObject::tr("Wrong selection"),
8208
            QObject::tr("Select one or more arcs or circles from the sketch."));
8209
        return;
8210
    }
8211

8212
    if (poles && nonpoles) {
8213
        Gui::TranslatedUserWarning(
8214
            Obj,
8215
            QObject::tr("Wrong selection"),
8216
            QObject::tr("Select either only one or more B-spline poles or only one or more arcs or "
8217
                        "circles from the sketch, but not mixed."));
8218
        return;
8219
    }
8220

8221
    bool commitNeeded = false;
8222
    bool updateNeeded = false;
8223
    bool commandopened = false;
8224

8225
    if (!externalGeoIdRadiamMap.empty()) {
8226
        // Create the non-driving radiam constraints now
8227
        openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
8228
        commandopened = true;
8229
        unsigned int constrSize = 0;
8230

8231
        for (std::vector<std::pair<int, double>>::iterator it = externalGeoIdRadiamMap.begin();
8232
             it != externalGeoIdRadiamMap.end();
8233
             ++it) {
8234
            if (isArcOfCircle(*(Obj->getGeometry(it->first)))) {
8235
                if (nonpoles) {
8236
                    Gui::cmdAppObjectArgs(Obj,
8237
                                          "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
8238
                                          it->first,
8239
                                          it->second);
8240
                }
8241
                else {
8242
                    Gui::cmdAppObjectArgs(Obj,
8243
                                          "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
8244
                                          it->first,
8245
                                          it->second);
8246
                }
8247
            }
8248
            else {
8249
                Gui::cmdAppObjectArgs(Obj,
8250
                                      "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
8251
                                      it->first,
8252
                                      it->second * 2);
8253
            }
8254

8255
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8256

8257
            constrSize = ConStr.size();
8258

8259
            Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", constrSize - 1, "False");
8260
        }
8261

8262
        finishDatumConstraint(this, Obj, false, externalGeoIdRadiamMap.size());
8263

8264
        commitNeeded = true;
8265
        updateNeeded = true;
8266
    }
8267

8268
    if (!geoIdRadiamMap.empty()) {
8269
        if (geoIdRadiamMap.size() > 1 && constraintCreationMode == Driving) {
8270

8271
            int refGeoId = geoIdRadiamMap.front().first;
8272
            double radiam = geoIdRadiamMap.front().second;
8273

8274
            if (!commandopened) {
8275
                openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
8276
            }
8277

8278
            // Add the equality constraints
8279
            for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiamMap.begin() + 1;
8280
                 it != geoIdRadiamMap.end();
8281
                 ++it) {
8282
                Gui::cmdAppObjectArgs(Obj,
8283
                                      "addConstraint(Sketcher.Constraint('Equal',%d,%d))",
8284
                                      refGeoId,
8285
                                      it->first);
8286
            }
8287

8288
            if (poles) {
8289
                Gui::cmdAppObjectArgs(Obj,
8290
                                      "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
8291
                                      refGeoId,
8292
                                      radiam);
8293
            }
8294
            else if (isCircle(*(Obj->getGeometry(refGeoId)))) {
8295
                Gui::cmdAppObjectArgs(Obj,
8296
                                      "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
8297
                                      refGeoId,
8298
                                      radiam * 2);
8299
            }
8300
            else {
8301
                Gui::cmdAppObjectArgs(Obj,
8302
                                      "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
8303
                                      refGeoId,
8304
                                      radiam);
8305
            }
8306
        }
8307
        else {
8308
            // Create the radiam constraints now
8309
            if (!commandopened) {
8310
                openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
8311
            }
8312
            for (std::vector<std::pair<int, double>>::iterator it = geoIdRadiamMap.begin();
8313
                 it != geoIdRadiamMap.end();
8314
                 ++it) {
8315
                if (poles) {
8316
                    Gui::cmdAppObjectArgs(Obj,
8317
                                          "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
8318
                                          it->first,
8319
                                          it->second);
8320
                }
8321
                else if (isCircle(*(Obj->getGeometry(it->first)))){
8322
                    Gui::cmdAppObjectArgs(Obj,
8323
                                          "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
8324
                                          it->first,
8325
                                          it->second * 2);
8326
                }
8327
                else {
8328
                    Gui::cmdAppObjectArgs(Obj,
8329
                                          "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
8330
                                          it->first,
8331
                                          it->second);
8332
                }
8333

8334
                if (constraintCreationMode == Reference) {
8335

8336
                    const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8337

8338
                    Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
8339
                }
8340
            }
8341
        }
8342

8343
        finishDatumConstraint(this, Obj, constraintCreationMode == Driving);
8344

8345
        // updateActive();
8346
        getSelection().clearSelection();
8347
    }
8348

8349
    if (commitNeeded) {
8350
        commitCommand();
8351
    }
8352

8353
    if (updateNeeded) {
8354
        tryAutoRecomputeIfNotSolve(Obj);// we have to update the solver after this aborted addition.
8355
    }
8356
}
8357

8358
void CmdSketcherConstrainRadiam::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
8359
{
8360
    SketcherGui::ViewProviderSketch* sketchgui =
8361
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
8362
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
8363

8364
    int GeoId = selSeq.at(0).GeoId;
8365
    double radiam = 0.0;
8366

8367
    bool updateNeeded = false;
8368

8369
    bool isCircleGeom = false;
8370
    bool isPole = false;
8371

8372
    switch (seqIndex) {
8373
        case 0:// {SelEdge}
8374
        case 1:// {SelExternalEdge}
8375
        {
8376
            const Part::Geometry* geom = Obj->getGeometry(GeoId);
8377

8378
            if (geom && isArcOfCircle(*geom)) {
8379
                auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
8380
                radiam = arc->getRadius();
8381
            }
8382
            else if (geom && isCircle(*geom)) {
8383
                auto circle = static_cast<const Part::GeomCircle*>(geom);
8384
                radiam = circle->getRadius();
8385
                isCircleGeom= true;
8386
                if (isBsplinePole(geom)) {
8387
                    isPole = true;
8388
                }
8389
            }
8390
            else {
8391
                Gui::TranslatedUserWarning(
8392
                    Obj,
8393
                    QObject::tr("Wrong selection"),
8394
                    QObject::tr("Constraint only applies to arcs or circles."));
8395
                return;
8396
            }
8397

8398
            // Create the radiam constraint now
8399
            openCommand(QT_TRANSLATE_NOOP("Command", "Add radiam constraint"));
8400

8401
            if (isPole) {
8402
                Gui::cmdAppObjectArgs(Obj,
8403
                                      "addConstraint(Sketcher.Constraint('Weight',%d,%f))",
8404
                                      GeoId,
8405
                                      radiam);
8406
            }
8407
            else if (isCircleGeom) {
8408
                Gui::cmdAppObjectArgs(Obj,
8409
                                      "addConstraint(Sketcher.Constraint('Diameter',%d,%f))",
8410
                                      GeoId,
8411
                                      radiam * 2);
8412
            }
8413
            else {
8414
                Gui::cmdAppObjectArgs(Obj,
8415
                                      "addConstraint(Sketcher.Constraint('Radius',%d,%f))",
8416
                                      GeoId,
8417
                                      radiam);
8418
            }
8419

8420
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8421

8422
            bool fixed = isPointOrSegmentFixed(Obj, GeoId);
8423
            if (fixed || constraintCreationMode == Reference) {
8424
                Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
8425
                updateNeeded = true;// We do need to update the solver DoF after setting the
8426
                                    // constraint driving.
8427
            }
8428

8429
            finishDatumConstraint(this, Obj, constraintCreationMode == Driving && !fixed);
8430

8431
            // updateActive();
8432
            getSelection().clearSelection();
8433

8434
            commitCommand();
8435

8436
            if (updateNeeded) {
8437
                tryAutoRecomputeIfNotSolve(
8438
                    Obj);// we have to update the solver after this aborted addition.
8439
            }
8440
        }
8441
    }
8442
}
8443

8444
void CmdSketcherConstrainRadiam::updateAction(int mode)
8445
{
8446
    switch (mode) {
8447
        case Reference:
8448
            if (getAction()) {
8449
                getAction()->setIcon(
8450
                    Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
8451
            }
8452
            break;
8453
        case Driving:
8454
            if (getAction()) {
8455
                getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
8456
            }
8457
            break;
8458
    }
8459
}
8460

8461
// ======================================================================================
8462

8463
DEF_STD_CMD_ACLU(CmdSketcherCompConstrainRadDia)
8464

8465
CmdSketcherCompConstrainRadDia::CmdSketcherCompConstrainRadDia()
8466
    : Command("Sketcher_CompConstrainRadDia")
8467
{
8468
    sAppModule = "Sketcher";
8469
    sGroup = "Sketcher";
8470
    sMenuText = QT_TR_NOOP("Constrain arc or circle");
8471
    sToolTipText = QT_TR_NOOP("Constrain an arc or a circle");
8472
    sWhatsThis = "Sketcher_CompConstrainRadDia";
8473
    sStatusTip = sToolTipText;
8474
    sAccel = "R";
8475
    eType = ForEdit;
8476
}
8477

8478
void CmdSketcherCompConstrainRadDia::activated(int iMsg)
8479
{
8480
    Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
8481
    if (iMsg == 0) {
8482
        rcCmdMgr.runCommandByName("Sketcher_ConstrainRadius");
8483
    }
8484
    else if (iMsg == 1) {
8485
        rcCmdMgr.runCommandByName("Sketcher_ConstrainDiameter");
8486
    }
8487
    else if (iMsg == 2) {
8488
        rcCmdMgr.runCommandByName("Sketcher_ConstrainRadiam");
8489
    }
8490
    else {
8491
        return;
8492
    }
8493

8494
    // Save new choice as default
8495
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
8496
        "User parameter:BaseApp/Preferences/Mod/Sketcher");
8497
    hGrp->SetInt("CurRadDiaCons", iMsg);
8498

8499
    // Since the default icon is reset when enabling/disabling the command we have
8500
    // to explicitly set the icon of the used command.
8501
    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
8502
    QList<QAction*> a = pcAction->actions();
8503

8504
    assert(iMsg < a.size());
8505
    pcAction->setIcon(a[iMsg]->icon());
8506
}
8507

8508
Gui::Action* CmdSketcherCompConstrainRadDia::createAction()
8509
{
8510
    Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
8511
    pcAction->setDropDownMenu(true);
8512
    applyCommandData(this->className(), pcAction);
8513

8514
    QAction* arc1 = pcAction->addAction(QString());
8515
    arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
8516
    QAction* arc2 = pcAction->addAction(QString());
8517
    arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
8518
    QAction* arc3 = pcAction->addAction(QString());
8519
    arc3->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
8520

8521
    _pcAction = pcAction;
8522
    languageChange();
8523

8524
    ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
8525
        "User parameter:BaseApp/Preferences/Mod/Sketcher");
8526
    int curRadDiaCons = hGrp->GetInt("CurRadDiaCons", 2);
8527

8528
    switch (curRadDiaCons) {
8529
        case 0:
8530
            pcAction->setIcon(arc1->icon());
8531
            break;
8532
        case 1:
8533
            pcAction->setIcon(arc2->icon());
8534
            break;
8535
        default:
8536
            pcAction->setIcon(arc3->icon());
8537
            curRadDiaCons = 2;
8538
    }
8539
    pcAction->setProperty("defaultAction", QVariant(curRadDiaCons));
8540
    pcAction->setShortcut(QString::fromLatin1(getAccel()));
8541

8542
    return pcAction;
8543
}
8544

8545
void CmdSketcherCompConstrainRadDia::updateAction(int mode)
8546
{
8547
    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(getAction());
8548
    if (!pcAction) {
8549
        return;
8550
    }
8551

8552
    QList<QAction*> a = pcAction->actions();
8553
    int index = pcAction->property("defaultAction").toInt();
8554
    switch (mode) {
8555
        case Reference:
8556
            a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius_Driven"));
8557
            a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter_Driven"));
8558
            a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam_Driven"));
8559
            getAction()->setIcon(a[index]->icon());
8560
            break;
8561
        case Driving:
8562
            a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radius"));
8563
            a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Diameter"));
8564
            a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Constraint_Radiam"));
8565
            getAction()->setIcon(a[index]->icon());
8566
            break;
8567
    }
8568
}
8569

8570
void CmdSketcherCompConstrainRadDia::languageChange()
8571
{
8572
    Command::languageChange();
8573

8574
    if (!_pcAction) {
8575
        return;
8576
    }
8577
    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
8578
    QList<QAction*> a = pcAction->actions();
8579

8580
    QAction* arc1 = a[0];
8581
    arc1->setText(QApplication::translate("CmdSketcherCompConstrainRadDia", "Constrain radius"));
8582
    arc1->setToolTip(QApplication::translate("Sketcher_ConstrainRadius",
8583
                                             "Fix the radius of an arc or a circle"));
8584
    arc1->setStatusTip(QApplication::translate("Sketcher_ConstrainRadius",
8585
                                               "Fix the radius of an arc or a circle"));
8586
    QAction* arc2 = a[1];
8587
    arc2->setText(QApplication::translate("CmdSketcherCompConstrainRadDia", "Constrain diameter"));
8588
    arc2->setToolTip(QApplication::translate("Sketcher_ConstrainDiameter",
8589
                                             "Fix the diameter of a circle or an arc"));
8590
    arc2->setStatusTip(QApplication::translate("Sketcher_ConstrainDiameter",
8591
                                               "Fix the diameter of a circle or an arc"));
8592
    QAction* arc3 = a[2];
8593
    arc3->setText(QApplication::translate("CmdSketcherCompConstrainRadDia",
8594
                                          "Constrain auto radius/diameter"));
8595
    arc3->setToolTip(QApplication::translate("Sketcher_ConstrainRadiam",
8596
                                             "Fix the radius/diameter of an arc or a circle"));
8597
    arc3->setStatusTip(QApplication::translate("Sketcher_ConstrainRadiam",
8598
                                               "Fix the radius/diameter of an arc or a circle"));
8599
}
8600

8601
bool CmdSketcherCompConstrainRadDia::isActive()
8602
{
8603
    return isCommandActive(getActiveGuiDocument());
8604
}
8605

8606
// ======================================================================================
8607

8608
class CmdSketcherConstrainAngle: public CmdSketcherConstraint
8609
{
8610
public:
8611
    CmdSketcherConstrainAngle();
8612
    ~CmdSketcherConstrainAngle() override
8613
    {}
8614
    void updateAction(int mode) override;
8615
    const char* className() const override
8616
    {
8617
        return "CmdSketcherConstrainAngle";
8618
    }
8619

8620
protected:
8621
    void activated(int iMsg) override;
8622
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
8623
};
8624

8625
CmdSketcherConstrainAngle::CmdSketcherConstrainAngle()
8626
    : CmdSketcherConstraint("Sketcher_ConstrainAngle")
8627
{
8628
    sAppModule = "Sketcher";
8629
    sGroup = "Sketcher";
8630
    sMenuText = QT_TR_NOOP("Constrain angle");
8631
    sToolTipText = QT_TR_NOOP("Fix the angle of a line or the angle between two lines");
8632
    sWhatsThis = "Sketcher_ConstrainAngle";
8633
    sStatusTip = sToolTipText;
8634
    sPixmap = "Constraint_InternalAngle";
8635
    sAccel = "K, A";
8636
    eType = ForEdit;
8637

8638
    allowedSelSequences = {{SelEdge, SelEdgeOrAxis},
8639
                           {SelEdgeOrAxis, SelEdge},
8640
                           {SelEdge, SelExternalEdge},
8641
                           {SelExternalEdge, SelEdge},
8642
                           {SelExternalEdge, SelExternalEdge},
8643
                           {SelEdge, SelVertexOrRoot, SelEdgeOrAxis},
8644
                           {SelEdgeOrAxis, SelVertexOrRoot, SelEdge},
8645
                           {SelEdge, SelVertexOrRoot, SelExternalEdge},
8646
                           {SelExternalEdge, SelVertexOrRoot, SelEdge},
8647
                           {SelExternalEdge, SelVertexOrRoot, SelExternalEdge},
8648
                           {SelVertexOrRoot, SelEdge, SelEdgeOrAxis},
8649
                           {SelVertexOrRoot, SelEdgeOrAxis, SelEdge},
8650
                           {SelVertexOrRoot, SelEdge, SelExternalEdge},
8651
                           {SelVertexOrRoot, SelExternalEdge, SelEdge},
8652
                           {SelVertexOrRoot, SelExternalEdge, SelExternalEdge}};
8653
}
8654

8655
void CmdSketcherConstrainAngle::activated(int iMsg)
8656
{
8657
    Q_UNUSED(iMsg);
8658
    // TODO: comprehensive messages, like in CmdSketcherConstrainTangent
8659
    //  get the selection
8660
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
8661

8662
    // only one sketch with its subelements are allowed to be selected
8663
    if (selection.size() != 1
8664
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
8665
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
8666
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
8667
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
8668

8669
        if (constraintMode) {
8670
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
8671
            getSelection().clearSelection();
8672
        }
8673
        else {
8674
            // TODO: Get the exact message from git history and put it here
8675
            Gui::TranslatedUserWarning(getActiveGuiDocument(),
8676
                                       QObject::tr("Wrong selection"),
8677
                                       QObject::tr("Select the right things from the sketch."));
8678
        }
8679
        return;
8680
    }
8681

8682
    // get the needed lists and objects
8683
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
8684
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
8685

8686
    if (SubNames.empty() || SubNames.size() > 3) {
8687
        Gui::TranslatedUserWarning(
8688
            Obj,
8689
            QObject::tr("Wrong selection"),
8690
            QObject::tr(
8691
                "Select one or two lines from the sketch. Or select two edges and a point."));
8692
        return;
8693
    }
8694

8695
    int GeoId1, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
8696
    Sketcher::PointPos PosId1, PosId2 = Sketcher::PointPos::none, PosId3 = Sketcher::PointPos::none;
8697
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
8698
    if (SubNames.size() > 1) {
8699
        getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
8700
    }
8701
    if (SubNames.size() > 2) {
8702
        getIdsFromName(SubNames[2], Obj, GeoId3, PosId3);
8703
    }
8704

8705
    if (SubNames.size() == 3) {// standalone implementation of angle-via-point
8706

8707
        // let's sink the point to be GeoId3. We want to keep the order the two curves have been
8708
        // selected in.
8709
        if (isVertex(GeoId1, PosId1)) {
8710
            std::swap(GeoId1, GeoId2);
8711
            std::swap(PosId1, PosId2);
8712
        }
8713
        if (isVertex(GeoId2, PosId2)) {
8714
            std::swap(GeoId2, GeoId3);
8715
            std::swap(PosId2, PosId3);
8716
        }
8717

8718
        bool bothexternal = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
8719

8720
        if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
8721

8722
            if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
8723
                Gui::TranslatedUserWarning(
8724
                    Obj,
8725
                    QObject::tr("Wrong selection"),
8726
                    QObject::tr("Select an edge that is not a B-spline weight."));
8727
                return;
8728
            }
8729

8730
            double ActAngle = 0.0;
8731

8732
            openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
8733

8734
            // add missing point-on-object constraints
8735
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
8736
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
8737
                if (!(geom1 && isBSplineCurve(*geom1))) {
8738
                    Gui::cmdAppObjectArgs(
8739
                        selection[0].getObject(),
8740
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8741
                        GeoId3,
8742
                        static_cast<int>(PosId3),
8743
                        GeoId1);
8744
                }
8745
            }
8746
            if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
8747
                const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
8748
                if (!(geom2 && isBSplineCurve(*geom2))) {
8749
                    Gui::cmdAppObjectArgs(
8750
                        selection[0].getObject(),
8751
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8752
                        GeoId3,
8753
                        static_cast<int>(PosId3),
8754
                        GeoId2);
8755
                }
8756
            }
8757
            if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
8758
                // FIXME: it's a good idea to add a check if the sketch is solved
8759
                const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
8760
                if (!(geom1 && isBSplineCurve(*geom1))) {
8761
                    Gui::cmdAppObjectArgs(
8762
                        selection[0].getObject(),
8763
                        "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8764
                        GeoId3,
8765
                        static_cast<int>(PosId3),
8766
                        GeoId1);
8767
                }
8768
            }
8769

8770
            // assuming point-on-curves have been solved, calculate the angle.
8771
            // DeepSOIC: this may be slow, but I wanted to reuse the conversion
8772
            // from Geometry to GCS shapes that is done in Sketch
8773
            Base::Vector3d p = Obj->getPoint(GeoId3, PosId3);
8774
            ActAngle = Obj->calculateAngleViaPoint(GeoId1, GeoId2, p.x, p.y);
8775

8776
            // negative constraint value avoidance
8777
            if (ActAngle < -Precision::Angular()) {
8778
                std::swap(GeoId1, GeoId2);
8779
                std::swap(PosId1, PosId2);
8780
                ActAngle = -ActAngle;
8781
            }
8782

8783
            Gui::cmdAppObjectArgs(
8784
                selection[0].getObject(),
8785
                "addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f))",
8786
                GeoId1,
8787
                GeoId2,
8788
                GeoId3,
8789
                static_cast<int>(PosId3),
8790
                ActAngle);
8791

8792
            removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
8793

8794
            if (bothexternal
8795
                || constraintCreationMode
8796
                    == Reference) {// it is a constraint on a external line, make it non-driving
8797
                const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8798

8799
                Gui::cmdAppObjectArgs(selection[0].getObject(),
8800
                                      "setDriving(%d,%s)",
8801
                                      ConStr.size() - 1,
8802
                                      "False");
8803
                finishDatumConstraint(this, Obj, false);
8804
            }
8805
            else {
8806
                finishDatumConstraint(this, Obj, true);
8807
            }
8808

8809
            return;
8810
        }
8811
    }
8812
    else if (SubNames.size() < 3) {
8813

8814
        if (isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2)) {
8815
            std::swap(GeoId1, GeoId2);
8816
            std::swap(PosId1, PosId2);
8817
        }
8818

8819
        if (isBsplinePole(Obj, GeoId1)
8820
            || (GeoId2 != GeoEnum::GeoUndef && isBsplinePole(Obj, GeoId2))) {
8821
            Gui::TranslatedUserWarning(
8822
                Obj,
8823
                QObject::tr("Wrong selection"),
8824
                QObject::tr("Select an edge that is not a B-spline weight."));
8825
            return;
8826
        }
8827

8828
        if (isEdge(GeoId2, PosId2)) {// line to line angle
8829
            makeAngleBetweenTwoLines(Obj, this, GeoId1, GeoId2);
8830
            return;
8831
        }
8832
        else if (isEdge(GeoId1, PosId1)) {// line angle or arc angle
8833
            if (GeoId1 < 0 && GeoId1 >= Sketcher::GeoEnum::VAxis) {
8834
                Gui::TranslatedUserWarning(
8835
                    Obj,
8836
                    QObject::tr("Wrong selection"),
8837
                    QObject::tr("Cannot add an angle constraint on an axis!"));
8838
                return;
8839
            }
8840

8841
            const Part::Geometry* geom = Obj->getGeometry(GeoId1);
8842

8843
            if (isLineSegment(*geom)) {
8844
                auto lineSeg = static_cast<const Part::GeomLineSegment*>(geom);
8845
                Base::Vector3d dir = lineSeg->getEndPoint() - lineSeg->getStartPoint();
8846
                double ActAngle = atan2(dir.y, dir.x);
8847

8848
                openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
8849
                Gui::cmdAppObjectArgs(selection[0].getObject(),
8850
                                      "addConstraint(Sketcher.Constraint('Angle',%d,%f))",
8851
                                      GeoId1,
8852
                                      ActAngle);
8853

8854
                if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
8855
                    // it is a constraint on a external line, make it non-driving
8856
                    const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8857

8858
                    Gui::cmdAppObjectArgs(selection[0].getObject(),
8859
                                          "setDriving(%d,%s)",
8860
                                          ConStr.size() - 1,
8861
                                          "False");
8862
                    finishDatumConstraint(this, Obj, false);
8863
                }
8864
                else {
8865
                    finishDatumConstraint(this, Obj, true);
8866
                }
8867

8868
                return;
8869
            }
8870
            else if (isArcOfCircle(*geom)) {
8871
                auto arc = static_cast<const Part::GeomArcOfCircle*>(geom);
8872
                double angle = arc->getAngle(/*EmulateCCWXY=*/true);
8873

8874
                openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
8875
                Gui::cmdAppObjectArgs(selection[0].getObject(),
8876
                                      "addConstraint(Sketcher.Constraint('Angle',%d,%f))",
8877
                                      GeoId1,
8878
                                      angle);
8879

8880
                if (GeoId1 <= Sketcher::GeoEnum::RefExt || constraintCreationMode == Reference) {
8881
                    // it is a constraint on a external line, make it non-driving
8882
                    const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
8883

8884
                    Gui::cmdAppObjectArgs(selection[0].getObject(),
8885
                                          "setDriving(%d,%s)",
8886
                                          ConStr.size() - 1,
8887
                                          "False");
8888
                    finishDatumConstraint(this, Obj, false);
8889
                }
8890
                else {
8891
                    finishDatumConstraint(this, Obj, true);
8892
                }
8893

8894
                return;
8895
            }
8896
        }
8897
    }
8898

8899
    Gui::TranslatedUserWarning(
8900
        Obj,
8901
        QObject::tr("Wrong selection"),
8902
        QObject::tr("Select one or two lines from the sketch. Or select two edges and a point."));
8903
    return;
8904
}
8905

8906
void CmdSketcherConstrainAngle::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
8907
{
8908
    SketcherGui::ViewProviderSketch* sketchgui =
8909
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
8910
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
8911

8912
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
8913
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
8914
                       PosId3 = Sketcher::PointPos::none;
8915

8916
    switch (seqIndex) {
8917
        case 0:// {SelEdge, SelEdgeOrAxis}
8918
        case 1:// {SelEdgeOrAxis, SelEdge}
8919
        case 2:// {SelEdge, SelExternalEdge}
8920
        case 3:// {SelExternalEdge, SelEdge}
8921
        case 4:// {SelExternalEdge, SelExternalEdge}
8922
        {
8923
            GeoId1 = selSeq.at(0).GeoId;
8924
            GeoId2 = selSeq.at(1).GeoId;
8925

8926
            makeAngleBetweenTwoLines(Obj, this, GeoId1, GeoId2);
8927
            return;
8928
        }
8929
        case 5:// {SelEdge, SelVertexOrRoot, SelEdgeOrAxis}
8930
        case 6:// {SelEdgeOrAxis, SelVertexOrRoot, SelEdge}
8931
        case 7:// {SelEdge, SelVertexOrRoot, SelExternalEdge}
8932
        case 8:// {SelExternalEdge, SelVertexOrRoot, SelEdge}
8933
        case 9:// {SelExternalEdge, SelVertexOrRoot, SelExternalEdge}
8934
        {
8935
            GeoId1 = selSeq.at(0).GeoId;
8936
            GeoId2 = selSeq.at(2).GeoId;
8937
            GeoId3 = selSeq.at(1).GeoId;
8938
            PosId3 = selSeq.at(1).PosId;
8939
            break;
8940
        }
8941
        case 10:// {SelVertexOrRoot, SelEdge, SelEdgeOrAxis}
8942
        case 11:// {SelVertexOrRoot, SelEdgeOrAxis, SelEdge}
8943
        case 12:// {SelVertexOrRoot, SelEdge, SelExternalEdge}
8944
        case 13:// {SelVertexOrRoot, SelExternalEdge, SelEdge}
8945
        case 14:// {SelVertexOrRoot, SelExternalEdge, SelExternalEdge}
8946
        {
8947
            GeoId1 = selSeq.at(1).GeoId;
8948
            GeoId2 = selSeq.at(2).GeoId;
8949
            GeoId3 = selSeq.at(0).GeoId;
8950
            PosId3 = selSeq.at(0).PosId;
8951
            break;
8952
        }
8953
    }
8954

8955
    bool bothexternal = areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2);
8956

8957
    if (isEdge(GeoId1, PosId1) && isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
8958

8959
        if (isBsplinePole(Obj, GeoId1) || isBsplinePole(Obj, GeoId2)) {
8960
            Gui::TranslatedUserWarning(
8961
                Obj,
8962
                QObject::tr("Wrong selection"),
8963
                QObject::tr("Select an edge that is not a B-spline weight."));
8964
            return;
8965
        }
8966

8967
        double ActAngle = 0.0;
8968

8969
        openCommand(QT_TRANSLATE_NOOP("Command", "Add angle constraint"));
8970

8971
        // add missing point-on-object constraints
8972
        if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
8973
            const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
8974
            if (!(geom1 && isBSplineCurve(*geom1))) {
8975
                Gui::cmdAppObjectArgs(Obj,
8976
                                      "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8977
                                      GeoId3,
8978
                                      static_cast<int>(PosId3),
8979
                                      GeoId1);
8980
            }
8981
        }
8982
        if (!IsPointAlreadyOnCurve(GeoId2, GeoId3, PosId3, Obj)) {
8983
            const Part::Geometry *geom2 = Obj->getGeometry(GeoId2);
8984
            if (!(geom2 && isBSplineCurve(*geom2))) {
8985
                Gui::cmdAppObjectArgs(Obj,
8986
                                      "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8987
                                      GeoId3,
8988
                                      static_cast<int>(PosId3),
8989
                                      GeoId2);
8990
            }
8991
        }
8992
        if (!IsPointAlreadyOnCurve(GeoId1, GeoId3, PosId3, Obj)) {
8993
            // FIXME: it's a good idea to add a check if the sketch is solved
8994
            const Part::Geometry *geom1 = Obj->getGeometry(GeoId1);
8995
            if (!(geom1 && isBSplineCurve(*geom1))) {
8996
                Gui::cmdAppObjectArgs(Obj,
8997
                                      "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
8998
                                      GeoId3,
8999
                                      static_cast<int>(PosId3),
9000
                                      GeoId1);
9001
            }
9002
        }
9003

9004
        // assuming point-on-curves have been solved, calculate the angle.
9005
        // DeepSOIC: this may be slow, but I wanted to reuse the conversion
9006
        // from Geometry to GCS shapes that is done in Sketch
9007
        Base::Vector3d p = Obj->getPoint(GeoId3, PosId3);
9008
        ActAngle = Obj->calculateAngleViaPoint(GeoId1, GeoId2, p.x, p.y);
9009

9010
        // negative constraint value avoidance
9011
        if (ActAngle < -Precision::Angular()) {
9012
            std::swap(GeoId1, GeoId2);
9013
            std::swap(PosId1, PosId2);
9014
            ActAngle = -ActAngle;
9015
        }
9016

9017
        Gui::cmdAppObjectArgs(Obj,
9018
                              "addConstraint(Sketcher.Constraint('AngleViaPoint',%d,%d,%d,%d,%f))",
9019
                              GeoId1,
9020
                              GeoId2,
9021
                              GeoId3,
9022
                              static_cast<int>(PosId3),
9023
                              ActAngle);
9024

9025
        removeRedundantPointOnObject(Obj, GeoId1, GeoId2, GeoId3);
9026

9027
        if (bothexternal || constraintCreationMode == Reference) {
9028
            // it is a constraint on a external line, make it non-driving
9029
            const std::vector<Sketcher::Constraint*>& ConStr = Obj->Constraints.getValues();
9030

9031
            Gui::cmdAppObjectArgs(Obj, "setDriving(%d,%s)", ConStr.size() - 1, "False");
9032
            finishDatumConstraint(this, Obj, false);
9033
        }
9034
        else {
9035
            finishDatumConstraint(this, Obj, true);
9036
        }
9037

9038
        return;
9039
    }
9040
}
9041

9042
void CmdSketcherConstrainAngle::updateAction(int mode)
9043
{
9044
    switch (mode) {
9045
        case Reference:
9046
            if (getAction()) {
9047
                getAction()->setIcon(
9048
                    Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle_Driven"));
9049
            }
9050
            break;
9051
        case Driving:
9052
            if (getAction()) {
9053
                getAction()->setIcon(
9054
                    Gui::BitmapFactory().iconFromTheme("Constraint_InternalAngle"));
9055
            }
9056
            break;
9057
    }
9058
}
9059

9060
// ======================================================================================
9061

9062
class CmdSketcherConstrainEqual: public CmdSketcherConstraint
9063
{
9064
public:
9065
    CmdSketcherConstrainEqual();
9066
    ~CmdSketcherConstrainEqual() override
9067
    {}
9068
    const char* className() const override
9069
    {
9070
        return "CmdSketcherConstrainEqual";
9071
    }
9072

9073
protected:
9074
    void activated(int iMsg) override;
9075
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
9076
};
9077

9078
CmdSketcherConstrainEqual::CmdSketcherConstrainEqual()
9079
    : CmdSketcherConstraint("Sketcher_ConstrainEqual")
9080
{
9081
    sAppModule = "Sketcher";
9082
    sGroup = "Sketcher";
9083
    sMenuText = QT_TR_NOOP("Constrain equal");
9084
    sToolTipText =
9085
        QT_TR_NOOP("Create an equality constraint between two lines or between circles and arcs");
9086
    sWhatsThis = "Sketcher_ConstrainEqual";
9087
    sStatusTip = sToolTipText;
9088
    sPixmap = "Constraint_EqualLength";
9089
    sAccel = "E";
9090
    eType = ForEdit;
9091

9092
    allowedSelSequences = {{SelEdge, SelEdge},
9093
                           {SelEdge, SelExternalEdge},
9094
                           {SelExternalEdge, SelEdge}};// Only option for equal constraint
9095
}
9096

9097
void CmdSketcherConstrainEqual::activated(int iMsg)
9098
{
9099
    Q_UNUSED(iMsg);
9100
    // get the selection
9101
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
9102

9103
    // only one sketch with its subelements are allowed to be selected
9104
    if (selection.size() != 1
9105
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
9106
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
9107
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
9108
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
9109

9110
        if (constraintMode) {
9111
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
9112
            getSelection().clearSelection();
9113
        }
9114
        else {
9115
            Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
9116
                                       QObject::tr("Wrong selection"),
9117
                                       QObject::tr("Select two edges from the sketch."));
9118
        }
9119
        return;
9120
    }
9121

9122
    // get the needed lists and objects
9123
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
9124
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
9125

9126
    // go through the selected subelements
9127

9128
    if (SubNames.size() < 2) {
9129
        Gui::TranslatedUserWarning(Obj,
9130
                                   QObject::tr("Wrong selection"),
9131
                                   QObject::tr("Select at least two lines from the sketch."));
9132
        return;
9133
    }
9134

9135
    std::vector<int> ids;
9136
    bool lineSel = false, arcSel = false, circSel = false, ellipsSel = false, arcEllipsSel = false,
9137
         hasAlreadyExternal = false;
9138
    bool hyperbSel = false, parabSel = false, weightSel = false;
9139

9140
    for (auto& subname : SubNames) {
9141

9142
        int GeoId;
9143
        Sketcher::PointPos PosId;
9144
        getIdsFromName(subname, Obj, GeoId, PosId);
9145

9146
        if (!isEdge(GeoId, PosId)) {
9147
            Gui::TranslatedUserWarning(Obj,
9148
                                       QObject::tr("Wrong selection"),
9149
                                       QObject::tr("Select two or more compatible edges."));
9150
            return;
9151
        }
9152
        else if (GeoId == Sketcher::GeoEnum::HAxis || GeoId == Sketcher::GeoEnum::VAxis) {
9153
            Gui::TranslatedUserWarning(
9154
                Obj,
9155
                QObject::tr("Wrong selection"),
9156
                QObject::tr("Sketch axes cannot be used in equality constraints."));
9157
            return;
9158
        }
9159
        else if (isPointOrSegmentFixed(Obj, GeoId)) {
9160

9161
            if (hasAlreadyExternal) {
9162
                showNoConstraintBetweenFixedGeometry(Obj);
9163
                return;
9164
            }
9165
            else {
9166
                hasAlreadyExternal = true;
9167
            }
9168
        }
9169

9170
        const Part::Geometry* geo = Obj->getGeometry(GeoId);
9171

9172
        if (isBSplineCurve(*geo)) {
9173
            // unsupported as they are generally hereogeneus shapes
9174
            Gui::TranslatedUserWarning(
9175
                Obj,
9176
                QObject::tr("Wrong selection"),
9177
                QObject::tr("Equality for B-spline edge currently unsupported."));
9178
            return;
9179
        }
9180

9181
        if (isLineSegment(*geo)) {
9182
            lineSel = true;
9183
        }
9184
        else if (isArcOfCircle(*geo)) {
9185
            arcSel = true;
9186
        }
9187
        else if (isCircle(*geo)) {
9188
            if (isBsplinePole(geo)) {
9189
                weightSel = true;
9190
            }
9191
            else {
9192
                circSel = true;
9193
            }
9194
        }
9195
        else if (isEllipse(*geo)) {
9196
            ellipsSel = true;
9197
        }
9198
        else if (isArcOfEllipse(*geo)) {
9199
            arcEllipsSel = true;
9200
        }
9201
        else if (isArcOfHyperbola(*geo)) {
9202
            hyperbSel = true;
9203
        }
9204
        else if (isArcOfParabola(*geo)) {
9205
            parabSel = true;
9206
        }
9207
        else {
9208
            Gui::TranslatedUserWarning(Obj,
9209
                                       QObject::tr("Wrong selection"),
9210
                                       QObject::tr("Select two or more edges of similar type."));
9211
            return;
9212
        }
9213

9214
        ids.push_back(GeoId);
9215
    }
9216

9217
    // Check for heterogeneous groups in selection
9218
    if ((lineSel
9219
         && ((arcSel || circSel) || (ellipsSel || arcEllipsSel) || hyperbSel || parabSel || weightSel))
9220
        || ((arcSel || circSel) && ((ellipsSel || arcEllipsSel) || hyperbSel || parabSel || weightSel))
9221
        || ((ellipsSel || arcEllipsSel) && (hyperbSel || parabSel || weightSel))
9222
        || (hyperbSel && (parabSel || weightSel)) || (parabSel && weightSel)) {
9223

9224
        Gui::TranslatedUserWarning(Obj,
9225
                                   QObject::tr("Wrong selection"),
9226
                                   QObject::tr("Select two or more edges of similar type."));
9227
        return;
9228
    }
9229

9230
    // undo command open
9231
    openCommand(QT_TRANSLATE_NOOP("Command", "Add equality constraint"));
9232
    for (int i = 0; i < int(ids.size() - 1); i++) {
9233
        Gui::cmdAppObjectArgs(selection[0].getObject(),
9234
                              "addConstraint(Sketcher.Constraint('Equal',%d,%d))",
9235
                              ids[i],
9236
                              ids[i + 1]);
9237
    }
9238
    // finish the transaction and update
9239
    commitCommand();
9240
    tryAutoRecompute(Obj);
9241

9242
    // clear the selection (convenience)
9243
    getSelection().clearSelection();
9244
}
9245

9246
void CmdSketcherConstrainEqual::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
9247
{
9248
    SketcherGui::ViewProviderSketch* sketchgui =
9249
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
9250
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
9251

9252
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef;
9253

9254
    switch (seqIndex) {
9255
        case 0:// {SelEdge, SelEdge}
9256
        case 1:// {SelEdge, SelExternalEdge}
9257
        case 2:// {SelExternalEdge, SelEdge}
9258
        {
9259
            GeoId1 = selSeq.at(0).GeoId;
9260
            GeoId2 = selSeq.at(1).GeoId;
9261

9262
            // check if the edge already has a Block constraint
9263
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
9264
                showNoConstraintBetweenFixedGeometry(Obj);
9265
                return;
9266
            }
9267

9268
            const Part::Geometry* geo1 = Obj->getGeometry(GeoId1);
9269
            const Part::Geometry* geo2 = Obj->getGeometry(GeoId2);
9270

9271
            if ((isLineSegment(*geo1) && ! isLineSegment(*geo2))
9272
                || (isArcOfHyperbola(*geo1) && ! isArcOfHyperbola(*geo2))
9273
                || (isArcOfParabola(*geo1) && ! isArcOfParabola(*geo2))
9274
                || (isBsplinePole(geo1) && !isBsplinePole(geo2))
9275
                || ((isCircle(*geo1) || isArcOfCircle(*geo1)) && !(isCircle(*geo2) || isArcOfCircle(*geo2)))
9276
                || ((isEllipse(*geo1) || isArcOfEllipse(*geo1)) && !(isEllipse(*geo2) || isArcOfEllipse(*geo2)))) {
9277

9278
                Gui::TranslatedUserWarning(
9279
                    Obj,
9280
                    QObject::tr("Wrong selection"),
9281
                    QObject::tr("Select two or more edges of similar type."));
9282
                return;
9283
            }
9284

9285
            // undo command open
9286
            openCommand(QT_TRANSLATE_NOOP("Command", "Add equality constraint"));
9287
            Gui::cmdAppObjectArgs(Obj,
9288
                                  "addConstraint(Sketcher.Constraint('Equal',%d,%d))",
9289
                                  GeoId1,
9290
                                  GeoId2);
9291
            // finish the transaction and update
9292
            commitCommand();
9293
            tryAutoRecompute(Obj);
9294

9295
            return;
9296
        }
9297
        default:
9298
            break;
9299
    }
9300
}
9301

9302
// ======================================================================================
9303

9304
class CmdSketcherConstrainSymmetric: public CmdSketcherConstraint
9305
{
9306
public:
9307
    CmdSketcherConstrainSymmetric();
9308
    ~CmdSketcherConstrainSymmetric() override
9309
    {}
9310
    const char* className() const override
9311
    {
9312
        return "CmdSketcherConstrainSymmetric";
9313
    }
9314

9315
protected:
9316
    void activated(int iMsg) override;
9317
    void applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex) override;
9318
};
9319

9320
CmdSketcherConstrainSymmetric::CmdSketcherConstrainSymmetric()
9321
    : CmdSketcherConstraint("Sketcher_ConstrainSymmetric")
9322
{
9323
    sAppModule = "Sketcher";
9324
    sGroup = "Sketcher";
9325
    sMenuText = QT_TR_NOOP("Constrain symmetric");
9326
    sToolTipText = QT_TR_NOOP("Create a symmetry constraint "
9327
                              "between two points\n"
9328
                              "with respect to a line or a third point");
9329
    sWhatsThis = "Sketcher_ConstrainSymmetric";
9330
    sStatusTip = sToolTipText;
9331
    sPixmap = "Constraint_Symmetric";
9332
    sAccel = "S";
9333
    eType = ForEdit;
9334

9335
    allowedSelSequences = {{SelEdge, SelVertexOrRoot},
9336
                           {SelExternalEdge, SelVertex},
9337
                           {SelVertex, SelEdge, SelVertexOrRoot},
9338
                           {SelRoot, SelEdge, SelVertex},
9339
                           {SelVertex, SelExternalEdge, SelVertexOrRoot},
9340
                           {SelRoot, SelExternalEdge, SelVertex},
9341
                           {SelVertex, SelEdgeOrAxis, SelVertex},
9342
                           {SelVertex, SelVertexOrRoot, SelEdge},
9343
                           {SelRoot, SelVertex, SelEdge},
9344
                           {SelVertex, SelVertexOrRoot, SelExternalEdge},
9345
                           {SelRoot, SelVertex, SelExternalEdge},
9346
                           {SelVertex, SelVertex, SelEdgeOrAxis},
9347
                           {SelVertex, SelVertexOrRoot, SelVertex},
9348
                           {SelVertex, SelVertex, SelVertexOrRoot},
9349
                           {SelVertexOrRoot, SelVertex, SelVertex}};
9350
}
9351

9352
void CmdSketcherConstrainSymmetric::activated(int iMsg)
9353
{
9354
    Q_UNUSED(iMsg);
9355
    // get the selection
9356
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
9357

9358
    // only one sketch with its subelements are allowed to be selected
9359
    if (selection.size() != 1
9360
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
9361
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
9362
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
9363
        bool constraintMode = hGrp->GetBool("ContinuousConstraintMode", true);
9364

9365
        if (constraintMode) {
9366
            ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerGenConstraint>(this));
9367
            getSelection().clearSelection();
9368
        }
9369
        else {
9370
            Gui::TranslatedUserWarning(
9371
                getActiveGuiDocument()->getDocument(),
9372
                QObject::tr("Wrong selection"),
9373
                QObject::tr("Select two points and a symmetry line, "
9374
                            "two points and a symmetry point "
9375
                            "or a line and a symmetry point from the sketch."));
9376
        }
9377
        return;
9378
    }
9379

9380
    // get the needed lists and objects
9381
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
9382
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
9383

9384
    if (SubNames.size() != 3 && SubNames.size() != 2) {
9385
        Gui::TranslatedUserWarning(Obj,
9386
                                   QObject::tr("Wrong selection"),
9387
                                   QObject::tr("Select two points and a symmetry line, "
9388
                                               "two points and a symmetry point "
9389
                                               "or a line and a symmetry point from the sketch."));
9390
        return;
9391
    }
9392

9393
    int GeoId1, GeoId2, GeoId3;
9394
    Sketcher::PointPos PosId1, PosId2, PosId3;
9395
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
9396
    getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
9397

9398
    if (SubNames.size() == 2) {
9399
        if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
9400
            showNoConstraintBetweenFixedGeometry(Obj);
9401
            return;
9402
        }
9403
        if (isVertex(GeoId1, PosId1) && isEdge(GeoId2, PosId2)) {
9404
            std::swap(GeoId1, GeoId2);
9405
            std::swap(PosId1, PosId2);
9406
        }
9407
        if (isEdge(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {
9408
            const Part::Geometry* geom = Obj->getGeometry(GeoId1);
9409

9410
            if (isLineSegment(*geom)) {
9411
                if (GeoId1 == GeoId2) {
9412
                    Gui::TranslatedUserWarning(Obj,
9413
                                               QObject::tr("Wrong selection"),
9414
                                               QObject::tr("Cannot add a symmetry constraint "
9415
                                                           "between a line and its end points."));
9416
                    return;
9417
                }
9418

9419
                // undo command open
9420
                openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
9421
                Gui::cmdAppObjectArgs(
9422
                    selection[0].getObject(),
9423
                    "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
9424
                    GeoId1,
9425
                    static_cast<int>(Sketcher::PointPos::start),
9426
                    GeoId1,
9427
                    static_cast<int>(Sketcher::PointPos::end),
9428
                    GeoId2,
9429
                    static_cast<int>(PosId2));
9430

9431
                // finish the transaction and update
9432
                commitCommand();
9433
                tryAutoRecompute(Obj);
9434

9435
                // clear the selection (convenience)
9436
                getSelection().clearSelection();
9437
                return;
9438
            }
9439
        }
9440

9441
        Gui::TranslatedUserWarning(Obj,
9442
                                   QObject::tr("Wrong selection"),
9443
                                   QObject::tr("Select two points and a symmetry line, "
9444
                                               "two points and a symmetry point "
9445
                                               "or a line and a symmetry point from the sketch."));
9446
        return;
9447
    }
9448

9449
    getIdsFromName(SubNames[2], Obj, GeoId3, PosId3);
9450

9451
    if (isEdge(GeoId1, PosId1) && isVertex(GeoId3, PosId3)) {
9452
        std::swap(GeoId1, GeoId3);
9453
        std::swap(PosId1, PosId3);
9454
    }
9455
    else if (isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
9456
        std::swap(GeoId2, GeoId3);
9457
        std::swap(PosId2, PosId3);
9458
    }
9459

9460
    if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
9461
        showNoConstraintBetweenFixedGeometry(Obj);
9462
        return;
9463
    }
9464

9465
    if (isVertex(GeoId1, PosId1) && isVertex(GeoId2, PosId2)) {
9466
        if (isEdge(GeoId3, PosId3)) {
9467
            const Part::Geometry* geom = Obj->getGeometry(GeoId3);
9468

9469
            if (isLineSegment(*geom)) {
9470
                if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
9471
                    Gui::TranslatedUserWarning(Obj,
9472
                                               QObject::tr("Wrong selection"),
9473
                                               QObject::tr("Cannot add a symmetry constraint "
9474
                                                           "between a line and its end points!"));
9475
                    return;
9476
                }
9477

9478
                // undo command open
9479
                openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
9480
                Gui::cmdAppObjectArgs(
9481
                    selection[0].getObject(),
9482
                    "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d))",
9483
                    GeoId1,
9484
                    static_cast<int>(PosId1),
9485
                    GeoId2,
9486
                    static_cast<int>(PosId2),
9487
                    GeoId3);
9488

9489
                // finish the transaction and update
9490
                commitCommand();
9491
                tryAutoRecompute(Obj);
9492

9493
                // clear the selection (convenience)
9494
                getSelection().clearSelection();
9495
                return;
9496
            }
9497
        }
9498
        else if (isVertex(GeoId3, PosId3)) {
9499
            // undo command open
9500
            openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
9501
            Gui::cmdAppObjectArgs(
9502
                selection[0].getObject(),
9503
                "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
9504
                GeoId1,
9505
                static_cast<int>(PosId1),
9506
                GeoId2,
9507
                static_cast<int>(PosId2),
9508
                GeoId3,
9509
                static_cast<int>(PosId3));
9510

9511
            // finish the transaction and update
9512
            commitCommand();
9513
            tryAutoRecompute(Obj);
9514

9515
            // clear the selection (convenience)
9516
            getSelection().clearSelection();
9517
            return;
9518
        }
9519
    }
9520

9521
    Gui::TranslatedUserWarning(Obj,
9522
                               QObject::tr("Wrong selection"),
9523
                               QObject::tr("Select two points and a symmetry line, "
9524
                                           "two points and a symmetry point "
9525
                                           "or a line and a symmetry point from the sketch."));
9526
}
9527

9528
void CmdSketcherConstrainSymmetric::applyConstraint(std::vector<SelIdPair>& selSeq, int seqIndex)
9529
{
9530
    SketcherGui::ViewProviderSketch* sketchgui =
9531
        static_cast<SketcherGui::ViewProviderSketch*>(getActiveGuiDocument()->getInEdit());
9532
    Sketcher::SketchObject* Obj = sketchgui->getSketchObject();
9533

9534
    int GeoId1 = GeoEnum::GeoUndef, GeoId2 = GeoEnum::GeoUndef, GeoId3 = GeoEnum::GeoUndef;
9535
    Sketcher::PointPos PosId1 = Sketcher::PointPos::none, PosId2 = Sketcher::PointPos::none,
9536
                       PosId3 = Sketcher::PointPos::none;
9537

9538
    switch (seqIndex) {
9539
        case 0:// {SelEdge, SelVertexOrRoot}
9540
        case 1:// {SelExternalEdge, SelVertex}
9541
        {
9542
            GeoId1 = GeoId2 = selSeq.at(0).GeoId;
9543
            GeoId3 = selSeq.at(1).GeoId;
9544
            PosId1 = Sketcher::PointPos::start;
9545
            PosId2 = Sketcher::PointPos::end;
9546
            PosId3 = selSeq.at(1).PosId;
9547

9548
            if (GeoId1 == GeoId3) {
9549
                Gui::TranslatedUserWarning(
9550
                    Obj,
9551
                    QObject::tr("Wrong selection"),
9552
                    QObject::tr(
9553
                        "Cannot add a symmetry constraint between a line and its end points!"));
9554
                return;
9555
            }
9556

9557
            if (areBothPointsOrSegmentsFixed(Obj, GeoId1, GeoId2)) {
9558
                showNoConstraintBetweenFixedGeometry(Obj);
9559
                return;
9560
            }
9561
            break;
9562
        }
9563
        case 2: // {SelVertex, SelEdge, SelVertexOrRoot}
9564
        case 3: // {SelRoot, SelEdge, SelVertex}
9565
        case 4: // {SelVertex, SelExternalEdge, SelVertexOrRoot}
9566
        case 5: // {SelRoot, SelExternalEdge, SelVertex}
9567
        case 6: // {SelVertex, SelEdgeOrAxis, SelVertex}
9568
        case 7: // {SelVertex, SelVertexOrRoot,SelEdge}
9569
        case 8: // {SelRoot, SelVertex, SelEdge}
9570
        case 9: // {SelVertex, SelVertexOrRoot, SelExternalEdge}
9571
        case 10:// {SelRoot, SelVertex, SelExternalEdge}
9572
        case 11:// {SelVertex, SelVertex, SelEdgeOrAxis}
9573
        {
9574
            GeoId1 = selSeq.at(0).GeoId;
9575
            GeoId2 = selSeq.at(2).GeoId;
9576
            GeoId3 = selSeq.at(1).GeoId;
9577
            PosId1 = selSeq.at(0).PosId;
9578
            PosId2 = selSeq.at(2).PosId;
9579
            PosId3 = selSeq.at(1).PosId;
9580

9581
            if (isEdge(GeoId1, PosId1) && isVertex(GeoId3, PosId3)) {
9582
                std::swap(GeoId1, GeoId3);
9583
                std::swap(PosId1, PosId3);
9584
            }
9585
            else if (isEdge(GeoId2, PosId2) && isVertex(GeoId3, PosId3)) {
9586
                std::swap(GeoId2, GeoId3);
9587
                std::swap(PosId2, PosId3);
9588
            }
9589

9590
            if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
9591
                showNoConstraintBetweenFixedGeometry(Obj);
9592
                return;
9593
            }
9594

9595
            const Part::Geometry* geom = Obj->getGeometry(GeoId3);
9596

9597
            if (isLineSegment(*geom)) {
9598
                if (GeoId1 == GeoId2 && GeoId2 == GeoId3) {
9599
                    Gui::TranslatedUserWarning(Obj,
9600
                                               QObject::tr("Wrong selection"),
9601
                                               QObject::tr("Cannot add a symmetry constraint "
9602
                                                           "between a line and its end points."));
9603
                    return;
9604
                }
9605

9606
                // undo command open
9607
                openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
9608
                Gui::cmdAppObjectArgs(
9609
                    Obj,
9610
                    "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d))",
9611
                    GeoId1,
9612
                    static_cast<int>(PosId1),
9613
                    GeoId2,
9614
                    static_cast<int>(PosId2),
9615
                    GeoId3);
9616

9617
                // finish the transaction and update
9618
                commitCommand();
9619
                tryAutoRecompute(Obj);
9620
            }
9621
            else {
9622
                Gui::TranslatedUserWarning(
9623
                    Obj,
9624
                    QObject::tr("Wrong selection"),
9625
                    QObject::tr("Select two points and a symmetry line, "
9626
                                "two points and a symmetry point "
9627
                                "or a line and a symmetry point from the sketch."));
9628
            }
9629
            return;
9630
        }
9631
        case 12:// {SelVertex, SelVertexOrRoot, SelVertex}
9632
        case 13:// {SelVertex, SelVertex, SelVertexOrRoot}
9633
        case 14:// {SelVertexOrRoot, SelVertex, SelVertex}
9634
        {
9635
            GeoId1 = selSeq.at(0).GeoId;
9636
            GeoId2 = selSeq.at(1).GeoId;
9637
            GeoId3 = selSeq.at(2).GeoId;
9638
            PosId1 = selSeq.at(0).PosId;
9639
            PosId2 = selSeq.at(1).PosId;
9640
            PosId3 = selSeq.at(2).PosId;
9641

9642
            if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
9643
                showNoConstraintBetweenFixedGeometry(Obj);
9644
                return;
9645
            }
9646
            break;
9647
        }
9648
        default:
9649
            break;
9650
    }
9651

9652
    // undo command open
9653
    openCommand(QT_TRANSLATE_NOOP("Command", "Add symmetric constraint"));
9654
    Gui::cmdAppObjectArgs(Obj,
9655
                          "addConstraint(Sketcher.Constraint('Symmetric',%d,%d,%d,%d,%d,%d))",
9656
                          GeoId1,
9657
                          static_cast<int>(PosId1),
9658
                          GeoId2,
9659
                          static_cast<int>(PosId2),
9660
                          GeoId3,
9661
                          static_cast<int>(PosId3));
9662

9663
    // finish the transaction and update
9664
    commitCommand();
9665

9666
    tryAutoRecompute(Obj);
9667

9668
    // clear the selection (convenience)
9669
    getSelection().clearSelection();
9670
    return;
9671
}
9672

9673
// ======================================================================================
9674

9675
DEF_STD_CMD_A(CmdSketcherConstrainSnellsLaw)
9676

9677
CmdSketcherConstrainSnellsLaw::CmdSketcherConstrainSnellsLaw()
9678
    : Command("Sketcher_ConstrainSnellsLaw")
9679
{
9680
    sAppModule = "Sketcher";
9681
    sGroup = "Sketcher";
9682
    sMenuText = QT_TR_NOOP("Constrain refraction (Snell's law)");
9683
    sToolTipText = QT_TR_NOOP("Create a refraction law (Snell's law)"
9684
                              "constraint between two endpoints of rays\n"
9685
                              "and an edge as an interface.");
9686
    sWhatsThis = "Sketcher_ConstrainSnellsLaw";
9687
    sStatusTip = sToolTipText;
9688
    sPixmap = "Constraint_SnellsLaw";
9689
    sAccel = "K, W";
9690
    eType = ForEdit;
9691
}
9692

9693
void CmdSketcherConstrainSnellsLaw::activated(int iMsg)
9694
{
9695
    Q_UNUSED(iMsg);
9696

9697
    // get the selection
9698
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
9699

9700
    // only one sketch with its subelements are allowed to be selected
9701
    if (selection.size() != 1
9702
        || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
9703
        QString strHelp = QObject::tr("Select two endpoints of lines to act as rays, "
9704
                                      "and an edge representing a boundary. "
9705
                                      "The first selected point corresponds "
9706
                                      "to index n1, second to n2, "
9707
                                      "and the value sets the ratio n2/n1.",
9708
                                      "Constraint_SnellsLaw");
9709

9710
        const char dmbg[] = "Constraint_SnellsLaw";
9711

9712
        QString strError = QObject::tr("Selected objects are not just geometry "
9713
                                       "from one sketch.",
9714
                                       dmbg);
9715

9716
        strError.append(strHelp);
9717
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
9718
                                   QObject::tr("Wrong selection"),
9719
                                   std::move(strError));
9720
    }
9721

9722
    // get the needed lists and objects
9723
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
9724
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
9725

9726
    if (SubNames.size() != 3) {
9727
        Gui::TranslatedUserWarning(Obj,
9728
                                   QObject::tr("Wrong selection"),
9729
                                   QObject::tr("Number of selected objects is not 3"));
9730
        return;
9731
    }
9732

9733
    int GeoId1, GeoId2, GeoId3;
9734
    Sketcher::PointPos PosId1, PosId2, PosId3;
9735
    getIdsFromName(SubNames[0], Obj, GeoId1, PosId1);
9736
    getIdsFromName(SubNames[1], Obj, GeoId2, PosId2);
9737
    getIdsFromName(SubNames[2], Obj, GeoId3, PosId3);
9738

9739
    // sink the edge to be the last item
9740
    if (isEdge(GeoId1, PosId1)) {
9741
        std::swap(GeoId1, GeoId2);
9742
        std::swap(PosId1, PosId2);
9743
    }
9744
    if (isEdge(GeoId2, PosId2)) {
9745
        std::swap(GeoId2, GeoId3);
9746
        std::swap(PosId2, PosId3);
9747
    }
9748

9749
    // a bunch of validity checks
9750
    if (areAllPointsOrSegmentsFixed(Obj, GeoId1, GeoId2, GeoId3)) {
9751
        Gui::TranslatedUserWarning(
9752
            Obj,
9753
            QObject::tr("Wrong selection"),
9754
            QObject::tr("Cannot create constraint with external geometry only."));
9755
        return;
9756
    }
9757

9758
    if (!(isVertex(GeoId1, PosId1) && !isSimpleVertex(Obj, GeoId1, PosId1)
9759
          && isVertex(GeoId2, PosId2) && !isSimpleVertex(Obj, GeoId2, PosId2)
9760
          && isEdge(GeoId3, PosId3))) {
9761

9762
        Gui::TranslatedUserWarning(Obj,
9763
                                   QObject::tr("Wrong selection"),
9764
                                   QObject::tr("Incompatible geometry is selected."));
9765
        return;
9766
    }
9767

9768
    const Part::Geometry* geo = Obj->getGeometry(GeoId3);
9769

9770
    // if (geo && isBSplineCurve(*geo)) {
9771
    //     // unsupported until normal to B-spline at any point implemented.
9772
    //     Gui::TranslatedUserWarning(
9773
    //         Obj,
9774
    //         QObject::tr("Wrong selection"),
9775
    //         QObject::tr("SnellsLaw on B-spline edge is currently unsupported."));
9776
    //     return;
9777
    // }
9778

9779
    if (isBsplinePole(geo)) {
9780
        Gui::TranslatedUserWarning(Obj,
9781
                                   QObject::tr("Wrong selection"),
9782
                                   QObject::tr("Select an edge that is not a B-spline weight."));
9783
        return;
9784
    }
9785

9786
    double n2divn1 = 0;
9787

9788
    // the essence.
9789
    // Unlike other constraints, we'll ask for a value immediately.
9790
    QDialog dlg(Gui::getMainWindow());
9791
    Ui::InsertDatum ui_Datum;
9792
    ui_Datum.setupUi(&dlg);
9793
    dlg.setWindowTitle(EditDatumDialog::tr("Refractive index ratio"));
9794
    ui_Datum.label->setText(EditDatumDialog::tr("Ratio n2/n1:"));
9795
    Base::Quantity init_val;
9796
    init_val.setUnit(Base::Unit());
9797
    init_val.setValue(0.0);
9798

9799
    ui_Datum.labelEdit->setValue(init_val);
9800
    ui_Datum.labelEdit->setParamGrpPath(
9801
        QByteArray("User parameter:BaseApp/History/SketcherRefrIndexRatio"));
9802
    ui_Datum.labelEdit->setEntryName(QByteArray("DatumValue"));
9803
    ui_Datum.labelEdit->setToLastUsedValue();
9804
    ui_Datum.labelEdit->selectNumber();
9805
    ui_Datum.labelEdit->setSingleStep(0.05);
9806
    // Unable to bind, because the constraint does not yet exist
9807

9808
    if (dlg.exec() != QDialog::Accepted) {
9809
        return;
9810
    }
9811
    ui_Datum.labelEdit->pushToHistory();
9812

9813
    Base::Quantity newQuant = ui_Datum.labelEdit->value();
9814
    n2divn1 = newQuant.getValue();
9815

9816
    // add constraint
9817
    openCommand(QT_TRANSLATE_NOOP("Command", "Add Snell's law constraint"));
9818

9819
    bool safe = addConstraintSafely(Obj, [&]() {
9820
        if (!IsPointAlreadyOnCurve(GeoId2, GeoId1, PosId1, Obj)) {
9821
            Gui::cmdAppObjectArgs(selection[0].getObject(),
9822
                                  "addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d))",
9823
                                  GeoId1,
9824
                                  static_cast<int>(PosId1),
9825
                                  GeoId2,
9826
                                  static_cast<int>(PosId2));
9827
        }
9828

9829
        if (!IsPointAlreadyOnCurve(GeoId3, GeoId1, PosId1, Obj)) {
9830
            Gui::cmdAppObjectArgs(selection[0].getObject(),
9831
                                  "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d))",
9832
                                  GeoId1,
9833
                                  static_cast<int>(PosId1),
9834
                                  GeoId3);
9835
        }
9836

9837
        Gui::cmdAppObjectArgs(
9838
            selection[0].getObject(),
9839
            "addConstraint(Sketcher.Constraint('SnellsLaw',%d,%d,%d,%d,%d,%.12f))",
9840
            GeoId1,
9841
            static_cast<int>(PosId1),
9842
            GeoId2,
9843
            static_cast<int>(PosId2),
9844
            GeoId3,
9845
            n2divn1);
9846

9847
        /*if (allexternal || constraintCreationMode==Reference) { // it is a constraint on a
9848
        external line, make it non-driving const std::vector<Sketcher::Constraint *> &ConStr =
9849
        Obj->Constraints.getValues();
9850

9851
            Gui::cmdAppObjectArgs(selection[0].getObject(),"setDriving(%i,%s)",
9852
                ConStr.size()-1,"False");
9853
        }*/
9854
    });
9855

9856
    if (!safe) {
9857
        return;
9858
    }
9859
    else {
9860
        commitCommand();
9861
        tryAutoRecompute(Obj);
9862
    }
9863

9864
    // clear the selection (convenience)
9865
    getSelection().clearSelection();
9866
}
9867

9868
bool CmdSketcherConstrainSnellsLaw::isActive()
9869
{
9870
    return isCreateConstraintActive(getActiveGuiDocument());
9871
}
9872

9873
// ======================================================================================
9874
DEF_STD_CMD_A(CmdSketcherChangeDimensionConstraint)
9875

9876
CmdSketcherChangeDimensionConstraint::CmdSketcherChangeDimensionConstraint()
9877
    : Command("Sketcher_ChangeDimensionConstraint")
9878
{
9879
    sAppModule = "Sketcher";
9880
    sGroup = "Sketcher";
9881
    sMenuText = QT_TR_NOOP("Change value");
9882
    sToolTipText = QT_TR_NOOP("Change the value of a dimensional constraint");
9883
    sWhatsThis = "Sketcher_ChangeDimensionConstraint";
9884
    sStatusTip = sToolTipText;
9885
    eType = ForEdit;
9886
}
9887

9888
void CmdSketcherChangeDimensionConstraint::activated(int iMsg)
9889
{
9890
    Q_UNUSED(iMsg);
9891

9892
    auto getDimConstraint = []() {
9893
        std::vector<Gui::SelectionObject> selection{getSelection().getSelectionEx()};
9894
        if (selection.size() != 1 || selection[0].getSubNames().size() != 1) {
9895
            throw Base::RuntimeError();
9896
        }
9897

9898
        if (auto sketch = dynamic_cast<Sketcher::SketchObject*>(selection[0].getObject())) {
9899
            std::string subName = selection[0].getSubNames().at(0);
9900
            if (subName.size() > 10 && subName.substr(0, 10) == "Constraint") {
9901
                int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subName);
9902
                return std::make_tuple(sketch, ConstrId);
9903
            }
9904
        }
9905

9906
        throw Base::RuntimeError();
9907
    };
9908

9909
    try {
9910
        auto value = getDimConstraint();
9911
        EditDatumDialog editDatumDialog(std::get<0>(value), std::get<1>(value));
9912
        editDatumDialog.exec(false);
9913
    }
9914
    catch (const Base::RuntimeError&) {
9915
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
9916
                                   QObject::tr("Wrong selection"),
9917
                                   QObject::tr("Select one dimensional constraint from the sketch."));
9918
    }
9919
}
9920

9921
bool CmdSketcherChangeDimensionConstraint::isActive()
9922
{
9923
    return isCommandActive(getActiveGuiDocument());
9924
}
9925

9926
// ======================================================================================
9927
/*** Creation Mode / Toggle to or from Reference ***/
9928
DEF_STD_CMD_AU(CmdSketcherToggleDrivingConstraint)
9929

9930
CmdSketcherToggleDrivingConstraint::CmdSketcherToggleDrivingConstraint()
9931
    : Command("Sketcher_ToggleDrivingConstraint")
9932
{
9933
    sAppModule = "Sketcher";
9934
    sGroup = "Sketcher";
9935
    sMenuText = QT_TR_NOOP("Toggle driving/reference constraint");
9936
    sToolTipText = QT_TR_NOOP("Set the toolbar, "
9937
                              "or the selected constraints,\n"
9938
                              "into driving or reference mode");
9939
    sWhatsThis = "Sketcher_ToggleDrivingConstraint";
9940
    sStatusTip = sToolTipText;
9941
    sPixmap = "Sketcher_ToggleConstraint";
9942
    sAccel = "K, X";
9943
    eType = ForEdit;
9944

9945
    // list of toggle driving constraint commands
9946
    Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
9947
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainLock");
9948
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistance");
9949
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistanceX");
9950
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDistanceY");
9951
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainRadius");
9952
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainDiameter");
9953
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainRadiam");
9954
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainAngle");
9955
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_CompConstrainRadDia");
9956
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_Dimension");
9957
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_CompDimensionTools");
9958
    rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ToggleDrivingConstraint");
9959
    // rcCmdMgr.addCommandMode("ToggleDrivingConstraint", "Sketcher_ConstrainSnellsLaw");
9960
}
9961

9962
void CmdSketcherToggleDrivingConstraint::updateAction(int mode)
9963
{
9964
    auto act = getAction();
9965
    if (act) {
9966
        switch (static_cast<ConstraintCreationMode>(mode)) {
9967
        case ConstraintCreationMode::Driving:
9968
            act->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_ToggleConstraint"));
9969
            break;
9970
        case ConstraintCreationMode::Reference:
9971
            act->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_ToggleConstraint_Driven"));
9972
            break;
9973
        }
9974
    }
9975
}
9976

9977
void CmdSketcherToggleDrivingConstraint::activated(int iMsg)
9978
{
9979
    Q_UNUSED(iMsg);
9980
    bool modeChange = true;
9981

9982
    std::vector<Gui::SelectionObject> selection;
9983

9984
    if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId()) > 0) {
9985
        // Now we check whether we have a constraint selected or not.
9986

9987
        // get the selection
9988
        selection = getSelection().getSelectionEx();
9989

9990
        // only one sketch with its subelements are allowed to be selected
9991
        if (selection.size() != 1
9992
            || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
9993
            Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
9994
                                       QObject::tr("Wrong selection"),
9995
                                       QObject::tr("Select constraints from the sketch."));
9996
            return;
9997
        }
9998

9999
        Sketcher::SketchObject* Obj =
10000
            static_cast<Sketcher::SketchObject*>(selection[0].getObject());
10001

10002
        // get the needed lists and objects
10003
        const std::vector<std::string>& SubNames = selection[0].getSubNames();
10004
        if (SubNames.empty()) {
10005
            Gui::TranslatedUserWarning(Obj,
10006
                                       QObject::tr("Wrong selection"),
10007
                                       QObject::tr("Select constraints from the sketch."));
10008
            return;
10009
        }
10010

10011
        for (auto& subname : SubNames) {
10012
            // see if we have constraints, if we do it is not a mode change, but a toggle.
10013
            if (subname.size() > 10 && subname.substr(0, 10) == "Constraint") {
10014
                modeChange = false;
10015
            }
10016
        }
10017
    }
10018

10019
    if (modeChange) {
10020
        // Here starts the code for mode change
10021
        Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
10022

10023
        if (constraintCreationMode == Driving) {
10024
            constraintCreationMode = Reference;
10025
        }
10026
        else {
10027
            constraintCreationMode = Driving;
10028
        }
10029

10030
        rcCmdMgr.updateCommands("ToggleDrivingConstraint",
10031
                                static_cast<int>(constraintCreationMode));
10032
    }
10033
    else// toggle the selected constraint(s)
10034
    {
10035
        Sketcher::SketchObject* Obj =
10036
            static_cast<Sketcher::SketchObject*>(selection[0].getObject());
10037

10038
        // get the needed lists and objects
10039
        const std::vector<std::string>& SubNames = selection[0].getSubNames();
10040
        if (SubNames.empty()) {
10041
            Gui::TranslatedUserWarning(Obj,
10042
                                       QObject::tr("Wrong selection"),
10043
                                       QObject::tr("Select constraints from the sketch."));
10044
            return;
10045
        }
10046

10047
        // undo command open
10048
        openCommand(QT_TRANSLATE_NOOP("Command", "Toggle constraint to driving/reference"));
10049

10050
        int successful = SubNames.size();
10051
        // go through the selected subelements
10052
        for (auto& subname : SubNames) {
10053
            // only handle constraints
10054
            if (subname.size() > 10 && subname.substr(0, 10) == "Constraint") {
10055
                int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subname);
10056
                try {
10057
                    // issue the actual commands to toggle
10058
                    Gui::cmdAppObjectArgs(selection[0].getObject(), "toggleDriving(%d)", ConstrId);
10059
                }
10060
                catch (const Base::Exception&) {
10061
                    successful--;
10062
                }
10063
            }
10064
        }
10065

10066
        if (successful > 0) {
10067
            commitCommand();
10068
        }
10069
        else {
10070
            abortCommand();
10071
        }
10072

10073
        tryAutoRecompute(Obj);
10074

10075
        // clear the selection (convenience)
10076
        getSelection().clearSelection();
10077
    }
10078
}
10079

10080
bool CmdSketcherToggleDrivingConstraint::isActive()
10081
{
10082
    return isCommandActive(getActiveGuiDocument());
10083
}
10084

10085
DEF_STD_CMD_A(CmdSketcherToggleActiveConstraint)
10086

10087
CmdSketcherToggleActiveConstraint::CmdSketcherToggleActiveConstraint()
10088
    : Command("Sketcher_ToggleActiveConstraint")
10089
{
10090
    sAppModule = "Sketcher";
10091
    sGroup = "Sketcher";
10092
    sMenuText = QT_TR_NOOP("Activate/deactivate constraint");
10093
    sToolTipText = QT_TR_NOOP("Activates or deactivates "
10094
                              "the selected constraints");
10095
    sWhatsThis = "Sketcher_ToggleActiveConstraint";
10096
    sStatusTip = sToolTipText;
10097
    sPixmap = "Sketcher_ToggleActiveConstraint";
10098
    sAccel = "K, Z";
10099
    eType = ForEdit;
10100
}
10101

10102
void CmdSketcherToggleActiveConstraint::activated(int iMsg)
10103
{
10104
    Q_UNUSED(iMsg);
10105

10106
    std::vector<Gui::SelectionObject> selection;
10107

10108
    if (Gui::Selection().countObjectsOfType(Sketcher::SketchObject::getClassTypeId()) > 0) {
10109
        // Now we check whether we have a constraint selected or not.
10110

10111
        // get the selection
10112
        selection = getSelection().getSelectionEx();
10113

10114
        // only one sketch with its subelements are allowed to be selected
10115
        if (selection.size() != 1
10116
            || !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
10117
            Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
10118
                                       QObject::tr("Wrong selection"),
10119
                                       QObject::tr("Select constraints from the sketch."));
10120
            return;
10121
        }
10122

10123
        Sketcher::SketchObject* Obj =
10124
            static_cast<Sketcher::SketchObject*>(selection[0].getObject());
10125

10126
        // get the needed lists and objects
10127
        const std::vector<std::string>& SubNames = selection[0].getSubNames();
10128
        if (SubNames.empty()) {
10129
            Gui::TranslatedUserWarning(Obj,
10130
                                       QObject::tr("Wrong selection"),
10131
                                       QObject::tr("Select constraints from the sketch."));
10132
            return;
10133
        }
10134

10135
        // undo command open
10136
        openCommand(QT_TRANSLATE_NOOP("Command", "Activate/Deactivate constraints"));
10137

10138
        int successful = SubNames.size();
10139

10140
        for (auto& subname : SubNames) {
10141

10142
            if (subname.size() > 10 && subname.substr(0, 10) == "Constraint") {
10143
                int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(subname);
10144
                try {
10145
                    // issue the actual commands to toggle
10146
                    Gui::cmdAppObjectArgs(selection[0].getObject(), "toggleActive(%d)", ConstrId);
10147
                }
10148
                catch (const Base::Exception&) {
10149
                    successful--;
10150
                }
10151
            }
10152
        }
10153

10154
        if (successful > 0) {
10155
            commitCommand();
10156
        }
10157
        else {
10158
            abortCommand();
10159
        }
10160

10161
        tryAutoRecompute(Obj);
10162

10163
        // clear the selection (convenience)
10164
        getSelection().clearSelection();
10165
    }
10166
}
10167

10168
bool CmdSketcherToggleActiveConstraint::isActive()
10169
{
10170
    return isCreateConstraintActive(getActiveGuiDocument());
10171
}
10172

10173
void CreateSketcherCommandsConstraints()
10174
{
10175
    Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
10176

10177
    rcCmdMgr.addCommand(new CmdSketcherConstrainHorizontal());
10178
    rcCmdMgr.addCommand(new CmdSketcherConstrainVertical());
10179
    rcCmdMgr.addCommand(new CmdSketcherConstrainHorVer());
10180
    rcCmdMgr.addCommand(new CmdSketcherCompHorizontalVertical());
10181
    rcCmdMgr.addCommand(new CmdSketcherConstrainLock());
10182
    rcCmdMgr.addCommand(new CmdSketcherConstrainBlock());
10183
    rcCmdMgr.addCommand(new CmdSketcherConstrainCoincidentUnified());
10184
    rcCmdMgr.addCommand(new CmdSketcherConstrainCoincident());
10185
    rcCmdMgr.addCommand(new CmdSketcherDimension());
10186
    rcCmdMgr.addCommand(new CmdSketcherConstrainParallel());
10187
    rcCmdMgr.addCommand(new CmdSketcherConstrainPerpendicular());
10188
    rcCmdMgr.addCommand(new CmdSketcherConstrainTangent());
10189
    rcCmdMgr.addCommand(new CmdSketcherConstrainDistance());
10190
    rcCmdMgr.addCommand(new CmdSketcherConstrainDistanceX());
10191
    rcCmdMgr.addCommand(new CmdSketcherConstrainDistanceY());
10192
    rcCmdMgr.addCommand(new CmdSketcherConstrainRadius());
10193
    rcCmdMgr.addCommand(new CmdSketcherConstrainDiameter());
10194
    rcCmdMgr.addCommand(new CmdSketcherConstrainRadiam());
10195
    rcCmdMgr.addCommand(new CmdSketcherCompConstrainRadDia());
10196
    rcCmdMgr.addCommand(new CmdSketcherConstrainAngle());
10197
    rcCmdMgr.addCommand(new CmdSketcherConstrainEqual());
10198
    rcCmdMgr.addCommand(new CmdSketcherConstrainPointOnObject());
10199
    rcCmdMgr.addCommand(new CmdSketcherConstrainSymmetric());
10200
    rcCmdMgr.addCommand(new CmdSketcherConstrainSnellsLaw());
10201
    rcCmdMgr.addCommand(new CmdSketcherChangeDimensionConstraint());
10202
    rcCmdMgr.addCommand(new CmdSketcherToggleDrivingConstraint());
10203
    rcCmdMgr.addCommand(new CmdSketcherToggleActiveConstraint());
10204
    rcCmdMgr.addCommand(new CmdSketcherCompDimensionTools());
10205
    rcCmdMgr.addCommand(new CmdSketcherCompConstrainTools());
10206
    rcCmdMgr.addCommand(new CmdSketcherCompToggleConstraints());
10207
}
10208
// clang-format on
10209

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

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

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

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