FreeCAD

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

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <cfloat>
26
#include <memory>
27

28
#include <QApplication>
29
#include <QClipboard>
30
#include <QMessageBox>
31

32
#include <Inventor/SbString.h>
33
#endif
34

35
#include <App/Application.h>
36
#include <Base/Console.h>
37
#include <Base/Reader.h>
38
#include <Base/Writer.h>
39
#include <Gui/Action.h>
40
#include <Gui/Application.h>
41
#include <Gui/BitmapFactory.h>
42
#include <Gui/CommandT.h>
43
#include <Gui/Document.h>
44
#include <Gui/MainWindow.h>
45
#include <Gui/Notifications.h>
46
#include <Gui/Selection.h>
47
#include <Gui/SelectionObject.h>
48
#include <Mod/Sketcher/App/PythonConverter.h>
49
#include <Mod/Sketcher/App/SketchObject.h>
50
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
51

52
#include "DrawSketchHandler.h"
53
#include "SketchRectangularArrayDialog.h"
54
#include "Utils.h"
55
#include "ViewProviderSketch.h"
56

57
#include "DrawSketchHandlerTranslate.h"
58
#include "DrawSketchHandlerOffset.h"
59
#include "DrawSketchHandlerRotate.h"
60
#include "DrawSketchHandlerScale.h"
61
#include "DrawSketchHandlerSymmetry.h"
62

63
// Hint: this is to prevent to re-format big parts of the file. Remove it later again.
64
// clang-format off
65
using namespace std;
66
using namespace SketcherGui;
67
using namespace Sketcher;
68

69
std::vector<int> getListOfSelectedGeoIds(bool forceInternalSelection)
70
{
71
    std::vector<int> listOfGeoIds = {};
72

73
    // get the selection
74
    std::vector<Gui::SelectionObject> selection;
75
    selection = Gui::Selection().getSelectionEx(0, Sketcher::SketchObject::getClassTypeId());
76

77
    // only one sketch with its subelements are allowed to be selected
78
    if (selection.size() != 1) {
79
        QMessageBox::warning(Gui::getMainWindow(),
80
            QObject::tr("Wrong selection"),
81
            QObject::tr("Select elements from a single sketch."));
82
        return {};
83
    }
84

85
    // get the needed lists and objects
86
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
87
    const std::vector<std::string>& subNames = selection[0].getSubNames();
88
    if (!subNames.empty()) {
89

90
        for (auto& name : subNames) {
91
            // only handle non-external edges
92
            if (name.size() > 4 && name.substr(0, 4) == "Edge") {
93
                int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1;
94
                if (geoId >= 0) {
95
                    listOfGeoIds.push_back(geoId);
96
                }
97
            }
98
            else if (name.size() > 6 && name.substr(0, 6) == "Vertex") {
99
                // only if it is a GeomPoint
100
                int VtId = std::atoi(name.substr(6, 4000).c_str()) - 1;
101
                int geoId;
102
                Sketcher::PointPos PosId;
103
                Obj->getGeoVertexIndex(VtId, geoId, PosId);
104
                if (isPoint(*Obj->getGeometry(geoId))) {
105
                    if (geoId >= 0) {
106
                        listOfGeoIds.push_back(geoId);
107
                    }
108
                }
109
            }
110
        }
111
    }
112

113
    if (forceInternalSelection) {
114
        size_t loopSize = listOfGeoIds.size();
115
        for (size_t i = 0; i < loopSize; i++) {
116
            const Part::Geometry* geo = Obj->getGeometry(listOfGeoIds[i]);
117
            if (isEllipse(*geo) || isArcOfEllipse(*geo) || isArcOfHyperbola(*geo) || isArcOfParabola(*geo) || isBSplineCurve(*geo)) {
118
                const std::vector<Sketcher::Constraint*>& constraints = Obj->Constraints.getValues();
119
                for (auto constr : constraints) {
120
                    if (constr->Type == InternalAlignment && constr->Second == listOfGeoIds[i]) {
121
                        if (std::find(listOfGeoIds.begin(), listOfGeoIds.end(), constr->First) == listOfGeoIds.end()) {
122
                            // If the value is not found, add it to the vector
123
                            listOfGeoIds.push_back(constr->First);
124
                        }
125
                    }
126
                }
127
            }
128
        }
129
    }
130

131
    if (listOfGeoIds.empty()) {
132
        Gui::NotifyUserError(Obj,
133
            QT_TRANSLATE_NOOP("Notifications", "Invalid selection"),
134
            QT_TRANSLATE_NOOP("Notifications", "Selection has no valid geometries."));
135
    }
136

137
    return listOfGeoIds;
138
}
139

140
Sketcher::SketchObject* getSketchObject()
141
{
142
    Gui::Document* doc = Gui::Application::Instance->activeDocument();
143
    ReleaseHandler(doc);
144
    auto* vp = static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
145
    return vp->getSketchObject();
146
}
147

148
// ================================================================================
149

150
// Copy
151

152
bool copySelectionToClipboard(Sketcher::SketchObject* obj) {
153
    std::vector<int> listOfGeoId = getListOfSelectedGeoIds(true);
154
    if (listOfGeoId.empty()) { return false; }
155
    sort(listOfGeoId.begin(), listOfGeoId.end());
156

157
    //Export selected geometries as a formatted string.
158
    std::vector<Part::Geometry*> shapeGeometry;
159
    for (auto geoId : listOfGeoId) {
160
        Part::Geometry* geoNew = obj->getGeometry(geoId)->copy();
161
        shapeGeometry.push_back(geoNew);
162
    }
163
    std::string geosAsStr = Sketcher::PythonConverter::convert(
164
        "objectStr",
165
        shapeGeometry,
166
        Sketcher::PythonConverter::Mode::OmitInternalGeometry);
167

168
    // Export constraints of selected geos.
169
    std::vector<Sketcher::Constraint*> shapeConstraints;
170
    for (auto constr : obj->Constraints.getValues()) {
171

172
        auto isSelectedGeoOrAxis = [](const std::vector<int>& vec, int value) {
173
            return (std::find(vec.begin(), vec.end(), value) != vec.end())
174
                || value == GeoEnum::GeoUndef || value == GeoEnum::RtPnt
175
                || value == GeoEnum::VAxis || value == GeoEnum::HAxis;
176
        };
177

178
        if (!isSelectedGeoOrAxis(listOfGeoId, constr->First)
179
            || !isSelectedGeoOrAxis(listOfGeoId, constr->Second)
180
            || !isSelectedGeoOrAxis(listOfGeoId, constr->Third)) {
181
            continue;
182
        }
183

184
        Constraint* temp = constr->copy();
185
        for (size_t j = 0; j < listOfGeoId.size(); j++) {
186
            if (temp->First == listOfGeoId[j]) {
187
                temp->First = j;
188
            }
189
            if (temp->Second == listOfGeoId[j]) {
190
                temp->Second = j;
191
            }
192
            if (temp->Third == listOfGeoId[j]) {
193
                temp->Third = j;
194
            }
195
        }
196
        shapeConstraints.push_back(temp);
197
    }
198
    std::string cstrAsStr = Sketcher::PythonConverter::convert("objectStr", shapeConstraints, Sketcher::PythonConverter::GeoIdMode::AddLastGeoIdToGeoIds);
199

200
    std::string exportedData = "# Copied from sketcher. From:\n#objectStr = " + Gui::Command::getObjectCmd(obj) + "\n"
201
        + geosAsStr + "\n" + cstrAsStr;
202

203
    if (!exportedData.empty()) {
204
        QClipboard* clipboard = QGuiApplication::clipboard();
205
        clipboard->setText(QString::fromStdString(exportedData));
206
        return true;
207
    }
208
    return false;
209
}
210

211
DEF_STD_CMD_A(CmdSketcherCopyClipboard)
212

213
CmdSketcherCopyClipboard::CmdSketcherCopyClipboard()
214
    : Command("Sketcher_CopyClipboard")
215
{
216
    sAppModule = "Sketcher";
217
    sGroup = "Sketcher";
218
    sMenuText = QT_TR_NOOP("C&opy in sketcher");
219
    sToolTipText = QT_TR_NOOP("Copy selected geometries and constraints to the clipboard");
220
    sWhatsThis = "Sketcher_CopyClipboard";
221
    sStatusTip = sToolTipText;
222
    sPixmap = "edit-copy";
223
    sAccel = keySequenceToAccel(QKeySequence::Copy);
224
    eType = ForEdit;
225
}
226

227
void CmdSketcherCopyClipboard::activated(int iMsg)
228
{
229
    Q_UNUSED(iMsg);
230
    copySelectionToClipboard(getSketchObject());
231
}
232

233
bool CmdSketcherCopyClipboard::isActive()
234
{
235
    return isCommandActive(getActiveGuiDocument(), true);
236
}
237

238
// ================================================================================
239

240
// Cut
241

242
DEF_STD_CMD_A(CmdSketcherCut)
243

244
CmdSketcherCut::CmdSketcherCut()
245
    : Command("Sketcher_Cut")
246
{
247
    sAppModule = "Sketcher";
248
    sGroup = "Sketcher";
249
    sMenuText = QT_TR_NOOP("C&ut in sketcher");
250
    sToolTipText = QT_TR_NOOP("Cut selected geometries and constraints to the clipboard");
251
    sWhatsThis = "Sketcher_Cut";
252
    sStatusTip = sToolTipText;
253
    sPixmap = "edit-cut";
254
    sAccel = keySequenceToAccel(QKeySequence::Cut);
255
    eType = ForEdit;
256
}
257

258
void CmdSketcherCut::activated(int iMsg)
259
{
260
    Q_UNUSED(iMsg);
261
    if (copySelectionToClipboard(getSketchObject())) {
262

263
        Gui::Document* doc = getActiveGuiDocument();
264
        ReleaseHandler(doc);
265
        auto* vp = static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
266

267
        Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Cut in Sketcher"));
268
        vp->deleteSelected();
269
        Gui::Command::commitCommand();
270
    }
271
}
272

273
bool CmdSketcherCut::isActive()
274
{
275
    return isCommandActive(getActiveGuiDocument(), true);
276
}
277

278
// ================================================================================
279

280
// Paste
281

282
DEF_STD_CMD_A(CmdSketcherPaste)
283

284
CmdSketcherPaste::CmdSketcherPaste()
285
    : Command("Sketcher_Paste")
286
{
287
    sAppModule = "Sketcher";
288
    sGroup = "Sketcher";
289
    sMenuText = QT_TR_NOOP("P&aste in sketcher");
290
    sToolTipText = QT_TR_NOOP("Paste selected geometries and constraints from the clipboard");
291
    sWhatsThis = "Sketcher_Paste";
292
    sStatusTip = sToolTipText;
293
    sPixmap = "edit-paste";
294
    sAccel = keySequenceToAccel(QKeySequence::Paste);
295
    eType = ForEdit;
296
}
297

298
void CmdSketcherPaste::activated(int iMsg)
299
{
300
    Q_UNUSED(iMsg);
301
    Gui::Document* doc = getActiveGuiDocument();
302
    ReleaseHandler(doc);
303
    auto* vp = static_cast<SketcherGui::ViewProviderSketch*>(doc->getInEdit());
304
    Sketcher::SketchObject* obj = vp->getSketchObject();
305

306
    std::string data = QGuiApplication::clipboard()->text().toStdString();
307
    if (data.find("# Copied from sketcher.", 0) == std::string::npos) {
308
        return;
309
    }
310
    data = "objectStr = " + Gui::Command::getObjectCmd(obj) +"\n" + data;
311

312
    Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Paste in Sketcher"));
313

314
    Gui::Command::doCommand(Gui::Command::Doc, data.c_str());
315

316
    obj->solve(true);
317
    vp->draw(false, false);
318

319
    Gui::Command::commitCommand();
320
}
321

322
bool CmdSketcherPaste::isActive()
323
{
324
    return isCommandActive(getActiveGuiDocument(), false);
325
}
326

327
// ================================================================================
328

329
// Select Constraints of selected elements
330
DEF_STD_CMD_A(CmdSketcherSelectConstraints)
331

332
CmdSketcherSelectConstraints::CmdSketcherSelectConstraints()
333
    : Command("Sketcher_SelectConstraints")
334
{
335
    sAppModule = "Sketcher";
336
    sGroup = "Sketcher";
337
    sMenuText = QT_TR_NOOP("Select associated constraints");
338
    sToolTipText =
339
        QT_TR_NOOP("Select the constraints associated with the selected geometrical elements");
340
    sWhatsThis = "Sketcher_SelectConstraints";
341
    sStatusTip = sToolTipText;
342
    sPixmap = "Sketcher_SelectConstraints";
343
    sAccel = "Z, K";
344
    eType = ForEdit;
345
}
346

347
void CmdSketcherSelectConstraints::activated(int iMsg)
348
{
349
    Q_UNUSED(iMsg);
350

351
    // get the selection
352
    std::vector<Gui::SelectionObject> selection;
353
    selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());
354

355
    // Cancel any in-progress operation
356
    Gui::Document* doc = Gui::Application::Instance->activeDocument();
357
    SketcherGui::ReleaseHandler(doc);
358

359
    // only one sketch with its subelements are allowed to be selected
360
    if (selection.size() != 1) {
361
        Gui::TranslatedUserWarning(doc->getDocument(),
362
                                   QObject::tr("Wrong selection"),
363
                                   QObject::tr("Select elements from a single sketch."));
364

365
        return;
366
    }
367

368
    // get the needed lists and objects
369
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
370
    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
371
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
372

373
    std::string doc_name = Obj->getDocument()->getName();
374
    std::string obj_name = Obj->getNameInDocument();
375

376
    getSelection().clearSelection();
377

378
    std::vector<std::string> constraintSubNames;
379
    // go through the selected subelements
380
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
381
         ++it) {
382
        // only handle edges
383
        if (it->size() > 4 && it->substr(0, 4) == "Edge") {
384
            int GeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
385

386
            // push all the constraints
387
            int i = 0;
388
            for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin();
389
                 it != vals.end();
390
                 ++it, ++i) {
391
                if ((*it)->First == GeoId || (*it)->Second == GeoId || (*it)->Third == GeoId) {
392
                    constraintSubNames.push_back(
393
                        Sketcher::PropertyConstraintList::getConstraintName(i));
394
                }
395
            }
396
        }
397
    }
398

399
    if (!constraintSubNames.empty())
400
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
401
}
402

403
bool CmdSketcherSelectConstraints::isActive()
404
{
405
    return isCommandActive(getActiveGuiDocument(), true);
406
}
407

408
// ================================================================================
409

410
// Select Origin
411
DEF_STD_CMD_A(CmdSketcherSelectOrigin)
412

413
CmdSketcherSelectOrigin::CmdSketcherSelectOrigin()
414
    : Command("Sketcher_SelectOrigin")
415
{
416
    sAppModule = "Sketcher";
417
    sGroup = "Sketcher";
418
    sMenuText = QT_TR_NOOP("Select origin");
419
    sToolTipText = QT_TR_NOOP("Select the local origin point of the sketch");
420
    sWhatsThis = "Sketcher_SelectOrigin";
421
    sStatusTip = sToolTipText;
422
    sPixmap = "Sketcher_SelectOrigin";
423
    sAccel = "Z, O";
424
    eType = ForEdit;
425
}
426

427
void CmdSketcherSelectOrigin::activated(int iMsg)
428
{
429
    Q_UNUSED(iMsg);
430
    Sketcher::SketchObject * Obj = getSketchObject();
431
    //    ViewProviderSketch * vp = static_cast<ViewProviderSketch
432
    //    *>(Gui::Application::Instance->getViewProvider(docobj)); Sketcher::SketchObject* Obj =
433
    //    vp->getSketchObject();
434

435
    std::string doc_name = Obj->getDocument()->getName();
436
    std::string obj_name = Obj->getNameInDocument();
437
    std::stringstream ss;
438

439
    ss << "RootPoint";
440

441
    if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()))
442
        Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
443
    else
444
        Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
445
}
446

447
bool CmdSketcherSelectOrigin::isActive()
448
{
449
    return isCommandActive(getActiveGuiDocument(), false);
450
}
451

452
// ================================================================================
453

454
// Select Vertical Axis
455
DEF_STD_CMD_A(CmdSketcherSelectVerticalAxis)
456

457
CmdSketcherSelectVerticalAxis::CmdSketcherSelectVerticalAxis()
458
    : Command("Sketcher_SelectVerticalAxis")
459
{
460
    sAppModule = "Sketcher";
461
    sGroup = "Sketcher";
462
    sMenuText = QT_TR_NOOP("Select vertical axis");
463
    sToolTipText = QT_TR_NOOP("Select the local vertical axis of the sketch");
464
    sWhatsThis = "Sketcher_SelectVerticalAxis";
465
    sStatusTip = sToolTipText;
466
    sPixmap = "Sketcher_SelectVerticalAxis";
467
    sAccel = "Z, V";
468
    eType = ForEdit;
469
}
470

471
void CmdSketcherSelectVerticalAxis::activated(int iMsg)
472
{
473
    Q_UNUSED(iMsg);
474
    Sketcher::SketchObject* Obj = getSketchObject();
475

476
    std::string doc_name = Obj->getDocument()->getName();
477
    std::string obj_name = Obj->getNameInDocument();
478
    std::stringstream ss;
479

480
    ss << "V_Axis";
481

482
    if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()))
483
        Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
484
    else
485
        Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
486
}
487

488
bool CmdSketcherSelectVerticalAxis::isActive()
489
{
490
    return isCommandActive(getActiveGuiDocument(), false);
491
}
492

493
// ================================================================================
494

495
// Select Horizontal Axis
496
DEF_STD_CMD_A(CmdSketcherSelectHorizontalAxis)
497

498
CmdSketcherSelectHorizontalAxis::CmdSketcherSelectHorizontalAxis()
499
    : Command("Sketcher_SelectHorizontalAxis")
500
{
501
    sAppModule = "Sketcher";
502
    sGroup = "Sketcher";
503
    sMenuText = QT_TR_NOOP("Select horizontal axis");
504
    sToolTipText = QT_TR_NOOP("Select the local horizontal axis of the sketch");
505
    sWhatsThis = "Sketcher_SelectHorizontalAxis";
506
    sStatusTip = sToolTipText;
507
    sPixmap = "Sketcher_SelectHorizontalAxis";
508
    sAccel = "Z, H";
509
    eType = ForEdit;
510
}
511

512
void CmdSketcherSelectHorizontalAxis::activated(int iMsg)
513
{
514
    Q_UNUSED(iMsg);
515
    Sketcher::SketchObject* Obj = getSketchObject();
516

517
    std::string doc_name = Obj->getDocument()->getName();
518
    std::string obj_name = Obj->getNameInDocument();
519
    std::stringstream ss;
520

521
    ss << "H_Axis";
522

523
    if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()))
524
        Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
525
    else
526
        Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str());
527
}
528

529
bool CmdSketcherSelectHorizontalAxis::isActive()
530
{
531
    return isCommandActive(getActiveGuiDocument(), false);
532
}
533

534
// ================================================================================
535

536
DEF_STD_CMD_A(CmdSketcherSelectRedundantConstraints)
537

538
CmdSketcherSelectRedundantConstraints::CmdSketcherSelectRedundantConstraints()
539
    : Command("Sketcher_SelectRedundantConstraints")
540
{
541
    sAppModule = "Sketcher";
542
    sGroup = "Sketcher";
543
    sMenuText = QT_TR_NOOP("Select redundant constraints");
544
    sToolTipText = QT_TR_NOOP("Select redundant constraints");
545
    sWhatsThis = "Sketcher_SelectRedundantConstraints";
546
    sStatusTip = sToolTipText;
547
    sPixmap = "Sketcher_SelectRedundantConstraints";
548
    sAccel = "Z, P, R";
549
    eType = ForEdit;
550
}
551

552
void CmdSketcherSelectRedundantConstraints::activated(int iMsg)
553
{
554
    Q_UNUSED(iMsg);
555
    Sketcher::SketchObject* Obj = getSketchObject();
556

557
    std::string doc_name = Obj->getDocument()->getName();
558
    std::string obj_name = Obj->getNameInDocument();
559

560
    // get the needed lists and objects
561
    const std::vector<int>& solverredundant = Obj->getLastRedundant();
562
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
563

564
    getSelection().clearSelection();
565

566
    // push the constraints
567
    std::vector<std::string> constraintSubNames;
568

569
    int i = 0;
570
    for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin(); it != vals.end();
571
         ++it, ++i) {
572
        for (std::vector<int>::const_iterator itc = solverredundant.begin();
573
             itc != solverredundant.end();
574
             ++itc) {
575
            if ((*itc) - 1 == i) {
576
                constraintSubNames.push_back(
577
                    Sketcher::PropertyConstraintList::getConstraintName(i));
578
                break;
579
            }
580
        }
581
    }
582

583
    if (!constraintSubNames.empty())
584
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
585
}
586

587
bool CmdSketcherSelectRedundantConstraints::isActive()
588
{
589
    return isCommandActive(getActiveGuiDocument(), false);
590
}
591

592
// ================================================================================
593

594
DEF_STD_CMD_A(CmdSketcherSelectMalformedConstraints)
595

596
CmdSketcherSelectMalformedConstraints::CmdSketcherSelectMalformedConstraints()
597
    : Command("Sketcher_SelectMalformedConstraints")
598
{
599
    sAppModule = "Sketcher";
600
    sGroup = "Sketcher";
601
    sMenuText = QT_TR_NOOP("Select malformed constraints");
602
    sToolTipText = QT_TR_NOOP("Select malformed constraints");
603
    sWhatsThis = "Sketcher_SelectMalformedConstraints";
604
    sStatusTip = sToolTipText;
605
    eType = ForEdit;
606
}
607

608
void CmdSketcherSelectMalformedConstraints::activated(int iMsg)
609
{
610
    Q_UNUSED(iMsg);
611
    Sketcher::SketchObject* Obj = getSketchObject();
612

613
    std::string doc_name = Obj->getDocument()->getName();
614
    std::string obj_name = Obj->getNameInDocument();
615

616
    // get the needed lists and objects
617
    const std::vector<int>& solvermalformed = Obj->getLastMalformedConstraints();
618
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
619

620
    getSelection().clearSelection();
621

622
    // push the constraints
623
    std::vector<std::string> constraintSubNames;
624
    int i = 0;
625
    for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin(); it != vals.end();
626
         ++it, ++i) {
627
        for (std::vector<int>::const_iterator itc = solvermalformed.begin();
628
             itc != solvermalformed.end();
629
             ++itc) {
630
            if ((*itc) - 1 == i) {
631
                constraintSubNames.push_back(
632
                    Sketcher::PropertyConstraintList::getConstraintName(i));
633
                break;
634
            }
635
        }
636
    }
637

638
    if (!constraintSubNames.empty())
639
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
640
}
641

642
bool CmdSketcherSelectMalformedConstraints::isActive()
643
{
644
    return isCommandActive(getActiveGuiDocument(), false);
645
}
646

647
// ================================================================================
648

649
DEF_STD_CMD_A(CmdSketcherSelectPartiallyRedundantConstraints)
650

651
CmdSketcherSelectPartiallyRedundantConstraints::CmdSketcherSelectPartiallyRedundantConstraints()
652
    : Command("Sketcher_SelectPartiallyRedundantConstraints")
653
{
654
    sAppModule = "Sketcher";
655
    sGroup = "Sketcher";
656
    sMenuText = QT_TR_NOOP("Select partially redundant constraints");
657
    sToolTipText = QT_TR_NOOP("Select partially redundant constraints");
658
    sWhatsThis = "Sketcher_SelectPartiallyRedundantConstraints";
659
    sStatusTip = sToolTipText;
660
    eType = ForEdit;
661
}
662

663
void CmdSketcherSelectPartiallyRedundantConstraints::activated(int iMsg)
664
{
665
    Q_UNUSED(iMsg);
666
    Sketcher::SketchObject* Obj = getSketchObject();
667

668
    std::string doc_name = Obj->getDocument()->getName();
669
    std::string obj_name = Obj->getNameInDocument();
670

671
    // get the needed lists and objects
672
    const std::vector<int>& solverpartiallyredundant =
673
        Obj->getLastPartiallyRedundant();
674
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
675

676
    getSelection().clearSelection();
677

678
    // push the constraints
679
    std::vector<std::string> constraintSubNames;
680
    int i = 0;
681
    for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin(); it != vals.end();
682
         ++it, ++i) {
683
        for (std::vector<int>::const_iterator itc = solverpartiallyredundant.begin();
684
             itc != solverpartiallyredundant.end();
685
             ++itc) {
686
            if ((*itc) - 1 == i) {
687
                constraintSubNames.push_back(
688
                    Sketcher::PropertyConstraintList::getConstraintName(i));
689
                break;
690
            }
691
        }
692
    }
693

694
    if (!constraintSubNames.empty())
695
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
696
}
697

698
bool CmdSketcherSelectPartiallyRedundantConstraints::isActive()
699
{
700
    return isCommandActive(getActiveGuiDocument(), false);
701
}
702

703
// ================================================================================
704

705
DEF_STD_CMD_A(CmdSketcherSelectConflictingConstraints)
706

707
CmdSketcherSelectConflictingConstraints::CmdSketcherSelectConflictingConstraints()
708
    : Command("Sketcher_SelectConflictingConstraints")
709
{
710
    sAppModule = "Sketcher";
711
    sGroup = "Sketcher";
712
    sMenuText = QT_TR_NOOP("Select conflicting constraints");
713
    sToolTipText = QT_TR_NOOP("Select conflicting constraints");
714
    sWhatsThis = "Sketcher_SelectConflictingConstraints";
715
    sStatusTip = sToolTipText;
716
    sPixmap = "Sketcher_SelectConflictingConstraints";
717
    sAccel = "Z, P, C";
718
    eType = ForEdit;
719
}
720

721
void CmdSketcherSelectConflictingConstraints::activated(int iMsg)
722
{
723
    Q_UNUSED(iMsg);
724
    Sketcher::SketchObject* Obj = getSketchObject();
725

726
    std::string doc_name = Obj->getDocument()->getName();
727
    std::string obj_name = Obj->getNameInDocument();
728

729
    // get the needed lists and objects
730
    const std::vector<int>& solverconflicting = Obj->getLastConflicting();
731
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
732

733
    getSelection().clearSelection();
734

735
    // push the constraints
736
    std::vector<std::string> constraintSubNames;
737
    int i = 0;
738
    for (std::vector<Sketcher::Constraint*>::const_iterator it = vals.begin(); it != vals.end();
739
         ++it, ++i) {
740
        for (std::vector<int>::const_iterator itc = solverconflicting.begin();
741
             itc != solverconflicting.end();
742
             ++itc) {
743
            if ((*itc) - 1 == i) {
744
                constraintSubNames.push_back(
745
                    Sketcher::PropertyConstraintList::getConstraintName(i));
746
                break;
747
            }
748
        }
749
    }
750

751
    if (!constraintSubNames.empty())
752
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames);
753
}
754

755
bool CmdSketcherSelectConflictingConstraints::isActive()
756
{
757
    return isCommandActive(getActiveGuiDocument(), false);
758
}
759

760
// ================================================================================
761

762
DEF_STD_CMD_A(CmdSketcherSelectElementsAssociatedWithConstraints)
763

764
CmdSketcherSelectElementsAssociatedWithConstraints::
765
    CmdSketcherSelectElementsAssociatedWithConstraints()
766
    : Command("Sketcher_SelectElementsAssociatedWithConstraints")
767
{
768
    sAppModule = "Sketcher";
769
    sGroup = "Sketcher";
770
    sMenuText = QT_TR_NOOP("Select associated geometry");
771
    sToolTipText =
772
        QT_TR_NOOP("Select the geometrical elements associated with the selected constraints");
773
    sWhatsThis = "Sketcher_SelectElementsAssociatedWithConstraints";
774
    sStatusTip = sToolTipText;
775
    sPixmap = "Sketcher_SelectElementsAssociatedWithConstraints";
776
    sAccel = "Z, E";
777
    eType = ForEdit;
778
}
779

780
void CmdSketcherSelectElementsAssociatedWithConstraints::activated(int iMsg)
781
{
782
    Q_UNUSED(iMsg);
783
    std::vector<Gui::SelectionObject> selection = Gui::Selection().getSelectionEx();
784
    Sketcher::SketchObject* Obj = getSketchObject();
785

786
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
787
    const std::vector<Sketcher::Constraint*>& vals = Obj->Constraints.getValues();
788

789
    getSelection().clearSelection();
790

791
    std::string doc_name = Obj->getDocument()->getName();
792
    std::string obj_name = Obj->getNameInDocument();
793
    std::stringstream ss;
794

795
    std::vector<std::string> elementSubNames;
796
    // go through the selected subelements
797
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
798
         ++it) {
799
        // only handle constraints
800
        if (it->size() > 10 && it->substr(0, 10) == "Constraint") {
801
            int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it);
802

803
            if (ConstrId < static_cast<int>(vals.size())) {
804
                if (vals[ConstrId]->First != GeoEnum::GeoUndef) {
805
                    ss.str(std::string());
806

807
                    switch (vals[ConstrId]->FirstPos) {
808
                        case Sketcher::PointPos::none:
809
                            ss << "Edge" << vals[ConstrId]->First + 1;
810
                            break;
811
                        case Sketcher::PointPos::start:
812
                        case Sketcher::PointPos::end:
813
                        case Sketcher::PointPos::mid:
814
                            int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->First,
815
                                                                   vals[ConstrId]->FirstPos);
816
                            if (vertex > -1)
817
                                ss << "Vertex" << vertex + 1;
818
                            break;
819
                    }
820
                    elementSubNames.push_back(ss.str());
821
                }
822

823
                if (vals[ConstrId]->Second != GeoEnum::GeoUndef) {
824
                    ss.str(std::string());
825

826
                    switch (vals[ConstrId]->SecondPos) {
827
                        case Sketcher::PointPos::none:
828
                            ss << "Edge" << vals[ConstrId]->Second + 1;
829
                            break;
830
                        case Sketcher::PointPos::start:
831
                        case Sketcher::PointPos::end:
832
                        case Sketcher::PointPos::mid:
833
                            int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->Second,
834
                                                                   vals[ConstrId]->SecondPos);
835
                            if (vertex > -1)
836
                                ss << "Vertex" << vertex + 1;
837
                            break;
838
                    }
839

840
                    elementSubNames.push_back(ss.str());
841
                }
842

843
                if (vals[ConstrId]->Third != GeoEnum::GeoUndef) {
844
                    ss.str(std::string());
845

846
                    switch (vals[ConstrId]->ThirdPos) {
847
                        case Sketcher::PointPos::none:
848
                            ss << "Edge" << vals[ConstrId]->Third + 1;
849
                            break;
850
                        case Sketcher::PointPos::start:
851
                        case Sketcher::PointPos::end:
852
                        case Sketcher::PointPos::mid:
853
                            int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->Third,
854
                                                                   vals[ConstrId]->ThirdPos);
855
                            if (vertex > -1)
856
                                ss << "Vertex" << vertex + 1;
857
                            break;
858
                    }
859

860
                    elementSubNames.push_back(ss.str());
861
                }
862
            }
863
        }
864
    }
865

866
    if (elementSubNames.empty()) {
867
        Gui::TranslatedUserWarning(Obj,
868
                                   QObject::tr("No constraint selected"),
869
                                   QObject::tr("At least one constraint must be selected"));
870
    }
871
    else {
872
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), elementSubNames);
873
    }
874
}
875

876
bool CmdSketcherSelectElementsAssociatedWithConstraints::isActive()
877
{
878
    return isCommandActive(getActiveGuiDocument(), true);
879
}
880

881
// ================================================================================
882

883
DEF_STD_CMD_A(CmdSketcherSelectElementsWithDoFs)
884

885
CmdSketcherSelectElementsWithDoFs::CmdSketcherSelectElementsWithDoFs()
886
    : Command("Sketcher_SelectElementsWithDoFs")
887
{
888
    sAppModule = "Sketcher";
889
    sGroup = "Sketcher";
890
    sMenuText = QT_TR_NOOP("Select under-constrained elements");
891
    sToolTipText = QT_TR_NOOP("Select geometrical elements where the solver still detects "
892
                              "unconstrained degrees of freedom.");
893
    sWhatsThis = "Sketcher_SelectElementsWithDoFs";
894
    sStatusTip = sToolTipText;
895
    sPixmap = "Sketcher_SelectElementsWithDoFs";
896
    sAccel = "Z, F";
897
    eType = ForEdit;
898
}
899

900
void CmdSketcherSelectElementsWithDoFs::activated(int iMsg)
901
{
902
    Q_UNUSED(iMsg);
903
    getSelection().clearSelection();
904
    Sketcher::SketchObject* Obj = getSketchObject();
905

906
    std::string doc_name = Obj->getDocument()->getName();
907
    std::string obj_name = Obj->getNameInDocument();
908
    std::stringstream ss;
909

910
    auto geos = Obj->getInternalGeometry();
911

912
    std::vector<std::string> elementSubNames;
913

914
    auto testselectvertex = [&Obj, &ss, &elementSubNames](int geoId, PointPos pos) {
915
        ss.str(std::string());
916

917
        int vertex = Obj->getVertexIndexGeoPos(geoId, pos);
918
        if (vertex > -1) {
919
            ss << "Vertex" << vertex + 1;
920

921
            elementSubNames.push_back(ss.str());
922
        }
923
    };
924

925
    auto testselectedge = [&ss, &elementSubNames](int geoId) {
926
        ss.str(std::string());
927

928
        ss << "Edge" << geoId + 1;
929
        elementSubNames.push_back(ss.str());
930
    };
931

932
    int geoid = 0;
933

934
    for (auto geo : geos) {
935
        if (geo) {
936
            if (geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) {
937

938
                auto solvext = std::static_pointer_cast<const Sketcher::SolverGeometryExtension>(
939
                    geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock());
940

941
                if (solvext->getGeometry()
942
                    == Sketcher::SolverGeometryExtension::NotFullyConstraint) {
943
                    // Coded for consistency with getGeometryWithDependentParameters, read the
944
                    // comments on that function
945
                    if (solvext->getEdge() == SolverGeometryExtension::Dependent)
946
                        testselectedge(geoid);
947
                    if (solvext->getStart() == SolverGeometryExtension::Dependent)
948
                        testselectvertex(geoid, Sketcher::PointPos::start);
949
                    if (solvext->getEnd() == SolverGeometryExtension::Dependent)
950
                        testselectvertex(geoid, Sketcher::PointPos::end);
951
                    if (solvext->getMid() == SolverGeometryExtension::Dependent)
952
                        testselectvertex(geoid, Sketcher::PointPos::mid);
953
                }
954
            }
955
        }
956

957
        geoid++;
958
    }
959

960
    if (!elementSubNames.empty()) {
961
        Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), elementSubNames);
962
    }
963
}
964

965
bool CmdSketcherSelectElementsWithDoFs::isActive()
966
{
967
    return isCommandActive(getActiveGuiDocument(), false);
968
}
969

970
// ================================================================================
971

972
DEF_STD_CMD_A(CmdSketcherRestoreInternalAlignmentGeometry)
973

974
CmdSketcherRestoreInternalAlignmentGeometry::CmdSketcherRestoreInternalAlignmentGeometry()
975
    : Command("Sketcher_RestoreInternalAlignmentGeometry")
976
{
977
    sAppModule = "Sketcher";
978
    sGroup = "Sketcher";
979
    sMenuText = QT_TR_NOOP("Show/hide internal geometry");
980
    sToolTipText = QT_TR_NOOP("Show all internal geometry or hide unused internal geometry");
981
    sWhatsThis = "Sketcher_RestoreInternalAlignmentGeometry";
982
    sStatusTip = sToolTipText;
983
    sPixmap = "Sketcher_Element_Ellipse_All";
984
    sAccel = "Z, I";
985
    eType = ForEdit;
986
}
987

988
void CmdSketcherRestoreInternalAlignmentGeometry::activated(int iMsg)
989
{
990
    Q_UNUSED(iMsg);
991

992
    // Cancel any in-progress operation
993
    Gui::Document* doc = Gui::Application::Instance->activeDocument();
994
    SketcherGui::ReleaseHandler(doc);
995

996
    // get the selection
997
    std::vector<Gui::SelectionObject> selection;
998
    selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());
999

1000
    // only one sketch with its subelements are allowed to be selected
1001
    if (selection.size() != 1) {
1002
        Gui::TranslatedUserWarning(doc->getDocument(),
1003
                                   QObject::tr("Wrong selection"),
1004
                                   QObject::tr("Select elements from a single sketch."));
1005

1006
        return;
1007
    }
1008

1009
    // get the needed lists and objects
1010
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
1011
    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
1012

1013
    getSelection().clearSelection();
1014

1015
    // Return GeoId of the SubName only if it is an edge
1016
    auto getEdgeGeoId = [&Obj](const std::string& SubName) {
1017
        int GeoId;
1018
        Sketcher::PointPos PosId;
1019
        getIdsFromName(SubName, Obj, GeoId, PosId);
1020
        if (PosId == Sketcher::PointPos::none)
1021
            return GeoId;
1022
        else
1023
            return (int)GeoEnum::GeoUndef;
1024
    };
1025

1026
    // Tells if the geometry with given GeoId has internal geometry
1027
    auto noInternalGeo = [&Obj](const auto& GeoId) {
1028
        const Part::Geometry* geo = Obj->getGeometry(GeoId);
1029
        bool hasInternalGeo = geo
1030
            && (geo->is<Part::GeomEllipse>()
1031
                || geo->is<Part::GeomArcOfEllipse>()
1032
                || geo->is<Part::GeomArcOfHyperbola>()
1033
                || geo->is<Part::GeomArcOfParabola>()
1034
                || geo->is<Part::GeomBSplineCurve>());
1035
        return !hasInternalGeo;// so it's removed
1036
    };
1037

1038
    std::vector<int> SubGeoIds(SubNames.size());
1039
    std::transform(SubNames.begin(), SubNames.end(), SubGeoIds.begin(), getEdgeGeoId);
1040

1041
    // Handle highest GeoIds first to minimize GeoIds changing
1042
    // TODO: this might not completely resolve GeoIds changing
1043
    std::sort(SubGeoIds.begin(), SubGeoIds.end(), std::greater<>());
1044
    // Keep unique
1045
    SubGeoIds.erase(std::unique(SubGeoIds.begin(), SubGeoIds.end()), SubGeoIds.end());
1046

1047
    // Only for supported types and keep unique
1048
    SubGeoIds.erase(std::remove_if(SubGeoIds.begin(), SubGeoIds.end(), noInternalGeo),
1049
                    SubGeoIds.end());
1050

1051
    // go through the selected subelements
1052
    for (const auto& GeoId : SubGeoIds) {
1053
        int currentgeoid = Obj->getHighestCurveIndex();
1054

1055
        try {
1056
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Exposing Internal Geometry"));
1057
            Gui::cmdAppObjectArgs(Obj, "exposeInternalGeometry(%d)", GeoId);
1058

1059
            int aftergeoid = Obj->getHighestCurveIndex();
1060

1061
            if (aftergeoid == currentgeoid) {// if we did not expose anything, deleteunused
1062
                Gui::cmdAppObjectArgs(Obj, "deleteUnusedInternalGeometry(%d)", GeoId);
1063
            }
1064
        }
1065
        catch (const Base::Exception& e) {
1066
            Gui::NotifyUserError(
1067
                Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what());
1068
            Gui::Command::abortCommand();
1069

1070
            tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(Obj));
1071

1072
            return;
1073
        }
1074

1075
        Gui::Command::commitCommand();
1076
        tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(Obj));
1077
    }
1078
}
1079

1080
bool CmdSketcherRestoreInternalAlignmentGeometry::isActive()
1081
{
1082
    return isCommandActive(getActiveGuiDocument(), true);
1083
}
1084

1085
// ================================================================================
1086

1087
DEF_STD_CMD_A(CmdSketcherSymmetry)
1088

1089
CmdSketcherSymmetry::CmdSketcherSymmetry()
1090
    : Command("Sketcher_Symmetry")
1091
{
1092
    sAppModule = "Sketcher";
1093
    sGroup = "Sketcher";
1094
    sMenuText = QT_TR_NOOP("Symmetry");
1095
    sToolTipText =
1096
        QT_TR_NOOP("Creates symmetric of selected geometry. After starting the tool select the reference line or point.");
1097
    sWhatsThis = "Sketcher_Symmetry";
1098
    sStatusTip = sToolTipText;
1099
    sPixmap = "Sketcher_Symmetry";
1100
    sAccel = "Z, S";
1101
    eType = ForEdit;
1102
}
1103

1104
void CmdSketcherSymmetry::activated(int iMsg)
1105
{
1106
    Q_UNUSED(iMsg);
1107
    std::vector<int> listOfGeoIds = getListOfSelectedGeoIds(true);
1108

1109
    if (!listOfGeoIds.empty()) {
1110
        ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerSymmetry>(listOfGeoIds));
1111
    }
1112
    getSelection().clearSelection();
1113
}
1114

1115
bool CmdSketcherSymmetry::isActive()
1116
{
1117
    return isCommandActive(getActiveGuiDocument(), true);
1118
}
1119

1120
// ================================================================================
1121

1122
class SketcherCopy: public Gui::Command
1123
{
1124
public:
1125
    enum Op
1126
    {
1127
        Copy,
1128
        Clone,
1129
        Move
1130
    };
1131
    explicit SketcherCopy(const char* name);
1132
    void activate(SketcherCopy::Op op);
1133
    virtual void activate() = 0;
1134
};
1135

1136
// TODO: replace XPM cursor with SVG file
1137
static const char* cursor_createcopy[] = {"32 32 3 1",
1138
                                          "+ c white",
1139
                                          "# c red",
1140
                                          ". c None",
1141
                                          "................................",
1142
                                          ".......+........................",
1143
                                          ".......+........................",
1144
                                          ".......+........................",
1145
                                          ".......+........................",
1146
                                          ".......+........................",
1147
                                          "................................",
1148
                                          ".+++++...+++++..................",
1149
                                          "................................",
1150
                                          ".......+........................",
1151
                                          ".......+..............###.......",
1152
                                          ".......+..............###.......",
1153
                                          ".......+..............###.......",
1154
                                          ".......+..............###.......",
1155
                                          "......................###.......",
1156
                                          ".....###..............###.......",
1157
                                          ".....###..............###.......",
1158
                                          ".....###..............###.......",
1159
                                          ".....###..............###.......",
1160
                                          ".....###..............###.......",
1161
                                          ".....###..............###.......",
1162
                                          ".....###..............###.......",
1163
                                          ".....###..............###.......",
1164
                                          ".....###..............###.......",
1165
                                          ".....###........................",
1166
                                          ".....###........................",
1167
                                          ".....###........................",
1168
                                          ".....###........................",
1169
                                          "................................",
1170
                                          "................................",
1171
                                          "................................",
1172
                                          "................................"};
1173

1174
class DrawSketchHandlerCopy: public DrawSketchHandler
1175
{
1176
public:
1177
    DrawSketchHandlerCopy(string geoidlist, int origingeoid, Sketcher::PointPos originpos,
1178
                          int nelements, SketcherCopy::Op op)
1179
        : Mode(STATUS_SEEK_First)
1180
        , snapMode(SnapMode::Free)
1181
        , geoIdList(geoidlist)
1182
        , Origin()
1183
        , OriginGeoId(origingeoid)
1184
        , OriginPos(originpos)
1185
        , nElements(nelements)
1186
        , Op(op)
1187
        , EditCurve(2)
1188
    {}
1189

1190
    ~DrawSketchHandlerCopy() override
1191
    {}
1192
    /// mode table
1193
    enum SelectMode
1194
    {
1195
        STATUS_SEEK_First, /**< enum value ----. */
1196
        STATUS_End
1197
    };
1198

1199
    enum class SnapMode
1200
    {
1201
        Free,
1202
        Snap5Degree
1203
    };
1204

1205
    void mouseMove(Base::Vector2d onSketchPos) override
1206
    {
1207
        if (Mode == STATUS_SEEK_First) {
1208

1209
            if (QApplication::keyboardModifiers() == Qt::ControlModifier)
1210
                snapMode = SnapMode::Snap5Degree;
1211
            else
1212
                snapMode = SnapMode::Free;
1213

1214
            float length = (onSketchPos - EditCurve[0]).Length();
1215
            float angle = (onSketchPos - EditCurve[0]).Angle();
1216

1217
            Base::Vector2d endpoint = onSketchPos;
1218

1219
            if (snapMode == SnapMode::Snap5Degree) {
1220
                angle = round(angle / (M_PI / 36)) * M_PI / 36;
1221
                endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle), sin(angle));
1222
            }
1223

1224
            if (showCursorCoords()) {
1225
                SbString text;
1226
                std::string lengthString = lengthToDisplayFormat(length, 1);
1227
                std::string angleString = angleToDisplayFormat(angle * 180.0 / M_PI, 1);
1228
                text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str());
1229
                setPositionText(endpoint, text);
1230
            }
1231

1232
            EditCurve[1] = endpoint;
1233
            drawEdit(EditCurve);
1234
        }
1235
        applyCursor();
1236
    }
1237

1238
    bool pressButton(Base::Vector2d) override
1239
    {
1240
        if (Mode == STATUS_SEEK_First) {
1241
            drawEdit(EditCurve);
1242
            Mode = STATUS_End;
1243
        }
1244
        return true;
1245
    }
1246

1247
    bool releaseButton(Base::Vector2d onSketchPos) override
1248
    {
1249
        Q_UNUSED(onSketchPos);
1250
        if (Mode == STATUS_End) {
1251
            Base::Vector2d vector = EditCurve[1] - EditCurve[0];
1252
            unsetCursor();
1253
            resetPositionText();
1254

1255
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Copy/clone/move geometry"));
1256

1257
            try {
1258
                if (Op != SketcherCopy::Move) {
1259
                    Gui::cmdAppObjectArgs(sketchgui->getObject(),
1260
                                          "addCopy(%s, App.Vector(%f, %f, 0), %s)",
1261
                                          geoIdList.c_str(),
1262
                                          vector.x,
1263
                                          vector.y,
1264
                                          (Op == SketcherCopy::Clone ? "True" : "False"));
1265
                }
1266
                else {
1267
                    Gui::cmdAppObjectArgs(sketchgui->getObject(),
1268
                                          "addMove(%s, App.Vector(%f, %f, 0))",
1269
                                          geoIdList.c_str(),
1270
                                          vector.x,
1271
                                          vector.y);
1272
                }
1273
                Gui::Command::commitCommand();
1274
            }
1275
            catch (const Base::Exception& e) {
1276
                Gui::NotifyUserError(
1277
                    sketchgui->getObject(), QT_TRANSLATE_NOOP("Notifications", "Error"), e.what());
1278
                Gui::Command::abortCommand();
1279
            }
1280

1281
            tryAutoRecomputeIfNotSolve(
1282
                static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
1283
            EditCurve.clear();
1284
            drawEdit(EditCurve);
1285

1286
            // no code after this line, Handler gets deleted in ViewProvider
1287
            sketchgui->purgeHandler();
1288
        }
1289
        return true;
1290
    }
1291

1292
private:
1293
    void activated() override
1294
    {
1295
        setCursor(QPixmap(cursor_createcopy), 7, 7);
1296
        Origin = static_cast<Sketcher::SketchObject*>(sketchgui->getObject())
1297
                     ->getPoint(OriginGeoId, OriginPos);
1298
        EditCurve[0] = Base::Vector2d(Origin.x, Origin.y);
1299
    }
1300

1301
protected:
1302
    SelectMode Mode;
1303
    SnapMode snapMode;
1304
    string geoIdList;
1305
    Base::Vector3d Origin;
1306
    int OriginGeoId;
1307
    Sketcher::PointPos OriginPos;
1308
    int nElements;
1309
    SketcherCopy::Op Op;
1310
    std::vector<Base::Vector2d> EditCurve;
1311
    std::vector<AutoConstraint> sugConstr1;
1312
};
1313

1314
/*---- SketcherCopy definition ----*/
1315
SketcherCopy::SketcherCopy(const char* name)
1316
    : Command(name)
1317
{}
1318

1319
void SketcherCopy::activate(SketcherCopy::Op op)
1320
{
1321
    // get the selection
1322
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
1323

1324
    // only one sketch with its subelements are allowed to be selected
1325
    if (selection.size() != 1) {
1326
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
1327
                                   QObject::tr("Wrong selection"),
1328
                                   QObject::tr("Select elements from a single sketch."));
1329

1330
        return;
1331
    }
1332

1333
    // get the needed lists and objects
1334
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
1335
    if (SubNames.empty()) {
1336
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
1337
                                   QObject::tr("Wrong selection"),
1338
                                   QObject::tr("Select elements from a single sketch."));
1339

1340
        return;
1341
    }
1342

1343
    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
1344
    getSelection().clearSelection();
1345

1346
    int LastGeoId = 0;
1347
    Sketcher::PointPos LastPointPos = Sketcher::PointPos::none;
1348
    const Part::Geometry* LastGeo = nullptr;
1349

1350
    // create python command with list of elements
1351
    std::stringstream stream;
1352
    int geoids = 0;
1353
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
1354
         ++it) {
1355
        // only handle non-external edges
1356
        if (it->size() > 4 && it->substr(0, 4) == "Edge") {
1357
            LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
1358
            LastPointPos = Sketcher::PointPos::none;
1359
            LastGeo = Obj->getGeometry(LastGeoId);
1360
            // lines to copy
1361
            if (LastGeoId >= 0) {
1362
                geoids++;
1363
                stream << LastGeoId << ",";
1364
            }
1365
        }
1366
        else if (it->size() > 6 && it->substr(0, 6) == "Vertex") {
1367
            // only if it is a GeomPoint
1368
            int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1;
1369
            int GeoId;
1370
            Sketcher::PointPos PosId;
1371
            Obj->getGeoVertexIndex(VtId, GeoId, PosId);
1372
            if (Obj->getGeometry(GeoId)->is<Part::GeomPoint>()) {
1373
                LastGeoId = GeoId;
1374
                LastPointPos = Sketcher::PointPos::start;
1375
                // points to copy
1376
                if (LastGeoId >= 0) {
1377
                    geoids++;
1378
                    stream << LastGeoId << ",";
1379
                }
1380
            }
1381
        }
1382
    }
1383

1384
    // check if last selected element is a Vertex, not being a GeomPoint
1385
    if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") {
1386
        int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1;
1387
        int GeoId;
1388
        Sketcher::PointPos PosId;
1389
        Obj->getGeoVertexIndex(VtId, GeoId, PosId);
1390
        if (!Obj->getGeometry(GeoId)->is<Part::GeomPoint>()) {
1391
            LastGeoId = GeoId;
1392
            LastPointPos = PosId;
1393
        }
1394
    }
1395

1396
    if (geoids < 1) {
1397
        Gui::TranslatedUserWarning(
1398
            Obj,
1399
            QObject::tr("Wrong selection"),
1400
            QObject::tr("A copy requires at least one selected non-external geometric element"));
1401

1402
        return;
1403
    }
1404

1405
    std::string geoIdList = stream.str();
1406

1407
    // remove the last added comma and brackets to make the python list
1408
    int index = geoIdList.rfind(',');
1409
    geoIdList.resize(index);
1410
    geoIdList.insert(0, 1, '[');
1411
    geoIdList.append(1, ']');
1412

1413
    // if the last element is not a point serving as a reference for the copy process
1414
    // then make the start point of the last element the copy reference (if it exists, if not the
1415
    // center point)
1416
    if (LastPointPos == Sketcher::PointPos::none) {
1417
        if (LastGeo->is<Part::GeomCircle>()
1418
            || LastGeo->is<Part::GeomEllipse>()) {
1419
            LastPointPos = Sketcher::PointPos::mid;
1420
        }
1421
        else {
1422
            LastPointPos = Sketcher::PointPos::start;
1423
        }
1424
    }
1425

1426
    // Ask the user if they want to clone or to simple copy
1427
    /*
1428
    int ret = QMessageBox::question(Gui::getMainWindow(), QObject::tr("Dimensional/Geometric
1429
    constraints"), QObject::tr("Do you want to clone the object, i.e. substitute dimensional
1430
    constraints by geometric constraints?"), QMessageBox::Yes, QMessageBox::No,
1431
    QMessageBox::Cancel);
1432
    // use an equality constraint
1433
    if (ret == QMessageBox::Yes) {
1434
        clone = true;
1435
    }
1436
    else if (ret == QMessageBox::Cancel) {
1437
    // do nothing
1438
    return;
1439
    }
1440
*/
1441

1442
    ActivateHandler(getActiveGuiDocument(),
1443
                    std::make_unique<DrawSketchHandlerCopy>(geoIdList, LastGeoId, LastPointPos, geoids, op));
1444
}
1445

1446

1447
class CmdSketcherCopy: public SketcherCopy
1448
{
1449
public:
1450
    CmdSketcherCopy();
1451
    ~CmdSketcherCopy() override
1452
    {}
1453
    const char* className() const override
1454
    {
1455
        return "CmdSketcherCopy";
1456
    }
1457
    void activate() override;
1458

1459
protected:
1460
    void activated(int iMsg) override;
1461
    bool isActive() override;
1462
};
1463

1464
CmdSketcherCopy::CmdSketcherCopy()
1465
    : SketcherCopy("Sketcher_Copy")
1466
{
1467
    sAppModule = "Sketcher";
1468
    sGroup = "Sketcher";
1469
    sMenuText = QT_TR_NOOP("Copy");
1470
    sToolTipText = QT_TR_NOOP(
1471
        "Creates a simple copy of the geometry taking as reference the last selected point");
1472
    sWhatsThis = "Sketcher_Copy";
1473
    sStatusTip = sToolTipText;
1474
    sPixmap = "Sketcher_Copy";
1475
    sAccel = "Z, C";
1476
    eType = ForEdit;
1477
}
1478

1479
void CmdSketcherCopy::activated(int iMsg)
1480
{
1481
    Q_UNUSED(iMsg);
1482
    SketcherCopy::activate(SketcherCopy::Copy);
1483
}
1484

1485

1486
void CmdSketcherCopy::activate()
1487
{
1488
    SketcherCopy::activate(SketcherCopy::Copy);
1489
}
1490

1491
bool CmdSketcherCopy::isActive()
1492
{
1493
    return isCommandActive(getActiveGuiDocument(), true);
1494
}
1495

1496
// ================================================================================
1497

1498
class CmdSketcherClone: public SketcherCopy
1499
{
1500
public:
1501
    CmdSketcherClone();
1502
    ~CmdSketcherClone() override
1503
    {}
1504
    const char* className() const override
1505
    {
1506
        return "CmdSketcherClone";
1507
    }
1508
    void activate() override;
1509

1510
protected:
1511
    void activated(int iMsg) override;
1512
    bool isActive() override;
1513
};
1514

1515
CmdSketcherClone::CmdSketcherClone()
1516
    : SketcherCopy("Sketcher_Clone")
1517
{
1518
    sAppModule = "Sketcher";
1519
    sGroup = "Sketcher";
1520
    sMenuText = QT_TR_NOOP("Clone");
1521
    sToolTipText =
1522
        QT_TR_NOOP("Creates a clone of the geometry taking as reference the last selected point");
1523
    sWhatsThis = "Sketcher_Clone";
1524
    sStatusTip = sToolTipText;
1525
    sPixmap = "Sketcher_Clone";
1526
    sAccel = "Z, L";
1527
    eType = ForEdit;
1528
}
1529

1530
void CmdSketcherClone::activated(int iMsg)
1531
{
1532
    Q_UNUSED(iMsg);
1533
    SketcherCopy::activate(SketcherCopy::Clone);
1534
}
1535

1536
void CmdSketcherClone::activate()
1537
{
1538
    SketcherCopy::activate(SketcherCopy::Clone);
1539
}
1540

1541
bool CmdSketcherClone::isActive()
1542
{
1543
    return isCommandActive(getActiveGuiDocument(), true);
1544
}
1545

1546
class CmdSketcherMove: public SketcherCopy
1547
{
1548
public:
1549
    CmdSketcherMove();
1550
    ~CmdSketcherMove() override
1551
    {}
1552
    const char* className() const override
1553
    {
1554
        return "CmdSketcherMove";
1555
    }
1556
    void activate() override;
1557

1558
protected:
1559
    void activated(int iMsg) override;
1560
    bool isActive() override;
1561
};
1562

1563
CmdSketcherMove::CmdSketcherMove()
1564
    : SketcherCopy("Sketcher_Move")
1565
{
1566
    sAppModule = "Sketcher";
1567
    sGroup = "Sketcher";
1568
    sMenuText = QT_TR_NOOP("Move");
1569
    sToolTipText = QT_TR_NOOP("Moves the geometry taking as reference the last selected point");
1570
    sWhatsThis = "Sketcher_Move";
1571
    sStatusTip = sToolTipText;
1572
    sPixmap = "Sketcher_Move";
1573
    sAccel = "Z, M";
1574
    eType = ForEdit;
1575
}
1576

1577
void CmdSketcherMove::activated(int iMsg)
1578
{
1579
    Q_UNUSED(iMsg);
1580
    SketcherCopy::activate(SketcherCopy::Move);
1581
}
1582

1583
void CmdSketcherMove::activate()
1584
{
1585
    SketcherCopy::activate(SketcherCopy::Move);
1586
}
1587

1588
bool CmdSketcherMove::isActive()
1589
{
1590
    return isCommandActive(getActiveGuiDocument(), true);
1591
}
1592

1593
// ================================================================================
1594

1595
DEF_STD_CMD_ACL(CmdSketcherCompCopy)
1596

1597
CmdSketcherCompCopy::CmdSketcherCompCopy()
1598
    : Command("Sketcher_CompCopy")
1599
{
1600
    sAppModule = "Sketcher";
1601
    sGroup = "Sketcher";
1602
    sMenuText = QT_TR_NOOP("Clone");
1603
    sToolTipText =
1604
        QT_TR_NOOP("Creates a clone of the geometry taking as reference the last selected point");
1605
    sWhatsThis = "Sketcher_CompCopy";
1606
    sStatusTip = sToolTipText;
1607
    sAccel = "";
1608
    eType = ForEdit;
1609
}
1610

1611
void CmdSketcherCompCopy::activated(int iMsg)
1612
{
1613
    if (iMsg < 0 || iMsg > 2)
1614
        return;
1615

1616
    // Since the default icon is reset when enabling/disabling the command we have
1617
    // to explicitly set the icon of the used command.
1618
    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
1619
    QList<QAction*> a = pcAction->actions();
1620

1621
    assert(iMsg < a.size());
1622
    pcAction->setIcon(a[iMsg]->icon());
1623

1624
    if (iMsg == 0) {
1625
        CmdSketcherClone sc;
1626
        sc.activate();
1627
        pcAction->setShortcut(QString::fromLatin1(this->getAccel()));
1628
    }
1629
    else if (iMsg == 1) {
1630
        CmdSketcherCopy sc;
1631
        sc.activate();
1632
        pcAction->setShortcut(QString::fromLatin1(this->getAccel()));
1633
    }
1634
    else if (iMsg == 2) {
1635
        CmdSketcherMove sc;
1636
        sc.activate();
1637
        pcAction->setShortcut(QString::fromLatin1(""));
1638
    }
1639
}
1640

1641
Gui::Action* CmdSketcherCompCopy::createAction()
1642
{
1643
    Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
1644
    pcAction->setDropDownMenu(true);
1645
    applyCommandData(this->className(), pcAction);
1646

1647
    QAction* clone = pcAction->addAction(QString());
1648
    clone->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Clone"));
1649
    QAction* copy = pcAction->addAction(QString());
1650
    copy->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Copy"));
1651
    QAction* move = pcAction->addAction(QString());
1652
    move->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Move"));
1653

1654
    _pcAction = pcAction;
1655
    languageChange();
1656

1657
    pcAction->setIcon(clone->icon());
1658
    int defaultId = 0;
1659
    pcAction->setProperty("defaultAction", QVariant(defaultId));
1660

1661
    pcAction->setShortcut(QString::fromLatin1(getAccel()));
1662

1663
    return pcAction;
1664
}
1665

1666
void CmdSketcherCompCopy::languageChange()
1667
{
1668
    Command::languageChange();
1669

1670
    if (!_pcAction)
1671
        return;
1672
    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
1673
    QList<QAction*> a = pcAction->actions();
1674

1675
    QAction* clone = a[0];
1676
    clone->setText(QApplication::translate("Sketcher_CompCopy", "Clone"));
1677
    clone->setToolTip(QApplication::translate(
1678
        "Sketcher_Clone",
1679
        "Creates a clone of the geometry taking as reference the last selected point"));
1680
    clone->setStatusTip(QApplication::translate(
1681
        "Sketcher_Clone",
1682
        "Creates a clone of the geometry taking as reference the last selected point"));
1683
    QAction* copy = a[1];
1684
    copy->setText(QApplication::translate("Sketcher_CompCopy", "Copy"));
1685
    copy->setToolTip(QApplication::translate(
1686
        "Sketcher_Copy",
1687
        "Creates a simple copy of the geometry taking as reference the last selected point"));
1688
    copy->setStatusTip(QApplication::translate(
1689
        "Sketcher_Copy",
1690
        "Creates a simple copy of the geometry taking as reference the last selected point"));
1691
    QAction* move = a[2];
1692
    move->setText(QApplication::translate("Sketcher_CompCopy", "Move"));
1693
    move->setToolTip(QApplication::translate(
1694
        "Sketcher_Move", "Moves the geometry taking as reference the last selected point"));
1695
    move->setStatusTip(QApplication::translate(
1696
        "Sketcher_Move", "Moves the geometry taking as reference the last selected point"));
1697
}
1698

1699
bool CmdSketcherCompCopy::isActive()
1700
{
1701
    return isCommandActive(getActiveGuiDocument(), true);
1702
}
1703

1704
// ================================================================================
1705

1706
// TODO: replace XPM cursor with SVG file
1707
/* XPM */
1708
static const char* cursor_createrectangulararray[] = {"32 32 3 1",
1709
                                                      "+ c white",
1710
                                                      "# c red",
1711
                                                      ". c None",
1712
                                                      "................................",
1713
                                                      ".......+........................",
1714
                                                      ".......+........................",
1715
                                                      ".......+........................",
1716
                                                      ".......+........................",
1717
                                                      ".......+........................",
1718
                                                      "................................",
1719
                                                      ".+++++...+++++..................",
1720
                                                      ".......................###......",
1721
                                                      ".......+...............###......",
1722
                                                      ".......+...............###......",
1723
                                                      ".......+...............###......",
1724
                                                      ".......+......###......###......",
1725
                                                      ".......+......###......###......",
1726
                                                      "..............###......###......",
1727
                                                      "..............###......###......",
1728
                                                      ".....###......###......###......",
1729
                                                      ".....###......###......###......",
1730
                                                      ".....###......###......###......",
1731
                                                      ".....###......###......###......",
1732
                                                      ".....###......###......###......",
1733
                                                      ".....###......###......###......",
1734
                                                      ".....###......###...............",
1735
                                                      ".....###......###...............",
1736
                                                      ".....###......###...............",
1737
                                                      ".....###......###...............",
1738
                                                      ".....###........................",
1739
                                                      ".....###........................",
1740
                                                      ".....###........................",
1741
                                                      ".....###........................",
1742
                                                      "................................",
1743
                                                      "................................"};
1744

1745
class DrawSketchHandlerRectangularArray: public DrawSketchHandler
1746
{
1747
public:
1748
    DrawSketchHandlerRectangularArray(string geoidlist, int origingeoid,
1749
                                      Sketcher::PointPos originpos, int nelements, bool clone,
1750
                                      int rows, int cols, bool constraintSeparation,
1751
                                      bool equalVerticalHorizontalSpacing)
1752
        : Mode(STATUS_SEEK_First)
1753
        , snapMode(SnapMode::Free)
1754
        , geoIdList(geoidlist)
1755
        , OriginGeoId(origingeoid)
1756
        , OriginPos(originpos)
1757
        , nElements(nelements)
1758
        , Clone(clone)
1759
        , Rows(rows)
1760
        , Cols(cols)
1761
        , ConstraintSeparation(constraintSeparation)
1762
        , EqualVerticalHorizontalSpacing(equalVerticalHorizontalSpacing)
1763
        , EditCurve(2)
1764
    {}
1765

1766
    ~DrawSketchHandlerRectangularArray() override
1767
    {}
1768
    /// mode table
1769
    enum SelectMode
1770
    {
1771
        STATUS_SEEK_First, /**< enum value ----. */
1772
        STATUS_End
1773
    };
1774

1775
    enum class SnapMode
1776
    {
1777
        Free,
1778
        Snap5Degree
1779
    };
1780

1781
    void mouseMove(Base::Vector2d onSketchPos) override
1782
    {
1783
        if (Mode == STATUS_SEEK_First) {
1784

1785
            if (QApplication::keyboardModifiers() == Qt::ControlModifier)
1786
                snapMode = SnapMode::Snap5Degree;
1787
            else
1788
                snapMode = SnapMode::Free;
1789

1790
            float length = (onSketchPos - EditCurve[0]).Length();
1791
            float angle = (onSketchPos - EditCurve[0]).Angle();
1792

1793
            Base::Vector2d endpoint = onSketchPos;
1794

1795
            if (snapMode == SnapMode::Snap5Degree) {
1796
                angle = round(angle / (M_PI / 36)) * M_PI / 36;
1797
                endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle), sin(angle));
1798
            }
1799

1800
            if (showCursorCoords()) {
1801
                SbString text;
1802
                std::string lengthString = lengthToDisplayFormat(length, 1);
1803
                std::string angleString = angleToDisplayFormat(angle * 180.0 / M_PI, 1);
1804
                text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str());
1805
                setPositionText(endpoint, text);
1806
            }
1807

1808
            EditCurve[1] = endpoint;
1809
            drawEdit(EditCurve);
1810
            if (seekAutoConstraint(
1811
                    sugConstr1, endpoint, Base::Vector2d(0.0, 0.0), AutoConstraint::VERTEX)) {
1812
                renderSuggestConstraintsCursor(sugConstr1);
1813
                return;
1814
            }
1815
        }
1816
        applyCursor();
1817
    }
1818

1819
    bool pressButton(Base::Vector2d) override
1820
    {
1821
        if (Mode == STATUS_SEEK_First) {
1822
            drawEdit(EditCurve);
1823
            Mode = STATUS_End;
1824
        }
1825
        return true;
1826
    }
1827

1828
    bool releaseButton(Base::Vector2d onSketchPos) override
1829
    {
1830
        Q_UNUSED(onSketchPos);
1831
        if (Mode == STATUS_End) {
1832
            Base::Vector2d vector = EditCurve[1] - EditCurve[0];
1833
            unsetCursor();
1834
            resetPositionText();
1835

1836
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create copy of geometry"));
1837

1838
            try {
1839
                Gui::cmdAppObjectArgs(
1840
                    sketchgui->getObject(),
1841
                    "addRectangularArray(%s, App.Vector(%f, %f, 0), %s, %d, %d, %s, %f)",
1842
                    geoIdList.c_str(),
1843
                    vector.x,
1844
                    vector.y,
1845
                    (Clone ? "True" : "False"),
1846
                    Cols,
1847
                    Rows,
1848
                    (ConstraintSeparation ? "True" : "False"),
1849
                    (EqualVerticalHorizontalSpacing ? 1.0 : 0.5));
1850
                Gui::Command::commitCommand();
1851
            }
1852
            catch (const Base::Exception& e) {
1853
                Gui::NotifyUserError(
1854
                    sketchgui, QT_TRANSLATE_NOOP("Notifications", "Error"), e.what());
1855
                Gui::Command::abortCommand();
1856
            }
1857

1858
            // add auto constraints for the destination copy
1859
            if (!sugConstr1.empty()) {
1860
                createAutoConstraints(sugConstr1, OriginGeoId + nElements, OriginPos);
1861
                sugConstr1.clear();
1862
            }
1863
            tryAutoRecomputeIfNotSolve(
1864
                static_cast<Sketcher::SketchObject*>(sketchgui->getObject()));
1865

1866
            EditCurve.clear();
1867
            drawEdit(EditCurve);
1868

1869
            // no code after this line, Handler is deleted in ViewProvider
1870
            sketchgui->purgeHandler();
1871
        }
1872
        return true;
1873
    }
1874

1875
private:
1876
    void activated() override
1877
    {
1878
        setCursor(QPixmap(cursor_createrectangulararray), 7, 7);
1879
        Origin = static_cast<Sketcher::SketchObject*>(sketchgui->getObject())
1880
                     ->getPoint(OriginGeoId, OriginPos);
1881
        EditCurve[0] = Base::Vector2d(Origin.x, Origin.y);
1882
    }
1883

1884
protected:
1885
    SelectMode Mode;
1886
    SnapMode snapMode;
1887
    string geoIdList;
1888
    Base::Vector3d Origin;
1889
    int OriginGeoId;
1890
    Sketcher::PointPos OriginPos;
1891
    int nElements;
1892
    bool Clone;
1893
    int Rows;
1894
    int Cols;
1895
    bool ConstraintSeparation;
1896
    bool EqualVerticalHorizontalSpacing;
1897
    std::vector<Base::Vector2d> EditCurve;
1898
    std::vector<AutoConstraint> sugConstr1;
1899
};
1900

1901
DEF_STD_CMD_A(CmdSketcherRectangularArray)
1902

1903
CmdSketcherRectangularArray::CmdSketcherRectangularArray()
1904
    : Command("Sketcher_RectangularArray")
1905
{
1906
    sAppModule = "Sketcher";
1907
    sGroup = "Sketcher";
1908
    sMenuText = QT_TR_NOOP("Rectangular array");
1909
    sToolTipText = QT_TR_NOOP("Creates a rectangular array pattern of the geometry taking as "
1910
                              "reference the last selected point");
1911
    sWhatsThis = "Sketcher_RectangularArray";
1912
    sStatusTip = sToolTipText;
1913
    sPixmap = "Sketcher_RectangularArray";
1914
    sAccel = "Z, A";
1915
    eType = ForEdit;
1916
}
1917

1918
void CmdSketcherRectangularArray::activated(int iMsg)
1919
{
1920
    Q_UNUSED(iMsg);
1921
    // get the selection
1922
    std::vector<Gui::SelectionObject> selection;
1923
    selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());
1924

1925
    // only one sketch with its subelements are allowed to be selected
1926
    if (selection.size() != 1) {
1927
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
1928
                                   QObject::tr("Wrong selection"),
1929
                                   QObject::tr("Select elements from a single sketch."));
1930

1931
        return;
1932
    }
1933

1934
    // get the needed lists and objects
1935
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
1936
    if (SubNames.empty()) {
1937
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
1938
                                   QObject::tr("Wrong selection"),
1939
                                   QObject::tr("Select elements from a single sketch."));
1940
        return;
1941
    }
1942

1943
    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
1944

1945
    getSelection().clearSelection();
1946

1947
    int LastGeoId = 0;
1948
    Sketcher::PointPos LastPointPos = Sketcher::PointPos::none;
1949
    const Part::Geometry* LastGeo = nullptr;
1950

1951
    // create python command with list of elements
1952
    std::stringstream stream;
1953
    int geoids = 0;
1954

1955
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
1956
         ++it) {
1957
        // only handle non-external edges
1958
        if (it->size() > 4 && it->substr(0, 4) == "Edge") {
1959
            LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
1960
            LastPointPos = Sketcher::PointPos::none;
1961
            LastGeo = Obj->getGeometry(LastGeoId);
1962

1963
            // lines to copy
1964
            if (LastGeoId >= 0) {
1965
                geoids++;
1966
                stream << LastGeoId << ",";
1967
            }
1968
        }
1969
        else if (it->size() > 6 && it->substr(0, 6) == "Vertex") {
1970
            // only if it is a GeomPoint
1971
            int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1;
1972
            int GeoId;
1973
            Sketcher::PointPos PosId;
1974
            Obj->getGeoVertexIndex(VtId, GeoId, PosId);
1975
            if (Obj->getGeometry(GeoId)->is<Part::GeomPoint>()) {
1976
                LastGeoId = GeoId;
1977
                LastPointPos = Sketcher::PointPos::start;
1978
                // points to copy
1979
                if (LastGeoId >= 0) {
1980
                    geoids++;
1981
                    stream << LastGeoId << ",";
1982
                }
1983
            }
1984
        }
1985
    }
1986

1987
    // check if last selected element is a Vertex, not being a GeomPoint
1988
    if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") {
1989
        int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1;
1990
        int GeoId;
1991
        Sketcher::PointPos PosId;
1992
        Obj->getGeoVertexIndex(VtId, GeoId, PosId);
1993
        if (!Obj->getGeometry(GeoId)->is<Part::GeomPoint>()) {
1994
            LastGeoId = GeoId;
1995
            LastPointPos = PosId;
1996
        }
1997
    }
1998

1999
    if (geoids < 1) {
2000
        Gui::TranslatedUserWarning(
2001
            Obj,
2002
            QObject::tr("Wrong selection"),
2003
            QObject::tr("A copy requires at least one selected non-external geometric element"));
2004

2005
        return;
2006
    }
2007

2008
    std::string geoIdList = stream.str();
2009

2010
    // remove the last added comma and brackets to make the python list
2011
    int index = geoIdList.rfind(',');
2012
    geoIdList.resize(index);
2013
    geoIdList.insert(0, 1, '[');
2014
    geoIdList.append(1, ']');
2015

2016
    // if the last element is not a point serving as a reference for the copy process
2017
    // then make the start point of the last element the copy reference (if it exists, if not the
2018
    // center point)
2019
    if (LastPointPos == Sketcher::PointPos::none) {
2020
        if (LastGeo->is<Part::GeomCircle>()
2021
            || LastGeo->is<Part::GeomEllipse>()) {
2022
            LastPointPos = Sketcher::PointPos::mid;
2023
        }
2024
        else {
2025
            LastPointPos = Sketcher::PointPos::start;
2026
        }
2027
    }
2028

2029
    // Pop-up asking for values
2030
    SketchRectangularArrayDialog slad;
2031

2032
    if (slad.exec() == QDialog::Accepted) {
2033
        ActivateHandler(getActiveGuiDocument(),
2034
                        std::make_unique<DrawSketchHandlerRectangularArray>(geoIdList,
2035
                                                              LastGeoId,
2036
                                                              LastPointPos,
2037
                                                              geoids,
2038
                                                              slad.Clone,
2039
                                                              slad.Rows,
2040
                                                              slad.Cols,
2041
                                                              slad.ConstraintSeparation,
2042
                                                              slad.EqualVerticalHorizontalSpacing));
2043
    }
2044
}
2045

2046
bool CmdSketcherRectangularArray::isActive()
2047
{
2048
    return isCommandActive(getActiveGuiDocument(), true);
2049
}
2050

2051
// ================================================================================
2052

2053
DEF_STD_CMD_A(CmdSketcherDeleteAllGeometry)
2054

2055
CmdSketcherDeleteAllGeometry::CmdSketcherDeleteAllGeometry()
2056
    : Command("Sketcher_DeleteAllGeometry")
2057
{
2058
    sAppModule = "Sketcher";
2059
    sGroup = "Sketcher";
2060
    sMenuText = QT_TR_NOOP("Delete all geometry");
2061
    sToolTipText = QT_TR_NOOP("Delete all geometry and constraints in the current sketch, "
2062
                              "with the exception of external geometry");
2063
    sWhatsThis = "Sketcher_DeleteAllGeometry";
2064
    sStatusTip = sToolTipText;
2065
    sPixmap = "Sketcher_DeleteGeometry";
2066
    sAccel = "";
2067
    eType = ForEdit;
2068
}
2069

2070
void CmdSketcherDeleteAllGeometry::activated(int iMsg)
2071
{
2072
    Q_UNUSED(iMsg);
2073

2074
    int ret = QMessageBox::question(
2075
        Gui::getMainWindow(),
2076
        QObject::tr("Delete All Geometry"),
2077
        QObject::tr("Are you really sure you want to delete all geometry and constraints?"),
2078
        QMessageBox::Yes,
2079
        QMessageBox::Cancel);
2080
    // use an equality constraint
2081
    if (ret == QMessageBox::Yes) {
2082
        getSelection().clearSelection();
2083
        Sketcher::SketchObject* Obj = getSketchObject();
2084

2085
        try {
2086
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Delete all geometry"));
2087
            Gui::cmdAppObjectArgs(Obj, "deleteAllGeometry()");
2088
            Gui::Command::commitCommand();
2089
        }
2090
        catch (const Base::Exception& e) {
2091
            Gui::NotifyUserError(
2092
                Obj, QT_TRANSLATE_NOOP("Notifications", "Failed to delete all geometry"), e.what());
2093
            Gui::Command::abortCommand();
2094
        }
2095

2096
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
2097
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
2098
        bool autoRecompute = hGrp->GetBool("AutoRecompute", false);
2099

2100
        if (autoRecompute)
2101
            Gui::Command::updateActive();
2102
        else
2103
            Obj->solve();
2104
    }
2105
    else if (ret == QMessageBox::Cancel) {
2106
        // do nothing
2107
        return;
2108
    }
2109
}
2110

2111
bool CmdSketcherDeleteAllGeometry::isActive()
2112
{
2113
    return isCommandActive(getActiveGuiDocument(), false);
2114
}
2115

2116
// ================================================================================
2117

2118
DEF_STD_CMD_A(CmdSketcherDeleteAllConstraints)
2119

2120
CmdSketcherDeleteAllConstraints::CmdSketcherDeleteAllConstraints()
2121
    : Command("Sketcher_DeleteAllConstraints")
2122
{
2123
    sAppModule = "Sketcher";
2124
    sGroup = "Sketcher";
2125
    sMenuText = QT_TR_NOOP("Delete all constraints");
2126
    sToolTipText = QT_TR_NOOP("Delete all constraints in the sketch");
2127
    sWhatsThis = "Sketcher_DeleteAllConstraints";
2128
    sStatusTip = sToolTipText;
2129
    sPixmap = "Sketcher_DeleteConstraints";
2130
    sAccel = "";
2131
    eType = ForEdit;
2132
}
2133

2134
void CmdSketcherDeleteAllConstraints::activated(int iMsg)
2135
{
2136
    Q_UNUSED(iMsg);
2137

2138
    int ret = QMessageBox::question(
2139
        Gui::getMainWindow(),
2140
        QObject::tr("Delete All Constraints"),
2141
        QObject::tr("Are you really sure you want to delete all the constraints?"),
2142
        QMessageBox::Yes,
2143
        QMessageBox::Cancel);
2144

2145
    if (ret == QMessageBox::Yes) {
2146
        getSelection().clearSelection();
2147
        Sketcher::SketchObject* Obj = getSketchObject();
2148

2149
        try {
2150
            Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Delete All Constraints"));
2151
            Gui::cmdAppObjectArgs(Obj, "deleteAllConstraints()");
2152
            Gui::Command::commitCommand();
2153
        }
2154
        catch (const Base::Exception& e) {
2155
            Gui::NotifyUserError(
2156
                Obj,
2157
                QT_TRANSLATE_NOOP("Notifications", "Failed to delete all constraints"),
2158
                e.what());
2159
            Gui::Command::abortCommand();
2160
        }
2161

2162
        ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
2163
            "User parameter:BaseApp/Preferences/Mod/Sketcher");
2164
        bool autoRecompute = hGrp->GetBool("AutoRecompute", false);
2165

2166
        if (autoRecompute)
2167
            Gui::Command::updateActive();
2168
        else
2169
            Obj->solve();
2170
    }
2171
    else if (ret == QMessageBox::Cancel) {
2172
        // do nothing
2173
        return;
2174
    }
2175
}
2176

2177
bool CmdSketcherDeleteAllConstraints::isActive()
2178
{
2179
    return isCommandActive(getActiveGuiDocument(), false);
2180
}
2181

2182
// ================================================================================
2183

2184

2185
DEF_STD_CMD_A(CmdSketcherRemoveAxesAlignment)
2186

2187
CmdSketcherRemoveAxesAlignment::CmdSketcherRemoveAxesAlignment()
2188
    : Command("Sketcher_RemoveAxesAlignment")
2189
{
2190
    sAppModule = "Sketcher";
2191
    sGroup = "Sketcher";
2192
    sMenuText = QT_TR_NOOP("Remove axes alignment");
2193
    sToolTipText = QT_TR_NOOP("Modifies constraints to remove axes alignment while trying to "
2194
                              "preserve the constraint relationship of the selection");
2195
    sWhatsThis = "Sketcher_RemoveAxesAlignment";
2196
    sStatusTip = sToolTipText;
2197
    sPixmap = "Sketcher_RemoveAxesAlignment";
2198
    sAccel = "Z, R";
2199
    eType = ForEdit;
2200
}
2201

2202
void CmdSketcherRemoveAxesAlignment::activated(int iMsg)
2203
{
2204
    Q_UNUSED(iMsg);
2205
    // get the selection
2206
    std::vector<Gui::SelectionObject> selection;
2207
    selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId());
2208

2209
    // only one sketch with its subelements are allowed to be selected
2210
    if (selection.size() != 1) {
2211
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
2212
                                   QObject::tr("Wrong selection"),
2213
                                   QObject::tr("Select elements from a single sketch."));
2214

2215
        return;
2216
    }
2217

2218
    // get the needed lists and objects
2219
    const std::vector<std::string>& SubNames = selection[0].getSubNames();
2220
    if (SubNames.empty()) {
2221
        Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
2222
                                   QObject::tr("Wrong selection"),
2223
                                   QObject::tr("Select elements from a single sketch."));
2224
        return;
2225
    }
2226

2227
    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
2228

2229
    getSelection().clearSelection();
2230

2231
    int LastGeoId = 0;
2232

2233
    // create python command with list of elements
2234
    std::stringstream stream;
2235
    int geoids = 0;
2236

2237
    for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
2238
         ++it) {
2239
        // only handle non-external edges
2240
        if (it->size() > 4 && it->substr(0, 4) == "Edge") {
2241
            LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
2242

2243
            // lines to copy
2244
            if (LastGeoId >= 0) {
2245
                geoids++;
2246
                stream << LastGeoId << ",";
2247
            }
2248
        }
2249
        else if (it->size() > 6 && it->substr(0, 6) == "Vertex") {
2250
            // only if it is a GeomPoint
2251
            int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1;
2252
            int GeoId;
2253
            Sketcher::PointPos PosId;
2254
            Obj->getGeoVertexIndex(VtId, GeoId, PosId);
2255
            if (Obj->getGeometry(GeoId)->is<Part::GeomPoint>()) {
2256
                LastGeoId = GeoId;
2257
                // points to copy
2258
                if (LastGeoId >= 0) {
2259
                    geoids++;
2260
                    stream << LastGeoId << ",";
2261
                }
2262
            }
2263
        }
2264
    }
2265

2266
    if (geoids < 1) {
2267
        Gui::TranslatedUserWarning(
2268
            Obj,
2269
            QObject::tr("Wrong selection"),
2270
            QObject::tr("Removal of axes alignment requires at least one selected "
2271
                        "non-external geometric element"));
2272

2273
        return;
2274
    }
2275

2276
    std::string geoIdList = stream.str();
2277

2278
    // remove the last added comma and brackets to make the python list
2279
    int index = geoIdList.rfind(',');
2280
    geoIdList.resize(index);
2281
    geoIdList.insert(0, 1, '[');
2282
    geoIdList.append(1, ']');
2283

2284
    Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Remove Axes Alignment"));
2285

2286
    try {
2287
        Gui::cmdAppObjectArgs(Obj, "removeAxesAlignment(%s)", geoIdList.c_str());
2288
        Gui::Command::commitCommand();
2289
    }
2290
    catch (const Base::Exception& e) {
2291
        Gui::NotifyUserError(Obj, QT_TRANSLATE_NOOP("Notifications", "Error"), e.what());
2292
        Gui::Command::abortCommand();
2293
    }
2294

2295
    tryAutoRecomputeIfNotSolve(static_cast<Sketcher::SketchObject*>(Obj));
2296
}
2297

2298
bool CmdSketcherRemoveAxesAlignment::isActive()
2299
{
2300
    return isCommandActive(getActiveGuiDocument(), true);
2301
}
2302

2303

2304
// Offset tool =====================================================================
2305
DEF_STD_CMD_A(CmdSketcherOffset)
2306

2307
CmdSketcherOffset::CmdSketcherOffset()
2308
    : Command("Sketcher_Offset")
2309
{
2310
    sAppModule = "Sketcher";
2311
    sGroup = "Sketcher";
2312
    sMenuText = QT_TR_NOOP("Offset geometry");
2313
    sToolTipText = QT_TR_NOOP("Offset selected geometries. A positive offset length makes the offset go outward, a negative length inward.");
2314
    sWhatsThis = "Sketcher_Offset";
2315
    sStatusTip = sToolTipText;
2316
    sPixmap = "Sketcher_Offset";
2317
    sAccel = "Z, T";
2318
    eType = ForEdit;
2319
}
2320

2321
void CmdSketcherOffset::activated(int iMsg)
2322
{
2323
    Q_UNUSED(iMsg);
2324
    std::vector<int> listOfGeoIds = {};
2325

2326
    // get the selection
2327
    std::vector<Gui::SelectionObject> selection;
2328
    selection = getSelection().getSelectionEx(0, Sketcher::SketchObject::getClassTypeId());
2329

2330
    // only one sketch with its subelements are allowed to be selected
2331
    if (selection.size() != 1) {
2332
        Gui::TranslatedUserWarning(
2333
            getActiveGuiDocument(),
2334
            QObject::tr("Wrong selection"),
2335
            QObject::tr("Select elements from a single sketch."));
2336
        return;
2337
    }
2338

2339
    // get the needed lists and objects
2340
    auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
2341
    const std::vector<std::string>& subNames = selection[0].getSubNames();
2342
    if (!subNames.empty()) {
2343
        for (auto& name : subNames) {
2344
            // only handle non-external edges
2345
            if (name.size() > 4 && name.substr(0, 4) == "Edge") {
2346
                int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1;
2347
                if (geoId >= 0) {
2348
                    const Part::Geometry* geo = Obj->getGeometry(geoId);
2349
                    if (!isPoint(*geo)
2350
                        && !isBSplineCurve(*geo)
2351
                        && !isEllipse(*geo)
2352
                        && !isArcOfEllipse(*geo)
2353
                        && !isArcOfHyperbola(*geo)
2354
                        && !isArcOfParabola(*geo)
2355
                        && !GeometryFacade::isInternalAligned(geo)) {
2356
                        // Currently ellipse/parabola/hyperbola/bspline are not handled correctly.
2357
                        // Occ engine gives offset of those as set of lines and arcs and does not seem to work consistently.
2358
                        listOfGeoIds.push_back(geoId);
2359
                    }
2360
                }
2361
            }
2362
        }
2363
    }
2364

2365
    if (listOfGeoIds.size() != 0) {
2366
        ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerOffset>(listOfGeoIds));
2367
    }
2368
    else {
2369
        getSelection().clearSelection();
2370
        Gui::NotifyUserError(Obj,
2371
            QT_TRANSLATE_NOOP("Notifications", "Invalid selection"),
2372
            QT_TRANSLATE_NOOP("Notifications", "Selection has no valid geometries. B-splines and points are not supported yet."));
2373
    }
2374
}
2375

2376
bool CmdSketcherOffset::isActive()
2377
{
2378
    return isCommandActive(getActiveGuiDocument(), true);
2379
}
2380

2381
// Rotate tool =====================================================================
2382

2383
DEF_STD_CMD_A(CmdSketcherRotate)
2384

2385
CmdSketcherRotate::CmdSketcherRotate()
2386
    : Command("Sketcher_Rotate")
2387
{
2388
    sAppModule = "Sketcher";
2389
    sGroup = "Sketcher";
2390
    sMenuText = QT_TR_NOOP("Rotate / Polar transform");
2391
    sToolTipText = QT_TR_NOOP("Rotate selected geometries, making n copies, enable creation of circular patterns.");
2392
    sWhatsThis = "Sketcher_Rotate";
2393
    sStatusTip = sToolTipText;
2394
    sPixmap = "Sketcher_Rotate";
2395
    sAccel = "Z, P";
2396
    eType = ForEdit;
2397
}
2398

2399
void CmdSketcherRotate::activated(int iMsg)
2400
{
2401
    Q_UNUSED(iMsg);
2402
    std::vector<int> listOfGeoIds = getListOfSelectedGeoIds(true);
2403

2404
    if (!listOfGeoIds.empty()) {
2405
        ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerRotate>(listOfGeoIds));
2406
    }
2407
    getSelection().clearSelection();
2408
}
2409

2410
bool CmdSketcherRotate::isActive()
2411
{
2412
    return isCommandActive(getActiveGuiDocument(), true);
2413
}
2414

2415
// Scale tool =====================================================================
2416

2417
DEF_STD_CMD_A(CmdSketcherScale)
2418

2419
CmdSketcherScale::CmdSketcherScale()
2420
    : Command("Sketcher_Scale")
2421
{
2422
    sAppModule = "Sketcher";
2423
    sGroup = "Sketcher";
2424
    sMenuText = QT_TR_NOOP("Scale transform");
2425
    sToolTipText = QT_TR_NOOP("Scale selected geometries. After selecting the center point you can either enter the scale factor, or select two reference points then scale factor = length(p2-center) / length(p1-center).");
2426
    sWhatsThis = "Sketcher_Scale";
2427
    sStatusTip = sToolTipText;
2428
    sPixmap = "Sketcher_Scale";
2429
    sAccel = "Z, P, S";
2430
    eType = ForEdit;
2431
}
2432

2433
void CmdSketcherScale::activated(int iMsg)
2434
{
2435
    Q_UNUSED(iMsg);
2436
    std::vector<int> listOfGeoIds = getListOfSelectedGeoIds(true);
2437

2438
    if (!listOfGeoIds.empty()) {
2439
        ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerScale>(listOfGeoIds));
2440
    }
2441
    getSelection().clearSelection();
2442
}
2443

2444
bool CmdSketcherScale::isActive()
2445
{
2446
    return isCommandActive(getActiveGuiDocument(), true);
2447
}
2448

2449
// Translate / rectangular pattern tool =======================================================
2450

2451
DEF_STD_CMD_A(CmdSketcherTranslate)
2452

2453
CmdSketcherTranslate::CmdSketcherTranslate()
2454
    : Command("Sketcher_Translate")
2455
{
2456
    sAppModule = "Sketcher";
2457
    sGroup = "Sketcher";
2458
    sMenuText = QT_TR_NOOP("Move / Array transform");
2459
    sToolTipText = QT_TR_NOOP("Translate selected geometries. Enable creation of i * j copies.");
2460
    sWhatsThis = "Sketcher_Translate";
2461
    sStatusTip = sToolTipText;
2462
    sPixmap = "Sketcher_Translate";
2463
    sAccel = "W";
2464
    eType = ForEdit;
2465
}
2466

2467
void CmdSketcherTranslate::activated(int iMsg)
2468
{
2469
    Q_UNUSED(iMsg);
2470
    std::vector<int> listOfGeoIds = getListOfSelectedGeoIds(true);
2471

2472
    if (!listOfGeoIds.empty()) {
2473
        ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerTranslate>(listOfGeoIds));
2474
    }
2475
    getSelection().clearSelection();
2476
}
2477

2478
bool CmdSketcherTranslate::isActive()
2479
{
2480
    return isCommandActive(getActiveGuiDocument(), true);
2481
}
2482

2483
void CreateSketcherCommandsConstraintAccel()
2484
{
2485
    Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager();
2486

2487
    rcCmdMgr.addCommand(new CmdSketcherSelectConstraints());
2488
    rcCmdMgr.addCommand(new CmdSketcherSelectOrigin());
2489
    rcCmdMgr.addCommand(new CmdSketcherSelectVerticalAxis());
2490
    rcCmdMgr.addCommand(new CmdSketcherSelectHorizontalAxis());
2491
    rcCmdMgr.addCommand(new CmdSketcherSelectRedundantConstraints());
2492
    rcCmdMgr.addCommand(new CmdSketcherSelectConflictingConstraints());
2493
    rcCmdMgr.addCommand(new CmdSketcherSelectMalformedConstraints());
2494
    rcCmdMgr.addCommand(new CmdSketcherSelectPartiallyRedundantConstraints());
2495
    rcCmdMgr.addCommand(new CmdSketcherSelectElementsAssociatedWithConstraints());
2496
    rcCmdMgr.addCommand(new CmdSketcherSelectElementsWithDoFs());
2497
    rcCmdMgr.addCommand(new CmdSketcherRestoreInternalAlignmentGeometry());
2498
    rcCmdMgr.addCommand(new CmdSketcherTranslate());
2499
    rcCmdMgr.addCommand(new CmdSketcherOffset());
2500
    rcCmdMgr.addCommand(new CmdSketcherRotate());
2501
    rcCmdMgr.addCommand(new CmdSketcherScale());
2502
    rcCmdMgr.addCommand(new CmdSketcherSymmetry());
2503
    rcCmdMgr.addCommand(new CmdSketcherCopy());
2504
    rcCmdMgr.addCommand(new CmdSketcherClone());
2505
    rcCmdMgr.addCommand(new CmdSketcherMove());
2506
    rcCmdMgr.addCommand(new CmdSketcherCompCopy());
2507
    rcCmdMgr.addCommand(new CmdSketcherRectangularArray());
2508
    rcCmdMgr.addCommand(new CmdSketcherDeleteAllGeometry());
2509
    rcCmdMgr.addCommand(new CmdSketcherDeleteAllConstraints());
2510
    rcCmdMgr.addCommand(new CmdSketcherRemoveAxesAlignment());
2511
    rcCmdMgr.addCommand(new CmdSketcherCopyClipboard());
2512
    rcCmdMgr.addCommand(new CmdSketcherCut());
2513
    rcCmdMgr.addCommand(new CmdSketcherPaste());
2514
}
2515
// clang-format on
2516

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

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

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

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