// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2014 Abdullah Tahiri * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CommandSketcherTools.h" #include "DrawSketchHandler.h" #include "SketchRectangularArrayDialog.h" #include "Utils.h" #include "ViewProviderSketch.h" #include #include "DrawSketchHandlerTranslate.h" #include "DrawSketchHandlerOffset.h" #include "DrawSketchHandlerRotate.h" #include "DrawSketchHandlerScale.h" #include "DrawSketchHandlerSymmetry.h" #include "SnapManager.h" // Hint: this is to prevent to re-format big parts of the file. Remove it later again. // clang-format off using namespace std; using namespace SketcherGui; using namespace Sketcher; std::vector getListOfSelectedGeoIds(bool forceInternalSelection) { std::vector listOfGeoIds = {}; // get the selection std::vector selection; selection = Gui::Selection().getSelectionEx(0, Sketcher::SketchObject::getClassTypeId()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong Selection"), QObject::tr("Select elements from a single sketch.")); return {}; } // get the needed lists and objects auto* Obj = static_cast(selection[0].getObject()); const std::vector& subNames = selection[0].getSubNames(); if (!subNames.empty()) { for (auto& name : subNames) { if (name.size() > 4 && name.substr(0, 4) == "Edge") { int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1; listOfGeoIds.push_back(geoId); } else if (name.size() > 12 && name.substr(0, 12) == "ExternalEdge") { int geoId = -std::atoi(name.substr(12, 4000).c_str()) - 2; listOfGeoIds.push_back(geoId); } else if (name.size() > 6 && name.substr(0, 6) == "Vertex") { // only if it is a GeomPoint int VtId = std::atoi(name.substr(6, 4000).c_str()) - 1; int geoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, geoId, PosId); if (isPoint(*Obj->getGeometry(geoId))) { if (geoId >= 0) { listOfGeoIds.push_back(geoId); } } } } } if (forceInternalSelection) { const size_t loopSize = listOfGeoIds.size(); for (size_t i = 0; i < loopSize; i++) { const Part::Geometry* geo = Obj->getGeometry(listOfGeoIds[i]); if (isEllipse(*geo) || isArcOfEllipse(*geo) || isArcOfHyperbola(*geo) || isArcOfParabola(*geo) || isBSplineCurve(*geo)) { const std::vector& constraints = Obj->Constraints.getValues(); for (const auto constr : constraints) { if (constr->Type == InternalAlignment && constr->Second == listOfGeoIds[i]) { if (std::ranges::find(listOfGeoIds, constr->First) == listOfGeoIds.end()) { // If the value is not found, add it to the vector listOfGeoIds.push_back(constr->First); } } } } } } if (listOfGeoIds.empty()) { Gui::NotifyUserError(Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid selection"), QT_TRANSLATE_NOOP("Notifications", "Selection has no valid geometries.")); } return listOfGeoIds; } Sketcher::SketchObject* getSketchObject() { Gui::Document* doc = Gui::Application::Instance->activeDocument(); ReleaseHandler(doc); auto* vp = static_cast(doc->getInEdit()); return vp->getSketchObject(); } // ================================================================================ // Copy bool copySelectionToClipboard(Sketcher::SketchObject* obj) { std::vector listOfGeoId = getListOfSelectedGeoIds(true); if (listOfGeoId.empty()) { return false; } sort(listOfGeoId.begin(), listOfGeoId.end()); //Export selected geometries as a formatted string. std::vector shapeGeometry; for (auto geoId : listOfGeoId) { Part::Geometry* geoNew = obj->getGeometry(geoId)->copy(); shapeGeometry.push_back(geoNew); } std::string geosAsStr = Sketcher::PythonConverter::convert( "objectStr", shapeGeometry, Sketcher::PythonConverter::Mode::OmitInternalGeometry); // Export constraints of selected geos. std::vector shapeConstraints; for (auto constr : obj->Constraints.getValues()) { auto isSelectedGeoOrAxis = [](const std::vector& vec, int value) { return (std::ranges::find(vec, value) != vec.end()) || value == GeoEnum::GeoUndef || value == GeoEnum::RtPnt || value == GeoEnum::VAxis || value == GeoEnum::HAxis; }; if (!isSelectedGeoOrAxis(listOfGeoId, constr->First) || !isSelectedGeoOrAxis(listOfGeoId, constr->Second) || !isSelectedGeoOrAxis(listOfGeoId, constr->Third)) { continue; } Constraint* temp = constr->copy(); for (size_t j = 0; j < listOfGeoId.size(); j++) { if (temp->First == listOfGeoId[j]) { temp->First = j; } if (temp->Second == listOfGeoId[j]) { temp->Second = j; } if (temp->Third == listOfGeoId[j]) { temp->Third = j; } } shapeConstraints.push_back(temp); } std::string cstrAsStr = Sketcher::PythonConverter::convert("objectStr", shapeConstraints, Sketcher::PythonConverter::GeoIdMode::AddLastGeoIdToGeoIds); std::string exportedData = "# Copied from sketcher. From:\n#objectStr = " + Gui::Command::getObjectCmd(obj) + "\n" + geosAsStr + "\n" + cstrAsStr; if (!exportedData.empty()) { QClipboard* clipboard = QGuiApplication::clipboard(); clipboard->setText(QString::fromStdString(exportedData)); return true; } return false; } DEF_STD_CMD_A(CmdSketcherCopyClipboard) CmdSketcherCopyClipboard::CmdSketcherCopyClipboard() : Command("Sketcher_CopyClipboard") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("C&opy Elements"); sToolTipText = QT_TR_NOOP("Copies the selected geometries and constraints to the clipboard"); sWhatsThis = "Sketcher_CopyClipboard"; sStatusTip = sToolTipText; sPixmap = "edit-copy"; sAccel = keySequenceToAccel(QKeySequence::Copy); eType = ForEdit; } void CmdSketcherCopyClipboard::activated(int iMsg) { Q_UNUSED(iMsg); copySelectionToClipboard(getSketchObject()); } bool CmdSketcherCopyClipboard::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ // Cut DEF_STD_CMD_A(CmdSketcherCut) CmdSketcherCut::CmdSketcherCut() : Command("Sketcher_Cut") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("C&ut Elements"); sToolTipText = QT_TR_NOOP("Cuts the selected geometries and constraints to the clipboard"); sWhatsThis = "Sketcher_Cut"; sStatusTip = sToolTipText; sPixmap = "edit-cut"; sAccel = keySequenceToAccel(QKeySequence::Cut); eType = ForEdit; } void CmdSketcherCut::activated(int iMsg) { Q_UNUSED(iMsg); if (copySelectionToClipboard(getSketchObject())) { Gui::Document* doc = getActiveGuiDocument(); ReleaseHandler(doc); auto* vp = static_cast(doc->getInEdit()); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Cut in Sketcher")); vp->deleteSelected(); Gui::Command::commitCommand(); } } bool CmdSketcherCut::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ // Paste DEF_STD_CMD_A(CmdSketcherPaste) CmdSketcherPaste::CmdSketcherPaste() : Command("Sketcher_Paste") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("P&aste Elements"); sToolTipText = QT_TR_NOOP("Pastes the geometries and constraints from the clipboard into the sketch"); sWhatsThis = "Sketcher_Paste"; sStatusTip = sToolTipText; sPixmap = "edit-paste"; sAccel = keySequenceToAccel(QKeySequence::Paste); eType = ForEdit; } void CmdSketcherPaste::activated(int iMsg) { Q_UNUSED(iMsg); Gui::Document* doc = getActiveGuiDocument(); ReleaseHandler(doc); auto* vp = static_cast(doc->getInEdit()); Sketcher::SketchObject* obj = vp->getSketchObject(); std::string data = QGuiApplication::clipboard()->text().toStdString(); if (data.find("# Copied from sketcher.", 0) == std::string::npos) { return; } data = "objectStr = " + Gui::Command::getObjectCmd(obj) +"\n" + data; Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Paste in Sketcher")); Gui::Command::doCommand(Gui::Command::Doc, data.c_str()); obj->solve(true); vp->draw(false, false); Gui::Command::commitCommand(); } bool CmdSketcherPaste::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ // Select Constraints of selected elements DEF_STD_CMD_A(CmdSketcherSelectConstraints) CmdSketcherSelectConstraints::CmdSketcherSelectConstraints() : Command("Sketcher_SelectConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Associated Constraints"); sToolTipText = QT_TR_NOOP("Selects the constraints associated with the selected geometrical elements"); sWhatsThis = "Sketcher_SelectConstraints"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectConstraints"; sAccel = "Z, K"; eType = ForEdit; } void CmdSketcherSelectConstraints::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection; selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); // Cancel any in-progress operation Gui::Document* doc = Gui::Application::Instance->activeDocument(); SketcherGui::ReleaseHandler(doc); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning(doc->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); const std::vector& constraints = Obj->Constraints.getValues(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); getSelection().clearSelection(); std::vector constraintSubNames; // go through the selected subelements int i = 0; for (auto const& constraint : constraints) { auto isRelated = [&] (const std::string& subName){ int geoId; PointPos pointPos; Data::IndexedName name = Obj->checkSubName(subName.c_str()); if (!Obj->geoIdFromShapeType(name, geoId, pointPos)) { return false; } if (pointPos != PointPos::none) { return constraint->involvesGeoIdAndPosId(geoId, pointPos); } else { return constraint->involvesGeoId(geoId); } }; if (std::ranges::any_of(SubNames, isRelated)) { constraintSubNames.push_back(PropertyConstraintList::getConstraintName(i)); } ++i; } if (!constraintSubNames.empty()) Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames); } bool CmdSketcherSelectConstraints::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ // Select Origin DEF_STD_CMD_A(CmdSketcherSelectOrigin) CmdSketcherSelectOrigin::CmdSketcherSelectOrigin() : Command("Sketcher_SelectOrigin") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Origin"); sToolTipText = QT_TR_NOOP("Selects the local origin point of the sketch"); sWhatsThis = "Sketcher_SelectOrigin"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectOrigin"; sAccel = "Z, O"; eType = ForEdit; } void CmdSketcherSelectOrigin::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject * Obj = getSketchObject(); // ViewProviderSketch * vp = static_cast(Gui::Application::Instance->getViewProvider(docobj)); Sketcher::SketchObject* Obj = // vp->getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); std::stringstream ss; ss << "RootPoint"; if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str())) Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); else Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); } bool CmdSketcherSelectOrigin::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ // Select Vertical Axis DEF_STD_CMD_A(CmdSketcherSelectVerticalAxis) CmdSketcherSelectVerticalAxis::CmdSketcherSelectVerticalAxis() : Command("Sketcher_SelectVerticalAxis") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Vertical Axis"); sToolTipText = QT_TR_NOOP("Selects the local vertical axis of the sketch"); sWhatsThis = "Sketcher_SelectVerticalAxis"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectVerticalAxis"; sAccel = "Z, V"; eType = ForEdit; } void CmdSketcherSelectVerticalAxis::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); std::stringstream ss; ss << "V_Axis"; if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str())) Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); else Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); } bool CmdSketcherSelectVerticalAxis::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ // Select Horizontal Axis DEF_STD_CMD_A(CmdSketcherSelectHorizontalAxis) CmdSketcherSelectHorizontalAxis::CmdSketcherSelectHorizontalAxis() : Command("Sketcher_SelectHorizontalAxis") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Horizontal Axis"); sToolTipText = QT_TR_NOOP("Selects the local horizontal axis of the sketch"); sWhatsThis = "Sketcher_SelectHorizontalAxis"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectHorizontalAxis"; sAccel = "Z, H"; eType = ForEdit; } void CmdSketcherSelectHorizontalAxis::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); std::stringstream ss; ss << "H_Axis"; if (Gui::Selection().isSelected(doc_name.c_str(), obj_name.c_str(), ss.str().c_str())) Gui::Selection().rmvSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); else Gui::Selection().addSelection(doc_name.c_str(), obj_name.c_str(), ss.str().c_str()); } bool CmdSketcherSelectHorizontalAxis::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectRedundantConstraints) CmdSketcherSelectRedundantConstraints::CmdSketcherSelectRedundantConstraints() : Command("Sketcher_SelectRedundantConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Redundant Constraints"); sToolTipText = QT_TR_NOOP("Selects all redundant constraints"); sWhatsThis = "Sketcher_SelectRedundantConstraints"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectRedundantConstraints"; sAccel = "Z, P, R"; eType = ForEdit; } void CmdSketcherSelectRedundantConstraints::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); // get the needed lists and objects const std::vector& solverredundant = Obj->getLastRedundant(); const std::vector& vals = Obj->Constraints.getValues(); getSelection().clearSelection(); // push the constraints std::vector constraintSubNames; int i = 0; for (std::vector::const_iterator it = vals.begin(); it != vals.end(); ++it, ++i) { for (std::vector::const_iterator itc = solverredundant.begin(); itc != solverredundant.end(); ++itc) { if ((*itc) - 1 == i) { constraintSubNames.push_back( Sketcher::PropertyConstraintList::getConstraintName(i)); break; } } } if (!constraintSubNames.empty()) Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames); } bool CmdSketcherSelectRedundantConstraints::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectMalformedConstraints) CmdSketcherSelectMalformedConstraints::CmdSketcherSelectMalformedConstraints() : Command("Sketcher_SelectMalformedConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Malformed Constraints"); sToolTipText = QT_TR_NOOP("Selects all malformed constraints"); sWhatsThis = "Sketcher_SelectMalformedConstraints"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherSelectMalformedConstraints::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); // get the needed lists and objects const std::vector& solvermalformed = Obj->getLastMalformedConstraints(); const std::vector& vals = Obj->Constraints.getValues(); getSelection().clearSelection(); // push the constraints std::vector constraintSubNames; int i = 0; for (std::vector::const_iterator it = vals.begin(); it != vals.end(); ++it, ++i) { for (std::vector::const_iterator itc = solvermalformed.begin(); itc != solvermalformed.end(); ++itc) { if ((*itc) - 1 == i) { constraintSubNames.push_back( Sketcher::PropertyConstraintList::getConstraintName(i)); break; } } } if (!constraintSubNames.empty()) Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames); } bool CmdSketcherSelectMalformedConstraints::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectPartiallyRedundantConstraints) CmdSketcherSelectPartiallyRedundantConstraints::CmdSketcherSelectPartiallyRedundantConstraints() : Command("Sketcher_SelectPartiallyRedundantConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Partially Redundant Constraints"); sToolTipText = QT_TR_NOOP("Selects all partially redundant constraints"); sWhatsThis = "Sketcher_SelectPartiallyRedundantConstraints"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherSelectPartiallyRedundantConstraints::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); // get the needed lists and objects const std::vector& solverpartiallyredundant = Obj->getLastPartiallyRedundant(); const std::vector& vals = Obj->Constraints.getValues(); getSelection().clearSelection(); // push the constraints std::vector constraintSubNames; int i = 0; for (std::vector::const_iterator it = vals.begin(); it != vals.end(); ++it, ++i) { for (std::vector::const_iterator itc = solverpartiallyredundant.begin(); itc != solverpartiallyredundant.end(); ++itc) { if ((*itc) - 1 == i) { constraintSubNames.push_back( Sketcher::PropertyConstraintList::getConstraintName(i)); break; } } } if (!constraintSubNames.empty()) Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames); } bool CmdSketcherSelectPartiallyRedundantConstraints::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectConflictingConstraints) CmdSketcherSelectConflictingConstraints::CmdSketcherSelectConflictingConstraints() : Command("Sketcher_SelectConflictingConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Conflicting Constraints"); sToolTipText = QT_TR_NOOP("Selects all conflicting constraints"); sWhatsThis = "Sketcher_SelectConflictingConstraints"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectConflictingConstraints"; sAccel = "Z, P, C"; eType = ForEdit; } void CmdSketcherSelectConflictingConstraints::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); // get the needed lists and objects const std::vector& solverconflicting = Obj->getLastConflicting(); const std::vector& vals = Obj->Constraints.getValues(); getSelection().clearSelection(); // push the constraints std::vector constraintSubNames; int i = 0; for (std::vector::const_iterator it = vals.begin(); it != vals.end(); ++it, ++i) { for (std::vector::const_iterator itc = solverconflicting.begin(); itc != solverconflicting.end(); ++itc) { if ((*itc) - 1 == i) { constraintSubNames.push_back( Sketcher::PropertyConstraintList::getConstraintName(i)); break; } } } if (!constraintSubNames.empty()) Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), constraintSubNames); } bool CmdSketcherSelectConflictingConstraints::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectElementsAssociatedWithConstraints) CmdSketcherSelectElementsAssociatedWithConstraints:: CmdSketcherSelectElementsAssociatedWithConstraints() : Command("Sketcher_SelectElementsAssociatedWithConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Associated Geometry"); sToolTipText = QT_TR_NOOP("Selects the geometrical elements associated with the selected constraints"); sWhatsThis = "Sketcher_SelectElementsAssociatedWithConstraints"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectElementsAssociatedWithConstraints"; sAccel = "Z, E"; eType = ForEdit; } void CmdSketcherSelectElementsAssociatedWithConstraints::activated(int iMsg) { Q_UNUSED(iMsg); std::vector selection = Gui::Selection().getSelectionEx(); Sketcher::SketchObject* Obj = getSketchObject(); const std::vector& SubNames = selection[0].getSubNames(); const std::vector& vals = Obj->Constraints.getValues(); getSelection().clearSelection(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); std::stringstream ss; std::vector elementSubNames; // go through the selected subelements for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { // only handle constraints if (it->size() > 10 && it->substr(0, 10) == "Constraint") { int ConstrId = Sketcher::PropertyConstraintList::getIndexFromConstraintName(*it); if (ConstrId < static_cast(vals.size())) { if (vals[ConstrId]->First != GeoEnum::GeoUndef) { ss.str(std::string()); switch (vals[ConstrId]->FirstPos) { case Sketcher::PointPos::none: ss << "Edge" << vals[ConstrId]->First + 1; break; case Sketcher::PointPos::start: case Sketcher::PointPos::end: case Sketcher::PointPos::mid: int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->First, vals[ConstrId]->FirstPos); if (vertex > -1) ss << "Vertex" << vertex + 1; break; } elementSubNames.push_back(ss.str()); } if (vals[ConstrId]->Second != GeoEnum::GeoUndef) { ss.str(std::string()); switch (vals[ConstrId]->SecondPos) { case Sketcher::PointPos::none: ss << "Edge" << vals[ConstrId]->Second + 1; break; case Sketcher::PointPos::start: case Sketcher::PointPos::end: case Sketcher::PointPos::mid: int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->Second, vals[ConstrId]->SecondPos); if (vertex > -1) ss << "Vertex" << vertex + 1; break; } elementSubNames.push_back(ss.str()); } if (vals[ConstrId]->Third != GeoEnum::GeoUndef) { ss.str(std::string()); switch (vals[ConstrId]->ThirdPos) { case Sketcher::PointPos::none: ss << "Edge" << vals[ConstrId]->Third + 1; break; case Sketcher::PointPos::start: case Sketcher::PointPos::end: case Sketcher::PointPos::mid: int vertex = Obj->getVertexIndexGeoPos(vals[ConstrId]->Third, vals[ConstrId]->ThirdPos); if (vertex > -1) ss << "Vertex" << vertex + 1; break; } elementSubNames.push_back(ss.str()); } } } } if (elementSubNames.empty()) { Gui::TranslatedUserWarning(Obj, QObject::tr("No constraint selected"), QObject::tr("At least one constraint must be selected")); } else { Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), elementSubNames); } } bool CmdSketcherSelectElementsAssociatedWithConstraints::isActive() { return isCommandNeedingConstraintActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSelectElementsWithDoFs) CmdSketcherSelectElementsWithDoFs::CmdSketcherSelectElementsWithDoFs() : Command("Sketcher_SelectElementsWithDoFs") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Select Under-Constrained Elements"); sToolTipText = QT_TR_NOOP("Selects geometrical elements where the solver still detects " "unconstrained degrees of freedom"); sWhatsThis = "Sketcher_SelectElementsWithDoFs"; sStatusTip = sToolTipText; sPixmap = "Sketcher_SelectElementsWithDoFs"; sAccel = "Z, F"; eType = ForEdit; } void CmdSketcherSelectElementsWithDoFs::activated(int iMsg) { Q_UNUSED(iMsg); getSelection().clearSelection(); Sketcher::SketchObject* Obj = getSketchObject(); std::string doc_name = Obj->getDocument()->getName(); std::string obj_name = Obj->getNameInDocument(); std::stringstream ss; auto geos = Obj->getInternalGeometry(); std::vector elementSubNames; auto testselectvertex = [&Obj, &ss, &elementSubNames](int geoId, PointPos pos) { ss.str(std::string()); int vertex = Obj->getVertexIndexGeoPos(geoId, pos); if (vertex > -1) { ss << "Vertex" << vertex + 1; elementSubNames.push_back(ss.str()); } }; auto testselectedge = [&ss, &elementSubNames](int geoId) { ss.str(std::string()); ss << "Edge" << geoId + 1; elementSubNames.push_back(ss.str()); }; int geoid = 0; for (auto geo : geos) { if (geo) { if (geo->hasExtension(Sketcher::SolverGeometryExtension::getClassTypeId())) { auto solvext = std::static_pointer_cast( geo->getExtension(Sketcher::SolverGeometryExtension::getClassTypeId()).lock()); if (solvext->getGeometry() == Sketcher::SolverGeometryExtension::NotFullyConstraint) { // Coded for consistency with getGeometryWithDependentParameters, read the // comments on that function if (solvext->getEdge() == SolverGeometryExtension::Dependent) testselectedge(geoid); if (solvext->getStart() == SolverGeometryExtension::Dependent) testselectvertex(geoid, Sketcher::PointPos::start); if (solvext->getEnd() == SolverGeometryExtension::Dependent) testselectvertex(geoid, Sketcher::PointPos::end); if (solvext->getMid() == SolverGeometryExtension::Dependent) testselectvertex(geoid, Sketcher::PointPos::mid); } } } geoid++; } if (!elementSubNames.empty()) { Gui::Selection().addSelections(doc_name.c_str(), obj_name.c_str(), elementSubNames); } } bool CmdSketcherSelectElementsWithDoFs::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherRestoreInternalAlignmentGeometry) CmdSketcherRestoreInternalAlignmentGeometry::CmdSketcherRestoreInternalAlignmentGeometry() : Command("Sketcher_RestoreInternalAlignmentGeometry") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Toggle Internal Geometry"); sToolTipText = QT_TR_NOOP("Toggles the visibility of all internal geometry"); sWhatsThis = "Sketcher_RestoreInternalAlignmentGeometry"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Element_Ellipse_All"; sAccel = "Z, I"; eType = ForEdit; } void CmdSketcherRestoreInternalAlignmentGeometry::activated(int iMsg) { Q_UNUSED(iMsg); // Cancel any in-progress operation Gui::Document* doc = Gui::Application::Instance->activeDocument(); SketcherGui::ReleaseHandler(doc); // get the selection std::vector selection; selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning(doc->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); // Return GeoId of the SubName only if it is an edge auto getEdgeGeoId = [&Obj](const std::string& SubName) { int GeoId; Sketcher::PointPos PosId; getIdsFromName(SubName, Obj, GeoId, PosId); if (PosId == Sketcher::PointPos::none) return GeoId; else return (int)GeoEnum::GeoUndef; }; // Tells if the geometry with given GeoId has internal geometry auto noInternalGeo = [&Obj](const auto& GeoId) { const Part::Geometry* geo = Obj->getGeometry(GeoId); bool hasInternalGeo = geo && (geo->is() || geo->is() || geo->is() || geo->is() || geo->is()); return !hasInternalGeo;// so it's removed }; std::vector SubGeoIds(SubNames.size()); std::transform(SubNames.begin(), SubNames.end(), SubGeoIds.begin(), getEdgeGeoId); // Handle highest GeoIds first to minimize GeoIds changing // TODO: this might not completely resolve GeoIds changing std::sort(SubGeoIds.begin(), SubGeoIds.end(), std::greater<>()); // Keep unique SubGeoIds.erase(std::unique(SubGeoIds.begin(), SubGeoIds.end()), SubGeoIds.end()); // Only for supported types and keep unique SubGeoIds.erase(std::remove_if(SubGeoIds.begin(), SubGeoIds.end(), noInternalGeo), SubGeoIds.end()); // go through the selected subelements for (const auto& GeoId : SubGeoIds) { int currentgeoid = Obj->getHighestCurveIndex(); try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Exposing Internal Geometry")); Gui::cmdAppObjectArgs(Obj, "exposeInternalGeometry(%d)", GeoId); int aftergeoid = Obj->getHighestCurveIndex(); if (aftergeoid == currentgeoid) {// if we did not expose anything, deleteunused Gui::cmdAppObjectArgs(Obj, "deleteUnusedInternalGeometry(%d)", GeoId); } } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what()); Gui::Command::abortCommand(); tryAutoRecomputeIfNotSolve(static_cast(Obj)); return; } Gui::Command::commitCommand(); tryAutoRecomputeIfNotSolve(static_cast(Obj)); } } bool CmdSketcherRestoreInternalAlignmentGeometry::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherSymmetry) CmdSketcherSymmetry::CmdSketcherSymmetry() : Command("Sketcher_Symmetry") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Mirror"); sToolTipText = QT_TR_NOOP("Creates a mirrored copy of the selected geometry"); sWhatsThis = "Sketcher_Symmetry"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Symmetry"; sAccel = "Z, S"; eType = ForEdit; } void CmdSketcherSymmetry::activated(int iMsg) { Q_UNUSED(iMsg); std::vector listOfGeoIds = getListOfSelectedGeoIds(true); if (!listOfGeoIds.empty()) { ActivateHandler(getActiveGuiDocument(), std::make_unique(listOfGeoIds)); } getSelection().clearSelection(); } bool CmdSketcherSymmetry::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ class SketcherCopy: public Gui::Command { public: enum Op { Copy, Clone, Move }; explicit SketcherCopy(const char* name); void activate(SketcherCopy::Op op); virtual void activate() = 0; }; // TODO: replace XPM cursor with SVG file static const char* cursor_createcopy[] = {"32 32 3 1", "+ c white", "# c red", ". c None", "................................", ".......+........................", ".......+........................", ".......+........................", ".......+........................", ".......+........................", "................................", ".+++++...+++++..................", "................................", ".......+........................", ".......+..............###.......", ".......+..............###.......", ".......+..............###.......", ".......+..............###.......", "......................###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###..............###.......", ".....###........................", ".....###........................", ".....###........................", ".....###........................", "................................", "................................", "................................", "................................"}; class DrawSketchHandlerCopy: public DrawSketchHandler { public: DrawSketchHandlerCopy(string geoidlist, int origingeoid, Sketcher::PointPos originpos, int nelements, SketcherCopy::Op op) : Mode(STATUS_SEEK_First) , snapMode(SnapMode::Free) , geoIdList(geoidlist) , Origin() , OriginGeoId(origingeoid) , OriginPos(originpos) , nElements(nelements) , Op(op) , EditCurve(2) {} ~DrawSketchHandlerCopy() override {} /// mode table enum SelectMode { STATUS_SEEK_First, STATUS_End }; enum class SnapMode { Free, Snap5Degree }; void mouseMove(SnapManager::SnapHandle snapHandle) override { using std::numbers::pi; Base::Vector2d onSketchPos = snapHandle.compute(); if (Mode == STATUS_SEEK_First) { if (QApplication::keyboardModifiers() == Qt::ControlModifier) snapMode = SnapMode::Snap5Degree; else snapMode = SnapMode::Free; float length = (onSketchPos - EditCurve[0]).Length(); float angle = (onSketchPos - EditCurve[0]).Angle(); Base::Vector2d endpoint = onSketchPos; if (snapMode == SnapMode::Snap5Degree) { angle = round(angle / (pi / 36)) * pi / 36; endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle), sin(angle)); } if (showCursorCoords()) { SbString text; std::string lengthString = lengthToDisplayFormat(length, 1); std::string angleString = angleToDisplayFormat(angle * 180.0 / pi, 1); text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str()); setPositionText(endpoint, text); } EditCurve[1] = endpoint; drawEdit(EditCurve); } applyCursor(); } bool pressButton(Base::Vector2d) override { if (Mode == STATUS_SEEK_First) { drawEdit(EditCurve); Mode = STATUS_End; } return true; } bool releaseButton(Base::Vector2d onSketchPos) override { Q_UNUSED(onSketchPos); if (Mode == STATUS_End) { Base::Vector2d vector = EditCurve[1] - EditCurve[0]; unsetCursor(); resetPositionText(); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Copy/clone/move geometry")); try { if (Op != SketcherCopy::Move) { Gui::cmdAppObjectArgs(sketchgui->getObject(), "addCopy(%s, App.Vector(%f, %f, 0), %s)", geoIdList.c_str(), vector.x, vector.y, (Op == SketcherCopy::Clone ? "True" : "False")); } else { Gui::cmdAppObjectArgs(sketchgui->getObject(), "addMove(%s, App.Vector(%f, %f, 0))", geoIdList.c_str(), vector.x, vector.y); } Gui::Command::commitCommand(); } catch (const Base::Exception& e) { Gui::NotifyUserError( sketchgui->getObject(), QT_TRANSLATE_NOOP("Notifications", "Error"), e.what()); Gui::Command::abortCommand(); } tryAutoRecomputeIfNotSolve( sketchgui->getObject()); EditCurve.clear(); drawEdit(EditCurve); // no code after this line, Handler gets deleted in ViewProvider sketchgui->purgeHandler(); } return true; } private: void activated() override { setCursor(QPixmap(cursor_createcopy), 7, 7); Origin = sketchgui->getObject() ->getPoint(OriginGeoId, OriginPos); EditCurve[0] = Base::Vector2d(Origin.x, Origin.y); } protected: SelectMode Mode; SnapMode snapMode; string geoIdList; Base::Vector3d Origin; int OriginGeoId; Sketcher::PointPos OriginPos; int nElements; SketcherCopy::Op Op; std::vector EditCurve; std::vector sugConstr1; }; /*---- SketcherCopy definition ----*/ SketcherCopy::SketcherCopy(const char* name) : Command(name) {} void SketcherCopy::activate(SketcherCopy::Op op) { // get the selection std::vector selection = getSelection().getSelectionEx(); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.empty()) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); int LastGeoId = 0; Sketcher::PointPos LastPointPos = Sketcher::PointPos::none; const Part::Geometry* LastGeo = nullptr; // create python command with list of elements std::stringstream stream; int geoids = 0; for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { // only handle non-external edges if (it->size() > 4 && it->substr(0, 4) == "Edge") { LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1; LastPointPos = Sketcher::PointPos::none; LastGeo = Obj->getGeometry(LastGeoId); // lines to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } else if (it->size() > 6 && it->substr(0, 6) == "Vertex") { // only if it is a GeomPoint int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1; int GeoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, GeoId, PosId); if (Obj->getGeometry(GeoId)->is()) { LastGeoId = GeoId; LastPointPos = Sketcher::PointPos::start; // points to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } } } // check if last selected element is a Vertex, not being a GeomPoint if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") { int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1; int GeoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, GeoId, PosId); if (!Obj->getGeometry(GeoId)->is()) { LastGeoId = GeoId; LastPointPos = PosId; } } if (geoids < 1) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("A copy requires at least one selected non-external geometric element")); return; } std::string geoIdList = stream.str(); // remove the last added comma and brackets to make the python list int index = geoIdList.rfind(','); geoIdList.resize(index); geoIdList.insert(0, 1, '['); geoIdList.append(1, ']'); // if the last element is not a point serving as a reference for the copy process // then make the start point of the last element the copy reference (if it exists, if not the // center point) if (LastPointPos == Sketcher::PointPos::none) { if (LastGeo->is() || LastGeo->is()) { LastPointPos = Sketcher::PointPos::mid; } else { LastPointPos = Sketcher::PointPos::start; } } // Ask the user if they want to clone or to simple copy /* int ret = QMessageBox::question(Gui::getMainWindow(), QObject::tr("Dimensional/Geometric Constraints"), QObject::tr("Do you want to clone the object, i.e. substitute dimensional constraints by geometric constraints?"), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel); // use an equality constraint if (ret == QMessageBox::Yes) { clone = true; } else if (ret == QMessageBox::Cancel) { // do nothing return; } */ ActivateHandler(getActiveGuiDocument(), std::make_unique(geoIdList, LastGeoId, LastPointPos, geoids, op)); } class CmdSketcherCopy: public SketcherCopy { public: CmdSketcherCopy(); ~CmdSketcherCopy() override {} const char* className() const override { return "CmdSketcherCopy"; } void activate() override; protected: void activated(int iMsg) override; bool isActive() override; }; CmdSketcherCopy::CmdSketcherCopy() : SketcherCopy("Sketcher_Copy") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Copy"); sToolTipText = QT_TR_NOOP( "Creates a simple copy of the geometry taking as reference the last selected point"); sWhatsThis = "Sketcher_Copy"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Copy"; sAccel = "Z, C"; eType = ForEdit; } void CmdSketcherCopy::activated(int iMsg) { Q_UNUSED(iMsg); SketcherCopy::activate(SketcherCopy::Copy); } void CmdSketcherCopy::activate() { SketcherCopy::activate(SketcherCopy::Copy); } bool CmdSketcherCopy::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ class CmdSketcherClone: public SketcherCopy { public: CmdSketcherClone(); ~CmdSketcherClone() override {} const char* className() const override { return "CmdSketcherClone"; } void activate() override; protected: void activated(int iMsg) override; bool isActive() override; }; CmdSketcherClone::CmdSketcherClone() : SketcherCopy("Sketcher_Clone") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Clone"); sToolTipText = QT_TR_NOOP("Creates a clone of the geometry taking as reference the last selected point"); sWhatsThis = "Sketcher_Clone"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Clone"; sAccel = "Z, L"; eType = ForEdit; } void CmdSketcherClone::activated(int iMsg) { Q_UNUSED(iMsg); SketcherCopy::activate(SketcherCopy::Clone); } void CmdSketcherClone::activate() { SketcherCopy::activate(SketcherCopy::Clone); } bool CmdSketcherClone::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } class CmdSketcherMove: public SketcherCopy { public: CmdSketcherMove(); ~CmdSketcherMove() override {} const char* className() const override { return "CmdSketcherMove"; } void activate() override; protected: void activated(int iMsg) override; bool isActive() override; }; CmdSketcherMove::CmdSketcherMove() : SketcherCopy("Sketcher_Move") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Move"); sToolTipText = QT_TR_NOOP("Moves the geometry taking as reference the last selected point"); sWhatsThis = "Sketcher_Move"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Move"; sAccel = "Z, M"; eType = ForEdit; } void CmdSketcherMove::activated(int iMsg) { Q_UNUSED(iMsg); SketcherCopy::activate(SketcherCopy::Move); } void CmdSketcherMove::activate() { SketcherCopy::activate(SketcherCopy::Move); } bool CmdSketcherMove::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_ACL(CmdSketcherCompCopy) CmdSketcherCompCopy::CmdSketcherCompCopy() : Command("Sketcher_CompCopy") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Clone"); sToolTipText = QT_TR_NOOP("Creates a clone of the geometry taking as reference the last selected point"); sWhatsThis = "Sketcher_CompCopy"; sStatusTip = sToolTipText; sAccel = ""; eType = ForEdit; } void CmdSketcherCompCopy::activated(int iMsg) { if (iMsg < 0 || iMsg > 2) return; // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); if (iMsg == 0) { CmdSketcherClone sc; sc.activate(); pcAction->setShortcut(QString::fromLatin1(this->getAccel())); } else if (iMsg == 1) { CmdSketcherCopy sc; sc.activate(); pcAction->setShortcut(QString::fromLatin1(this->getAccel())); } else if (iMsg == 2) { CmdSketcherMove sc; sc.activate(); pcAction->setShortcut(QStringLiteral("")); } } Gui::Action* CmdSketcherCompCopy::createAction() { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* clone = pcAction->addAction(QString()); clone->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Clone")); QAction* copy = pcAction->addAction(QString()); copy->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Copy")); QAction* move = pcAction->addAction(QString()); move->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Move")); _pcAction = pcAction; languageChange(); pcAction->setIcon(clone->icon()); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); pcAction->setShortcut(QString::fromLatin1(getAccel())); return pcAction; } void CmdSketcherCompCopy::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* clone = a[0]; clone->setText(QApplication::translate("Sketcher_CompCopy", "Clone")); clone->setToolTip(QApplication::translate( "Sketcher_Clone", "Creates a clone of the geometry taking as reference the last selected point")); clone->setStatusTip(QApplication::translate( "Sketcher_Clone", "Creates a clone of the geometry taking as reference the last selected point")); QAction* copy = a[1]; copy->setText(QApplication::translate("Sketcher_CompCopy", "Copy")); copy->setToolTip(QApplication::translate( "Sketcher_Copy", "Creates a simple copy of the geometry taking as reference the last selected point")); copy->setStatusTip(QApplication::translate( "Sketcher_Copy", "Creates a simple copy of the geometry taking as reference the last selected point")); QAction* move = a[2]; move->setText(QApplication::translate("Sketcher_CompCopy", "Move")); move->setToolTip(QApplication::translate( "Sketcher_Move", "Moves the geometry taking as reference the last selected point")); move->setStatusTip(QApplication::translate( "Sketcher_Move", "Moves the geometry taking as reference the last selected point")); } bool CmdSketcherCompCopy::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ // TODO: replace XPM cursor with SVG file /* XPM */ static const char* cursor_createrectangulararray[] = {"32 32 3 1", "+ c white", "# c red", ". c None", "................................", ".......+........................", ".......+........................", ".......+........................", ".......+........................", ".......+........................", "................................", ".+++++...+++++..................", ".......................###......", ".......+...............###......", ".......+...............###......", ".......+...............###......", ".......+......###......###......", ".......+......###......###......", "..............###......###......", "..............###......###......", ".....###......###......###......", ".....###......###......###......", ".....###......###......###......", ".....###......###......###......", ".....###......###......###......", ".....###......###......###......", ".....###......###...............", ".....###......###...............", ".....###......###...............", ".....###......###...............", ".....###........................", ".....###........................", ".....###........................", ".....###........................", "................................", "................................"}; class DrawSketchHandlerRectangularArray: public DrawSketchHandler { public: DrawSketchHandlerRectangularArray(string geoidlist, int origingeoid, Sketcher::PointPos originpos, int nelements, bool clone, int rows, int cols, bool constraintSeparation, bool equalVerticalHorizontalSpacing) : Mode(STATUS_SEEK_First) , snapMode(SnapMode::Free) , geoIdList(geoidlist) , OriginGeoId(origingeoid) , OriginPos(originpos) , nElements(nelements) , Clone(clone) , Rows(rows) , Cols(cols) , ConstraintSeparation(constraintSeparation) , EqualVerticalHorizontalSpacing(equalVerticalHorizontalSpacing) , EditCurve(2) {} ~DrawSketchHandlerRectangularArray() override {} /// mode table enum SelectMode { STATUS_SEEK_First, STATUS_End }; enum class SnapMode { Free, Snap5Degree }; void mouseMove(SnapManager::SnapHandle snapHandle) override { using std::numbers::pi; Base::Vector2d onSketchPos = snapHandle.compute(); if (Mode == STATUS_SEEK_First) { if (QApplication::keyboardModifiers() == Qt::ControlModifier) snapMode = SnapMode::Snap5Degree; else snapMode = SnapMode::Free; float length = (onSketchPos - EditCurve[0]).Length(); float angle = (onSketchPos - EditCurve[0]).Angle(); Base::Vector2d endpoint = onSketchPos; if (snapMode == SnapMode::Snap5Degree) { angle = round(angle / (pi / 36)) * pi / 36; endpoint = EditCurve[0] + length * Base::Vector2d(cos(angle), sin(angle)); } if (showCursorCoords()) { SbString text; std::string lengthString = lengthToDisplayFormat(length, 1); std::string angleString = angleToDisplayFormat(angle * 180.0 / pi, 1); text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str()); setPositionText(endpoint, text); } EditCurve[1] = endpoint; drawEdit(EditCurve); if (seekAutoConstraint( sugConstr1, endpoint, Base::Vector2d(0.0, 0.0), AutoConstraint::VERTEX)) { renderSuggestConstraintsCursor(sugConstr1); return; } } applyCursor(); } bool pressButton(Base::Vector2d) override { if (Mode == STATUS_SEEK_First) { drawEdit(EditCurve); Mode = STATUS_End; } return true; } bool releaseButton(Base::Vector2d onSketchPos) override { Q_UNUSED(onSketchPos); if (Mode == STATUS_End) { Base::Vector2d vector = EditCurve[1] - EditCurve[0]; unsetCursor(); resetPositionText(); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create copy of geometry")); try { Gui::cmdAppObjectArgs( sketchgui->getObject(), "addRectangularArray(%s, App.Vector(%f, %f, 0), %s, %d, %d, %s, %f)", geoIdList.c_str(), vector.x, vector.y, (Clone ? "True" : "False"), Cols, Rows, (ConstraintSeparation ? "True" : "False"), (EqualVerticalHorizontalSpacing ? 1.0 : 0.5)); Gui::Command::commitCommand(); } catch (const Base::Exception& e) { Gui::NotifyUserError( sketchgui, QT_TRANSLATE_NOOP("Notifications", "Error"), e.what()); Gui::Command::abortCommand(); } // add auto constraints for the destination copy if (!sugConstr1.empty()) { createAutoConstraints(sugConstr1, OriginGeoId + nElements, OriginPos); sugConstr1.clear(); } tryAutoRecomputeIfNotSolve( sketchgui->getObject()); EditCurve.clear(); drawEdit(EditCurve); // no code after this line, Handler is deleted in ViewProvider sketchgui->purgeHandler(); } return true; } private: void activated() override { setCursor(QPixmap(cursor_createrectangulararray), 7, 7); Origin = sketchgui->getObject() ->getPoint(OriginGeoId, OriginPos); EditCurve[0] = Base::Vector2d(Origin.x, Origin.y); } protected: SelectMode Mode; SnapMode snapMode; string geoIdList; Base::Vector3d Origin; int OriginGeoId; Sketcher::PointPos OriginPos; int nElements; bool Clone; int Rows; int Cols; bool ConstraintSeparation; bool EqualVerticalHorizontalSpacing; std::vector EditCurve; std::vector sugConstr1; }; DEF_STD_CMD_A(CmdSketcherRectangularArray) CmdSketcherRectangularArray::CmdSketcherRectangularArray() : Command("Sketcher_RectangularArray") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Rectangular Array"); sToolTipText = QT_TR_NOOP("Creates a rectangular array pattern of the geometry taking as " "reference the last selected point"); sWhatsThis = "Sketcher_RectangularArray"; sStatusTip = sToolTipText; sPixmap = "Sketcher_RectangularArray"; sAccel = "Z, A"; eType = ForEdit; } void CmdSketcherRectangularArray::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection; selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.empty()) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); int LastGeoId = 0; Sketcher::PointPos LastPointPos = Sketcher::PointPos::none; const Part::Geometry* LastGeo = nullptr; // create python command with list of elements std::stringstream stream; int geoids = 0; for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { // only handle non-external edges if (it->size() > 4 && it->substr(0, 4) == "Edge") { LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1; LastPointPos = Sketcher::PointPos::none; LastGeo = Obj->getGeometry(LastGeoId); // lines to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } else if (it->size() > 6 && it->substr(0, 6) == "Vertex") { // only if it is a GeomPoint int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1; int GeoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, GeoId, PosId); if (Obj->getGeometry(GeoId)->is()) { LastGeoId = GeoId; LastPointPos = Sketcher::PointPos::start; // points to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } } } // check if last selected element is a Vertex, not being a GeomPoint if (SubNames.rbegin()->size() > 6 && SubNames.rbegin()->substr(0, 6) == "Vertex") { int VtId = std::atoi(SubNames.rbegin()->substr(6, 4000).c_str()) - 1; int GeoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, GeoId, PosId); if (!Obj->getGeometry(GeoId)->is()) { LastGeoId = GeoId; LastPointPos = PosId; } } if (geoids < 1) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("A copy requires at least one selected non-external geometric element")); return; } std::string geoIdList = stream.str(); // remove the last added comma and brackets to make the python list int index = geoIdList.rfind(','); geoIdList.resize(index); geoIdList.insert(0, 1, '['); geoIdList.append(1, ']'); // if the last element is not a point serving as a reference for the copy process // then make the start point of the last element the copy reference (if it exists, if not the // center point) if (LastPointPos == Sketcher::PointPos::none) { if (LastGeo->is() || LastGeo->is()) { LastPointPos = Sketcher::PointPos::mid; } else { LastPointPos = Sketcher::PointPos::start; } } // Pop-up asking for values SketchRectangularArrayDialog slad; if (slad.exec() == QDialog::Accepted) { ActivateHandler(getActiveGuiDocument(), std::make_unique(geoIdList, LastGeoId, LastPointPos, geoids, slad.Clone, slad.Rows, slad.Cols, slad.ConstraintSeparation, slad.EqualVerticalHorizontalSpacing)); } } bool CmdSketcherRectangularArray::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherDeleteAllGeometry) CmdSketcherDeleteAllGeometry::CmdSketcherDeleteAllGeometry() : Command("Sketcher_DeleteAllGeometry") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Delete All Geometry"); sToolTipText = QT_TR_NOOP("Deletes all geometry and their constraints in the current sketch, " "with the exception of external geometry"); sWhatsThis = "Sketcher_DeleteAllGeometry"; sStatusTip = sToolTipText; sPixmap = "Sketcher_DeleteGeometry"; sAccel = ""; eType = ForEdit; } void CmdSketcherDeleteAllGeometry::activated(int iMsg) { Q_UNUSED(iMsg); int ret = QMessageBox::question( Gui::getMainWindow(), QObject::tr("Delete All Geometry"), QObject::tr("Delete all geometry and constraints?"), QMessageBox::Yes, QMessageBox::Cancel); // use an equality constraint if (ret == QMessageBox::Yes) { getSelection().clearSelection(); Sketcher::SketchObject* Obj = getSketchObject(); try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Delete all geometry")); Gui::cmdAppObjectArgs(Obj, "deleteAllGeometry()"); Gui::Command::commitCommand(); } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Failed to delete all geometry"), e.what()); Gui::Command::abortCommand(); } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher"); bool autoRecompute = hGrp->GetBool("AutoRecompute", false); if (autoRecompute) Gui::Command::updateActive(); else Obj->solve(); } else if (ret == QMessageBox::Cancel) { // do nothing return; } } bool CmdSketcherDeleteAllGeometry::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherDeleteAllConstraints) CmdSketcherDeleteAllConstraints::CmdSketcherDeleteAllConstraints() : Command("Sketcher_DeleteAllConstraints") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Delete All Constraints"); sToolTipText = QT_TR_NOOP("Deletes all constraints in the sketch"); sWhatsThis = "Sketcher_DeleteAllConstraints"; sStatusTip = sToolTipText; sPixmap = "Sketcher_DeleteConstraints"; sAccel = ""; eType = ForEdit; } void CmdSketcherDeleteAllConstraints::activated(int iMsg) { Q_UNUSED(iMsg); int ret = QMessageBox::question( Gui::getMainWindow(), QObject::tr("Delete All Constraints"), QObject::tr("Delete all the constraints in the sketch?"), QMessageBox::Yes, QMessageBox::Cancel); if (ret == QMessageBox::Yes) { getSelection().clearSelection(); Sketcher::SketchObject* Obj = getSketchObject(); try { Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Delete all constraints")); Gui::cmdAppObjectArgs(Obj, "deleteAllConstraints()"); Gui::Command::commitCommand(); } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Failed to delete all constraints"), e.what()); Gui::Command::abortCommand(); } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher"); bool autoRecompute = hGrp->GetBool("AutoRecompute", false); if (autoRecompute) Gui::Command::updateActive(); else Obj->solve(); } else if (ret == QMessageBox::Cancel) { // do nothing return; } } bool CmdSketcherDeleteAllConstraints::isActive() { return isCommandActive(getActiveGuiDocument()); } // ================================================================================ DEF_STD_CMD_A(CmdSketcherRemoveAxesAlignment) CmdSketcherRemoveAxesAlignment::CmdSketcherRemoveAxesAlignment() : Command("Sketcher_RemoveAxesAlignment") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Remove Axes Alignment"); sToolTipText = QT_TR_NOOP("Modifies the constraints to remove axes alignment while trying to " "preserve the constraint relationship of the selection"); sWhatsThis = "Sketcher_RemoveAxesAlignment"; sStatusTip = sToolTipText; sPixmap = "Sketcher_RemoveAxesAlignment"; sAccel = "Z, R"; eType = ForEdit; } void CmdSketcherRemoveAxesAlignment::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection; selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.empty()) { Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); getSelection().clearSelection(); int LastGeoId = 0; // create python command with list of elements std::stringstream stream; int geoids = 0; for (std::vector::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) { // only handle non-external edges if (it->size() > 4 && it->substr(0, 4) == "Edge") { LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1; // lines to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } else if (it->size() > 6 && it->substr(0, 6) == "Vertex") { // only if it is a GeomPoint int VtId = std::atoi(it->substr(6, 4000).c_str()) - 1; int GeoId; Sketcher::PointPos PosId; Obj->getGeoVertexIndex(VtId, GeoId, PosId); if (Obj->getGeometry(GeoId)->is()) { LastGeoId = GeoId; // points to copy if (LastGeoId >= 0) { geoids++; stream << LastGeoId << ","; } } } } if (geoids < 1) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("Removal of axes alignment requires at least one selected " "non-external geometric element")); return; } std::string geoIdList = stream.str(); // remove the last added comma and brackets to make the python list int index = geoIdList.rfind(','); geoIdList.resize(index); geoIdList.insert(0, 1, '['); geoIdList.append(1, ']'); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Remove Axes Alignment")); try { Gui::cmdAppObjectArgs(Obj, "removeAxesAlignment(%s)", geoIdList.c_str()); Gui::Command::commitCommand(); } catch (const Base::Exception& e) { Gui::NotifyUserError(Obj, QT_TRANSLATE_NOOP("Notifications", "Error"), e.what()); Gui::Command::abortCommand(); } tryAutoRecomputeIfNotSolve(static_cast(Obj)); } bool CmdSketcherRemoveAxesAlignment::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // Offset tool ===================================================================== DEF_STD_CMD_A(CmdSketcherOffset) CmdSketcherOffset::CmdSketcherOffset() : Command("Sketcher_Offset") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Offset"); sToolTipText = QT_TR_NOOP("Adds an equidistant closed contour around selected geometry: positive values offset outward, negative values inward"); sWhatsThis = "Sketcher_Offset"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Offset"; sAccel = "Z, T"; eType = ForEdit; } void CmdSketcherOffset::activated(int iMsg) { Q_UNUSED(iMsg); std::vector listOfGeoIds = {}; // get the selection std::vector selection; selection = getSelection().getSelectionEx(0, Sketcher::SketchObject::getClassTypeId()); // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { Gui::TranslatedUserWarning( getActiveGuiDocument(), QObject::tr("Wrong selection"), QObject::tr("Select elements from a single sketch.")); return; } // get the needed lists and objects auto* Obj = static_cast(selection[0].getObject()); const std::vector& subNames = selection[0].getSubNames(); if (!subNames.empty()) { for (auto& name : subNames) { int geoId; if (name.size() > 4 && name.substr(0, 4) == "Edge") { geoId = std::atoi(name.substr(4, 4000).c_str()) - 1; } else if (name.size() > 12 && name.substr(0, 12) == "ExternalEdge") { geoId = -std::atoi(name.substr(12, 4000).c_str()) - 2; } else { continue; } const Part::Geometry* geo = Obj->getGeometry(geoId); if (!isPoint(*geo) && !isBSplineCurve(*geo) && !isEllipse(*geo) && !isArcOfEllipse(*geo) && !isArcOfHyperbola(*geo) && !isArcOfParabola(*geo) && !GeometryFacade::isInternalAligned(geo)) { // Currently ellipse/parabola/hyperbola/bspline are not handled correctly. // Occ engine gives offset of those as set of lines and arcs and does not seem to work consistently. listOfGeoIds.push_back(geoId); } } } if (listOfGeoIds.size() != 0) { ActivateHandler(getActiveGuiDocument(), std::make_unique(listOfGeoIds)); } else { getSelection().clearSelection(); Gui::NotifyUserError(Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid selection"), QT_TRANSLATE_NOOP("Notifications", "Selection has no valid geometries. B-splines and points are not supported yet.")); } } bool CmdSketcherOffset::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // Rotate tool ===================================================================== DEF_STD_CMD_A(CmdSketcherRotate) CmdSketcherRotate::CmdSketcherRotate() : Command("Sketcher_Rotate") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Rotate / Polar Transform"); sToolTipText = QT_TR_NOOP("Rotates the selected geometry by creating 'n' copies, enabling circular pattern creation"); sWhatsThis = "Sketcher_Rotate"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Rotate"; sAccel = "Z, P"; eType = ForEdit; } void CmdSketcherRotate::activated(int iMsg) { Q_UNUSED(iMsg); std::vector listOfGeoIds = getListOfSelectedGeoIds(true); if (!listOfGeoIds.empty()) { ActivateHandler(getActiveGuiDocument(), std::make_unique(listOfGeoIds)); } getSelection().clearSelection(); } bool CmdSketcherRotate::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // Scale tool ===================================================================== DEF_STD_CMD_A(CmdSketcherScale) CmdSketcherScale::CmdSketcherScale() : Command("Sketcher_Scale") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Scale"); sToolTipText = QT_TR_NOOP("Scales the selected geometries"); sWhatsThis = "Sketcher_Scale"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Scale"; sAccel = "Z, P, S"; eType = ForEdit; } void CmdSketcherScale::activated(int iMsg) { Q_UNUSED(iMsg); std::vector listOfGeoIds = getListOfSelectedGeoIds(true); if (!listOfGeoIds.empty()) { ActivateHandler(getActiveGuiDocument(), std::make_unique(listOfGeoIds)); } getSelection().clearSelection(); } bool CmdSketcherScale::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // Translate / rectangular pattern tool ======================================================= DEF_STD_CMD_A(CmdSketcherTranslate) CmdSketcherTranslate::CmdSketcherTranslate() : Command("Sketcher_Translate") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Move / Array Transform"); sToolTipText = QT_TR_NOOP("Translates the selected geometries and enables the creation of 'i' * 'j' copies"); sWhatsThis = "Sketcher_Translate"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Translate"; sAccel = "W"; eType = ForEdit; } void CmdSketcherTranslate::activated(int iMsg) { Q_UNUSED(iMsg); std::vector listOfGeoIds = getListOfSelectedGeoIds(true); if (!listOfGeoIds.empty()) { ActivateHandler(getActiveGuiDocument(), std::make_unique(listOfGeoIds)); } getSelection().clearSelection(); } bool CmdSketcherTranslate::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } void CreateSketcherCommandsConstraintAccel() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdSketcherSelectConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectOrigin()); rcCmdMgr.addCommand(new CmdSketcherSelectVerticalAxis()); rcCmdMgr.addCommand(new CmdSketcherSelectHorizontalAxis()); rcCmdMgr.addCommand(new CmdSketcherSelectRedundantConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectConflictingConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectMalformedConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectPartiallyRedundantConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectElementsAssociatedWithConstraints()); rcCmdMgr.addCommand(new CmdSketcherSelectElementsWithDoFs()); rcCmdMgr.addCommand(new CmdSketcherRestoreInternalAlignmentGeometry()); rcCmdMgr.addCommand(new CmdSketcherTranslate()); rcCmdMgr.addCommand(new CmdSketcherOffset()); rcCmdMgr.addCommand(new CmdSketcherRotate()); rcCmdMgr.addCommand(new CmdSketcherScale()); rcCmdMgr.addCommand(new CmdSketcherSymmetry()); rcCmdMgr.addCommand(new CmdSketcherCopy()); rcCmdMgr.addCommand(new CmdSketcherClone()); rcCmdMgr.addCommand(new CmdSketcherMove()); rcCmdMgr.addCommand(new CmdSketcherCompCopy()); rcCmdMgr.addCommand(new CmdSketcherRectangularArray()); rcCmdMgr.addCommand(new CmdSketcherDeleteAllGeometry()); rcCmdMgr.addCommand(new CmdSketcherDeleteAllConstraints()); rcCmdMgr.addCommand(new CmdSketcherRemoveAxesAlignment()); rcCmdMgr.addCommand(new CmdSketcherCopyClipboard()); rcCmdMgr.addCommand(new CmdSketcherCut()); rcCmdMgr.addCommand(new CmdSketcherPaste()); } // clang-format on void SketcherGui::centerScale(double scaleFactor) { Gui::Document* doc = Gui::Application::Instance->activeDocument(); auto* vp = static_cast(doc->getInEdit()); auto scaler = DrawSketchHandlerScale::make_centerScaleAll(vp, scaleFactor, false); scaler->setSketchGui(vp); scaler->executeCommands(); if (auto* view3d = dynamic_cast(doc->getActiveView())) { auto viewer = view3d->getViewer(); bool isAnimating = viewer->isAnimationEnabled(); viewer->setAnimationEnabled(false); viewer->scale(scaleFactor); viewer->setAnimationEnabled(isAnimating); } }