diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index b45f6bed37..820218dfd9 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -64,6 +64,25 @@ SET(SketcherGui_SRCS AutoConstraint.h Utils.h Utils.cpp + DrawSketchHandlerLine.h + DrawSketchHandlerRectangle.h + DrawSketchHandlerPolygon.h + DrawSketchHandlerLineSet.h + DrawSketchHandlerCircle.h + DrawSketchHandlerEllipse.h + DrawSketchHandlerArc.h + DrawSketchHandlerArcOfEllipse.h + DrawSketchHandlerArcOfHyperbola.h + DrawSketchHandlerArcOfParabola.h + DrawSketchHandlerBSpline.h + DrawSketchHandlerPoint.h + DrawSketchHandlerFillet.h + DrawSketchHandlerTrimming.h + DrawSketchHandlerExtend.h + DrawSketchHandlerSplitting.h + DrawSketchHandlerExternal.h + DrawSketchHandlerCarbonCopy.h + DrawSketchHandlerSlot.h CommandCreateGeo.cpp CommandConstraints.h CommandConstraints.cpp diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp index acabaef01e..4dbf7b323e 100644 --- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp +++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp @@ -68,7 +68,25 @@ #include "GeometryCreationMode.h" -#include "SketcherRegularPolygonDialog.h" +#include "DrawSketchHandlerLine.h" +#include "DrawSketchHandlerRectangle.h" +#include "DrawSketchHandlerPolygon.h" +#include "DrawSketchHandlerLineSet.h" +#include "DrawSketchHandlerCircle.h" +#include "DrawSketchHandlerEllipse.h" +#include "DrawSketchHandlerArc.h" +#include "DrawSketchHandlerArcOfEllipse.h" +#include "DrawSketchHandlerArcOfHyperbola.h" +#include "DrawSketchHandlerArcOfParabola.h" +#include "DrawSketchHandlerBSpline.h" +#include "DrawSketchHandlerPoint.h" +#include "DrawSketchHandlerFillet.h" +#include "DrawSketchHandlerTrimming.h" +#include "DrawSketchHandlerExtend.h" +#include "DrawSketchHandlerSplitting.h" +#include "DrawSketchHandlerExternal.h" +#include "DrawSketchHandlerCarbonCopy.h" +#include "DrawSketchHandlerSlot.h" using namespace std; using namespace SketcherGui; @@ -79,133 +97,6 @@ GeometryCreationMode geometryCreationMode=Normal; /* Sketch commands =======================================================*/ -class DrawSketchHandlerLine: public DrawSketchHandler -{ -public: - DrawSketchHandlerLine():Mode(STATUS_SEEK_First),EditCurve(2){} - virtual ~DrawSketchHandlerLine(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second){ - float length = (onSketchPos - EditCurve[0]).Length(); - float angle = (onSketchPos - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f)); - SbString text; - text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI); - setPositionText(onSketchPos, text); - - EditCurve[1] = onSketchPos; - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - EditCurve[0] = onSketchPos; - - Mode = STATUS_SEEK_Second; - } - else { - EditCurve[1] = onSketchPos; - drawEdit(EditCurve); - Mode = STATUS_End; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_End){ - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch line")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", - EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, - geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add line: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue(); - - if(avoidredundant) - removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); - - // add auto constraints for the line segment start - if (!sugConstr1.empty()) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); - sugConstr1.clear(); - } - - // add auto constraints for the line segment end - if (!sugConstr2.empty()) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::end); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - EditCurve.clear(); - drawEdit(EditCurve); - - bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); - if(continuousMode){ - // This code enables the continuous creation mode. - Mode=STATUS_SEEK_First; - EditCurve.resize(2); - applyCursor(); - /* It is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Line"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; -}; - DEF_STD_CMD_AU(CmdSketcherCreateLine) CmdSketcherCreateLine::CmdSketcherCreateLine() @@ -250,271 +141,6 @@ bool CmdSketcherCreateLine::isActive(void) /* Create Box =======================================================*/ -class DrawSketchHandlerBox: public DrawSketchHandler -{ -public: - enum ConstructionMethod { - Diagonal, - CenterAndCorner - }; - - DrawSketchHandlerBox(ConstructionMethod constrMethod = Diagonal): Mode(STATUS_SEEK_First), - EditCurve(5), - constructionMethod(constrMethod){} - virtual ~DrawSketchHandlerBox(){} - - /// mode table - enum BoxMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_End - }; - -public: - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - if(constructionMethod == Diagonal) { - float dx = onSketchPos.x - EditCurve[0].x; - float dy = onSketchPos.y - EditCurve[0].y; - SbString text; - text.sprintf(" (%.1f x %.1f)", dx, dy); - setPositionText(onSketchPos, text); - - EditCurve[2] = onSketchPos; - EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y); - EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); - - } - else if (constructionMethod == CenterAndCorner) { - float dx = onSketchPos.x - center.x; - float dy = onSketchPos.y - center.y; - SbString text; - text.sprintf(" (%.1f x %.1f)", dx, dy); - setPositionText(onSketchPos, text); - - EditCurve[0] = center - (onSketchPos - center); - EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); - EditCurve[2] = onSketchPos; - EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y); - EditCurve[4] = EditCurve[0]; - } - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.0,0.0))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - if(constructionMethod == Diagonal) { - EditCurve[0] = onSketchPos; - EditCurve[4] = onSketchPos; - } - else if (constructionMethod == CenterAndCorner) { - center = onSketchPos; - } - - Mode = STATUS_SEEK_Second; - } - else { - if(constructionMethod == Diagonal) { - EditCurve[2] = onSketchPos; - EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y); - EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); - drawEdit(EditCurve); - Mode = STATUS_End; - } - else if (constructionMethod == CenterAndCorner) { - EditCurve[0] = center - (onSketchPos - center); - EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); - EditCurve[2] = onSketchPos; - EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y); - EditCurve[4] = EditCurve[0]; - drawEdit(EditCurve); - Mode = STATUS_End; - } - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_End){ - unsetCursor(); - resetPositionText(); - int firstCurve = getHighestCurveIndex() + 1; - - try { - if(constructionMethod == Diagonal) { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch box")); - Gui::Command::doCommand(Gui::Command::Doc, - "geoList = []\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "%s.addGeometry(geoList,%s)\n" - "conList = []\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Horizontal',%i))\n" - "conList.append(Sketcher.Constraint('Horizontal',%i))\n" - "conList.append(Sketcher.Constraint('Vertical',%i))\n" - "conList.append(Sketcher.Constraint('Vertical',%i))\n" - "%s.addConstraint(conList)\n" - "del geoList, conList\n", - EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 - EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 - EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 - EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4 - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch - geometryCreationMode==Construction?"True":"False", // geometry as construction or not - firstCurve,firstCurve+1, // coincident1 - firstCurve+1,firstCurve+2, // coincident2 - firstCurve+2,firstCurve+3, // coincident3 - firstCurve+3,firstCurve, // coincident4 - firstCurve, // horizontal1 - firstCurve+2, // horizontal2 - firstCurve+1, // vertical1 - firstCurve+3, // vertical2 - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch - - Gui::Command::commitCommand(); - } - else if (constructionMethod == CenterAndCorner) { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add centered sketch box")); - Gui::Command::doCommand(Gui::Command::Doc, - "geoList = []\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" - "geoList.append(Part.Point(App.Vector(%f,%f,0)))\n" - "%s.addGeometry(geoList,%s)\n" - "conList = []\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" - "conList.append(Sketcher.Constraint('Horizontal',%i))\n" - "conList.append(Sketcher.Constraint('Horizontal',%i))\n" - "conList.append(Sketcher.Constraint('Vertical',%i))\n" - "conList.append(Sketcher.Constraint('Vertical',%i))\n" - "conList.append(Sketcher.Constraint('Symmetric',%i,2,%i,1,%i,1))\n" - "%s.addConstraint(conList)\n" - "del geoList, conList\n", - EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 - EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 - EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 - EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4 - center.x,center.y, // center point - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch - geometryCreationMode==Construction?"True":"False", // geometry as construction or not - firstCurve,firstCurve+1, // coincident1 - firstCurve+1,firstCurve+2, // coincident2 - firstCurve+2,firstCurve+3, // coincident3 - firstCurve+3,firstCurve, // coincident4 - firstCurve+1, // horizontal1 - firstCurve+3, // horizontal2 - firstCurve, // vertical1 - firstCurve+2, // vertical2 - firstCurve+1, firstCurve, firstCurve + 4, // Symmetric - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch - - Gui::Command::commitCommand(); - } - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add box: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - if(constructionMethod == Diagonal) { - // add auto constraints at the start of the first side - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3 , Sketcher::PointPos::start); - sugConstr1.clear(); - } - - // add auto constraints at the end of the second side - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::PointPos::end); - sugConstr2.clear(); - } - - } - else if (constructionMethod == CenterAndCorner) { - // add auto constraints at the start of the first side - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); - sugConstr1.clear(); - } - - // add auto constraints at the end of the second side - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex() - 3, Sketcher::PointPos::end); - sugConstr2.clear(); - } - } - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(5); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - - - } - return true; - } - -private: - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Box"); - } -protected: - BoxMode Mode; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; - ConstructionMethod constructionMethod; - Base::Vector2d center; -}; - DEF_STD_CMD_AU(CmdSketcherCreateRectangle) CmdSketcherCreateRectangle::CmdSketcherCreateRectangle() @@ -600,290 +226,6 @@ bool CmdSketcherCreateRectangleCenter::isActive(void) /* Create rounded oblong =======================================================*/ -class DrawSketchHandlerOblong : public DrawSketchHandler -{ -public: - DrawSketchHandlerOblong() - : Mode(STATUS_SEEK_First) - , lengthX(0), lengthY(0), radius(0), signX(1), signY(1) - , EditCurve(37) - { - } - virtual ~DrawSketchHandlerOblong() {} - /// mode table - enum BoxMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - - if (Mode == STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode == STATUS_SEEK_Second) { - float distanceX = onSketchPos.x - StartPos.x; - float distanceY = onSketchPos.y - StartPos.y; - - lengthX = distanceX; lengthY = distanceY; - signX = Base::sgn(distanceX); - signY = Base::sgn(distanceY); - if (fabs(distanceX) > fabs(distanceY)) { - radius = fabs(distanceY) / 4; // we use a fourth of the smaller distance as default radius - } - else { - radius = fabs(distanceX) / 4; - } - - // we draw the lines with 36 segments, 8 for each arc and 4 lines - // draw the arcs - for (int i = 0; i < 8; i++) { - // calculate the x,y positions forming the the arc - double angle = i * M_PI / 16.0; - double x_i = -radius * sin(angle); - double y_i = -radius * cos(angle); - // we are drawing clockwise starting with the arc that is besides StartPos - if (signX == signY) { - EditCurve[i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + signY * (radius + y_i)); - EditCurve[9 + i] = Base::Vector2d(StartPos.x + signY * (radius + y_i), StartPos.y + lengthY - signX * (radius + x_i)); - EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i)); - EditCurve[27 + i] = Base::Vector2d(StartPos.x + lengthX - signY * (radius + y_i), StartPos.y + signX * (radius + x_i)); - } - else { - EditCurve[i] = Base::Vector2d(StartPos.x - signY * (radius + y_i), StartPos.y - signX * (radius + x_i)); - EditCurve[9 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + signY * (radius + y_i)); - EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX + signY * (radius + y_i), StartPos.y + lengthY + signX * (radius + x_i)); - EditCurve[27 + i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i)); - } - } - // draw the lines - if (signX == signY) { - EditCurve[8] = Base::Vector2d(StartPos.x, StartPos.y + (signY * radius)); - EditCurve[17] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y + lengthY); - EditCurve[26] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + lengthY - (signY * radius)); - EditCurve[35] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y); - } - else { - EditCurve[8] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y); - EditCurve[17] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + (signY * radius)); - EditCurve[26] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY); - EditCurve[35] = Base::Vector2d(StartPos.x, StartPos.y + lengthY - (signY * radius)); - } - // close the curve - EditCurve[36] = EditCurve[0]; - - SbString text; - text.sprintf(" (%.1fR %.1fX %.1fY)", radius, lengthX, lengthY); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_SEEK_First) { - StartPos = onSketchPos; - Mode = STATUS_SEEK_Second; - } - else { - EndPos = onSketchPos; - Mode = STATUS_End; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode == STATUS_End) { - unsetCursor(); - resetPositionText(); - - int firstCurve = getHighestCurveIndex() + 1; - // add the geometry to the sketch - // first determine the angles for the first arc - double start = 0; - double end = M_PI / 2; - if (signX > 0 && signY > 0) { - start = -2 * end; - end = -1 * end; - } - else if (signX > 0 && signY < 0) { - start = end; - end = 2 * end; - } - else if (signX < 0 && signY > 0) { - start = -1 * end; - end = 0; - } - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add rounded rectangle")); - Gui::Command::doCommand(Gui::Command::Doc, - // syntax for arcs: Part.ArcOfCircle(Part.Circle(center, axis, radius), startangle, endangle) - "geoList = []\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "%s.addGeometry(geoList, %s)\n" - "conList = []\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" - "conList.append(Sketcher.Constraint('Horizontal', %i))\n" - "conList.append(Sketcher.Constraint('Horizontal', %i))\n" - "conList.append(Sketcher.Constraint('Vertical', %i))\n" - "conList.append(Sketcher.Constraint('Vertical', %i))\n" - "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" - "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" - "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" - "%s.addConstraint(conList)\n" - "del geoList, conList\n", - StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1 - radius, - start, end, // start and end angle of arc1 - EditCurve[8].x, EditCurve[8].y, EditCurve[9].x, EditCurve[9].y, // line 1 - signX == signY ? StartPos.x + (signX * radius) : StartPos.x + lengthX - (signX * radius), // center of the arc 2 - signX == signY ? StartPos.y + lengthY - (signY * radius) : StartPos.y + (signY * radius), - radius, - // start and end angle of arc 2 - // the logic is that end is start + M_PI / 2 and start is the previous end - M_PI - end - M_PI, - end - 0.5 * M_PI, - EditCurve[17].x, EditCurve[17].y, EditCurve[18].x, EditCurve[18].y, // line 2 - StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY - (signY * radius), // center of the arc 3 - radius, - end - 1.5 * M_PI, - end - M_PI, - EditCurve[26].x, EditCurve[26].y, EditCurve[27].x, EditCurve[27].y, // line 3 - signX == signY ? StartPos.x + lengthX - (signX * radius) : StartPos.x + (signX * radius), // center of the arc 4 - signX == signY ? StartPos.y + (signY * radius) : StartPos.y + lengthY - (signY * radius), - radius, - end - 2 * M_PI, - end - 1.5 * M_PI, - EditCurve[35].x, EditCurve[35].y, EditCurve[36].x, EditCurve[36].y, // line 4 - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch - geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not - firstCurve, firstCurve + 1, // tangent 1 - firstCurve + 1, firstCurve + 2, // tangent 2 - firstCurve + 2, firstCurve + 3, // tangent 3 - firstCurve + 3, firstCurve + 4, // tangent 4 - firstCurve + 4, firstCurve + 5, // tangent 5 - firstCurve + 5, firstCurve + 6, // tangent 6 - firstCurve + 6, firstCurve + 7, // tangent 7 - firstCurve + 7, firstCurve, // tangent 8 - signX == signY ? firstCurve + 3 : firstCurve + 1, // horizontal constraint - signX == signY ? firstCurve + 7 : firstCurve + 5, // horizontal constraint - signX == signY ? firstCurve + 1 : firstCurve + 3, // vertical constraint - signX == signY ? firstCurve + 5 : firstCurve + 7, // vertical constraint - firstCurve, firstCurve + 2, // equal 1 - firstCurve + 2, firstCurve + 4, // equal 2 - firstCurve + 4, firstCurve + 6, // equal 3 - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch - - // now add construction geometry - two points used to take suggested constraints - Gui::Command::doCommand(Gui::Command::Doc, - "geoList = []\n" - "geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n" - "geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n" - "%s.addGeometry(geoList, True)\n" // geometry as construction - "conList = []\n" - "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" - "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" - "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" - "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" - "%s.addConstraint(conList)\n" - "del geoList, conList\n", - StartPos.x, StartPos.y, // point at StartPos - EndPos.x, EndPos.y, // point at EndPos - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch - firstCurve + 8, firstCurve + 1, // point on object constraint - firstCurve + 8, firstCurve + 7, // point on object constraint - firstCurve + 9, firstCurve + 3, // point on object constraint - firstCurve + 9, firstCurve + 5, // point on object constraint - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch - - Gui::Command::commitCommand(); - - // add auto constraints at the StartPos auxiliary point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex() - 1, Sketcher::PointPos::start); - sugConstr1.clear(); - } - - // add auto constraints at the EndPos auxiliary point - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::start); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add rounded rectangle: %s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecompute(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. - Mode = STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(37); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else { - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Oblong"); - } - -protected: - BoxMode Mode; - Base::Vector2d StartPos, EndPos; - double lengthX, lengthY, radius; - float signX, signY; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; -}; - DEF_STD_CMD_AU(CmdSketcherCreateOblong) CmdSketcherCreateOblong::CmdSketcherCreateOblong() @@ -1038,652 +380,6 @@ bool CmdSketcherCompCreateRectangles::isActive(void) // ====================================================================================== -class DrawSketchHandlerLineSet: public DrawSketchHandler -{ -public: - DrawSketchHandlerLineSet() - : Mode(STATUS_SEEK_First), SegmentMode(SEGMENT_MODE_Line) - , TransitionMode(TRANSITION_MODE_Free) - , SnapMode(SNAP_MODE_Free) - , suppressTransition(false) - , EditCurve(2) - , firstCurve(-1) - , previousCurve(-1) - , firstPosId(Sketcher::PointPos::none) - , previousPosId(Sketcher::PointPos::none) - , startAngle(0) - , endAngle(0) - , arcRadius(0) - , firstsegment(true) - { - } - virtual ~DrawSketchHandlerLineSet() {} - /// mode table - enum SELECT_MODE { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_Do, - STATUS_Close - }; - - enum SEGMENT_MODE - { - SEGMENT_MODE_Arc, - SEGMENT_MODE_Line - }; - - enum TRANSITION_MODE - { - TRANSITION_MODE_Free, - TRANSITION_MODE_Tangent, - TRANSITION_MODE_Perpendicular_L, - TRANSITION_MODE_Perpendicular_R - }; - - enum SNAP_MODE - { - SNAP_MODE_Free, - SNAP_MODE_45Degree - }; - - virtual void registerPressedKey(bool pressed, int key) override - { - if (Mode != STATUS_SEEK_Second) - return; // SegmentMode can be changed only in STATUS_SEEK_Second mode - - if (key == SoKeyboardEvent::M && pressed && previousCurve != -1) { - // loop through the following modes: - // SEGMENT_MODE_Line, TRANSITION_MODE_Free / TRANSITION_MODE_Tangent - // SEGMENT_MODE_Line, TRANSITION_MODE_Perpendicular_L - // SEGMENT_MODE_Line, TRANSITION_MODE_Tangent / TRANSITION_MODE_Free - // SEGMENT_MODE_Arc, TRANSITION_MODE_Tangent - // SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_L - // SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_R - - SnapMode = SNAP_MODE_Free; - - Base::Vector2d onSketchPos; - if (SegmentMode == SEGMENT_MODE_Line) - onSketchPos = EditCurve[EditCurve.size()-1]; - else - onSketchPos = EditCurve[29]; - - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(previousCurve); - - if (SegmentMode == SEGMENT_MODE_Line) { - switch (TransitionMode) { - case TRANSITION_MODE_Free: - if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { // 3rd mode - SegmentMode = SEGMENT_MODE_Arc; - TransitionMode = TRANSITION_MODE_Tangent; - } - else // 1st mode - TransitionMode = TRANSITION_MODE_Perpendicular_L; - break; - case TRANSITION_MODE_Perpendicular_L: // 2nd mode - if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) - TransitionMode = TRANSITION_MODE_Free; - else - TransitionMode = TRANSITION_MODE_Tangent; - break; - case TRANSITION_MODE_Tangent: - if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) // 1st mode - TransitionMode = TRANSITION_MODE_Perpendicular_L; - else { // 3rd mode - SegmentMode = SEGMENT_MODE_Arc; - TransitionMode = TRANSITION_MODE_Tangent; - } - break; - default: // unexpected mode - TransitionMode = TRANSITION_MODE_Free; - break; - } - } - else { - switch (TransitionMode) { - case TRANSITION_MODE_Tangent: // 4th mode - TransitionMode = TRANSITION_MODE_Perpendicular_L; - break; - case TRANSITION_MODE_Perpendicular_L: // 5th mode - TransitionMode = TRANSITION_MODE_Perpendicular_R; - break; - default: // 6th mode (Perpendicular_R) + unexpected mode - SegmentMode = SEGMENT_MODE_Line; - if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) - TransitionMode = TRANSITION_MODE_Tangent; - else - TransitionMode = TRANSITION_MODE_Free; - break; - } - } - - if (SegmentMode == SEGMENT_MODE_Line) - EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3); - else - EditCurve.resize(32); - mouseMove(onSketchPos); // trigger an update of EditCurve - } - } - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - suppressTransition = false; - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second){ - if (SegmentMode == SEGMENT_MODE_Line) { - EditCurve[EditCurve.size()-1] = onSketchPos; - if (TransitionMode == TRANSITION_MODE_Tangent) { - Base::Vector2d Tangent(dirVec.x,dirVec.y); - EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Tangent); - if (EditCurve[1] * Tangent < 0) { - EditCurve[1] = EditCurve[2]; - suppressTransition = true; - } - else - EditCurve[1] = EditCurve[0] + EditCurve[1]; - } - else if (TransitionMode == TRANSITION_MODE_Perpendicular_L || - TransitionMode == TRANSITION_MODE_Perpendicular_R) { - Base::Vector2d Perpendicular(-dirVec.y,dirVec.x); - EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Perpendicular); - EditCurve[1] = EditCurve[0] + EditCurve[1]; - } - - drawEdit(EditCurve); - - float length = (EditCurve[1] - EditCurve[0]).Length(); - float angle = (EditCurve[1] - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f)); - - SbString text; - text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI); - setPositionText(EditCurve[1], text); - - if (TransitionMode == TRANSITION_MODE_Free) { - if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - } - else if (SegmentMode == SEGMENT_MODE_Arc) { - - if(QApplication::keyboardModifiers() == Qt::ControlModifier) - SnapMode = SNAP_MODE_45Degree; - else - SnapMode = SNAP_MODE_Free; - - Base::Vector2d Tangent; - if (TransitionMode == TRANSITION_MODE_Tangent) - Tangent = Base::Vector2d(dirVec.x,dirVec.y); - else if (TransitionMode == TRANSITION_MODE_Perpendicular_L) - Tangent = Base::Vector2d(-dirVec.y,dirVec.x); - else if (TransitionMode == TRANSITION_MODE_Perpendicular_R) - Tangent = Base::Vector2d(dirVec.y,-dirVec.x); - - double theta = Tangent.GetAngle(onSketchPos - EditCurve[0]); - - arcRadius = (onSketchPos - EditCurve[0]).Length()/(2.0*sin(theta)); - - // At this point we need a unit normal vector pointing towards - // the center of the arc we are drawing. Derivation of the formula - // used here can be found at http://people.richland.edu/james/lecture/m116/matrices/area.html - double x1 = EditCurve[0].x; - double y1 = EditCurve[0].y; - double x2 = x1 + Tangent.x; - double y2 = y1 + Tangent.y; - double x3 = onSketchPos.x; - double y3 = onSketchPos.y; - if ((x2*y3-x3*y2)-(x1*y3-x3*y1)+(x1*y2-x2*y1) > 0) - arcRadius *= -1; - if (boost::math::isnan(arcRadius) || boost::math::isinf(arcRadius)) - arcRadius = 0.f; - - CenterPoint = EditCurve[0] + Base::Vector2d(arcRadius * Tangent.y, -arcRadius * Tangent.x); - - double rx = EditCurve[0].x - CenterPoint.x; - double ry = EditCurve[0].y - CenterPoint.y; - - startAngle = atan2(ry,rx); - - double rxe = onSketchPos.x - CenterPoint.x; - double rye = onSketchPos.y - CenterPoint.y; - double arcAngle = atan2(-rxe*ry + rye*rx, rxe*rx + rye*ry); - if (boost::math::isnan(arcAngle) || boost::math::isinf(arcAngle)) - arcAngle = 0.f; - if (arcRadius >= 0 && arcAngle > 0) - arcAngle -= 2*M_PI; - if (arcRadius < 0 && arcAngle < 0) - arcAngle += 2*M_PI; - - if (SnapMode == SNAP_MODE_45Degree) - arcAngle = round(arcAngle / (M_PI/4)) * M_PI/4; - - endAngle = startAngle + arcAngle; - - for (int i=1; i <= 29; i++) { - double angle = i*arcAngle/29.0; - double dx = rx * cos(angle) - ry * sin(angle); - double dy = rx * sin(angle) + ry * cos(angle); - EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy); - } - - EditCurve[30] = CenterPoint; - EditCurve[31] = EditCurve[0]; - - drawEdit(EditCurve); - - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", std::abs(arcRadius), arcAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_SEEK_First) { - - EditCurve[0] = onSketchPos; // this may be overwritten if previousCurve is found - - virtualsugConstr1 = sugConstr1; // store original autoconstraints. - - // here we check if there is a preselected point and - // we set up a transition from the neighbouring segment. - // (peviousCurve, previousPosId, dirVec, TransitionMode) - for (unsigned int i=0; i < sugConstr1.size(); i++) - if (sugConstr1[i].Type == Sketcher::Coincident) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(sugConstr1[i].GeoId); - if ((geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) && - (sugConstr1[i].PosId == Sketcher::PointPos::start || - sugConstr1[i].PosId == Sketcher::PointPos::end)) { - previousCurve = sugConstr1[i].GeoId; - previousPosId = sugConstr1[i].PosId; - updateTransitionData(previousCurve,previousPosId); // -> dirVec, EditCurve[0] - if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - TransitionMode = TRANSITION_MODE_Tangent; - SnapMode = SNAP_MODE_Free; - } - sugConstr1.erase(sugConstr1.begin()+i); // actually we should clear the vector completely - break; - } - } - - // remember our first point (even if we are doing a transition from a previous curve) - firstCurve = getHighestCurveIndex() + 1; - firstPosId = Sketcher::PointPos::start; - - if (SegmentMode == SEGMENT_MODE_Line) - EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3); - else if (SegmentMode == SEGMENT_MODE_Arc) - EditCurve.resize(32); - Mode = STATUS_SEEK_Second; - } - else if (Mode == STATUS_SEEK_Second) { - // exit on clicking exactly at the same position (e.g. double click) - if (onSketchPos == EditCurve[0]) { - unsetCursor(); - resetPositionText(); - EditCurve.clear(); - drawEdit(EditCurve); - - 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. - Mode=STATUS_SEEK_First; - SegmentMode=SEGMENT_MODE_Line; - TransitionMode=TRANSITION_MODE_Free; - SnapMode = SNAP_MODE_Free; - suppressTransition=false; - firstCurve=-1; - previousCurve=-1; - firstPosId=Sketcher::PointPos::none; - previousPosId=Sketcher::PointPos::none; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this 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 */ - return true; - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - return true; - } - } - - Mode = STATUS_Do; - - if (getPreselectPoint() != -1 && firstPosId != Sketcher::PointPos::none) { - int GeoId; - Sketcher::PointPos PosId; - sketchgui->getSketchObject()->getGeoVertexIndex(getPreselectPoint(),GeoId,PosId); - if (sketchgui->getSketchObject()->arePointsCoincident(GeoId,PosId,firstCurve,firstPosId)) - Mode = STATUS_Close; - } - else if (getPreselectCross() == 0 && firstPosId != Sketcher::PointPos::none) { - // close line started at root point - if (sketchgui->getSketchObject()->arePointsCoincident(-1,Sketcher::PointPos::start,firstCurve,firstPosId)) - Mode = STATUS_Close; - } - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_Do || Mode == STATUS_Close) { - bool addedGeometry = true; - if (SegmentMode == SEGMENT_MODE_Line) { - // issue the geometry - try { - // open the transaction - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add line to sketch wire")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", - EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, - geometryCreationMode==Construction?"True":"False"); - } - catch (const Base::Exception& e) { - addedGeometry = false; - Base::Console().Error("Failed to add line: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - firstsegment=false; - } - else if (SegmentMode == SEGMENT_MODE_Arc) { // We're dealing with an Arc - if (!boost::math::isnormal(arcRadius)) { - Mode = STATUS_SEEK_Second; - return true; - } - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add arc to sketch wire")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" - "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", - CenterPoint.x, CenterPoint.y, std::abs(arcRadius), - std::min(startAngle,endAngle), std::max(startAngle,endAngle), - geometryCreationMode==Construction?"True":"False"); - } - catch (const Base::Exception& e) { - addedGeometry = false; - Base::Console().Error("Failed to add arc: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - firstsegment=false; - } - - int lastCurve = getHighestCurveIndex(); - // issue the constraint - if (addedGeometry && (previousPosId != Sketcher::PointPos::none)) { - Sketcher::PointPos lastStartPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? - Sketcher::PointPos::end : Sketcher::PointPos::start; - Sketcher::PointPos lastEndPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? - Sketcher::PointPos::start : Sketcher::PointPos::end; - // in case of a tangency constraint, the coincident constraint is redundant - std::string constrType = "Coincident"; - if (!suppressTransition && previousCurve != -1) { - if (TransitionMode == TRANSITION_MODE_Tangent) - constrType = "Tangent"; - else if (TransitionMode == TRANSITION_MODE_Perpendicular_L || - TransitionMode == TRANSITION_MODE_Perpendicular_R) - constrType = "Perpendicular"; - } - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('%s',%i,%i,%i,%i)) ", - constrType.c_str(), previousCurve, static_cast(previousPosId), lastCurve, static_cast(lastStartPosId)); - - if(SnapMode == SNAP_MODE_45Degree && Mode != STATUS_Close) { - // -360, -315, -270, -225, -180, -135, -90, -45, 0, 45, 90, 135, 180, 225, 270, 315, 360 - // N/A, a, perp, a, par, a,perp, a,N/A, a,perp, a, par, a,perp, a, N/A - - // #3974: if in radians, the printf %f defaults to six decimals, which leads to loss of precision - double arcAngle = abs(round( (endAngle - startAngle) / (M_PI/4)) * 45); // in degrees - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Angle',%i,App.Units.Quantity('%f deg'))) ", - lastCurve, arcAngle); - } - if (Mode == STATUS_Close) { - // close the loop by constrain to the first curve point - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Coincident',%i,%i,%i,%i)) ", - lastCurve,static_cast(lastEndPosId),firstCurve,static_cast(firstPosId)); - } - Gui::Command::commitCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - } - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue(); - - if (Mode == STATUS_Close) { - - if(avoidredundant) { - if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints. - if (sugConstr1.size() > 0) - removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); - else - removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),virtualsugConstr1,sugConstr2); - } - } - - if (sugConstr2.size() > 0) { - // exclude any coincidence constraints - std::vector sugConstr; - for (unsigned int i=0; i < sugConstr2.size(); i++) { - if (sugConstr2[i].Type != Sketcher::Coincident) - sugConstr.push_back(sugConstr2[i]); - } - createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::PointPos::end); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - unsetCursor(); - - resetPositionText(); - EditCurve.clear(); - drawEdit(EditCurve); - - 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. - Mode=STATUS_SEEK_First; - SegmentMode=SEGMENT_MODE_Line; - TransitionMode=TRANSITION_MODE_Free; - SnapMode = SNAP_MODE_Free; - suppressTransition=false; - firstCurve=-1; - previousCurve=-1; - firstPosId=Sketcher::PointPos::none; - previousPosId=Sketcher::PointPos::none; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this 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 { - Gui::Command::commitCommand(); - - // Add auto constraints - if (sugConstr1.size() > 0) { // this is relevant only to the very first point - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); - sugConstr1.clear(); - } - - - if(avoidredundant) { - if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints. - if (sugConstr1.size() > 0) - removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); - else - removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),virtualsugConstr1,sugConstr2); - } - } - - virtualsugConstr1 = sugConstr2; // these are the initial constraints for the next iteration. - - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), - (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? - Sketcher::PointPos::start : Sketcher::PointPos::end); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - // remember the vertex for the next rounds constraint.. - previousCurve = getHighestCurveIndex(); - previousPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? - Sketcher::PointPos::start : Sketcher::PointPos::end; // cw arcs are rendered in reverse - - // setup for the next line segment - // calculate dirVec and EditCurve[0] - updateTransitionData(previousCurve,previousPosId); - - applyCursor(); - Mode = STATUS_SEEK_Second; - if (SegmentMode == SEGMENT_MODE_Arc) { - TransitionMode = TRANSITION_MODE_Tangent; - EditCurve.resize(3); - EditCurve[2] = EditCurve[0]; - } - else { - TransitionMode = TRANSITION_MODE_Free; - EditCurve.resize(2); - } - SegmentMode = SEGMENT_MODE_Line; - SnapMode = SNAP_MODE_Free; - EditCurve[1] = EditCurve[0]; - mouseMove(onSketchPos); // trigger an update of EditCurve - } - } - return true; - } - - virtual void quit(void) 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 (firstsegment) { - // user when right-clicking with no segment in really wants to exit - DrawSketchHandler::quit(); - } - else { - - if(!continuousMode){ - DrawSketchHandler::quit(); - } - else { - // This code disregards existing data and enables the continuous creation mode. - Mode=STATUS_SEEK_First; - SegmentMode=SEGMENT_MODE_Line; - TransitionMode=TRANSITION_MODE_Free; - SnapMode = SNAP_MODE_Free; - suppressTransition=false; - firstCurve=-1; - previousCurve=-1; - firstPosId=Sketcher::PointPos::none; - previousPosId=Sketcher::PointPos::none; - firstsegment=true; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - } - } - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Lineset"); - } - -protected: - SELECT_MODE Mode; - SEGMENT_MODE SegmentMode; - TRANSITION_MODE TransitionMode; - SNAP_MODE SnapMode; - bool suppressTransition; - - std::vector EditCurve; - int firstCurve; - int previousCurve; - Sketcher::PointPos firstPosId; - Sketcher::PointPos previousPosId; - // the latter stores those constraints that a first point would have been given in absence of the transition mechanism - std::vector sugConstr1, sugConstr2, virtualsugConstr1; - - Base::Vector2d CenterPoint; - Base::Vector3d dirVec; - double startAngle, endAngle, arcRadius; - - bool firstsegment; - - void updateTransitionData(int GeoId, Sketcher::PointPos PosId) { - - // Use updated startPoint/endPoint as autoconstraints can modify the position - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geom); - dirVec.Set(lineSeg->getEndPoint().x - lineSeg->getStartPoint().x, - lineSeg->getEndPoint().y - lineSeg->getStartPoint().y, - 0.f); - if (PosId == Sketcher::PointPos::start) { - dirVec *= -1; - EditCurve[0] = Base::Vector2d(lineSeg->getStartPoint().x, lineSeg->getStartPoint().y); - } - else - EditCurve[0] = Base::Vector2d(lineSeg->getEndPoint().x, lineSeg->getEndPoint().y); - } - else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *arcSeg = static_cast(geom); - if (PosId == Sketcher::PointPos::start) { - EditCurve[0] = Base::Vector2d(arcSeg->getStartPoint(/*emulateCCW=*/true).x,arcSeg->getStartPoint(/*emulateCCW=*/true).y); - dirVec = Base::Vector3d(0.f,0.f,-1.0) % (arcSeg->getStartPoint(/*emulateCCW=*/true)-arcSeg->getCenter()); - } - else { - EditCurve[0] = Base::Vector2d(arcSeg->getEndPoint(/*emulateCCW=*/true).x,arcSeg->getEndPoint(/*emulateCCW=*/true).y); - dirVec = Base::Vector3d(0.f,0.f,1.0) % (arcSeg->getEndPoint(/*emulateCCW=*/true)-arcSeg->getCenter()); - } - } - dirVec.Normalize(); - } -}; - DEF_STD_CMD_AU(CmdSketcherCreatePolyline) CmdSketcherCreatePolyline::CmdSketcherCreatePolyline() @@ -1728,207 +424,6 @@ bool CmdSketcherCreatePolyline::isActive(void) // ====================================================================================== -class DrawSketchHandlerArc : public DrawSketchHandler -{ -public: - DrawSketchHandlerArc() - : Mode(STATUS_SEEK_First) - , EditCurve(2) - , rx(0), ry(0) - , startAngle(0) - , endAngle(0) - , arcAngle(0) - { - } - virtual ~DrawSketchHandlerArc(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - double dx_ = onSketchPos.x - EditCurve[0].x; - double dy_ = onSketchPos.y - EditCurve[0].y; - for (int i=0; i < 16; i++) { - double angle = i*M_PI/16.0; - double dx = dx_ * cos(angle) + dy_ * sin(angle); - double dy = -dx_ * sin(angle) + dy_ * cos(angle); - EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + dx, EditCurve[0].y + dy); - EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - dx, EditCurve[0].y - dy); - } - EditCurve[33] = EditCurve[1]; - - // Display radius and start angle - float radius = (onSketchPos - EditCurve[0]).Length(); - float angle = atan2f(dy_ , dx_); - - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", radius, angle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else if (Mode==STATUS_SEEK_Third) { - double angle1 = atan2(onSketchPos.y - CenterPoint.y, - onSketchPos.x - CenterPoint.x) - startAngle; - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; - for (int i=1; i <= 29; i++) { - double angle = i*arcAngle/29.0; - double dx = rx * cos(angle) - ry * sin(angle); - double dy = rx * sin(angle) + ry * cos(angle); - EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy); - } - - // Display radius and arc angle - float radius = (onSketchPos - EditCurve[0]).Length(); - - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", radius, arcAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0))) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - applyCursor(); - - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - CenterPoint = onSketchPos; - EditCurve.resize(34); - EditCurve[0] = onSketchPos; - Mode = STATUS_SEEK_Second; - } - else if (Mode==STATUS_SEEK_Second){ - EditCurve.resize(31); - EditCurve[0] = onSketchPos; - EditCurve[30] = CenterPoint; - rx = EditCurve[0].x - CenterPoint.x; - ry = EditCurve[0].y - CenterPoint.y; - startAngle = atan2(ry, rx); - arcAngle = 0.; - Mode = STATUS_SEEK_Third; - } - else { - EditCurve.resize(30); - double angle1 = atan2(onSketchPos.y - CenterPoint.y, - onSketchPos.x - CenterPoint.x) - startAngle; - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; - if (arcAngle > 0) - endAngle = startAngle + arcAngle; - else { - endAngle = startAngle; - startAngle += arcAngle; - } - - drawEdit(EditCurve); - applyCursor(); - Mode = STATUS_End; - } - - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_End) { - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" - "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", - CenterPoint.x, CenterPoint.y, sqrt(rx*rx + ry*ry), - startAngle, endAngle, - geometryCreationMode==Construction?"True":"False"); //arcAngle > 0 ? 0 : 1); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add arc: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - // Auto Constraint center point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // Auto Constraint first picked point - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::PointPos::start : Sketcher::PointPos::end ); - sugConstr2.clear(); - } - - // Auto Constraint second picked point - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::PointPos::end : Sketcher::PointPos::start); - sugConstr3.clear(); - } - - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Arc"); - } -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d CenterPoint; - double rx, ry, startAngle, endAngle, arcAngle; - std::vector sugConstr1, sugConstr2, sugConstr3; -}; - DEF_STD_CMD_A(CmdSketcherCreateArc) CmdSketcherCreateArc::CmdSketcherCreateArc() @@ -1959,248 +454,6 @@ bool CmdSketcherCreateArc::isActive(void) // ====================================================================================== -class DrawSketchHandler3PointArc : public DrawSketchHandler -{ -public: - DrawSketchHandler3PointArc() - : Mode(STATUS_SEEK_First), EditCurve(2) - , radius(0), startAngle(0) - , endAngle(0), arcAngle(0) - , arcPos1(Sketcher::PointPos::none) - , arcPos2(Sketcher::PointPos::none) - { - } - virtual ~DrawSketchHandler3PointArc(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - CenterPoint = EditCurve[0] = (onSketchPos - FirstPoint)/2 + FirstPoint; - EditCurve[1] = EditCurve[33] = onSketchPos; - radius = (onSketchPos - CenterPoint).Length(); - double lineAngle = GetPointAngle(CenterPoint, onSketchPos); - - // Build a 32 point circle ignoring already constructed points - for (int i=1; i <= 32; i++) { - // Start at current angle - double angle = (i-1)*2*M_PI/32.0 + lineAngle; // N point closed circle has N segments - if (i != 1 && i != 17 ) { - EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), - CenterPoint.y + radius*sin(angle)); - } - } - - // Display radius and start angle - // This lineAngle will report counter-clockwise from +X, not relatively - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else if (Mode==STATUS_SEEK_Third) { - /* - Centerline inverts when the arc flips sides. Easily taken care of by replacing - centerline with a point. It happens because the direction the curve is being drawn - reverses. - */ - try { - CenterPoint = EditCurve[30] = Part::Geom2dCircle::getCircleCenter(FirstPoint, SecondPoint, onSketchPos); - - radius = (SecondPoint - CenterPoint).Length(); - - double angle1 = GetPointAngle(CenterPoint, FirstPoint); - double angle2 = GetPointAngle(CenterPoint, SecondPoint); - double angle3 = GetPointAngle(CenterPoint, onSketchPos); - - // Always build arc counter-clockwise - // Point 3 is between Point 1 and 2 - if ( angle3 > min(angle1, angle2) && angle3 < max(angle1, angle2) ) { - if (angle2 > angle1) { - EditCurve[0] = FirstPoint; - EditCurve[29] = SecondPoint; - arcPos1 = Sketcher::PointPos::start; - arcPos2 = Sketcher::PointPos::end; - } - else { - EditCurve[0] = SecondPoint; - EditCurve[29] = FirstPoint; - arcPos1 = Sketcher::PointPos::end; - arcPos2 = Sketcher::PointPos::start; - } - startAngle = min(angle1, angle2); - endAngle = max(angle1, angle2); - arcAngle = endAngle - startAngle; - } - // Point 3 is not between Point 1 and 2 - else { - if (angle2 > angle1) { - EditCurve[0] = SecondPoint; - EditCurve[29] = FirstPoint; - arcPos1 = Sketcher::PointPos::end; - arcPos2 = Sketcher::PointPos::start; - } - else { - EditCurve[0] = FirstPoint; - EditCurve[29] = SecondPoint; - arcPos1 = Sketcher::PointPos::start; - arcPos2 = Sketcher::PointPos::end; - } - startAngle = max(angle1, angle2); - endAngle = min(angle1, angle2); - arcAngle = 2*M_PI - (startAngle - endAngle); - } - - // Build a 30 point circle ignoring already constructed points - for (int i=1; i <= 28; i++) { - double angle = startAngle + i*arcAngle/29.0; // N point arc has N-1 segments - EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), - CenterPoint.y + radius*sin(angle)); - } - - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) arcAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - catch(Base::ValueError &e) { - e.ReportException(); - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - // 32 point curve + center + endpoint - EditCurve.resize(34); - // 17 is circle halfway point (1+32/2) - FirstPoint = EditCurve[17] = onSketchPos; - - Mode = STATUS_SEEK_Second; - } - else if (Mode==STATUS_SEEK_Second){ - // 30 point arc and center point - EditCurve.resize(31); - SecondPoint = onSketchPos; - - Mode = STATUS_SEEK_Third; - } - else { - EditCurve.resize(30); - - drawEdit(EditCurve); - applyCursor(); - Mode = STATUS_End; - } - - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - // Need to look at. rx might need fixing. - if (Mode==STATUS_End) { - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" - "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", - CenterPoint.x, CenterPoint.y, radius, - startAngle, endAngle, - geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add arc: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - // Auto Constraint first picked point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), arcPos1); - sugConstr1.clear(); - } - - // Auto Constraint second picked point - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), arcPos2); - sugConstr2.clear(); - } - - // Auto Constraint third picked point - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::PointPos::none); - sugConstr3.clear(); - } - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_3PointArc"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d CenterPoint, FirstPoint, SecondPoint; - double radius, startAngle, endAngle, arcAngle; - std::vector sugConstr1, sugConstr2, sugConstr3; - Sketcher::PointPos arcPos1, arcPos2; -}; - DEF_STD_CMD_A(CmdSketcherCreate3PointArc) CmdSketcherCreate3PointArc::CmdSketcherCreate3PointArc() @@ -2331,139 +584,6 @@ bool CmdSketcherCompCreateArc::isActive(void) // ====================================================================================== -class DrawSketchHandlerCircle : public DrawSketchHandler -{ -public: - DrawSketchHandlerCircle() : Mode(STATUS_SEEK_First),EditCurve(34){} - virtual ~DrawSketchHandlerCircle(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_Close - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - double rx0 = onSketchPos.x - EditCurve[0].x; - double ry0 = onSketchPos.y - EditCurve[0].y; - for (int i=0; i < 16; i++) { - double angle = i*M_PI/16.0; - double rx = rx0 * cos(angle) + ry0 * sin(angle); - double ry = -rx0 * sin(angle) + ry0 * cos(angle); - EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx, EditCurve[0].y + ry); - EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx, EditCurve[0].y - ry); - } - EditCurve[33] = EditCurve[1]; - - // Display radius for user - float radius = (onSketchPos - EditCurve[0]).Length(); - - SbString text; - text.sprintf(" (%.1fR)", radius); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0], - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - EditCurve[0] = onSketchPos; - Mode = STATUS_SEEK_Second; - } else { - EditCurve[1] = onSketchPos; - Mode = STATUS_Close; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_Close) { - double rx = EditCurve[1].x - EditCurve[0].x; - double ry = EditCurve[1].y - EditCurve[0].y; - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle" - "(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)", - EditCurve[0].x, EditCurve[0].y, - sqrt(rx*rx + ry*ry), - geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add circle: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - // add auto constraints for the center point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // add suggested constraints for circumference - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::none); - sugConstr2.clear(); - } - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(34); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Circle"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; - -}; DEF_STD_CMD_A(CmdSketcherCreateCircle) @@ -2493,774 +613,6 @@ bool CmdSketcherCreateCircle::isActive(void) } // ====================================================================================== - -/** - * @brief This class handles user interaction to draw and save the ellipse - * - * Two construction methods are implemented: - * -Periapsis, apoapsis, and b; and - * -Center, periapsis, and b. - * - * The first method limits the ellipse to a circle, while the second method allows for - * swapping of the semi-major and semi-minor axes. - * - * We use three reference frames in this class. The first (and primary), is the cartesian - * frame of the sketcher; all our work begins and ends in this frame. The second is the - * perifocal frame of the ellipse using polar coordinates. We use this frame for naming - * conventions and working with the ellipse. The last is a rotated right-handed cartesian - * frame centered at the ellipse center with the +X direction towards periapsis, +Z out of - * screen. - * - * When working with an ellipse in the perifocal frame, the following equations are useful: - * - * \f{eqnarray*}{ - * r &\equiv& \textrm{ radial distance from the focus to a point on the ellipse}\\ - * r_a &\equiv& \textrm{ radial distance from the focus to apopasis}\\ - * r_p &\equiv& \textrm{ radial distance from the focus to periapsis}\\ - * a &\equiv& \textrm{ length of the semi-major axis, colloquially 'radius'}\\ - * b &\equiv& \textrm{ length of the semi-minor axis, colloquially 'radius'}\\ - * e &\equiv& \textrm{ eccentricity of the ellipse}\\ - * \theta_b &\equiv& \textrm{ angle to the intersection of the semi-minor axis and the ellipse, relative to the focus}\\ - * ae &\equiv& \textrm{ distance from the focus to the centroid}\\ - * r &=& \frac{a(1-e^2)}{1+e\cos(\theta)} = \frac{r_a(1-e)}{1+e\cos(\theta)} = \frac{r_p(1+e)}{1+e\cos(\theta)}\\ - * r_a &=& a(1-e)\\ - * r_p &=& a(1+e)\\ - * a &=& \frac{r_p+r_a}{2}\\ - * b &=& a\sqrt{1-e^2}\\ - * e &=& \frac{r_a-r_p}{r_a+r_p} = \sqrt{1-\frac{b^2}{a^2}}\\ - * \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N\pi - * \f} - * - */ -class DrawSketchHandlerEllipse : public DrawSketchHandler -{ -public: - DrawSketchHandlerEllipse(int constructionMethod) - : mode(STATUS_Close) - , method(CENTER_PERIAPSIS_B) - , constrMethod(constructionMethod) - , a(0), b(0), e(0), ratio(0), ae(0) - , num(0), r(0), theta(0), phi(0) - , editCurve(33), fixedAxisLength(0) - { - } - virtual ~DrawSketchHandlerEllipse(){} - /// Mode table, describes what step of the process we are in - enum SelectMode { - STATUS_SEEK_PERIAPSIS, /**< enum value, looking for click to set periapsis. */ - STATUS_SEEK_APOAPSIS, /**< enum value, looking for click to set apoapsis. */ - STATUS_SEEK_CENTROID, /**< enum value, looking for click to set centroid. */ - STATUS_SEEK_A, /**< enum value, looking for click to set a. */ - STATUS_SEEK_B, /**< enum value, looking for click to set b. */ - STATUS_Close /**< enum value, finalizing and saving ellipse. */ - }; - /// Construction methods, describes the method used to construct the ellipse - enum ConstructionMethod { - CENTER_PERIAPSIS_B, /**< enum value, click on center, then periapsis, then b point. */ - PERIAPSIS_APOAPSIS_B /**< enum value, click on periapsis, then apoapsis, then b point. */ - }; - - /** - * @brief Updates the ellipse when the cursor moves - * @param onSketchPos the position of the cursor on the sketch - */ - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (method == PERIAPSIS_APOAPSIS_B) { - if (mode == STATUS_SEEK_PERIAPSIS) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } else if (mode == STATUS_SEEK_APOAPSIS) { - solveEllipse(onSketchPos); - approximateEllipse(); - - // Display radius for user - float semiMajorRadius = a * 2; - SbString text; - text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius); - setPositionText(onSketchPos, text); - - drawEdit(editCurve); - // Suggestions for ellipse and curves are disabled because many tangent constraints - // need an intermediate point or line. - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } else if (mode == STATUS_SEEK_B) { - solveEllipse(onSketchPos); - approximateEllipse(); - - // Display radius for user - SbString text; - text.sprintf(" (%.1fR,%.1fR)", a, b); - setPositionText(onSketchPos, text); - - drawEdit(editCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - } else { // method is CENTER_PERIAPSIS_B - if (mode == STATUS_SEEK_CENTROID) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1 - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } else if (mode == STATUS_SEEK_PERIAPSIS) { - solveEllipse(onSketchPos); - approximateEllipse(); - - // Display radius for user - float semiMajorRadius = a * 2; - SbString text; - text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius); - setPositionText(onSketchPos, text); - - drawEdit(editCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centroid, - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } else if ((mode == STATUS_SEEK_A) || (mode == STATUS_SEEK_B)) { - solveEllipse(onSketchPos); - approximateEllipse(); - - // Display radius for user - SbString text; - text.sprintf(" (%.1fR,%.1fR)", a, b); - setPositionText(onSketchPos, text); - - drawEdit(editCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, onSketchPos - centroid, - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - } - applyCursor(); - } - - /** - * @brief Changes drawing mode on user-click - * @param onSketchPos the position of the cursor on the sketch - * @return - */ - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (method == PERIAPSIS_APOAPSIS_B) { - if (mode == STATUS_SEEK_PERIAPSIS) { - periapsis = onSketchPos; - mode = STATUS_SEEK_APOAPSIS; - } - else if (mode == STATUS_SEEK_APOAPSIS) { - apoapsis = onSketchPos; - mode = STATUS_SEEK_B; - } - else { - mode = STATUS_Close; - } - } else { // method is CENTER_PERIAPSIS_B - if (mode == STATUS_SEEK_CENTROID) { - centroid = onSketchPos; - mode = STATUS_SEEK_PERIAPSIS; - } - else if (mode == STATUS_SEEK_PERIAPSIS) { - periapsis = onSketchPos; - mode = STATUS_SEEK_B; - } - else { - mode = STATUS_Close; - } - } - return true; - } - - /** - * @brief Calls \c saveEllipse() after last user input - * @param onSketchPos the position of the cursor on the sketch - * @return - */ - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (mode == STATUS_Close) { - saveEllipse(); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); - - if(continuousMode){ - if (constrMethod == 0) { - method = CENTER_PERIAPSIS_B; - mode = STATUS_SEEK_CENTROID; - } else { - method = PERIAPSIS_APOAPSIS_B; - mode = STATUS_SEEK_PERIAPSIS; - } - } - } - return true; - } - -private: - /** - * @brief Slot called when the create ellipse command is activated - * @param sketchgui A pointer to the active sketch - */ - virtual void activated() override - { - if (constrMethod == 0) { - method = CENTER_PERIAPSIS_B; - mode = STATUS_SEEK_CENTROID; - } else { - method = PERIAPSIS_APOAPSIS_B; - mode = STATUS_SEEK_PERIAPSIS; - } - } - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Ellipse"); - } - -protected: - std::vector sugConstr1, sugConstr2, sugConstr3; -private: - SelectMode mode; - /// the method of constructing the ellipse - ConstructionMethod method; - int constrMethod; - /// periapsis position vector, in standard position in sketch coordinate system - Base::Vector2d periapsis; - /// apoapsis position vector, in standard position in sketch coordinate system - Base::Vector2d apoapsis; - /// centroid position vector, in standard position in sketch coordinate system - Base::Vector2d centroid; - /** - * @brief position vector of positive b point, in standard position in sketch coordinate system - * I.E. in polar perifocal system, the first intersection of the semiminor axis with the ellipse - * as theta increases from 0. This always happens when: - * \f{eqnarray*}{ - * \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N 2\pi - * \f} - * - * In a rotated R^3 cartesian system, centered at the centroid, +X towards periapsis, and - * +Z coming out of the sketch, this b position is in the +Y direction from the centroid. - */ - Base::Vector2d positiveB; - /// the other b position - Base::Vector2d negativeB; - /// cart. position vector for primary focus - Base::Vector2d f; - /// cart. position vector for other focus - Base::Vector2d fPrime; - /// Unit vector for apse line - Base::Vector2d apseHat; - /// length of semimajor axis, i.e. 'radius' colloquially - double a; - /// length of semiminor axis, i.e. 'radius' colloquially - double b; - /// eccentricity [unitless] - double e; - /// optimization, holds a term that helps calculate b in terms of a and e - double ratio; - /// holds product of a * e - double ae; - /// holds numerator of orbit equation of form a(1-e^2) - double num; - /// holds a radial distance from f to the ellipse for a given theta - double r; - /// angle of a point in a perifocal frame centered at f - double theta; - /// angle of apse line relative to sketch coordinate system - double phi; - /// holds a position vector for a point on the ellipse from f - Base::Vector2d pos; - /// holds a position vector for a point on the ellipse from fPrime - Base::Vector2d posPrime; - /// holds position vectors for a points on the ellipse - std::vector editCurve; - /// local i_hat vector for ellipse, from centroid to periapsis - Base::Vector3d iPrime; - /// local j_hat vector for ellipse, from centroid to b point - Base::Vector3d jPrime; - /// length (radius) of the fixed axis - double fixedAxisLength; - /// position vector of fixed axis point in sketch coordinates - Base::Vector2d fixedAxis; - - /** - * @brief Computes a vector of 2D points representing an ellipse - * @param onSketchPos Current position of the cursor on the sketch - */ - void solveEllipse(Base::Vector2d onSketchPos) - { - const double GOLDEN_RATIO = 1.6180339887; - Base::Vector3d k(0,0,1); - - if (method == PERIAPSIS_APOAPSIS_B) { - if (mode == STATUS_SEEK_APOAPSIS) { - apoapsis = onSketchPos; - } - a = (apoapsis - periapsis).Length() / 2; - apseHat = (periapsis - apoapsis); - apseHat.Normalize(); - centroid = apseHat; - centroid.Scale(-1 * a); - centroid = periapsis + centroid; - if (mode == STATUS_SEEK_APOAPSIS) { - // for first step, we draw an ellipse inscribed in a golden rectangle - ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887 - e = sqrt(ratio); // ~= 0.7861513777 - b = a * ratio; - } - else if (mode == STATUS_SEEK_B) { - // Get the closest distance from onSketchPos to apse line, as a 'requested' value for b - Base::Vector2d cursor = Base::Vector2d(onSketchPos - f); // vector from f to cursor pos - // decompose cursor with a projection, then length of w_2 will give us b - Base::Vector2d w_1 = cursor; - w_1.ProjectToLine(cursor, (periapsis - apoapsis)); // projection of cursor line onto apse line - Base::Vector2d w_2 = (cursor - w_1); - b = w_2.Length(); - - // limit us to ellipse or circles - if (b > a) { - b = a; - } - - e = sqrt(1 - ((b * b) / (a * a))); - ratio = sqrt(1 - (e*e)); - } - ae = a * e; - f = apseHat; - f.Scale(ae); - f = centroid + f; - fPrime = apseHat; - fPrime.Scale(-1 * ae); - fPrime = centroid + fPrime; - phi = atan2(apseHat.y, apseHat.x); - num = a * (1 - (e * e)); - // The ellipse is now solved - } else { // method == CENTER_PERIAPSIS_B - if (mode == STATUS_SEEK_PERIAPSIS) { - // solve the ellipse inscribed in a golden rectangle - periapsis = onSketchPos; - a = (centroid - periapsis).Length(); - iPrime.x = periapsis.x - centroid.x; - iPrime.y = periapsis.y - centroid.y; - iPrime.z = 0; - jPrime = k % iPrime; // j = k cross i - - // these are constant for any ellipse inscribed in a golden rectangle - ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887 - e = sqrt(ratio); // ~= 0.7861513777 - - b = a * ratio; - ae = a * e; - apseHat = (periapsis - centroid); - apseHat.Normalize(); - f = apseHat; - f.Scale(ae); - f = centroid + f; - fPrime = apseHat; - fPrime.Scale(-1 * ae); - fPrime = centroid + fPrime; - apoapsis = apseHat; - apoapsis.Scale(-1 * a); - apoapsis = centroid + apoapsis; - phi = atan2(apseHat.y, apseHat.x); - num = a * (1 - (e * e)); - fixedAxisLength = a; - fixedAxis = periapsis; - } else if ((mode == STATUS_SEEK_B) || (mode == STATUS_SEEK_A)) { - // while looking for the last click, we may switch back and forth - // between looking for a b point and looking for periapsis, so ensure - // we are in the right mode - Base::Vector2d cursor = Base::Vector2d(onSketchPos - centroid); // vector from centroid to cursor pos - // decompose cursor with a projection, then length of w_2 will give us b - Base::Vector2d w_1 = cursor; - w_1.ProjectToLine(cursor, (fixedAxis - centroid)); // projection of cursor line onto fixed axis line - Base::Vector2d w_2 = (cursor - w_1); - if (w_2.Length() > fixedAxisLength) { - // b is fixed, we are seeking a - mode = STATUS_SEEK_A; - jPrime.x = (fixedAxis - centroid).x; - jPrime.y = (fixedAxis - centroid).y; - jPrime.Normalize(); - iPrime = jPrime % k; // cross - b = fixedAxisLength; - a = w_2.Length(); - } else { - // a is fixed, we are seeking b - mode = STATUS_SEEK_B; - iPrime.x = (fixedAxis - centroid).x; - iPrime.y = (fixedAxis - centroid).y; - iPrime.Normalize(); - jPrime = k % iPrime; // cross - a = fixedAxisLength; - b = w_2.Length(); - } - // now finish solving the ellipse - periapsis.x = centroid.x + (iPrime * a).x; - periapsis.y = centroid.y + (iPrime * a).y; - e = sqrt(1 - ((b * b) / (a * a))); - ratio = sqrt(1 - (e*e)); - ae = a * e; - apseHat = (periapsis - centroid); - apseHat.Normalize(); - f = apseHat; - f.Scale(ae); - f = centroid + f; - fPrime = apseHat; - fPrime.Scale(-1 * ae); - fPrime = centroid + fPrime; - apoapsis = apseHat; - apoapsis.Scale(-1 * a); - apoapsis = centroid + apoapsis; - phi = atan2(apseHat.y, apseHat.x); - num = a * (1 - (e * e)); - } - } - } - - - /** - * @brief Computes a sequence of 2D vectors to approximate the ellipse - */ - void approximateEllipse() - { - // We will approximate the ellipse as a sequence of connected chords - // Number of points per quadrant of the ellipse - int n = static_cast((editCurve.size() - 1) / 4); - - // We choose points in the perifocal frame then translate them to sketch cartesian. - // This gives us a better approximation of an ellipse, i.e. more points where the - // curvature is higher. If the eccentricity is high, we shift the points a bit towards - // the semi-minor axis. - double partitionAngle = (M_PI - atan2(b, ae)) / n; - double radianShift = 0; - if (e > 0.8) {radianShift = (partitionAngle / 5) * 4;} - for (int i=0; i < n; i++) { - theta = i * partitionAngle; - if (i > 0) {theta = theta + radianShift;} - r = num / (1 + (e * cos(theta))); - // r(pi/2) is semi-latus rectum, if we need it - pos.x = r*cos(theta+phi); // phi rotates, sin/cos translate - pos.y = r*sin(theta+phi); - pos = pos + f; - posPrime.x = r*cos(theta+phi+M_PI); - posPrime.y = r*sin(theta+phi+M_PI); - posPrime = posPrime + fPrime; - // over the loop, loads Quadrant I points, by using f as origin - editCurve[i] = pos; - // over the loop, loads Quadrant III points, by using fPrime as origin - editCurve[(2*n) + i] = posPrime; - // load points with negative theta angles (i.e. cw) - if (i>0) { - pos.x = r*cos(-1*theta+phi); - pos.y = r*sin(-1*theta+phi); - pos = pos + f; - // loads Quadrant IV points - editCurve[(4*n) - i] = pos; - posPrime.x = r*cos(-1*theta+phi+M_PI); - posPrime.y = r*sin(-1*theta+phi+M_PI); - posPrime = posPrime + fPrime; - // loads Quadrant II points - editCurve[(2*n) - i] = posPrime; - } - } - // load pos & neg b points - theta = M_PI - atan2(b, ae); // the angle from f to the positive b point - r = num / (1 + (e * cos(theta))); - pos.x = r*cos(theta+phi); - pos.y = r*sin(theta+phi); - pos = pos + f; - editCurve[n] = pos; // positive - pos.x = r*cos(-1*theta+phi); - pos.y = r*sin(-1*theta+phi); - pos = pos + f; - editCurve[(3*n)] = pos; // negative - // force the curve to be a closed shape - editCurve[(4*n)] = editCurve[0]; - } - - /** - * @brief Prints the ellipse data to STDOUT as an GNU Octave script - * @param onSketchPos position of the cursor on the sketch - */ - void ellipseToOctave(Base::Vector2d /*onSketchPos*/) - { - int n = static_cast((editCurve.size() - 1) / 4); - - // send a GNU Octave script to stdout to plot points for debugging - std::ostringstream octave; - octave << std::fixed << std::setprecision(12); - octave << "\nclear all;\nclose all;\nclc;\n\n"; - octave << "periapsis = [" << periapsis.x << ", " << periapsis.y << "];\n"; - octave << "apoapsis = [" << apoapsis.x << ", " << apoapsis.y << "];\n"; - octave << "positiveB = [" << editCurve[n].x << ", " << editCurve[n].y << "];\n"; - octave << "apseHat = [" << apseHat.x << ", " << apseHat.y << "];\n"; - octave << "a = " << a << ";\n"; - octave << "b = " << b << ";\n"; - octave << "eccentricity = " << e << ";\n"; - octave << "centroid = [" << centroid.x << ", " << centroid.y << "];\n"; - octave << "f = [" << f.x << ", " << f.y << "];\n"; - octave << "fPrime = [" << fPrime.x << ", " << fPrime.y << "];\n"; - octave << "phi = " << phi << ";\n\n"; - octave << "x = ["; - for (int i=0; i < 4*n + 1; i++) { - octave << editCurve[i].x; - if (i < 4*n) { - octave << ", "; - } - } - octave << "];\n"; - octave << "y = ["; - for (int i=0; i < 4*n + 1; i++) { - octave << editCurve[i].y; - if (i < 4*n) { - octave << ", "; - } - } - octave << "];\n\n"; - octave << "% Draw ellipse points in red;\n"; - octave << "plot (x, y, \"r.\", \"markersize\", 5);\n"; - octave << "axis ([-300, 300, -300, 300], \"square\");grid on;\n"; - octave << "hold on;\n\n"; - octave << "% Draw centroid in blue, f in cyan, and fPrime in magenta;\n"; - octave << "plot(centroid(1), centroid(2), \"b.\", \"markersize\", 5);\n"; - octave << "plot(f(1), f(2), \"c.\", \"markersize\", 5);\n"; - octave << "plot(fPrime(1), fPrime(2), \"m.\", \"markersize\", 5);\n"; - octave << "n = [periapsis(1) - f(1), periapsis(2) - f(2)];\n"; - octave << "h = quiver(f(1),f(2),n(1),n(2), 0);\n"; - octave << "set (h, \"maxheadsize\", 0.1);\n\n"; - octave << "% Draw the three position vectors used for Gui::Command::doCommand(...)\n"; - octave << "periapsisVec = quiver(0,0,periapsis(1),periapsis(2), 0);\n"; - octave << "set (periapsisVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n"; - octave << "centroidVec = quiver(0,0,centroid(1),centroid(2), 0);\n"; - octave << "set (centroidVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n"; - octave << "bVec = quiver(0,0,positiveB(1),positiveB(2), 0);\n"; - octave << "set (bVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n\n"; - octave << "% Draw the local x & y basis vectors, scaled to a and b, in red and blue, respectively\n"; - octave << "xLocalVec = quiver(centroid(1),centroid(2),periapsis(1)-centroid(1),periapsis(2)-centroid(2), 0);\n"; - octave << "set (xLocalVec, \"maxheadsize\", 0.01, \"color\", \"red\");\n"; - octave << "yLocalVec = quiver(centroid(1),centroid(2), positiveB(1)-centroid(1), positiveB(2)-centroid(2), 0);\n"; - octave << "set (yLocalVec, \"maxheadsize\", 0.01, \"color\", \"blue\");\nhold off;\n"; - qDebug() << QString::fromStdString(octave.str()); - } - - /** - * @brief Finalizes and saves the drawn ellipse - * @return nothing - */ - void saveEllipse() - { - unsetCursor(); - resetPositionText(); - - /* There are a couple of issues with Gui::Command::doCommand(...) and - * GC_MakeEllipse(...) that cause bugs if not handled properly, even - * when we give them a mathematically-correct ellipse. - * - * GC_MakeEllipse may fail with a gce_InvertAxis error for a small - * circular ellipse when floating point roundoff or representation - * errors make the b axis slightly larger than the a axis. - * - * A similar, larger, issue arises in Gui::Command::doCommand(...) because - * we cast our double vector components into strings with a fixed - * precision of six, and then create new doubles from the strings - * in EllipsePy::PyInit(...). Thus, by the time we call GC_MakeEllipse(...) - * in EllipsePy::PyInit(...), our ellipse may not be valid anymore - * because b is now greater than a. - * - * To handle these issues, we simulate the effects Gui::Command::doCommand(...) - * has on our ellipse, and we adjust our ellipse parameters until - * GC_MakeEllipse successfully creates an ellipse with our mangled - * parameters. - * - * In almost all cases, we only have to make our test ellipse one time; - * it is only in the rare edge cases that require repeated test ellipses - * until we get a valid one, or fail due to excessive attempts. With a - * limit of 25 attempts, I have been unable to make it fail. - */ - - // simulate loss of precision in centroid, periapsis, and apoapsis - char cx[64]; - char cy[64]; - char px[64]; - char py[64]; - char ax[64]; - char ay[64]; - sprintf(cx, "%.6lf\n", centroid.x); - sprintf(cy, "%.6lf\n", centroid.y); - sprintf(px, "%.6lf\n", periapsis.x); - sprintf(py, "%.6lf\n", periapsis.y); - sprintf(ax, "%.6lf\n", apoapsis.x); - sprintf(ay, "%.6lf\n", apoapsis.y); - centroid.x = atof(cx); - centroid.y = atof(cy); - periapsis.x = atof(px); - periapsis.y = atof(py); - apoapsis.x = atof(ax); - apoapsis.y = atof(ay); - double majorLength = (periapsis - apoapsis).Length(); - double minorLength = 0; - - /* GC_MakeEllipse requires a right-handed coordinate system, with +X - * from centroid to periapsis, +Z out of the page. - */ - Base::Vector3d k(0,0,1); - Base::Vector3d i(periapsis.x - centroid.x, periapsis.y - centroid.y, 0); - Base::Vector3d j = k % i; // j = k cross i - double beta = 1e-7; - int count = 0; - int limit = 25; // no infinite loops! - bool success = false; - double tempB = b; - - // adjust b until our mangled vectors produce a good ellipse in GC_MakeEllipse - // and the mangled major and minor lines in LinePy::PyInit(...) are such that - // major is at least slightly larger than minor - do { - tempB = b - double(count * beta); - j = j.Normalize() * tempB; - positiveB.x = centroid.x + j.x; - positiveB.y = centroid.y + j.y; - negativeB.x = centroid.x + (j.x * -1); - negativeB.y = centroid.y + (j.y * -1); - char bpx[64]; - char bpy[64]; - char bnx[64]; - char bny[64]; - sprintf(bpx, "%.6lf\n", positiveB.x); - sprintf(bpy, "%.6lf\n", positiveB.y); - sprintf(bnx, "%.6lf\n", negativeB.x); - sprintf(bny, "%.6lf\n", negativeB.y); - positiveB.x = atof(bpx); - positiveB.y = atof(bpy); - negativeB.x = atof(bnx); - negativeB.y = atof(bny); - GC_MakeEllipse me(gp_Pnt(periapsis.x,periapsis.y,0), - gp_Pnt(positiveB.x,positiveB.y,0), - gp_Pnt(centroid.x,centroid.y,0)); - minorLength = (negativeB - positiveB).Length(); - count++; - success = me.IsDone() && (minorLength + beta < majorLength); - } while (!success && (count <= limit)); - if (!success) { - qDebug() << "Failed to create a valid mangled ellipse after" << count << "attempts"; - } - - // save any changes to b, then recalculate ellipse as required due to change in b - b = tempB; - e = sqrt(1 - ((b * b) / (a * a))); - ae = a * e; - f = apseHat; - f.Scale(ae); - f = centroid + f; - fPrime = apseHat; - fPrime.Scale(-1 * ae); - fPrime = centroid + fPrime; - - int currentgeoid = getHighestCurveIndex(); // index of the ellipse we just created - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch ellipse")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Ellipse" - "(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", - periapsis.x, periapsis.y, - positiveB.x, positiveB.y, - centroid.x, centroid.y, - geometryCreationMode==Construction?"True":"False"); - - currentgeoid++; - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - return; - } - - Gui::Command::commitCommand(); - - if (method == CENTER_PERIAPSIS_B) { - // add auto constraints for the center point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); - sugConstr1.clear(); - } - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); - sugConstr2.clear(); - } - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, currentgeoid, Sketcher::PointPos::none); - sugConstr3.clear(); - } - } - - if (method == PERIAPSIS_APOAPSIS_B) { - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::none); - sugConstr1.clear(); - } - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); - sugConstr2.clear(); - } - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, currentgeoid, Sketcher::PointPos::none); - sugConstr3.clear(); - } - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - // This code enables the continuous creation mode. - if (constrMethod == 0) { - method = CENTER_PERIAPSIS_B; - mode = STATUS_SEEK_CENTROID; - } else { - method = PERIAPSIS_APOAPSIS_B; - mode = STATUS_SEEK_PERIAPSIS; - } - editCurve.clear(); - drawEdit(editCurve); - - 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. - editCurve.resize(33); - applyCursor(); - /* It is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - - } -}; - /// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseByCenter' DEF_STD_CMD_A(CmdSketcherCreateEllipseByCenter) @@ -3323,309 +675,6 @@ bool CmdSketcherCreateEllipseBy3Points::isActive(void) return isCommandActive(getActiveGuiDocument()); } -class DrawSketchHandlerArcOfEllipse : public DrawSketchHandler -{ -public: - DrawSketchHandlerArcOfEllipse() - : Mode(STATUS_SEEK_First), EditCurve(34) - , rx(0), ry(0), startAngle(0), endAngle(0) - , arcAngle(0), arcAngle_t(0) - { - } - virtual ~DrawSketchHandlerArcOfEllipse(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_SEEK_Fourth, /**< enum value ----. */ - STATUS_Close - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1 - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - double rx0 = onSketchPos.x - EditCurve[0].x; - double ry0 = onSketchPos.y - EditCurve[0].y; - for (int i=0; i < 16; i++) { - double angle = i*M_PI/16.0; - double rx1 = rx0 * cos(angle) + ry0 * sin(angle); - double ry1 = -rx0 * sin(angle) + ry0 * cos(angle); - EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1); - EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1); - } - EditCurve[33] = EditCurve[1]; - - // Display radius for user - float radius = (onSketchPos - EditCurve[0]).Length(); - - SbString text; - text.sprintf(" (%.1fR,%.1fR)", radius,radius); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centerPoint, - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else if (Mode==STATUS_SEEK_Third) { - // angle between the major axis of the ellipse and the X axis - double a = (EditCurve[1]-EditCurve[0]).Length(); - double phi = atan2(EditCurve[1].y-EditCurve[0].y,EditCurve[1].x-EditCurve[0].x); - - // This is the angle at cursor point - double angleatpoint = acos((onSketchPos.x-EditCurve[0].x+(onSketchPos.y-EditCurve[0].y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); - double b=(onSketchPos.y-EditCurve[0].y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi)); - - for (int i=1; i < 16; i++) { - double angle = i*M_PI/16.0; - double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi); - double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi); - EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1); - EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1); - } - EditCurve[33] = EditCurve[1]; - EditCurve[17] = EditCurve[16]; - - // Display radius for user - SbString text; - text.sprintf(" (%.1fR,%.1fR)", a, b); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - else if (Mode==STATUS_SEEK_Fourth) { // here we differ from ellipse creation - // angle between the major axis of the ellipse and the X axis - double a = (axisPoint-centerPoint).Length(); - double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); - - // This is the angle at cursor point - double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); - double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi))); - - double rxs = startingPoint.x - centerPoint.x; - double rys = startingPoint.y - centerPoint.y; - startAngle = atan2(a*(rys*cos(phi)-rxs*sin(phi)), b*(rxs*cos(phi)+rys*sin(phi))); // eccentric anomaly angle - - double angle1 = atan2(a*((onSketchPos.y - centerPoint.y)*cos(phi)-(onSketchPos.x - centerPoint.x)*sin(phi)), - b*((onSketchPos.x - centerPoint.x)*cos(phi)+(onSketchPos.y - centerPoint.y)*sin(phi)))- startAngle; - - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; - - for (int i=0; i < 34; i++) { - double angle = startAngle+i*arcAngle/34.0; - double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi); - double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi); - EditCurve[i] = Base::Vector2d(centerPoint.x + rx1, centerPoint.y + ry1); - } -// EditCurve[33] = EditCurve[1]; -// EditCurve[17] = EditCurve[16]; - - // Display radii and angle for user - SbString text; - text.sprintf(" (%.1fR,%.1fR,%.1fdeg)", a, b, arcAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr4); - return; - } - } - - - - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - EditCurve[0] = onSketchPos; - centerPoint = onSketchPos; - Mode = STATUS_SEEK_Second; - } - else if(Mode==STATUS_SEEK_Second) { - EditCurve[1] = onSketchPos; - axisPoint = onSketchPos; - Mode = STATUS_SEEK_Third; - } - else if(Mode==STATUS_SEEK_Third) { - startingPoint = onSketchPos; - arcAngle = 0.; - arcAngle_t= 0.; - Mode = STATUS_SEEK_Fourth; - } - else { // Fourth - endPoint = onSketchPos; - - Mode = STATUS_Close; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_Close) { - unsetCursor(); - resetPositionText(); - - // angle between the major axis of the ellipse and the X axisEllipse - double a = (axisPoint-centerPoint).Length(); - double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); - - // This is the angle at cursor point - double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); - double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi))); - - double angle1 = atan2(a*((endPoint.y - centerPoint.y)*cos(phi)-(endPoint.x - centerPoint.x)*sin(phi)), - b*((endPoint.x - centerPoint.x)*cos(phi)+(endPoint.y - centerPoint.y)*sin(phi)))- startAngle; - - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; - - bool isOriginalArcCCW=true; - - if (arcAngle > 0) - endAngle = startAngle + arcAngle; - else { - endAngle = startAngle; - startAngle += arcAngle; - isOriginalArcCCW=false; - } - - Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint; - // We always create a CCW ellipse, because we want our XY reference system to be in the +X +Y direction - // Our normal will then always be in the +Z axis (local +Z axis of the sketcher) - - if(a>b) - { - // force second semidiameter to be perpendicular to first semidiamater - majAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(-majAxisDir.y,majAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - minAxisPoint = centerPoint+perp; - majAxisPoint = centerPoint+majAxisDir; - } - else { - // force second semidiameter to be perpendicular to first semidiamater - minAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(minAxisDir.y,-minAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - majAxisPoint = centerPoint+perp; - minAxisPoint = centerPoint+minAxisDir; - endAngle += M_PI/2; - startAngle += M_PI/2; - phi-=M_PI/2; - double t=a; a=b; b=t;//swap a,b - } - - int currentgeoid = getHighestCurveIndex(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of ellipse")); - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfEllipse" - "(Part.Ellipse(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)", - majAxisPoint.x, majAxisPoint.y, - minAxisPoint.x, minAxisPoint.y, - centerPoint.x, centerPoint.y, - startAngle, endAngle, - geometryCreationMode==Construction?"True":"False"); - - currentgeoid++; - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - return false; - } - - Gui::Command::commitCommand(); - - // add auto constraints for the center point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // add suggested constraints for arc - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); - sugConstr2.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); - sugConstr3.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr4.size() > 0) { - createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); - sugConstr4.clear(); - } - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(34); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfEllipse"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint; - double rx, ry, startAngle, endAngle, arcAngle, arcAngle_t; - std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; -}; - DEF_STD_CMD_A(CmdSketcherCreateArcOfEllipse) CmdSketcherCreateArcOfEllipse::CmdSketcherCreateArcOfEllipse() @@ -3653,325 +702,6 @@ bool CmdSketcherCreateArcOfEllipse::isActive(void) return isCommandActive(getActiveGuiDocument()); } -class DrawSketchHandlerArcOfHyperbola : public DrawSketchHandler -{ -public: - DrawSketchHandlerArcOfHyperbola() - : Mode(STATUS_SEEK_First) - , EditCurve(34) - , arcAngle(0) - , arcAngle_t(0) - { - } - virtual ~DrawSketchHandlerArcOfHyperbola(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_SEEK_Fourth, /**< enum value ----. */ - STATUS_Close - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - EditCurve[1]= onSketchPos; - - // Display radius for user - float radius = (onSketchPos - centerPoint).Length(); - - SbString text; - text.sprintf(" (%.1fR,%.1fR)", radius,radius); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else if (Mode==STATUS_SEEK_Third) { - // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint-centerPoint).Length(); - double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); - - // This is the angle at cursor point - double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); - double b=(onSketchPos.y-centerPoint.y-a*cosh(angleatpoint)*sin(phi))/(sinh(angleatpoint)*cos(phi)); - - if(!boost::math::isnan(b)){ - for (int i=15; i >= -15; i--) { - // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir - //double angle = i*M_PI/16.0; - double angle=i*angleatpoint/15; - double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); - double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); - EditCurve[15+i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); - } - - // Display radius for user - SbString text; - text.sprintf(" (%.1fR,%.1fR)", a, b); - setPositionText(onSketchPos, text); - } - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - else if (Mode==STATUS_SEEK_Fourth) { - // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint-centerPoint).Length(); - double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); - - // This is the angle at cursor point - double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a); - double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi)); - - double startAngle = angleatstartingpoint; - - //double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); - - double angleatpoint = atanh( (((onSketchPos.y-centerPoint.y)*cos(phi)-(onSketchPos.x-centerPoint.x)*sin(phi))*a) / - (((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))*b) ); - - /*double angle1 = angleatpoint - startAngle; - - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;*/ - - arcAngle = angleatpoint - startAngle; - - //if(!boost::math::isnan(angle1) && !boost::math::isnan(angle2)){ - if (!boost::math::isnan(arcAngle)) { - EditCurve.resize(33); - for (int i=0; i < 33; i++) { - // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir - //double angle=i*angleatpoint/16; - double angle = startAngle+i*arcAngle/32.0; - double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); - double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); - EditCurve[i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); - } - - // Display radius for user - SbString text; - text.sprintf(" (%.1fR,%.1fR)", a, b); - setPositionText(onSketchPos, text); - } - else { - arcAngle=0.; - } - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr4); - return; - } - } - - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - EditCurve[0] = onSketchPos; - centerPoint = onSketchPos; - EditCurve.resize(2); - Mode = STATUS_SEEK_Second; - } - else if(Mode==STATUS_SEEK_Second) { - EditCurve[1] = onSketchPos; - axisPoint = onSketchPos; - EditCurve.resize(31); - Mode = STATUS_SEEK_Third; - } - else if(Mode==STATUS_SEEK_Third) { - startingPoint = onSketchPos; - arcAngle = 0.; - arcAngle_t= 0.; - Mode = STATUS_SEEK_Fourth; - } - else { // Fourth - endPoint = onSketchPos; - - Mode = STATUS_Close; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d /*onSketchPos*/) override - { - if (Mode==STATUS_Close) { - unsetCursor(); - resetPositionText(); - - - // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint-centerPoint).Length(); - double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); - - // This is the angle at cursor point - double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a); - double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi)); - - double startAngle = angleatstartingpoint; - - //double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); - - double endAngle = atanh( (((endPoint.y-centerPoint.y)*cos(phi)-(endPoint.x-centerPoint.x)*sin(phi))*a) / - (((endPoint.x-centerPoint.x)*cos(phi)+(endPoint.y-centerPoint.y)*sin(phi))*b) ); - - if (boost::math::isnan(startAngle) || boost::math::isnan(endAngle)) { - sketchgui->purgeHandler(); - Base::Console().Error("Cannot create arc of hyperbola from invalid angles, try again!\n"); - return false; - } - - - bool isOriginalArcCCW=true; - - if (arcAngle > 0) - endAngle = startAngle + arcAngle; - else { - endAngle = startAngle; - startAngle += arcAngle; - isOriginalArcCCW=false; - } - - Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint; - // We always create a CCW hyperbola, because we want our XY reference system to be in the +X +Y direction - // Our normal will then always be in the +Z axis (local +Z axis of the sketcher) - - if(a>b) - { - // force second semidiameter to be perpendicular to first semidiamater - majAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(-majAxisDir.y,majAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - minAxisPoint = centerPoint+perp; - majAxisPoint = centerPoint+majAxisDir; - } - else { - // force second semidiameter to be perpendicular to first semidiamater - minAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(minAxisDir.y,-minAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - majAxisPoint = centerPoint+perp; - minAxisPoint = centerPoint+minAxisDir; - endAngle += M_PI/2; - startAngle += M_PI/2; - } - - int currentgeoid = getHighestCurveIndex(); - - try { - - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of hyperbola")); - - //Add arc of hyperbola, point and constrain point as focus2. We add focus2 for it to balance - //the intrinsic focus1, in order to balance out the intrinsic invisible focus1 when AOE is - //dragged by its center - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfHyperbola" - "(Part.Hyperbola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)", - majAxisPoint.x, majAxisPoint.y, - minAxisPoint.x, minAxisPoint.y, - centerPoint.x, centerPoint.y, - startAngle, endAngle, - geometryCreationMode==Construction?"True":"False"); - - currentgeoid++; - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - return false; - } - - Gui::Command::commitCommand(); - - // add auto constraints for the center point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // add suggested constraints for arc - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); - sugConstr2.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); - sugConstr3.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr4.size() > 0) { - createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); - sugConstr4.clear(); - } - - 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. - Mode = STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(34); - applyCursor(); - /* It is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfHyperbola"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint; - double arcAngle, arcAngle_t; - std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; - -}; - DEF_STD_CMD_A(CmdSketcherCreateArcOfHyperbola) CmdSketcherCreateArcOfHyperbola::CmdSketcherCreateArcOfHyperbola() @@ -3998,280 +728,6 @@ bool CmdSketcherCreateArcOfHyperbola::isActive(void) return isCommandActive(getActiveGuiDocument()); } -class DrawSketchHandlerArcOfParabola : public DrawSketchHandler -{ -public: - DrawSketchHandlerArcOfParabola() - : Mode(STATUS_SEEK_First) - , EditCurve(34) - , startAngle(0) - , endAngle(0) - , arcAngle(0) - , arcAngle_t(0) - { - } - virtual ~DrawSketchHandlerArcOfParabola(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_SEEK_Fourth, /**< enum value ----. */ - STATUS_Close - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - EditCurve[1]= onSketchPos; - - // Display radius for user - float radius = (onSketchPos - focusPoint).Length(); - - SbString text; - text.sprintf(" (F%.1f)", radius); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else if (Mode==STATUS_SEEK_Third) { - double focal = (axisPoint-focusPoint).Length(); - double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); - - // P(U) = O + U*U/(4.*F)*XDir + U*YDir - // - // pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi), - // pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi), - // 0.f); - - // This is the angle at cursor point - double u = - ( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi)); - - for (int i=15; i >= -15; i--) { - double angle=i*u/15; - double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi); - double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi); - EditCurve[15+i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry); - } - - // Display radius for user - SbString text; - text.sprintf(" (F%.1f)", focal); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - else if (Mode==STATUS_SEEK_Fourth) { - double focal = (axisPoint-focusPoint).Length(); - double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); - - // P(U) = O + U*U/(4.*F)*XDir + U*YDir - // - // pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi), - // pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi), - // 0.f); - - // This is the angle at starting point - double ustartpoint = - ( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi)); - - double startValue = ustartpoint; - - double u = - ( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi)); - - - arcAngle = u - startValue; - - if (!boost::math::isnan(arcAngle)) { - EditCurve.resize(33); - for (std::size_t i=0; i < 33; i++) { - double angle = startValue+i*arcAngle/32.0; - double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi); - double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi); - EditCurve[i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry); - } - - SbString text; - text.sprintf(" (F%.1f)", focal); - setPositionText(onSketchPos, text); - } - else { - arcAngle=0.; - } - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr4); - return; - } - } - - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - EditCurve[0] = onSketchPos; - focusPoint = onSketchPos; - EditCurve.resize(2); - Mode = STATUS_SEEK_Second; - } - else if(Mode==STATUS_SEEK_Second) { - EditCurve[1] = onSketchPos; - axisPoint = onSketchPos; - EditCurve.resize(31); - Mode = STATUS_SEEK_Third; - } - else if(Mode==STATUS_SEEK_Third) { - startingPoint = onSketchPos; - arcAngle = 0.; - arcAngle_t= 0.; - Mode = STATUS_SEEK_Fourth; - } - else { // Fourth - endPoint = onSketchPos; - Mode = STATUS_Close; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d /*onSketchPos*/) override - { - if (Mode==STATUS_Close) { - unsetCursor(); - resetPositionText(); - - double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); - - double ustartpoint = - ( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi)); - - double uendpoint = - ( cos(phi) * (endPoint.y - axisPoint.y) - (endPoint.x - axisPoint.x) * sin(phi)); - - double startAngle = ustartpoint; - - double endAngle = uendpoint; - - bool isOriginalArcCCW=true; - - if (arcAngle > 0) { - endAngle = startAngle + arcAngle; - } - else { - endAngle = startAngle; - startAngle += arcAngle; - isOriginalArcCCW=false; - } - - int currentgeoid = getHighestCurveIndex(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of Parabola")); - - //Add arc of parabola - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfParabola" - "(Part.Parabola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(0,0,1)),%f,%f),%s)", - focusPoint.x, focusPoint.y, - axisPoint.x, axisPoint.y, - startAngle, endAngle, - geometryCreationMode==Construction?"True":"False"); - - currentgeoid++; - - Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - return false; - } - - Gui::Command::commitCommand(); - - // add auto constraints for the focus point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, currentgeoid+1, Sketcher::PointPos::start); - sugConstr1.clear(); - } - - // add suggested constraints for vertex point - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::mid); - sugConstr2.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); - sugConstr3.clear(); - } - - // add suggested constraints for start of arc - if (sugConstr4.size() > 0) { - createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); - sugConstr4.clear(); - } - - 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. - Mode = STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(34); - applyCursor(); - /* It is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else { - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfParabola"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d focusPoint, axisPoint, startingPoint, endPoint; - double startAngle, endAngle, arcAngle, arcAngle_t; - std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; -}; - DEF_STD_CMD_A(CmdSketcherCreateArcOfParabola) CmdSketcherCreateArcOfParabola::CmdSketcherCreateArcOfParabola() @@ -4446,475 +902,6 @@ bool CmdSketcherCompCreateConic::isActive(void) // ====================================================================================== -class DrawSketchHandlerBSpline: public DrawSketchHandler -{ -public: - DrawSketchHandlerBSpline(int constructionMethod) - : Mode(STATUS_SEEK_FIRST_CONTROLPOINT) - , MousePressMode(MOUSE_NOT_PRESSED) - , ConstrMethod(constructionMethod) - , SplineDegree(3) - , IsClosed(false) - { - addSugConstraint(); - applyCursor(); - } - - virtual ~DrawSketchHandlerBSpline() {} - /// 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 - }; - - virtual 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) { - - drawControlPolygonToPosition(onSketchPos); - - drawCursorToPosition(onSketchPos); - - if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr.back()); - return; - } - } - } - - virtual 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& e) { - Base::Console().Error("%s\n", e.what()); - 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().size() > 0) { - 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 && ac.GeoId == poleGeoIds[0] && ac.PosId == 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& e) { - Base::Console().Error("%s\n", e.what()); - 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().size() > 0) { - createAutoConstraints(sugConstr.back(), poleGeoIds.back(), Sketcher::PointPos::mid, false); - } - - //static_cast(sketchgui->getObject())->solve(); - - if (!IsClosed) { - addSugConstraint(); - } - - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - prevCursorPosition = onSketchPos; - MousePressMode = MOUSE_NOT_PRESSED; - - return finishCommand(onSketchPos); - } - - virtual 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 - drawControlPolygonToPosition(prevCursorPosition); - drawCursorToPosition(prevCursorPosition); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - // 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 - - return; - } - - virtual void quit(void) 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() - { - Mode = STATUS_SEEK_FIRST_CONTROLPOINT; - applyCursor(); - - SplineDegree = 3; - - sugConstr.clear(); - poleGeoIds.clear(); - BSplinePoles.clear(); - - eraseEditCurve(); - - addSugConstraint(); - - IsClosed = false; - } - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_BSpline"); - } - - void addSugConstraint() { - 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 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)); - - SbString text; - text.sprintf(" (%.1f,%.1fdeg)", length, (angle != -FLOAT_MAX) ? angle * 180 / M_PI : 0); - 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(); - - 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", - SplineDegree, - geometryCreationMode==Construction?"True":"False"); - - 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] && constr->FirstPos == Sketcher::PointPos::mid) { - constr->First = currentgeoid; - constr->FirstPos = Sketcher::PointPos::start; - } - else if(constr->First == poleGeoIds.back() && constr->FirstPos == Sketcher::PointPos::mid) { - constr->First = currentgeoid; - constr->FirstPos = Sketcher::PointPos::end; - } - } - } - - // Constraint pole circles to B-spline. - std::stringstream cstream; - - cstream << "conList = []\n"; - - for (size_t i = 0; i < poleGeoIds.size(); i++) { - cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint'," << poleGeoIds[0] + i - << "," << static_cast(Sketcher::PointPos::mid) << "," << currentgeoid << "," << i << "))\n"; - } - - 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 knots on creation - Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); - } - catch (const Base::Exception& e) { - Base::Console().Error("%s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - 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 poles of the BSpline. - std::vector BSplinePoles; - - // suggested autoconstraints for poles. - // A new one must be added e.g. using addSugConstraint() before adding a new pole. - std::vector> sugConstr; - - int ConstrMethod; - int SplineDegree; - bool IsClosed; - std::vector poleGeoIds; - Base::Vector2d prevCursorPosition; -}; - DEF_STD_CMD_A(CmdSketcherCreateBSpline) CmdSketcherCreateBSpline::CmdSketcherCreateBSpline() @@ -5101,182 +1088,6 @@ bool CmdSketcherCompCreateBSpline::isActive(void) // ====================================================================================== -class DrawSketchHandler3PointCircle : public DrawSketchHandler -{ -public: - DrawSketchHandler3PointCircle() - : Mode(STATUS_SEEK_First),EditCurve(2),radius(1),N(32.0){} - virtual ~DrawSketchHandler3PointCircle(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_SEEK_Third, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode == STATUS_SEEK_Second || Mode == STATUS_SEEK_Third) { - try - { - if (Mode == STATUS_SEEK_Second) - CenterPoint = EditCurve[N+1] = (onSketchPos - FirstPoint)/2 + FirstPoint; - else - CenterPoint = EditCurve[N+1] = Part::Geom2dCircle::getCircleCenter(FirstPoint, SecondPoint, onSketchPos); - radius = (onSketchPos - CenterPoint).Length(); - double lineAngle = GetPointAngle(CenterPoint, onSketchPos); - - // Build a N point circle - for (int i=1; i < N; i++) { - // Start at current angle - double angle = i*2*M_PI/N + lineAngle; // N point closed circle has N segments - EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), - CenterPoint.y + radius*sin(angle)); - } - // Beginning and end of curve should be exact - EditCurve[0] = EditCurve[N] = onSketchPos; - - // Display radius and start angle - // This lineAngle will report counter-clockwise from +X, not relatively - SbString text; - text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (Mode == STATUS_SEEK_Second) { - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - else { - if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f), - AutoConstraint::CURVE)) { - renderSuggestConstraintsCursor(sugConstr3); - return; - } - } - } - catch(Base::ValueError &e) { - e.ReportException(); - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_SEEK_First) { - // N point curve + center + endpoint - EditCurve.resize(N+2); - FirstPoint = onSketchPos; - - Mode = STATUS_SEEK_Second; - } - else if (Mode == STATUS_SEEK_Second) { - SecondPoint = onSketchPos; - - Mode = STATUS_SEEK_Third; - } - else { - EditCurve.resize(N); - - drawEdit(EditCurve); - applyCursor(); - Mode = STATUS_End; - } - - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - // Need to look at. rx might need fixing. - if (Mode==STATUS_End) { - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle" - "(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)", - CenterPoint.x, CenterPoint.y, - radius, - geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add circle: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - // Auto Constraint first picked point - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::none); - sugConstr1.clear(); - } - - // Auto Constraint second picked point - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::none); - sugConstr2.clear(); - } - - // Auto Constraint third picked point - if (sugConstr3.size() > 0) { - createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::PointPos::none); - sugConstr3.clear(); - } - - 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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_3PointCircle"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - Base::Vector2d CenterPoint, FirstPoint, SecondPoint; - double radius, N; // N should be even - std::vector sugConstr1, sugConstr2, sugConstr3; -}; - DEF_STD_CMD_A(CmdSketcherCreate3PointCircle) CmdSketcherCreate3PointCircle::CmdSketcherCreate3PointCircle() @@ -5407,84 +1218,6 @@ bool CmdSketcherCompCreateCircle::isActive(void) // ====================================================================================== -class DrawSketchHandlerPoint: public DrawSketchHandler -{ -public: - DrawSketchHandlerPoint() : selectionDone(false) {} - virtual ~DrawSketchHandlerPoint() {} - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr); - return; - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - EditPoint = onSketchPos; - selectionDone = true; - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (selectionDone){ - unsetCursor(); - resetPositionText(); - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch point")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Point(App.Vector(%f,%f,0)))", - EditPoint.x,EditPoint.y); - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add point: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - // add auto constraints for the line segment start - if (sugConstr.size() > 0) { - createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::PointPos::start); - sugConstr.clear(); - } - - 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. - applyCursor(); - /* It is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Create_Point"); - } - -protected: - bool selectionDone; - Base::Vector2d EditPoint; - std::vector sugConstr; -}; DEF_STD_CMD_A(CmdSketcherCreatePoint) @@ -5515,260 +1248,6 @@ bool CmdSketcherCreatePoint::isActive(void) // ====================================================================================== -namespace SketcherGui { - class FilletSelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - FilletSelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()), object(obj) - {} - - bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) - { - if (pObj != this->object) - return false; - if (!sSubName || sSubName[0] == '\0') - return false; - std::string element(sSubName); - if (element.substr(0,4) == "Edge") { - int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; - Sketcher::SketchObject *Sketch = static_cast(object); - const Part::Geometry *geom = Sketch->getGeometry(GeoId); - if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId())) - return true; - } - if (element.substr(0,6) == "Vertex") { - int VtId = std::atoi(element.substr(6,4000).c_str()) - 1; - Sketcher::SketchObject *Sketch = static_cast(object); - std::vector GeoIdList; - std::vector PosIdList; - Sketch->getDirectlyCoincidentPoints(VtId, GeoIdList, PosIdList); - if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) { - const Part::Geometry *geom1 = Sketch->getGeometry(GeoIdList[0]); - const Part::Geometry *geom2 = Sketch->getGeometry(GeoIdList[1]); - if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && - geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) - return true; - } - } - return false; - } - }; -} - -class DrawSketchHandlerFillet: public DrawSketchHandler -{ -public: - enum FilletType { - SimpleFillet, - ConstraintPreservingFillet - }; - - DrawSketchHandlerFillet(FilletType filletType) : filletType(filletType), Mode(STATUS_SEEK_First), firstCurve(0) {} - virtual ~DrawSketchHandlerFillet() - { - Gui::Selection().rmvSelectionGate(); - } - - enum SelectMode{ - STATUS_SEEK_First, - STATUS_SEEK_Second - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - bool construction=false; - int VtId = getPreselectPoint(); - if (Mode == STATUS_SEEK_First && VtId != -1) { - int GeoId; - Sketcher::PointPos PosId=Sketcher::PointPos::none; - sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId,PosId); - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() && - (PosId == Sketcher::PointPos::start || PosId == Sketcher::PointPos::end)) { - - // guess fillet radius - double radius=-1; - std::vector GeoIdList; - std::vector PosIdList; - sketchgui->getSketchObject()->getDirectlyCoincidentPoints(GeoId, PosId, GeoIdList, PosIdList); - if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) { - const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(GeoIdList[0]); - const Part::Geometry *geom2 = sketchgui->getSketchObject()->getGeometry(GeoIdList[1]); - construction=Sketcher::GeometryFacade::getConstruction(geom1) && Sketcher::GeometryFacade::getConstruction(geom2); - if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && - geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg1 = static_cast(geom1); - const Part::GeomLineSegment *lineSeg2 = static_cast(geom2); - Base::Vector3d dir1 = lineSeg1->getEndPoint() - lineSeg1->getStartPoint(); - Base::Vector3d dir2 = lineSeg2->getEndPoint() - lineSeg2->getStartPoint(); - if (PosIdList[0] == Sketcher::PointPos::end) - dir1 *= -1; - if (PosIdList[1] == Sketcher::PointPos::end) - dir2 *= -1; - double l1 = dir1.Length(); - double l2 = dir2.Length(); - double angle = dir1.GetAngle(dir2); - radius = (l1 < l2 ? l1 : l2) * 0.2 * sin(angle/2); - } - } - if (radius < 0) - return false; - - int currentgeoid= getHighestCurveIndex(); - // create fillet at point - try { - bool pointFillet = (filletType == 1); - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,%f,%s,%s)", GeoId, static_cast(PosId), radius, "True", - pointFillet ? "True":"False"); - - if (construction) { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", currentgeoid+1); - } - - Gui::Command::commitCommand(); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to create fillet: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - } - return true; - } - - int GeoId = getPreselectCurve(); - if (GeoId > -1) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId())) { - if (Mode==STATUS_SEEK_First) { - firstCurve = GeoId; - firstPos = onSketchPos; - Mode = STATUS_SEEK_Second; - // add the line to the selection - std::stringstream ss; - ss << "Edge" << firstCurve + 1; - Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName() - ,sketchgui->getSketchObject()->getNameInDocument() - ,ss.str().c_str() - ,onSketchPos.x - ,onSketchPos.y - ,0.f); - } - else if (Mode==STATUS_SEEK_Second) { - int secondCurve = GeoId; - Base::Vector2d secondPos = onSketchPos; - - Base::Vector3d refPnt1(firstPos.x, firstPos.y, 0.f); - Base::Vector3d refPnt2(secondPos.x, secondPos.y, 0.f); - - const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(firstCurve); - - double radius = 0; - - if( geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() && - geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - // guess fillet radius - const Part::GeomLineSegment *lineSeg1 = static_cast - (sketchgui->getSketchObject()->getGeometry(firstCurve)); - const Part::GeomLineSegment *lineSeg2 = static_cast - (sketchgui->getSketchObject()->getGeometry(secondCurve)); - - radius = Part::suggestFilletRadius(lineSeg1, lineSeg2, refPnt1, refPnt2); - if (radius < 0) - return false; - - construction=Sketcher::GeometryFacade::getConstruction(lineSeg1) && Sketcher::GeometryFacade::getConstruction(lineSeg2); - } - else { // other supported curves - const Part::Geometry *geo1 = static_cast - (sketchgui->getSketchObject()->getGeometry(firstCurve)); - const Part::Geometry *geo2 = static_cast - (sketchgui->getSketchObject()->getGeometry(secondCurve)); - - construction=Sketcher::GeometryFacade::getConstruction(geo1) && Sketcher::GeometryFacade::getConstruction(geo2); - } - - - int currentgeoid= getHighestCurveIndex(); - - // create fillet between lines - try { - bool pointFillet = (filletType == 1); - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%f,%s,%s)", - firstCurve, secondCurve, - firstPos.x, firstPos.y, - secondPos.x, secondPos.y, radius, - "True", pointFillet ? "True":"False"); - Gui::Command::commitCommand(); - } - catch (const Base::CADKernelError& e) { - e.ReportException(); - if(e.getTranslatable()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("CAD Kernel Error"), - QObject::tr(e.getMessage().c_str())); - } - Gui::Selection().clearSelection(); - Gui::Command::abortCommand(); - Mode = STATUS_SEEK_First; - } - catch (const Base::ValueError& e) { - e.ReportException(); - Gui::Selection().clearSelection(); - Gui::Command::abortCommand(); - Mode = STATUS_SEEK_First; - } - - tryAutoRecompute(static_cast(sketchgui->getObject())); - - if(construction) { - Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", - currentgeoid+1); - } - - - Gui::Selection().clearSelection(); - Mode = STATUS_SEEK_First; - } - } - } - - if (VtId < 0 && GeoId < 0) // exit the fillet tool if the user clicked on empty space - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new FilletSelection(sketchgui->getObject())); - return QString::fromLatin1("Sketcher_Pointer_Create_Fillet"); - } - -protected: - int filletType; - SelectMode Mode; - int firstCurve; - Base::Vector2d firstPos; -}; - DEF_STD_CMD_A(CmdSketcherCreateFillet) CmdSketcherCreateFillet::CmdSketcherCreateFillet() @@ -5928,139 +1407,6 @@ bool CmdSketcherCompCreateFillets::isActive(void) // ====================================================================================== -namespace SketcherGui { - class TrimmingSelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - TrimmingSelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()), object(obj) - {} - - bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) - { - if (pObj != this->object) - return false; - if (!sSubName || sSubName[0] == '\0') - return false; - std::string element(sSubName); - if (element.substr(0,4) == "Edge") { - int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; - Sketcher::SketchObject *Sketch = static_cast(object); - const Part::Geometry *geom = Sketch->getGeometry(GeoId); - if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || - geom->getTypeId() == Part::GeomCircle::getClassTypeId() || - geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || - geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() - ) { - // We do not trim internal geometry of complex geometries - if( Sketcher::GeometryFacade::isInternalType(geom, Sketcher::InternalType::None)) - return true; - } - } - return false; - } - }; -} - -class DrawSketchHandlerTrimming: public DrawSketchHandler -{ -public: - DrawSketchHandlerTrimming() {} - virtual ~DrawSketchHandlerTrimming() - { - Gui::Selection().rmvSelectionGate(); - } - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - - int GeoId = getPreselectCurve(); - - if (GeoId > -1) { - auto sk = static_cast(sketchgui->getObject()); - int GeoId1, GeoId2; - Base::Vector3d intersect1, intersect2; - if(sk->seekTrimPoints(GeoId, Base::Vector3d(onSketchPos.x,onSketchPos.y,0), - GeoId1, intersect1, - GeoId2, intersect2)) { - - EditMarkers.resize(0); - - if(GeoId1 != Sketcher::GeoEnum::GeoUndef) - EditMarkers.emplace_back(intersect1.x, intersect1.y); - else { - auto start = sk->getPoint(GeoId, Sketcher::PointPos::start); - EditMarkers.emplace_back(start.x, start.y); - } - - if(GeoId2 != Sketcher::GeoEnum::GeoUndef) - EditMarkers.emplace_back(intersect2.x, intersect2.y); - else { - auto end = sk->getPoint(GeoId, Sketcher::PointPos::end); - EditMarkers.emplace_back( end.x, end.y); - } - - drawEditMarkers(EditMarkers, 2); // maker augmented by two sizes (see supported marker sizes) - } - } - else { - EditMarkers.resize(0); - drawEditMarkers(EditMarkers, 2); - } - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - int GeoId = getPreselectCurve(); - if (GeoId > -1) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || - geom->getTypeId() == Part::GeomCircle::getClassTypeId() || - geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || - geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) { - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Trim edge")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "trim(%d,App.Vector(%f,%f,0))", - GeoId, onSketchPos.x, onSketchPos.y); - Gui::Command::commitCommand(); - tryAutoRecompute(static_cast(sketchgui->getObject())); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to trim edge: %s\n", e.what()); - Gui::Command::abortCommand(); - } - } - - EditMarkers.resize(0); - drawEditMarkers(EditMarkers); - } - else // exit the trimming tool if the user clicked on empty space - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - Gui::Selection().clearSelection(); - Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new TrimmingSelection(sketchgui->getObject())); - return QString::fromLatin1("Sketcher_Pointer_Trimming"); - } - -private: - std::vector EditMarkers; -}; - DEF_STD_CMD_A(CmdSketcherTrimming) CmdSketcherTrimming::CmdSketcherTrimming() @@ -6091,294 +1437,6 @@ bool CmdSketcherTrimming::isActive(void) // ====================================================================================== -namespace SketcherGui { - class ExtendSelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - ExtendSelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()) - , object(obj) - , disabled(false) - {} - - bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) - { - if (pObj != this->object) - return false; - if (!sSubName || sSubName[0] == '\0') - return false; - if (disabled) - return true; - std::string element(sSubName); - if (element.substr(0, 4) == "Edge") { - int GeoId = std::atoi(element.substr(4, 4000).c_str()) - 1; - Sketcher::SketchObject *Sketch = static_cast(object); - const Part::Geometry *geom = Sketch->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || - geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) - return true; - } - return false; - } - - void setDisabled(bool isDisabled) { - disabled = isDisabled; - } - protected: - bool disabled; - }; -} - -class DrawSketchHandlerExtend: public DrawSketchHandler -{ -public: - DrawSketchHandlerExtend() - : Mode(STATUS_SEEK_First) - , EditCurve(2) - , BaseGeoId(-1) - , ExtendFromStart(false) - , SavedExtendFromStart(false) - , Increment(0) - { - } - virtual ~DrawSketchHandlerExtend() - { - Gui::Selection().rmvSelectionGate(); - } - enum SelectMode { - STATUS_SEEK_First, - STATUS_SEEK_Second, - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode == STATUS_SEEK_Second) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *lineSeg = static_cast(geom); - // project point to the existing curve - Base::Vector3d start3d = lineSeg->getStartPoint(); - Base::Vector3d end3d = lineSeg->getEndPoint(); - - Base::Vector2d startPoint = Base::Vector2d(start3d.x, start3d.y); - Base::Vector2d endPoint = Base::Vector2d(end3d.x, end3d.y); - Base::Vector2d recenteredLine = endPoint - startPoint; - Base::Vector2d recenteredPoint = onSketchPos - startPoint; - Base::Vector2d projection; - projection.ProjectToLine(recenteredPoint, recenteredLine); - if (recenteredPoint.Length() < recenteredPoint.Distance(recenteredLine)) { - EditCurve[0] = startPoint + projection; - EditCurve[1] = endPoint; - } else { - EditCurve[0] = startPoint; - EditCurve[1] = startPoint + projection; - } - /** - * If in-curve, the intuitive behavior is for the line to shrink an amount from - * the original click-point. - * - * If out-of-curve, the intuitive behavior is for the closest line endpoint to - * expand. - */ - bool inCurve = (projection.Length() < recenteredLine.Length() - && projection.GetAngle(recenteredLine) < 0.1); // Two possible values here, M_PI and 0, but 0.1 is to avoid floating point problems. - if (inCurve) { - Increment = SavedExtendFromStart ? -1 * projection.Length() : projection.Length() - recenteredLine.Length(); - ExtendFromStart = SavedExtendFromStart; - } else { - ExtendFromStart = onSketchPos.Distance(startPoint) < onSketchPos.Distance(endPoint); - Increment = ExtendFromStart ? projection.Length() : projection.Length() - recenteredLine.Length(); - } - drawEdit(EditCurve); - - } else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *arc = static_cast(geom); - Base::Vector3d center = arc->getCenter(); - double radius = arc->getRadius(); - - double start, end; - arc->getRange(start, end, true); - double arcAngle = end - start; - - Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y); - Base::Vector2d startAngle = Base::Vector2d(cos(start), sin(start)); - Base::Vector2d endAngle = Base::Vector2d(cos(end), sin(end)); - - Base::Vector2d arcHalf = Base::Vector2d(cos(start + arcAngle/ 2.0), sin(start+ arcAngle / 2.0)); - double angleToEndAngle = angle.GetAngle(endAngle); - double angleToStartAngle = angle.GetAngle(startAngle); - - - double modStartAngle = start; - double modArcAngle = end - start; - bool outOfArc = arcHalf.GetAngle(angle) * 2.0 > arcAngle; - if (ExtendFromStart) { - bool isCCWFromStart = crossProduct(angle, startAngle) < 0; - if (outOfArc) { - if (isCCWFromStart) { - modStartAngle -= 2*M_PI - angleToStartAngle; - modArcAngle += 2*M_PI - angleToStartAngle; - } else { - modStartAngle -= angleToStartAngle; - modArcAngle += angleToStartAngle; - } - } else { - if (isCCWFromStart) { - modStartAngle += angleToStartAngle; - modArcAngle -= angleToStartAngle; - } else { - modStartAngle += 2*M_PI - angleToStartAngle; - modArcAngle -= 2*M_PI - angleToStartAngle; - } - } - } else { - bool isCWFromEnd = crossProduct(angle, endAngle) >= 0; - if (outOfArc) { - if (isCWFromEnd) { - modArcAngle += 2*M_PI - angleToEndAngle; - } else { - modArcAngle += angleToEndAngle; - } - } else { - if (isCWFromEnd) { - modArcAngle -= angleToEndAngle; - } else { - modArcAngle -= 2*M_PI - angleToEndAngle; - } - } - } - Increment = modArcAngle - (end - start); - for (int i = 0; i < 31; i++) { - double angle = modStartAngle + i * modArcAngle/30.0; - EditCurve[i] = Base::Vector2d(center.x + radius * cos(angle), center.y + radius * sin(angle)); - } - drawEdit(EditCurve); - } - int curveId = getPreselectCurve(); - if (BaseGeoId != curveId && seekAutoConstraint(SugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(SugConstr); - return; - } - } - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode == STATUS_SEEK_First) { - BaseGeoId = getPreselectCurve(); - if (BaseGeoId > -1) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { - const Part::GeomLineSegment *seg = static_cast(geom); - Base::Vector3d start3d = seg->getStartPoint(); - Base::Vector3d end3d = seg->getEndPoint(); - Base::Vector2d start = Base::Vector2d(start3d.x, start3d.y); - Base::Vector2d end = Base::Vector2d(end3d.x, end3d.y); - SavedExtendFromStart = (onSketchPos.Distance(start) < onSketchPos.Distance(end)); - ExtendFromStart = SavedExtendFromStart; - Mode = STATUS_SEEK_Second; - } else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - const Part::GeomArcOfCircle *arc = static_cast(geom); - double start, end; - arc->getRange(start, end, true); - - Base::Vector3d center = arc->getCenter(); - Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y); - double angleToStart = angle.GetAngle(Base::Vector2d(cos(start), sin(start))); - double angleToEnd = angle.GetAngle(Base::Vector2d(cos(end), sin(end))); - ExtendFromStart = (angleToStart < angleToEnd); // move start point if closer to angle than end point - EditCurve.resize(31); - Mode = STATUS_SEEK_Second; - } - filterGate->setDisabled(true); - } - } else if (Mode == STATUS_SEEK_Second) { - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Extend edge")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "extend(%d, %f, %d)\n", // GeoId, increment, PointPos - BaseGeoId, Increment, ExtendFromStart ? static_cast(Sketcher::PointPos::start) : static_cast(Sketcher::PointPos::end)); - Gui::Command::commitCommand(); - - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); - bool autoRecompute = hGrp->GetBool("AutoRecompute",false); - if(autoRecompute) - Gui::Command::updateActive(); - - // constrain chosen point - if (SugConstr.size() > 0) { - createAutoConstraints(SugConstr, BaseGeoId, (ExtendFromStart) ? Sketcher::PointPos::start : Sketcher::PointPos::end); - SugConstr.clear(); - } - bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); - - if(continuousMode){ - // This code enables the continuous creation mode. - Mode=STATUS_SEEK_First; - filterGate->setDisabled(false); - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(2); - applyCursor(); - /* this 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 - } - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to extend edge: %s\n", e.what()); - Gui::Command::abortCommand(); - } - - } else { // exit extension tool if user clicked on empty space - BaseGeoId = -1; - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - return true; - } - -private: - virtual void activated() override - { - Q_UNUSED(sketchgui) - Gui::Selection().clearSelection(); - Gui::Selection().rmvSelectionGate(); - filterGate = new ExtendSelection(sketchgui->getObject()); - Gui::Selection().addSelectionGate(filterGate); - } - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Extension"); - } - -protected: - SelectMode Mode; - std::vector EditCurve; - int BaseGeoId; - ExtendSelection* filterGate = nullptr; - bool ExtendFromStart; // if true, extend from start, else extend from end (circle only) - bool SavedExtendFromStart; - double Increment; - std::vector SugConstr; - -private: - int crossProduct(Base::Vector2d &vec1, Base::Vector2d &vec2) { - return vec1.x * vec2.y - vec1.y * vec2.x; - } -}; - DEF_STD_CMD_A(CmdSketcherExtend) //TODO: fix the translations for this @@ -6410,95 +1468,6 @@ bool CmdSketcherExtend::isActive(void) // ====================================================================================== -namespace SketcherGui { - class SplittingSelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - SplittingSelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()), object(obj) - {} - - bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) - { - if (pObj != this->object) - return false; - if (!sSubName || sSubName[0] == '\0') - return false; - std::string element(sSubName); - if (element.substr(0,4) == "Edge") { - int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; - Sketcher::SketchObject *Sketch = static_cast(object); - const Part::Geometry *geom = Sketch->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() - || geom->getTypeId() == Part::GeomCircle::getClassTypeId() - || geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - return true; - } - } - return false; - } - }; -} - -class DrawSketchHandlerSplitting: public DrawSketchHandler -{ -public: - DrawSketchHandlerSplitting() {} - virtual ~DrawSketchHandlerSplitting() - { - Gui::Selection().rmvSelectionGate(); - } - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - int GeoId = getPreselectCurve(); - if (GeoId >= 0) { - const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); - if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() - || geom->getTypeId() == Part::GeomCircle::getClassTypeId() - || geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Split edge")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "split(%d,App.Vector(%f,%f,0))", - GeoId, onSketchPos.x, onSketchPos.y); - Gui::Command::commitCommand(); - tryAutoRecompute(static_cast(sketchgui->getObject())); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to split edge: %s\n", e.what()); - Gui::Command::abortCommand(); - } - } - } - else { - sketchgui->purgeHandler(); - } - - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - Gui::Selection().clearSelection(); - Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new SplittingSelection(sketchgui->getObject())); - return QString::fromLatin1("Sketcher_Pointer_Splitting"); - } -}; - DEF_STD_CMD_A(CmdSketcherSplit) //TODO: fix the translations for this @@ -6527,169 +1496,6 @@ bool CmdSketcherSplit::isActive(void) return isCommandActive(getActiveGuiDocument()); } - -namespace SketcherGui { - class ExternalSelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - ExternalSelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()), object(obj) - {} - - bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) - { - Sketcher::SketchObject *sketch = static_cast(object); - - this->notAllowedReason = ""; - Sketcher::SketchObject::eReasonList msg; - if (!sketch->isExternalAllowed(pDoc, pObj, &msg)){ - switch(msg){ - case Sketcher::SketchObject::rlCircularReference: - this->notAllowedReason = QT_TR_NOOP("Linking this will cause circular dependency."); - break; - case Sketcher::SketchObject::rlOtherDoc: - this->notAllowedReason = QT_TR_NOOP("This object is in another document."); - break; - case Sketcher::SketchObject::rlOtherBody: - this->notAllowedReason = QT_TR_NOOP("This object belongs to another body, can't link."); - break; - case Sketcher::SketchObject::rlOtherPart: - this->notAllowedReason = QT_TR_NOOP("This object belongs to another part, can't link."); - break; - default: - break; - } - return false; - } - - // Note: its better to search the support of the sketch in case the sketch support is a base plane - //Part::BodyBase* body = Part::BodyBase::findBodyOf(sketch); - //if ( body && body->hasFeature ( pObj ) && body->isAfter ( pObj, sketch ) ) { - // Don't allow selection after the sketch in the same body - // NOTE: allowness of features in other bodies is handled by SketchObject::isExternalAllowed() - // TODO may be this should be in SketchObject::isExternalAllowed() (2015-08-07, Fat-Zer) - //return false; - //} - - if (!sSubName || sSubName[0] == '\0') - return false; - std::string element(sSubName); - if ((element.size() > 4 && element.substr(0,4) == "Edge") || - (element.size() > 6 && element.substr(0,6) == "Vertex") || - (element.size() > 4 && element.substr(0,4) == "Face")) { - return true; - } - if (pObj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) || - pObj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) - return true; - return false; - } - }; -} - -class DrawSketchHandlerExternal: public DrawSketchHandler -{ -public: - DrawSketchHandlerExternal() {} - virtual ~DrawSketchHandlerExternal() - { - Gui::Selection().rmvSelectionGate(); - } - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Gui::Selection().getPreselection().pObjectName) - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - /* this 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 */ - return true; - } - - virtual bool onSelectionChanged(const Gui::SelectionChanges& msg) override - { - if (msg.Type == Gui::SelectionChanges::AddSelection) { - App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName); - if (obj == nullptr) - throw Base::ValueError("Sketcher: External geometry: Invalid object in selection"); - std::string subName(msg.pSubName); - if (obj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) || - obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId()) || - (subName.size() > 4 && subName.substr(0,4) == "Edge") || - (subName.size() > 6 && subName.substr(0,6) == "Vertex") || - (subName.size() > 4 && subName.substr(0,4) == "Face")) { - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add external geometry")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "addExternal(\"%s\",\"%s\")", - msg.pObjectName, msg.pSubName); - Gui::Command::commitCommand(); - - // adding external geometry does not require a solve() per se (the DoF is the same), - // however a solve is required to update the amount of solver geometry, because we only - // redraw a changed Sketch if the solver geometry amount is the same as the SkethObject - // geometry amount (as this avoids other issues). - // This solver is a very low cost one anyway (there is actually nothing to solve). - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - Gui::Selection().clearSelection(); - /* this 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 */ - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add external geometry: %s\n", e.what()); - Gui::Selection().clearSelection(); - Gui::Command::abortCommand(); - } - return true; - } - } - return false; - } - -private: - virtual void activated() override - { - setAxisPickStyle(false); - Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); - Gui::View3DInventorViewer *viewer; - viewer = static_cast(mdi)->getViewer(); - - SoNode* root = viewer->getSceneGraph(); - static_cast(root)->selectionRole.setValue(true); - - Gui::Selection().clearSelection(); - Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new ExternalSelection(sketchgui->getObject())); - } - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_External"); - } - - virtual void deactivated() override - { - Q_UNUSED(sketchgui); - setAxisPickStyle(true); - } -}; - DEF_STD_CMD_A(CmdSketcherExternal) CmdSketcherExternal::CmdSketcherExternal() @@ -6719,159 +1525,6 @@ bool CmdSketcherExternal::isActive(void) // ====================================================================================== -namespace SketcherGui { - class CarbonCopySelection : public Gui::SelectionFilterGate - { - App::DocumentObject* object; - public: - CarbonCopySelection(App::DocumentObject* obj) - : Gui::SelectionFilterGate(nullPointer()), object(obj) - {} - - bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) - { - Q_UNUSED(sSubName); - - Sketcher::SketchObject *sketch = static_cast(object); - sketch->setAllowOtherBody(QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier)); - sketch->setAllowUnaligned(QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier)); - - this->notAllowedReason = ""; - Sketcher::SketchObject::eReasonList msg; - // Reusing code: All good reasons not to allow a carbon copy - bool xinv = false, yinv = false; - if (!sketch->isCarbonCopyAllowed(pDoc, pObj, xinv, yinv, &msg)){ - switch(msg){ - case Sketcher::SketchObject::rlCircularReference: - this->notAllowedReason = QT_TR_NOOP("Carbon copy would cause a circular dependency."); - break; - case Sketcher::SketchObject::rlOtherDoc: - this->notAllowedReason = QT_TR_NOOP("This object is in another document."); - break; - case Sketcher::SketchObject::rlOtherBody: - this->notAllowedReason = QT_TR_NOOP("This object belongs to another body. Hold Ctrl to allow cross-references."); - break; - case Sketcher::SketchObject::rlOtherBodyWithLinks: - this->notAllowedReason = QT_TR_NOOP("This object belongs to another body and it contains external geometry. Cross-reference not allowed."); - break; - case Sketcher::SketchObject::rlOtherPart: - this->notAllowedReason = QT_TR_NOOP("This object belongs to another part."); - break; - case Sketcher::SketchObject::rlNonParallel: - this->notAllowedReason = QT_TR_NOOP("The selected sketch is not parallel to this sketch. Hold Ctrl+Alt to allow non-parallel sketches."); - break; - case Sketcher::SketchObject::rlAxesMisaligned: - this->notAllowedReason = QT_TR_NOOP("The XY axes of the selected sketch do not have the same direction as this sketch. Hold Ctrl+Alt to disregard it."); - break; - case Sketcher::SketchObject::rlOriginsMisaligned: - this->notAllowedReason = QT_TR_NOOP("The origin of the selected sketch is not aligned with the origin of this sketch. Hold Ctrl+Alt to disregard it."); - break; - default: - break; - } - return false; - } - // Carbon copy only works on sketches that are not disallowed (e.g. would produce a circular reference) - return true; - } - }; -} - - -class DrawSketchHandlerCarbonCopy: public DrawSketchHandler -{ -public: - DrawSketchHandlerCarbonCopy() {} - virtual ~DrawSketchHandlerCarbonCopy() - { - Gui::Selection().rmvSelectionGate(); - } - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Gui::Selection().getPreselection().pObjectName) - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - /* this 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 */ - return true; - } - - virtual bool onSelectionChanged(const Gui::SelectionChanges& msg) override - { - if (msg.Type == Gui::SelectionChanges::AddSelection) { - App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName); - if (obj == nullptr) - throw Base::ValueError("Sketcher: Carbon Copy: Invalid object in selection"); - - if (obj->getTypeId() == Sketcher::SketchObject::getClassTypeId()) { - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add carbon copy")); - Gui::cmdAppObjectArgs(sketchgui->getObject(), "carbonCopy(\"%s\",%s)", - msg.pObjectName, geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - - Gui::Selection().clearSelection(); - /* this 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 */ - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add carbon copy: %s\n", e.what()); - Gui::Command::abortCommand(); - } - return true; - } - } - return false; - } - -private: - virtual void activated() override - { - setAxisPickStyle(false); - Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); - Gui::View3DInventorViewer *viewer; - viewer = static_cast(mdi)->getViewer(); - - SoNode* root = viewer->getSceneGraph(); - static_cast(root)->selectionRole.setValue(true); - - Gui::Selection().clearSelection(); - Gui::Selection().rmvSelectionGate(); - Gui::Selection().addSelectionGate(new CarbonCopySelection(sketchgui->getObject())); - } - - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_CarbonCopy"); - } - - virtual void deactivated() override - { - Q_UNUSED(sketchgui); - setAxisPickStyle(true); - } -}; - DEF_STD_CMD_AU(CmdSketcherCarbonCopy) CmdSketcherCarbonCopy::CmdSketcherCarbonCopy() @@ -6917,272 +1570,6 @@ void CmdSketcherCarbonCopy::updateAction(int mode) /** * Create Slot */ -class DrawSketchHandlerSlot : public DrawSketchHandler -{ -public: - DrawSketchHandlerSlot() - : Mode(STATUS_SEEK_First) - , SnapMode(SNAP_MODE_Free) - , SnapDir(SNAP_DIR_Horz) - , dx(0), dy(0), r(0) - , EditCurve(35) - { - } - virtual ~DrawSketchHandlerSlot() {} - /// mode table - enum BoxMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_End - }; - - enum SNAP_MODE - { - SNAP_MODE_Free, - SNAP_MODE_Straight - }; - - enum SNAP_DIR - { - SNAP_DIR_Horz, - SNAP_DIR_Vert - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - - if (Mode == STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode == STATUS_SEEK_Second) { - dx = onSketchPos.x - StartPos.x; - dy = onSketchPos.y - StartPos.y; - - if(QApplication::keyboardModifiers() == Qt::ControlModifier) - SnapMode = SNAP_MODE_Straight; - else - SnapMode = SNAP_MODE_Free; - - double a = 0; - double rev = 0; - if (fabs(dx) > fabs(dy)) { - r = fabs(dx) / 4; - rev = Base::sgn(dx); - SnapDir = SNAP_DIR_Horz; - if (SnapMode == SNAP_MODE_Straight) dy = 0; - } - else { - r = fabs(dy) / 4; - a = 8; - rev = Base::sgn(dy); - SnapDir = SNAP_DIR_Vert; - if (SnapMode == SNAP_MODE_Straight) dx = 0; - } - - // draw the arcs with each 16 segments - for (int i = 0; i < 17; i++) { - // first get the position at the arc - // if a is 0, the end points of the arc are at the y-axis, if it is 8, they are on the x-axis - double angle = (i + a) * M_PI / 16.0; - double rx = -r * rev * sin(angle); - double ry = r * rev * cos(angle); - // now apply the rotation matrix according to the angle between StartPos and onSketchPos - if (!(dx == 0 || dy == 0)) { - double rotAngle = atan(dy / dx); - if (a > 0) - rotAngle = -atan(dx / dy); - double rxRot = rx * cos(rotAngle) - ry * sin(rotAngle); - double ryRot = rx * sin(rotAngle) + ry * cos(rotAngle); - rx = rxRot; - ry = ryRot; - } - EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry); - EditCurve[17 + i] = Base::Vector2d(StartPos.x + dx - rx, StartPos.y + dy - ry); - } - EditCurve[34] = EditCurve[0]; - - SbString text; - text.sprintf(" (%.1fR %.1fL)", r, sqrt(dx * dx + dy * dy)); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(dx, dy), AutoConstraint::VERTEX_NO_TANGENCY)) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode == STATUS_SEEK_First) { - StartPos = onSketchPos; - Mode = STATUS_SEEK_Second; - } - else { - Mode = STATUS_End; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode == STATUS_End) { - unsetCursor(); - resetPositionText(); - - int firstCurve = getHighestCurveIndex() + 1; - // add the geometry to the sketch - // first determine the rotation angle for the first arc - double start, end; - if (fabs(dx) > fabs(dy)) { - if (dx > 0) { - start = 0.5 * M_PI; - end = 1.5 * M_PI; - } - else { - start = 1.5 * M_PI; - end = 0.5 * M_PI; - } - } - else { - if (dy > 0) { - start = -M_PI; - end = 0; - } - else { - start = 0; - end = -M_PI; - } - } - - try { - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add slot")); - - AutoConstraint lastCons = {Sketcher::None, Sketcher::GeoEnum::GeoUndef, Sketcher::PointPos::none}; - - if (!sugConstr2.empty()) lastCons = sugConstr2.back(); - - ostringstream snapCon = ostringstream(""); - if (SnapMode == SNAP_MODE_Straight) { - snapCon << "conList.append(Sketcher.Constraint('"; - if (SnapDir == SNAP_DIR_Horz) { - snapCon << "Horizontal"; - } - else { - snapCon << "Vertical"; - } - snapCon << "'," << firstCurve + 2 << "))\n"; - - // If horizontal/vertical already applied because of snap, do not duplicate with Autocontraint - if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) - sugConstr2.pop_back(); - } - else { - // If horizontal/vertical Autoconstraint suggested, applied it on first line (rather than last arc) - if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) - sugConstr2.back().GeoId = firstCurve + 2; - } - - Gui::Command::doCommand(Gui::Command::Doc, - "geoList = []\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f ,0), App.Vector(0, 0, 1), %f), %f, %f))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" - "%s.addGeometry(geoList, %s)\n" - "conList = []\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" - "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" - "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" - "%s" - "%s.addConstraint(conList)\n" - "del geoList, conList\n", - StartPos.x, StartPos.y, // center of the arc1 - r, // radius arc1 - start, end, // start and end angle of arc1 - StartPos.x + dx, StartPos.y + dy, // center of the arc2 - r, // radius arc2 - end, end + M_PI, // start and end angle of arc2 - EditCurve[16].x, EditCurve[16].y, EditCurve[17].x, EditCurve[17].y, // line1 - EditCurve[33].x, EditCurve[33].y, EditCurve[34].x, EditCurve[34].y, // line2 - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch - geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not - firstCurve, firstCurve + 2, // tangent1 - firstCurve + 2, firstCurve + 1, // tangent2 - firstCurve + 1, firstCurve + 3, // tangent3 - firstCurve + 3, firstCurve, // tangent4 - firstCurve, firstCurve + 1, // equal constraint - snapCon.str().c_str(), // horizontal/vertical constraint if snapping - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch - - Gui::Command::commitCommand(); - - // add auto constraints at the center of the first arc - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3, Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // add auto constraints at the center of the second arc - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::PointPos::mid); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add slot: %s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecompute(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. - Mode = STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(35); - applyCursor(); - /* this 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 - } - SnapMode = SNAP_MODE_Straight; - } - return true; - } -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Slot"); - } - -protected: - BoxMode Mode; - SNAP_MODE SnapMode; - SNAP_DIR SnapDir; - Base::Vector2d StartPos; - double dx, dy, r; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; -}; DEF_STD_CMD_AU(CmdSketcherCreateSlot) @@ -7227,157 +1614,6 @@ bool CmdSketcherCreateSlot::isActive(void) /* Create Regular Polygon ==============================================*/ -class DrawSketchHandlerRegularPolygon: public DrawSketchHandler -{ -public: - DrawSketchHandlerRegularPolygon( size_t nof_corners ): - Corners( nof_corners ), - AngleOfSeparation( 2.0*M_PI/static_cast(Corners) ), - cos_v( cos( AngleOfSeparation ) ), - sin_v( sin( AngleOfSeparation ) ), - Mode(STATUS_SEEK_First), - EditCurve(Corners+1) - { - } - virtual ~DrawSketchHandlerRegularPolygon(){} - /// mode table - enum SelectMode { - STATUS_SEEK_First, /**< enum value ----. */ - STATUS_SEEK_Second, /**< enum value ----. */ - STATUS_End - }; - - virtual void mouseMove(Base::Vector2d onSketchPos) override - { - - if (Mode==STATUS_SEEK_First) { - setPositionText(onSketchPos); - if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr1); - return; - } - } - else if (Mode==STATUS_SEEK_Second) { - EditCurve[0]= Base::Vector2d(onSketchPos.x, onSketchPos.y); - EditCurve[Corners]= Base::Vector2d(onSketchPos.x, onSketchPos.y); - - Base::Vector2d dV = onSketchPos - StartPos; - double rx = dV.x; - double ry = dV.y; - for (int i=1; i < static_cast(Corners); i++) { - const double old_rx = rx; - rx = cos_v * rx - sin_v * ry; - ry = cos_v * ry + sin_v * old_rx; - EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry); - } - - // Display radius for user - const float radius = dV.Length(); - const float angle = ( 180.0 / M_PI ) * atan2( dV.y, dV.x ); - - SbString text; - text.sprintf(" (%.1fR %.1fdeg)", radius, angle ); - setPositionText(onSketchPos, text); - - drawEdit(EditCurve); - if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { - renderSuggestConstraintsCursor(sugConstr2); - return; - } - } - applyCursor(); - } - - virtual bool pressButton(Base::Vector2d onSketchPos) override - { - if (Mode==STATUS_SEEK_First){ - StartPos = onSketchPos; - Mode = STATUS_SEEK_Second; - } - else { - Mode = STATUS_End; - } - return true; - } - - virtual bool releaseButton(Base::Vector2d onSketchPos) override - { - Q_UNUSED(onSketchPos); - if (Mode==STATUS_End){ - unsetCursor(); - resetPositionText(); - Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add hexagon")); - - try { - Gui::Command::doCommand(Gui::Command::Doc, - "import ProfileLib.RegularPolygon\n" - "ProfileLib.RegularPolygon.makeRegularPolygon(%s,%i,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%s)", - Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), - Corners, - StartPos.x,StartPos.y,EditCurve[0].x,EditCurve[0].y, - geometryCreationMode==Construction?"True":"False"); - - Gui::Command::commitCommand(); - - // add auto constraints at the center of the polygon - if (sugConstr1.size() > 0) { - createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); - sugConstr1.clear(); - } - - // add auto constraints to the last side of the polygon - if (sugConstr2.size() > 0) { - createAutoConstraints(sugConstr2, getHighestCurveIndex() - 1, Sketcher::PointPos::end); - sugConstr2.clear(); - } - - tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); - } - catch (const Base::Exception& e) { - Base::Console().Error("Failed to add hexagon: %s\n", e.what()); - Gui::Command::abortCommand(); - - tryAutoRecompute(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. - Mode=STATUS_SEEK_First; - EditCurve.clear(); - drawEdit(EditCurve); - EditCurve.resize(Corners+1); - applyCursor(); - /* this is ok not to call to purgeHandler - * in continuous creation mode because the - * handler is destroyed by the quit() method on pressing the - * right button of the mouse */ - } - else{ - sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider - } - } - return true; - } - -private: - virtual QString getCrosshairCursorSVGName() const override - { - return QString::fromLatin1("Sketcher_Pointer_Regular_Polygon"); - } -protected: - const size_t Corners; - const double AngleOfSeparation; - const double cos_v, sin_v; - SelectMode Mode; - Base::Vector2d StartPos; - std::vector EditCurve; - std::vector sugConstr1, sugConstr2; -}; - - DEF_STD_CMD_A(CmdSketcherCreateTriangle) CmdSketcherCreateTriangle::CmdSketcherCreateTriangle() diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h new file mode 100644 index 0000000000..a8224fc7e9 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArc.h @@ -0,0 +1,485 @@ + +/*************************************************************************** + * 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_DrawSketchHandlerArc_H +#define SKETCHERGUI_DrawSketchHandlerArc_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +using namespace std; + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerArc : public DrawSketchHandler +{ +public: + DrawSketchHandlerArc() + : Mode(STATUS_SEEK_First) + , EditCurve(2) + , rx(0), ry(0) + , startAngle(0) + , endAngle(0) + , arcAngle(0) + { + } + virtual ~DrawSketchHandlerArc(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + double dx_ = onSketchPos.x - EditCurve[0].x; + double dy_ = onSketchPos.y - EditCurve[0].y; + for (int i=0; i < 16; i++) { + double angle = i*M_PI/16.0; + double dx = dx_ * cos(angle) + dy_ * sin(angle); + double dy = -dx_ * sin(angle) + dy_ * cos(angle); + EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + dx, EditCurve[0].y + dy); + EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - dx, EditCurve[0].y - dy); + } + EditCurve[33] = EditCurve[1]; + + // Display radius and start angle + float radius = (onSketchPos - EditCurve[0]).Length(); + float angle = atan2f(dy_ , dx_); + + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", radius, angle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else if (Mode==STATUS_SEEK_Third) { + double angle1 = atan2(onSketchPos.y - CenterPoint.y, + onSketchPos.x - CenterPoint.x) - startAngle; + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; + arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; + for (int i=1; i <= 29; i++) { + double angle = i*arcAngle/29.0; + double dx = rx * cos(angle) - ry * sin(angle); + double dy = rx * sin(angle) + ry * cos(angle); + EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy); + } + + // Display radius and arc angle + float radius = (onSketchPos - EditCurve[0]).Length(); + + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", radius, arcAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0))) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + applyCursor(); + + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + CenterPoint = onSketchPos; + EditCurve.resize(34); + EditCurve[0] = onSketchPos; + Mode = STATUS_SEEK_Second; + } + else if (Mode==STATUS_SEEK_Second){ + EditCurve.resize(31); + EditCurve[0] = onSketchPos; + EditCurve[30] = CenterPoint; + rx = EditCurve[0].x - CenterPoint.x; + ry = EditCurve[0].y - CenterPoint.y; + startAngle = atan2(ry, rx); + arcAngle = 0.; + Mode = STATUS_SEEK_Third; + } + else { + EditCurve.resize(30); + double angle1 = atan2(onSketchPos.y - CenterPoint.y, + onSketchPos.x - CenterPoint.x) - startAngle; + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; + arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; + if (arcAngle > 0) + endAngle = startAngle + arcAngle; + else { + endAngle = startAngle; + startAngle += arcAngle; + } + + drawEdit(EditCurve); + applyCursor(); + Mode = STATUS_End; + } + + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_End) { + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" + "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", + CenterPoint.x, CenterPoint.y, sqrt(rx*rx + ry*ry), + startAngle, endAngle, + geometryCreationMode==Construction?"True":"False"); //arcAngle > 0 ? 0 : 1); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add arc: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + // Auto Constraint center point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // Auto Constraint first picked point + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::PointPos::start : Sketcher::PointPos::end ); + sugConstr2.clear(); + } + + // Auto Constraint second picked point + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, getHighestCurveIndex(), (arcAngle > 0) ? Sketcher::PointPos::end : Sketcher::PointPos::start); + sugConstr3.clear(); + } + + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Arc"); + } +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d CenterPoint; + double rx, ry, startAngle, endAngle, arcAngle; + std::vector sugConstr1, sugConstr2, sugConstr3; +}; + +class DrawSketchHandler3PointArc : public DrawSketchHandler +{ +public: + DrawSketchHandler3PointArc() + : Mode(STATUS_SEEK_First), EditCurve(2) + , radius(0), startAngle(0) + , endAngle(0), arcAngle(0) + , arcPos1(Sketcher::PointPos::none) + , arcPos2(Sketcher::PointPos::none) + { + } + virtual ~DrawSketchHandler3PointArc(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + CenterPoint = EditCurve[0] = (onSketchPos - FirstPoint)/2 + FirstPoint; + EditCurve[1] = EditCurve[33] = onSketchPos; + radius = (onSketchPos - CenterPoint).Length(); + double lineAngle = GetPointAngle(CenterPoint, onSketchPos); + + // Build a 32 point circle ignoring already constructed points + for (int i=1; i <= 32; i++) { + // Start at current angle + double angle = (i-1)*2*M_PI/32.0 + lineAngle; // N point closed circle has N segments + if (i != 1 && i != 17 ) { + EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), + CenterPoint.y + radius*sin(angle)); + } + } + + // Display radius and start angle + // This lineAngle will report counter-clockwise from +X, not relatively + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else if (Mode==STATUS_SEEK_Third) { + /* + Centerline inverts when the arc flips sides. Easily taken care of by replacing + centerline with a point. It happens because the direction the curve is being drawn + reverses. + */ + try { + CenterPoint = EditCurve[30] = Part::Geom2dCircle::getCircleCenter(FirstPoint, SecondPoint, onSketchPos); + + radius = (SecondPoint - CenterPoint).Length(); + + double angle1 = GetPointAngle(CenterPoint, FirstPoint); + double angle2 = GetPointAngle(CenterPoint, SecondPoint); + double angle3 = GetPointAngle(CenterPoint, onSketchPos); + + // Always build arc counter-clockwise + // Point 3 is between Point 1 and 2 + if ( angle3 > min(angle1, angle2) && angle3 < max(angle1, angle2) ) { + if (angle2 > angle1) { + EditCurve[0] = FirstPoint; + EditCurve[29] = SecondPoint; + arcPos1 = Sketcher::PointPos::start; + arcPos2 = Sketcher::PointPos::end; + } + else { + EditCurve[0] = SecondPoint; + EditCurve[29] = FirstPoint; + arcPos1 = Sketcher::PointPos::end; + arcPos2 = Sketcher::PointPos::start; + } + startAngle = min(angle1, angle2); + endAngle = max(angle1, angle2); + arcAngle = endAngle - startAngle; + } + // Point 3 is not between Point 1 and 2 + else { + if (angle2 > angle1) { + EditCurve[0] = SecondPoint; + EditCurve[29] = FirstPoint; + arcPos1 = Sketcher::PointPos::end; + arcPos2 = Sketcher::PointPos::start; + } + else { + EditCurve[0] = FirstPoint; + EditCurve[29] = SecondPoint; + arcPos1 = Sketcher::PointPos::start; + arcPos2 = Sketcher::PointPos::end; + } + startAngle = max(angle1, angle2); + endAngle = min(angle1, angle2); + arcAngle = 2*M_PI - (startAngle - endAngle); + } + + // Build a 30 point circle ignoring already constructed points + for (int i=1; i <= 28; i++) { + double angle = startAngle + i*arcAngle/29.0; // N point arc has N-1 segments + EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), + CenterPoint.y + radius*sin(angle)); + } + + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) arcAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.0,0.0), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + catch(Base::ValueError &e) { + e.ReportException(); + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + // 32 point curve + center + endpoint + EditCurve.resize(34); + // 17 is circle halfway point (1+32/2) + FirstPoint = EditCurve[17] = onSketchPos; + + Mode = STATUS_SEEK_Second; + } + else if (Mode==STATUS_SEEK_Second){ + // 30 point arc and center point + EditCurve.resize(31); + SecondPoint = onSketchPos; + + Mode = STATUS_SEEK_Third; + } + else { + EditCurve.resize(30); + + drawEdit(EditCurve); + applyCursor(); + Mode = STATUS_End; + } + + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + // Need to look at. rx might need fixing. + if (Mode==STATUS_End) { + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" + "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", + CenterPoint.x, CenterPoint.y, radius, + startAngle, endAngle, + geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add arc: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + // Auto Constraint first picked point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), arcPos1); + sugConstr1.clear(); + } + + // Auto Constraint second picked point + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), arcPos2); + sugConstr2.clear(); + } + + // Auto Constraint third picked point + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::PointPos::none); + sugConstr3.clear(); + } + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_3PointArc"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d CenterPoint, FirstPoint, SecondPoint; + double radius, startAngle, endAngle, arcAngle; + std::vector sugConstr1, sugConstr2, sugConstr3; + Sketcher::PointPos arcPos1, arcPos2; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerArc_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h new file mode 100644 index 0000000000..905b171f27 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfEllipse.h @@ -0,0 +1,341 @@ +/*************************************************************************** + * 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_DrawSketchHandlerArcOfEllipse_H +#define SKETCHERGUI_DrawSketchHandlerArcOfEllipse_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerArcOfEllipse : public DrawSketchHandler +{ +public: + DrawSketchHandlerArcOfEllipse() + : Mode(STATUS_SEEK_First), EditCurve(34) + , rx(0), ry(0), startAngle(0), endAngle(0) + , arcAngle(0), arcAngle_t(0) {} + + virtual ~DrawSketchHandlerArcOfEllipse() = default; + + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_SEEK_Fourth, /**< enum value ----. */ + STATUS_Close + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1 + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + double rx0 = onSketchPos.x - EditCurve[0].x; + double ry0 = onSketchPos.y - EditCurve[0].y; + for (int i=0; i < 16; i++) { + double angle = i*M_PI/16.0; + double rx1 = rx0 * cos(angle) + ry0 * sin(angle); + double ry1 = -rx0 * sin(angle) + ry0 * cos(angle); + EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1); + EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1); + } + EditCurve[33] = EditCurve[1]; + + // Display radius for user + float radius = (onSketchPos - EditCurve[0]).Length(); + + SbString text; + text.sprintf(" (%.1fR,%.1fR)", radius,radius); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centerPoint, + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else if (Mode==STATUS_SEEK_Third) { + // angle between the major axis of the ellipse and the X axis + double a = (EditCurve[1]-EditCurve[0]).Length(); + double phi = atan2(EditCurve[1].y-EditCurve[0].y,EditCurve[1].x-EditCurve[0].x); + + // This is the angle at cursor point + double angleatpoint = acos((onSketchPos.x-EditCurve[0].x+(onSketchPos.y-EditCurve[0].y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); + double b=(onSketchPos.y-EditCurve[0].y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi)); + + for (int i=1; i < 16; i++) { + double angle = i*M_PI/16.0; + double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi); + double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi); + EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx1, EditCurve[0].y + ry1); + EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx1, EditCurve[0].y - ry1); + } + EditCurve[33] = EditCurve[1]; + EditCurve[17] = EditCurve[16]; + + // Display radius for user + SbString text; + text.sprintf(" (%.1fR,%.1fR)", a, b); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + else if (Mode==STATUS_SEEK_Fourth) { // here we differ from ellipse creation + // angle between the major axis of the ellipse and the X axis + double a = (axisPoint-centerPoint).Length(); + double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); + + // This is the angle at cursor point + double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); + double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi))); + + double rxs = startingPoint.x - centerPoint.x; + double rys = startingPoint.y - centerPoint.y; + startAngle = atan2(a*(rys*cos(phi)-rxs*sin(phi)), b*(rxs*cos(phi)+rys*sin(phi))); // eccentric anomaly angle + + double angle1 = atan2(a*((onSketchPos.y - centerPoint.y)*cos(phi)-(onSketchPos.x - centerPoint.x)*sin(phi)), + b*((onSketchPos.x - centerPoint.x)*cos(phi)+(onSketchPos.y - centerPoint.y)*sin(phi)))- startAngle; + + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; + arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; + + for (int i=0; i < 34; i++) { + double angle = startAngle+i*arcAngle/34.0; + double rx1 = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi); + double ry1 = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi); + EditCurve[i] = Base::Vector2d(centerPoint.x + rx1, centerPoint.y + ry1); + } +// EditCurve[33] = EditCurve[1]; +// EditCurve[17] = EditCurve[16]; + + // Display radii and angle for user + SbString text; + text.sprintf(" (%.1fR,%.1fR,%.1fdeg)", a, b, arcAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr4); + return; + } + } + + + + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + EditCurve[0] = onSketchPos; + centerPoint = onSketchPos; + Mode = STATUS_SEEK_Second; + } + else if(Mode==STATUS_SEEK_Second) { + EditCurve[1] = onSketchPos; + axisPoint = onSketchPos; + Mode = STATUS_SEEK_Third; + } + else if(Mode==STATUS_SEEK_Third) { + startingPoint = onSketchPos; + arcAngle = 0.; + arcAngle_t= 0.; + Mode = STATUS_SEEK_Fourth; + } + else { // Fourth + endPoint = onSketchPos; + + Mode = STATUS_Close; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_Close) { + unsetCursor(); + resetPositionText(); + + // angle between the major axis of the ellipse and the X axisEllipse + double a = (axisPoint-centerPoint).Length(); + double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); + + // This is the angle at cursor point + double angleatpoint = acos((startingPoint.x-centerPoint.x+(startingPoint.y-centerPoint.y)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi)))); + double b=abs((startingPoint.y-centerPoint.y-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi))); + + double angle1 = atan2(a*((endPoint.y - centerPoint.y)*cos(phi)-(endPoint.x - centerPoint.x)*sin(phi)), + b*((endPoint.x - centerPoint.x)*cos(phi)+(endPoint.y - centerPoint.y)*sin(phi)))- startAngle; + + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; + arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2; + + bool isOriginalArcCCW=true; + + if (arcAngle > 0) + endAngle = startAngle + arcAngle; + else { + endAngle = startAngle; + startAngle += arcAngle; + isOriginalArcCCW=false; + } + + Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint; + // We always create a CCW ellipse, because we want our XY reference system to be in the +X +Y direction + // Our normal will then always be in the +Z axis (local +Z axis of the sketcher) + + if(a>b) + { + // force second semidiameter to be perpendicular to first semidiamater + majAxisDir = axisPoint - centerPoint; + Base::Vector2d perp(-majAxisDir.y,majAxisDir.x); + perp.Normalize(); + perp.Scale(abs(b)); + minAxisPoint = centerPoint+perp; + majAxisPoint = centerPoint+majAxisDir; + } + else { + // force second semidiameter to be perpendicular to first semidiamater + minAxisDir = axisPoint - centerPoint; + Base::Vector2d perp(minAxisDir.y,-minAxisDir.x); + perp.Normalize(); + perp.Scale(abs(b)); + majAxisPoint = centerPoint+perp; + minAxisPoint = centerPoint+minAxisDir; + endAngle += M_PI/2; + startAngle += M_PI/2; + phi-=M_PI/2; + double t=a; a=b; b=t;//swap a,b + } + + int currentgeoid = getHighestCurveIndex(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of ellipse")); + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfEllipse" + "(Part.Ellipse(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)", + majAxisPoint.x, majAxisPoint.y, + minAxisPoint.x, minAxisPoint.y, + centerPoint.x, centerPoint.y, + startAngle, endAngle, + geometryCreationMode==Construction?"True":"False"); + + currentgeoid++; + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + return false; + } + + Gui::Command::commitCommand(); + + // add auto constraints for the center point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // add suggested constraints for arc + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); + sugConstr2.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); + sugConstr3.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr4.size() > 0) { + createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); + sugConstr4.clear(); + } + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(34); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfEllipse"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint; + double rx, ry, startAngle, endAngle, arcAngle, arcAngle_t; + std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerArcOfEllipse_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h new file mode 100644 index 0000000000..3bb3c9dd62 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h @@ -0,0 +1,358 @@ +/*************************************************************************** + * 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_DrawSketchHandlerArcOfHyperbola_H +#define SKETCHERGUI_DrawSketchHandlerArcOfHyperbola_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerArcOfHyperbola : public DrawSketchHandler +{ +public: + DrawSketchHandlerArcOfHyperbola() + : Mode(STATUS_SEEK_First) + , EditCurve(34) + , arcAngle(0) + , arcAngle_t(0) {} + + virtual ~DrawSketchHandlerArcOfHyperbola() = default; + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_SEEK_Fourth, /**< enum value ----. */ + STATUS_Close + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + EditCurve[1]= onSketchPos; + + // Display radius for user + float radius = (onSketchPos - centerPoint).Length(); + + SbString text; + text.sprintf(" (%.1fR,%.1fR)", radius,radius); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else if (Mode==STATUS_SEEK_Third) { + // angle between the major axis of the hyperbola and the X axis + double a = (axisPoint-centerPoint).Length(); + double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); + + // This is the angle at cursor point + double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); + double b=(onSketchPos.y-centerPoint.y-a*cosh(angleatpoint)*sin(phi))/(sinh(angleatpoint)*cos(phi)); + + if(!boost::math::isnan(b)){ + for (int i=15; i >= -15; i--) { + // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir + //double angle = i*M_PI/16.0; + double angle=i*angleatpoint/15; + double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); + double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); + EditCurve[15+i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); + } + + // Display radius for user + SbString text; + text.sprintf(" (%.1fR,%.1fR)", a, b); + setPositionText(onSketchPos, text); + } + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + else if (Mode==STATUS_SEEK_Fourth) { + // angle between the major axis of the hyperbola and the X axis + double a = (axisPoint-centerPoint).Length(); + double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); + + // This is the angle at cursor point + double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a); + double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi)); + + double startAngle = angleatstartingpoint; + + //double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); + + double angleatpoint = atanh( (((onSketchPos.y-centerPoint.y)*cos(phi)-(onSketchPos.x-centerPoint.x)*sin(phi))*a) / + (((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))*b) ); + + /*double angle1 = angleatpoint - startAngle; + + double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI ; + arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;*/ + + arcAngle = angleatpoint - startAngle; + + //if(!boost::math::isnan(angle1) && !boost::math::isnan(angle2)){ + if (!boost::math::isnan(arcAngle)) { + EditCurve.resize(33); + for (int i=0; i < 33; i++) { + // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir + //double angle=i*angleatpoint/16; + double angle = startAngle+i*arcAngle/32.0; + double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); + double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); + EditCurve[i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); + } + + // Display radius for user + SbString text; + text.sprintf(" (%.1fR,%.1fR)", a, b); + setPositionText(onSketchPos, text); + } + else { + arcAngle=0.; + } + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr4); + return; + } + } + + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + EditCurve[0] = onSketchPos; + centerPoint = onSketchPos; + EditCurve.resize(2); + Mode = STATUS_SEEK_Second; + } + else if(Mode==STATUS_SEEK_Second) { + EditCurve[1] = onSketchPos; + axisPoint = onSketchPos; + EditCurve.resize(31); + Mode = STATUS_SEEK_Third; + } + else if(Mode==STATUS_SEEK_Third) { + startingPoint = onSketchPos; + arcAngle = 0.; + arcAngle_t= 0.; + Mode = STATUS_SEEK_Fourth; + } + else { // Fourth + endPoint = onSketchPos; + + Mode = STATUS_Close; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d /*onSketchPos*/) override + { + if (Mode==STATUS_Close) { + unsetCursor(); + resetPositionText(); + + + // angle between the major axis of the hyperbola and the X axis + double a = (axisPoint-centerPoint).Length(); + double phi = atan2(axisPoint.y-centerPoint.y,axisPoint.x-centerPoint.x); + + // This is the angle at cursor point + double angleatstartingpoint = acosh(((startingPoint.x-centerPoint.x)*cos(phi)+(startingPoint.y-centerPoint.y)*sin(phi))/a); + double b=(startingPoint.y-centerPoint.y-a*cosh(angleatstartingpoint)*sin(phi))/(sinh(angleatstartingpoint)*cos(phi)); + + double startAngle = angleatstartingpoint; + + //double angleatpoint = acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); + + double endAngle = atanh( (((endPoint.y-centerPoint.y)*cos(phi)-(endPoint.x-centerPoint.x)*sin(phi))*a) / + (((endPoint.x-centerPoint.x)*cos(phi)+(endPoint.y-centerPoint.y)*sin(phi))*b) ); + + if (boost::math::isnan(startAngle) || boost::math::isnan(endAngle)) { + sketchgui->purgeHandler(); + Base::Console().Error("Cannot create arc of hyperbola from invalid angles, try again!\n"); + return false; + } + + + bool isOriginalArcCCW=true; + + if (arcAngle > 0) + endAngle = startAngle + arcAngle; + else { + endAngle = startAngle; + startAngle += arcAngle; + isOriginalArcCCW=false; + } + + Base::Vector2d majAxisDir,minAxisDir,minAxisPoint,majAxisPoint; + // We always create a CCW hyperbola, because we want our XY reference system to be in the +X +Y direction + // Our normal will then always be in the +Z axis (local +Z axis of the sketcher) + + if(a>b) + { + // force second semidiameter to be perpendicular to first semidiamater + majAxisDir = axisPoint - centerPoint; + Base::Vector2d perp(-majAxisDir.y,majAxisDir.x); + perp.Normalize(); + perp.Scale(abs(b)); + minAxisPoint = centerPoint+perp; + majAxisPoint = centerPoint+majAxisDir; + } + else { + // force second semidiameter to be perpendicular to first semidiamater + minAxisDir = axisPoint - centerPoint; + Base::Vector2d perp(minAxisDir.y,-minAxisDir.x); + perp.Normalize(); + perp.Scale(abs(b)); + majAxisPoint = centerPoint+perp; + minAxisPoint = centerPoint+minAxisDir; + endAngle += M_PI/2; + startAngle += M_PI/2; + } + + int currentgeoid = getHighestCurveIndex(); + + try { + + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of hyperbola")); + + //Add arc of hyperbola, point and constrain point as focus2. We add focus2 for it to balance + //the intrinsic focus1, in order to balance out the intrinsic invisible focus1 when AOE is + //dragged by its center + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfHyperbola" + "(Part.Hyperbola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%f,%f),%s)", + majAxisPoint.x, majAxisPoint.y, + minAxisPoint.x, minAxisPoint.y, + centerPoint.x, centerPoint.y, + startAngle, endAngle, + geometryCreationMode==Construction?"True":"False"); + + currentgeoid++; + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + return false; + } + + Gui::Command::commitCommand(); + + // add auto constraints for the center point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // add suggested constraints for arc + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); + sugConstr2.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); + sugConstr3.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr4.size() > 0) { + createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); + sugConstr4.clear(); + } + + 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. + Mode = STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(34); + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfHyperbola"); + } + + +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint; + double arcAngle, arcAngle_t; + std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; + +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerArcOfHyperbola_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfParabola.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfParabola.h new file mode 100644 index 0000000000..43ca50c27f --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfParabola.h @@ -0,0 +1,312 @@ +/*************************************************************************** + * 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_DrawSketchHandlerArcOfParabola_H +#define SKETCHERGUI_DrawSketchHandlerArcOfParabola_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerArcOfParabola : public DrawSketchHandler +{ +public: + DrawSketchHandlerArcOfParabola() + : Mode(STATUS_SEEK_First) + , EditCurve(34) + , startAngle(0) + , endAngle(0) + , arcAngle(0) + , arcAngle_t(0) {} + + virtual ~DrawSketchHandlerArcOfParabola() = default; + + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_SEEK_Fourth, /**< enum value ----. */ + STATUS_Close + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + EditCurve[1]= onSketchPos; + + // Display radius for user + float radius = (onSketchPos - focusPoint).Length(); + + SbString text; + text.sprintf(" (F%.1f)", radius); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else if (Mode==STATUS_SEEK_Third) { + double focal = (axisPoint-focusPoint).Length(); + double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); + + // P(U) = O + U*U/(4.*F)*XDir + U*YDir + // + // pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi), + // pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi), + // 0.f); + + // This is the angle at cursor point + double u = + ( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi)); + + for (int i=15; i >= -15; i--) { + double angle=i*u/15; + double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi); + double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi); + EditCurve[15+i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry); + } + + // Display radius for user + SbString text; + text.sprintf(" (F%.1f)", focal); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + else if (Mode==STATUS_SEEK_Fourth) { + double focal = (axisPoint-focusPoint).Length(); + double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); + + // P(U) = O + U*U/(4.*F)*XDir + U*YDir + // + // pnt = Base::Vector3d(pnt0.x + angle * angle / 4 / focal * cos(phi) - angle * sin(phi), + // pnt0.y + angle * angle / 4 / focal * sin(phi) + angle * cos(phi), + // 0.f); + + // This is the angle at starting point + double ustartpoint = + ( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi)); + + double startValue = ustartpoint; + + double u = + ( cos(phi) * (onSketchPos.y - axisPoint.y) - (onSketchPos.x - axisPoint.x) * sin(phi)); + + + arcAngle = u - startValue; + + if (!boost::math::isnan(arcAngle)) { + EditCurve.resize(33); + for (std::size_t i=0; i < 33; i++) { + double angle = startValue+i*arcAngle/32.0; + double rx = angle * angle / 4 / focal * cos(phi) - angle * sin(phi); + double ry = angle * angle / 4 / focal * sin(phi) + angle * cos(phi); + EditCurve[i] = Base::Vector2d(axisPoint.x + rx, axisPoint.y + ry); + } + + SbString text; + text.sprintf(" (F%.1f)", focal); + setPositionText(onSketchPos, text); + } + else { + arcAngle=0.; + } + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr4, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr4); + return; + } + } + + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + EditCurve[0] = onSketchPos; + focusPoint = onSketchPos; + EditCurve.resize(2); + Mode = STATUS_SEEK_Second; + } + else if(Mode==STATUS_SEEK_Second) { + EditCurve[1] = onSketchPos; + axisPoint = onSketchPos; + EditCurve.resize(31); + Mode = STATUS_SEEK_Third; + } + else if(Mode==STATUS_SEEK_Third) { + startingPoint = onSketchPos; + arcAngle = 0.; + arcAngle_t= 0.; + Mode = STATUS_SEEK_Fourth; + } + else { // Fourth + endPoint = onSketchPos; + Mode = STATUS_Close; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d /*onSketchPos*/) override + { + if (Mode==STATUS_Close) { + unsetCursor(); + resetPositionText(); + + double phi = atan2(focusPoint.y-axisPoint.y,focusPoint.x-axisPoint.x); + + double ustartpoint = + ( cos(phi) * (startingPoint.y - axisPoint.y) - (startingPoint.x - axisPoint.x) * sin(phi)); + + double uendpoint = + ( cos(phi) * (endPoint.y - axisPoint.y) - (endPoint.x - axisPoint.x) * sin(phi)); + + double startAngle = ustartpoint; + + double endAngle = uendpoint; + + bool isOriginalArcCCW=true; + + if (arcAngle > 0) { + endAngle = startAngle + arcAngle; + } + else { + endAngle = startAngle; + startAngle += arcAngle; + isOriginalArcCCW=false; + } + + int currentgeoid = getHighestCurveIndex(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch arc of Parabola")); + + //Add arc of parabola + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfParabola" + "(Part.Parabola(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(0,0,1)),%f,%f),%s)", + focusPoint.x, focusPoint.y, + axisPoint.x, axisPoint.y, + startAngle, endAngle, + geometryCreationMode==Construction?"True":"False"); + + currentgeoid++; + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + return false; + } + + Gui::Command::commitCommand(); + + // add auto constraints for the focus point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, currentgeoid+1, Sketcher::PointPos::start); + sugConstr1.clear(); + } + + // add suggested constraints for vertex point + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::mid); + sugConstr2.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::start:Sketcher::PointPos::end); + sugConstr3.clear(); + } + + // add suggested constraints for start of arc + if (sugConstr4.size() > 0) { + createAutoConstraints(sugConstr4, currentgeoid, isOriginalArcCCW?Sketcher::PointPos::end:Sketcher::PointPos::start); + sugConstr4.clear(); + } + + 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. + Mode = STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(34); + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else { + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Create_ArcOfParabola"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d focusPoint, axisPoint, startingPoint, endPoint; + double startAngle, endAngle, arcAngle, arcAngle_t; + std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerArcOfParabola_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h new file mode 100644 index 0000000000..704c91a3fe --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerBSpline.h @@ -0,0 +1,509 @@ +/*************************************************************************** + * 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_DrawSketchHandlerBSpline_H +#define SKETCHERGUI_DrawSketchHandlerBSpline_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerBSpline: public DrawSketchHandler +{ +public: + DrawSketchHandlerBSpline(int constructionMethod) + : Mode(STATUS_SEEK_FIRST_CONTROLPOINT) + , MousePressMode(MOUSE_NOT_PRESSED) + , ConstrMethod(constructionMethod) + , SplineDegree(3) + , IsClosed(false) + { + addSugConstraint(); + applyCursor(); + } + + virtual ~DrawSketchHandlerBSpline() = 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 + }; + + virtual 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) { + + drawControlPolygonToPosition(onSketchPos); + + drawCursorToPosition(onSketchPos); + + if (seekAutoConstraint(sugConstr.back(), onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr.back()); + return; + } + } + } + + virtual 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& e) { + Base::Console().Error("%s\n", e.what()); + 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().size() > 0) { + 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 && ac.GeoId == poleGeoIds[0] && ac.PosId == 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& e) { + Base::Console().Error("%s\n", e.what()); + 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().size() > 0) { + createAutoConstraints(sugConstr.back(), poleGeoIds.back(), Sketcher::PointPos::mid, false); + } + + //static_cast(sketchgui->getObject())->solve(); + + if (!IsClosed) { + addSugConstraint(); + } + + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + prevCursorPosition = onSketchPos; + MousePressMode = MOUSE_NOT_PRESSED; + + return finishCommand(onSketchPos); + } + + virtual 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 + drawControlPolygonToPosition(prevCursorPosition); + drawCursorToPosition(prevCursorPosition); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + // 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 + + return; + } + + virtual void quit(void) 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() + { + Mode = STATUS_SEEK_FIRST_CONTROLPOINT; + applyCursor(); + + SplineDegree = 3; + + sugConstr.clear(); + poleGeoIds.clear(); + BSplinePoles.clear(); + + eraseEditCurve(); + + addSugConstraint(); + + IsClosed = false; + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Create_BSpline"); + } + + void addSugConstraint() { + 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 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)); + + SbString text; + text.sprintf(" (%.1f,%.1fdeg)", length, (angle != -FLOAT_MAX) ? angle * 180 / M_PI : 0); + 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(); + + 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", + SplineDegree, + geometryCreationMode==Construction?"True":"False"); + + 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] && constr->FirstPos == Sketcher::PointPos::mid) { + constr->First = currentgeoid; + constr->FirstPos = Sketcher::PointPos::start; + } + else if(constr->First == poleGeoIds.back() && constr->FirstPos == Sketcher::PointPos::mid) { + constr->First = currentgeoid; + constr->FirstPos = Sketcher::PointPos::end; + } + } + } + + // Constraint pole circles to B-spline. + std::stringstream cstream; + + cstream << "conList = []\n"; + + for (size_t i = 0; i < poleGeoIds.size(); i++) { + cstream << "conList.append(Sketcher.Constraint('InternalAlignment:Sketcher::BSplineControlPoint'," << poleGeoIds[0] + i + << "," << static_cast(Sketcher::PointPos::mid) << "," << currentgeoid << "," << i << "))\n"; + } + + 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 knots on creation + Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + 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 poles of the BSpline. + std::vector BSplinePoles; + + // suggested autoconstraints for poles. + // A new one must be added e.g. using addSugConstraint() before adding a new pole. + std::vector> sugConstr; + + int ConstrMethod; + int SplineDegree; + bool IsClosed; + std::vector poleGeoIds; + Base::Vector2d prevCursorPosition; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerBSpline_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerCarbonCopy.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerCarbonCopy.h new file mode 100644 index 0000000000..ef5ea909d9 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerCarbonCopy.h @@ -0,0 +1,188 @@ +/*************************************************************************** + * 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_DrawSketchHandlerCarbonCopy_H +#define SKETCHERGUI_DrawSketchHandlerCarbonCopy_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class CarbonCopySelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + CarbonCopySelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()), object(obj) + {} + + bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + { + Q_UNUSED(sSubName); + + Sketcher::SketchObject *sketch = static_cast(object); + sketch->setAllowOtherBody(QApplication::keyboardModifiers() == Qt::ControlModifier || QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier)); + sketch->setAllowUnaligned(QApplication::keyboardModifiers() == (Qt::ControlModifier | Qt::AltModifier)); + + this->notAllowedReason = ""; + Sketcher::SketchObject::eReasonList msg; + // Reusing code: All good reasons not to allow a carbon copy + bool xinv = false, yinv = false; + if (!sketch->isCarbonCopyAllowed(pDoc, pObj, xinv, yinv, &msg)){ + switch(msg){ + case Sketcher::SketchObject::rlCircularReference: + this->notAllowedReason = QT_TR_NOOP("Carbon copy would cause a circular dependency."); + break; + case Sketcher::SketchObject::rlOtherDoc: + this->notAllowedReason = QT_TR_NOOP("This object is in another document."); + break; + case Sketcher::SketchObject::rlOtherBody: + this->notAllowedReason = QT_TR_NOOP("This object belongs to another body. Hold Ctrl to allow cross-references."); + break; + case Sketcher::SketchObject::rlOtherBodyWithLinks: + this->notAllowedReason = QT_TR_NOOP("This object belongs to another body and it contains external geometry. Cross-reference not allowed."); + break; + case Sketcher::SketchObject::rlOtherPart: + this->notAllowedReason = QT_TR_NOOP("This object belongs to another part."); + break; + case Sketcher::SketchObject::rlNonParallel: + this->notAllowedReason = QT_TR_NOOP("The selected sketch is not parallel to this sketch. Hold Ctrl+Alt to allow non-parallel sketches."); + break; + case Sketcher::SketchObject::rlAxesMisaligned: + this->notAllowedReason = QT_TR_NOOP("The XY axes of the selected sketch do not have the same direction as this sketch. Hold Ctrl+Alt to disregard it."); + break; + case Sketcher::SketchObject::rlOriginsMisaligned: + this->notAllowedReason = QT_TR_NOOP("The origin of the selected sketch is not aligned with the origin of this sketch. Hold Ctrl+Alt to disregard it."); + break; + default: + break; + } + return false; + } + // Carbon copy only works on sketches that are not disallowed (e.g. would produce a circular reference) + return true; + } +}; + +class DrawSketchHandlerCarbonCopy: public DrawSketchHandler +{ +public: + DrawSketchHandlerCarbonCopy() = default; + virtual ~DrawSketchHandlerCarbonCopy() + { + Gui::Selection().rmvSelectionGate(); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Gui::Selection().getPreselection().pObjectName) + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + /* this 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 */ + return true; + } + + virtual bool onSelectionChanged(const Gui::SelectionChanges& msg) override + { + if (msg.Type == Gui::SelectionChanges::AddSelection) { + App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName); + if (obj == nullptr) + throw Base::ValueError("Sketcher: Carbon Copy: Invalid object in selection"); + + if (obj->getTypeId() == Sketcher::SketchObject::getClassTypeId()) { + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add carbon copy")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "carbonCopy(\"%s\",%s)", + msg.pObjectName, geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + Gui::Selection().clearSelection(); + /* this 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 */ + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add carbon copy: %s\n", e.what()); + Gui::Command::abortCommand(); + } + return true; + } + } + return false; + } + +private: + virtual void activated() override + { + setAxisPickStyle(false); + Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); + Gui::View3DInventorViewer *viewer; + viewer = static_cast(mdi)->getViewer(); + + SoNode* root = viewer->getSceneGraph(); + static_cast(root)->selectionRole.setValue(true); + + Gui::Selection().clearSelection(); + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new CarbonCopySelection(sketchgui->getObject())); + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_CarbonCopy"); + } + + virtual void deactivated() override + { + Q_UNUSED(sketchgui); + setAxisPickStyle(true); + } +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerCarbonCopy_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerCircle.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerCircle.h new file mode 100644 index 0000000000..3f5a875e22 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerCircle.h @@ -0,0 +1,350 @@ +/*************************************************************************** + * 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_DrawSketchHandlerCircle_H +#define SKETCHERGUI_DrawSketchHandlerCircle_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerCircle : public DrawSketchHandler +{ +public: + DrawSketchHandlerCircle() : Mode(STATUS_SEEK_First),EditCurve(34){} + virtual ~DrawSketchHandlerCircle(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_Close + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + double rx0 = onSketchPos.x - EditCurve[0].x; + double ry0 = onSketchPos.y - EditCurve[0].y; + for (int i=0; i < 16; i++) { + double angle = i*M_PI/16.0; + double rx = rx0 * cos(angle) + ry0 * sin(angle); + double ry = -rx0 * sin(angle) + ry0 * cos(angle); + EditCurve[1+i] = Base::Vector2d(EditCurve[0].x + rx, EditCurve[0].y + ry); + EditCurve[17+i] = Base::Vector2d(EditCurve[0].x - rx, EditCurve[0].y - ry); + } + EditCurve[33] = EditCurve[1]; + + // Display radius for user + float radius = (onSketchPos - EditCurve[0]).Length(); + + SbString text; + text.sprintf(" (%.1fR)", radius); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0], + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + EditCurve[0] = onSketchPos; + Mode = STATUS_SEEK_Second; + } else { + EditCurve[1] = onSketchPos; + Mode = STATUS_Close; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_Close) { + double rx = EditCurve[1].x - EditCurve[0].x; + double ry = EditCurve[1].y - EditCurve[0].y; + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle" + "(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)", + EditCurve[0].x, EditCurve[0].y, + sqrt(rx*rx + ry*ry), + geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add circle: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + // add auto constraints for the center point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // add suggested constraints for circumference + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::none); + sugConstr2.clear(); + } + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(34); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Circle"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; + +}; + +class DrawSketchHandler3PointCircle : public DrawSketchHandler +{ +public: + DrawSketchHandler3PointCircle() + : Mode(STATUS_SEEK_First),EditCurve(2),radius(1),N(32.0){} + virtual ~DrawSketchHandler3PointCircle(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_SEEK_Third, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode == STATUS_SEEK_Second || Mode == STATUS_SEEK_Third) { + try + { + if (Mode == STATUS_SEEK_Second) + CenterPoint = EditCurve[N+1] = (onSketchPos - FirstPoint)/2 + FirstPoint; + else + CenterPoint = EditCurve[N+1] = Part::Geom2dCircle::getCircleCenter(FirstPoint, SecondPoint, onSketchPos); + radius = (onSketchPos - CenterPoint).Length(); + double lineAngle = GetPointAngle(CenterPoint, onSketchPos); + + // Build a N point circle + for (int i=1; i < N; i++) { + // Start at current angle + double angle = i*2*M_PI/N + lineAngle; // N point closed circle has N segments + EditCurve[i] = Base::Vector2d(CenterPoint.x + radius*cos(angle), + CenterPoint.y + radius*sin(angle)); + } + // Beginning and end of curve should be exact + EditCurve[0] = EditCurve[N] = onSketchPos; + + // Display radius and start angle + // This lineAngle will report counter-clockwise from +X, not relatively + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", (float) radius, (float) lineAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (Mode == STATUS_SEEK_Second) { + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + else { + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + } + catch(Base::ValueError &e) { + e.ReportException(); + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_SEEK_First) { + // N point curve + center + endpoint + EditCurve.resize(N+2); + FirstPoint = onSketchPos; + + Mode = STATUS_SEEK_Second; + } + else if (Mode == STATUS_SEEK_Second) { + SecondPoint = onSketchPos; + + Mode = STATUS_SEEK_Third; + } + else { + EditCurve.resize(N); + + drawEdit(EditCurve); + applyCursor(); + Mode = STATUS_End; + } + + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + // Need to look at. rx might need fixing. + if (Mode==STATUS_End) { + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch circle")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Circle" + "(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%s)", + CenterPoint.x, CenterPoint.y, + radius, + geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add circle: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + // Auto Constraint first picked point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::none); + sugConstr1.clear(); + } + + // Auto Constraint second picked point + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::none); + sugConstr2.clear(); + } + + // Auto Constraint third picked point + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, getHighestCurveIndex(), Sketcher::PointPos::none); + sugConstr3.clear(); + } + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_3PointCircle"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + Base::Vector2d CenterPoint, FirstPoint, SecondPoint; + double radius, N; // N should be even + std::vector sugConstr1, sugConstr2, sugConstr3; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerCircle_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h new file mode 100644 index 0000000000..342048cc7f --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerEllipse.h @@ -0,0 +1,808 @@ +/*************************************************************************** + * 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_DrawSketchHandlerEllipse_H +#define SKETCHERGUI_DrawSketchHandlerEllipse_H + +#include + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +/* Ellipse ==============================================================================*/ +/** + * @brief This class handles user interaction to draw and save the ellipse + * + * Two construction methods are implemented: + * -Periapsis, apoapsis, and b; and + * -Center, periapsis, and b. + * + * The first method limits the ellipse to a circle, while the second method allows for + * swapping of the semi-major and semi-minor axes. + * + * We use three reference frames in this class. The first (and primary), is the cartesian + * frame of the sketcher; all our work begins and ends in this frame. The second is the + * perifocal frame of the ellipse using polar coordinates. We use this frame for naming + * conventions and working with the ellipse. The last is a rotated right-handed cartesian + * frame centered at the ellipse center with the +X direction towards periapsis, +Z out of + * screen. + * + * When working with an ellipse in the perifocal frame, the following equations are useful: + * + * \f{eqnarray*}{ + * r &\equiv& \textrm{ radial distance from the focus to a point on the ellipse}\\ + * r_a &\equiv& \textrm{ radial distance from the focus to apopasis}\\ + * r_p &\equiv& \textrm{ radial distance from the focus to periapsis}\\ + * a &\equiv& \textrm{ length of the semi-major axis, colloquially 'radius'}\\ + * b &\equiv& \textrm{ length of the semi-minor axis, colloquially 'radius'}\\ + * e &\equiv& \textrm{ eccentricity of the ellipse}\\ + * \theta_b &\equiv& \textrm{ angle to the intersection of the semi-minor axis and the ellipse, relative to the focus}\\ + * ae &\equiv& \textrm{ distance from the focus to the centroid}\\ + * r &=& \frac{a(1-e^2)}{1+e\cos(\theta)} = \frac{r_a(1-e)}{1+e\cos(\theta)} = \frac{r_p(1+e)}{1+e\cos(\theta)}\\ + * r_a &=& a(1-e)\\ + * r_p &=& a(1+e)\\ + * a &=& \frac{r_p+r_a}{2}\\ + * b &=& a\sqrt{1-e^2}\\ + * e &=& \frac{r_a-r_p}{r_a+r_p} = \sqrt{1-\frac{b^2}{a^2}}\\ + * \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N\pi + * \f} + * + */ +class DrawSketchHandlerEllipse : public DrawSketchHandler +{ +public: + DrawSketchHandlerEllipse(int constructionMethod) + : mode(STATUS_Close) + , method(CENTER_PERIAPSIS_B) + , constrMethod(constructionMethod) + , a(0), b(0), e(0), ratio(0), ae(0) + , num(0), r(0), theta(0), phi(0) + , editCurve(33), fixedAxisLength(0) + { + } + virtual ~DrawSketchHandlerEllipse(){} + /// Mode table, describes what step of the process we are in + enum SelectMode { + STATUS_SEEK_PERIAPSIS, /**< enum value, looking for click to set periapsis. */ + STATUS_SEEK_APOAPSIS, /**< enum value, looking for click to set apoapsis. */ + STATUS_SEEK_CENTROID, /**< enum value, looking for click to set centroid. */ + STATUS_SEEK_A, /**< enum value, looking for click to set a. */ + STATUS_SEEK_B, /**< enum value, looking for click to set b. */ + STATUS_Close /**< enum value, finalizing and saving ellipse. */ + }; + /// Construction methods, describes the method used to construct the ellipse + enum ConstructionMethod { + CENTER_PERIAPSIS_B, /**< enum value, click on center, then periapsis, then b point. */ + PERIAPSIS_APOAPSIS_B /**< enum value, click on periapsis, then apoapsis, then b point. */ + }; + + /** + * @brief Updates the ellipse when the cursor moves + * @param onSketchPos the position of the cursor on the sketch + */ + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (method == PERIAPSIS_APOAPSIS_B) { + if (mode == STATUS_SEEK_PERIAPSIS) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } else if (mode == STATUS_SEEK_APOAPSIS) { + solveEllipse(onSketchPos); + approximateEllipse(); + + // Display radius for user + float semiMajorRadius = a * 2; + SbString text; + text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius); + setPositionText(onSketchPos, text); + + drawEdit(editCurve); + // Suggestions for ellipse and curves are disabled because many tangent constraints + // need an intermediate point or line. + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } else if (mode == STATUS_SEEK_B) { + solveEllipse(onSketchPos); + approximateEllipse(); + + // Display radius for user + SbString text; + text.sprintf(" (%.1fR,%.1fR)", a, b); + setPositionText(onSketchPos, text); + + drawEdit(editCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f,0.f), + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + } else { // method is CENTER_PERIAPSIS_B + if (mode == STATUS_SEEK_CENTROID) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { // TODO: ellipse prio 1 + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } else if (mode == STATUS_SEEK_PERIAPSIS) { + solveEllipse(onSketchPos); + approximateEllipse(); + + // Display radius for user + float semiMajorRadius = a * 2; + SbString text; + text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius); + setPositionText(onSketchPos, text); + + drawEdit(editCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - centroid, + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } else if ((mode == STATUS_SEEK_A) || (mode == STATUS_SEEK_B)) { + solveEllipse(onSketchPos); + approximateEllipse(); + + // Display radius for user + SbString text; + text.sprintf(" (%.1fR,%.1fR)", a, b); + setPositionText(onSketchPos, text); + + drawEdit(editCurve); + if (seekAutoConstraint(sugConstr3, onSketchPos, onSketchPos - centroid, + AutoConstraint::CURVE)) { + renderSuggestConstraintsCursor(sugConstr3); + return; + } + } + } + applyCursor(); + } + + /** + * @brief Changes drawing mode on user-click + * @param onSketchPos the position of the cursor on the sketch + * @return + */ + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (method == PERIAPSIS_APOAPSIS_B) { + if (mode == STATUS_SEEK_PERIAPSIS) { + periapsis = onSketchPos; + mode = STATUS_SEEK_APOAPSIS; + } + else if (mode == STATUS_SEEK_APOAPSIS) { + apoapsis = onSketchPos; + mode = STATUS_SEEK_B; + } + else { + mode = STATUS_Close; + } + } else { // method is CENTER_PERIAPSIS_B + if (mode == STATUS_SEEK_CENTROID) { + centroid = onSketchPos; + mode = STATUS_SEEK_PERIAPSIS; + } + else if (mode == STATUS_SEEK_PERIAPSIS) { + periapsis = onSketchPos; + mode = STATUS_SEEK_B; + } + else { + mode = STATUS_Close; + } + } + return true; + } + + /** + * @brief Calls \c saveEllipse() after last user input + * @param onSketchPos the position of the cursor on the sketch + * @return + */ + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (mode == STATUS_Close) { + saveEllipse(); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); + + if(continuousMode){ + if (constrMethod == 0) { + method = CENTER_PERIAPSIS_B; + mode = STATUS_SEEK_CENTROID; + } else { + method = PERIAPSIS_APOAPSIS_B; + mode = STATUS_SEEK_PERIAPSIS; + } + } + } + return true; + } + +private: + /** + * @brief Slot called when the create ellipse command is activated + * @param sketchgui A pointer to the active sketch + */ + virtual void activated() override + { + if (constrMethod == 0) { + method = CENTER_PERIAPSIS_B; + mode = STATUS_SEEK_CENTROID; + } else { + method = PERIAPSIS_APOAPSIS_B; + mode = STATUS_SEEK_PERIAPSIS; + } + } + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Ellipse"); + } + +protected: + std::vector sugConstr1, sugConstr2, sugConstr3; +private: + SelectMode mode; + /// the method of constructing the ellipse + ConstructionMethod method; + int constrMethod; + /// periapsis position vector, in standard position in sketch coordinate system + Base::Vector2d periapsis; + /// apoapsis position vector, in standard position in sketch coordinate system + Base::Vector2d apoapsis; + /// centroid position vector, in standard position in sketch coordinate system + Base::Vector2d centroid; + /** + * @brief position vector of positive b point, in standard position in sketch coordinate system + * I.E. in polar perifocal system, the first intersection of the semiminor axis with the ellipse + * as theta increases from 0. This always happens when: + * \f{eqnarray*}{ + * \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N 2\pi + * \f} + * + * In a rotated R^3 cartesian system, centered at the centroid, +X towards periapsis, and + * +Z coming out of the sketch, this b position is in the +Y direction from the centroid. + */ + Base::Vector2d positiveB; + /// the other b position + Base::Vector2d negativeB; + /// cart. position vector for primary focus + Base::Vector2d f; + /// cart. position vector for other focus + Base::Vector2d fPrime; + /// Unit vector for apse line + Base::Vector2d apseHat; + /// length of semimajor axis, i.e. 'radius' colloquially + double a; + /// length of semiminor axis, i.e. 'radius' colloquially + double b; + /// eccentricity [unitless] + double e; + /// optimization, holds a term that helps calculate b in terms of a and e + double ratio; + /// holds product of a * e + double ae; + /// holds numerator of orbit equation of form a(1-e^2) + double num; + /// holds a radial distance from f to the ellipse for a given theta + double r; + /// angle of a point in a perifocal frame centered at f + double theta; + /// angle of apse line relative to sketch coordinate system + double phi; + /// holds a position vector for a point on the ellipse from f + Base::Vector2d pos; + /// holds a position vector for a point on the ellipse from fPrime + Base::Vector2d posPrime; + /// holds position vectors for a points on the ellipse + std::vector editCurve; + /// local i_hat vector for ellipse, from centroid to periapsis + Base::Vector3d iPrime; + /// local j_hat vector for ellipse, from centroid to b point + Base::Vector3d jPrime; + /// length (radius) of the fixed axis + double fixedAxisLength; + /// position vector of fixed axis point in sketch coordinates + Base::Vector2d fixedAxis; + + /** + * @brief Computes a vector of 2D points representing an ellipse + * @param onSketchPos Current position of the cursor on the sketch + */ + void solveEllipse(Base::Vector2d onSketchPos) + { + const double GOLDEN_RATIO = 1.6180339887; + Base::Vector3d k(0,0,1); + + if (method == PERIAPSIS_APOAPSIS_B) { + if (mode == STATUS_SEEK_APOAPSIS) { + apoapsis = onSketchPos; + } + a = (apoapsis - periapsis).Length() / 2; + apseHat = (periapsis - apoapsis); + apseHat.Normalize(); + centroid = apseHat; + centroid.Scale(-1 * a); + centroid = periapsis + centroid; + if (mode == STATUS_SEEK_APOAPSIS) { + // for first step, we draw an ellipse inscribed in a golden rectangle + ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887 + e = sqrt(ratio); // ~= 0.7861513777 + b = a * ratio; + } + else if (mode == STATUS_SEEK_B) { + // Get the closest distance from onSketchPos to apse line, as a 'requested' value for b + Base::Vector2d cursor = Base::Vector2d(onSketchPos - f); // vector from f to cursor pos + // decompose cursor with a projection, then length of w_2 will give us b + Base::Vector2d w_1 = cursor; + w_1.ProjectToLine(cursor, (periapsis - apoapsis)); // projection of cursor line onto apse line + Base::Vector2d w_2 = (cursor - w_1); + b = w_2.Length(); + + // limit us to ellipse or circles + if (b > a) { + b = a; + } + + e = sqrt(1 - ((b * b) / (a * a))); + ratio = sqrt(1 - (e*e)); + } + ae = a * e; + f = apseHat; + f.Scale(ae); + f = centroid + f; + fPrime = apseHat; + fPrime.Scale(-1 * ae); + fPrime = centroid + fPrime; + phi = atan2(apseHat.y, apseHat.x); + num = a * (1 - (e * e)); + // The ellipse is now solved + } else { // method == CENTER_PERIAPSIS_B + if (mode == STATUS_SEEK_PERIAPSIS) { + // solve the ellipse inscribed in a golden rectangle + periapsis = onSketchPos; + a = (centroid - periapsis).Length(); + iPrime.x = periapsis.x - centroid.x; + iPrime.y = periapsis.y - centroid.y; + iPrime.z = 0; + jPrime = k % iPrime; // j = k cross i + + // these are constant for any ellipse inscribed in a golden rectangle + ratio = 1 / GOLDEN_RATIO; // ~= 0.6180339887 + e = sqrt(ratio); // ~= 0.7861513777 + + b = a * ratio; + ae = a * e; + apseHat = (periapsis - centroid); + apseHat.Normalize(); + f = apseHat; + f.Scale(ae); + f = centroid + f; + fPrime = apseHat; + fPrime.Scale(-1 * ae); + fPrime = centroid + fPrime; + apoapsis = apseHat; + apoapsis.Scale(-1 * a); + apoapsis = centroid + apoapsis; + phi = atan2(apseHat.y, apseHat.x); + num = a * (1 - (e * e)); + fixedAxisLength = a; + fixedAxis = periapsis; + } else if ((mode == STATUS_SEEK_B) || (mode == STATUS_SEEK_A)) { + // while looking for the last click, we may switch back and forth + // between looking for a b point and looking for periapsis, so ensure + // we are in the right mode + Base::Vector2d cursor = Base::Vector2d(onSketchPos - centroid); // vector from centroid to cursor pos + // decompose cursor with a projection, then length of w_2 will give us b + Base::Vector2d w_1 = cursor; + w_1.ProjectToLine(cursor, (fixedAxis - centroid)); // projection of cursor line onto fixed axis line + Base::Vector2d w_2 = (cursor - w_1); + if (w_2.Length() > fixedAxisLength) { + // b is fixed, we are seeking a + mode = STATUS_SEEK_A; + jPrime.x = (fixedAxis - centroid).x; + jPrime.y = (fixedAxis - centroid).y; + jPrime.Normalize(); + iPrime = jPrime % k; // cross + b = fixedAxisLength; + a = w_2.Length(); + } else { + // a is fixed, we are seeking b + mode = STATUS_SEEK_B; + iPrime.x = (fixedAxis - centroid).x; + iPrime.y = (fixedAxis - centroid).y; + iPrime.Normalize(); + jPrime = k % iPrime; // cross + a = fixedAxisLength; + b = w_2.Length(); + } + // now finish solving the ellipse + periapsis.x = centroid.x + (iPrime * a).x; + periapsis.y = centroid.y + (iPrime * a).y; + e = sqrt(1 - ((b * b) / (a * a))); + ratio = sqrt(1 - (e*e)); + ae = a * e; + apseHat = (periapsis - centroid); + apseHat.Normalize(); + f = apseHat; + f.Scale(ae); + f = centroid + f; + fPrime = apseHat; + fPrime.Scale(-1 * ae); + fPrime = centroid + fPrime; + apoapsis = apseHat; + apoapsis.Scale(-1 * a); + apoapsis = centroid + apoapsis; + phi = atan2(apseHat.y, apseHat.x); + num = a * (1 - (e * e)); + } + } + } + + + /** + * @brief Computes a sequence of 2D vectors to approximate the ellipse + */ + void approximateEllipse() + { + // We will approximate the ellipse as a sequence of connected chords + // Number of points per quadrant of the ellipse + int n = static_cast((editCurve.size() - 1) / 4); + + // We choose points in the perifocal frame then translate them to sketch cartesian. + // This gives us a better approximation of an ellipse, i.e. more points where the + // curvature is higher. If the eccentricity is high, we shift the points a bit towards + // the semi-minor axis. + double partitionAngle = (M_PI - atan2(b, ae)) / n; + double radianShift = 0; + if (e > 0.8) {radianShift = (partitionAngle / 5) * 4;} + for (int i=0; i < n; i++) { + theta = i * partitionAngle; + if (i > 0) {theta = theta + radianShift;} + r = num / (1 + (e * cos(theta))); + // r(pi/2) is semi-latus rectum, if we need it + pos.x = r*cos(theta+phi); // phi rotates, sin/cos translate + pos.y = r*sin(theta+phi); + pos = pos + f; + posPrime.x = r*cos(theta+phi+M_PI); + posPrime.y = r*sin(theta+phi+M_PI); + posPrime = posPrime + fPrime; + // over the loop, loads Quadrant I points, by using f as origin + editCurve[i] = pos; + // over the loop, loads Quadrant III points, by using fPrime as origin + editCurve[(2*n) + i] = posPrime; + // load points with negative theta angles (i.e. cw) + if (i>0) { + pos.x = r*cos(-1*theta+phi); + pos.y = r*sin(-1*theta+phi); + pos = pos + f; + // loads Quadrant IV points + editCurve[(4*n) - i] = pos; + posPrime.x = r*cos(-1*theta+phi+M_PI); + posPrime.y = r*sin(-1*theta+phi+M_PI); + posPrime = posPrime + fPrime; + // loads Quadrant II points + editCurve[(2*n) - i] = posPrime; + } + } + // load pos & neg b points + theta = M_PI - atan2(b, ae); // the angle from f to the positive b point + r = num / (1 + (e * cos(theta))); + pos.x = r*cos(theta+phi); + pos.y = r*sin(theta+phi); + pos = pos + f; + editCurve[n] = pos; // positive + pos.x = r*cos(-1*theta+phi); + pos.y = r*sin(-1*theta+phi); + pos = pos + f; + editCurve[(3*n)] = pos; // negative + // force the curve to be a closed shape + editCurve[(4*n)] = editCurve[0]; + } + + /** + * @brief Prints the ellipse data to STDOUT as an GNU Octave script + * @param onSketchPos position of the cursor on the sketch + */ + void ellipseToOctave(Base::Vector2d /*onSketchPos*/) + { + int n = static_cast((editCurve.size() - 1) / 4); + + // send a GNU Octave script to stdout to plot points for debugging + std::ostringstream octave; + octave << std::fixed << std::setprecision(12); + octave << "\nclear all;\nclose all;\nclc;\n\n"; + octave << "periapsis = [" << periapsis.x << ", " << periapsis.y << "];\n"; + octave << "apoapsis = [" << apoapsis.x << ", " << apoapsis.y << "];\n"; + octave << "positiveB = [" << editCurve[n].x << ", " << editCurve[n].y << "];\n"; + octave << "apseHat = [" << apseHat.x << ", " << apseHat.y << "];\n"; + octave << "a = " << a << ";\n"; + octave << "b = " << b << ";\n"; + octave << "eccentricity = " << e << ";\n"; + octave << "centroid = [" << centroid.x << ", " << centroid.y << "];\n"; + octave << "f = [" << f.x << ", " << f.y << "];\n"; + octave << "fPrime = [" << fPrime.x << ", " << fPrime.y << "];\n"; + octave << "phi = " << phi << ";\n\n"; + octave << "x = ["; + for (int i=0; i < 4*n + 1; i++) { + octave << editCurve[i].x; + if (i < 4*n) { + octave << ", "; + } + } + octave << "];\n"; + octave << "y = ["; + for (int i=0; i < 4*n + 1; i++) { + octave << editCurve[i].y; + if (i < 4*n) { + octave << ", "; + } + } + octave << "];\n\n"; + octave << "% Draw ellipse points in red;\n"; + octave << "plot (x, y, \"r.\", \"markersize\", 5);\n"; + octave << "axis ([-300, 300, -300, 300], \"square\");grid on;\n"; + octave << "hold on;\n\n"; + octave << "% Draw centroid in blue, f in cyan, and fPrime in magenta;\n"; + octave << "plot(centroid(1), centroid(2), \"b.\", \"markersize\", 5);\n"; + octave << "plot(f(1), f(2), \"c.\", \"markersize\", 5);\n"; + octave << "plot(fPrime(1), fPrime(2), \"m.\", \"markersize\", 5);\n"; + octave << "n = [periapsis(1) - f(1), periapsis(2) - f(2)];\n"; + octave << "h = quiver(f(1),f(2),n(1),n(2), 0);\n"; + octave << "set (h, \"maxheadsize\", 0.1);\n\n"; + octave << "% Draw the three position vectors used for Gui::Command::doCommand(...)\n"; + octave << "periapsisVec = quiver(0,0,periapsis(1),periapsis(2), 0);\n"; + octave << "set (periapsisVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n"; + octave << "centroidVec = quiver(0,0,centroid(1),centroid(2), 0);\n"; + octave << "set (centroidVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n"; + octave << "bVec = quiver(0,0,positiveB(1),positiveB(2), 0);\n"; + octave << "set (bVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n\n"; + octave << "% Draw the local x & y basis vectors, scaled to a and b, in red and blue, respectively\n"; + octave << "xLocalVec = quiver(centroid(1),centroid(2),periapsis(1)-centroid(1),periapsis(2)-centroid(2), 0);\n"; + octave << "set (xLocalVec, \"maxheadsize\", 0.01, \"color\", \"red\");\n"; + octave << "yLocalVec = quiver(centroid(1),centroid(2), positiveB(1)-centroid(1), positiveB(2)-centroid(2), 0);\n"; + octave << "set (yLocalVec, \"maxheadsize\", 0.01, \"color\", \"blue\");\nhold off;\n"; + qDebug() << QString::fromStdString(octave.str()); + } + + /** + * @brief Finalizes and saves the drawn ellipse + * @return nothing + */ + void saveEllipse() + { + unsetCursor(); + resetPositionText(); + + /* There are a couple of issues with Gui::Command::doCommand(...) and + * GC_MakeEllipse(...) that cause bugs if not handled properly, even + * when we give them a mathematically-correct ellipse. + * + * GC_MakeEllipse may fail with a gce_InvertAxis error for a small + * circular ellipse when floating point roundoff or representation + * errors make the b axis slightly larger than the a axis. + * + * A similar, larger, issue arises in Gui::Command::doCommand(...) because + * we cast our double vector components into strings with a fixed + * precision of six, and then create new doubles from the strings + * in EllipsePy::PyInit(...). Thus, by the time we call GC_MakeEllipse(...) + * in EllipsePy::PyInit(...), our ellipse may not be valid anymore + * because b is now greater than a. + * + * To handle these issues, we simulate the effects Gui::Command::doCommand(...) + * has on our ellipse, and we adjust our ellipse parameters until + * GC_MakeEllipse successfully creates an ellipse with our mangled + * parameters. + * + * In almost all cases, we only have to make our test ellipse one time; + * it is only in the rare edge cases that require repeated test ellipses + * until we get a valid one, or fail due to excessive attempts. With a + * limit of 25 attempts, I have been unable to make it fail. + */ + + // simulate loss of precision in centroid, periapsis, and apoapsis + char cx[64]; + char cy[64]; + char px[64]; + char py[64]; + char ax[64]; + char ay[64]; + sprintf(cx, "%.6lf\n", centroid.x); + sprintf(cy, "%.6lf\n", centroid.y); + sprintf(px, "%.6lf\n", periapsis.x); + sprintf(py, "%.6lf\n", periapsis.y); + sprintf(ax, "%.6lf\n", apoapsis.x); + sprintf(ay, "%.6lf\n", apoapsis.y); + centroid.x = atof(cx); + centroid.y = atof(cy); + periapsis.x = atof(px); + periapsis.y = atof(py); + apoapsis.x = atof(ax); + apoapsis.y = atof(ay); + double majorLength = (periapsis - apoapsis).Length(); + double minorLength = 0; + + /* GC_MakeEllipse requires a right-handed coordinate system, with +X + * from centroid to periapsis, +Z out of the page. + */ + Base::Vector3d k(0,0,1); + Base::Vector3d i(periapsis.x - centroid.x, periapsis.y - centroid.y, 0); + Base::Vector3d j = k % i; // j = k cross i + double beta = 1e-7; + int count = 0; + int limit = 25; // no infinite loops! + bool success = false; + double tempB = b; + + // adjust b until our mangled vectors produce a good ellipse in GC_MakeEllipse + // and the mangled major and minor lines in LinePy::PyInit(...) are such that + // major is at least slightly larger than minor + do { + tempB = b - double(count * beta); + j = j.Normalize() * tempB; + positiveB.x = centroid.x + j.x; + positiveB.y = centroid.y + j.y; + negativeB.x = centroid.x + (j.x * -1); + negativeB.y = centroid.y + (j.y * -1); + char bpx[64]; + char bpy[64]; + char bnx[64]; + char bny[64]; + sprintf(bpx, "%.6lf\n", positiveB.x); + sprintf(bpy, "%.6lf\n", positiveB.y); + sprintf(bnx, "%.6lf\n", negativeB.x); + sprintf(bny, "%.6lf\n", negativeB.y); + positiveB.x = atof(bpx); + positiveB.y = atof(bpy); + negativeB.x = atof(bnx); + negativeB.y = atof(bny); + GC_MakeEllipse me(gp_Pnt(periapsis.x,periapsis.y,0), + gp_Pnt(positiveB.x,positiveB.y,0), + gp_Pnt(centroid.x,centroid.y,0)); + minorLength = (negativeB - positiveB).Length(); + count++; + success = me.IsDone() && (minorLength + beta < majorLength); + } while (!success && (count <= limit)); + if (!success) { + qDebug() << "Failed to create a valid mangled ellipse after" << count << "attempts"; + } + + // save any changes to b, then recalculate ellipse as required due to change in b + b = tempB; + e = sqrt(1 - ((b * b) / (a * a))); + ae = a * e; + f = apseHat; + f.Scale(ae); + f = centroid + f; + fPrime = apseHat; + fPrime.Scale(-1 * ae); + fPrime = centroid + fPrime; + + int currentgeoid = getHighestCurveIndex(); // index of the ellipse we just created + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch ellipse")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Ellipse" + "(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", + periapsis.x, periapsis.y, + positiveB.x, positiveB.y, + centroid.x, centroid.y, + geometryCreationMode==Construction?"True":"False"); + + currentgeoid++; + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "exposeInternalGeometry(%d)", currentgeoid); + } + catch (const Base::Exception& e) { + Base::Console().Error("%s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + return; + } + + Gui::Command::commitCommand(); + + if (method == CENTER_PERIAPSIS_B) { + // add auto constraints for the center point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::mid); + sugConstr1.clear(); + } + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); + sugConstr2.clear(); + } + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, currentgeoid, Sketcher::PointPos::none); + sugConstr3.clear(); + } + } + + if (method == PERIAPSIS_APOAPSIS_B) { + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, currentgeoid, Sketcher::PointPos::none); + sugConstr1.clear(); + } + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, currentgeoid, Sketcher::PointPos::none); + sugConstr2.clear(); + } + if (sugConstr3.size() > 0) { + createAutoConstraints(sugConstr3, currentgeoid, Sketcher::PointPos::none); + sugConstr3.clear(); + } + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + // This code enables the continuous creation mode. + if (constrMethod == 0) { + method = CENTER_PERIAPSIS_B; + mode = STATUS_SEEK_CENTROID; + } else { + method = PERIAPSIS_APOAPSIS_B; + mode = STATUS_SEEK_PERIAPSIS; + } + editCurve.clear(); + drawEdit(editCurve); + + 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. + editCurve.resize(33); + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + + } +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerEllipse_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerExtend.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerExtend.h new file mode 100644 index 0000000000..5c4e704a97 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerExtend.h @@ -0,0 +1,324 @@ +/*************************************************************************** + * 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_DrawSketchHandlerExtend_H +#define SKETCHERGUI_DrawSketchHandlerExtend_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class ExtendSelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + ExtendSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()) + , object(obj) + , disabled(false) + {} + + bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + if (disabled) + return true; + std::string element(sSubName); + if (element.substr(0, 4) == "Edge") { + int GeoId = std::atoi(element.substr(4, 4000).c_str()) - 1; + Sketcher::SketchObject *Sketch = static_cast(object); + const Part::Geometry *geom = Sketch->getGeometry(GeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || + geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) + return true; + } + return false; + } + + void setDisabled(bool isDisabled) { + disabled = isDisabled; + } +protected: + bool disabled; +}; + + +class DrawSketchHandlerExtend: public DrawSketchHandler +{ +public: + DrawSketchHandlerExtend() + : Mode(STATUS_SEEK_First) + , EditCurve(2) + , BaseGeoId(-1) + , ExtendFromStart(false) + , SavedExtendFromStart(false) + , Increment(0) {} + + virtual ~DrawSketchHandlerExtend() + { + Gui::Selection().rmvSelectionGate(); + } + enum SelectMode { + STATUS_SEEK_First, + STATUS_SEEK_Second, + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode == STATUS_SEEK_Second) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg = static_cast(geom); + // project point to the existing curve + Base::Vector3d start3d = lineSeg->getStartPoint(); + Base::Vector3d end3d = lineSeg->getEndPoint(); + + Base::Vector2d startPoint = Base::Vector2d(start3d.x, start3d.y); + Base::Vector2d endPoint = Base::Vector2d(end3d.x, end3d.y); + Base::Vector2d recenteredLine = endPoint - startPoint; + Base::Vector2d recenteredPoint = onSketchPos - startPoint; + Base::Vector2d projection; + projection.ProjectToLine(recenteredPoint, recenteredLine); + if (recenteredPoint.Length() < recenteredPoint.Distance(recenteredLine)) { + EditCurve[0] = startPoint + projection; + EditCurve[1] = endPoint; + } else { + EditCurve[0] = startPoint; + EditCurve[1] = startPoint + projection; + } + /** + * If in-curve, the intuitive behavior is for the line to shrink an amount from + * the original click-point. + * + * If out-of-curve, the intuitive behavior is for the closest line endpoint to + * expand. + */ + bool inCurve = (projection.Length() < recenteredLine.Length() + && projection.GetAngle(recenteredLine) < 0.1); // Two possible values here, M_PI and 0, but 0.1 is to avoid floating point problems. + if (inCurve) { + Increment = SavedExtendFromStart ? -1 * projection.Length() : projection.Length() - recenteredLine.Length(); + ExtendFromStart = SavedExtendFromStart; + } else { + ExtendFromStart = onSketchPos.Distance(startPoint) < onSketchPos.Distance(endPoint); + Increment = ExtendFromStart ? projection.Length() : projection.Length() - recenteredLine.Length(); + } + drawEdit(EditCurve); + + } else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *arc = static_cast(geom); + Base::Vector3d center = arc->getCenter(); + double radius = arc->getRadius(); + + double start, end; + arc->getRange(start, end, true); + double arcAngle = end - start; + + Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y); + Base::Vector2d startAngle = Base::Vector2d(cos(start), sin(start)); + Base::Vector2d endAngle = Base::Vector2d(cos(end), sin(end)); + + Base::Vector2d arcHalf = Base::Vector2d(cos(start + arcAngle/ 2.0), sin(start+ arcAngle / 2.0)); + double angleToEndAngle = angle.GetAngle(endAngle); + double angleToStartAngle = angle.GetAngle(startAngle); + + + double modStartAngle = start; + double modArcAngle = end - start; + bool outOfArc = arcHalf.GetAngle(angle) * 2.0 > arcAngle; + if (ExtendFromStart) { + bool isCCWFromStart = crossProduct(angle, startAngle) < 0; + if (outOfArc) { + if (isCCWFromStart) { + modStartAngle -= 2*M_PI - angleToStartAngle; + modArcAngle += 2*M_PI - angleToStartAngle; + } else { + modStartAngle -= angleToStartAngle; + modArcAngle += angleToStartAngle; + } + } else { + if (isCCWFromStart) { + modStartAngle += angleToStartAngle; + modArcAngle -= angleToStartAngle; + } else { + modStartAngle += 2*M_PI - angleToStartAngle; + modArcAngle -= 2*M_PI - angleToStartAngle; + } + } + } else { + bool isCWFromEnd = crossProduct(angle, endAngle) >= 0; + if (outOfArc) { + if (isCWFromEnd) { + modArcAngle += 2*M_PI - angleToEndAngle; + } else { + modArcAngle += angleToEndAngle; + } + } else { + if (isCWFromEnd) { + modArcAngle -= angleToEndAngle; + } else { + modArcAngle -= 2*M_PI - angleToEndAngle; + } + } + } + Increment = modArcAngle - (end - start); + for (int i = 0; i < 31; i++) { + double angle = modStartAngle + i * modArcAngle/30.0; + EditCurve[i] = Base::Vector2d(center.x + radius * cos(angle), center.y + radius * sin(angle)); + } + drawEdit(EditCurve); + } + int curveId = getPreselectCurve(); + if (BaseGeoId != curveId && seekAutoConstraint(SugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(SugConstr); + return; + } + } + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode == STATUS_SEEK_First) { + BaseGeoId = getPreselectCurve(); + if (BaseGeoId > -1) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(BaseGeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *seg = static_cast(geom); + Base::Vector3d start3d = seg->getStartPoint(); + Base::Vector3d end3d = seg->getEndPoint(); + Base::Vector2d start = Base::Vector2d(start3d.x, start3d.y); + Base::Vector2d end = Base::Vector2d(end3d.x, end3d.y); + SavedExtendFromStart = (onSketchPos.Distance(start) < onSketchPos.Distance(end)); + ExtendFromStart = SavedExtendFromStart; + Mode = STATUS_SEEK_Second; + } else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *arc = static_cast(geom); + double start, end; + arc->getRange(start, end, true); + + Base::Vector3d center = arc->getCenter(); + Base::Vector2d angle = Base::Vector2d(onSketchPos.x - center.x, onSketchPos.y - center.y); + double angleToStart = angle.GetAngle(Base::Vector2d(cos(start), sin(start))); + double angleToEnd = angle.GetAngle(Base::Vector2d(cos(end), sin(end))); + ExtendFromStart = (angleToStart < angleToEnd); // move start point if closer to angle than end point + EditCurve.resize(31); + Mode = STATUS_SEEK_Second; + } + filterGate->setDisabled(true); + } + } else if (Mode == STATUS_SEEK_Second) { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Extend edge")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "extend(%d, %f, %d)\n", // GeoId, increment, PointPos + BaseGeoId, Increment, ExtendFromStart ? static_cast(Sketcher::PointPos::start) : static_cast(Sketcher::PointPos::end)); + Gui::Command::commitCommand(); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool autoRecompute = hGrp->GetBool("AutoRecompute",false); + if(autoRecompute) + Gui::Command::updateActive(); + + // constrain chosen point + if (SugConstr.size() > 0) { + createAutoConstraints(SugConstr, BaseGeoId, (ExtendFromStart) ? Sketcher::PointPos::start : Sketcher::PointPos::end); + SugConstr.clear(); + } + bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); + + if(continuousMode){ + // This code enables the continuous creation mode. + Mode=STATUS_SEEK_First; + filterGate->setDisabled(false); + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this 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 + } + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to extend edge: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + } else { // exit extension tool if user clicked on empty space + BaseGeoId = -1; + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + return true; + } + +private: + virtual void activated() override + { + Gui::Selection().clearSelection(); + Gui::Selection().rmvSelectionGate(); + filterGate = new ExtendSelection(sketchgui->getObject()); + Gui::Selection().addSelectionGate(filterGate); + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Extension"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + int BaseGeoId; + ExtendSelection* filterGate = nullptr; + bool ExtendFromStart; // if true, extend from start, else extend from end (circle only) + bool SavedExtendFromStart; + double Increment; + std::vector SugConstr; + +private: + int crossProduct(Base::Vector2d &vec1, Base::Vector2d &vec2) { + return vec1.x * vec2.y - vec1.y * vec2.x; + } +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerExtend_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h new file mode 100644 index 0000000000..ea0738e8af --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerExternal.h @@ -0,0 +1,199 @@ +/*************************************************************************** + * 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_DrawSketchHandlerExternal_H +#define SKETCHERGUI_DrawSketchHandlerExternal_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class ExternalSelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + ExternalSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()), object(obj) + {} + + bool allow(App::Document *pDoc, App::DocumentObject *pObj, const char *sSubName) + { + Sketcher::SketchObject *sketch = static_cast(object); + + this->notAllowedReason = ""; + Sketcher::SketchObject::eReasonList msg; + if (!sketch->isExternalAllowed(pDoc, pObj, &msg)){ + switch(msg){ + case Sketcher::SketchObject::rlCircularReference: + this->notAllowedReason = QT_TR_NOOP("Linking this will cause circular dependency."); + break; + case Sketcher::SketchObject::rlOtherDoc: + this->notAllowedReason = QT_TR_NOOP("This object is in another document."); + break; + case Sketcher::SketchObject::rlOtherBody: + this->notAllowedReason = QT_TR_NOOP("This object belongs to another body, can't link."); + break; + case Sketcher::SketchObject::rlOtherPart: + this->notAllowedReason = QT_TR_NOOP("This object belongs to another part, can't link."); + break; + default: + break; + } + return false; + } + + // Note: its better to search the support of the sketch in case the sketch support is a base plane + //Part::BodyBase* body = Part::BodyBase::findBodyOf(sketch); + //if ( body && body->hasFeature ( pObj ) && body->isAfter ( pObj, sketch ) ) { + // Don't allow selection after the sketch in the same body + // NOTE: allowness of features in other bodies is handled by SketchObject::isExternalAllowed() + // TODO may be this should be in SketchObject::isExternalAllowed() (2015-08-07, Fat-Zer) + //return false; + //} + + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if ((element.size() > 4 && element.substr(0,4) == "Edge") || + (element.size() > 6 && element.substr(0,6) == "Vertex") || + (element.size() > 4 && element.substr(0,4) == "Face")) { + return true; + } + if (pObj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) || + pObj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) + return true; + return false; + } +}; + +class DrawSketchHandlerExternal: public DrawSketchHandler +{ +public: + DrawSketchHandlerExternal() = default; + virtual ~DrawSketchHandlerExternal() + { + Gui::Selection().rmvSelectionGate(); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Gui::Selection().getPreselection().pObjectName) + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + /* this 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 */ + return true; + } + + virtual bool onSelectionChanged(const Gui::SelectionChanges& msg) override + { + if (msg.Type == Gui::SelectionChanges::AddSelection) { + App::DocumentObject* obj = sketchgui->getObject()->getDocument()->getObject(msg.pObjectName); + if (obj == nullptr) + throw Base::ValueError("Sketcher: External geometry: Invalid object in selection"); + std::string subName(msg.pSubName); + if (obj->getTypeId().isDerivedFrom(App::Plane::getClassTypeId()) || + obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId()) || + (subName.size() > 4 && subName.substr(0,4) == "Edge") || + (subName.size() > 6 && subName.substr(0,6) == "Vertex") || + (subName.size() > 4 && subName.substr(0,4) == "Face")) { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add external geometry")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addExternal(\"%s\",\"%s\")", + msg.pObjectName, msg.pSubName); + Gui::Command::commitCommand(); + + // adding external geometry does not require a solve() per se (the DoF is the same), + // however a solve is required to update the amount of solver geometry, because we only + // redraw a changed Sketch if the solver geometry amount is the same as the SkethObject + // geometry amount (as this avoids other issues). + // This solver is a very low cost one anyway (there is actually nothing to solve). + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + Gui::Selection().clearSelection(); + /* this 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 */ + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add external geometry: %s\n", e.what()); + Gui::Selection().clearSelection(); + Gui::Command::abortCommand(); + } + return true; + } + } + return false; + } + +private: + virtual void activated() override + { + setAxisPickStyle(false); + Gui::MDIView *mdi = Gui::Application::Instance->activeDocument()->getActiveView(); + Gui::View3DInventorViewer *viewer; + viewer = static_cast(mdi)->getViewer(); + + SoNode* root = viewer->getSceneGraph(); + static_cast(root)->selectionRole.setValue(true); + + Gui::Selection().clearSelection(); + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new ExternalSelection(sketchgui->getObject())); + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_External"); + } + + virtual void deactivated() override + { + Q_UNUSED(sketchgui); + setAxisPickStyle(true); + } +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerExternal_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerFillet.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerFillet.h new file mode 100644 index 0000000000..228171596a --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerFillet.h @@ -0,0 +1,291 @@ +/*************************************************************************** + * 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_DrawSketchHandlerFillet_H +#define SKETCHERGUI_DrawSketchHandlerFillet_H + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class FilletSelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + FilletSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()), object(obj) + {} + + bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if (element.substr(0,4) == "Edge") { + int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; + Sketcher::SketchObject *Sketch = static_cast(object); + const Part::Geometry *geom = Sketch->getGeometry(GeoId); + if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId())) + return true; + } + if (element.substr(0,6) == "Vertex") { + int VtId = std::atoi(element.substr(6,4000).c_str()) - 1; + Sketcher::SketchObject *Sketch = static_cast(object); + std::vector GeoIdList; + std::vector PosIdList; + Sketch->getDirectlyCoincidentPoints(VtId, GeoIdList, PosIdList); + if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) { + const Part::Geometry *geom1 = Sketch->getGeometry(GeoIdList[0]); + const Part::Geometry *geom2 = Sketch->getGeometry(GeoIdList[1]); + if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) + return true; + } + } + return false; + } +}; + + +class DrawSketchHandlerFillet: public DrawSketchHandler +{ +public: + enum FilletType { + SimpleFillet, + ConstraintPreservingFillet + }; + + DrawSketchHandlerFillet(FilletType filletType) : filletType(filletType), Mode(STATUS_SEEK_First), firstCurve(0) {} + virtual ~DrawSketchHandlerFillet() + { + Gui::Selection().rmvSelectionGate(); + } + + enum SelectMode{ + STATUS_SEEK_First, + STATUS_SEEK_Second + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + bool construction=false; + int VtId = getPreselectPoint(); + if (Mode == STATUS_SEEK_First && VtId != -1) { + int GeoId; + Sketcher::PointPos PosId=Sketcher::PointPos::none; + sketchgui->getSketchObject()->getGeoVertexIndex(VtId,GeoId,PosId); + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + (PosId == Sketcher::PointPos::start || PosId == Sketcher::PointPos::end)) { + + // guess fillet radius + double radius=-1; + std::vector GeoIdList; + std::vector PosIdList; + sketchgui->getSketchObject()->getDirectlyCoincidentPoints(GeoId, PosId, GeoIdList, PosIdList); + if (GeoIdList.size() == 2 && GeoIdList[0] >= 0 && GeoIdList[1] >= 0) { + const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(GeoIdList[0]); + const Part::Geometry *geom2 = sketchgui->getSketchObject()->getGeometry(GeoIdList[1]); + construction=Sketcher::GeometryFacade::getConstruction(geom1) && Sketcher::GeometryFacade::getConstruction(geom2); + if (geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + geom2->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg1 = static_cast(geom1); + const Part::GeomLineSegment *lineSeg2 = static_cast(geom2); + Base::Vector3d dir1 = lineSeg1->getEndPoint() - lineSeg1->getStartPoint(); + Base::Vector3d dir2 = lineSeg2->getEndPoint() - lineSeg2->getStartPoint(); + if (PosIdList[0] == Sketcher::PointPos::end) + dir1 *= -1; + if (PosIdList[1] == Sketcher::PointPos::end) + dir2 *= -1; + double l1 = dir1.Length(); + double l2 = dir2.Length(); + double angle = dir1.GetAngle(dir2); + radius = (l1 < l2 ? l1 : l2) * 0.2 * sin(angle/2); + } + } + if (radius < 0) + return false; + + int currentgeoid= getHighestCurveIndex(); + // create fillet at point + try { + bool pointFillet = (filletType == 1); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,%f,%s,%s)", GeoId, static_cast(PosId), radius, "True", + pointFillet ? "True":"False"); + + if (construction) { + Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", currentgeoid+1); + } + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to create fillet: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + } + return true; + } + + int GeoId = getPreselectCurve(); + if (GeoId > -1) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); + if (geom->getTypeId().isDerivedFrom(Part::GeomBoundedCurve::getClassTypeId())) { + if (Mode==STATUS_SEEK_First) { + firstCurve = GeoId; + firstPos = onSketchPos; + Mode = STATUS_SEEK_Second; + // add the line to the selection + std::stringstream ss; + ss << "Edge" << firstCurve + 1; + Gui::Selection().addSelection(sketchgui->getSketchObject()->getDocument()->getName() + ,sketchgui->getSketchObject()->getNameInDocument() + ,ss.str().c_str() + ,onSketchPos.x + ,onSketchPos.y + ,0.f); + } + else if (Mode==STATUS_SEEK_Second) { + int secondCurve = GeoId; + Base::Vector2d secondPos = onSketchPos; + + Base::Vector3d refPnt1(firstPos.x, firstPos.y, 0.f); + Base::Vector3d refPnt2(secondPos.x, secondPos.y, 0.f); + + const Part::Geometry *geom1 = sketchgui->getSketchObject()->getGeometry(firstCurve); + + double radius = 0; + + if( geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() && + geom1->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + // guess fillet radius + const Part::GeomLineSegment *lineSeg1 = static_cast + (sketchgui->getSketchObject()->getGeometry(firstCurve)); + const Part::GeomLineSegment *lineSeg2 = static_cast + (sketchgui->getSketchObject()->getGeometry(secondCurve)); + + radius = Part::suggestFilletRadius(lineSeg1, lineSeg2, refPnt1, refPnt2); + if (radius < 0) + return false; + + construction=Sketcher::GeometryFacade::getConstruction(lineSeg1) && Sketcher::GeometryFacade::getConstruction(lineSeg2); + } + else { // other supported curves + const Part::Geometry *geo1 = static_cast + (sketchgui->getSketchObject()->getGeometry(firstCurve)); + const Part::Geometry *geo2 = static_cast + (sketchgui->getSketchObject()->getGeometry(secondCurve)); + + construction=Sketcher::GeometryFacade::getConstruction(geo1) && Sketcher::GeometryFacade::getConstruction(geo2); + } + + + int currentgeoid= getHighestCurveIndex(); + + // create fillet between lines + try { + bool pointFillet = (filletType == 1); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Create fillet")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "fillet(%d,%d,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%f,%s,%s)", + firstCurve, secondCurve, + firstPos.x, firstPos.y, + secondPos.x, secondPos.y, radius, + "True", pointFillet ? "True":"False"); + Gui::Command::commitCommand(); + } + catch (const Base::CADKernelError& e) { + e.ReportException(); + if(e.getTranslatable()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("CAD Kernel Error"), + QObject::tr(e.getMessage().c_str())); + } + Gui::Selection().clearSelection(); + Gui::Command::abortCommand(); + Mode = STATUS_SEEK_First; + } + catch (const Base::ValueError& e) { + e.ReportException(); + Gui::Selection().clearSelection(); + Gui::Command::abortCommand(); + Mode = STATUS_SEEK_First; + } + + tryAutoRecompute(static_cast(sketchgui->getObject())); + + if(construction) { + Gui::cmdAppObjectArgs(sketchgui->getObject(), "toggleConstruction(%d) ", + currentgeoid+1); + } + + + Gui::Selection().clearSelection(); + Mode = STATUS_SEEK_First; + } + } + } + + if (VtId < 0 && GeoId < 0) // exit the fillet tool if the user clicked on empty space + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new FilletSelection(sketchgui->getObject())); + return QString::fromLatin1("Sketcher_Pointer_Create_Fillet"); + } + +protected: + int filletType; + SelectMode Mode; + int firstCurve; + Base::Vector2d firstPos; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerFillet_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h new file mode 100644 index 0000000000..14fcbe56f1 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerLine.h @@ -0,0 +1,165 @@ +/*************************************************************************** + * 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_DrawSketchHandlerLine_H +#define SKETCHERGUI_DrawSketchHandlerLine_H + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerLine: public DrawSketchHandler +{ +public: + DrawSketchHandlerLine():Mode(STATUS_SEEK_First),EditCurve(2){} + virtual ~DrawSketchHandlerLine(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second){ + float length = (onSketchPos - EditCurve[0]).Length(); + float angle = (onSketchPos - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f)); + SbString text; + text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI); + setPositionText(onSketchPos, text); + + EditCurve[1] = onSketchPos; + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + EditCurve[0] = onSketchPos; + + Mode = STATUS_SEEK_Second; + } + else { + EditCurve[1] = onSketchPos; + drawEdit(EditCurve); + Mode = STATUS_End; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_End){ + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch line")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", + EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, + geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add line: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue(); + + if(avoidredundant) + removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); + + // add auto constraints for the line segment start + if (!sugConstr1.empty()) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); + sugConstr1.clear(); + } + + // add auto constraints for the line segment end + if (!sugConstr2.empty()) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::end); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + EditCurve.clear(); + drawEdit(EditCurve); + + bool continuousMode = hGrp->GetBool("ContinuousCreationMode",true); + if(continuousMode){ + // This code enables the continuous creation mode. + Mode=STATUS_SEEK_First; + EditCurve.resize(2); + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Line"); + } + +protected: + SelectMode Mode; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerLine_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h new file mode 100644 index 0000000000..e4afd76a0a --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerLineSet.h @@ -0,0 +1,685 @@ +/*************************************************************************** + * 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_DrawSketchHandlerLineSet_H +#define SKETCHERGUI_DrawSketchHandlerLineSet_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerLineSet: public DrawSketchHandler +{ +public: + DrawSketchHandlerLineSet() + : Mode(STATUS_SEEK_First), SegmentMode(SEGMENT_MODE_Line) + , TransitionMode(TRANSITION_MODE_Free) + , SnapMode(SNAP_MODE_Free) + , suppressTransition(false) + , EditCurve(2) + , firstCurve(-1) + , previousCurve(-1) + , firstPosId(Sketcher::PointPos::none) + , previousPosId(Sketcher::PointPos::none) + , startAngle(0) + , endAngle(0) + , arcRadius(0) + , firstsegment(true) {} + + virtual ~DrawSketchHandlerLineSet() = default; + + /// mode table + enum SELECT_MODE { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_Do, + STATUS_Close + }; + + enum SEGMENT_MODE + { + SEGMENT_MODE_Arc, + SEGMENT_MODE_Line + }; + + enum TRANSITION_MODE + { + TRANSITION_MODE_Free, + TRANSITION_MODE_Tangent, + TRANSITION_MODE_Perpendicular_L, + TRANSITION_MODE_Perpendicular_R + }; + + enum SNAP_MODE + { + SNAP_MODE_Free, + SNAP_MODE_45Degree + }; + + virtual void registerPressedKey(bool pressed, int key) override + { + if (Mode != STATUS_SEEK_Second) + return; // SegmentMode can be changed only in STATUS_SEEK_Second mode + + if (key == SoKeyboardEvent::M && pressed && previousCurve != -1) { + // loop through the following modes: + // SEGMENT_MODE_Line, TRANSITION_MODE_Free / TRANSITION_MODE_Tangent + // SEGMENT_MODE_Line, TRANSITION_MODE_Perpendicular_L + // SEGMENT_MODE_Line, TRANSITION_MODE_Tangent / TRANSITION_MODE_Free + // SEGMENT_MODE_Arc, TRANSITION_MODE_Tangent + // SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_L + // SEGMENT_MODE_Arc, TRANSITION_MODE_Perpendicular_R + + SnapMode = SNAP_MODE_Free; + + Base::Vector2d onSketchPos; + if (SegmentMode == SEGMENT_MODE_Line) + onSketchPos = EditCurve[EditCurve.size()-1]; + else + onSketchPos = EditCurve[29]; + + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(previousCurve); + + if (SegmentMode == SEGMENT_MODE_Line) { + switch (TransitionMode) { + case TRANSITION_MODE_Free: + if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { // 3rd mode + SegmentMode = SEGMENT_MODE_Arc; + TransitionMode = TRANSITION_MODE_Tangent; + } + else // 1st mode + TransitionMode = TRANSITION_MODE_Perpendicular_L; + break; + case TRANSITION_MODE_Perpendicular_L: // 2nd mode + if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) + TransitionMode = TRANSITION_MODE_Free; + else + TransitionMode = TRANSITION_MODE_Tangent; + break; + case TRANSITION_MODE_Tangent: + if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) // 1st mode + TransitionMode = TRANSITION_MODE_Perpendicular_L; + else { // 3rd mode + SegmentMode = SEGMENT_MODE_Arc; + TransitionMode = TRANSITION_MODE_Tangent; + } + break; + default: // unexpected mode + TransitionMode = TRANSITION_MODE_Free; + break; + } + } + else { + switch (TransitionMode) { + case TRANSITION_MODE_Tangent: // 4th mode + TransitionMode = TRANSITION_MODE_Perpendicular_L; + break; + case TRANSITION_MODE_Perpendicular_L: // 5th mode + TransitionMode = TRANSITION_MODE_Perpendicular_R; + break; + default: // 6th mode (Perpendicular_R) + unexpected mode + SegmentMode = SEGMENT_MODE_Line; + if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) + TransitionMode = TRANSITION_MODE_Tangent; + else + TransitionMode = TRANSITION_MODE_Free; + break; + } + } + + if (SegmentMode == SEGMENT_MODE_Line) + EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3); + else + EditCurve.resize(32); + mouseMove(onSketchPos); // trigger an update of EditCurve + } + } + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + suppressTransition = false; + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second){ + if (SegmentMode == SEGMENT_MODE_Line) { + EditCurve[EditCurve.size()-1] = onSketchPos; + if (TransitionMode == TRANSITION_MODE_Tangent) { + Base::Vector2d Tangent(dirVec.x,dirVec.y); + EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Tangent); + if (EditCurve[1] * Tangent < 0) { + EditCurve[1] = EditCurve[2]; + suppressTransition = true; + } + else + EditCurve[1] = EditCurve[0] + EditCurve[1]; + } + else if (TransitionMode == TRANSITION_MODE_Perpendicular_L || + TransitionMode == TRANSITION_MODE_Perpendicular_R) { + Base::Vector2d Perpendicular(-dirVec.y,dirVec.x); + EditCurve[1].ProjectToLine(EditCurve[2] - EditCurve[0], Perpendicular); + EditCurve[1] = EditCurve[0] + EditCurve[1]; + } + + drawEdit(EditCurve); + + float length = (EditCurve[1] - EditCurve[0]).Length(); + float angle = (EditCurve[1] - EditCurve[0]).GetAngle(Base::Vector2d(1.f,0.f)); + + SbString text; + text.sprintf(" (%.1f,%.1fdeg)", length, angle * 180 / M_PI); + setPositionText(EditCurve[1], text); + + if (TransitionMode == TRANSITION_MODE_Free) { + if (seekAutoConstraint(sugConstr2, onSketchPos, onSketchPos - EditCurve[0])) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + } + else if (SegmentMode == SEGMENT_MODE_Arc) { + + if(QApplication::keyboardModifiers() == Qt::ControlModifier) + SnapMode = SNAP_MODE_45Degree; + else + SnapMode = SNAP_MODE_Free; + + Base::Vector2d Tangent; + if (TransitionMode == TRANSITION_MODE_Tangent) + Tangent = Base::Vector2d(dirVec.x,dirVec.y); + else if (TransitionMode == TRANSITION_MODE_Perpendicular_L) + Tangent = Base::Vector2d(-dirVec.y,dirVec.x); + else if (TransitionMode == TRANSITION_MODE_Perpendicular_R) + Tangent = Base::Vector2d(dirVec.y,-dirVec.x); + + double theta = Tangent.GetAngle(onSketchPos - EditCurve[0]); + + arcRadius = (onSketchPos - EditCurve[0]).Length()/(2.0*sin(theta)); + + // At this point we need a unit normal vector pointing towards + // the center of the arc we are drawing. Derivation of the formula + // used here can be found at http://people.richland.edu/james/lecture/m116/matrices/area.html + double x1 = EditCurve[0].x; + double y1 = EditCurve[0].y; + double x2 = x1 + Tangent.x; + double y2 = y1 + Tangent.y; + double x3 = onSketchPos.x; + double y3 = onSketchPos.y; + if ((x2*y3-x3*y2)-(x1*y3-x3*y1)+(x1*y2-x2*y1) > 0) + arcRadius *= -1; + if (boost::math::isnan(arcRadius) || boost::math::isinf(arcRadius)) + arcRadius = 0.f; + + CenterPoint = EditCurve[0] + Base::Vector2d(arcRadius * Tangent.y, -arcRadius * Tangent.x); + + double rx = EditCurve[0].x - CenterPoint.x; + double ry = EditCurve[0].y - CenterPoint.y; + + startAngle = atan2(ry,rx); + + double rxe = onSketchPos.x - CenterPoint.x; + double rye = onSketchPos.y - CenterPoint.y; + double arcAngle = atan2(-rxe*ry + rye*rx, rxe*rx + rye*ry); + if (boost::math::isnan(arcAngle) || boost::math::isinf(arcAngle)) + arcAngle = 0.f; + if (arcRadius >= 0 && arcAngle > 0) + arcAngle -= 2*M_PI; + if (arcRadius < 0 && arcAngle < 0) + arcAngle += 2*M_PI; + + if (SnapMode == SNAP_MODE_45Degree) + arcAngle = round(arcAngle / (M_PI/4)) * M_PI/4; + + endAngle = startAngle + arcAngle; + + for (int i=1; i <= 29; i++) { + double angle = i*arcAngle/29.0; + double dx = rx * cos(angle) - ry * sin(angle); + double dy = rx * sin(angle) + ry * cos(angle); + EditCurve[i] = Base::Vector2d(CenterPoint.x + dx, CenterPoint.y + dy); + } + + EditCurve[30] = CenterPoint; + EditCurve[31] = EditCurve[0]; + + drawEdit(EditCurve); + + SbString text; + text.sprintf(" (%.1fR,%.1fdeg)", std::abs(arcRadius), arcAngle * 180 / M_PI); + setPositionText(onSketchPos, text); + + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_SEEK_First) { + + EditCurve[0] = onSketchPos; // this may be overwritten if previousCurve is found + + virtualsugConstr1 = sugConstr1; // store original autoconstraints. + + // here we check if there is a preselected point and + // we set up a transition from the neighbouring segment. + // (peviousCurve, previousPosId, dirVec, TransitionMode) + for (unsigned int i=0; i < sugConstr1.size(); i++) + if (sugConstr1[i].Type == Sketcher::Coincident) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(sugConstr1[i].GeoId); + if ((geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() || + geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) && + (sugConstr1[i].PosId == Sketcher::PointPos::start || + sugConstr1[i].PosId == Sketcher::PointPos::end)) { + previousCurve = sugConstr1[i].GeoId; + previousPosId = sugConstr1[i].PosId; + updateTransitionData(previousCurve,previousPosId); // -> dirVec, EditCurve[0] + if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + TransitionMode = TRANSITION_MODE_Tangent; + SnapMode = SNAP_MODE_Free; + } + sugConstr1.erase(sugConstr1.begin()+i); // actually we should clear the vector completely + break; + } + } + + // remember our first point (even if we are doing a transition from a previous curve) + firstCurve = getHighestCurveIndex() + 1; + firstPosId = Sketcher::PointPos::start; + + if (SegmentMode == SEGMENT_MODE_Line) + EditCurve.resize(TransitionMode == TRANSITION_MODE_Free ? 2 : 3); + else if (SegmentMode == SEGMENT_MODE_Arc) + EditCurve.resize(32); + Mode = STATUS_SEEK_Second; + } + else if (Mode == STATUS_SEEK_Second) { + // exit on clicking exactly at the same position (e.g. double click) + if (onSketchPos == EditCurve[0]) { + unsetCursor(); + resetPositionText(); + EditCurve.clear(); + drawEdit(EditCurve); + + 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. + Mode=STATUS_SEEK_First; + SegmentMode=SEGMENT_MODE_Line; + TransitionMode=TRANSITION_MODE_Free; + SnapMode = SNAP_MODE_Free; + suppressTransition=false; + firstCurve=-1; + previousCurve=-1; + firstPosId=Sketcher::PointPos::none; + previousPosId=Sketcher::PointPos::none; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this 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 */ + return true; + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + return true; + } + } + + Mode = STATUS_Do; + + if (getPreselectPoint() != -1 && firstPosId != Sketcher::PointPos::none) { + int GeoId; + Sketcher::PointPos PosId; + sketchgui->getSketchObject()->getGeoVertexIndex(getPreselectPoint(),GeoId,PosId); + if (sketchgui->getSketchObject()->arePointsCoincident(GeoId,PosId,firstCurve,firstPosId)) + Mode = STATUS_Close; + } + else if (getPreselectCross() == 0 && firstPosId != Sketcher::PointPos::none) { + // close line started at root point + if (sketchgui->getSketchObject()->arePointsCoincident(-1,Sketcher::PointPos::start,firstCurve,firstPosId)) + Mode = STATUS_Close; + } + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_Do || Mode == STATUS_Close) { + bool addedGeometry = true; + if (SegmentMode == SEGMENT_MODE_Line) { + // issue the geometry + try { + // open the transaction + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add line to sketch wire")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)),%s)", + EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, + geometryCreationMode==Construction?"True":"False"); + } + catch (const Base::Exception& e) { + addedGeometry = false; + Base::Console().Error("Failed to add line: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + firstsegment=false; + } + else if (SegmentMode == SEGMENT_MODE_Arc) { // We're dealing with an Arc + if (!boost::math::isnormal(arcRadius)) { + Mode = STATUS_SEEK_Second; + return true; + } + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add arc to sketch wire")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.ArcOfCircle" + "(Part.Circle(App.Vector(%f,%f,0),App.Vector(0,0,1),%f),%f,%f),%s)", + CenterPoint.x, CenterPoint.y, std::abs(arcRadius), + std::min(startAngle,endAngle), std::max(startAngle,endAngle), + geometryCreationMode==Construction?"True":"False"); + } + catch (const Base::Exception& e) { + addedGeometry = false; + Base::Console().Error("Failed to add arc: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + firstsegment=false; + } + + int lastCurve = getHighestCurveIndex(); + // issue the constraint + if (addedGeometry && (previousPosId != Sketcher::PointPos::none)) { + Sketcher::PointPos lastStartPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? + Sketcher::PointPos::end : Sketcher::PointPos::start; + Sketcher::PointPos lastEndPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? + Sketcher::PointPos::start : Sketcher::PointPos::end; + // in case of a tangency constraint, the coincident constraint is redundant + std::string constrType = "Coincident"; + if (!suppressTransition && previousCurve != -1) { + if (TransitionMode == TRANSITION_MODE_Tangent) + constrType = "Tangent"; + else if (TransitionMode == TRANSITION_MODE_Perpendicular_L || + TransitionMode == TRANSITION_MODE_Perpendicular_R) + constrType = "Perpendicular"; + } + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('%s',%i,%i,%i,%i)) ", + constrType.c_str(), previousCurve, static_cast(previousPosId), lastCurve, static_cast(lastStartPosId)); + + if(SnapMode == SNAP_MODE_45Degree && Mode != STATUS_Close) { + // -360, -315, -270, -225, -180, -135, -90, -45, 0, 45, 90, 135, 180, 225, 270, 315, 360 + // N/A, a, perp, a, par, a,perp, a,N/A, a,perp, a, par, a,perp, a, N/A + + // #3974: if in radians, the printf %f defaults to six decimals, which leads to loss of precision + double arcAngle = abs(round( (endAngle - startAngle) / (M_PI/4)) * 45); // in degrees + + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Angle',%i,App.Units.Quantity('%f deg'))) ", + lastCurve, arcAngle); + } + if (Mode == STATUS_Close) { + // close the loop by constrain to the first curve point + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Coincident',%i,%i,%i,%i)) ", + lastCurve,static_cast(lastEndPosId),firstCurve,static_cast(firstPosId)); + } + Gui::Command::commitCommand(); + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + } + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Mod/Sketcher"); + bool avoidredundant = sketchgui->AvoidRedundant.getValue() && sketchgui->Autoconstraints.getValue(); + + if (Mode == STATUS_Close) { + + if(avoidredundant) { + if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints. + if (sugConstr1.size() > 0) + removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); + else + removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),virtualsugConstr1,sugConstr2); + } + } + + if (sugConstr2.size() > 0) { + // exclude any coincidence constraints + std::vector sugConstr; + for (unsigned int i=0; i < sugConstr2.size(); i++) { + if (sugConstr2[i].Type != Sketcher::Coincident) + sugConstr.push_back(sugConstr2[i]); + } + createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::PointPos::end); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + unsetCursor(); + + resetPositionText(); + EditCurve.clear(); + drawEdit(EditCurve); + + 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. + Mode=STATUS_SEEK_First; + SegmentMode=SEGMENT_MODE_Line; + TransitionMode=TRANSITION_MODE_Free; + SnapMode = SNAP_MODE_Free; + suppressTransition=false; + firstCurve=-1; + previousCurve=-1; + firstPosId=Sketcher::PointPos::none; + previousPosId=Sketcher::PointPos::none; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + /* this 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 { + Gui::Command::commitCommand(); + + // Add auto constraints + if (sugConstr1.size() > 0) { // this is relevant only to the very first point + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); + sugConstr1.clear(); + } + + + if(avoidredundant) { + if (SegmentMode == SEGMENT_MODE_Line) { // avoid redundant constraints. + if (sugConstr1.size() > 0) + removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),sugConstr1,sugConstr2); + else + removeRedundantHorizontalVertical(static_cast(sketchgui->getObject()),virtualsugConstr1,sugConstr2); + } + } + + virtualsugConstr1 = sugConstr2; // these are the initial constraints for the next iteration. + + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), + (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? + Sketcher::PointPos::start : Sketcher::PointPos::end); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + + // remember the vertex for the next rounds constraint.. + previousCurve = getHighestCurveIndex(); + previousPosId = (SegmentMode == SEGMENT_MODE_Arc && startAngle > endAngle) ? + Sketcher::PointPos::start : Sketcher::PointPos::end; // cw arcs are rendered in reverse + + // setup for the next line segment + // calculate dirVec and EditCurve[0] + updateTransitionData(previousCurve,previousPosId); + + applyCursor(); + Mode = STATUS_SEEK_Second; + if (SegmentMode == SEGMENT_MODE_Arc) { + TransitionMode = TRANSITION_MODE_Tangent; + EditCurve.resize(3); + EditCurve[2] = EditCurve[0]; + } + else { + TransitionMode = TRANSITION_MODE_Free; + EditCurve.resize(2); + } + SegmentMode = SEGMENT_MODE_Line; + SnapMode = SNAP_MODE_Free; + EditCurve[1] = EditCurve[0]; + mouseMove(onSketchPos); // trigger an update of EditCurve + } + } + return true; + } + + virtual void quit(void) 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 (firstsegment) { + // user when right-clicking with no segment in really wants to exit + DrawSketchHandler::quit(); + } + else { + + if(!continuousMode){ + DrawSketchHandler::quit(); + } + else { + // This code disregards existing data and enables the continuous creation mode. + Mode=STATUS_SEEK_First; + SegmentMode=SEGMENT_MODE_Line; + TransitionMode=TRANSITION_MODE_Free; + SnapMode = SNAP_MODE_Free; + suppressTransition=false; + firstCurve=-1; + previousCurve=-1; + firstPosId=Sketcher::PointPos::none; + previousPosId=Sketcher::PointPos::none; + firstsegment=true; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(2); + applyCursor(); + } + } + } + +private: + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Create_Lineset"); + } + +protected: + SELECT_MODE Mode; + SEGMENT_MODE SegmentMode; + TRANSITION_MODE TransitionMode; + SNAP_MODE SnapMode; + bool suppressTransition; + + std::vector EditCurve; + int firstCurve; + int previousCurve; + Sketcher::PointPos firstPosId; + Sketcher::PointPos previousPosId; + // the latter stores those constraints that a first point would have been given in absence of the transition mechanism + std::vector sugConstr1, sugConstr2, virtualsugConstr1; + + Base::Vector2d CenterPoint; + Base::Vector3d dirVec; + double startAngle, endAngle, arcRadius; + + bool firstsegment; + + void updateTransitionData(int GeoId, Sketcher::PointPos PosId) { + + // Use updated startPoint/endPoint as autoconstraints can modify the position + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId()) { + const Part::GeomLineSegment *lineSeg = static_cast(geom); + dirVec.Set(lineSeg->getEndPoint().x - lineSeg->getStartPoint().x, + lineSeg->getEndPoint().y - lineSeg->getStartPoint().y, + 0.f); + if (PosId == Sketcher::PointPos::start) { + dirVec *= -1; + EditCurve[0] = Base::Vector2d(lineSeg->getStartPoint().x, lineSeg->getStartPoint().y); + } + else + EditCurve[0] = Base::Vector2d(lineSeg->getEndPoint().x, lineSeg->getEndPoint().y); + } + else if (geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + const Part::GeomArcOfCircle *arcSeg = static_cast(geom); + if (PosId == Sketcher::PointPos::start) { + EditCurve[0] = Base::Vector2d(arcSeg->getStartPoint(/*emulateCCW=*/true).x,arcSeg->getStartPoint(/*emulateCCW=*/true).y); + dirVec = Base::Vector3d(0.f,0.f,-1.0) % (arcSeg->getStartPoint(/*emulateCCW=*/true)-arcSeg->getCenter()); + } + else { + EditCurve[0] = Base::Vector2d(arcSeg->getEndPoint(/*emulateCCW=*/true).x,arcSeg->getEndPoint(/*emulateCCW=*/true).y); + dirVec = Base::Vector3d(0.f,0.f,1.0) % (arcSeg->getEndPoint(/*emulateCCW=*/true)-arcSeg->getCenter()); + } + } + dirVec.Normalize(); + } +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerLineSet_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerPoint.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerPoint.h new file mode 100644 index 0000000000..58b35ac46b --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerPoint.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * 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_DrawSketchHandlerPoint_H +#define SKETCHERGUI_DrawSketchHandlerPoint_H + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerPoint: public DrawSketchHandler +{ +public: + DrawSketchHandlerPoint() : selectionDone(false) {} + virtual ~DrawSketchHandlerPoint() {} + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr); + return; + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + EditPoint = onSketchPos; + selectionDone = true; + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (selectionDone){ + unsetCursor(); + resetPositionText(); + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch point")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "addGeometry(Part.Point(App.Vector(%f,%f,0)))", + EditPoint.x,EditPoint.y); + + Gui::Command::commitCommand(); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add point: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + // add auto constraints for the line segment start + if (sugConstr.size() > 0) { + createAutoConstraints(sugConstr, getHighestCurveIndex(), Sketcher::PointPos::start); + sugConstr.clear(); + } + + 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. + applyCursor(); + /* It is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Point"); + } + +protected: + bool selectionDone; + Base::Vector2d EditPoint; + std::vector sugConstr; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerPoint_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h new file mode 100644 index 0000000000..f43af0ae62 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerPolygon.h @@ -0,0 +1,191 @@ +/*************************************************************************** + * 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_DrawSketchHandlerPolygon_H +#define SKETCHERGUI_DrawSketchHandlerPolygon_H + +#include "GeometryCreationMode.h" +#include "Utils.h" + +#include "SketcherRegularPolygonDialog.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerRegularPolygon: public DrawSketchHandler +{ +public: + DrawSketchHandlerRegularPolygon( size_t nof_corners ): + Corners( nof_corners ), + AngleOfSeparation( 2.0*M_PI/static_cast(Corners) ), + cos_v( cos( AngleOfSeparation ) ), + sin_v( sin( AngleOfSeparation ) ), + Mode(STATUS_SEEK_First), + EditCurve(Corners+1) + { + } + virtual ~DrawSketchHandlerRegularPolygon(){} + /// mode table + enum SelectMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + EditCurve[0]= Base::Vector2d(onSketchPos.x, onSketchPos.y); + EditCurve[Corners]= Base::Vector2d(onSketchPos.x, onSketchPos.y); + + Base::Vector2d dV = onSketchPos - StartPos; + double rx = dV.x; + double ry = dV.y; + for (int i=1; i < static_cast(Corners); i++) { + const double old_rx = rx; + rx = cos_v * rx - sin_v * ry; + ry = cos_v * ry + sin_v * old_rx; + EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry); + } + + // Display radius for user + const float radius = dV.Length(); + const float angle = ( 180.0 / M_PI ) * atan2( dV.y, dV.x ); + + SbString text; + text.sprintf(" (%.1fR %.1fdeg)", radius, angle ); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + StartPos = onSketchPos; + Mode = STATUS_SEEK_Second; + } + else { + Mode = STATUS_End; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_End){ + unsetCursor(); + resetPositionText(); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add hexagon")); + + try { + Gui::Command::doCommand(Gui::Command::Doc, + "import ProfileLib.RegularPolygon\n" + "ProfileLib.RegularPolygon.makeRegularPolygon(%s,%i,App.Vector(%f,%f,0),App.Vector(%f,%f,0),%s)", + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), + Corners, + StartPos.x,StartPos.y,EditCurve[0].x,EditCurve[0].y, + geometryCreationMode==Construction?"True":"False"); + + Gui::Command::commitCommand(); + + // add auto constraints at the center of the polygon + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // add auto constraints to the last side of the polygon + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex() - 1, Sketcher::PointPos::end); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add hexagon: %s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecompute(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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(Corners+1); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Regular_Polygon"); + } +protected: + const size_t Corners; + const double AngleOfSeparation; + const double cos_v, sin_v; + SelectMode Mode; + Base::Vector2d StartPos; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerPolygon_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerRectangle.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerRectangle.h new file mode 100644 index 0000000000..6793433e78 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerRectangle.h @@ -0,0 +1,589 @@ +/*************************************************************************** + * 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_DrawSketchHandlerRectangle_H +#define SKETCHERGUI_DrawSketchHandlerRectangle_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerBox: public DrawSketchHandler +{ +public: + enum ConstructionMethod { + Diagonal, + CenterAndCorner + }; + + DrawSketchHandlerBox(ConstructionMethod constrMethod = Diagonal): Mode(STATUS_SEEK_First), + EditCurve(5), + constructionMethod(constrMethod){} + virtual ~DrawSketchHandlerBox(){} + + /// mode table + enum BoxMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_End + }; + +public: + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + + if (Mode==STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f,0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode==STATUS_SEEK_Second) { + if(constructionMethod == Diagonal) { + float dx = onSketchPos.x - EditCurve[0].x; + float dy = onSketchPos.y - EditCurve[0].y; + SbString text; + text.sprintf(" (%.1f x %.1f)", dx, dy); + setPositionText(onSketchPos, text); + + EditCurve[2] = onSketchPos; + EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y); + EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); + + } + else if (constructionMethod == CenterAndCorner) { + float dx = onSketchPos.x - center.x; + float dy = onSketchPos.y - center.y; + SbString text; + text.sprintf(" (%.1f x %.1f)", dx, dy); + setPositionText(onSketchPos, text); + + EditCurve[0] = center - (onSketchPos - center); + EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); + EditCurve[2] = onSketchPos; + EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y); + EditCurve[4] = EditCurve[0]; + } + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.0,0.0))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode==STATUS_SEEK_First){ + if(constructionMethod == Diagonal) { + EditCurve[0] = onSketchPos; + EditCurve[4] = onSketchPos; + } + else if (constructionMethod == CenterAndCorner) { + center = onSketchPos; + } + + Mode = STATUS_SEEK_Second; + } + else { + if(constructionMethod == Diagonal) { + EditCurve[2] = onSketchPos; + EditCurve[1] = Base::Vector2d(onSketchPos.x ,EditCurve[0].y); + EditCurve[3] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); + drawEdit(EditCurve); + Mode = STATUS_End; + } + else if (constructionMethod == CenterAndCorner) { + EditCurve[0] = center - (onSketchPos - center); + EditCurve[1] = Base::Vector2d(EditCurve[0].x,onSketchPos.y); + EditCurve[2] = onSketchPos; + EditCurve[3] = Base::Vector2d(onSketchPos.x,EditCurve[0].y); + EditCurve[4] = EditCurve[0]; + drawEdit(EditCurve); + Mode = STATUS_End; + } + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode==STATUS_End){ + unsetCursor(); + resetPositionText(); + int firstCurve = getHighestCurveIndex() + 1; + + try { + if(constructionMethod == Diagonal) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch box")); + Gui::Command::doCommand(Gui::Command::Doc, + "geoList = []\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "%s.addGeometry(geoList,%s)\n" + "conList = []\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Horizontal',%i))\n" + "conList.append(Sketcher.Constraint('Horizontal',%i))\n" + "conList.append(Sketcher.Constraint('Vertical',%i))\n" + "conList.append(Sketcher.Constraint('Vertical',%i))\n" + "%s.addConstraint(conList)\n" + "del geoList, conList\n", + EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 + EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 + EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 + EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4 + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch + geometryCreationMode==Construction?"True":"False", // geometry as construction or not + firstCurve,firstCurve+1, // coincident1 + firstCurve+1,firstCurve+2, // coincident2 + firstCurve+2,firstCurve+3, // coincident3 + firstCurve+3,firstCurve, // coincident4 + firstCurve, // horizontal1 + firstCurve+2, // horizontal2 + firstCurve+1, // vertical1 + firstCurve+3, // vertical2 + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch + + Gui::Command::commitCommand(); + } + else if (constructionMethod == CenterAndCorner) { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add centered sketch box")); + Gui::Command::doCommand(Gui::Command::Doc, + "geoList = []\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f,%f,0),App.Vector(%f,%f,0)))\n" + "geoList.append(Part.Point(App.Vector(%f,%f,0)))\n" + "%s.addGeometry(geoList,%s)\n" + "conList = []\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Coincident',%i,2,%i,1))\n" + "conList.append(Sketcher.Constraint('Horizontal',%i))\n" + "conList.append(Sketcher.Constraint('Horizontal',%i))\n" + "conList.append(Sketcher.Constraint('Vertical',%i))\n" + "conList.append(Sketcher.Constraint('Vertical',%i))\n" + "conList.append(Sketcher.Constraint('Symmetric',%i,2,%i,1,%i,1))\n" + "%s.addConstraint(conList)\n" + "del geoList, conList\n", + EditCurve[0].x,EditCurve[0].y,EditCurve[1].x,EditCurve[1].y, // line 1 + EditCurve[1].x,EditCurve[1].y,EditCurve[2].x,EditCurve[2].y, // line 2 + EditCurve[2].x,EditCurve[2].y,EditCurve[3].x,EditCurve[3].y, // line 3 + EditCurve[3].x,EditCurve[3].y,EditCurve[0].x,EditCurve[0].y, // line 4 + center.x,center.y, // center point + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch + geometryCreationMode==Construction?"True":"False", // geometry as construction or not + firstCurve,firstCurve+1, // coincident1 + firstCurve+1,firstCurve+2, // coincident2 + firstCurve+2,firstCurve+3, // coincident3 + firstCurve+3,firstCurve, // coincident4 + firstCurve+1, // horizontal1 + firstCurve+3, // horizontal2 + firstCurve, // vertical1 + firstCurve+2, // vertical2 + firstCurve+1, firstCurve, firstCurve + 4, // Symmetric + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch + + Gui::Command::commitCommand(); + } + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add box: %s\n", e.what()); + Gui::Command::abortCommand(); + } + + if(constructionMethod == Diagonal) { + // add auto constraints at the start of the first side + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3 , Sketcher::PointPos::start); + sugConstr1.clear(); + } + + // add auto constraints at the end of the second side + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::PointPos::end); + sugConstr2.clear(); + } + + } + else if (constructionMethod == CenterAndCorner) { + // add auto constraints at the start of the first side + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::PointPos::start); + sugConstr1.clear(); + } + + // add auto constraints at the end of the second side + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex() - 3, Sketcher::PointPos::end); + sugConstr2.clear(); + } + } + + 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. + Mode=STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(5); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else{ + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + + + } + return true; + } + +private: + + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Create_Box"); + } +protected: + BoxMode Mode; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; + ConstructionMethod constructionMethod; + Base::Vector2d center; +}; + +class DrawSketchHandlerOblong : public DrawSketchHandler +{ +public: + DrawSketchHandlerOblong() + : Mode(STATUS_SEEK_First) + , lengthX(0), lengthY(0), radius(0), signX(1), signY(1) + , EditCurve(37) + { + } + virtual ~DrawSketchHandlerOblong() {} + /// mode table + enum BoxMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_End + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + + if (Mode == STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode == STATUS_SEEK_Second) { + float distanceX = onSketchPos.x - StartPos.x; + float distanceY = onSketchPos.y - StartPos.y; + + lengthX = distanceX; lengthY = distanceY; + signX = Base::sgn(distanceX); + signY = Base::sgn(distanceY); + if (fabs(distanceX) > fabs(distanceY)) { + radius = fabs(distanceY) / 4; // we use a fourth of the smaller distance as default radius + } + else { + radius = fabs(distanceX) / 4; + } + + // we draw the lines with 36 segments, 8 for each arc and 4 lines + // draw the arcs + for (int i = 0; i < 8; i++) { + // calculate the x,y positions forming the the arc + double angle = i * M_PI / 16.0; + double x_i = -radius * sin(angle); + double y_i = -radius * cos(angle); + // we are drawing clockwise starting with the arc that is besides StartPos + if (signX == signY) { + EditCurve[i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + signY * (radius + y_i)); + EditCurve[9 + i] = Base::Vector2d(StartPos.x + signY * (radius + y_i), StartPos.y + lengthY - signX * (radius + x_i)); + EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i)); + EditCurve[27 + i] = Base::Vector2d(StartPos.x + lengthX - signY * (radius + y_i), StartPos.y + signX * (radius + x_i)); + } + else { + EditCurve[i] = Base::Vector2d(StartPos.x - signY * (radius + y_i), StartPos.y - signX * (radius + x_i)); + EditCurve[9 + i] = Base::Vector2d(StartPos.x + lengthX - signX * (radius + x_i), StartPos.y + signY * (radius + y_i)); + EditCurve[18 + i] = Base::Vector2d(StartPos.x + lengthX + signY * (radius + y_i), StartPos.y + lengthY + signX * (radius + x_i)); + EditCurve[27 + i] = Base::Vector2d(StartPos.x + signX * (radius + x_i), StartPos.y + lengthY - signY * (radius + y_i)); + } + } + // draw the lines + if (signX == signY) { + EditCurve[8] = Base::Vector2d(StartPos.x, StartPos.y + (signY * radius)); + EditCurve[17] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y + lengthY); + EditCurve[26] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + lengthY - (signY * radius)); + EditCurve[35] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y); + } + else { + EditCurve[8] = Base::Vector2d(StartPos.x + (signX * radius), StartPos.y); + EditCurve[17] = Base::Vector2d(StartPos.x + lengthX, StartPos.y + (signY * radius)); + EditCurve[26] = Base::Vector2d(StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY); + EditCurve[35] = Base::Vector2d(StartPos.x, StartPos.y + lengthY - (signY * radius)); + } + // close the curve + EditCurve[36] = EditCurve[0]; + + SbString text; + text.sprintf(" (%.1fR %.1fX %.1fY)", radius, lengthX, lengthY); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(0.f, 0.f))) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_SEEK_First) { + StartPos = onSketchPos; + Mode = STATUS_SEEK_Second; + } + else { + EndPos = onSketchPos; + Mode = STATUS_End; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode == STATUS_End) { + unsetCursor(); + resetPositionText(); + + int firstCurve = getHighestCurveIndex() + 1; + // add the geometry to the sketch + // first determine the angles for the first arc + double start = 0; + double end = M_PI / 2; + if (signX > 0 && signY > 0) { + start = -2 * end; + end = -1 * end; + } + else if (signX > 0 && signY < 0) { + start = end; + end = 2 * end; + } + else if (signX < 0 && signY > 0) { + start = -1 * end; + end = 0; + } + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add rounded rectangle")); + Gui::Command::doCommand(Gui::Command::Doc, + // syntax for arcs: Part.ArcOfCircle(Part.Circle(center, axis, radius), startangle, endangle) + "geoList = []\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "%s.addGeometry(geoList, %s)\n" + "conList = []\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 1, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 2))\n" + "conList.append(Sketcher.Constraint('Horizontal', %i))\n" + "conList.append(Sketcher.Constraint('Horizontal', %i))\n" + "conList.append(Sketcher.Constraint('Vertical', %i))\n" + "conList.append(Sketcher.Constraint('Vertical', %i))\n" + "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" + "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" + "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" + "%s.addConstraint(conList)\n" + "del geoList, conList\n", + StartPos.x + (signX * radius), StartPos.y + (signY * radius), // center of the arc 1 + radius, + start, end, // start and end angle of arc1 + EditCurve[8].x, EditCurve[8].y, EditCurve[9].x, EditCurve[9].y, // line 1 + signX == signY ? StartPos.x + (signX * radius) : StartPos.x + lengthX - (signX * radius), // center of the arc 2 + signX == signY ? StartPos.y + lengthY - (signY * radius) : StartPos.y + (signY * radius), + radius, + // start and end angle of arc 2 + // the logic is that end is start + M_PI / 2 and start is the previous end - M_PI + end - M_PI, + end - 0.5 * M_PI, + EditCurve[17].x, EditCurve[17].y, EditCurve[18].x, EditCurve[18].y, // line 2 + StartPos.x + lengthX - (signX * radius), StartPos.y + lengthY - (signY * radius), // center of the arc 3 + radius, + end - 1.5 * M_PI, + end - M_PI, + EditCurve[26].x, EditCurve[26].y, EditCurve[27].x, EditCurve[27].y, // line 3 + signX == signY ? StartPos.x + lengthX - (signX * radius) : StartPos.x + (signX * radius), // center of the arc 4 + signX == signY ? StartPos.y + (signY * radius) : StartPos.y + lengthY - (signY * radius), + radius, + end - 2 * M_PI, + end - 1.5 * M_PI, + EditCurve[35].x, EditCurve[35].y, EditCurve[36].x, EditCurve[36].y, // line 4 + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch + geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not + firstCurve, firstCurve + 1, // tangent 1 + firstCurve + 1, firstCurve + 2, // tangent 2 + firstCurve + 2, firstCurve + 3, // tangent 3 + firstCurve + 3, firstCurve + 4, // tangent 4 + firstCurve + 4, firstCurve + 5, // tangent 5 + firstCurve + 5, firstCurve + 6, // tangent 6 + firstCurve + 6, firstCurve + 7, // tangent 7 + firstCurve + 7, firstCurve, // tangent 8 + signX == signY ? firstCurve + 3 : firstCurve + 1, // horizontal constraint + signX == signY ? firstCurve + 7 : firstCurve + 5, // horizontal constraint + signX == signY ? firstCurve + 1 : firstCurve + 3, // vertical constraint + signX == signY ? firstCurve + 5 : firstCurve + 7, // vertical constraint + firstCurve, firstCurve + 2, // equal 1 + firstCurve + 2, firstCurve + 4, // equal 2 + firstCurve + 4, firstCurve + 6, // equal 3 + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch + + // now add construction geometry - two points used to take suggested constraints + Gui::Command::doCommand(Gui::Command::Doc, + "geoList = []\n" + "geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n" + "geoList.append(Part.Point(App.Vector(%f, %f, 0)))\n" + "%s.addGeometry(geoList, True)\n" // geometry as construction + "conList = []\n" + "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" + "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" + "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" + "conList.append(Sketcher.Constraint('PointOnObject', %i, 1, %i, ))\n" + "%s.addConstraint(conList)\n" + "del geoList, conList\n", + StartPos.x, StartPos.y, // point at StartPos + EndPos.x, EndPos.y, // point at EndPos + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch + firstCurve + 8, firstCurve + 1, // point on object constraint + firstCurve + 8, firstCurve + 7, // point on object constraint + firstCurve + 9, firstCurve + 3, // point on object constraint + firstCurve + 9, firstCurve + 5, // point on object constraint + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch + + Gui::Command::commitCommand(); + + // add auto constraints at the StartPos auxiliary point + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex() - 1, Sketcher::PointPos::start); + sugConstr1.clear(); + } + + // add auto constraints at the EndPos auxiliary point + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::PointPos::start); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add rounded rectangle: %s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecompute(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. + Mode = STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(37); + applyCursor(); + /* this is ok not to call to purgeHandler + * in continuous creation mode because the + * handler is destroyed by the quit() method on pressing the + * right button of the mouse */ + } + else { + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + } + } + return true; + } + +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Oblong"); + } + +protected: + BoxMode Mode; + Base::Vector2d StartPos, EndPos; + double lengthX, lengthY, radius; + float signX, signY; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerRectangle_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerSlot.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerSlot.h new file mode 100644 index 0000000000..6965aa2b41 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerSlot.h @@ -0,0 +1,306 @@ +/*************************************************************************** + * 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_DrawSketchHandlerSlot_H +#define SKETCHERGUI_DrawSketchHandlerSlot_H + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class DrawSketchHandlerSlot : public DrawSketchHandler +{ +public: + DrawSketchHandlerSlot() + : Mode(STATUS_SEEK_First) + , SnapMode(SNAP_MODE_Free) + , SnapDir(SNAP_DIR_Horz) + , dx(0), dy(0), r(0) + , EditCurve(35) + { + } + virtual ~DrawSketchHandlerSlot() {} + /// mode table + enum BoxMode { + STATUS_SEEK_First, /**< enum value ----. */ + STATUS_SEEK_Second, /**< enum value ----. */ + STATUS_End + }; + + enum SNAP_MODE + { + SNAP_MODE_Free, + SNAP_MODE_Straight + }; + + enum SNAP_DIR + { + SNAP_DIR_Horz, + SNAP_DIR_Vert + }; + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + + if (Mode == STATUS_SEEK_First) { + setPositionText(onSketchPos); + if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f))) { + renderSuggestConstraintsCursor(sugConstr1); + return; + } + } + else if (Mode == STATUS_SEEK_Second) { + dx = onSketchPos.x - StartPos.x; + dy = onSketchPos.y - StartPos.y; + + if(QApplication::keyboardModifiers() == Qt::ControlModifier) + SnapMode = SNAP_MODE_Straight; + else + SnapMode = SNAP_MODE_Free; + + double a = 0; + double rev = 0; + if (fabs(dx) > fabs(dy)) { + r = fabs(dx) / 4; + rev = Base::sgn(dx); + SnapDir = SNAP_DIR_Horz; + if (SnapMode == SNAP_MODE_Straight) dy = 0; + } + else { + r = fabs(dy) / 4; + a = 8; + rev = Base::sgn(dy); + SnapDir = SNAP_DIR_Vert; + if (SnapMode == SNAP_MODE_Straight) dx = 0; + } + + // draw the arcs with each 16 segments + for (int i = 0; i < 17; i++) { + // first get the position at the arc + // if a is 0, the end points of the arc are at the y-axis, if it is 8, they are on the x-axis + double angle = (i + a) * M_PI / 16.0; + double rx = -r * rev * sin(angle); + double ry = r * rev * cos(angle); + // now apply the rotation matrix according to the angle between StartPos and onSketchPos + if (!(dx == 0 || dy == 0)) { + double rotAngle = atan(dy / dx); + if (a > 0) + rotAngle = -atan(dx / dy); + double rxRot = rx * cos(rotAngle) - ry * sin(rotAngle); + double ryRot = rx * sin(rotAngle) + ry * cos(rotAngle); + rx = rxRot; + ry = ryRot; + } + EditCurve[i] = Base::Vector2d(StartPos.x + rx, StartPos.y + ry); + EditCurve[17 + i] = Base::Vector2d(StartPos.x + dx - rx, StartPos.y + dy - ry); + } + EditCurve[34] = EditCurve[0]; + + SbString text; + text.sprintf(" (%.1fR %.1fL)", r, sqrt(dx * dx + dy * dy)); + setPositionText(onSketchPos, text); + + drawEdit(EditCurve); + if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2d(dx, dy), AutoConstraint::VERTEX_NO_TANGENCY)) { + renderSuggestConstraintsCursor(sugConstr2); + return; + } + } + applyCursor(); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + if (Mode == STATUS_SEEK_First) { + StartPos = onSketchPos; + Mode = STATUS_SEEK_Second; + } + else { + Mode = STATUS_End; + } + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + if (Mode == STATUS_End) { + unsetCursor(); + resetPositionText(); + + int firstCurve = getHighestCurveIndex() + 1; + // add the geometry to the sketch + // first determine the rotation angle for the first arc + double start, end; + if (fabs(dx) > fabs(dy)) { + if (dx > 0) { + start = 0.5 * M_PI; + end = 1.5 * M_PI; + } + else { + start = 1.5 * M_PI; + end = 0.5 * M_PI; + } + } + else { + if (dy > 0) { + start = -M_PI; + end = 0; + } + else { + start = 0; + end = -M_PI; + } + } + + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add slot")); + + AutoConstraint lastCons = {Sketcher::None, Sketcher::GeoEnum::GeoUndef, Sketcher::PointPos::none}; + + if (!sugConstr2.empty()) lastCons = sugConstr2.back(); + + ostringstream snapCon = ostringstream(""); + if (SnapMode == SNAP_MODE_Straight) { + snapCon << "conList.append(Sketcher.Constraint('"; + if (SnapDir == SNAP_DIR_Horz) { + snapCon << "Horizontal"; + } + else { + snapCon << "Vertical"; + } + snapCon << "'," << firstCurve + 2 << "))\n"; + + // If horizontal/vertical already applied because of snap, do not duplicate with Autocontraint + if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) + sugConstr2.pop_back(); + } + else { + // If horizontal/vertical Autoconstraint suggested, applied it on first line (rather than last arc) + if (lastCons.Type == Sketcher::Horizontal || lastCons.Type == Sketcher::Vertical) + sugConstr2.back().GeoId = firstCurve + 2; + } + + Gui::Command::doCommand(Gui::Command::Doc, + "geoList = []\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f, 0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.ArcOfCircle(Part.Circle(App.Vector(%f, %f ,0), App.Vector(0, 0, 1), %f), %f, %f))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "geoList.append(Part.LineSegment(App.Vector(%f, %f, 0), App.Vector(%f, %f, 0)))\n" + "%s.addGeometry(geoList, %s)\n" + "conList = []\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" + "conList.append(Sketcher.Constraint('Tangent', %i, 2, %i, 1))\n" + "conList.append(Sketcher.Constraint('Equal', %i, %i))\n" + "%s" + "%s.addConstraint(conList)\n" + "del geoList, conList\n", + StartPos.x, StartPos.y, // center of the arc1 + r, // radius arc1 + start, end, // start and end angle of arc1 + StartPos.x + dx, StartPos.y + dy, // center of the arc2 + r, // radius arc2 + end, end + M_PI, // start and end angle of arc2 + EditCurve[16].x, EditCurve[16].y, EditCurve[17].x, EditCurve[17].y, // line1 + EditCurve[33].x, EditCurve[33].y, EditCurve[34].x, EditCurve[34].y, // line2 + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str(), // the sketch + geometryCreationMode == Construction ? "True" : "False", // geometry as construction or not + firstCurve, firstCurve + 2, // tangent1 + firstCurve + 2, firstCurve + 1, // tangent2 + firstCurve + 1, firstCurve + 3, // tangent3 + firstCurve + 3, firstCurve, // tangent4 + firstCurve, firstCurve + 1, // equal constraint + snapCon.str().c_str(), // horizontal/vertical constraint if snapping + Gui::Command::getObjectCmd(sketchgui->getObject()).c_str()); // the sketch + + Gui::Command::commitCommand(); + + // add auto constraints at the center of the first arc + if (sugConstr1.size() > 0) { + createAutoConstraints(sugConstr1, getHighestCurveIndex() - 3, Sketcher::PointPos::mid); + sugConstr1.clear(); + } + + // add auto constraints at the center of the second arc + if (sugConstr2.size() > 0) { + createAutoConstraints(sugConstr2, getHighestCurveIndex() - 2, Sketcher::PointPos::mid); + sugConstr2.clear(); + } + + tryAutoRecomputeIfNotSolve(static_cast(sketchgui->getObject())); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to add slot: %s\n", e.what()); + Gui::Command::abortCommand(); + + tryAutoRecompute(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. + Mode = STATUS_SEEK_First; + EditCurve.clear(); + drawEdit(EditCurve); + EditCurve.resize(35); + applyCursor(); + /* this 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 + } + SnapMode = SNAP_MODE_Straight; + } + return true; + } +private: + virtual QString getCrosshairCursorSVGName() const override + { + return QString::fromLatin1("Sketcher_Pointer_Slot"); + } + +protected: + BoxMode Mode; + SNAP_MODE SnapMode; + SNAP_DIR SnapDir; + Base::Vector2d StartPos; + double dx, dy, r; + std::vector EditCurve; + std::vector sugConstr1, sugConstr2; +}; + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerSlot_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerSplitting.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerSplitting.h new file mode 100644 index 0000000000..b3108a6357 --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerSplitting.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * 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_DrawSketchHandlerSplitting_H +#define SKETCHERGUI_DrawSketchHandlerSplitting_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + + +class SplittingSelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + SplittingSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()), object(obj) + {} + + bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if (element.substr(0,4) == "Edge") { + int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; + Sketcher::SketchObject *Sketch = static_cast(object); + const Part::Geometry *geom = Sketch->getGeometry(GeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() + || geom->getTypeId() == Part::GeomCircle::getClassTypeId() + || geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + return true; + } + } + return false; + } +}; + + +class DrawSketchHandlerSplitting: public DrawSketchHandler +{ +public: + DrawSketchHandlerSplitting() = default; + virtual ~DrawSketchHandlerSplitting() + { + Gui::Selection().rmvSelectionGate(); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + int GeoId = getPreselectCurve(); + if (GeoId >= 0) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); + if (geom->getTypeId() == Part::GeomLineSegment::getClassTypeId() + || geom->getTypeId() == Part::GeomCircle::getClassTypeId() + || geom->getTypeId() == Part::GeomArcOfCircle::getClassTypeId()) { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Split edge")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "split(%d,App.Vector(%f,%f,0))", + GeoId, onSketchPos.x, onSketchPos.y); + Gui::Command::commitCommand(); + tryAutoRecompute(static_cast(sketchgui->getObject())); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to split edge: %s\n", e.what()); + Gui::Command::abortCommand(); + } + } + } + else { + sketchgui->purgeHandler(); + } + + return true; + } + +private: + virtual void activated() override + { + Gui::Selection().clearSelection(); + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new SplittingSelection(sketchgui->getObject())); + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Splitting"); + } +}; + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerSplitting_H + diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerTrimming.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerTrimming.h new file mode 100644 index 0000000000..9e27b166cf --- /dev/null +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerTrimming.h @@ -0,0 +1,175 @@ +/*************************************************************************** + * 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_DrawSketchHandlerTrimming_H +#define SKETCHERGUI_DrawSketchHandlerTrimming_H + + +#include "GeometryCreationMode.h" +#include "Utils.h" + +namespace SketcherGui { + +extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp + +class TrimmingSelection : public Gui::SelectionFilterGate +{ + App::DocumentObject* object; +public: + TrimmingSelection(App::DocumentObject* obj) + : Gui::SelectionFilterGate(nullPointer()), object(obj) + {} + + bool allow(App::Document * /*pDoc*/, App::DocumentObject *pObj, const char *sSubName) + { + if (pObj != this->object) + return false; + if (!sSubName || sSubName[0] == '\0') + return false; + std::string element(sSubName); + if (element.substr(0,4) == "Edge") { + int GeoId = std::atoi(element.substr(4,4000).c_str()) - 1; + Sketcher::SketchObject *Sketch = static_cast(object); + const Part::Geometry *geom = Sketch->getGeometry(GeoId); + if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || + geom->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() + ) { + // We do not trim internal geometry of complex geometries + if( Sketcher::GeometryFacade::isInternalType(geom, Sketcher::InternalType::None)) + return true; + } + } + return false; + } +}; + +class DrawSketchHandlerTrimming: public DrawSketchHandler +{ +public: + DrawSketchHandlerTrimming() = default; + virtual ~DrawSketchHandlerTrimming() + { + Gui::Selection().rmvSelectionGate(); + } + + virtual void mouseMove(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + + int GeoId = getPreselectCurve(); + + if (GeoId > -1) { + auto sk = static_cast(sketchgui->getObject()); + int GeoId1, GeoId2; + Base::Vector3d intersect1, intersect2; + if(sk->seekTrimPoints(GeoId, Base::Vector3d(onSketchPos.x,onSketchPos.y,0), + GeoId1, intersect1, + GeoId2, intersect2)) { + + EditMarkers.resize(0); + + if(GeoId1 != Sketcher::GeoEnum::GeoUndef) + EditMarkers.emplace_back(intersect1.x, intersect1.y); + else { + auto start = sk->getPoint(GeoId, Sketcher::PointPos::start); + EditMarkers.emplace_back(start.x, start.y); + } + + if(GeoId2 != Sketcher::GeoEnum::GeoUndef) + EditMarkers.emplace_back(intersect2.x, intersect2.y); + else { + auto end = sk->getPoint(GeoId, Sketcher::PointPos::end); + EditMarkers.emplace_back( end.x, end.y); + } + + drawEditMarkers(EditMarkers, 2); // maker augmented by two sizes (see supported marker sizes) + } + } + else { + EditMarkers.resize(0); + drawEditMarkers(EditMarkers, 2); + } + } + + virtual bool pressButton(Base::Vector2d onSketchPos) override + { + Q_UNUSED(onSketchPos); + return true; + } + + virtual bool releaseButton(Base::Vector2d onSketchPos) override + { + int GeoId = getPreselectCurve(); + if (GeoId > -1) { + const Part::Geometry *geom = sketchgui->getSketchObject()->getGeometry(GeoId); + if (geom->getTypeId().isDerivedFrom(Part::GeomTrimmedCurve::getClassTypeId()) || + geom->getTypeId() == Part::GeomCircle::getClassTypeId() || + geom->getTypeId() == Part::GeomEllipse::getClassTypeId() || + geom->getTypeId() == Part::GeomBSplineCurve::getClassTypeId() ) { + try { + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Trim edge")); + Gui::cmdAppObjectArgs(sketchgui->getObject(), "trim(%d,App.Vector(%f,%f,0))", + GeoId, onSketchPos.x, onSketchPos.y); + Gui::Command::commitCommand(); + tryAutoRecompute(static_cast(sketchgui->getObject())); + } + catch (const Base::Exception& e) { + Base::Console().Error("Failed to trim edge: %s\n", e.what()); + Gui::Command::abortCommand(); + } + } + + EditMarkers.resize(0); + drawEditMarkers(EditMarkers); + } + else // exit the trimming tool if the user clicked on empty space + sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider + + return true; + } + +private: + virtual void activated() override + { + Gui::Selection().clearSelection(); + Gui::Selection().rmvSelectionGate(); + Gui::Selection().addSelectionGate(new TrimmingSelection(sketchgui->getObject())); + } + + virtual QString getCrosshairCursorSVGName() const override { + return QString::fromLatin1("Sketcher_Pointer_Trimming"); + } + +private: + std::vector EditMarkers; +}; + + + +} // namespace SketcherGui + + +#endif // SKETCHERGUI_DrawSketchHandlerTrimming_H +