From e6b18dc381b74c0d8bcb72a73ba5a066149c65c2 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 24 May 2024 15:47:31 +0200 Subject: [PATCH 1/2] Sketcher: polygone DSH, typo fix --- src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h index d0fc81fb92..ce51c13603 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h @@ -303,15 +303,15 @@ void DSHPolygonController::configureToolWidget() { toolWidget->setParameterLabel( - OnViewParameter::First, + WParameter::First, QApplication::translate("ToolWidgetManager_p4", "Sides (+'U'/ -'J')")); - toolWidget->setParameter(OnViewParameter::First, + toolWidget->setParameter(WParameter::First, handler->numberOfCorners); // unconditionally set - toolWidget->configureParameterUnit(OnViewParameter::First, Base::Unit()); - toolWidget->configureParameterMin(OnViewParameter::First, 3.0); // NOLINT + toolWidget->configureParameterUnit(WParameter::First, Base::Unit()); + toolWidget->configureParameterMin(WParameter::First, 3.0); // NOLINT // We set a reasonable max to avoid the spinbox from being very large - toolWidget->configureParameterMax(OnViewParameter::First, 9999.0); // NOLINT - toolWidget->configureParameterDecimals(OnViewParameter::First, 0); + toolWidget->configureParameterMax(WParameter::First, 9999.0); // NOLINT + toolWidget->configureParameterDecimals(WParameter::First, 0); onViewParameters[OnViewParameter::First]->setLabelType(Gui::SoDatumLabel::DISTANCEX); onViewParameters[OnViewParameter::Second]->setLabelType(Gui::SoDatumLabel::DISTANCEY); From 177673a056b577a47f809c5f20d81c9c1940473a Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 27 May 2024 09:57:11 +0200 Subject: [PATCH 2/2] Sketcher: BSpline DSH: implement OVP/widget --- src/Mod/Sketcher/Gui/CMakeLists.txt | 1 - src/Mod/Sketcher/Gui/CommandCreateGeo.cpp | 36 +- src/Mod/Sketcher/Gui/DrawSketchController.h | 11 +- .../Sketcher/Gui/DrawSketchDefaultHandler.h | 15 +- .../Sketcher/Gui/DrawSketchHandlerBSpline.h | 1585 +++++++++++------ .../DrawSketchHandlerBSplineByInterpolation.h | 755 -------- 6 files changed, 1103 insertions(+), 1300 deletions(-) delete mode 100644 src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 2a1a8aff00..6c3028ec48 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -67,7 +67,6 @@ SET(SketcherGui_SRCS DrawSketchHandlerArcOfParabola.h DrawSketchHandlerArcSlot.h DrawSketchHandlerBSpline.h - DrawSketchHandlerBSplineByInterpolation.h DrawSketchHandlerPoint.h DrawSketchHandlerFillet.h DrawSketchHandlerTranslate.h diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index 64de98b848..0f2b60309f 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -57,7 +57,6 @@ #include "DrawSketchHandlerArcOfParabola.h" #include "DrawSketchHandlerArcSlot.h" #include "DrawSketchHandlerBSpline.h" -#include "DrawSketchHandlerBSplineByInterpolation.h" #include "DrawSketchHandlerCarbonCopy.h" #include "DrawSketchHandlerCircle.h" #include "DrawSketchHandlerEllipse.h" @@ -860,7 +859,9 @@ CONSTRUCTION_UPDATE_ACTION(CmdSketcherCreateBSpline, "Sketcher_CreateBSpline") void CmdSketcherCreateBSpline::activated(int iMsg) { Q_UNUSED(iMsg); - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(0)); + ActivateHandler(getActiveGuiDocument(), + new DrawSketchHandlerBSpline( + ConstructionMethods::BSplineConstructionMethod::ControlPoints)); } bool CmdSketcherCreateBSpline::isActive() @@ -893,7 +894,10 @@ CONSTRUCTION_UPDATE_ACTION(CmdSketcherCreatePeriodicBSpline, "Sketcher_Create_Pe void CmdSketcherCreatePeriodicBSpline::activated(int iMsg) { Q_UNUSED(iMsg); - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(1)); + ActivateHandler( + getActiveGuiDocument(), + new DrawSketchHandlerBSpline(ConstructionMethods::BSplineConstructionMethod::ControlPoints, + true)); } bool CmdSketcherCreatePeriodicBSpline::isActive() @@ -925,7 +929,9 @@ CONSTRUCTION_UPDATE_ACTION(CmdSketcherCreateBSplineByInterpolation, void CmdSketcherCreateBSplineByInterpolation::activated(int iMsg) { Q_UNUSED(iMsg); - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSplineByInterpolation(0)); + ActivateHandler( + getActiveGuiDocument(), + new DrawSketchHandlerBSpline(ConstructionMethods::BSplineConstructionMethod::Knots)); } bool CmdSketcherCreateBSplineByInterpolation::isActive() @@ -958,7 +964,9 @@ CONSTRUCTION_UPDATE_ACTION(CmdSketcherCreatePeriodicBSplineByInterpolation, void CmdSketcherCreatePeriodicBSplineByInterpolation::activated(int iMsg) { Q_UNUSED(iMsg); - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSplineByInterpolation(1)); + ActivateHandler( + getActiveGuiDocument(), + new DrawSketchHandlerBSpline(ConstructionMethods::BSplineConstructionMethod::Knots, true)); } bool CmdSketcherCreatePeriodicBSplineByInterpolation::isActive() @@ -992,16 +1000,26 @@ CmdSketcherCompCreateBSpline::CmdSketcherCompCreateBSpline() void CmdSketcherCompCreateBSpline::activated(int iMsg) { if (iMsg == 0) { - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); + ActivateHandler(getActiveGuiDocument(), + new DrawSketchHandlerBSpline( + ConstructionMethods::BSplineConstructionMethod::ControlPoints)); } else if (iMsg == 1) { - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); + ActivateHandler(getActiveGuiDocument(), + new DrawSketchHandlerBSpline( + ConstructionMethods::BSplineConstructionMethod::ControlPoints, + true)); } else if (iMsg == 2) { - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSplineByInterpolation(0)); + ActivateHandler( + getActiveGuiDocument(), + new DrawSketchHandlerBSpline(ConstructionMethods::BSplineConstructionMethod::Knots)); } else if (iMsg == 3) { - ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSplineByInterpolation(1)); + ActivateHandler( + getActiveGuiDocument(), + new DrawSketchHandlerBSpline(ConstructionMethods::BSplineConstructionMethod::Knots, + true)); } else { return; diff --git a/src/Mod/Sketcher/Gui/DrawSketchController.h b/src/Mod/Sketcher/Gui/DrawSketchController.h index b5e6899b1e..604a4dc0e0 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchController.h +++ b/src/Mod/Sketcher/Gui/DrawSketchController.h @@ -323,12 +323,21 @@ public: handler->updateCursor(); - handler->reset(); // reset of handler to restart. + if (resetOnConstructionMethodeChanged()) { + handler->reset(); // reset of handler to restart. + } handler->mouseMove(prevCursorPosition); } //@} + /** function that define if the handler should be reset on construction methode change */ + virtual bool resetOnConstructionMethodeChanged() + { + return true; + } + //@} + /** @name functions NOT intended for specialisation offering specialisation interface for * extension */ /** These functions offer a specialisation interface to ensure the order on initialisation. It diff --git a/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h b/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h index 5301c7ea53..a0eec677a6 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h +++ b/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h @@ -431,20 +431,18 @@ public: this->iterateToNextConstructionMethod(); } else if (key == SoKeyboardEvent::ESCAPE && pressed) { - - if (this->isFirstState()) { - quit(); - } - else { - handleContinuousMode(); - } + rightButtonOrEsc(); } } void pressRightButton(Base::Vector2d onSketchPos) override { Q_UNUSED(onSketchPos); + rightButtonOrEsc(); + } + virtual void rightButtonOrEsc() + { if (this->isFirstState()) { quit(); } @@ -502,8 +500,7 @@ protected: createAutoConstraints(); } - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); + tryAutoRecomputeIfNotSolve(sketchgui->getSketchObject()); } catch (const Base::RuntimeError& e) { // RuntimeError exceptions inside of the block above must provide a translatable diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h index 2cae185c6e..2902db1bcc 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h @@ -23,19 +23,20 @@ #ifndef SKETCHERGUI_DrawSketchHandlerBSpline_H #define SKETCHERGUI_DrawSketchHandlerBSpline_H -#include -#include +#include +#include #include #include #include #include -#include "DrawSketchHandler.h" +#include "DrawSketchDefaultWidgetController.h" +#include "DrawSketchControllableHandler.h" + #include "GeometryCreationMode.h" #include "Utils.h" -#include "ViewProviderSketch.h" namespace SketcherGui @@ -43,507 +44,114 @@ namespace SketcherGui extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp -class DrawSketchHandlerBSpline: public DrawSketchHandler -{ -public: - explicit DrawSketchHandlerBSpline(int constructionMethod) - : Mode(STATUS_SEEK_FIRST_CONTROLPOINT) - , MousePressMode(MOUSE_NOT_PRESSED) - , ConstrMethod(constructionMethod) - , SplineDegree(3) - , IsClosed(false) - { - addSugConstraint(); - applyCursor(); - } +class DrawSketchHandlerBSpline; +namespace ConstructionMethods +{ +enum class BSplineConstructionMethod +{ + ControlPoints, + Knots, + End // Must be the last one +}; +} + +using DSHBSplineController = + DrawSketchDefaultWidgetController, // NOLINT + /*WidgetParametersT =*/WidgetParameters<1, 1>, // NOLINT + /*WidgetCheckboxesT =*/WidgetCheckboxes<1, 1>, // NOLINT + /*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT + ConstructionMethods::BSplineConstructionMethod, + /*bool PFirstComboboxIsConstructionMethod =*/true>; + +using DSHBSplineControllerBase = DSHBSplineController::ControllerBase; + +using DrawSketchHandlerBSplineBase = DrawSketchControllableHandler; + + +class DrawSketchHandlerBSpline: public DrawSketchHandlerBSplineBase +{ + friend DSHBSplineController; + friend DSHBSplineControllerBase; + +public: + explicit DrawSketchHandlerBSpline( + ConstructionMethod constrMethod = ConstructionMethod::ControlPoints, + bool periodic = false) + : DrawSketchHandlerBSplineBase(constrMethod) + , SplineDegree(3) + , periodic(periodic) + , prevCursorPosition(Base::Vector2d()) + , resetSeekSecond(false) {}; ~DrawSketchHandlerBSpline() override = default; - /// modes - enum SELECT_MODE - { - STATUS_SEEK_FIRST_CONTROLPOINT, - STATUS_SEEK_ADDITIONAL_CONTROLPOINTS, - STATUS_CLOSE - }; - - // TODO: this kind of behavior will be useful in a superclass - // when LMB is pressed it's a transitional state so some undos can't be done - // (like delete last pole) - enum MOUSE_PRESS_MODE - { - MOUSE_PRESSED, - MOUSE_NOT_PRESSED - }; - - void mouseMove(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - - if (Mode == STATUS_SEEK_FIRST_CONTROLPOINT) { - setPositionText(onSketchPos); - - if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr.back()); - return; - } - } - else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) { - - drawBSplineToPosition(onSketchPos); - - drawCursorToPosition(onSketchPos); - - if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr.back()); - return; - } - } - } - - bool pressButton(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - - MousePressMode = MOUSE_PRESSED; - - if (Mode == STATUS_SEEK_FIRST_CONTROLPOINT) { - BSplinePoles.push_back(onSketchPos); - - Mode = STATUS_SEEK_ADDITIONAL_CONTROLPOINTS; - - // insert circle point for pole, defer internal alignment constraining. - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Pole circle")); - - // Add pole - Gui::cmdAppObjectArgs( - sketchgui->getObject(), - "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)", - BSplinePoles.back().x, - BSplinePoles.back().y); - - poleGeoIds.push_back(getHighestCurveIndex()); - - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ", - poleGeoIds.back(), - 1.0); // First pole defaults to 1.0 weight - } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error adding B-Spline pole")); - - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return false; - } - - // Gui::Command::commitCommand(); - - // static_cast(sketchgui->getObject())->solve(); - - // add auto constraints on pole - if (!sugConstr.back().empty()) { - createAutoConstraints(sugConstr.back(), - poleGeoIds.back(), - Sketcher::PointPos::mid, - false); - } - - static_cast(sketchgui->getObject())->solve(); - - addSugConstraint(); - } - else if (Mode == STATUS_SEEK_ADDITIONAL_CONTROLPOINTS) { - BSplinePoles.push_back(onSketchPos); - - // check if coincident with first pole - for (auto& ac : sugConstr.back()) { - if (ac.Type == Sketcher::Coincident) { - if (ac.GeoId == poleGeoIds[0] && ac.PosId == Sketcher::PointPos::mid) { - IsClosed = true; - } - else { - // The coincidence with first point may be indirect - const auto coincidents = - static_cast(sketchgui->getObject()) - ->getAllCoincidentPoints(ac.GeoId, ac.PosId); - if (coincidents.find(poleGeoIds[0]) != coincidents.end() - && coincidents.at(poleGeoIds[0]) == Sketcher::PointPos::mid) { - IsClosed = true; - } - } - } - } - - if (IsClosed) { - Mode = STATUS_CLOSE; - - if (ConstrMethod == 1) { // if periodic we do not need the last pole - BSplinePoles.pop_back(); - sugConstr.pop_back(); - - return true; - } - } - - // insert circle point for pole, defer internal alignment constraining. - try { - - // Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Pole circle")); - - // Add pole - Gui::cmdAppObjectArgs( - sketchgui->getObject(), - "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)", - BSplinePoles.back().x, - BSplinePoles.back().y); - - poleGeoIds.push_back(getHighestCurveIndex()); - - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ", - poleGeoIds[0], - poleGeoIds.back()); - } - catch (const Base::Exception&) { - Gui::NotifyError( - sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error creating B-spline pole")); - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return false; - } - - // Gui::Command::commitCommand(); - - // static_cast(sketchgui->getObject())->solve(); - - // add auto constraints on pole - if (!sugConstr.back().empty()) { - createAutoConstraints(sugConstr.back(), - poleGeoIds.back(), - Sketcher::PointPos::mid, - false); - } - - // static_cast(sketchgui->getObject())->solve(); - - if (!IsClosed) { - addSugConstraint(); - } - } - return true; - } - - bool releaseButton(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - MousePressMode = MOUSE_NOT_PRESSED; - - return finishCommand(onSketchPos); - } - - void registerPressedKey(bool pressed, int key) override - { - if (SoKeyboardEvent::D == key && pressed) { - SplineDegree = - QInputDialog::getInt(Gui::getMainWindow(), - QObject::tr("B-Spline Degree"), - QObject::tr("Define B-Spline Degree, between 1 and %1:") - .arg(QString::number(Geom_BSplineCurve::MaxDegree())), - SplineDegree, - 1, - Geom_BSplineCurve::MaxDegree(), - 1); - // FIXME: Pressing Esc here also finishes the B-Spline creation. - // The user may only want to exit the dialog. - } - // On pressing Backspace delete last pole - else if (SoKeyboardEvent::BACKSPACE == key && pressed) { - // when mouse is pressed we are in a transitional state so don't mess with it - if (MOUSE_PRESSED == MousePressMode) { - return; - } - - // can only delete last pole if it exists - if (STATUS_SEEK_FIRST_CONTROLPOINT == Mode || STATUS_CLOSE == Mode) { - return; - } - - // if only first pole exists it's equivalent to canceling current spline - if (poleGeoIds.size() == 1) { - // this also exits b-spline creation if continuous mode is off - this->quit(); - return; - } - - // reverse the steps of press/release button - try { - // already ensured that CurrentConstraint == EditCurve.size() > 1 - const int delGeoId = poleGeoIds.back(); - const auto& constraints = - static_cast(sketchgui->getObject()) - ->Constraints.getValues(); - for (int i = constraints.size() - 1; i >= 0; --i) { - if (delGeoId == constraints[i]->First || delGeoId == constraints[i]->Second - || delGeoId == constraints[i]->Third) { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "delConstraint(%d)", i); - } - } - - // Remove pole - Gui::cmdAppObjectArgs(sketchgui->getObject(), "delGeometry(%d)", delGeoId); - - static_cast(sketchgui->getObject())->solve(); - - poleGeoIds.pop_back(); - BSplinePoles.pop_back(); - - // last entry is kept, as it corresponds to the current pole, but the one - // corresponding to the erased pole is removed - sugConstr.erase(std::prev(std::prev(sugConstr.end()))); - - - // run this in the end to draw lines and position text - drawBSplineToPosition(prevCursorPosition); - drawCursorToPosition(prevCursorPosition); - } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error deleting last pole")); - // some commands might have already deleted some constraints/geometries but not - // others - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return; - } - } - // TODO: On pressing, say, W, modify last pole's weight - // TODO: On pressing, say, M, modify next knot's multiplicity - else { - DrawSketchHandler::registerPressedKey(pressed, key); - } - - return; - } - - void quit() override - { - // We must see if we need to create a B-spline before cancelling everything - // and now just like any other Handler, - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Sketcher"); - - bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); - - if (poleGeoIds.size() > 1) { - // create B-spline from existing poles - Mode = STATUS_CLOSE; - finishCommand(Base::Vector2d(0.f, 0.f)); - } - else if (poleGeoIds.size() == 1) { - // if we just have one point and we can not close anything, then cancel this creation - // but continue according to continuous mode - // sketchgui->getDocument()->undo(1); - - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); - - if (!continuousMode) { - DrawSketchHandler::quit(); - } - else { - // This code disregards existing data and enables the continuous creation mode. - resetHandlerState(); - } - } - else { // we have no data (CurrentConstraint == 0) so user when right-clicking really wants - // to exit - DrawSketchHandler::quit(); - } - } - private: - void resetHandlerState() + void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override { - Mode = STATUS_SEEK_FIRST_CONTROLPOINT; - applyCursor(); + prevCursorPosition = onSketchPos; - SplineDegree = 3; + switch (state()) { + case SelectMode::SeekFirst: { + toolWidgetManager.drawPositionAtCursor(onSketchPos); - sugConstr.clear(); - poleGeoIds.clear(); - BSplinePoles.clear(); + if (seekAutoConstraint(sugConstraints[0], onSketchPos, Base::Vector2d(0.f, 0.f))) { + renderSuggestConstraintsCursor(sugConstraints[0]); + return; + } + } break; + case SelectMode::SeekSecond: { + toolWidgetManager.drawDirectionAtCursor(onSketchPos, getLastPoint()); - eraseEditCurve(); + try { + CreateAndDrawShapeGeometry(); + } + catch (const Base::ValueError&) { + } // equal points while hovering raise an objection that can be safely ignored - addSugConstraint(); - - IsClosed = false; - } - - QString getCrosshairCursorSVGName() const override - { - if (SketcherGui::DrawSketchHandlerBSpline::ConstrMethod == 1) { - return QString::fromLatin1("Sketcher_Pointer_Create_Periodic_BSpline"); - } - else { - return QString::fromLatin1("Sketcher_Pointer_Create_BSpline"); + if (seekAutoConstraint(sugConstraints[1], onSketchPos, Base::Vector2d(0.f, 0.f))) { + renderSuggestConstraintsCursor(sugConstraints[1]); + return; + } + } break; + default: + break; } } - void addSugConstraint() + void executeCommands() override { - std::vector sugConstr1; - sugConstr.push_back(std::move(sugConstr1)); - } - - void drawControlPolygonToPosition(Base::Vector2d position) - { - std::vector editcurve(BSplinePoles); - editcurve.push_back(position); - - drawEdit(editcurve); - } - - void drawBSplineToPosition(Base::Vector2d position) - { - std::vector editcurve; - for (auto& pole : BSplinePoles) { - editcurve.emplace_back(pole.x, pole.y, 0.0); - } - editcurve.emplace_back(position.x, position.y, 0.0); - size_t degree = std::min(editcurve.size() - 1, static_cast(SplineDegree)); - bool periodic = (ConstrMethod != 0); - - std::vector weights(editcurve.size(), 1.0); - std::vector knots; - std::vector mults; - if (!periodic) { - for (size_t i = 0; i < editcurve.size() - degree + 1; ++i) { - knots.push_back(i); - } - mults.resize(editcurve.size() - degree + 1, 1); - mults.front() = degree + 1; - mults.back() = degree + 1; - } - else { - for (size_t i = 0; i < editcurve.size() + 1; ++i) { - knots.push_back(i); - } - mults.resize(editcurve.size() + 1, 1); + if (geoIds.size() == 1) { + // if we just have one point and we can not close anything + Gui::Command::abortCommand(); + return; } - // TODO: This maybe optimized by storing the spline as an attribute. - Part::GeomBSplineCurve editBSpline(editcurve, weights, knots, mults, degree, periodic); - editBSpline.setPoles(editcurve); + try { + if (constructionMethod() == ConstructionMethod::ControlPoints) { + createShape(false); - std::vector editBSplines; - editBSplines.push_back(&editBSpline); + commandAddShapeGeometryAndConstraints(); - drawEdit(editBSplines); - } + int currentgeoid = getHighestCurveIndex(); - void drawCursorToPosition(Base::Vector2d position) - { - if (!BSplinePoles.empty()) { - float length = (position - BSplinePoles.back()).Length(); - float angle = (position - BSplinePoles.back()).GetAngle(Base::Vector2d(1.f, 0.f)); - - if (showCursorCoords()) { - SbString text; - std::string lengthString = lengthToDisplayFormat(length, 1); - std::string angleString = - angleToDisplayFormat((angle != -FLOAT_MAX) ? angle * 180 / M_PI : 0, 1); - text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str()); - setPositionText(position, text); - } - } - } - - void eraseEditCurve() - { - drawEdit(std::vector()); - } - - bool finishCommand(Base::Vector2d position) - { - if (Mode == STATUS_CLOSE) { - unsetCursor(); - resetPositionText(); - - std::stringstream stream; - - for (auto& pole : BSplinePoles) { - stream << "App.Vector(" << pole.x << "," << pole.y << "),"; - } - - std::string controlpoints = stream.str(); - - // remove last comma and add brackets - int index = controlpoints.rfind(','); - controlpoints.resize(index); - - controlpoints.insert(0, 1, '['); - controlpoints.append(1, ']'); - - int currentgeoid = getHighestCurveIndex(); - - unsigned int maxDegree = - ConstrMethod == 0 ? (BSplinePoles.size() - 1) : (BSplinePoles.size()); - - try { - // Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add B-spline curve")); - - /*Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.BSplineCurve" - "(%s,%s)," - "%s)", - controlpoints.c_str(), - ConstrMethod == 0 ?"False":"True", - geometryCreationMode==Construction?"True":"False"); */ - - // {"poles", "mults", "knots", "periodic", "degree", "weights", "CheckRational", - // NULL}; - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addGeometry(Part.BSplineCurve" - "(%s,None,None,%s,%d,None,False),%s)", - controlpoints.c_str(), - ConstrMethod == 0 ? "False" : "True", - std::min(maxDegree, SplineDegree), - constructionModeAsBooleanText()); - - currentgeoid++; - - // autoconstraints were added to the circles of the poles, which is ok because they - // must go to the right position, or the user will freak-out if they appear out of - // the autoconstrained position. However, autoconstraints on the first and last - // pole, in normal non-periodic b-splines (with appropriate endpoint knot - // multiplicity) as the ones created by this tool are intended for the b-spline - // endpoints, and not for the poles, so here we retrieve any autoconstraint on those - // poles' center and mangle it to the endpoint. - if (ConstrMethod == 0) { - for (auto& constr : static_cast(sketchgui->getObject()) - ->Constraints.getValues()) { - if (constr->First == poleGeoIds[0] + // autoconstraints were added to the circles of the poles or knots, which is ok + // because they must go to the right position, or the user will freak-out if they + // appear out of the autoconstrained position. However, autoconstraints on the first + // and last pole/knot, in normal non-periodic b-splines (with appropriate endpoint + // knot multiplicity) as the ones created by this tool are intended for the b-spline + // endpoints, and not for the poles/knots, so here we retrieve any autoconstraint on + // those poles/knots center and mangle it to the endpoint. + if (!periodic) { + for (auto& constr : sketchgui->getSketchObject()->Constraints.getValues()) { + if (constr->First == geoIds[0] && constr->FirstPos == Sketcher::PointPos::mid) { constr->First = currentgeoid; constr->FirstPos = Sketcher::PointPos::start; } - else if (constr->First == poleGeoIds.back() + else if (constr->First == geoIds.back() && constr->FirstPos == Sketcher::PointPos::mid) { constr->First = currentgeoid; constr->FirstPos = Sketcher::PointPos::end; @@ -551,21 +159,20 @@ private: } } - // Constraint pole circles to B-spline. + // Constraint pole/knot circles to B-spline. std::stringstream cstream; - cstream << "conList = []\n"; - for (size_t i = 0; i < poleGeoIds.size(); i++) { + for (size_t i = 0; i < geoIds.size(); i++) { cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::" "BSplineControlPoint'," - << poleGeoIds[0] + i << "," << static_cast(Sketcher::PointPos::mid) + << geoIds[0] + i << "," << static_cast(Sketcher::PointPos::mid) << "," << currentgeoid << "," << i << "))\n"; } cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) - << ".addConstraint(conList)\n"; - cstream << "del conList\n"; + << ".addConstraint(conList)\n" + << "del conList\n"; Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str()); @@ -574,68 +181,996 @@ private: "exposeInternalGeometry(%d)", currentgeoid); } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error creating B-spline")); - Gui::Command::abortCommand(); + else { + int myDegree = 3; - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); + if (!periodic) { + // FIXME: This is hardcoded until degree can be changed + multiplicities.front() = myDegree + 1; + multiplicities.back() = myDegree + 1; + } - return false; + std::vector streams; + + // Create subsets of points between C0 knots. + // The first point + streams.emplace_back(); + streams.back() << "App.Vector(" << points.front().x << "," << points.front().y + << "),"; + // Middle points + for (size_t i = 1; i < points.size() - 1; ++i) { + streams.back() << "App.Vector(" << points[i].x << "," << points[i].y << "),"; + if (multiplicities[i] >= myDegree) { + streams.emplace_back(); + streams.back() + << "App.Vector(" << points[i].x << "," << points[i].y << "),"; + } + } + // The last point + streams.back() << "App.Vector(" << points.back().x << "," << points.back().y + << "),"; + + // Note the plural of plurals. Each element is a separate sequence. + std::vector controlpointses; + controlpointses.reserve(streams.size()); + for (auto& stream : streams) { + controlpointses.emplace_back(stream.str()); + + + auto& controlpoints = controlpointses.back(); + + // remove last comma and add brackets + int index = controlpoints.rfind(','); + controlpoints.resize(index); + + controlpoints.insert(0, 1, '['); + controlpoints.append(1, ']'); + } + + // With just 3 points provided OCCT gives a quadratic spline where + // the middle point is NOT a knot. This needs to be treated differently. + // FIXME: Decide whether to force a knot or not. + std::vector isBetweenC0Points(points.size(), false); + for (size_t i = 1; i < points.size() - 1; ++i) { + if (multiplicities[i - 1] >= myDegree && multiplicities[i + 1] >= myDegree) { + isBetweenC0Points[i] = true; + } + } + + int currentgeoid = getHighestCurveIndex(); + + // TODO: Bypass this for when there are no C0 knots + // Create B-spline in pieces between C0 knots + Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_poles = []"); + Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_knots = []"); + Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults = []"); + Gui::Command::runCommand(Gui::Command::Gui, "_bsps = []"); + for (auto& controlpoints : controlpointses) { + // TODO: variable degrees? + QString cmdstr = + QString::fromLatin1("_bsps.append(Part.BSplineCurve())\n" + "_bsps[-1].interpolate(%1, PeriodicFlag=%2)\n" + "_bsps[-1].increaseDegree(%3)") + .arg(QString::fromLatin1(controlpoints.c_str())) + .arg(QString::fromLatin1(periodic ? "True" : "False")) + .arg(myDegree); + Gui::Command::runCommand(Gui::Command::Gui, cmdstr.toLatin1()); + // Adjust internal knots here (raise multiplicity) + // How this contributes to the final B-spline + if (controlpoints == controlpointses.front()) { + Gui::Command::runCommand(Gui::Command::Gui, + "_finalbsp_poles.extend(_bsps[-1].getPoles())"); + Gui::Command::runCommand(Gui::Command::Gui, + "_finalbsp_knots.extend(_bsps[-1].getKnots())"); + Gui::Command::runCommand( + Gui::Command::Gui, + "_finalbsp_mults.extend(_bsps[-1].getMultiplicities())"); + } + else { + Gui::Command::runCommand( + Gui::Command::Gui, + "_finalbsp_poles.extend(_bsps[-1].getPoles()[1:])"); + Gui::Command::runCommand(Gui::Command::Gui, + "_finalbsp_knots.extend([_finalbsp_knots[-1] + i " + "for i in _bsps[-1].getKnots()[1:]])"); + Gui::Command::runCommand(Gui::Command::Gui, + "_finalbsp_mults[-1] = 3"); // FIXME: Hardcoded + Gui::Command::runCommand( + Gui::Command::Gui, + "_finalbsp_mults.extend(_bsps[-1].getMultiplicities()[1:])"); + } + } + + // {"poles", "mults", "knots", "periodic", "degree", "weights", "CheckRational", + // NULL}; + Gui::cmdAppObjectArgs( + sketchgui->getObject(), + "addGeometry(Part.BSplineCurve" + "(_finalbsp_poles,_finalbsp_mults,_finalbsp_knots,%s,%d,None,False),%s)", + periodic ? "True" : "False", + myDegree, + constructionModeAsBooleanText()); + currentgeoid++; + + // TODO: Confirm we do not need to delete individual elements + Gui::Command::runCommand(Gui::Command::Gui, "del(_bsps)\n"); + Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_poles)\n"); + Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_knots)\n"); + Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_mults)\n"); + + // autoconstraints were added to the knots, which is ok because they must go to the + // right position, or the user will freak-out if they appear out of the + // autoconstrained position. However, autoconstraints on the first and last knot, in + // non-periodic b-splines (with appropriate endpoint knot multiplicity) as the ones + // created by this tool are intended for the b-spline endpoints, and not for the + // knots, so here we retrieve any autoconstraint on those knots and mangle it to the + // endpoint. + if (!periodic) { + for (auto& constr : sketchgui->getSketchObject()->Constraints.getValues()) { + if (constr->First == geoIds[0] + && constr->FirstPos == Sketcher::PointPos::start) { + constr->First = currentgeoid; + constr->FirstPos = Sketcher::PointPos::start; + } + else if (constr->First == geoIds.back() + && constr->FirstPos == Sketcher::PointPos::start) { + constr->First = currentgeoid; + constr->FirstPos = Sketcher::PointPos::end; + } + } + } + + // Constraint knots to B-spline. + std::stringstream cstream; + + cstream << "conList = []\n"; + + int knotNumber = 0; + for (size_t i = 0; i < geoIds.size(); i++) { + if (isBetweenC0Points[i]) { + // Constraint point on curve + cstream << "conList.append(Sketcher.Constraint('PointOnObject'," + << geoIds[0] + i << "," + << static_cast(Sketcher::PointPos::start) << "," + << currentgeoid << "))\n"; + } + else { + cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher:" + ":BSplineKnotPoint'," + << geoIds[0] + i << "," + << static_cast(Sketcher::PointPos::start) << "," + << currentgeoid << "," << knotNumber << "))\n"; + // NOTE: Assume here that the spline shape doesn't change on increasing knot + // multiplicity. + // Change the knot multiplicity here because the user asked and it's not C0 + // NOTE: The knot number here has to be provided in the OCCT ordering. + if (multiplicities[i] > 1 && multiplicities[i] < myDegree) { + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "modifyBSplineKnotMultiplicity(%d, %d, %d) ", + currentgeoid, + knotNumber + 1, + multiplicities[i] - 1); + } + knotNumber++; + } + } + + cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) + << ".addConstraint(conList)\n"; + cstream << "del conList\n"; + + Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str()); + + // for showing the rest of internal geometry on creation + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "exposeInternalGeometry(%d)", + currentgeoid); } Gui::Command::commitCommand(); + } + catch (const Base::Exception&) { + Gui::NotifyError(sketchgui, + QT_TRANSLATE_NOOP("Notifications", "Error"), + QT_TRANSLATE_NOOP("Notifications", "Error creating B-spline")); + Gui::Command::abortCommand(); - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); + tryAutoRecomputeIfNotSolve(sketchgui->getSketchObject()); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); + return; + } + } - if (continuousMode) { - // This code enables the continuous creation mode. - resetHandlerState(); + void generateAutoConstraints() override + { + // The auto constraints are already generated in canGoToNextMode - drawCursorToPosition(position); - /* 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 */ + // Ensure temporary autoconstraints do not generate a redundancy and that the geometry + // parameters are accurate This is particularly important for adding widget mandated + // constraints. + removeRedundantAutoConstraints(); + } + + void createAutoConstraints() override + { + // execute python command to create autoconstraints + createGeneratedAutoConstraints(true); + + sugConstraints[0].clear(); + sugConstraints[1].clear(); + } + + std::string getToolName() const override + { + return "DSH_BSpline"; + } + + QString getCrosshairCursorSVGName() const override + { + if (constructionMethod() == ConstructionMethod::ControlPoints) { + if (periodic) { + return QString::fromLatin1("Sketcher_Pointer_Create_Periodic_BSpline"); } else { - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in - // ViewProvider + return QString::fromLatin1("Sketcher_Pointer_Create_BSpline"); } } else { - drawCursorToPosition(position); + if (periodic) { + return QString::fromLatin1( + "Sketcher_Pointer_Create_Periodic_BSplineByInterpolation"); + } + else { + return QString::fromLatin1("Sketcher_Pointer_Create_BSplineByInterpolation"); + } } + } + std::unique_ptr createWidget() const override + { + return std::make_unique(); + } + + bool isWidgetVisible() const override + { + return true; + }; + + QPixmap getToolIcon() const override + { + return Gui::BitmapFactory().pixmap("Sketcher_CreateBSpline"); + } + + QString getToolWidgetText() const override + { + return QString(QObject::tr("BSpline parameters")); + } + + bool canGoToNextMode() override + { + Sketcher::PointPos pointPos = constructionMethod() == ConstructionMethod::ControlPoints + ? Sketcher::PointPos::mid + : Sketcher::PointPos::start; + if (state() == SelectMode::SeekFirst) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch bSpline")); + // insert point for pole/knot, defer internal alignment constraining. + if (!addPos()) { + return false; + } + + // add auto constraints on pole/knot + auto& ac0 = sugConstraints[0]; + generateAutoConstraintsOnElement(ac0, geoIds.back(), pointPos); + + sketchgui->getSketchObject()->solve(); + } + else if (state() == SelectMode::SeekSecond) { + // We stay in SeekSecond unless the user closed the bspline. + bool isClosed = false; + + // check if coincident with first pole/knot + for (auto& ac : sugConstraints.back()) { + if (ac.Type == Sketcher::Coincident) { + if (ac.GeoId == geoIds[0]) { + isClosed = true; + } + else { + // The coincidence with first point may be indirect + const auto coincidents = + sketchgui->getSketchObject()->getAllCoincidentPoints(ac.GeoId, + ac.PosId); + if (coincidents.find(geoIds[0]) != coincidents.end()) { + isClosed = true; + } + } + } + } + + if (isClosed) { + if (periodic) { // if periodic we do not need the last pole/knot + return true; + } + } + else { + setAngleSnapping(true, getLastPoint()); + resetSeekSecond = true; + } + + // insert circle point for pole/knot, defer internal alignment constraining. + if (!addPos()) { + return false; + } + + // add auto constraints on pole/knot + auto& ac1 = sugConstraints[1]; + generateAutoConstraintsOnElement(ac1, geoIds.back(), pointPos); + sugConstraintsBackup.push_back(std::move(ac1)); + ac1.clear(); + + return isClosed; + } return true; } -protected: - SELECT_MODE Mode; - MOUSE_PRESS_MODE MousePressMode; + void angleSnappingControl() override + { + if (state() == SelectMode::SeekSecond && !points.empty()) { + setAngleSnapping(true, getLastPoint()); + } + else { + setAngleSnapping(false); + } + } - // Stores position of the poles of the BSpline. - std::vector BSplinePoles; + void quit() override + { + // We must see if we need to create a B-spline before cancelling everything - // suggested autoconstraints for poles. - // A new one must be added e.g. using addSugConstraint() before adding a new pole. - std::vector> sugConstr; + if (state() == SelectMode::SeekSecond) { + // create B-spline from existing poles/knots + setState(SelectMode::End); + finish(); + } + else { + DrawSketchHandler::quit(); + } + } - int ConstrMethod; - unsigned int SplineDegree; - bool IsClosed; - std::vector poleGeoIds; + void rightButtonOrEsc() override + { + quit(); + } + + void onReset() override + { + Gui::Command::abortCommand(); + tryAutoRecomputeIfNotSolve(sketchgui->getSketchObject()); + + SplineDegree = 3; + geoIds.clear(); + points.clear(); + multiplicities.clear(); + sugConstraintsBackup.clear(); + + toolWidgetManager.resetControls(); + } + + void undoLastPoint() + { + // can only delete last pole/knot if it exists + if (state() != SelectMode::SeekSecond) { + return; + } + + // if only first pole/knot exists it's equivalent to canceling current spline + if (geoIds.size() == 1) { + // this also exits b-spline creation if continuous mode is off + quit(); + return; + } + + // reverse the steps of press/release button + try { + // already ensured that CurrentConstraint == EditCurve.size() > 1 + const int delGeoId = geoIds.back(); + const auto& constraints = sketchgui->getSketchObject()->Constraints.getValues(); + for (int i = constraints.size() - 1; i >= 0; --i) { + if (delGeoId == constraints[i]->First || delGeoId == constraints[i]->Second + || delGeoId == constraints[i]->Third) { + Gui::cmdAppObjectArgs(sketchgui->getObject(), "delConstraint(%d)", i); + } + } + + // Remove pole/knot + Gui::cmdAppObjectArgs(sketchgui->getObject(), "delGeometry(%d)", delGeoId); + + sketchgui->getSketchObject()->solve(); + + geoIds.pop_back(); + points.pop_back(); + multiplicities.pop_back(); + distances.pop_back(); + + updateDataAndDrawToPosition(prevCursorPosition); + } + catch (const Base::Exception&) { + Gui::NotifyError(sketchgui, + QT_TRANSLATE_NOOP("Notifications", "Error"), + QT_TRANSLATE_NOOP("Notifications", "Error deleting last pole/knot")); + // some commands might have already deleted some constraints/geometries but not + // others + Gui::Command::abortCommand(); + + sketchgui->getSketchObject()->solve(); + + return; + } + } + +private: + size_t SplineDegree; + bool periodic; Base::Vector2d prevCursorPosition; + std::vector points; + std::vector multiplicities; + std::vector geoIds; + std::vector isBetweenC0Points; + std::vector distances; + bool resetSeekSecond; + + std::vector> sugConstraintsBackup; + + bool addPos() + { + addToVectors(); + return addGeometry(getLastPoint(), geoIds.back(), points.size() == 1); + } + + void addToVectors() + { + points.push_back(prevCursorPosition); + multiplicities.push_back(1); + geoIds.push_back(getHighestCurveIndex() + 1); + if (geoIds.size() != distances.size()) { + distances.push_back(-1); + } + } + + bool addGeometry(Base::Vector2d pos, int geoId, bool firstPoint) + { + try { + Gui::cmdAppObjectArgs( + sketchgui->getObject(), + constructionMethod() == ConstructionMethod::ControlPoints + ? "addGeometry(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),10),True)" + : "addGeometry(Part.Point(App.Vector(%f,%f,0)),True)", + pos.x, + pos.y); + + + if (constructionMethod() == ConstructionMethod::ControlPoints) { + if (firstPoint) { // First pole defaults to 1.0 weight + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "addConstraint(Sketcher.Constraint('Weight',%d,%f)) ", + geoId, + 1.0); + } + else { + Gui::cmdAppObjectArgs(sketchgui->getObject(), + "addConstraint(Sketcher.Constraint('Equal',%d,%d)) ", + geoIds[0], + geoId); + } + } + } + catch (const Base::Exception&) { + Gui::NotifyError(sketchgui, + QT_TRANSLATE_NOOP("Notifications", "Error"), + QT_TRANSLATE_NOOP("Notifications", "Error adding B-Spline pole/knot")); + + Gui::Command::abortCommand(); + + sketchgui->getSketchObject()->solve(); + + return false; + } + return true; + } + + void changeConstructionMethode() + { + // Restart the command + Gui::Command::abortCommand(); + tryAutoRecomputeIfNotSolve(sketchgui->getSketchObject()); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch bSpline")); + + // Add the necessary alignment geometries and constraints + for (size_t i = 0; i < geoIds.size(); ++i) { + addGeometry(points[i], geoIds[i], i == 0); + } + + // reapply the auto-constraints + Sketcher::PointPos pointPos = constructionMethod() == ConstructionMethod::ControlPoints + ? Sketcher::PointPos::mid + : Sketcher::PointPos::start; + if (!geoIds.empty()) { + generateAutoConstraintsOnElement(sugConstraints[0], geoIds[0], pointPos); + } + + size_t i = 1; + for (auto& ac : sugConstraintsBackup) { + if (i < geoIds.size()) { + generateAutoConstraintsOnElement(ac, geoIds[i], pointPos); + } + ++i; + } + } + + Base::Vector2d getLastPoint() + { + return points.empty() ? Base::Vector2d() : points.back(); + } + + void createShape(bool onlyeditoutline) override + { + ShapeGeometry.clear(); + + std::vector bsplinePoints3D; + for (auto& point : points) { + bsplinePoints3D.emplace_back(point.x, point.y, 0.0); + } + if (onlyeditoutline) { + bsplinePoints3D.emplace_back(prevCursorPosition.x, prevCursorPosition.y, 0.0); + } + + if (constructionMethod() == ConstructionMethod::ControlPoints) { + size_t vSize = bsplinePoints3D.size(); + size_t maxDegree = vSize - (periodic ? 0 : 1); + size_t degree = std::min(maxDegree, SplineDegree); + + std::vector weights(vSize, 1.0); + std::vector knots; + std::vector mults; + if (!periodic) { + for (size_t i = 0; i < vSize - degree + 1; ++i) { + knots.push_back(i); + } + mults.resize(vSize - degree + 1, 1); + mults.front() = degree + 1; + mults.back() = degree + 1; + } + else { + for (size_t i = 0; i < vSize + 1; ++i) { + knots.push_back(i); + } + mults.resize(vSize + 1, 1); + } + + auto bSpline = std::make_unique(bsplinePoints3D, + weights, + knots, + mults, + degree, + periodic); + bSpline->setPoles(bsplinePoints3D); + Sketcher::GeometryFacade::setConstruction(bSpline.get(), isConstructionMode()); + ShapeGeometry.emplace_back(std::move(bSpline)); + } + else { + try { + std::vector editCurveForOCCT; + editCurveForOCCT.reserve(bsplinePoints3D.size()); + for (auto& p : bsplinePoints3D) { + editCurveForOCCT.emplace_back(p.x, p.y, 0.0); + } + + // TODO: This maybe optimized by storing the spline as an attribute. + auto bSpline = std::make_unique(); + bSpline.get()->interpolate(editCurveForOCCT, periodic); + + Sketcher::GeometryFacade::setConstruction(bSpline.get(), isConstructionMode()); + ShapeGeometry.emplace_back(std::move(bSpline)); + } + catch (const Standard_Failure&) { + // Since it happens very frequently that the interpolation fails + // it's sufficient to report this as log message to avoid to pollute + // the output window + Base::Console().Log(std::string("drawBSplineToPosition"), "interpolation failed\n"); + } + } + } }; +template<> +auto DSHBSplineControllerBase::getState(int labelindex) const +{ + switch (labelindex) { + case OnViewParameter::First: + case OnViewParameter::Second: + return SelectMode::SeekFirst; + break; + case OnViewParameter::Third: + case OnViewParameter::Fourth: + return SelectMode::SeekSecond; + break; + default: + THROWM(Base::ValueError, "Label index without an associated machine state") + } +} + +template<> +void DSHBSplineController::firstKeyShortcut() +{ + auto value = toolWidget->getParameter(WParameter::First); + toolWidget->setParameterWithoutPassingFocus(WParameter::First, value + 1); +} + +template<> +void DSHBSplineController::secondKeyShortcut() +{ + auto value = toolWidget->getParameter(WParameter::First); + if (value > 1.0) { // NOLINT + toolWidget->setParameterWithoutPassingFocus(WParameter::First, value - 1); + } +} + +template<> +void DSHBSplineController::thirdKeyShortcut() +{ + auto firstchecked = toolWidget->getCheckboxChecked(WCheckbox::FirstBox); + toolWidget->setCheckboxChecked(WCheckbox::FirstBox, !firstchecked); +} + +template<> +void DSHBSplineController::fourthKeyShortcut() +{ + handler->undoLastPoint(); +} + +template<> +void DSHBSplineController::configureToolWidget() +{ + if (!init) { // Code to be executed only upon initialisation + toolWidget->setNoticeVisible(true); + toolWidget->setNoticeText( + QApplication::translate("TaskSketcherTool_c1_bspline", "Press F to undo last point.")); + + QStringList names = {QApplication::translate("Sketcher_CreateBSpline", "By control points"), + QApplication::translate("Sketcher_CreateBSpline", "By knots")}; + toolWidget->setComboboxElements(WCombobox::FirstCombo, names); + + toolWidget->setCheckboxLabel( + WCheckbox::FirstBox, + QApplication::translate("TaskSketcherTool_c1_bspline", "Periodic (R)")); + toolWidget->setCheckboxToolTip( + WCheckbox::FirstBox, + QApplication::translate("TaskSketcherTool_c1_bspline", "Create a periodic bspline.")); + syncCheckboxToHandler(WCheckbox::FirstBox, handler->periodic); + + if (isConstructionMode()) { + toolWidget->setComboboxItemIcon( + WCombobox::FirstCombo, + 0, + Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline_Constr")); + toolWidget->setComboboxItemIcon( + WCombobox::FirstCombo, + 1, + Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSplineByInterpolation_Constr")); + toolWidget->setCheckboxIcon( + WCheckbox::FirstBox, + Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline_Constr")); + } + else { + toolWidget->setComboboxItemIcon( + WCombobox::FirstCombo, + 0, + Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline")); + toolWidget->setComboboxItemIcon( + WCombobox::FirstCombo, + 1, + Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSplineByInterpolation")); + toolWidget->setCheckboxIcon( + WCheckbox::FirstBox, + Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline")); + } + + toolWidget->setParameterLabel( + WParameter::First, + QApplication::translate("ToolWidgetManager_p4", "Degree (+'U'/ -'J')")); + toolWidget->setParameter(WParameter::First, handler->SplineDegree); + toolWidget->configureParameterUnit(WParameter::First, Base::Unit()); + toolWidget->configureParameterMin(WParameter::First, 1.0); // NOLINT + // We set a reasonable max to avoid the spinbox from being very large + toolWidget->configureParameterDecimals(WParameter::First, 0); + } + + toolWidget->configureParameterMax(WParameter::First, Geom_BSplineCurve::MaxDegree()); // NOLINT + + onViewParameters[OnViewParameter::First]->setLabelType(Gui::SoDatumLabel::DISTANCEX); + onViewParameters[OnViewParameter::Second]->setLabelType(Gui::SoDatumLabel::DISTANCEY); + onViewParameters[OnViewParameter::Third]->setLabelType( + Gui::SoDatumLabel::DISTANCE, + Gui::EditableDatumLabel::Function::Dimensioning); + onViewParameters[OnViewParameter::Fourth]->setLabelType( + Gui::SoDatumLabel::ANGLE, + Gui::EditableDatumLabel::Function::Dimensioning); +} + +template<> +void DSHBSplineController::adaptDrawingToParameterChange(int parameterindex, double value) +{ + switch (parameterindex) { + case WParameter::First: + handler->SplineDegree = std::max(1, static_cast(value)); + break; + } +} + +template<> +void DSHBSplineController::adaptDrawingToCheckboxChange(int checkboxindex, bool value) +{ + switch (checkboxindex) { + case WCheckbox::FirstBox: + handler->periodic = value; + break; + } + + handler->updateCursor(); +} + +template<> +void DSHBSplineControllerBase::doEnforceControlParameters(Base::Vector2d& onSketchPos) +{ + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (onViewParameters[OnViewParameter::First]->isSet) { + onSketchPos.x = onViewParameters[OnViewParameter::First]->getValue(); + } + + if (onViewParameters[OnViewParameter::Second]->isSet) { + onSketchPos.y = onViewParameters[OnViewParameter::Second]->getValue(); + } + } break; + case SelectMode::SeekSecond: { + if (handler->resetSeekSecond) { + handler->resetSeekSecond = false; + unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); + unsetOnViewParameter(onViewParameters[OnViewParameter::Fourth].get()); + setFocusToOnViewParameter(OnViewParameter::Third); + return; + } + + Base::Vector2d prevPoint = handler->getLastPoint(); + + Base::Vector2d dir = onSketchPos - prevPoint; + if (dir.Length() < Precision::Confusion()) { + dir.x = 1.0; // if direction null, default to (1,0) + } + double length = dir.Length(); + + if (onViewParameters[OnViewParameter::Third]->isSet) { + length = onViewParameters[OnViewParameter::Third]->getValue(); + if (length < Precision::Confusion()) { + unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); + return; + } + + onSketchPos = prevPoint + length * dir.Normalize(); + if (handler->geoIds.size() == handler->distances.size()) { + handler->distances.push_back(length); + } + else { + // update in case it changed + handler->distances[handler->distances.size() - 1] = length; + } + } + + if (onViewParameters[OnViewParameter::Fourth]->isSet) { + double angle = + Base::toRadians(onViewParameters[OnViewParameter::Fourth]->getValue()); + onSketchPos.x = prevPoint.x + cos(angle) * length; + onSketchPos.y = prevPoint.y + sin(angle) * length; + } + + if (onViewParameters[OnViewParameter::Third]->isSet + && onViewParameters[OnViewParameter::Fourth]->isSet + && (onSketchPos - prevPoint).Length() < Precision::Confusion()) { + unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); + unsetOnViewParameter(onViewParameters[OnViewParameter::Fourth].get()); + } + } break; + default: + break; + } +} + +template<> +void DSHBSplineController::adaptParameters(Base::Vector2d onSketchPos) +{ + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (!onViewParameters[OnViewParameter::First]->isSet) { + setOnViewParameterValue(OnViewParameter::First, onSketchPos.x); + } + + if (!onViewParameters[OnViewParameter::Second]->isSet) { + setOnViewParameterValue(OnViewParameter::Second, onSketchPos.y); + } + + bool sameSign = onSketchPos.x * onSketchPos.y > 0.; + onViewParameters[OnViewParameter::First]->setLabelAutoDistanceReverse(!sameSign); + onViewParameters[OnViewParameter::Second]->setLabelAutoDistanceReverse(sameSign); + onViewParameters[OnViewParameter::First]->setPoints(Base::Vector3d(), + toVector3d(onSketchPos)); + onViewParameters[OnViewParameter::Second]->setPoints(Base::Vector3d(), + toVector3d(onSketchPos)); + } break; + case SelectMode::SeekSecond: { + Base::Vector2d prevPoint; + if (!handler->points.empty()) { + prevPoint = handler->getLastPoint(); + } + + Base::Vector3d start = toVector3d(prevPoint); + Base::Vector3d end = toVector3d(onSketchPos); + Base::Vector3d vec = end - start; + + if (!onViewParameters[OnViewParameter::Third]->isSet) { + setOnViewParameterValue(OnViewParameter::Third, vec.Length()); + } + + double range = (onSketchPos - prevPoint).Angle(); + if (!onViewParameters[OnViewParameter::Fourth]->isSet) { + setOnViewParameterValue(OnViewParameter::Fourth, + Base::toDegrees(range), + Base::Unit::Angle); + } + + onViewParameters[OnViewParameter::Third]->setPoints(start, end); + onViewParameters[OnViewParameter::Fourth]->setPoints(start, Base::Vector3d()); + onViewParameters[OnViewParameter::Fourth]->setLabelRange(range); + } break; + default: + break; + } +} + +template<> +void DSHBSplineController::doChangeDrawSketchHandlerMode() +{ + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (onViewParameters[OnViewParameter::First]->isSet + && onViewParameters[OnViewParameter::Second]->isSet) { + double x = onViewParameters[OnViewParameter::First]->getValue(); + double y = onViewParameters[OnViewParameter::Second]->getValue(); + handler->onButtonPressed(Base::Vector2d(x, y)); + } + } break; + case SelectMode::SeekSecond: { + if (onViewParameters[OnViewParameter::Third]->isSet + && onViewParameters[OnViewParameter::Fourth]->isSet) { + handler->canGoToNextMode(); // its not going to next mode + + unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); + unsetOnViewParameter(onViewParameters[OnViewParameter::Fourth].get()); + } + } break; + default: + break; + } +} + + +template<> +bool DSHBSplineControllerBase::resetOnConstructionMethodeChanged() +{ + handler->changeConstructionMethode(); + return false; +} + + +template<> +void DSHBSplineController::addConstraints() +{ + + App::DocumentObject* obj = handler->sketchgui->getObject(); + + int firstCurve = handler->geoIds[0]; + + Sketcher::PointPos pPos = handler->constructionMethod() == ConstructionMethod::ControlPoints + ? Sketcher::PointPos::mid + : Sketcher::PointPos::start; + + auto x0 = onViewParameters[OnViewParameter::First]->getValue(); + auto y0 = onViewParameters[OnViewParameter::Second]->getValue(); + + auto x0set = onViewParameters[OnViewParameter::First]->isSet; + auto y0set = onViewParameters[OnViewParameter::Second]->isSet; + + using namespace Sketcher; + + auto constraintToOrigin = [&]() { + ConstraintToAttachment(GeoElementId(firstCurve, pPos), GeoElementId::RtPnt, x0, obj); + }; + + auto constraintx0 = [&]() { + ConstraintToAttachment(GeoElementId(firstCurve, pPos), GeoElementId::VAxis, x0, obj); + }; + + auto constrainty0 = [&]() { + ConstraintToAttachment(GeoElementId(firstCurve, pPos), GeoElementId::HAxis, y0, obj); + }; + + auto constraintlengths = [&](bool checkDof) { + for (size_t i = 0; i < handler->geoIds.size() - 1; ++i) { + bool dofOk = true; + if (checkDof) { + handler->diagnoseWithAutoConstraints(); + auto p1info = handler->getPointInfo(GeoElementId(handler->geoIds[i], pPos)); + auto p2info = handler->getPointInfo(GeoElementId(handler->geoIds[i + 1], pPos)); + + int DoFs = p1info.getDoFs(); + DoFs += p2info.getDoFs(); + dofOk = DoFs > 0; + } + + if (handler->distances[i + 1] > 0 && dofOk) { + Gui::cmdAppObjectArgs( + obj, + "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ", + handler->geoIds[i], + static_cast(pPos), + handler->geoIds[i + 1], + static_cast(pPos), + handler->distances[i + 1]); + } + } + }; + + + if (handler->AutoConstraints.empty()) { // No valid diagnosis. Every constraint can be added. + + if (x0set && y0set && x0 == 0. && y0 == 0.) { + constraintToOrigin(); + } + else { + if (x0set) { + constraintx0(); + } + + if (y0set) { + constrainty0(); + } + } + + constraintlengths(false); + } + else { // Valid diagnosis. Must check which constraints may be added. + auto startpointinfo = handler->getPointInfo(GeoElementId(firstCurve, PointPos::start)); + + if (x0set && startpointinfo.isXDoF()) { + constraintx0(); + + // ensure we have recalculated parameters after each constraint addition + handler->diagnoseWithAutoConstraints(); + + // get updated point position + startpointinfo = handler->getPointInfo(GeoElementId(firstCurve, PointPos::start)); + } + + if (y0set && startpointinfo.isYDoF()) { + constrainty0(); + } + + constraintlengths(true); + } +} + +// TODO: On pressing, say, W, modify last pole's weight +// TODO: On pressing, say, M, modify next knot's multiplicity + } // namespace SketcherGui diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h deleted file mode 100644 index 14b73d03bb..0000000000 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSplineByInterpolation.h +++ /dev/null @@ -1,755 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2023 Ajinkya Dahale * - * * - * 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 * - * * - ***************************************************************************/ - -#ifndef SKETCHERGUI_DrawSketchHandlerBSplineByInterpolation_H -#define SKETCHERGUI_DrawSketchHandlerBSplineByInterpolation_H - -#include -#include - -#include -#include -#include - -#include - -#include "DrawSketchHandler.h" -#include "GeometryCreationMode.h" -#include "Utils.h" -#include "ViewProviderSketch.h" - - -namespace SketcherGui -{ - -extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp - -class DrawSketchHandlerBSplineByInterpolation: public DrawSketchHandler -{ -public: - explicit DrawSketchHandlerBSplineByInterpolation(int constructionMethod) - : Mode(STATUS_SEEK_FIRST_POINT) - , MousePressMode(MOUSE_NOT_PRESSED) - , ConstrMethod(constructionMethod) - , SplineDegree(3) - , IsClosed(false) - { - addSugConstraint(); - applyCursor(); - } - - ~DrawSketchHandlerBSplineByInterpolation() override = default; - - /// modes - enum SELECT_MODE - { - STATUS_SEEK_FIRST_POINT, - STATUS_SEEK_ADDITIONAL_POINTS, - STATUS_CLOSE - }; - - // TODO: this kind of behavior will be useful in a superclass - // when LMB is pressed it's a transitional state so some undos can't be done - // (like delete last knot) - enum MOUSE_PRESS_MODE - { - MOUSE_PRESSED, - MOUSE_NOT_PRESSED - }; - - void mouseMove(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - - if (Mode == STATUS_SEEK_FIRST_POINT) { - setPositionText(onSketchPos); - - if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr.back()); - return; - } - } - else if (Mode == STATUS_SEEK_ADDITIONAL_POINTS) { - - drawBSplineToPosition(onSketchPos); - - drawCursorToPosition(onSketchPos); - - if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr.back()); - return; - } - } - } - - bool pressButton(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - - MousePressMode = MOUSE_PRESSED; - - if (Mode == STATUS_SEEK_FIRST_POINT) { - BSplineKnots.push_back(onSketchPos); - BSplineMults.push_back(1); // NOTE: not strictly true for end-points - - Mode = STATUS_SEEK_ADDITIONAL_POINTS; - - // insert point for knot, defer internal alignment constraining. - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add Knot Point")); - - // Add knot - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addGeometry(Part.Point(App.Vector(%f,%f,0)),True)", - BSplineKnots.back().x, - BSplineKnots.back().y); - - knotGeoIds.push_back(getHighestCurveIndex()); - } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Cannot add knot point")); - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return false; - } - - // add auto constraints on knot - if (!sugConstr.back().empty()) { - createAutoConstraints(sugConstr.back(), - knotGeoIds.back(), - Sketcher::PointPos::start, - false); - } - - static_cast(sketchgui->getObject())->solve(); - - addSugConstraint(); - } - else if (Mode == STATUS_SEEK_ADDITIONAL_POINTS) { - // check if coincidence issues with first or last added knot - for (auto& ac : sugConstr.back()) { - if (ac.Type == Sketcher::Coincident) { - if (ac.GeoId == knotGeoIds[0] && ac.PosId == Sketcher::PointPos::start) { - IsClosed = true; - } - else { - // The coincidence with first point may be indirect - const auto coincidents = - static_cast(sketchgui->getObject()) - ->getAllCoincidentPoints(ac.GeoId, ac.PosId); - if (coincidents.find(knotGeoIds[0]) != coincidents.end() - && coincidents.at(knotGeoIds[0]) == Sketcher::PointPos::start) { - IsClosed = true; - } - else if (coincidents.find(knotGeoIds.back()) != coincidents.end() - && coincidents.at(knotGeoIds.back()) - == Sketcher::PointPos::start) { - return true; // OCCT doesn't allow consecutive points being coincident - } - } - } - } - - BSplineKnots.push_back(onSketchPos); - BSplineMults.push_back(1); // NOTE: not strictly true for end-points - - if (IsClosed) { - Mode = STATUS_CLOSE; - - if (ConstrMethod == 1) { // if periodic we do not need the last pole - BSplineKnots.pop_back(); - sugConstr.pop_back(); - - return true; - } - } - - // insert point for knot, defer internal alignment constraining. - try { - - - // Add knot - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "addGeometry(Part.Point(App.Vector(%f,%f,0)),True)", - BSplineKnots.back().x, - BSplineKnots.back().y); - - knotGeoIds.push_back(getHighestCurveIndex()); - } - catch (const Base::Exception&) { - Gui::NotifyError( - sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Cannot add internal alignment points")); - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return false; - } - - // add auto constraints on knot - if (!sugConstr.back().empty()) { - createAutoConstraints(sugConstr.back(), - knotGeoIds.back(), - Sketcher::PointPos::start, - false); - } - - if (!IsClosed) { - addSugConstraint(); - } - } - return true; - } - - bool releaseButton(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - MousePressMode = MOUSE_NOT_PRESSED; - - return finishCommand(onSketchPos); - } - - void registerPressedKey(bool pressed, int key) override - { - // if (SoKeyboardEvent::D == key && pressed) { - // SplineDegree = QInputDialog::getInt( - // Gui::getMainWindow(), - // QObject::tr("B-Spline Degree"), - // QObject::tr("Define B-Spline Degree, between 1 and %1:") - // .arg(QString::number(Geom_BSplineCurve::MaxDegree())), - // SplineDegree, 1, Geom_BSplineCurve::MaxDegree(), 1); - // // FIXME: Pressing Esc here also finishes the B-Spline creation. - // // The user may only want to exit the dialog. - // } - if (SoKeyboardEvent::M == key && pressed) { - if (BSplineMults.size() > 1) { - BSplineMults.back() = QInputDialog::getInt( - Gui::getMainWindow(), - QObject::tr("Set knot multiplicity"), - QObject::tr( - "Set knot multiplicity at the last point provided, between 1 and %1:" - "Note that multiplicity may be ignored under certain circumstances." - "Please refer to documentation for details") - .arg(QString::number(SplineDegree)), - BSplineMults.back(), - 1, - SplineDegree, - 1); - } - // FIXME: Pressing Esc here also finishes the B-Spline creation. - // The user may only want to exit the dialog. - } - // On pressing Backspace delete last knot - else if (SoKeyboardEvent::BACKSPACE == key && pressed) { - // when mouse is pressed we are in a transitional state so don't mess with it - if (MOUSE_PRESSED == MousePressMode) { - return; - } - - // can only delete last knot if it exists - if (STATUS_SEEK_FIRST_POINT == Mode || STATUS_CLOSE == Mode) { - return; - } - - // if only first knot exists it's equivalent to canceling current spline - if (knotGeoIds.size() == 1) { - // this also exits b-spline creation if continuous mode is off - this->quit(); - return; - } - - // reverse the steps of press/release button - try { - // already ensured that CurrentConstraint == EditCurve.size() > 1 - const int delGeoId = knotGeoIds.back(); - const auto& constraints = - static_cast(sketchgui->getObject()) - ->Constraints.getValues(); - for (int i = constraints.size() - 1; i >= 0; --i) { - if (delGeoId == constraints[i]->First || delGeoId == constraints[i]->Second - || delGeoId == constraints[i]->Third) { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "delConstraint(%d)", i); - } - } - - // Remove knot - Gui::cmdAppObjectArgs(sketchgui->getObject(), "delGeometry(%d)", delGeoId); - - static_cast(sketchgui->getObject())->solve(); - - knotGeoIds.pop_back(); - BSplineKnots.pop_back(); - - // last entry is kept, as it corresponds to the current knot, but the one - // corresponding to the erased knot is removed - sugConstr.erase(std::prev(std::prev(sugConstr.end()))); - - - // run this in the end to draw lines and position text - drawBSplineToPosition(prevCursorPosition); - drawCursorToPosition(prevCursorPosition); - } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error removing knot")); - // some commands might have already deleted some constraints/geometries but not - // others - Gui::Command::abortCommand(); - - static_cast(sketchgui->getObject())->solve(); - - return; - } - } - - return; - } - - void quit() override - { - // We must see if we need to create a B-spline before cancelling everything - // and now just like any other Handler, - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Sketcher"); - - bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); - - if (knotGeoIds.size() > 1) { - // create B-spline from existing knots - Mode = STATUS_CLOSE; - finishCommand(Base::Vector2d(0.f, 0.f)); - } - else if (knotGeoIds.size() == 1) { - // if we just have one point and we can not close anything, then cancel this creation - // but continue according to continuous mode - // sketchgui->getDocument()->undo(1); - - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); - - if (!continuousMode) { - DrawSketchHandler::quit(); - } - else { - // This code disregards existing data and enables the continuous creation mode. - resetHandlerState(); - } - } - else { // we have no data (CurrentConstraint == 0) so user when right-clicking really wants - // to exit - DrawSketchHandler::quit(); - } - } - -private: - void resetHandlerState() - { - Mode = STATUS_SEEK_FIRST_POINT; - applyCursor(); - - SplineDegree = 3; - - sugConstr.clear(); - knotGeoIds.clear(); - BSplineKnots.clear(); - BSplineMults.clear(); - - eraseEditCurve(); - - addSugConstraint(); - - IsClosed = false; - } - - QString getCrosshairCursorSVGName() const override - { - if (SketcherGui::DrawSketchHandlerBSplineByInterpolation::ConstrMethod == 1) { - return QString::fromLatin1("Sketcher_Pointer_Create_Periodic_BSplineByInterpolation"); - } - else { - return QString::fromLatin1("Sketcher_Pointer_Create_BSplineByInterpolation"); - } - } - - void addSugConstraint() - { - std::vector sugConstr1; - sugConstr.push_back(std::move(sugConstr1)); - } - - // NOTE: In this context, it is not a control polygon, but a 1-degree interpolation - void drawControlPolygonToPosition(Base::Vector2d position) - { - std::vector editcurve(BSplineKnots); - editcurve.push_back(position); - - drawEdit(editcurve); - } - - void drawBSplineToPosition(Base::Vector2d position) - { - try { - tryInterpolateSpline(position); - } - catch (const Standard_Failure&) { - // Since it happens very frequently that the interpolation fails - // it's sufficient to report this as log message to avoid to pollute - // the output window - Base::Console().Log(std::string("drawBSplineToPosition"), "interpolation failed\n"); - } - } - - void tryInterpolateSpline(Base::Vector2d position) - { - std::vector editcurve(BSplineKnots); - editcurve.push_back(position); - - std::vector editCurveForOCCT; - editCurveForOCCT.reserve(editcurve.size()); - for (auto& p : editcurve) { - editCurveForOCCT.emplace_back(p.x, p.y, 0.0); - } - - // TODO: This maybe optimized by storing the spline as an attribute. - Part::GeomBSplineCurve editBSpline; - editBSpline.interpolate(editCurveForOCCT, ConstrMethod != 0); - - std::vector editBSplines; - editBSplines.push_back(&editBSpline); - - drawEdit(editBSplines); - } - - void drawCursorToPosition(Base::Vector2d position) - { - if (!BSplineKnots.empty()) { - float length = (position - BSplineKnots.back()).Length(); - float angle = (position - BSplineKnots.back()).GetAngle(Base::Vector2d(1.f, 0.f)); - - if (showCursorCoords()) { - SbString text; - std::string lengthString = lengthToDisplayFormat(length, 1); - std::string angleString = - angleToDisplayFormat((angle != -FLOAT_MAX) ? angle * 180 / M_PI : 0, 1); - text.sprintf(" (%s, %s)", lengthString.c_str(), angleString.c_str()); - setPositionText(position, text); - } - } - } - - void eraseEditCurve() - { - drawEdit(std::vector()); - } - - bool finishCommand(Base::Vector2d position) - { - if (Mode == STATUS_CLOSE) { - unsetCursor(); - resetPositionText(); - - unsigned int myDegree = 3; - - if (ConstrMethod == 0) { - BSplineMults.front() = - myDegree + 1; // FIXME: This is hardcoded until degree can be changed - BSplineMults.back() = - myDegree + 1; // FIXME: This is hardcoded until degree can be changed - } - - std::vector streams; - - // Create subsets of points between C0 knots. - // The first point - streams.emplace_back(); - streams.back() << "App.Vector(" << BSplineKnots.front().x << "," - << BSplineKnots.front().y << "),"; - // Middle points - for (size_t i = 1; i < BSplineKnots.size() - 1; ++i) { - streams.back() << "App.Vector(" << BSplineKnots[i].x << "," << BSplineKnots[i].y - << "),"; - if (BSplineMults[i] >= myDegree) { - streams.emplace_back(); - streams.back() - << "App.Vector(" << BSplineKnots[i].x << "," << BSplineKnots[i].y << "),"; - } - } - // The last point - streams.back() << "App.Vector(" << BSplineKnots.back().x << "," << BSplineKnots.back().y - << "),"; - - // Note the plural of plurals. Each element is a separate sequence. - std::vector controlpointses; - controlpointses.reserve(streams.size()); - for (auto& stream : streams) { - controlpointses.emplace_back(stream.str()); - - - auto& controlpoints = controlpointses.back(); - - // remove last comma and add brackets - int index = controlpoints.rfind(','); - controlpoints.resize(index); - - controlpoints.insert(0, 1, '['); - controlpoints.append(1, ']'); - } - - // With just 3 points provided OCCT gives a quadratic spline where - // the middle point is NOT a knot. This needs to be treated differently. - // FIXME: Decide whether to force a knot or not. - std::vector isBetweenC0Points(BSplineKnots.size(), false); - for (size_t i = 1; i < BSplineKnots.size() - 1; ++i) { - if (BSplineMults[i - 1] >= myDegree && BSplineMults[i + 1] >= myDegree) { - isBetweenC0Points[i] = true; - } - } - - int currentgeoid = getHighestCurveIndex(); - - try { - // TODO: Bypass this for when there are no C0 knots - // Create B-spline in pieces between C0 knots - Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_poles = []"); - Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_knots = []"); - Gui::Command::runCommand(Gui::Command::Gui, "_finalbsp_mults = []"); - Gui::Command::runCommand(Gui::Command::Gui, "_bsps = []"); - for (auto& controlpoints : controlpointses) { - // TODO: variable degrees? - QString cmdstr = - QString::fromLatin1("_bsps.append(Part.BSplineCurve())\n" - "_bsps[-1].interpolate(%1, PeriodicFlag=%2)\n" - "_bsps[-1].increaseDegree(%3)") - .arg(QString::fromLatin1(controlpoints.c_str())) - .arg(QString::fromLatin1(ConstrMethod == 0 ? "False" : "True")) - .arg(myDegree); - Gui::Command::runCommand(Gui::Command::Gui, cmdstr.toLatin1()); - // Adjust internal knots here (raise multiplicity) - // How this contributes to the final B-spline - if (controlpoints == controlpointses.front()) { - Gui::Command::runCommand(Gui::Command::Gui, - "_finalbsp_poles.extend(_bsps[-1].getPoles())"); - Gui::Command::runCommand(Gui::Command::Gui, - "_finalbsp_knots.extend(_bsps[-1].getKnots())"); - Gui::Command::runCommand( - Gui::Command::Gui, - "_finalbsp_mults.extend(_bsps[-1].getMultiplicities())"); - } - else { - Gui::Command::runCommand( - Gui::Command::Gui, - "_finalbsp_poles.extend(_bsps[-1].getPoles()[1:])"); - Gui::Command::runCommand(Gui::Command::Gui, - "_finalbsp_knots.extend([_finalbsp_knots[-1] + i " - "for i in _bsps[-1].getKnots()[1:]])"); - Gui::Command::runCommand(Gui::Command::Gui, - "_finalbsp_mults[-1] = 3"); // FIXME: Hardcoded - Gui::Command::runCommand( - Gui::Command::Gui, - "_finalbsp_mults.extend(_bsps[-1].getMultiplicities()[1:])"); - } - } - - // {"poles", "mults", "knots", "periodic", "degree", "weights", "CheckRational", - // NULL}; - Gui::cmdAppObjectArgs( - sketchgui->getObject(), - "addGeometry(Part.BSplineCurve" - "(_finalbsp_poles,_finalbsp_mults,_finalbsp_knots,%s,%d,None,False),%s)", - ConstrMethod == 0 ? "False" : "True", - myDegree, - constructionModeAsBooleanText()); - currentgeoid++; - - // TODO: Confirm we do not need to delete individual elements - Gui::Command::runCommand(Gui::Command::Gui, "del(_bsps)\n"); - Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_poles)\n"); - Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_knots)\n"); - Gui::Command::runCommand(Gui::Command::Gui, "del(_finalbsp_mults)\n"); - - // autoconstraints were added to the knots, which is ok because they must go to the - // right position, or the user will freak-out if they appear out of the - // autoconstrained position. However, autoconstraints on the first and last knot, in - // non-periodic b-splines (with appropriate endpoint knot multiplicity) as the ones - // created by this tool are intended for the b-spline endpoints, and not for the - // knots, so here we retrieve any autoconstraint on those knots and mangle it to the - // endpoint. - if (ConstrMethod == 0) { - for (auto& constr : static_cast(sketchgui->getObject()) - ->Constraints.getValues()) { - if (constr->First == knotGeoIds[0] - && constr->FirstPos == Sketcher::PointPos::start) { - constr->First = currentgeoid; - constr->FirstPos = Sketcher::PointPos::start; - } - else if (constr->First == knotGeoIds.back() - && constr->FirstPos == Sketcher::PointPos::start) { - constr->First = currentgeoid; - constr->FirstPos = Sketcher::PointPos::end; - } - } - } - - // Constraint knots to B-spline. - std::stringstream cstream; - - cstream << "conList = []\n"; - - int knotNumber = 0; - for (size_t i = 0; i < knotGeoIds.size(); i++) { - if (isBetweenC0Points[i]) { - // Constraint point on curve - cstream << "conList.append(Sketcher.Constraint('PointOnObject'," - << knotGeoIds[0] + i << "," - << static_cast(Sketcher::PointPos::start) << "," - << currentgeoid << "))\n"; - } - else { - cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher:" - ":BSplineKnotPoint'," - << knotGeoIds[0] + i << "," - << static_cast(Sketcher::PointPos::start) << "," - << currentgeoid << "," << knotNumber << "))\n"; - // NOTE: Assume here that the spline shape doesn't change on increasing knot - // multiplicity. - // Change the knot multiplicity here because the user asked and it's not C0 - // NOTE: The knot number here has to be provided in the OCCT ordering. - if (BSplineMults[i] > 1 && BSplineMults[i] < myDegree) { - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "modifyBSplineKnotMultiplicity(%d, %d, %d) ", - currentgeoid, - knotNumber + 1, - BSplineMults[i] - 1); - } - knotNumber++; - } - } - - cstream << Gui::Command::getObjectCmd(sketchgui->getObject()) - << ".addConstraint(conList)\n"; - cstream << "del conList\n"; - - Gui::Command::doCommand(Gui::Command::Doc, cstream.str().c_str()); - - // for showing the rest of internal geometry on creation - Gui::cmdAppObjectArgs(sketchgui->getObject(), - "exposeInternalGeometry(%d)", - currentgeoid); - } - catch (const Base::Exception&) { - Gui::NotifyError(sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP("Notifications", "Error creating B-spline")); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); - - if (continuousMode) { - // This code enables the continuous creation mode. - resetHandlerState(); - - drawCursorToPosition(position); - - /* 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 false; - } - - Gui::Command::commitCommand(); - - tryAutoRecomputeIfNotSolve( - static_cast(sketchgui->getObject())); - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool continuousMode = hGrp->GetBool("ContinuousCreationMode", true); - - if (continuousMode) { - // This code enables the continuous creation mode. - resetHandlerState(); - - drawCursorToPosition(position); - - /* 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 - } - } - else { - drawCursorToPosition(position); - } - - return true; - } - -protected: - SELECT_MODE Mode; - MOUSE_PRESS_MODE MousePressMode; - - // Stores position of the knots of the BSpline. - std::vector BSplineKnots; - std::vector BSplineMults; - - // suggested autoconstraints for knots. - // A new one must be added e.g. using addSugConstraint() before adding a new knot. - std::vector> sugConstr; - - int ConstrMethod; - unsigned int SplineDegree; - bool IsClosed; - std::vector knotGeoIds; - Base::Vector2d prevCursorPosition; -}; - - -} // namespace SketcherGui - - -#endif // SKETCHERGUI_DrawSketchHandlerBSplineByInterpolation_H