From 677694b0bacebf0cfed65bbf4e726712072d4657 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sat, 14 Oct 2023 07:03:32 +0200 Subject: [PATCH] Sketcher: New DSH architecture to support multiple input ======================================================== Architecture to support multiple input from a widget and a mouse. --- src/Mod/Sketcher/Gui/CMakeLists.txt | 2 + .../Sketcher/Gui/DrawSketchDefaultHandler.h | 1118 ++++++++++++++++ .../Gui/DrawSketchDefaultWidgetHandler.h | 1154 +++++++++++++++++ 3 files changed, 2274 insertions(+) create mode 100644 src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h create mode 100644 src/Mod/Sketcher/Gui/DrawSketchDefaultWidgetHandler.h diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index 6f237d6816..c2e2dda293 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -132,6 +132,8 @@ SET(SketcherGui_SRCS ViewProviderSketch.h DrawSketchHandler.cpp DrawSketchHandler.h + DrawSketchDefaultHandler.h + DrawSketchDefaultWidgetHandler.h Workbench.cpp Workbench.h EditDatumDialog.cpp diff --git a/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h b/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h new file mode 100644 index 0000000000..1acb05dabe --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchDefaultHandler.h @@ -0,0 +1,1118 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHERGUI_DrawSketchDefaultHandler_H +#define SKETCHERGUI_DrawSketchDefaultHandler_H + +#include + +#include +#include + +#include +#include +#include + +#include "DrawSketchHandler.h" + +#include "Utils.h" + +namespace bp = boost::placeholders; + +namespace SketcherGui +{ + +/*********************** Ancillary classes for DrawSketch Hierarchy *******************************/ + +/** @name Basic State Machines */ +//@{ + +namespace StateMachines +{ + +enum class OneSeekEnd +{ + SeekFirst, + End // MUST be the last one +}; + +enum class TwoSeekEnd +{ + SeekFirst, + SeekSecond, + End // MUST be the last one +}; + +enum class ThreeSeekEnd +{ + SeekFirst, + SeekSecond, + SeekThird, + End // MUST be the last one +}; + +enum class FourSeekEnd +{ + SeekFirst, + SeekSecond, + SeekThird, + SeekFourth, + End // MUST be the last one +}; + +enum class FiveSeekEnd +{ + SeekFirst, + SeekSecond, + SeekThird, + SeekFourth, + SeekFifth, + End // MUST be the last one +}; + +enum class TwoSeekDoEnd +{ + SeekFirst, + SeekSecond, + Do, + End // MUST be the last one +}; + +} // namespace StateMachines + +//@} + +/** @brief A state machine to encapsulate a state + * + * @details + * + * A template class for a state machine defined by template type SelectModeT, + * automatically initialised to the first state, encapsulating the actual state, + * and enabling to change the state, while generating a call to onModeChanged() + * after every change. + * + * the getNextMode() returns the next mode in the state machine, unless it is in + * End mode, in which End mode is returned. + * + * The goal of this class to sense navigation through the states of the time machine (for example + * as triggered by one input method) and notifying of changes (for example to another input method + * to update it appropriately). + * + * NOTE: Some basic state machines are provided. However, it is possible for the user to provide its + * own customised time machine instead. The state machine provided MUST include a last state named + * End. + */ +template +class StateMachine +{ +public: + StateMachine() + : Mode(static_cast(0)) + {} + virtual ~StateMachine() + {} + +protected: + void setState(SelectModeT mode) + { + Mode = mode; + onModeChanged(); + } + + void ensureState(SelectModeT mode) + { + if (Mode != mode) { + Mode = mode; + onModeChanged(); + } + } + + /** Ensure the state machine is the provided mode + * but only if the mode is an earlier state. + * + * This allows to return to previous states (e.g. + * for modification), only if that state has previously + * been completed. + */ + void ensureStateIfEarlier(SelectModeT mode) + { + if (Mode != mode) { + if (mode < Mode) { + Mode = mode; + onModeChanged(); + } + } + } + + SelectModeT state() const + { + return Mode; + } + + bool isState(SelectModeT state) const + { + return Mode == state; + } + + bool isFirstState() const + { + return Mode == (static_cast(0)); + } + + bool isLastState() const + { + return Mode == SelectModeT::End; + } + + constexpr SelectModeT getFirstState() const + { + return static_cast(0); + } + + SelectModeT getNextMode() const + { + auto modeint = static_cast(state()); + + + if (modeint < maxMode) { + auto newmode = static_cast(modeint + 1); + return newmode; + } + else { + return SelectModeT::End; + } + } + + void moveToNextMode() + { + setState(getNextMode()); + } + + void reset() + { + if (Mode != static_cast(0)) { + setState(static_cast(0)); + } + } + + virtual void onModeChanged() {}; + +private: + SelectModeT Mode; + static const constexpr int maxMode = static_cast(SelectModeT::End); +}; + + +namespace ConstructionMethods +{ + +enum class DefaultConstructionMethod +{ + End // Must be the last one +}; + +} // namespace ConstructionMethods + +/** @brief An encapsulation of a construction method + * + * @details + * + * A template class for a construction method defined by template type ConstructionMethodT. + * + * A construction method is an alternative UI sequence of steps or input parameters to achieve a + * goal.For example, for creation of a circle, point center + radius is a possible construction + * method. Three points on the rim is another possible construction method. + * + * The construction method is automatically initialised to the first or default construction method. + * It senses construction method change, triggering onModeChanged() after every change. + * + * the getNextMethod() returns the next construction method, in a round-robin fashion. + * + * NOTE: A construction method enum defining the different construction methods must be provided and + * the last item shall be ConstructionMethodT::End. + */ +template +class ConstructionMethodMachine +{ +public: + ConstructionMethodMachine( + ConstructionMethodT constructionmethod = static_cast(0)) + : ConstructionMode(constructionmethod) + {} + virtual ~ConstructionMethodMachine() + {} + +protected: + void setConstructionMethod(ConstructionMethodT mode) + { + ConstructionMode = mode; + onConstructionMethodChanged(); + } + + ConstructionMethodT constructionMethod() const + { + return ConstructionMode; + } + + bool isConstructionMethod(ConstructionMethodT state) const + { + return ConstructionMode == state; + } + + void resetConstructionMode() + { + ConstructionMode = static_cast(0); + } + + void initConstructionMethod(ConstructionMethodT mode) + { + ConstructionMode = mode; + } + + // Cyclically iterate construction methods + ConstructionMethodT getNextMethod() const + { + auto modeint = static_cast(ConstructionMode); + + + if (modeint < (maxMode - 1)) { + auto newmode = static_cast(modeint + 1); + return newmode; + } + else { + return static_cast(0); + } + } + + void iterateToNextConstructionMethod() + { + if (ConstructionMethodsCount() > 1) { + setConstructionMethod(getNextMethod()); + } + } + + virtual void onConstructionMethodChanged() {}; + + static constexpr int ConstructionMethodsCount() + { + return maxMode; + } + +private: + ConstructionMethodT ConstructionMode; + static const constexpr int maxMode = static_cast(ConstructionMethodT::End); +}; + +/** @brief A DrawSketchHandler template for geometry creation. + * + * @details + * A state machine DrawSketchHandler providing: + * - generic initialisation including setting the cursor + * - structured command finalisation + * - handling of continuous creation mode + * + * This class is intended to be used by instantiating the template with a new DSH type, and + * then derive the new type from the instantiated template. This allows to inherit all + * the functionality, have direct access to all handler members, while allowing the DSH creator to + * add additional data members and functions (and avoiding extensive usage of macros). + * + * Example: + * + * class DrawSketchHandlerPoint; + * + * using DrawSketchHandlerPointBase = + * DrawSketchDefaultHandler; + * + * class DrawSketchHandlerPoint: public DrawSketchHandlerPointBase + * { + * ... + * } + * + * This approach enables seamless upgrading to a default or custom widget DSH. + * + * This class provides an NVI interface for extension. + * + * Question 1: Do I need to use this handler or derive from this handler to make a new handler? + * + * No, you do not NEED to. But you are encouraged to. Structuring a handler following this NVI, + * apart from savings in amount of code typed, enables a much easier and less verbose implementation + * of a handler using a default widget (toolwidget). + * + * For handlers using a custom widget it will also help by structuring the code in a way consistent + * with other handlers. It will result in an easier to maintain code. + * + * Question 2: I want to use the default widget, do I need to use this handler or derive from this + * handler? + * + * You should use DrawSketchDefaultWidgetHandler instead. However, both classes use the same + * interface, so if you derive from this class when implementing your handler and then decide to use + * the tool widget, all you have to do is to change the base class from DrawSketchDefaultHandler to + * DrawSketchDefaultWidgetHandler. Then you will have to implement the code that is exclusively + * necessary for the default widget to work. + */ +template + typename ConstructionMethodT = ConstructionMethods::DefaultConstructionMethod> +class DrawSketchDefaultHandler: public DrawSketchHandler, + public StateMachine, + public ConstructionMethodMachine +{ +public: + DrawSketchDefaultHandler( + ConstructionMethodT constructionmethod = static_cast(0)) + : ConstructionMethodMachine(constructionmethod) + , sugConstraints(PInitAutoConstraintSize) + { + applyCursor(); + } + + virtual ~DrawSketchDefaultHandler() + {} + + /** @name public DrawSketchHandler interface + * NOTE: Not intended to be specialised. It calls some functions intended to be + * overridden/specialised instead. + */ + //@{ + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + updateDataAndDrawToPosition(onSketchPos); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + + onButtonPressed(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + finish(); + return true; + } + + virtual void registerPressedKey(bool pressed, int key) override + { + if (key == SoKeyboardEvent::M && pressed && !this->isLastState()) { + this->iterateToNextConstructionMethod(); + } + } + //@} + + +protected: + using SelectMode = SelectModeT; + using ModeStateMachine = StateMachine; + using ConstructionMethod = ConstructionMethodT; + using ConstructionMachine = ConstructionMethodMachine; + + /** @name functions NOT intended for specialisation or to be hidden + These functions define a predefined structure and are extendable using NVI. + 1. Call them from your handle + 2. Do not hide them or otherwise redefine them UNLESS YOU HAVE A REALLY GOOD REASON TO + 3. EXTEND their functionality, if needed, using the NVI interface (or if you do not need to + derive, by specialising these functions).*/ + //@{ + /** @brief This function finalises the creation operation. It works only if the state machine is + * in state End. + * + * @details + * The functionality need to be provided by extending these virtual private functions: + * 1. executeCommands() : Must be provided with the Commands to create the geometry + * 2. generateAutoConstraints() : When using AutoConstraints vector, this function populates the + * AutoConstraints vector, ensuring no redundant autoconstraints + * 2. beforeCreateAutoConstraints() : Enables derived classes to define specific actions before + * executeCommands and createAutoConstraints (optional). + * 3. createAutoConstraints() : Must be provided with the commands to create autoconstraints + * + * It recomputes if not solves and handles continuous mode automatically + */ + void finish() + { + if (this->isState(SelectMode::End)) { + unsetCursor(); + resetPositionText(); + + try { + executeCommands(); + + if (sugConstraints.size() > 0) { + generateAutoConstraints(); + + beforeCreateAutoConstraints(); + + createAutoConstraints(); + } + + tryAutoRecomputeIfNotSolve( + static_cast(sketchgui->getObject())); + } + catch (const Base::RuntimeError& e) { + // RuntimeError exceptions inside of the block above must provide a translatable + // message. It is reported both to developer (report view) and user (notifications + // area). + Base::Console().Error(e.what()); + } + + handleContinuousMode(); + } + } + + /** @brief This function resets the handler to the initial state. + * + * @details + * The functionality can be extended using these virtual private function: + * 1. onReset() : Any further initialisation applicable to your handler + * + * It clears the edit curve, resets the state machine and resizes edit curve and autoconstraints + * to initial size. Reapplies the cursor bitmap. + */ + void reset() + { + clearEdit(); + + ModeStateMachine::reset(); + + for (auto& ac : sugConstraints) { + ac.clear(); + } + + AutoConstraints.clear(); + ShapeGeometry.clear(); + ShapeConstraints.clear(); + + onReset(); + applyCursor(); + } + + /** @brief This function handles the geometry continuous mode. + * + * @details + * The functionality can be extended using the virtual private function called from reset(), + * namely: + * 1. onReset() : Any further initialisation applicable to your handler + * + * It performs all the operations in reset(). + */ + void handleContinuousMode() + { + if (continuousMode) { + // This code enables the continuous creation mode. + reset(); + // 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, Handler get deleted in ViewProvider + } + } + //@} + +private: + /** @name functions are intended to be overridden/specialised to extend basic functionality + NVI interface. See documentation of the functions above.*/ + //@{ + virtual void onReset() + {} + virtual void executeCommands() + {} + virtual void generateAutoConstraints() + {} + virtual void beforeCreateAutoConstraints() + {} + virtual void createAutoConstraints() + {} + + virtual void onConstructionMethodChanged() override {}; + + virtual void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) + { + Q_UNUSED(onSketchPos) + }; + + virtual void angleSnappingControl() + {} + + // function intended to populate ShapeGeometry and ShapeConstraints + virtual void createShape(bool onlyeditoutline) + { + Q_UNUSED(onlyeditoutline) + } + //@} +protected: + /** @name functions are intended to be overridden/specialised to extend basic functionality + See documentation of the functions above*/ + //@{ + /** @brief Minimal handle activation respecting avoid redundants and continuous mode.*/ + virtual void activated() override + { + avoidRedundants = + sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Sketcher"); + + continuousMode = hGrp->GetBool("ContinuousCreationMode", true); + } + + /** @brief Default button pressing implementation, which redraws and moves to the next machine + * state, until reaching end. + * + * If this behaviour is not acceptable, then the function must be specialised (or overloaded).*/ + virtual void onButtonPressed(Base::Vector2d onSketchPos) + { + this->updateDataAndDrawToPosition(onSketchPos); + this->moveToNextMode(); + } + + /** @brief Default behaviour that upon arriving to the End state of the state machine, the + * command is finished. */ + virtual void onModeChanged() override + { + finish(); // internally checks that state is SelectMode::End, and only finishes then. + angleSnappingControl(); + }; + //@} + + /** @name Helper functions + See documentation of the functions above*/ + //@{ + + /** @brief Convenience function to automatically generate and add to the AutoConstraints vector + * the suggested constraints. + * + * This generates actual Sketcher::Constraint which can be used for diagnostic before addition. + */ + void generateAutoConstraintsOnElement(const std::vector& autoConstrs, + int geoId1, + Sketcher::PointPos posId1) + { + if (!sketchgui->Autoconstraints.getValue()) { + return; + } + + if (autoConstrs.size() > 0) { + for (auto& ac : autoConstrs) { + int geoId2 = ac.GeoId; + + switch (ac.Type) { + case Sketcher::Coincident: { + if (posId1 == Sketcher::PointPos::none) { + continue; + } + + // find if there is already a matching tangency + auto result = std::find_if(AutoConstraints.begin(), + AutoConstraints.end(), + [&](const auto& ace) { + return ace->Type == Sketcher::Tangent + && ace->First == geoId1 + && ace->Second == ac.GeoId; + }); + + + if (result + != AutoConstraints.end()) { // modify tangency to endpoint-to-endpoint + (*result)->FirstPos = posId1; + (*result)->SecondPos = ac.PosId; + } + else { + auto c = std::make_unique(); + c->Type = Sketcher::Coincident; + c->First = geoId1; + c->FirstPos = posId1; + c->Second = ac.GeoId; + c->SecondPos = ac.PosId; + AutoConstraints.push_back(std::move(c)); + } + + } break; + case Sketcher::PointOnObject: { + Sketcher::PointPos posId2 = ac.PosId; + if (posId1 == Sketcher::PointPos::none) { + // Auto constraining an edge so swap parameters + std::swap(geoId1, geoId2); + std::swap(posId1, posId2); + } + + auto result = std::find_if(AutoConstraints.begin(), + AutoConstraints.end(), + [&](const auto& ace) { + return ace->Type == Sketcher::Tangent + && ace->First == geoId1 + && ace->Second == ac.GeoId; + }); + + // if tangency, convert to point-to-edge tangency + if (result != AutoConstraints.end()) { + (*result)->FirstPos = posId1; + + if ((*result)->First != geoId1) { + std::swap((*result)->Second, (*result)->First); + } + } + else { + auto c = std::make_unique(); + c->Type = Sketcher::PointOnObject; + c->First = geoId1; + c->FirstPos = posId1; + c->Second = geoId2; + AutoConstraints.push_back(std::move(c)); + } + } break; + // In special case of Horizontal/Vertical constraint, geoId2 is normally unused + // and should be 'Constraint::GeoUndef' However it can be used as a way to + // require the function to apply these constraints on another geometry In this + // case the caller as to set geoId2, then it will be used as target instead of + // geoId2 + case Sketcher::Horizontal: { + auto c = std::make_unique(); + c->Type = Sketcher::Horizontal; + c->First = (geoId2 != Sketcher::GeoEnum::GeoUndef ? geoId2 : geoId1); + AutoConstraints.push_back(std::move(c)); + } break; + case Sketcher::Vertical: { + auto c = std::make_unique(); + c->Type = Sketcher::Vertical; + c->First = (geoId2 != Sketcher::GeoEnum::GeoUndef ? geoId2 : geoId1); + AutoConstraints.push_back(std::move(c)); + } break; + case Sketcher::Tangent: { + Sketcher::SketchObject* Obj = + static_cast(sketchgui->getObject()); + + const Part::Geometry* geom1 = Obj->getGeometry(geoId1); + const Part::Geometry* geom2 = Obj->getGeometry(ac.GeoId); + + // ellipse tangency support using construction elements (lines) + if (geom1 && geom2 + && (geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() + || geom2->getTypeId() == Part::GeomEllipse::getClassTypeId())) { + + if (geom1->getTypeId() != Part::GeomEllipse::getClassTypeId()) { + std::swap(geoId1, geoId2); + } + + // geoId1 is the ellipse + geom1 = Obj->getGeometry(geoId1); + geom2 = Obj->getGeometry(geoId2); + + if (geom2->getTypeId() == Part::GeomEllipse::getClassTypeId() + || geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() + || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() + || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + // in all these cases an intermediate element is needed + /*makeTangentToEllipseviaNewPoint(Obj, + static_cast(geom1), geom2, geoId1, geoId2);*/ + // NOTE: Temporarily deactivated + return; + } + } + + // arc of ellipse tangency support using external elements + if (geom1 && geom2 + && (geom1->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() + || geom2->getTypeId() + == Part::GeomArcOfEllipse::getClassTypeId())) { + + if (geom1->getTypeId() != Part::GeomArcOfEllipse::getClassTypeId()) { + std::swap(geoId1, geoId2); + } + + // geoId1 is the arc of ellipse + geom1 = Obj->getGeometry(geoId1); + geom2 = Obj->getGeometry(geoId2); + + if (geom2->getTypeId() == Part::GeomArcOfEllipse::getClassTypeId() + || geom2->getTypeId() == Part::GeomCircle::getClassTypeId() + || geom2->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + // in all these cases an intermediate element is needed + // makeTangentToArcOfEllipseviaNewPoint(Obj, + // static_cast(geom1), geom2, geoId1, + // geoId2); + // NOTE: Temporarily deactivated + return; + } + } + + auto resultcoincident = + std::find_if(AutoConstraints.begin(), + AutoConstraints.end(), + [&](const auto& ace) { + return ace->Type == Sketcher::Coincident + && ace->First == geoId1 && ace->Second == ac.GeoId; + }); + + auto resultpointonobject = std::find_if( + AutoConstraints.begin(), + AutoConstraints.end(), + [&](const auto& ace) { + return ace->Type == Sketcher::PointOnObject + && ((ace->First == geoId1 && ace->Second == ac.GeoId) + || (ace->First == ac.GeoId && ace->Second == geoId1)); + }); + + if (resultcoincident + != AutoConstraints.end()) { // endpoint-to-endpoint tangency + (*resultcoincident)->Type = Sketcher::Tangent; + } + else if (resultpointonobject + != AutoConstraints.end()) { // endpoint-to-edge tangency + (*resultpointonobject)->Type = Sketcher::Tangent; + } + else { // regular edge to edge tangency + auto c = std::make_unique(); + c->Type = Sketcher::Tangent; + c->First = geoId1; + c->Second = ac.GeoId; + AutoConstraints.push_back(std::move(c)); + } + } break; + default: + break; + } + } + } + } + + /** @brief Convenience function to automatically add to the SketchObjects (via Python command) + * all the constraints stored in the AutoConstraints vector. */ + void createGeneratedAutoConstraints(bool owncommand) + { + // add auto-constraints + if (owncommand) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add auto constraints")); + } + + auto autoConstraints = toPointerVector(AutoConstraints); + + Gui::Command::doCommand( + Gui::Command::Doc, + Sketcher::PythonConverter::convert(Gui::Command::getObjectCmd(sketchgui->getObject()), + autoConstraints) + .c_str()); + + if (owncommand) { + Gui::Command::commitCommand(); + } + } + + /** @brief Convenience function to remove redundant autoconstraints from the AutoConstraints + * vector (before having added them to the SketchObject). + * + * @details The rationale is that constraints associated with the newly generated geometry SHALL + * not be redundant by design. Then, the only source of redundancy lies with the + * autoconstraints. This observation is also what motivates the separate treatment of both sets + * of constraints. + * + * This approach avoids the previous issues with old style DSHs, that the autoremoval mechanism + * sometimes removed a shape constraint, or a previously already existing constraint, instead of + * an autoconstraint. + */ + void removeRedundantAutoConstraints() + { + + if (AutoConstraints.empty()) { + return; + } + + auto sketchobject = getSketchObject(); + + auto autoConstraints = toPointerVector(AutoConstraints); + + // Allows a diagnose with the new autoconstraints as if they were part of the sketchobject, + // but WITHOUT adding them to the sketchobject.. + sketchobject->diagnoseAdditionalConstraints(autoConstraints); + + if (sketchobject->getLastHasRedundancies()) { + Base::Console().Warning( + QT_TRANSLATE_NOOP("Notifications", + "Autoconstraints cause redundancy. Removing them") "\n"); + + auto lastsketchconstraintindex = sketchobject->Constraints.getSize() - 1; + + auto redundants = sketchobject->getLastRedundant(); // redundants is always sorted + + for (int index = redundants.size() - 1; index >= 0; index--) { + int redundantconstraintindex = redundants[index] - 1; + if (redundantconstraintindex > lastsketchconstraintindex) { + int removeindex = redundantconstraintindex - lastsketchconstraintindex - 1; + AutoConstraints.erase(std::next(AutoConstraints.begin(), removeindex)); + } + else { + // This exception stops the procedure here, which means that: + // 1) Geometry (and constraints of the geometry in case of a multicurve shape) + // are created 2) No autoconstrains are actually added 3) No widget mandated + // constraints are added + THROWM(Base::RuntimeError, + QT_TRANSLATE_NOOP( + "Notifications", + "Redundant constraint is not an autoconstraint. No autoconstraints " + "or additional constraints were added. Please report!") "\n"); + } + } + + // NOTE: If we removed all redundants in the list, then at this moment there are no + // redundants anymore + } + + // This is an awful situation. It should not be possible if the DSH works properly. It is + // just a saveguard. + if (sketchobject->getLastHasConflicts()) { + THROWM(Base::RuntimeError, + QT_TRANSLATE_NOOP( + "Notifications", + "Autoconstraints cause conflicting constraints - Please report!") "\n"); + } + } + + /** @brief Function that performs a sketcher solver diagnose (determination of DoF and dependent + * parameters), taking into account the suggested AutoConstraints. + * + * @details This function allows to refresh solver information by taking into account any added + * constraint, such as the ones introduced by a widget or on-screen parameters during the + * execution of the DSH. + * + * Ultimately, it is intended to operate in combination with functions obtaining point/element + * specific solver information, in order to decide whether to add constraints or not. + */ + void diagnoseWithAutoConstraints() + { + auto sketchobject = getSketchObject(); + + auto autoConstraints = toPointerVector(AutoConstraints); + + sketchobject->diagnoseAdditionalConstraints(autoConstraints); + + if (sketchobject->getLastHasRedundancies() || sketchobject->getLastHasConflicts()) { + THROWM(Base::RuntimeError, + QT_TRANSLATE_NOOP("Notifications", + "Unexpected Redundancy/Conflicting constraint. Check the " + "constraints and autoconstraints of this operation.") "\n"); + } + } + + /** @brief Function to obtain detailed solver information on one point type geometric element.*/ + Sketcher::SolverGeometryExtension::PointParameterStatus + getPointInfo(const Sketcher::GeoElementId& element) + { + if (element.isCurve()) { + THROWM(Base::TypeError, "getPointInfo: Provided geometry element is not a point!") + } + + auto sketchobject = getSketchObject(); + // it is important not to rely on Geometry attached extension from the solver, as geometry + // is not updated during temporary diagnose of additional constraints + const auto& solvedsketch = sketchobject->getSolvedSketch(); + + auto solvext = solvedsketch.getSolverExtension(element.GeoId); + + if (solvext) { + auto pointinfo = solvext->getPoint(element.Pos); + + return pointinfo; + } + + THROWM(Base::ValueError, + "Geometry element does not have solver information (possibly when trying to apply " + "widget constraints)!") + } + + /** @brief Function to obtain detailed DoFs of one line type geometric element.*/ + int getLineDoFs(int geoid) + { + auto startpointinfo = + getPointInfo(Sketcher::GeoElementId(geoid, Sketcher::PointPos::start)); + + auto endpointinfo = getPointInfo(Sketcher::GeoElementId(geoid, Sketcher::PointPos::end)); + + int DoFs = startpointinfo.getDoFs(); + DoFs += endpointinfo.getDoFs(); + + return DoFs; + } + + /** @brief Function to obtain detailed solver information on one edge.*/ + Sketcher::SolverGeometryExtension::EdgeParameterStatus getEdgeInfo(int geoid) + { + + auto sketchobject = getSketchObject(); + // it is important not to rely on Geometry attached extension from the solver, as geometry + // is not updated during temporary diagnose of additional constraints + const auto& solvedsketch = sketchobject->getSolvedSketch(); + + auto solvext = solvedsketch.getSolverExtension(geoid); + + if (solvext) { + Sketcher::SolverGeometryExtension::EdgeParameterStatus edgeinfo = + solvext->getEdgeParameters(); + + return edgeinfo; + } + + THROWM(Base::ValueError, + "Geometry does not have solver extension when trying to apply widget constraints!") + } + + /** @brief Function to add shape inherent constraints (the ones that define the shape) to the + * ShapeConstraints vector. + * + * @details These functions have the highest priority when adding the geometry as they are + * inherent part of it and the shape would not go without them. Lower priority constraints are + * AutoConstraints and constraints mandated by the widget/on-screen parameters. + * .*/ + void addToShapeConstraints(Sketcher::ConstraintType type, + int first, + Sketcher::PointPos firstPos = Sketcher::PointPos::none, + int second = -2000, + Sketcher::PointPos secondPos = Sketcher::PointPos::none, + int third = -2000, + Sketcher::PointPos thirdPos = Sketcher::PointPos::none) + { + auto constr = std::make_unique(); + constr->Type = type; + constr->First = first; + constr->FirstPos = firstPos; + constr->Second = second; + constr->SecondPos = secondPos; + constr->Third = third; + constr->ThirdPos = thirdPos; + ShapeConstraints.push_back(std::move(constr)); + } + + /** @brief Function to add a line to the ShapeGeometry vector.*/ + void addLineToShapeGeometry(Base::Vector3d p1, Base::Vector3d p2, bool constructionMode) + { + auto line = std::make_unique(); + line->setPoints(p1, p2); + Sketcher::GeometryFacade::setConstruction(line.get(), constructionMode); + ShapeGeometry.push_back(std::move(line)); + } + + /** @brief Function to add an arc to the ShapeGeometry vector.*/ + void addArcToShapeGeometry(Base::Vector3d p1, + double start, + double end, + double radius, + bool constructionMode) + { + auto arc = std::make_unique(); + arc->setCenter(p1); + arc->setRange(start, end, true); + arc->setRadius(radius); + Sketcher::GeometryFacade::setConstruction(arc.get(), constructionMode); + ShapeGeometry.push_back(std::move(arc)); + } + + /** @brief Function to add a point to the ShapeGeometry vector.*/ + void addPointToShapeGeometry(Base::Vector3d p1, bool constructionMode) + { + auto point = std::make_unique(); + point->setPoint(p1); + Sketcher::GeometryFacade::setConstruction(point.get(), constructionMode); + ShapeGeometry.push_back(std::move(point)); + } + + /** @brief Function to add an ellipse to the ShapeGeometry vector.*/ + void addEllipseToShapeGeometry(Base::Vector3d centerPoint, + Base::Vector3d majorAxisDirection, + double majorRadius, + double minorRadius, + bool constructionMode) + { + auto ellipse = std::make_unique(); + ellipse->setMajorRadius(majorRadius); + ellipse->setMinorRadius(minorRadius); + ellipse->setMajorAxisDir(majorAxisDirection); + ellipse->setCenter(centerPoint); + Sketcher::GeometryFacade::setConstruction(ellipse.get(), constructionMode); + ShapeGeometry.push_back(std::move(ellipse)); + } + + /** @brief Function to add a circle to the ShapeGeometry vector.*/ + void addCircleToShapeGeometry(Base::Vector3d centerPoint, double radius, bool constructionMode) + { + auto circle = std::make_unique(); + circle->setRadius(radius); + circle->setCenter(centerPoint); + Sketcher::GeometryFacade::setConstruction(circle.get(), constructionMode); + ShapeGeometry.push_back(std::move(circle)); + } + + /** @brief Function to add all the geometries and constraints in ShapeGeometry and + * ShapeConstraints vectors to the SketchObject.*/ + void commandAddShapeGeometryAndConstraints() + { + auto shapeGeometry = toPointerVector(ShapeGeometry); + Gui::Command::doCommand( + Gui::Command::Doc, + Sketcher::PythonConverter::convert(Gui::Command::getObjectCmd(sketchgui->getObject()), + shapeGeometry) + .c_str()); + + auto shapeConstraints = toPointerVector(ShapeConstraints); + Gui::Command::doCommand( + Gui::Command::Doc, + Sketcher::PythonConverter::convert(Gui::Command::getObjectCmd(sketchgui->getObject()), + shapeConstraints) + .c_str()); + } + + /** @brief Function to draw as an edit curve all the geometry in the ShapeGeometry vector.*/ + void DrawShapeGeometry() + { + drawEdit(toPointerVector(ShapeGeometry)); + } + + /** @brief Function to create a shape into ShapeGeometry vector and draw it.*/ + void CreateAndDrawShapeGeometry() + { + createShape(true); + drawEdit(toPointerVector(ShapeGeometry)); + } + + //@} + +protected: + std::vector> sugConstraints; + + std::vector> ShapeGeometry; + std::vector> ShapeConstraints; + std::vector> AutoConstraints; + + bool avoidRedundants; + bool continuousMode; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchDefaultHandler_H diff --git a/src/Mod/Sketcher/Gui/DrawSketchDefaultWidgetHandler.h b/src/Mod/Sketcher/Gui/DrawSketchDefaultWidgetHandler.h new file mode 100644 index 0000000000..55fa936f42 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchDefaultWidgetHandler.h @@ -0,0 +1,1154 @@ +/*************************************************************************** + * Copyright (c) 2022 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 * + * * + ***************************************************************************/ + + +#ifndef SKETCHERGUI_DrawSketchHandlerDefaultWidget_H +#define SKETCHERGUI_DrawSketchHandlerDefaultWidget_H + +#include "DrawSketchDefaultHandler.h" + +#include "SketcherToolDefaultWidget.h" + +#include + +#include + +namespace bp = boost::placeholders; + +namespace SketcherGui +{ + +/** @brief Template class intended for handlers that interact with SketcherToolDefaultWidget. + * + * @details + * The template encapsulates a DSH and a nested class ToolWidgetManager which is responsible for + * handling the interaction between the DSH and SketcherToolDefaultWidget. + * + * This class can be used: + * 1. By instantiating it and specialising it (both NVI and ToolWidgetManager). + * 2. By deriving from it to implement the DSH via the NVI interface and by specialising the nested + * ToolWidgetManager class functions. + * + * Method 2 enables to add new data members. + * + * Template Types/Parameters: + * PTool : Parameter to specialise behaviour to a specific tool + * SelectModeT : The type of statemachine to be used (see namespace StateMachines above). + * PInitEditCurveSize : Initial size of the EditCurve vector + * PInitAutoConstraintSize : Initial size of the AutoConstraint vector + * WidgetParametersT: The number of parameter spinboxes in the default widget + * WidgetCheckboxesT: The number of checkboxes in the default widget + * + * Widget: + * - Automatically initialises the parameters necessary + * - Automatically connects boost signals of the widget to slots. + * - Interface to modify point coordinates based on widget state. + * - Interface to update Widget with value. + * + * Widget MUST specialise: + * - configureToolWidget() for example to initialise widget labels. + * - doUpdateDrawSketchHandlerValues() to update EditData when Widget values are changed. + * - addConstraints() to add any constraint mandated by the Widget. + * + * Widget MAY need to specialise, if default behaviour is not appropriate: + * - doChangeDrawSketchHandlerMode() + * - onHandlerModeChanged() + * - adaptWidgetParameters() + * - doEnforceWidgetParameters() + * + * Handler provides: + * - generic initialisation + * - structured command finalisation + * - handling of continuous creation mode + * - interaction with widget + * + * Handler MUST specialise: + * - getToolName => provide the string name of the tool + * - getCrosshairCursorSVGName => provide the string for the svg icon of the cursor + * - updateEditDataAndDrawToPositionImpl => function to update the EditData structure and draw a + * temporal curve + * - executeCommands => execution of commands to create the geometry + * - createAutoConstraints => execution of commands to create autoconstraints + * (widget mandated constraints are called BEFORE this) + * + * Question: Do I need to use this handler or derive from this handler to make a new handler using + * the default tool widget? + * + * No, you do not NEED to. But you are encouraged to. Structuring a handler following this NVI, + * apart from substantial savings in amount of code typed, enables a much easier and less verbose + * implementation of a handler using a default widget (toolwidget), and will lead to easier to + * maintain code. + */ + +template // Initial sizes for each mode +class WidgetInitialValues +{ +public: + template + static constexpr int size(constructionT constructionmethod) + { + auto modeint = static_cast(constructionmethod); + + return constructionMethodParameters[modeint]; + } + + static constexpr int defaultMethodSize() + { + return size(0); + } + +private: + static constexpr std::array constructionMethodParameters = {{sizes...}}; +}; + +template // Initial sizes for each mode +class WidgetParameters: public WidgetInitialValues +{ +}; + +template // Initial sizes for each mode +class WidgetCheckboxes: public WidgetInitialValues +{ +}; + +template // Initial sizes for each mode +class WidgetComboboxes: public WidgetInitialValues +{ +}; + +template +class DrawSketchDefaultWidgetHandler: public DrawSketchDefaultHandler +{ + using DSDefaultHandler = + DrawSketchDefaultHandler; + using ConstructionMachine = ConstructionMethodMachine; + +private: + class ToolWidgetManager + { + int nParameter = WidgetParametersT::defaultMethodSize(); + int nCheckbox = WidgetCheckboxesT::defaultMethodSize(); + int nCombobox = WidgetComboboxesT::defaultMethodSize(); + + // std::array + // constructionMethodParameters; + + SketcherToolDefaultWidget* toolWidget; + DrawSketchDefaultWidgetHandler* handler; // used to access private implementations + HandlerT* dHandler; // real derived type + bool init = false; // returns true if the widget has been configured + + using Connection = boost::signals2::connection; + + Connection connectionParameterValueChanged; + Connection connectionCheckboxCheckedChanged; + Connection connectionComboboxSelectionChanged; + + Base::Vector2d prevCursorPosition; + Base::Vector2d lastWidgetEnforcedPosition; + + using WParameter = SketcherToolDefaultWidget::Parameter; + using WCheckbox = SketcherToolDefaultWidget::Checkbox; + using WCombobox = SketcherToolDefaultWidget::Combobox; + using SelectMode = SelectModeT; + + public: + ToolWidgetManager(DrawSketchDefaultWidgetHandler* dshandler) + : handler(dshandler) + , dHandler(static_cast(dshandler)) + {} + + ~ToolWidgetManager() + { + connectionParameterValueChanged.disconnect(); + connectionCheckboxCheckedChanged.disconnect(); + connectionComboboxSelectionChanged.disconnect(); + } + + /** @name functions NOT intended for specialisation */ + //@{ + void initWidget(QWidget* widget) + { + toolWidget = static_cast(widget); + + connectionParameterValueChanged = toolWidget->registerParameterValueChanged( + boost::bind(&ToolWidgetManager::parameterValueChanged, this, bp::_1, bp::_2)); + + connectionCheckboxCheckedChanged = toolWidget->registerCheckboxCheckedChanged( + boost::bind(&ToolWidgetManager::checkboxCheckedChanged, this, bp::_1, bp::_2)); + + connectionComboboxSelectionChanged = toolWidget->registerComboboxSelectionChanged( + boost::bind(&ToolWidgetManager::comboboxSelectionChanged, this, bp::_1, bp::_2)); + + reset(); + init = true; + } + + void reset() + { + + boost::signals2::shared_connection_block parameter_block( + connectionParameterValueChanged); + boost::signals2::shared_connection_block checkbox_block( + connectionCheckboxCheckedChanged); + boost::signals2::shared_connection_block combobox_block( + connectionComboboxSelectionChanged); + + toolWidget->initNParameters(nParameter); + toolWidget->initNCheckboxes(nCheckbox); + toolWidget->initNComboboxes(nCombobox); + + configureToolWidget(); + } + + void enforceWidgetParameters(Base::Vector2d& onSketchPos) + { + prevCursorPosition = onSketchPos; + + doEnforceWidgetParameters(onSketchPos); + + lastWidgetEnforcedPosition = onSketchPos; // store enforced cursor position. + } + + /** boost slot triggering when a parameter has changed in the widget + * It is intended to remote control the DrawSketchDefaultWidgetHandler + */ + void parameterValueChanged(int parameterindex, double value) + { + // -> A machine does not forward to a next state when adapting the parameter (though it + // may forward to + // a next state if all the parameters are fulfilled, see + // doChangeDrawSketchHandlerMode). This ensures that the geometry has been defined + // (either by mouse clicking or by widget). Autoconstraints on point should be picked + // when the state is reached upon machine state advancement. + // + // -> A machine goes back to a previous state if a parameter of a previous state is + // modified. This ensures + // that appropriate autoconstraints are picked. + if (isParameterOfPreviousMode(parameterindex)) { + // change to previous state + handler->setState(getState(parameterindex)); + } + + enforceWidgetParametersOnPreviousCursorPosition(); + + adaptDrawingToParameterChange(parameterindex, value); + + finishWidgetChanged(); + } + + /** boost slot triggering when a checkbox has changed in the widget + * It is intended to remote control the DrawSketchDefaultWidgetHandler + */ + void checkboxCheckedChanged(int checkboxindex, bool value) + { + enforceWidgetParametersOnPreviousCursorPosition(); + + adaptDrawingToCheckboxChange(checkboxindex, value); + + onHandlerModeChanged(); // re-focus/select spinbox + + finishWidgetChanged(); + } + + /** boost slot triggering when a combobox has changed in the widget + * It is intended to remote control the DrawSketchDefaultWidgetHandler + */ + void comboboxSelectionChanged(int comboboxindex, int value) + { + enforceWidgetParametersOnPreviousCursorPosition(); + + adaptDrawingToComboboxChange(comboboxindex, value); + + finishWidgetChanged(); + } + + void adaptWidgetParameters() + { + adaptWidgetParameters(lastWidgetEnforcedPosition); + } + + //@} + + /** @name functions which MUST be specialised */ + //@{ + /// Change DSH to reflect a value entered in the widget + void adaptDrawingToParameterChange(int parameterindex, double value) + { + Q_UNUSED(parameterindex); + Q_UNUSED(value); + } + + /// Change DSH to reflect a checkbox changed in the widget + void adaptDrawingToCheckboxChange(int checkboxindex, bool value) + { + Q_UNUSED(checkboxindex); + Q_UNUSED(value); + } + + /// Change DSH to reflect a comboBox changed in the widget + void adaptDrawingToComboboxChange(int comboboxindex, int value) + { + Q_UNUSED(comboboxindex); + Q_UNUSED(value); + + if constexpr (PFirstComboboxIsConstructionMethod == true) { + + if (comboboxindex == WCombobox::FirstCombo + && handler->ConstructionMethodsCount() > 1) { + handler->iterateToNextConstructionMethod(); + } + } + } + + /** Returns the state to which the widget parameter corresponds in the current construction + * method + */ + auto getState(int parameterindex) const + { + Q_UNUSED(parameterindex); + return handler->getFirstState(); + } + + /// function to create constraints based on widget information. + void addConstraints() + {} + //@} + + /** @name functions which MAY need to be specialised */ + //@{ + /// Function to specialise to set the correct widget strings and commands + void configureToolWidget() + { + if constexpr (std::is_same_v) { + toolWidget->setParameterLabel( + WParameter::First, + QApplication::translate("ToolWidgetManager_p1", "x of point")); + toolWidget->setParameterLabel( + WParameter::Second, + QApplication::translate("ToolWidgetManager_p2", "y of point")); + } + else if constexpr (std::is_same_v) { + toolWidget->setParameterLabel( + WParameter::First, + QApplication::translate("ToolWidgetManager_p1", "x of 1st point")); + toolWidget->setParameterLabel( + WParameter::Second, + QApplication::translate("ToolWidgetManager_p2", "y of 1st point")); + toolWidget->setParameterLabel( + WParameter::Third, + QApplication::translate("ToolWidgetManager_p3", "x of 2nd point")); + toolWidget->setParameterLabel( + WParameter::Fourth, + QApplication::translate("ToolWidgetManager_p4", "y of 2nd point")); + } + else if constexpr (std::is_same_v) { + toolWidget->setParameterLabel( + WParameter::First, + QApplication::translate("ToolWidgetManager_p1", "x of 1st point")); + toolWidget->setParameterLabel( + WParameter::Second, + QApplication::translate("ToolWidgetManager_p2", "y of 1st point")); + toolWidget->setParameterLabel( + WParameter::Third, + QApplication::translate("ToolWidgetManager_p3", "x of 2nd point")); + toolWidget->setParameterLabel( + WParameter::Fourth, + QApplication::translate("ToolWidgetManager_p4", "y of 2nd point")); + toolWidget->setParameterLabel( + WParameter::Fifth, + QApplication::translate("ToolWidgetManager_p5", "x of 3rd point")); + toolWidget->setParameterLabel( + WParameter::Sixth, + QApplication::translate("ToolWidgetManager_p6", "y of 3rd point")); + } + else if constexpr (std::is_same_v) { + toolWidget->setParameterLabel( + WParameter::First, + QApplication::translate("ToolWidgetManager_p1", "x of 1st point")); + toolWidget->setParameterLabel( + WParameter::Second, + QApplication::translate("ToolWidgetManager_p2", "y of 1st point")); + toolWidget->setParameterLabel( + WParameter::Third, + QApplication::translate("ToolWidgetManager_p3", "x of 2nd point")); + toolWidget->setParameterLabel( + WParameter::Fourth, + QApplication::translate("ToolWidgetManager_p4", "y of 2nd point")); + toolWidget->setParameterLabel( + WParameter::Fifth, + QApplication::translate("ToolWidgetManager_p5", "x of 3rd point")); + toolWidget->setParameterLabel( + WParameter::Sixth, + QApplication::translate("ToolWidgetManager_p6", "y of 3rd point")); + } + } + + /** Change DSH to reflect the SelectMode it should be in based on values entered in the + * widget + * + * This is just a default implementation for common stateMachines, that may + * or may not do what you expect. It assumes two parameters per seek state. + * + * It MUST be specialised otherwise + */ + void doChangeDrawSketchHandlerMode() + { + if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First) + && toolWidget->isParameterSet(WParameter::Second)) { + + handler->setState(SelectMode::End); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First) + && toolWidget->isParameterSet(WParameter::Second)) { + + handler->setState(SelectMode::SeekSecond); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third) + || toolWidget->isParameterSet(WParameter::Fourth)) { + + if (toolWidget->isParameterSet(WParameter::Third) + && toolWidget->isParameterSet(WParameter::Fourth)) { + + handler->setState(SelectMode::End); + } + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First) + && toolWidget->isParameterSet(WParameter::Second)) { + + handler->setState(SelectMode::SeekSecond); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third) + || toolWidget->isParameterSet(WParameter::Fourth)) { + + if (toolWidget->isParameterSet(WParameter::Third) + && toolWidget->isParameterSet(WParameter::Fourth)) { + + handler->setState(SelectMode::SeekThird); + } + } + } break; + case SelectMode::SeekThird: { + if (toolWidget->isParameterSet(WParameter::Fifth) + || toolWidget->isParameterSet(WParameter::Sixth)) { + + if (toolWidget->isParameterSet(WParameter::Fifth) + && toolWidget->isParameterSet(WParameter::Sixth)) { + + handler->setState(SelectMode::End); + } + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First) + && toolWidget->isParameterSet(WParameter::Second)) { + + handler->setState(SelectMode::SeekSecond); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third) + || toolWidget->isParameterSet(WParameter::Fourth)) { + + if (toolWidget->isParameterSet(WParameter::Third) + && toolWidget->isParameterSet(WParameter::Fourth)) { + + handler->setState(SelectMode::SeekThird); + } + } + } break; + case SelectMode::SeekThird: { + if (toolWidget->isParameterSet(WParameter::Fifth)) { + + handler->setState(SelectMode::SeekFourth); + } + } break; + case SelectMode::SeekFourth: { + if (toolWidget->isParameterSet(WParameter::Sixth)) { + + handler->setState(SelectMode::End); + } + } break; + default: + break; + } + } + } + + /** function that is called by the handler when the selection mode changed + * + * This is just a default implementation for common stateMachines, that may + * or may not do what you expect. It assumes two parameters per seek state. + * + * It MUST be specialised otherwise + */ + void onHandlerModeChanged() + { + if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: + toolWidget->setParameterFocus(WParameter::First); + break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: + toolWidget->setParameterFocus(WParameter::First); + break; + case SelectMode::SeekSecond: + toolWidget->setParameterFocus(WParameter::Third); + break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: + toolWidget->setParameterFocus(WParameter::First); + break; + case SelectMode::SeekSecond: + toolWidget->setParameterFocus(WParameter::Third); + break; + case SelectMode::SeekThird: + toolWidget->setParameterFocus(WParameter::Fifth); + break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: + toolWidget->setParameterFocus(WParameter::First); + break; + case SelectMode::SeekSecond: + toolWidget->setParameterFocus(WParameter::Third); + break; + case SelectMode::SeekThird: + toolWidget->setParameterFocus(WParameter::Fifth); + break; + case SelectMode::SeekFourth: + toolWidget->setParameterFocus(WParameter::Sixth); + break; + default: + break; + } + } + } + + /** function that is called by the handler when the construction mode changed + * + * This is just a default implementation for common stateMachines, that may + * or may not do what you expect. It assumes two parameters per seek state. + * + * It MUST be specialised otherwise + */ + void onConstructionMethodChanged() + { + + nParameter = WidgetParametersT::size(handler->constructionMethod()); + nCheckbox = WidgetCheckboxesT::size(handler->constructionMethod()); + nCombobox = WidgetComboboxesT::size(handler->constructionMethod()); + + // update the combobox only if necessary (if the change was not triggered by the + // combobox) + if constexpr (PFirstComboboxIsConstructionMethod == true) { + auto currentindex = toolWidget->getComboboxIndex(WCombobox::FirstCombo); + auto methodint = static_cast(handler->constructionMethod()); + + if (currentindex != methodint) { + // avoid triggering of method change + boost::signals2::shared_connection_block combobox_block( + connectionComboboxSelectionChanged); + toolWidget->setComboboxIndex(WCombobox::FirstCombo, methodint); + } + } + + dHandler->updateCursor(); + + dHandler->reset(); // reset of handler to restart. + } + + /** function that is called by the handler with a Vector2d position to update the widget + * + * This is just a default implementation for common stateMachines, that may + * or may not do what you expect. It assumes two parameters per seek state. + * + * It MUST be specialised if the states correspond to different parameters + */ + void adaptWidgetParameters(Base::Vector2d onSketchPos) + { + if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (!toolWidget->isParameterSet(WParameter::First)) { + toolWidget->updateVisualValue(WParameter::First, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Second)) { + toolWidget->updateVisualValue(WParameter::Second, onSketchPos.y); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (!toolWidget->isParameterSet(WParameter::First)) { + toolWidget->updateVisualValue(WParameter::First, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Second)) { + toolWidget->updateVisualValue(WParameter::Second, onSketchPos.y); + } + } break; + case SelectMode::SeekSecond: { + if (!toolWidget->isParameterSet(WParameter::Third)) { + toolWidget->updateVisualValue(WParameter::Third, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Fourth)) { + toolWidget->updateVisualValue(WParameter::Fourth, onSketchPos.y); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (!toolWidget->isParameterSet(WParameter::First)) { + toolWidget->updateVisualValue(WParameter::First, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Second)) { + toolWidget->updateVisualValue(WParameter::Second, onSketchPos.y); + } + } break; + case SelectMode::SeekSecond: { + if (!toolWidget->isParameterSet(WParameter::Third)) { + toolWidget->updateVisualValue(WParameter::Third, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Fourth)) { + toolWidget->updateVisualValue(WParameter::Fourth, onSketchPos.y); + } + } break; + case SelectMode::SeekThird: { + if (!toolWidget->isParameterSet(WParameter::Fifth)) { + toolWidget->updateVisualValue(WParameter::Fifth, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Sixth)) { + toolWidget->updateVisualValue(WParameter::Sixth, onSketchPos.y); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (!toolWidget->isParameterSet(WParameter::First)) { + toolWidget->updateVisualValue(WParameter::First, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Second)) { + toolWidget->updateVisualValue(WParameter::Second, onSketchPos.y); + } + } break; + case SelectMode::SeekSecond: { + if (!toolWidget->isParameterSet(WParameter::Third)) { + toolWidget->updateVisualValue(WParameter::Third, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Fourth)) { + toolWidget->updateVisualValue(WParameter::Fourth, onSketchPos.y); + } + } break; + case SelectMode::SeekThird: { + if (!toolWidget->isParameterSet(WParameter::Fifth)) { + toolWidget->updateVisualValue(WParameter::Fifth, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Sixth)) { + toolWidget->updateVisualValue(WParameter::Sixth, onSketchPos.y); + } + } break; + case SelectMode::SeekFourth: { + if (!toolWidget->isParameterSet(WParameter::Fifth)) { + toolWidget->updateVisualValue(WParameter::Fifth, onSketchPos.x); + } + + if (!toolWidget->isParameterSet(WParameter::Sixth)) { + toolWidget->updateVisualValue(WParameter::Sixth, onSketchPos.y); + } + } break; + default: + break; + } + } + } + + /** function that is called by the handler with a mouse position, enabling the + * widget to override it having regard to the widget information. + * + * This is just a default implementation for common stateMachines, that may + * or may not do what you expect. It assumes two parameters per seek state. + * + * It MUST be specialised if the states correspond to different parameters + */ + void doEnforceWidgetParameters(Base::Vector2d& onSketchPos) + { + if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First)) { + onSketchPos.x = toolWidget->getParameter(WParameter::First); + } + + if (toolWidget->isParameterSet(WParameter::Second)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Second); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First)) { + onSketchPos.x = toolWidget->getParameter(WParameter::First); + } + + if (toolWidget->isParameterSet(WParameter::Second)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Second); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third)) { + onSketchPos.x = toolWidget->getParameter(WParameter::Third); + } + + if (toolWidget->isParameterSet(WParameter::Fourth)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Fourth); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First)) { + onSketchPos.x = toolWidget->getParameter(WParameter::First); + } + + if (toolWidget->isParameterSet(WParameter::Second)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Second); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third)) { + onSketchPos.x = toolWidget->getParameter(WParameter::Third); + } + + if (toolWidget->isParameterSet(WParameter::Fourth)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Fourth); + } + } break; + case SelectMode::SeekThird: { + if (toolWidget->isParameterSet(WParameter::Fifth)) { + onSketchPos.x = toolWidget->getParameter(WParameter::Fifth); + } + + if (toolWidget->isParameterSet(WParameter::Sixth)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Sixth); + } + } break; + default: + break; + } + } + else if constexpr (std::is_same_v) { + switch (handler->state()) { + case SelectMode::SeekFirst: { + if (toolWidget->isParameterSet(WParameter::First)) { + onSketchPos.x = toolWidget->getParameter(WParameter::First); + } + + if (toolWidget->isParameterSet(WParameter::Second)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Second); + } + } break; + case SelectMode::SeekSecond: { + if (toolWidget->isParameterSet(WParameter::Third)) { + onSketchPos.x = toolWidget->getParameter(WParameter::Third); + } + + if (toolWidget->isParameterSet(WParameter::Fourth)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Fourth); + } + } break; + case SelectMode::SeekThird: { + if (toolWidget->isParameterSet(WParameter::Fifth)) { + onSketchPos.x = toolWidget->getParameter(WParameter::Fifth); + } + + if (toolWidget->isParameterSet(WParameter::Sixth)) { + onSketchPos.y = toolWidget->getParameter(WParameter::Sixth); + } + } break; + case SelectMode::SeekFourth: { + // nothing. It has to be reimplemented. + } break; + default: + break; + } + } + } + + /** on first shortcut, it toggles the first checkbox if there is go. Must be specialised if + * this is not intended */ + void firstKeyShortcut() + { + if (nCheckbox >= 1) { + auto firstchecked = toolWidget->getCheckboxChecked(WCheckbox::FirstBox); + toolWidget->setCheckboxChecked(WCheckbox::FirstBox, !firstchecked); + } + } + + void secondKeyShortcut() + { + if (nCheckbox >= 2) { + auto secondchecked = toolWidget->getCheckboxChecked(WCheckbox::SecondBox); + toolWidget->setCheckboxChecked(WCheckbox::SecondBox, !secondchecked); + } + } + //@} + + private: + /** @name helper functions */ + //@{ + /// function to assist in adaptDrawingToComboboxChange specialisation + /// assigns the modevalue to the modeenum and updates the number of parameters according to + /// map it also triggers an update of the cursor + /*template + void setModeAndAdaptParameters(T & modeenum, int modevalue, const std::vector & + parametersmap) { if (modevalue < static_cast(parametersmap.size())) { auto mode = + static_cast(modevalue); + + nParameter = WidgetParametersT::constructionMethodParameters[modevalue]; + nCheckbox = WidgetCheckboxesT::constructionMethodParameters[modevalue]; + nCombobox = WidgetComboboxesT::constructionMethodParameters[modevalue]; + + modeenum = mode; + + dHandler->updateCursor(); + + reset(); //reset the widget to take into account the change of nparameter + dHandler->reset(); //reset of handler to restart. + } + }*/ + + /// function to assist in adaptDrawingToComboboxChange specialisation + /// assigns the modevalue to the modeenum + /// it also triggers an update of the cursor + template + void setMode(T& modeenum, int modevalue) + { + auto mode = static_cast(modevalue); + + modeenum = mode; + + dHandler->updateCursor(); + + dHandler->reset(); // reset of handler to restart. + } + + /// function to redraw before and after any eventual mode change in reaction to a widget + /// change + void finishWidgetChanged() + { + + // handler->moveCursorToSketchPoint(lastWidgetEnforcedPosition); + + auto currentstate = handler->state(); + // ensure that object at point is preselected, so that autoconstraints are generated + handler->preselectAtPoint(lastWidgetEnforcedPosition); + // ensure drawing in the previous mode + handler->updateDataAndDrawToPosition(lastWidgetEnforcedPosition); + + doChangeDrawSketchHandlerMode(); + + // if the state changed and is not the last state (End) + if (!handler->isLastState() && handler->state() != currentstate) { + // mode has changed, so reprocess the previous position to the new widget state + enforceWidgetParametersOnPreviousCursorPosition(); + + // update the widget if state changed + adaptWidgetParameters(lastWidgetEnforcedPosition); + + // ensure drawing in the next mode + handler->updateDataAndDrawToPosition(lastWidgetEnforcedPosition); + } + } + + void enforceWidgetParametersOnPreviousCursorPosition() + { + auto simulatedCursorPosition = + prevCursorPosition; // ensure prevCursorPosition is preserved + + doEnforceWidgetParameters( + simulatedCursorPosition); // updates lastWidgetEnforcedPosition with new widget + // state + + lastWidgetEnforcedPosition = + simulatedCursorPosition; // store enforced cursor position. + } + + /// returns the status to which the handler was updated + bool syncHandlerToCheckbox(int checkboxindex, bool& handlerboolean) + { + bool status = toolWidget->getCheckboxChecked(checkboxindex); + handlerboolean = status; + + return status; + } + + /// returns true if checkbox was changed, and false if no sync was necessary + bool syncCheckboxToHandler(int checkboxindex, bool handlerboolean) + { + bool status = toolWidget->getCheckboxChecked(checkboxindex); + if (handlerboolean != status) { + toolWidget->setCheckboxChecked(checkboxindex, handlerboolean); + return true; + } + + return false; + } + + void syncHandlerToConstructionMethodCombobox() + { + + if constexpr (PFirstComboboxIsConstructionMethod == true) { + auto constructionmethod = toolWidget->getComboboxIndex(WCombobox::FirstCombo); + + handler->initConstructionMethod( + static_cast(constructionmethod)); + } + } + void syncConstructionMethodComboboxToHandler() + { + + if constexpr (PFirstComboboxIsConstructionMethod == true) { + auto constructionmethod = toolWidget->getComboboxIndex(WCombobox::FirstCombo); + + auto actualconstructionmethod = static_cast(handler->constructionMethod()); + + if (constructionmethod != actualconstructionmethod) { + toolWidget->setComboboxIndex(WCombobox::FirstCombo, actualconstructionmethod); + } + } + } + + bool isParameterOfCurrentMode(int parameterindex) const + { + return getState(parameterindex) == handler->state(); + } + + bool isParameterOfPreviousMode(int parameterindex) const + { + return getState(parameterindex) < handler->state(); + } + //@} + }; + +public: + DrawSketchDefaultWidgetHandler( + ConstructionMethodT constructionmethod = static_cast(0)) + : DSDefaultHandler(constructionmethod) + , toolWidgetManager(this) + {} + virtual ~DrawSketchDefaultWidgetHandler() = default; + + /** @name functions NOT intended for specialisation */ + //@{ + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + toolWidgetManager.enforceWidgetParameters(onSketchPos); + toolWidgetManager.adaptWidgetParameters(onSketchPos); + updateDataAndDrawToPosition(onSketchPos); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + toolWidgetManager.enforceWidgetParameters(onSketchPos); + + onButtonPressed(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + DSDefaultHandler::finish(); + return true; + } + //@} + + +protected: + /** @name functions requiring specialisation */ + //@{ + virtual std::string getToolName() const override + { + return DrawSketchHandler::getToolName(); + } + virtual QString getCrosshairCursorSVGName() const override + { + return DrawSketchHandler::getCrosshairCursorSVGName(); + } + //@} + +private: + /** @name functions requiring specialisation */ + //@{ + // For every machine state, it updates the EditData temporary + // curve, and draws the temporary curve during edit mode. + virtual void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos) + }; + + virtual void executeCommands() override {}; + virtual void createAutoConstraints() override {}; + //@} + + /** @name functions which MAY require specialisation*/ + //@{ + /** Default implementation is that on every mouse click the mode is changed to the next seek + On the last seek, it changes to SelectMode::End + If this behaviour is not acceptable, then the function must be specialised.*/ + virtual void onButtonPressed(Base::Vector2d onSketchPos) override + { + DSDefaultHandler::onButtonPressed(onSketchPos); + } + + virtual void beforeCreateAutoConstraints() override + { + toolWidgetManager.addConstraints(); + } + + virtual void onWidgetChanged() override + { + toolWidgetManager.initWidget(DSDefaultHandler::toolwidget); + } + + virtual void onReset() override + { + toolWidgetManager.reset(); + } + + virtual void onModeChanged() override + { + toolWidgetManager.onHandlerModeChanged(); + DSDefaultHandler::onModeChanged(); + } + + virtual void onConstructionMethodChanged() override + { + toolWidgetManager.onConstructionMethodChanged(); + } + + virtual void registerPressedKey(bool pressed, int key) override + { + DSDefaultHandler::registerPressedKey(pressed, key); + + if (key == SoKeyboardEvent::U && !pressed && !this->isLastState()) { + toolWidgetManager.firstKeyShortcut(); + } + + if (key == SoKeyboardEvent::J && !pressed && !this->isLastState()) { + toolWidgetManager.secondKeyShortcut(); + } + } + //@} + +protected: + ToolWidgetManager toolWidgetManager; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerDefaultWidget_H