From 3a656d8597187dcc0e5d3db483cd5f5dbf7c2b43 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 3 Feb 2021 06:27:34 +0100 Subject: [PATCH] Sketcher: UI Constraint Creation - PointOnObject + Tangency on edge constraint substitution ============================================================================================ On creation of a constraint from the UI (toolbar/menu): 1. if a PointOnObject constraint preexisted the addition of an edge-to-edge tangency, substitute it with a point-to-edge tangency. 2. if an edge-to-edge tangency preexisted, addition of a PointOnObject results in a substitution of the edge-to-edge tangency with an edge-to-curve tangency. Bonus: - Refactor of this with preexisting coincident+tangent substitution. - Activate both substitutions in continuous constraint addition mode. --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 253 ++++++++++++++------ src/Mod/Sketcher/Gui/CommandConstraints.h | 6 +- 2 files changed, 190 insertions(+), 69 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index 64b2f46163..77826c7247 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -642,9 +642,9 @@ bool SketcherGui::checkConstraint(const std::vector< Sketcher::Constraint * > &v return false; } - -void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, Gui::SelectionObject &selection, - int GeoId1, int GeoId2, PointPos PosId1, PointPos PosId2){ +void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, + int GeoId1, int GeoId2, PointPos PosId1, PointPos PosId2) +{ // This code supports simple B-spline endpoint tangency to any other geometric curve const Part::Geometry *geom1 = Obj->getGeometry(GeoId1); const Part::Geometry *geom2 = Obj->getGeometry(GeoId2); @@ -660,10 +660,16 @@ void SketcherGui::doEndpointTangency(Sketcher::SketchObject* Obj, Gui::Selection // GeoId1 is the B-spline now } // end of code supports simple B-spline endpoint tangency - Gui::cmdAppObjectArgs(selection.getObject(), "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", + Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d,%d)) ", GeoId1,PosId1,GeoId2,PosId2); } +void SketcherGui::doEndpointToEdgeTangency( Sketcher::SketchObject* Obj, int GeoId1, PointPos PosId1, int GeoId2) +{ + Gui::cmdAppObjectArgs(Obj, "addConstraint(Sketcher.Constraint('Tangent',%d,%d,%d)) ", + GeoId1,PosId1,GeoId2); +} + void SketcherGui::notifyConstraintSubstitutions(const QString & message) { Gui::Dialog::DlgCheckableMessageBox::showMessage( QObject::tr("Sketcher Constraint Substitution"), @@ -2066,6 +2072,10 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, + int GeoId2, PointPos PosId2); }; CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() @@ -2084,6 +2094,48 @@ CmdSketcherConstrainCoincident::CmdSketcherConstrainCoincident() allowedSelSequences = {{SelVertex, SelVertexOrRoot}, {SelRoot, SelVertex}}; } +bool CmdSketcherConstrainCoincident::substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, + int GeoId2, PointPos PosId2) +{ + // checks for direct and indirect coincidence constraints + bool constraintExists = Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); + + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int j=0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it,++j) { + if( (*it)->Type == Sketcher::Tangent && + (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && + (*it)->Third == Constraint::GeoUndef && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap edge tangency with ptp tangency")); + + if( constraintExists ) { + // try to remove any pre-existing direct coincident constraints + Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", GeoId1, PosId1); + } + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", j); + + doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2); + + commitCommand(); + Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainCoincident::activated(int iMsg) { Q_UNUSED(iMsg); @@ -2145,42 +2197,16 @@ void CmdSketcherConstrainCoincident::activated(int iMsg) return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, PosId1,GeoId2, PosId2)) + return; + // check if this coincidence is already enforced (even indirectly) bool constraintExists=Obj->arePointsCoincident(GeoId1,PosId1,GeoId2,PosId2); - // check for a preexisting edge-to-edge tangency - const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); - - int j=0; - for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it,++j) { - if( (*it)->Type == Sketcher::Tangent && - (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && - (*it)->Third == Constraint::GeoUndef && - (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || - ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap edge tangency with ptp tangency")); - - if(constraintExists) { - // try to remove any pre-existing direct coincident constraints - Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", GeoId1, PosId1); - } - - Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", j); - - doEndpointTangency(Obj, selection[0], GeoId1, GeoId2, PosId1, PosId2); - - commitCommand(); - Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. - tryAutoRecomputeIfNotSolve(Obj); - - notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); - - getSelection().clearSelection(); - return; - } - } - if (!constraintExists) { constraintsAdded = true; Gui::cmdAppObjectArgs(selection[0].getObject(), "addConstraint(Sketcher.Constraint('Coincident',%d,%d,%d,%d)) ", @@ -2218,6 +2244,13 @@ void CmdSketcherConstrainCoincident::applyConstraint(std::vector &sel return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, PosId1,GeoId2, PosId2)) + return; + // undo command open Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add coincident constraint")); @@ -2595,10 +2628,11 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, + int GeoId1, PointPos PosId1, int GeoId2); }; -//DEF_STD_CMD_A(CmdSketcherConstrainPointOnObject); - CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject() :CmdSketcherConstraint("Sketcher_ConstrainPointOnObject") { @@ -2619,6 +2653,36 @@ CmdSketcherConstrainPointOnObject::CmdSketcherConstrainPointOnObject() } +bool CmdSketcherConstrainPointOnObject::substituteConstraintCombinations( SketchObject * Obj, + int GeoId1, PointPos PosId1, int GeoId2) +{ + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int cid = 0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it, ++cid) { + if( (*it)->Type == Sketcher::Tangent && + (*it)->FirstPos == Sketcher::none && (*it)->SecondPos == Sketcher::none && + (*it)->Third == Constraint::GeoUndef && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + // NOTE: This function does not either open or commit a command as it is used for group addition + // it relies on such infrastructure being provided by the caller. + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", cid); + + doEndpointToEdgeTangency(Obj, GeoId1, PosId1, GeoId2); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied instead.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainPointOnObject::activated(int iMsg) { Q_UNUSED(iMsg); @@ -2689,6 +2753,11 @@ void CmdSketcherConstrainPointOnObject::activated(int iMsg) continue; } + if(substituteConstraintCombinations(Obj, points[iPnt].GeoId, points[iPnt].PosId, curves[iCrv].GeoId)) { + cnt++; + continue; + } + cnt++; Gui::cmdAppObjectArgs(selection[0].getObject(),"addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", points[iPnt].GeoId, points[iPnt].PosId, curves[iCrv].GeoId); @@ -2773,6 +2842,12 @@ void CmdSketcherConstrainPointOnObject::applyConstraint(std::vector & return; } + if(substituteConstraintCombinations(Obj, GeoIdVt, PosIdVt, GeoIdCrv)) { + commitCommand(); + tryAutoRecompute(Obj); + return; + } + if (allOK) { Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('PointOnObject',%d,%d,%d)) ", GeoIdVt, PosIdVt, GeoIdCrv); @@ -4090,6 +4165,8 @@ public: protected: virtual void activated(int iMsg); virtual void applyConstraint(std::vector &selSeq, int seqIndex); + // returns true if a substitution took place + bool substituteConstraintCombinations(SketchObject * Obj, int GeoId1, int GeoId2); }; CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() @@ -4118,6 +4195,62 @@ CmdSketcherConstrainTangent::CmdSketcherConstrainTangent() {SelVertexOrRoot, SelVertex} /*Two Endpoints*/ /*No Place for One Endpoint and One Curve*/}; } +bool CmdSketcherConstrainTangent::substituteConstraintCombinations(SketchObject * Obj, int GeoId1, int GeoId2) +{ + const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); + + int cid = 0; + for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it, ++cid) { + if( (*it)->Type == Sketcher::Coincident && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + // save values because 'doEndpointTangency' changes the + // constraint property and thus invalidates this iterator + int first = (*it)->First; + int firstpos = static_cast((*it)->FirstPos); + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency")); + + doEndpointTangency(Obj, (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos); + + Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", first, firstpos); + + commitCommand(); + Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied. The coincident constraint was deleted.")); + + getSelection().clearSelection(); + return true; + } + else if( (*it)->Type == Sketcher::PointOnObject && + (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || + ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap PointOnObject+tangency with point to curve tangency")); + + doEndpointToEdgeTangency(Obj, (*it)->First, (*it)->FirstPos, (*it)->Second); + + Gui::cmdAppObjectArgs(Obj, "delConstraint(%i)", cid); // remove the preexisting point on object constraint. + + commitCommand(); + + // A substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. However, + // delConstraint includes such solve() internally. So at this point it is already solved. + tryAutoRecomputeIfNotSolve(Obj); + + notifyConstraintSubstitutions(QObject::tr("Endpoint to edge tangency was applied. The point on object constraint was deleted.")); + + getSelection().clearSelection(); + return true; + } + } + + return false; +} + void CmdSketcherConstrainTangent::activated(int iMsg) { Q_UNUSED(iMsg); @@ -4244,7 +4377,7 @@ void CmdSketcherConstrainTangent::activated(int iMsg) } openCommand(QT_TRANSLATE_NOOP("Command", "Add tangent constraint")); - doEndpointTangency(Obj, selection[0], GeoId1, GeoId2, PosId1, PosId2); + doEndpointTangency(Obj, GeoId1, GeoId2, PosId1, PosId2); commitCommand(); tryAutoRecompute(Obj); @@ -4308,35 +4441,13 @@ void CmdSketcherConstrainTangent::activated(int iMsg) QObject::tr("Select an edge that is not a B-spline weight")); return; } - // check if there is a coincidence constraint on GeoId1, GeoId2 - const std::vector< Constraint * > &cvals = Obj->Constraints.getValues(); - for (std::vector::const_iterator it = cvals.begin(); it != cvals.end(); ++it) { - if( (*it)->Type == Sketcher::Coincident && - (((*it)->First == GeoId1 && (*it)->Second == GeoId2) || - ((*it)->Second == GeoId1 && (*it)->First == GeoId2)) ) { - - // save values because 'doEndpointTangency' changes the - // constraint property and thus invalidates this iterator - int first = (*it)->First; - int firstpos = static_cast((*it)->FirstPos); - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Swap coincident+tangency with ptp tangency")); - - doEndpointTangency(Obj, selection[0], (*it)->First, (*it)->Second, (*it)->FirstPos, (*it)->SecondPos); - - Gui::cmdAppObjectArgs(Obj, "delConstraintOnPoint(%i,%i)", first, firstpos); - - commitCommand(); - Obj->solve(); // The substitution requires a solve() so that the autoremove redundants works when Autorecompute not active. - tryAutoRecomputeIfNotSolve(Obj); - - notifyConstraintSubstitutions(QObject::tr("Endpoint to endpoint tangency was applied instead.")); - - getSelection().clearSelection(); - return; - } - } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, GeoId2)) + return; if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || @@ -4529,6 +4640,12 @@ void CmdSketcherConstrainTangent::applyConstraint(std::vector &selSeq return; } + // check if as a consequence of this command undesirable combinations of constraints would + // arise and substitute them with more appropriate counterparts, examples: + // - coincidence + tangency on edge + // - point on object + tangency on edge + if(substituteConstraintCombinations(Obj, GeoId1, GeoId2)) + return; if( geom1 && geom2 && ( geom1->getTypeId() == Part::GeomEllipse::getClassTypeId() || diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.h b/src/Mod/Sketcher/Gui/CommandConstraints.h index ee589805b6..14f1aa8cd3 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.h +++ b/src/Mod/Sketcher/Gui/CommandConstraints.h @@ -137,10 +137,14 @@ void tryAutoRecomputeIfNotSolve(Sketcher::SketchObject* obj); bool checkConstraint(const std::vector< Sketcher::Constraint * > &vals, Sketcher::ConstraintType type, int geoid, Sketcher::PointPos pos); /// Does an endpoint-to-endpoint tangency -void doEndpointTangency(Sketcher::SketchObject* Obj, Gui::SelectionObject &selection, int GeoId1, int GeoId2, Sketcher::PointPos PosId1, Sketcher::PointPos PosId2); +void doEndpointTangency(Sketcher::SketchObject* Obj, int GeoId1, int GeoId2, Sketcher::PointPos PosId1, Sketcher::PointPos PosId2); + +/// Does an endpoint-edge tangency +void doEndpointToEdgeTangency( Sketcher::SketchObject* Obj, int GeoId1, Sketcher::PointPos PosId1, int GeoId2); /// shows constraint substitution information dialog box, enabling the user to forgo further notifications void notifyConstraintSubstitutions(const QString & message); + } #endif // SKETCHERGUI_DrawSketchHandler_H