// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2008 Jürgen Riegel * * * * 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 #include #include #include "SketchMirrorDialog.h" #include "SketchOrientationDialog.h" #include "TaskSketcherValidation.h" #include "Utils.h" #include "ViewProviderSketch.h" #include "Command.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 Part; using namespace Attacher; namespace SketcherGui { class ExceptionWrongInput: public Base::Exception { public: ExceptionWrongInput() : ErrMsg(QString()) {} // Pass untranslated strings, enclosed in QT_TR_NOOP() explicit ExceptionWrongInput(const char* ErrMsg) { this->ErrMsg = QObject::tr(ErrMsg); this->setMessage(ErrMsg); } ~ExceptionWrongInput() noexcept override {} QString ErrMsg; }; Attacher::eMapMode SuggestAutoMapMode(Attacher::SuggestResult::eSuggestResult* pMsgId = nullptr, QString* message = nullptr, std::vector* allmodes = nullptr) { // convert pointers into valid references, to avoid checking for null pointers everywhere Attacher::SuggestResult::eSuggestResult buf; if (!pMsgId) pMsgId = &buf; Attacher::SuggestResult::eSuggestResult& msg = *pMsgId; QString buf2; if (!message) message = &buf2; QString& msg_str = *message; App::PropertyLinkSubList tmpSupport; Gui::Selection().getAsPropertyLinkSubList(tmpSupport); Attacher::SuggestResult sugr; AttachEngine3D eng; eng.setUp(tmpSupport); eng.suggestMapModes(sugr); if (allmodes) *allmodes = sugr.allApplicableModes; msg = sugr.message; switch (msg) { case Attacher::SuggestResult::srOK: break; case Attacher::SuggestResult::srNoModesFit: msg_str = QObject::tr("There are no modes that accept the selected set of subelements"); break; case Attacher::SuggestResult::srLinkBroken: msg_str = QObject::tr("Broken link to support subelements"); break; case Attacher::SuggestResult::srUnexpectedError: msg_str = QObject::tr("Unexpected error"); break; case Attacher::SuggestResult::srIncompatibleGeometry: if (tmpSupport.getSubValues()[0].substr(0, 4) == std::string("Face")) msg_str = QObject::tr("Face is non-planar"); else msg_str = QObject::tr("Selected shapes are of wrong form (e.g., a curved edge " "where a straight one is needed)"); break; default: msg_str = QObject::tr("Unexpected error"); assert(0 /*no message for eSuggestResult enum item*/); } return sugr.bestFitMode; } }// namespace SketcherGui /* Sketch commands =======================================================*/ DEF_STD_CMD_A(CmdSketcherNewSketch) CmdSketcherNewSketch::CmdSketcherNewSketch() : Command("Sketcher_NewSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("New Sketch"); sToolTipText = QT_TR_NOOP("Creates a new sketch"); sWhatsThis = "Sketcher_NewSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_NewSketch"; } void CmdSketcherNewSketch::activated(int iMsg) { Q_UNUSED(iMsg); Attacher::eMapMode mapmode = Attacher::mmDeactivated; std::string groupName; bool bAttach = false; bool groupSelected = false; if (Gui::Selection().countObjectsOfType() > 0) { auto selection = Gui::Selection().getSelection(); if (selection.size() > 1) { Gui::TranslatedUserWarning( getActiveGuiDocument(), QObject::tr("Invalid selection"), QObject::tr("Too many objects selected")); return; } groupName = selection[0].FeatName; groupSelected = true; } else if (Gui::Selection().hasSelection()) { Attacher::SuggestResult::eSuggestResult msgid = Attacher::SuggestResult::srOK; QString msg_str; std::vector validModes; mapmode = SuggestAutoMapMode(&msgid, &msg_str, &validModes); if (msgid == Attacher::SuggestResult::srOK) bAttach = true; if (msgid != Attacher::SuggestResult::srOK && msgid != Attacher::SuggestResult::srNoModesFit) { Gui::TranslatedUserWarning( getActiveGuiDocument(), QObject::tr("Sketch mapping"), QObject::tr("Cannot map the sketch to the selected object. %1.").arg(msg_str)); return; } if (validModes.size() > 1) { validModes.insert(validModes.begin(), Attacher::mmDeactivated); bool ok; QStringList items; items.push_back(QObject::tr("Do not attach")); int iSugg = 0;// index of the auto-suggested mode in the list of valid modes for (size_t i = 0; i < validModes.size(); ++i) { auto uiStrings = AttacherGui::getUIStrings(AttachEnginePlane::getClassTypeId(), validModes[i]); items.push_back(uiStrings[0]); if (validModes[i] == mapmode) iSugg = items.size() - 1; } QString text = QInputDialog::getItem( Gui::getMainWindow(), qApp->translate("Sketcher_NewSketch", "Sketch Attachment"), qApp->translate("Sketcher_NewSketch", "Select the method to attach this sketch to selected object"), items, iSugg, false, &ok, Qt::MSWindowsFixedSizeDialogHint); if (!ok) return; int index = items.indexOf(text); if (index == 0) { bAttach = false; mapmode = Attacher::mmDeactivated; } else { bAttach = true; mapmode = validModes[index - 1]; } } } if (bAttach) { std::vector objects = Gui::Selection().getSelectionEx(); // assert (objects.size() == 1); //should have been filtered out by SuggestAutoMapMode // Gui::SelectionObject &sel_support = objects[0]; App::PropertyLinkSubList support; Gui::Selection().getAsPropertyLinkSubList(support); std::string supportString = support.getPyReprString(); // create Sketch on Face std::string FeatName = getUniqueObjectName("Sketch"); openCommand(QT_TRANSLATE_NOOP("Command", "Create a new sketch on a face")); doCommand(Doc, "App.activeDocument().addObject('Sketcher::SketchObject', '%s')", FeatName.c_str()); if (mapmode < Attacher::mmDummy_NumberOfModes) doCommand(Gui, "App.activeDocument().%s.MapMode = \"%s\"", FeatName.c_str(), AttachEngine::getModeName(mapmode).c_str()); else assert(0 /* mapmode index out of range */); doCommand( Gui, "App.activeDocument().%s.AttachmentSupport = %s", FeatName.c_str(), supportString.c_str()); doCommand(Gui, "App.activeDocument().recompute()");// recompute the sketch placement based // on its support doCommand(Gui, "Gui.activeDocument().setEdit('%s')", FeatName.c_str()); Part::Feature* part = static_cast( support.getValue());// if multi-part support, this will return 0 if (part) { App::DocumentObjectGroup* grp = part->getGroup(); if (grp) { doCommand(Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)", grp->getNameInDocument(), FeatName.c_str()); } } } else { // ask user for orientation SketchOrientationDialog Dlg; Dlg.adjustSize(); if (Dlg.exec() != QDialog::Accepted) return;// canceled Base::Vector3d p = Dlg.Pos.getPosition(); Base::Rotation r = Dlg.Pos.getRotation(); std::string FeatName = getUniqueObjectName("Sketch"); openCommand(QT_TRANSLATE_NOOP("Command", "Create a new sketch")); if (groupSelected) { doCommand(Doc, "App.activeDocument().getObject('%s').addObject(App.activeDocument().addObject('Sketcher::SketchObject', '%s'))", groupName.c_str(), FeatName.c_str()); } else { doCommand(Doc, "App.activeDocument().addObject('Sketcher::SketchObject', '%s')", FeatName.c_str()); } doCommand(Doc, "App.activeDocument().%s.Placement = App.Placement(App.Vector(%f, %f, %f), " "App.Rotation(%f, %f, %f, %f))", FeatName.c_str(), p.x, p.y, p.z, r[0], r[1], r[2], r[3]); doCommand(Doc, "App.activeDocument().%s.MapMode = \"%s\"", FeatName.c_str(), AttachEngine::getModeName(Attacher::mmDeactivated).c_str()); doCommand(Gui, "Gui.activeDocument().setEdit('%s')", FeatName.c_str()); } } bool CmdSketcherNewSketch::isActive() { if (getActiveGuiDocument()) return true; else return false; } DEF_STD_CMD_A(CmdSketcherEditSketch) CmdSketcherEditSketch::CmdSketcherEditSketch() : Command("Sketcher_EditSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Edit Sketch"); sToolTipText = QT_TR_NOOP("Opens the selected sketch for editing"); sWhatsThis = "Sketcher_EditSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_EditSketch"; } void CmdSketcherEditSketch::activated(int iMsg) { Q_UNUSED(iMsg); Gui::SelectionFilter SketchFilter("SELECT Sketcher::SketchObject COUNT 1"); if (SketchFilter.match()) { Sketcher::SketchObject* Sketch = static_cast(SketchFilter.Result[0][0].getObject()); doCommand(Gui, "Gui.activeDocument().setEdit('%s')", Sketch->getNameInDocument()); } } bool CmdSketcherEditSketch::isActive() { return Gui::Selection().countObjectsOfType() == 1; } DEF_STD_CMD_A(CmdSketcherLeaveSketch) CmdSketcherLeaveSketch::CmdSketcherLeaveSketch() : Command("Sketcher_LeaveSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Leave Sketch"); sToolTipText = QT_TR_NOOP("Exits the active sketch"); sWhatsThis = "Sketcher_LeaveSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_LeaveSketch"; eType = 0; } void CmdSketcherLeaveSketch::activated(int iMsg) { Q_UNUSED(iMsg); Gui::Document* doc = getActiveGuiDocument(); if (doc) { // checks if a Sketch Viewprovider is in Edit and is in no special mode SketcherGui::ViewProviderSketch* vp = dynamic_cast(doc->getInEdit()); if (vp && vp->getSketchMode() != ViewProviderSketch::STATUS_NONE) vp->purgeHandler(); } // See also TaskDlgEditSketch::reject doCommand(Gui, "Gui.activeDocument().resetEdit()"); doCommand(Doc, "App.ActiveDocument.recompute()"); } bool CmdSketcherLeaveSketch::isActive() { return isSketchInEdit(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherStopOperation) CmdSketcherStopOperation::CmdSketcherStopOperation() : Command("Sketcher_StopOperation") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Stop Operation"); sToolTipText = QT_TR_NOOP("Stops the active operation while in edit mode"); sWhatsThis = "Sketcher_StopOperation"; sStatusTip = sToolTipText; sPixmap = "process-stop"; eType = 0; } void CmdSketcherStopOperation::activated(int iMsg) { Q_UNUSED(iMsg); Gui::Document* doc = getActiveGuiDocument(); if (doc) { SketcherGui::ViewProviderSketch* vp = dynamic_cast(doc->getInEdit()); if (vp) { vp->purgeHandler(); } } } bool CmdSketcherStopOperation::isActive() { return isSketchInEdit(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherReorientSketch) CmdSketcherReorientSketch::CmdSketcherReorientSketch() : Command("Sketcher_ReorientSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Reorient Sketch"); sToolTipText = QT_TR_NOOP("Places the selected sketch on one of the global coordinate planes.\n" "This will clear the AttachmentSupport property."); sWhatsThis = "Sketcher_ReorientSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ReorientSketch"; } void CmdSketcherReorientSketch::activated(int iMsg) { Q_UNUSED(iMsg); Sketcher::SketchObject* sketch = Gui::Selection().getObjectsOfType().front(); if (sketch->AttachmentSupport.getValue()) { int ret = QMessageBox::question( Gui::getMainWindow(), qApp->translate("Sketcher_ReorientSketch", "Sketch Has Support"), qApp->translate("Sketcher_ReorientSketch", "Sketch with a support face cannot be reoriented.\n" "Detach it from the support?"), QMessageBox::Yes | QMessageBox::No); if (ret == QMessageBox::No) return; sketch->AttachmentSupport.setValue(nullptr); } // ask user for orientation SketchOrientationDialog Dlg; if (Dlg.exec() != QDialog::Accepted) return;// canceled Base::Vector3d p = Dlg.Pos.getPosition(); Base::Rotation r = Dlg.Pos.getRotation(); // do the right view direction std::string camstring; switch (Dlg.DirType) { case 0: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position 0 0 87\\n" " orientation 0 0 1 0\\n" " nearDistance -112.88701\\n" " farDistance 287.28702\\n" " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005 }"; break; case 1: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position 0 0 -87\\n" " orientation -1 0 0 3.1415927\\n" " nearDistance -112.88701\\n" " farDistance 287.28702\\n " " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005 }"; break; case 2: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position 0 -87 0\\n" " orientation -1 0 0 4.712389\\n" " nearDistance -112.88701\\n" " farDistance 287.28702\\n" " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005\\n\\n}"; break; case 3: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position 0 87 0\\n" " orientation 0 0.70710683 0.70710683 3.1415927\\n" " nearDistance -112.88701\\n" " farDistance 287.28702\\n" " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005\\n\\n}"; break; case 4: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position 87 0 0\\n" " orientation 0.57735026 0.57735026 0.57735026 2.0943952\\n" " nearDistance -112.887\\n" " farDistance 287.28699\\n" " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005\\n\\n}"; break; case 5: camstring = "#Inventor V2.1 ascii\\n" "OrthographicCamera {\\n" " viewportMapping ADJUST_CAMERA\\n" " position -87 0 0\\n" " orientation -0.57735026 0.57735026 0.57735026 4.1887903\\n" " nearDistance -112.887\\n" " farDistance 287.28699\\n" " aspectRatio 1\\n" " focalDistance 87\\n" " height 143.52005\\n\\n}"; break; } openCommand(QT_TRANSLATE_NOOP("Command", "Reorient sketch")); Gui::cmdAppObjectArgs( sketch, "Placement = App.Placement(App.Vector(%f, %f, %f), App.Rotation(%f, %f, %f, %f))", p.x, p.y, p.z, r[0], r[1], r[2], r[3]); doCommand(Gui, "Gui.ActiveDocument.setEdit('%s')", sketch->getNameInDocument()); } bool CmdSketcherReorientSketch::isActive() { return Gui::Selection().countObjectsOfType() == 1; } DEF_STD_CMD_A(CmdSketcherMapSketch) CmdSketcherMapSketch::CmdSketcherMapSketch() : Command("Sketcher_MapSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Attach Sketch"); sToolTipText = QT_TR_NOOP( "Attaches a sketch to the selected geometry element"); sWhatsThis = "Sketcher_MapSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_MapSketch"; } void CmdSketcherMapSketch::activated(int iMsg) { Q_UNUSED(iMsg); QString msg_str; try { Attacher::eMapMode suggMapMode; std::vector validModes; // check that selection is valid for at least some mapping mode. Attacher::SuggestResult::eSuggestResult msgid = Attacher::SuggestResult::srOK; suggMapMode = SuggestAutoMapMode(&msgid, &msg_str, &validModes); bool sketchInSelection = false; std::vector selectedSketches = Gui::Selection() .getObjectsOfType(Part::Part2DObject::getClassTypeId()); App::Document* doc = App::GetApplication().getActiveDocument(); std::vector sketches = doc->getObjectsOfType(Part::Part2DObject::getClassTypeId()); /** remove any sketches that are in the current selection to avoid * the case where the user attaches the sketch to itself issue #17629 * circular dependency check happens later, but a sketch does not appear * in its own outlist, so we remove it from the dialog list proactively * rather than wait and generate an error after the fact. */ const auto newEnd = std::ranges::remove_if(sketches, [&selectedSketches, &sketchInSelection](App::DocumentObject* obj) { if (const auto sketch = dynamic_cast(obj); sketch && std::ranges::find(selectedSketches, sketch) != selectedSketches.end()) { sketchInSelection = true; return true; } return false; }).begin(); sketches.erase(newEnd, sketches.end()); if (sketches.empty()) { Gui::TranslatedUserWarning( doc->Label.getStrValue(), qApp->translate("Sketcher_MapSketch", "No sketch found"), sketchInSelection ? qApp->translate("Sketcher_MapSketch", "Cannot attach sketch to itself!") : qApp->translate("Sketcher_MapSketch", "The document does not contain a sketch")); return; } std::sort(sketches.begin(), sketches.end(), [](const auto &a, const auto &b) { return QString::fromUtf8(a->Label.getValue()) < QString::fromUtf8(b->Label.getValue()); }); bool ok; QStringList items; for (std::vector::iterator it = sketches.begin(); it != sketches.end(); ++it) items.push_back(QString::fromUtf8((*it)->Label.getValue())); QString text = QInputDialog::getItem( Gui::getMainWindow(), qApp->translate("Sketcher_MapSketch", "Select Sketch"), sketchInSelection ? qApp->translate("Sketcher_MapSketch", "Select a sketch (some sketches not shown to prevent a circular dependency)") : qApp->translate("Sketcher_MapSketch", "Select a sketch from the list"), items, 0, false, &ok, Qt::MSWindowsFixedSizeDialogHint); if (!ok) return; int index = items.indexOf(text); Part2DObject* sketch = static_cast(sketches[index]); // check circular dependency std::vector selobjs = Gui::Selection().getSelectionEx(); for (size_t i = 0; i < selobjs.size(); ++i) { App::DocumentObject* part = static_cast(selobjs[i].getObject()); if (!part) { assert(0); throw Base::ValueError( "Unexpected null pointer in CmdSketcherMapSketch::activated"); } if (std::vector input = part->getOutListRecursive(); std::ranges::find(input, sketch) != input.end()) { throw ExceptionWrongInput( QT_TR_NOOP("Some of the selected objects depend on the sketch to be mapped. " "Circular dependencies are not allowed.")); } } // Ask for a new mode. // outline: // * find out the modes that are compatible with selection. // * Test if current mode is OK. // * fill in the dialog // * execute the dialog // * collect dialog result // * action bool bAttach = true; bool bCurIncompatible = false; // * find out the modes that are compatible with selection. const auto curMapMode = eMapMode(sketch->MapMode.getValue()); // * Test if current mode is OK. if (std::ranges::find(validModes, curMapMode) == validModes.end()) bCurIncompatible = true; // * fill in the dialog validModes.insert(validModes.begin(), Attacher::mmDeactivated); if (bCurIncompatible) validModes.push_back(curMapMode); // bool ok; //already defined // QStringList items; //already defined items.clear(); items.push_back(QObject::tr("Do not attach")); int iSugg = 0;// index of the auto-suggested mode in the list of valid modes int iCurr = 0;// index of current mode in the list of valid modes for (size_t i = 0; i < validModes.size(); ++i) { // Get the 2-element vector of caption, tooltip -- this class cannot use the tooltip, // so it is just ignored. auto uiStrings = AttacherGui::getUIStrings(AttachEnginePlane::getClassTypeId(), validModes[i]); items.push_back(uiStrings[0]); if (validModes[i] == curMapMode) { iCurr = items.size() - 1; items.back().append( bCurIncompatible ? qApp->translate("Sketcher_MapSketch", " (incompatible with selection)") : qApp->translate("Sketcher_MapSketch", " (current)")); } if (validModes[i] == suggMapMode) { iSugg = items.size() - 1; if (iSugg == 1) { iSugg = 0;// redirect deactivate to detach } else { items.back().append(qApp->translate("Sketcher_MapSketch", " (suggested)")); } } } // * execute the dialog text = QInputDialog::getItem( Gui::getMainWindow(), qApp->translate("Sketcher_MapSketch", "Sketch Attachment"), bCurIncompatible ? qApp->translate( "Sketcher_MapSketch", "Current attachment mode is incompatible with the new selection.\n" "Select the method to attach this sketch to selected objects.") : qApp->translate("Sketcher_MapSketch", "Select the method to attach this sketch to selected objects."), items, bCurIncompatible ? iSugg : iCurr, false, &ok, Qt::MSWindowsFixedSizeDialogHint); // * collect dialog result if (!ok) return; index = items.indexOf(text); if (index == 0) { bAttach = false; suggMapMode = Attacher::mmDeactivated; } else { bAttach = true; suggMapMode = validModes[index - 1]; } // * action if (bAttach) { App::PropertyLinkSubList support; Gui::Selection().getAsPropertyLinkSubList(support); std::string supportString = support.getPyReprString(); openCommand(QT_TRANSLATE_NOOP("Command", "Attach sketch")); Gui::cmdAppObjectArgs( sketch, "MapMode = \"%s\"", AttachEngine::getModeName(suggMapMode).c_str()); Gui::cmdAppObjectArgs(sketch, "AttachmentSupport = %s", supportString.c_str()); commitCommand(); doCommand(Gui, "App.activeDocument().recompute()"); } else { openCommand(QT_TRANSLATE_NOOP("Command", "Detach sketch")); Gui::cmdAppObjectArgs( sketch, "MapMode = \"%s\"", AttachEngine::getModeName(suggMapMode).c_str()); Gui::cmdAppObjectArgs(sketch, "AttachmentSupport = None"); commitCommand(); doCommand(Gui, "App.activeDocument().recompute()"); } } catch (ExceptionWrongInput& e) { Gui::TranslatedUserWarning(getActiveGuiDocument(), qApp->translate("Sketcher_MapSketch", "Map sketch"), qApp->translate("Sketcher_MapSketch", "Can't map a sketch to support:\n" "%1") .arg(e.ErrMsg.length() ? e.ErrMsg : msg_str)); } } bool CmdSketcherMapSketch::isActive() { App::Document* doc = App::GetApplication().getActiveDocument(); std::vector selobjs = Gui::Selection().getSelectionEx(); return doc && doc->countObjectsOfType() > 0 && !selobjs.empty(); } DEF_STD_CMD_A(CmdSketcherViewSketch) CmdSketcherViewSketch::CmdSketcherViewSketch() : Command("Sketcher_ViewSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Align View to Sketch"); sToolTipText = QT_TR_NOOP("Aligns the camera orientation perpendicular to the active sketch plane"); sWhatsThis = "Sketcher_ViewSketch"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ViewSketch"; sAccel = "Q, P"; eType = 0; } void CmdSketcherViewSketch::activated(int iMsg) { Q_UNUSED(iMsg); Gui::Document* doc = getActiveGuiDocument(); SketcherGui::ViewProviderSketch* vp = dynamic_cast(doc->getInEdit()); if (vp) { runCommand(Gui, "Gui.ActiveDocument.ActiveView.setCameraOrientation(" "App.Placement(Gui.editDocument().EditingTransform).Rotation.Q)"); } } bool CmdSketcherViewSketch::isActive() { return isSketchInEdit(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherValidateSketch) CmdSketcherValidateSketch::CmdSketcherValidateSketch() : Command("Sketcher_ValidateSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Validate Sketch"); sToolTipText = QT_TR_NOOP("Validates a sketch by checking for missing coincidences,\n" "invalid constraints, and degenerate geometry"); sWhatsThis = "Sketcher_ValidateSketch"; sStatusTip = sToolTipText; eType = 0; sPixmap = "Sketcher_ValidateSketch"; } void CmdSketcherValidateSketch::activated(int iMsg) { Q_UNUSED(iMsg); std::vector selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); if (selection.size() != 1) { Gui::TranslatedUserWarning( getActiveGuiDocument(), qApp->translate("CmdSketcherValidateSketch", "Wrong selection"), qApp->translate("CmdSketcherValidateSketch", "Select only 1 sketch.")); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); Gui::Control().showDialog(new TaskSketcherValidation(Obj)); } bool CmdSketcherValidateSketch::isActive() { if (Gui::Control().activeDialog()) return false; return Gui::Selection().countObjectsOfType() == 1; } DEF_STD_CMD_A(CmdSketcherMirrorSketch) CmdSketcherMirrorSketch::CmdSketcherMirrorSketch() : Command("Sketcher_MirrorSketch") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Mirror Sketch"); sToolTipText = QT_TR_NOOP("Creates a new mirrored sketch for each selected sketch\n" "by using the X or Y axes, or the origin point,\n" "as mirroring reference"); sWhatsThis = "Sketcher_MirrorSketch"; sStatusTip = sToolTipText; eType = 0; sPixmap = "Sketcher_MirrorSketch"; } void CmdSketcherMirrorSketch::activated(int iMsg) { Q_UNUSED(iMsg); std::vector selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); if (selection.empty()) { Gui::TranslatedUserWarning( getActiveGuiDocument(), qApp->translate("CmdSketcherMirrorSketch", "Wrong selection"), qApp->translate("CmdSketcherMirrorSketch", "Select at least 1 sketch")); return; } int refgeoid = -1; Sketcher::PointPos refposid = Sketcher::PointPos::none; // Ask the user the type of mirroring SketchMirrorDialog smd; if (smd.exec() != QDialog::Accepted) return; refgeoid = smd.RefGeoid; refposid = smd.RefPosid; App::Document* doc = App::GetApplication().getActiveDocument(); openCommand(QT_TRANSLATE_NOOP("Command", "Create a mirrored sketch for each selected sketch")); for (std::vector::const_iterator it = selection.begin(); it != selection.end(); ++it) { // create Sketch std::string FeatName = getUniqueObjectName("MirroredSketch"); doCommand(Doc, "App.activeDocument().addObject('Sketcher::SketchObject', '%s')", FeatName.c_str()); Sketcher::SketchObject* mirrorsketch = static_cast(doc->getObject(FeatName.c_str())); const Sketcher::SketchObject* Obj = static_cast((*it).getObject()); Base::Placement pl = Obj->Placement.getValue(); Base::Vector3d p = pl.getPosition(); Base::Rotation r = pl.getRotation(); doCommand(Doc, "App.activeDocument().%s.Placement = App.Placement(App.Vector(%f, %f, %f), " "App.Rotation(%f, %f, %f, %f))", FeatName.c_str(), p.x, p.y, p.z, r[0], r[1], r[2], r[3]); Sketcher::SketchObject* tempsketch = new Sketcher::SketchObject(); int addedGeometries = tempsketch->addGeometry(Obj->getInternalGeometry()); int addedConstraints = tempsketch->addConstraints(Obj->Constraints.getValues()); std::vector geoIdList; for (int i = 0; i <= addedGeometries; i++) geoIdList.push_back(i); tempsketch->addSymmetric(geoIdList, refgeoid, refposid); std::vector tempgeo = tempsketch->getInternalGeometry(); std::vector tempconstr = tempsketch->Constraints.getValues(); // If value of addedGeometries or addedConstraints is -1, it gets added to vector begin // iterator and that is invalid std::vector mirrorgeo(tempgeo.begin() + (addedGeometries + 1), tempgeo.end()); std::vector mirrorconstr(tempconstr.begin() + (addedConstraints + 1), tempconstr.end()); for (std::vector::const_iterator itc = mirrorconstr.begin(); itc != mirrorconstr.end(); ++itc) { if ((*itc)->First != Sketcher::GeoEnum::GeoUndef || (*itc)->First == Sketcher::GeoEnum::HAxis || (*itc)->First == Sketcher::GeoEnum::VAxis) // not x, y axes or origin (*itc)->First -= (addedGeometries + 1); if ((*itc)->Second != Sketcher::GeoEnum::GeoUndef || (*itc)->Second == Sketcher::GeoEnum::HAxis || (*itc)->Second == Sketcher::GeoEnum::VAxis) // not x, y axes or origin (*itc)->Second -= (addedGeometries + 1); if ((*itc)->Third != Sketcher::GeoEnum::GeoUndef || (*itc)->Third == Sketcher::GeoEnum::HAxis || (*itc)->Third == Sketcher::GeoEnum::VAxis) // not x, y axes or origin (*itc)->Third -= (addedGeometries + 1); } mirrorsketch->addGeometry(mirrorgeo); mirrorsketch->addConstraints(mirrorconstr); delete tempsketch; } doCommand(Gui, "App.activeDocument().recompute()"); } bool CmdSketcherMirrorSketch::isActive() { return Gui::Selection().countObjectsOfType() > 0; } DEF_STD_CMD_A(CmdSketcherMergeSketches) CmdSketcherMergeSketches::CmdSketcherMergeSketches() : Command("Sketcher_MergeSketches") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Merge Sketches"); sToolTipText = QT_TR_NOOP("Creates a new sketch by merging at least 2 selected sketches"); sWhatsThis = "Sketcher_MergeSketches"; sStatusTip = sToolTipText; eType = 0; sPixmap = "Sketcher_MergeSketch"; } void CmdSketcherMergeSketches::activated(int iMsg) { Q_UNUSED(iMsg); std::vector selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); if (selection.size() < 2) { Gui::TranslatedUserWarning( getActiveGuiDocument(), qApp->translate("CmdSketcherMergeSketches", "Wrong selection"), qApp->translate("CmdSketcherMergeSketches", "Select at least 2 sketches")); return; } App::Document* doc = App::GetApplication().getActiveDocument(); // create Sketch std::string FeatName = getUniqueObjectName("Sketch"); openCommand(QT_TRANSLATE_NOOP("Command", "Merge sketches")); doCommand( Doc, "App.activeDocument().addObject('Sketcher::SketchObject', '%s')", FeatName.c_str()); Sketcher::SketchObject* mergesketch = static_cast(doc->getObject(FeatName.c_str())); int baseGeometry = 0; int baseConstraints = 0; for (std::vector::const_iterator it = selection.begin(); it != selection.end(); ++it) { const Sketcher::SketchObject* Obj = static_cast((*it).getObject()); int addedGeometries = mergesketch->addGeometry(Obj->getInternalGeometry()); int addedConstraints = mergesketch->addCopyOfConstraints(*Obj); // Coverity issue 513796: make sure the loop below can complete if (addedConstraints < 0) { continue; // There were no constraints } if (addedConstraints - baseConstraints < 0) { throw Base::ValueError("Constraint error in CmdSketcherMergeSketches"); } for (int i = 0; i <= (addedConstraints - baseConstraints); i++) { Sketcher::Constraint* constraint = mergesketch->Constraints.getValues()[i + baseConstraints]; if (constraint->First != Sketcher::GeoEnum::GeoUndef && constraint->First != Sketcher::GeoEnum::HAxis && constraint->First != Sketcher::GeoEnum::VAxis) // not x, y axes or origin constraint->First += baseGeometry; if (constraint->Second != Sketcher::GeoEnum::GeoUndef && constraint->Second != Sketcher::GeoEnum::HAxis && constraint->Second != Sketcher::GeoEnum::VAxis) // not x, y axes or origin constraint->Second += baseGeometry; if (constraint->Third != Sketcher::GeoEnum::GeoUndef && constraint->Third != Sketcher::GeoEnum::HAxis && constraint->Third != Sketcher::GeoEnum::VAxis) // not x, y axes or origin constraint->Third += baseGeometry; } baseGeometry = addedGeometries + 1; baseConstraints = addedConstraints + 1; } // apply the placement of the first sketch in the list (#0002434) doCommand(Doc, "App.activeDocument().ActiveObject.Placement = App.activeDocument().%s.Placement", selection.front().getFeatName()); doCommand(Doc, "App.activeDocument().recompute()"); } bool CmdSketcherMergeSketches::isActive() { return Gui::Selection().countObjectsOfType() > 1; } // Acknowledgement of idea and original python macro goes to SpritKopf: // https://github.com/Spritkopf/freecad-macros/blob/master/clip-sketch/clip_sketch.FCMacro // https://forum.freecad.org/viewtopic.php?p=231481#p231085 DEF_STD_CMD_A(CmdSketcherViewSection) CmdSketcherViewSection::CmdSketcherViewSection() : Command("Sketcher_ViewSection") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Toggle Section View"); sToolTipText = QT_TR_NOOP("Toggles between section view and full view"); sWhatsThis = "Sketcher_ViewSection"; sStatusTip = sToolTipText; sPixmap = "Sketcher_ViewSection"; sAccel = "Q, S"; eType = 0; } void CmdSketcherViewSection::activated(int iMsg) { Q_UNUSED(iMsg); QString cmdStr = QLatin1String("ActiveSketch.ViewObject.TempoVis.sketchClipPlane(ActiveSketch, None, %1)\n"); Gui::Document* doc = getActiveGuiDocument(); bool revert = false; if (doc) { SketcherGui::ViewProviderSketch* vp = dynamic_cast(doc->getInEdit()); if (vp) { revert = vp->getViewOrientationFactor() < 0 ? true : false; } } cmdStr = cmdStr.arg(revert ? QLatin1String("True") : QLatin1String("False")); doCommand(Doc, cmdStr.toLatin1()); } bool CmdSketcherViewSection::isActive() { return isSketchInEdit(getActiveGuiDocument()); } /* Grid tool */ GridSpaceAction::GridSpaceAction(QObject* parent) : QWidgetAction(parent) { setEnabled(false); } void GridSpaceAction::updateWidget() { auto* sketchView = getView(); if (sketchView) { auto updateCheckBox = [](QCheckBox* checkbox, bool value) { auto checked = checkbox->checkState() == Qt::Checked; if (value != checked) { const QSignalBlocker blocker(checkbox); checkbox->setChecked(value); } }; auto updateCheckBoxFromProperty = [updateCheckBox](QCheckBox* checkbox, App::PropertyBool& property) { auto propvalue = property.getValue(); updateCheckBox(checkbox, propvalue); }; updateCheckBoxFromProperty(gridShow, sketchView->ShowGrid); updateCheckBoxFromProperty(gridAutoSpacing, sketchView->GridAuto); ParameterGrp::handle hGrp = getParameterPath(); updateCheckBox(snapToGrid, hGrp->GetBool("SnapToGrid", false)); gridSizeBox->setValue(sketchView->GridSize.getValue()); } } void GridSpaceAction::languageChange() { gridShow->setText(tr("Display grid")); gridShow->setToolTip(tr("Toggles the visibility of the grid in the active sketch")); gridShow->setStatusTip(gridAutoSpacing->toolTip()); gridAutoSpacing->setText(tr("Grid auto-spacing")); gridAutoSpacing->setToolTip(tr("Automatically adjusts the grid spacing based on the zoom level")); gridAutoSpacing->setStatusTip(gridAutoSpacing->toolTip()); sizeLabel->setText(tr("Spacing")); gridSizeBox->setToolTip(tr("Distance between two subsequent grid lines")); snapToGrid->setText(tr("Snap to grid")); snapToGrid->setToolTip( tr("New points will snap to the nearest grid line.\nPoints must be set closer than a " "fifth of the grid spacing to a grid line to snap.")); snapToGrid->setStatusTip(snapToGrid->toolTip()); } QWidget* GridSpaceAction::createWidget(QWidget* parent) { gridShow = new QCheckBox(); gridAutoSpacing = new QCheckBox(); snapToGrid = new QCheckBox(); sizeLabel = new QLabel(); gridSizeBox = new Gui::QuantitySpinBox(); gridSizeBox->setProperty("unit", QVariant(QStringLiteral("mm"))); gridSizeBox->setObjectName(QStringLiteral("gridSize")); gridSizeBox->setMaximum(99999999.0); gridSizeBox->setMinimum(0.001); QWidget* gridSizeW = new QWidget(parent); auto* layout = new QGridLayout(gridSizeW); layout->addWidget(gridShow, 0, 0, 1, 2); layout->addWidget(gridAutoSpacing, 1, 0, 1, 2); layout->addWidget(snapToGrid, 2, 0, 1, 2); layout->addWidget(sizeLabel, 3, 0); layout->addWidget(gridSizeBox, 3, 1); languageChange(); #if QT_VERSION >= QT_VERSION_CHECK(6,7,0) QObject::connect(gridShow, &QCheckBox::checkStateChanged, [this](int state) { #else QObject::connect(gridShow, &QCheckBox::stateChanged, [this](int state) { #endif auto* sketchView = getView(); if (sketchView) { auto enable = (state == Qt::Checked); sketchView->ShowGrid.setValue(enable); } }); #if QT_VERSION >= QT_VERSION_CHECK(6,7,0) QObject::connect(gridAutoSpacing, &QCheckBox::checkStateChanged, [this](int state) { #else QObject::connect(gridAutoSpacing, &QCheckBox::stateChanged, [this](int state) { #endif auto* sketchView = getView(); if (sketchView) { auto enable = (state == Qt::Checked); sketchView->GridAuto.setValue(enable); } }); #if QT_VERSION >= QT_VERSION_CHECK(6,7,0) QObject::connect(snapToGrid, &QCheckBox::checkStateChanged, [this](int state) { #else QObject::connect(snapToGrid, &QCheckBox::stateChanged, [this](int state) { #endif ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->SetBool("SnapToGrid", state == Qt::Checked); }); QObject::connect(gridSizeBox, qOverload(&Gui::QuantitySpinBox::valueChanged), [this](double val) { auto* sketchView = getView(); if (sketchView) { sketchView->GridSize.setValue(val); } }); return gridSizeW; } ViewProviderSketch* GridSpaceAction::getView() { Gui::Document* doc = Gui::Application::Instance->activeDocument(); if (doc) { return dynamic_cast(doc->getInEdit()); } return nullptr; } ParameterGrp::handle GridSpaceAction::getParameterPath() { return App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); } class CmdSketcherGrid: public Gui::Command { public: CmdSketcherGrid(); ~CmdSketcherGrid() override {} const char* className() const override { return "CmdSketcherGrid"; } void languageChange() override; protected: void activated(int iMsg) override; bool isActive() override; Gui::Action* createAction() override; public: CmdSketcherGrid(const CmdSketcherGrid&) = delete; CmdSketcherGrid(CmdSketcherGrid&&) = delete; CmdSketcherGrid& operator=(const CmdSketcherGrid&) = delete; CmdSketcherGrid& operator=(CmdSketcherGrid&&) = delete; }; CmdSketcherGrid::CmdSketcherGrid() : Command("Sketcher_Grid") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Toggle Grid"); sToolTipText = QT_TR_NOOP("Toggles the grid display in the active sketch"); sWhatsThis = "Sketcher_Grid"; sStatusTip = sToolTipText; eType = 0; } void CmdSketcherGrid::activated(int iMsg) { Q_UNUSED(iMsg); Gui::Document* doc = getActiveGuiDocument(); assert(doc); auto* sketchView = dynamic_cast(doc->getInEdit()); assert(sketchView); auto value = sketchView->ShowGrid.getValue(); sketchView->ShowGrid.setValue(!value); } Gui::Action* CmdSketcherGrid::createAction() { auto* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); pcAction->setExclusive(false); applyCommandData(this->className(), pcAction); GridSpaceAction* gsa = new GridSpaceAction(pcAction); pcAction->addAction(gsa); _pcAction = pcAction; QObject::connect(pcAction, &Gui::ActionGroup::aboutToShow, [gsa](QMenu* menu) { Q_UNUSED(menu) gsa->updateWidget(); }); return pcAction; } void CmdSketcherGrid::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); auto* gsa = static_cast(a[0]); gsa->languageChange(); } bool CmdSketcherGrid::isActive() { auto* vp = getInactiveHandlerEditModeSketchViewProvider(); if (vp) { return true; } return false; } /* Snap tool */ SnapSpaceAction::SnapSpaceAction(QObject* parent) : QWidgetAction(parent) { setEnabled(false); } void SnapSpaceAction::updateWidget(bool snapenabled) { auto updateCheckBox = [](QCheckBox* checkbox, bool value) { auto checked = checkbox->checkState() == Qt::Checked; if (value != checked) { const QSignalBlocker blocker(checkbox); checkbox->setChecked(value); } }; auto updateSpinBox = [](Gui::QuantitySpinBox* spinbox, double value) { auto currentvalue = spinbox->rawValue(); if (currentvalue != value) { const QSignalBlocker blocker(spinbox); spinbox->setValue(value); } }; ParameterGrp::handle hGrp = getParameterPath(); updateCheckBox(snapToObjects, hGrp->GetBool("SnapToObjects", true)); updateSpinBox(snapAngle, hGrp->GetFloat("SnapAngle", 5.0)); snapToObjects->setEnabled(snapenabled); angleLabel->setEnabled(snapenabled); snapAngle->setEnabled(snapenabled); } void SnapSpaceAction::languageChange() { snapToObjects->setText(tr("Snap to objects")); snapToObjects->setToolTip(tr("New points will snap to the currently preselected object. It " "will also snap to the middle of lines and arcs.")); snapToObjects->setStatusTip(snapToObjects->toolTip()); angleLabel->setText(tr("Snap angle")); snapAngle->setToolTip( tr("Angular step for tools that use 'Snap at angle'. Hold Ctrl to " "enable 'Snap at angle'. The angle starts from the positive X axis of the sketch.")); } QWidget* SnapSpaceAction::createWidget(QWidget* parent) { snapToObjects = new QCheckBox(); angleLabel = new QLabel(); snapAngle = new Gui::QuantitySpinBox(); snapAngle->setProperty("unit", QVariant(QStringLiteral("deg"))); snapAngle->setObjectName(QStringLiteral("snapAngle")); snapAngle->setMaximum(99999999.0); snapAngle->setMinimum(0); QWidget* snapW = new QWidget(parent); auto* layout = new QGridLayout(snapW); layout->addWidget(snapToObjects, 0, 0, 1, 2); layout->addWidget(angleLabel, 1, 0); layout->addWidget(snapAngle, 1, 1); languageChange(); #if QT_VERSION >= QT_VERSION_CHECK(6,7,0) QObject::connect(snapToObjects, &QCheckBox::checkStateChanged, [this](int state) { #else QObject::connect(snapToObjects, &QCheckBox::stateChanged, [this](int state) { #endif ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->SetBool("SnapToObjects", state == Qt::Checked); }); QObject::connect( snapAngle, qOverload(&Gui::QuantitySpinBox::valueChanged), [this](double val) { ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->SetFloat("SnapAngle", val); }); return snapW; } ParameterGrp::handle SnapSpaceAction::getParameterPath() { return App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); } class CmdSketcherSnap: public Gui::Command, public ParameterGrp::ObserverType { public: CmdSketcherSnap(); ~CmdSketcherSnap() override; CmdSketcherSnap(const CmdSketcherSnap&) = delete; CmdSketcherSnap(CmdSketcherSnap&&) = delete; CmdSketcherSnap& operator=(const CmdSketcherSnap&) = delete; CmdSketcherSnap& operator=(CmdSketcherSnap&&) = delete; const char* className() const override { return "CmdSketcherSnap"; } void languageChange() override; void OnChange(Base::Subject& rCaller, const char* sReason) override; protected: void activated(int iMsg) override; bool isActive() override; Gui::Action* createAction() override; private: ParameterGrp::handle getParameterPath() { return App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/Snap"); } bool snapEnabled = true; }; CmdSketcherSnap::CmdSketcherSnap() : Command("Sketcher_Snap") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Toggle Snap"); sToolTipText = QT_TR_NOOP("Toggles snapping"); sWhatsThis = "Sketcher_Snap"; sStatusTip = sToolTipText; eType = 0; ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->Attach(this); } CmdSketcherSnap::~CmdSketcherSnap() { ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->Detach(this); } void CmdSketcherSnap::OnChange(Base::Subject& rCaller, const char* sReason) { Q_UNUSED(rCaller) if (strcmp(sReason, "Snap") == 0) { snapEnabled = getParameterPath()->GetBool("Snap", true); } } void CmdSketcherSnap::activated(int iMsg) { Q_UNUSED(iMsg); getParameterPath()->SetBool("Snap", !snapEnabled); // Update the widget : if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); auto* ssa = static_cast(a[0]); ssa->updateWidget(snapEnabled); } Gui::Action* CmdSketcherSnap::createAction() { auto* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); pcAction->setExclusive(false); applyCommandData(this->className(), pcAction); SnapSpaceAction* ssa = new SnapSpaceAction(pcAction); pcAction->addAction(ssa); _pcAction = pcAction; QObject::connect(pcAction, &Gui::ActionGroup::aboutToShow, [ssa, this](QMenu* menu) { Q_UNUSED(menu) ssa->updateWidget(snapEnabled); }); return pcAction; } void CmdSketcherSnap::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); auto* ssa = static_cast(a[0]); ssa->languageChange(); } bool CmdSketcherSnap::isActive() { auto* vp = getInactiveHandlerEditModeSketchViewProvider(); if (vp) { return true; } return false; } /* Rendering Order */ RenderingOrderAction::RenderingOrderAction(QObject* parent) : QWidgetAction(parent) { setEnabled(false); } void RenderingOrderAction::updateWidget() { auto hGrp = getParameterPath(); // 1->Normal Geometry, 2->Construction, 3->External int topid = hGrp->GetInt("TopRenderGeometryId", 1); int midid = hGrp->GetInt("MidRenderGeometryId", 2); int lowid = hGrp->GetInt("LowRenderGeometryId", 3); auto idToText = [](int id) -> QString { switch (id) { case 1: return tr("Normal geometry"); case 2: return tr("Construction geometry"); case 3: return tr("External geometry"); default: // Fallback for an unexpected ID return tr("Unknown geometry"); } }; { QSignalBlocker block(this); list->clear(); QListWidgetItem* itemTop = new QListWidgetItem; itemTop->setData(Qt::UserRole, QVariant(topid)); itemTop->setText(idToText(topid)); list->insertItem(0, itemTop); QListWidgetItem* itemMid = new QListWidgetItem; itemMid->setData(Qt::UserRole, QVariant(midid)); itemMid->setText(idToText(midid)); list->insertItem(1, itemMid); QListWidgetItem* itemLow = new QListWidgetItem; itemLow->setData(Qt::UserRole, QVariant(lowid)); itemLow->setText(idToText(lowid)); list->insertItem(2, itemLow); } } void RenderingOrderAction::languageChange() { updateWidget(); } QWidget* RenderingOrderAction::createWidget(QWidget* parent) { list = new QListWidget(); list->setDragDropMode(QAbstractItemView::InternalMove); list->setDefaultDropAction(Qt::MoveAction); list->setSelectionMode(QAbstractItemView::SingleSelection); list->setDragEnabled(true); list->setFixedSize(200, 50); QWidget* renderingWidget = new QWidget(parent); auto* label = new QLabel(tr("Rendering order"), renderingWidget); auto* layout = new QVBoxLayout(renderingWidget); layout->addWidget(label); layout->addWidget(list); languageChange(); // Handle change in the order of the list entries QObject::connect(list->model(), &QAbstractItemModel::rowsMoved, [this](const QModelIndex& sourceParent, int sourceStart, int sourceEnd, const QModelIndex& destinationParent, int destinationRow) { Q_UNUSED(sourceParent) Q_UNUSED(sourceStart) Q_UNUSED(sourceEnd) Q_UNUSED(destinationParent) Q_UNUSED(destinationRow) int topid = list->item(0)->data(Qt::UserRole).toInt(); int midid = list->item(1)->data(Qt::UserRole).toInt(); int lowid = list->item(2)->data(Qt::UserRole).toInt(); auto hGrp = getParameterPath(); hGrp->SetInt("TopRenderGeometryId", topid); hGrp->SetInt("MidRenderGeometryId", midid); hGrp->SetInt("LowRenderGeometryId", lowid); }); return renderingWidget; } ParameterGrp::handle RenderingOrderAction::getParameterPath() { return App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/General"); } class CmdRenderingOrder: public Gui::Command, public ParameterGrp::ObserverType { enum class ElementType { Normal = 1, Construction = 2, External = 3, }; public: CmdRenderingOrder(); ~CmdRenderingOrder() override; CmdRenderingOrder(const CmdRenderingOrder&) = delete; CmdRenderingOrder(CmdRenderingOrder&&) = delete; CmdRenderingOrder& operator=(const CmdRenderingOrder&) = delete; CmdRenderingOrder& operator=(CmdRenderingOrder&&) = delete; const char* className() const override { return "CmdRenderingOrder"; } void languageChange() override; void OnChange(Base::Subject& rCaller, const char* sReason) override; protected: void activated(int iMsg) override; bool isActive() override; Gui::Action* createAction() override; private: ParameterGrp::handle getParameterPath() { return App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher/General"); } ElementType TopElement = ElementType::Normal; }; CmdRenderingOrder::CmdRenderingOrder() : Command("Sketcher_RenderingOrder") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Rendering Order"); sToolTipText = QT_TR_NOOP("Reorders items in the rendering order"); sWhatsThis = "Sketcher_RenderingOrder"; sStatusTip = sToolTipText; eType = 0; ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->Attach(this); TopElement = static_cast(getParameterPath()->GetInt("TopRenderGeometryId", 1)); } CmdRenderingOrder::~CmdRenderingOrder() { ParameterGrp::handle hGrp = this->getParameterPath(); hGrp->Detach(this); } void CmdRenderingOrder::OnChange(Base::Subject& rCaller, const char* sReason) { Q_UNUSED(rCaller) if (strcmp(sReason, "TopRenderGeometryId") == 0) { TopElement = static_cast(getParameterPath()->GetInt("TopRenderGeometryId", 1)); } } void CmdRenderingOrder::activated(int iMsg) { Q_UNUSED(iMsg); } Gui::Action* CmdRenderingOrder::createAction() { auto* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); pcAction->setExclusive(false); applyCommandData(this->className(), pcAction); RenderingOrderAction* roa = new RenderingOrderAction(pcAction); pcAction->addAction(roa); _pcAction = pcAction; QObject::connect(pcAction, &Gui::ActionGroup::aboutToShow, [roa](QMenu* menu) { Q_UNUSED(menu) roa->updateWidget(); }); return pcAction; } void CmdRenderingOrder::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); auto* roa = static_cast(a[0]); roa->languageChange(); } bool CmdRenderingOrder::isActive() { return isSketchInEdit(getActiveGuiDocument()); ; } void CreateSketcherCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdSketcherNewSketch()); rcCmdMgr.addCommand(new CmdSketcherEditSketch()); rcCmdMgr.addCommand(new CmdSketcherLeaveSketch()); rcCmdMgr.addCommand(new CmdSketcherStopOperation()); rcCmdMgr.addCommand(new CmdSketcherReorientSketch()); rcCmdMgr.addCommand(new CmdSketcherMapSketch()); rcCmdMgr.addCommand(new CmdSketcherViewSketch()); rcCmdMgr.addCommand(new CmdSketcherValidateSketch()); rcCmdMgr.addCommand(new CmdSketcherMirrorSketch()); rcCmdMgr.addCommand(new CmdSketcherMergeSketches()); rcCmdMgr.addCommand(new CmdSketcherViewSection()); rcCmdMgr.addCommand(new CmdSketcherGrid()); rcCmdMgr.addCommand(new CmdSketcherSnap()); rcCmdMgr.addCommand(new CmdRenderingOrder()); } // clang-format on