// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2017 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 "DrawSketchHandler.h" #include "Utils.h" #include "ViewProviderSketch.h" #include "SnapManager.h" using namespace std; using namespace SketcherGui; using namespace Sketcher; void ActivateBSplineHandler(Gui::Document* doc, DrawSketchHandler* handler) { std::unique_ptr ptr(handler); if (doc) { if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom()) { SketcherGui::ViewProviderSketch* vp = static_cast( doc->getInEdit() ); vp->purgeHandler(); vp->activateHandler(std::move(ptr)); } } } /// For a knot given by (GeoId, PosId) finds the B-Spline and the knot's /// index within it (by OCC numbering). /// Returns true if the entities are found, false otherwise. /// If returns false, `splineGeoId` and `knotIndexOCC` have garbage values. bool findBSplineAndKnotIndex( Sketcher::SketchObject* Obj, int knotGeoId, Sketcher::PointPos knotPosId, int& splineGeoId, int& knotIndexOCC ) { for (auto const constraint : Obj->Constraints.getValues()) { if (constraint->Type == Sketcher::InternalAlignment && constraint->First == knotGeoId && constraint->AlignmentType == Sketcher::BSplineKnotPoint) { splineGeoId = constraint->Second; knotIndexOCC = constraint->InternalAlignmentIndex + 1; return true; // we have already found our knot. } } // TODO: what to do if multiple splines have the same first/last point? const Part::Geometry* geo = Obj->getGeometry(knotGeoId); if (geo->is()) { splineGeoId = knotGeoId; switch (knotPosId) { case Sketcher::PointPos::start: knotIndexOCC = 1; return true; case Sketcher::PointPos::end: knotIndexOCC = static_cast(geo)->countKnots(); return true; default: // If we reach here something went wrong. // isBsplineKnotOrEndPoint (that we expect is run before) will // only accept spline knotGeoID if knotPosId is start or end. return false; } } return false; } // Convert to NURBS DEF_STD_CMD_A(CmdSketcherConvertToNURBS) CmdSketcherConvertToNURBS::CmdSketcherConvertToNURBS() : Command("Sketcher_BSplineConvertToNURBS") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Geometry to B-Spline"); sToolTipText = QT_TR_NOOP("Converts the selected geometry to B-splines"); sWhatsThis = "Sketcher_BSplineConvertToNURBS"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineConvertToNURBS"; sAccel = ""; eType = ForEdit; } void CmdSketcherConvertToNURBS::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) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); openCommand(QT_TRANSLATE_NOOP("Command", "Convert to NURBS")); std::vector GeoIdList; for (const auto& subName : SubNames) { // only handle edges if (subName.size() > 4 && subName.substr(0, 4) == "Edge") { int GeoId = std::atoi(subName.substr(4, 4000).c_str()) - 1; GeoIdList.push_back(GeoId); } else if (subName.size() > 12 && subName.substr(0, 12) == "ExternalEdge") { int GeoId = -(std::atoi(subName.substr(12, 4000).c_str()) + 2); GeoIdList.push_back(GeoId); } } // for creating the poles and knots for (auto GeoId : GeoIdList) { Gui::cmdAppObjectArgs(selection[0].getObject(), "convertToNURBS(%d) ", GeoId); } for (auto GeoId : GeoIdList) { Gui::cmdAppObjectArgs(selection[0].getObject(), "exposeInternalGeometry(%d)", GeoId); } if (GeoIdList.empty()) { abortCommand(); Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("None of the selected elements is an edge.") ); } else { commitCommand(); } tryAutoRecomputeIfNotSolve(Obj); } bool CmdSketcherConvertToNURBS::isActive() { return isCommandNeedingGeometryActive(getActiveGuiDocument()); } // Increase degree of the spline DEF_STD_CMD_A(CmdSketcherIncreaseDegree) CmdSketcherIncreaseDegree::CmdSketcherIncreaseDegree() : Command("Sketcher_BSplineIncreaseDegree") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Increase B-Spline Degree"); sToolTipText = QT_TR_NOOP("Increases the degree of the B-spline"); sWhatsThis = "Sketcher_BSplineIncreaseDegree"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineIncreaseDegree"; sAccel = ""; eType = ForEdit; } void CmdSketcherIncreaseDegree::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) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); auto* Obj = static_cast(selection[0].getObject()); openCommand(QT_TRANSLATE_NOOP("Command", "Increase B-spline degree")); bool ignored = false; for (size_t i = 0; i < SubNames.size(); i++) { // only handle edges if (SubNames[i].size() > 4 && SubNames[i].substr(0, 4) == "Edge") { int GeoId = std::atoi(SubNames[i].substr(4, 4000).c_str()) - 1; const Part::Geometry* geo = Obj->getGeometry(GeoId); if (geo->is()) { Gui::cmdAppObjectArgs(Obj, "increaseBSplineDegree(%d) ", GeoId); // add new control points Gui::cmdAppObjectArgs(Obj, "exposeInternalGeometry(%d)", GeoId); } else { ignored = true; } } } if (ignored) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr( "At least one of the selected " "objects was not a B-spline and was ignored." ) ); } commitCommand(); tryAutoRecomputeIfNotSolve(Obj); getSelection().clearSelection(); } bool CmdSketcherIncreaseDegree::isActive() { return isCommandNeedingBSplineActive(getActiveGuiDocument()); } // Decrease degree of the spline DEF_STD_CMD_A(CmdSketcherDecreaseDegree) CmdSketcherDecreaseDegree::CmdSketcherDecreaseDegree() : Command("Sketcher_BSplineDecreaseDegree") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Decrease B-Spline Degree"); sToolTipText = QT_TR_NOOP("Decreases the degree of the B-spline"); sWhatsThis = "Sketcher_BSplineDecreaseDegree"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineDecreaseDegree"; sAccel = ""; eType = ForEdit; } void CmdSketcherDecreaseDegree::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) { return; } getSelection().clearSelection(); // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); openCommand(QT_TRANSLATE_NOOP("Command", "Decrease B-spline degree")); bool ignored = false; for (size_t i = 0; i < SubNames.size(); i++) { // only handle edges if (SubNames[i].size() > 4 && SubNames[i].substr(0, 4) == "Edge") { int GeoId = std::atoi(SubNames[i].substr(4, 4000).c_str()) - 1; const Part::Geometry* geo = Obj->getGeometry(GeoId); if (geo->is()) { Gui::cmdAppObjectArgs(selection[0].getObject(), "decreaseBSplineDegree(%d) ", GeoId); // add new control points // Currently exposeInternalGeometry is called from within decreaseBSplineDegree // because the old spline is deleted and a new one is added so that the GeoId is // invalid afterwards // Gui::cmdAppObjectArgs(selection[0].getObject(), "exposeInternalGeometry(%d)", // GeoId); break; // cannot handle more than spline because the GeoIds will be invalidated // after the first change } else { ignored = true; } } } if (ignored) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr( "At least one of the selected " "objects was not a B-spline and was ignored." ) ); } commitCommand(); tryAutoRecomputeIfNotSolve(Obj); getSelection().clearSelection(); } bool CmdSketcherDecreaseDegree::isActive() { return isCommandNeedingBSplineActive(getActiveGuiDocument()); } bool isCommandNeedingBSplineKnotActive(Gui::Document* doc) { if (!isCommandActive(doc)) { return false; } std::vector sel = Gui::Selection().getSelectionEx( doc->getDocument()->getName(), Sketcher::SketchObject::getClassTypeId() ); if (sel.size() == 1) { const std::vector& names = sel[0].getSubNames(); if (names.size() != 1) { return false; } auto* Obj = static_cast(sel[0].getObject()); if (!Obj) { return false; } const std::string& name = names[0]; int geoId {GeoEnum::GeoUndef}; PointPos posId {PointPos::none}; getIdsFromName(name, Obj, geoId, posId); if (geoId == GeoEnum::GeoUndef) { return false; } int splineGeoId {GeoEnum::GeoUndef}; int knotIndexOCC {-1}; return isBsplineKnotOrEndPoint(Obj, geoId, posId) && findBSplineAndKnotIndex(Obj, geoId, posId, splineGeoId, knotIndexOCC); } return false; } DEF_STD_CMD_A(CmdSketcherIncreaseKnotMultiplicity) CmdSketcherIncreaseKnotMultiplicity::CmdSketcherIncreaseKnotMultiplicity() : Command("Sketcher_BSplineIncreaseKnotMultiplicity") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Increase Knot Multiplicity"); sToolTipText = QT_TR_NOOP("Increases the multiplicity of the selected knot of a B-spline"); sWhatsThis = "Sketcher_BSplineIncreaseKnotMultiplicity"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineIncreaseKnotMultiplicity"; sAccel = ""; eType = ForEdit; } void CmdSketcherIncreaseKnotMultiplicity::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) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.size() > 1) { // Check that only one object is selected, // as we need only one object to get the new GeoId after multiplicity change Gui::TranslatedUserWarning( getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("The selection comprises more than one item. Select just one knot.") ); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); openCommand(QT_TRANSLATE_NOOP("Command", "Increase knot multiplicity")); int GeoId; Sketcher::PointPos PosId; getIdsFromName(SubNames[0], Obj, GeoId, PosId); int splineGeoId; int knotIndexOCC; bool applied = false; bool notaknot = !( isBsplineKnotOrEndPoint(Obj, GeoId, PosId) && findBSplineAndKnotIndex(Obj, GeoId, PosId, splineGeoId, knotIndexOCC) ); boost::uuids::uuid bsplinetag; if (!notaknot) { bsplinetag = Obj->getGeometry(splineGeoId)->getTag(); try { Gui::cmdAppObjectArgs( selection[0].getObject(), "modifyBSplineKnotMultiplicity(%d, %d, %d) ", splineGeoId, knotIndexOCC, 1 ); applied = true; // Warning: GeoId list might have changed // as the consequence of deleting pole circles and // particularly B-spline GeoID might have changed. } catch (const Base::CADKernelError& e) { e.reportException(); if (e.getTranslatable()) { Gui::TranslatedUserError( Obj, QObject::tr("CAD Kernel Error"), QObject::tr(e.getMessage().c_str()) ); } getSelection().clearSelection(); } catch (const Base::Exception& e) { e.reportException(); if (e.getTranslatable()) { Gui::TranslatedUserError( Obj, QObject::tr("Input Error"), QObject::tr(e.getMessage().c_str()) ); } getSelection().clearSelection(); } } if (notaknot) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("None of the selected elements is a knot of a B-spline") ); } if (applied) { // find new geoid for B-spline as GeoId might have changed const std::vector& gvals = Obj->getInternalGeometry(); int ngeoid = 0; bool ngfound = false; for (std::vector::const_iterator geo = gvals.begin(); geo != gvals.end(); geo++, ngeoid++) { if ((*geo) && (*geo)->getTag() == bsplinetag) { ngfound = true; break; } } if (ngfound) { try { // add internalalignment for new pole Gui::cmdAppObjectArgs(selection[0].getObject(), "exposeInternalGeometry(%d)", ngeoid); } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what() ); getSelection().clearSelection(); } } } if (!applied) { abortCommand(); } else { commitCommand(); } tryAutoRecomputeIfNotSolve(Obj); getSelection().clearSelection(); } bool CmdSketcherIncreaseKnotMultiplicity::isActive() { return isCommandNeedingBSplineKnotActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherDecreaseKnotMultiplicity) CmdSketcherDecreaseKnotMultiplicity::CmdSketcherDecreaseKnotMultiplicity() : Command("Sketcher_BSplineDecreaseKnotMultiplicity") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Decrease Knot Multiplicity"); sToolTipText = QT_TR_NOOP("Decreases the multiplicity of the selected knot of a B-spline"); sWhatsThis = "Sketcher_BSplineDecreaseKnotMultiplicity"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineDecreaseKnotMultiplicity"; sAccel = ""; eType = ForEdit; } void CmdSketcherDecreaseKnotMultiplicity::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) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.size() > 1) { // Check that only one object is selected, // as we need only one object to get the new GeoId after multiplicity change Gui::TranslatedUserWarning( getActiveGuiDocument()->getDocument(), QObject::tr("Wrong selection"), QObject::tr("The selection comprises more than one item. Select just one knot.") ); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); openCommand(QT_TRANSLATE_NOOP("Command", "Decrease knot multiplicity")); int GeoId; Sketcher::PointPos PosId; getIdsFromName(SubNames[0], Obj, GeoId, PosId); int splineGeoId; int knotIndexOCC; bool applied = false; bool notaknot = !( isBsplineKnotOrEndPoint(Obj, GeoId, PosId) && findBSplineAndKnotIndex(Obj, GeoId, PosId, splineGeoId, knotIndexOCC) ); boost::uuids::uuid bsplinetag; if (!notaknot) { bsplinetag = Obj->getGeometry(splineGeoId)->getTag(); try { Gui::cmdAppObjectArgs( selection[0].getObject(), "modifyBSplineKnotMultiplicity(%d, %d, %d) ", splineGeoId, knotIndexOCC, -1 ); applied = true; // Warning: GeoId list might have changed as the consequence of deleting pole circles // and particularly B-spline GeoID might have changed. } catch (const Base::Exception& e) { Gui::TranslatedUserError( Obj, QObject::tr("Error"), QObject::tr(getStrippedPythonExceptionString(e).c_str()) ); getSelection().clearSelection(); } } if (notaknot) { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("None of the selected elements is a knot of a B-spline") ); } if (applied) { // find new geoid for B-spline as GeoId might have changed const std::vector& gvals = Obj->getInternalGeometry(); int ngeoid = 0; bool ngfound = false; for (std::vector::const_iterator geo = gvals.begin(); geo != gvals.end(); geo++, ngeoid++) { if ((*geo) && (*geo)->getTag() == bsplinetag) { ngfound = true; break; } } if (ngfound) { try { // add internalalignment for new pole Gui::cmdAppObjectArgs(selection[0].getObject(), "exposeInternalGeometry(%d)", ngeoid); } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what() ); getSelection().clearSelection(); } } } if (!applied) { abortCommand(); } else { commitCommand(); } tryAutoRecomputeIfNotSolve(Obj); getSelection().clearSelection(); } bool CmdSketcherDecreaseKnotMultiplicity::isActive() { return isCommandNeedingBSplineKnotActive(getActiveGuiDocument()); } // Composite drop down for knot increase/decrease DEF_STD_CMD_ACLU(CmdSketcherCompModifyKnotMultiplicity) CmdSketcherCompModifyKnotMultiplicity::CmdSketcherCompModifyKnotMultiplicity() : Command("Sketcher_CompModifyKnotMultiplicity") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Modify Knot Multiplicity"); sToolTipText = QT_TR_NOOP("Modifies the multiplicity of the selected knot of a B-spline"); sWhatsThis = "Sketcher_CompModifyKnotMultiplicity"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherCompModifyKnotMultiplicity::activated(int iMsg) { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); Gui::Command* cmd; if (iMsg == 0) { cmd = rcCmdMgr.getCommandByName("Sketcher_BSplineIncreaseKnotMultiplicity"); } else if (iMsg == 1) { cmd = rcCmdMgr.getCommandByName("Sketcher_BSplineDecreaseKnotMultiplicity"); } else { return; } cmd->invoke(0); // 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()); } Gui::Action* CmdSketcherCompModifyKnotMultiplicity::createAction() { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* c1 = pcAction->addAction(QString()); c1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_BSplineIncreaseKnotMultiplicity")); QAction* c2 = pcAction->addAction(QString()); c2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_BSplineDecreaseKnotMultiplicity")); _pcAction = pcAction; languageChange(); pcAction->setIcon(c1->icon()); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompModifyKnotMultiplicity::languageChange() { Command::languageChange(); if (!_pcAction) { return; } Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* c1 = a[0]; c1->setText( QApplication::translate("CmdSketcherCompModifyKnotMultiplicity", "Increase knot multiplicity") ); c1->setToolTip( QApplication::translate( "Sketcher_BSplineIncreaseKnotMultiplicity", "Increases the multiplicity of the selected knot of a B-spline" ) ); c1->setStatusTip( QApplication::translate( "Sketcher_BSplineIncreaseKnotMultiplicity", "Increases the multiplicity of the selected knot of a B-spline" ) ); QAction* c2 = a[1]; c2->setText( QApplication::translate("CmdSketcherCompModifyKnotMultiplicity", "Decrease knot multiplicity") ); c2->setToolTip( QApplication::translate( "Sketcher_BSplineDecreaseKnotMultiplicity", "Decreases the multiplicity of the selected knot of a B-spline" ) ); c2->setStatusTip( QApplication::translate( "Sketcher_BSplineDecreaseKnotMultiplicity", "Decreases the multiplicity of the selected knot of a B-spline" ) ); } void CmdSketcherCompModifyKnotMultiplicity::updateAction(int /*mode*/) {} bool CmdSketcherCompModifyKnotMultiplicity::isActive() { return isCommandNeedingBSplineKnotActive(getActiveGuiDocument()); } class DrawSketchHandlerBSplineInsertKnot: public DrawSketchHandler { public: DrawSketchHandlerBSplineInsertKnot(Sketcher::SketchObject* _Obj, int _GeoId) : Obj(_Obj) , GeoId(_GeoId) , EditMarkers(1) { auto bsp = static_cast(Obj->getGeometry(GeoId)); guessParam = bsp->getFirstParameter(); } ~DrawSketchHandlerBSplineInsertKnot() override {} void mouseMove(SnapManager::SnapHandle snapHandle) override { auto bsp = static_cast(Obj->getGeometry(GeoId)); Base::Vector2d onSketchPos = snapHandle.compute(); // get closest parameter using OCC // TODO: This is called every time we move the cursor. Can get overwhelming. Base::Vector3d onSketchPos3d(onSketchPos.x, onSketchPos.y, 0.0); SbString text; text.sprintf(" %.3f", guessParam); // FIXME: Sometimes the "closest" point is on the other end of the B-Spline. // Find when it happens and fix it? bsp->closestParameter(onSketchPos3d, guessParam); Base::Vector3d pointOnCurve3d = bsp->value(guessParam); // TODO: Also draw a point at our position instead of just text Base::Vector2d pointOnCurve(pointOnCurve3d.x, pointOnCurve3d.y); setPositionText(pointOnCurve, text); EditMarkers[0] = pointOnCurve; drawEditMarkers(EditMarkers); applyCursor(); } bool pressButton(Base::Vector2d /*onSketchPos*/) override { // just here to consume the button press return true; } bool releaseButton(Base::Vector2d onSketchPos) override { Q_UNUSED(onSketchPos); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Insert knot")); bool applied = false; boost::uuids::uuid bsplinetag = Obj->getGeometry(GeoId)->getTag(); try { Gui::cmdAppObjectArgs(Obj, "insertBSplineKnot(%d, %lf, %d) ", GeoId, guessParam, 1); applied = true; // Warning: GeoId list might have changed // as the consequence of deleting pole circles and // particularly B-spline GeoID might have changed. } catch (const Base::CADKernelError& e) { e.reportException(); if (e.getTranslatable()) { Gui::TranslatedUserError( Obj, QObject::tr("CAD Kernel Error"), QObject::tr(e.getMessage().c_str()) ); } } catch (const Base::Exception& e) { e.reportException(); if (e.getTranslatable()) { Gui::TranslatedUserError( Obj, QObject::tr("Input Error"), QObject::tr(e.getMessage().c_str()) ); } } int newGeoId = 0; bool newGeoIdFound = false; if (applied) { // find new geoid for B-spline as GeoId might have changed const std::vector& gvals = Obj->getInternalGeometry(); for (std::vector::const_iterator geo = gvals.begin(); geo != gvals.end(); geo++, newGeoId++) { if ((*geo) && (*geo)->getTag() == bsplinetag) { newGeoIdFound = true; break; } } if (newGeoIdFound) { try { // add internalalignment for new pole Gui::cmdAppObjectArgs(Obj, "exposeInternalGeometry(%d)", newGeoId); } catch (const Base::Exception& e) { Gui::NotifyUserError( Obj, QT_TRANSLATE_NOOP("Notifications", "Invalid Constraint"), e.what() ); } } } if (applied) { Gui::Command::commitCommand(); } else { Gui::Command::abortCommand(); } tryAutoRecomputeIfNotSolve(Obj); ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Sketcher" ); bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); if (continuousMode && newGeoIdFound) { // This code enables the continuous creation mode. // The new entities created changed the B-Spline's GeoId GeoId = newGeoId; applyCursor(); /* It is ok not to call to purgeHandler * in continuous creation mode because the * handler is destroyed by the quit() method on pressing the * right button of the mouse */ } else { sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider } return true; } private: QString getCrosshairCursorSVGName() const override { return QStringLiteral("Sketcher_Pointer_InsertKnot"); } protected: Sketcher::SketchObject* Obj; int GeoId; double guessParam; std::vector EditMarkers; }; DEF_STD_CMD_A(CmdSketcherInsertKnot) CmdSketcherInsertKnot::CmdSketcherInsertKnot() : Command("Sketcher_BSplineInsertKnot") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Insert Knot"); sToolTipText = QT_TR_NOOP( "Inserts a knot at a given parameter. If a knot already exists at that " "parameter, its multiplicity is increased by 1." ); sWhatsThis = "Sketcher_BSplineInsertKnot"; sStatusTip = sToolTipText; sPixmap = "Sketcher_BSplineInsertKnot"; sAccel = ""; eType = ForEdit; } void CmdSketcherInsertKnot::activated(int iMsg) { Q_UNUSED(iMsg); // get the selection std::vector selection; selection = getSelection().getSelectionEx(nullptr, Sketcher::SketchObject::getClassTypeId()); // TODO: let user click on a curve after pressing command. // only one sketch with its subelements are allowed to be selected if (selection.size() != 1) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); if (SubNames.empty()) { // Check that only one object is selected, // as we need only one object to get the new GeoId after multiplicity change Gui::TranslatedUserWarning( getActiveGuiDocument()->getDocument(), QObject::tr("Selection is empty"), QObject::tr("Nothing is selected. Select a B-spline.") ); return; } Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); // TODO: Ensure GeoId is for the BSpline and not for it's internal geometry int GeoId = std::atoi(SubNames[0].substr(4, 4000).c_str()) - 1; const Part::Geometry* geo = Obj->getGeometry(GeoId); if (geo->is()) { ActivateBSplineHandler( getActiveGuiDocument(), new DrawSketchHandlerBSplineInsertKnot(Obj, GeoId) ); } else { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr( "Select a B-spline to insert a knot (not a knot on it). " "If the curve is not a B-spline, convert it into one first." ) ); } getSelection().clearSelection(); } bool CmdSketcherInsertKnot::isActive() { return isCommandNeedingBSplineActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherJoinCurves) CmdSketcherJoinCurves::CmdSketcherJoinCurves() : Command("Sketcher_JoinCurves") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Join Curves"); sToolTipText = QT_TR_NOOP("Joins 2 curves at selected end points"); sWhatsThis = "Sketcher_JoinCurves"; sStatusTip = sToolTipText; sPixmap = "Sketcher_JoinCurves"; sAccel = ""; eType = ForEdit; } void CmdSketcherJoinCurves::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) { return; } // get the needed lists and objects const std::vector& SubNames = selection[0].getSubNames(); int GeoIds[2]; Sketcher::PointPos PosIds[2]; Sketcher::SketchObject* Obj = static_cast(selection[0].getObject()); switch (SubNames.size()) { case 0: { // Nothing is selected Gui::TranslatedUserWarning( Obj, QObject::tr("Selection is empty"), QObject::tr("Nothing is selected. Select end points of curves.") ); return; } case 1: { std::vector GeoIdList; std::vector PosIdList; int selGeoId; Sketcher::PointPos selPosId; getIdsFromName(SubNames[0], Obj, selGeoId, selPosId); Obj->getDirectlyCoincidentPoints(selGeoId, selPosId, GeoIdList, PosIdList); // Find the right pair of coincident points size_t j = 0; for (size_t i = 0; i < GeoIdList.size(); ++i) { if (Sketcher::PointPos::start == PosIdList[i] || Sketcher::PointPos::end == PosIdList[i]) { if (j < 2) { GeoIds[j] = GeoIdList[i]; PosIds[j] = PosIdList[i]; ++j; } else { Gui::TranslatedUserWarning( Obj, QObject::tr("Too many curves on point"), QObject::tr( "Exactly two curves should end at the selected point to be " "able to join them." ) ); return; } } } if (j < 2) { Gui::TranslatedUserWarning( Obj, QObject::tr("Too few curves on point"), QObject::tr( "Exactly two curves should end at the " "selected point to be able to join them." ) ); return; } break; } case 2: { getIdsFromName(SubNames[0], Obj, GeoIds[0], PosIds[0]); getIdsFromName(SubNames[1], Obj, GeoIds[1], PosIds[1]); break; } default: { Gui::TranslatedUserWarning( Obj, QObject::tr("Wrong selection"), QObject::tr("Two end points, or coincident point should be selected.") ); return; } } // TODO: Check for tangent constraints between these the two points. // These need to be explicit: indirect tangency because poles are collinear will not work. bool tangentConstraintExists = false; for (const auto& constr : Obj->Constraints.getValues()) { if (constr->Type == Sketcher::ConstraintType::Tangent && ((constr->First == GeoIds[0] && constr->FirstPos == PosIds[0] && constr->Second == GeoIds[1] && constr->SecondPos == PosIds[1]) || (constr->First == GeoIds[1] && constr->FirstPos == PosIds[1] && constr->Second == GeoIds[0] && constr->SecondPos == PosIds[0]))) { tangentConstraintExists = true; } } Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Join Curves")); bool applied = false; try { Gui::cmdAppObjectArgs( selection[0].getObject(), "join(%d, %d, %d, %d, %d) ", GeoIds[0], static_cast(PosIds[0]), GeoIds[1], static_cast(PosIds[1]), tangentConstraintExists ? 1 : 0 ); applied = true; // Warning: GeoId list will have changed } catch (const Base::Exception& e) { Gui::TranslatedUserError( Obj, QObject::tr("Error"), QObject::tr(getStrippedPythonExceptionString(e).c_str()) ); getSelection().clearSelection(); } if (applied) { Gui::Command::commitCommand(); } else { Gui::Command::abortCommand(); } tryAutoRecomputeIfNotSolve(Obj); getSelection().clearSelection(); } bool CmdSketcherJoinCurves::isActive() { return isCommandNeedingBSplineActive(getActiveGuiDocument()); } void CreateSketcherCommandsBSpline() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdSketcherConvertToNURBS()); rcCmdMgr.addCommand(new CmdSketcherIncreaseDegree()); rcCmdMgr.addCommand(new CmdSketcherDecreaseDegree()); rcCmdMgr.addCommand(new CmdSketcherIncreaseKnotMultiplicity()); rcCmdMgr.addCommand(new CmdSketcherDecreaseKnotMultiplicity()); rcCmdMgr.addCommand(new CmdSketcherCompModifyKnotMultiplicity()); rcCmdMgr.addCommand(new CmdSketcherInsertKnot()); rcCmdMgr.addCommand(new CmdSketcherJoinCurves()); }