/*************************************************************************** * Copyright (c) 2010 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ViewProviderSketch.h" #include "DrawSketchHandler.h" #include "Utils.h" #include #include #include #include #include "GeometryCreationMode.h" #include "SketcherRegularPolygonDialog.h" using namespace std; using namespace SketcherGui; namespace SketcherGui { GeometryCreationMode geometryCreationMode=Normal; } /* helper functions ======================================================*/ // Return counter-clockwise angle from horizontal out of p1 to p2 in radians. double GetPointAngle (const Base::Vector2d &p1, const Base::Vector2d &p2) { double dX = p2.x - p1.x; double dY = p2.y - p1.y; return dY >= 0 ? atan2(dY, dX) : atan2(dY, dX) + 2*M_PI; } void ActivateHandler(Gui::Document *doc, DrawSketchHandler *handler) { std::unique_ptr ptr(handler); if (doc) { if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom(SketcherGui::ViewProviderSketch::getClassTypeId())) { SketcherGui::ViewProviderSketch* vp = static_cast (doc->getInEdit()); vp->purgeHandler(); vp->activateHandler(ptr.release()); } } } bool isCreateGeoActive(Gui::Document *doc) { if (doc) { // checks if a Sketch Viewprovider is in Edit and is in no special mode if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom (SketcherGui::ViewProviderSketch::getClassTypeId())) { /*if (dynamic_cast(doc->getInEdit())-> getSketchMode() == ViewProviderSketch::STATUS_NONE)*/ return true; } } return false; } SketcherGui::ViewProviderSketch* getSketchViewprovider(Gui::Document *doc) { if (doc) { if (doc->getInEdit() && doc->getInEdit()->isDerivedFrom (SketcherGui::ViewProviderSketch::getClassTypeId()) ) return dynamic_cast(doc->getInEdit()); } return 0; } void removeRedundantHorizontalVertical(Sketcher::SketchObject* psketch, std::vector &sug1, std::vector &sug2) { if(!sug1.empty() && !sug2.empty()) { bool rmvhorvert = false; // we look for: // 1. Coincident to external on both endpoints // 2. Coincident in one endpoint to origin and pointonobject/tangent to an axis on the other auto detectredundant = [psketch](std::vector &sug, bool &ext, bool &orig, bool &axis) { ext = false; orig = false; axis = false; for(std::vector::const_iterator it = sug.begin(); it!=sug.end(); ++it) { if( (*it).Type == Sketcher::Coincident && ext == false) { const std::map coincidents = psketch->getAllCoincidentPoints((*it).GeoId, (*it).PosId); if(!coincidents.empty()) { // the keys are ordered, so if the first is negative, it is coincident with external ext = coincidents.begin()->first < 0; std::map::const_iterator geoId1iterator; geoId1iterator = coincidents.find(-1); if( geoId1iterator != coincidents.end()) { if( (*geoId1iterator).second == Sketcher::PointPos::start ) orig = true; } } else { // it may be that there is no constraint at all, but there is external geometry ext = (*it).GeoId < 0; orig = ((*it).GeoId == -1 && (*it).PosId == Sketcher::PointPos::start); } } else if( (*it).Type == Sketcher::PointOnObject && axis == false) { axis = (((*it).GeoId == -1 && (*it).PosId == Sketcher::PointPos::none) || ((*it).GeoId == -2 && (*it).PosId == Sketcher::PointPos::none)); } } }; bool firstext = false, secondext = false, firstorig = false, secondorig = false, firstaxis = false, secondaxis = false; detectredundant(sug1, firstext, firstorig, firstaxis); detectredundant(sug2, secondext, secondorig, secondaxis); rmvhorvert = ((firstext && secondext) || // coincident with external on both endpoints (firstorig && secondaxis) || // coincident origin and point on object on other (secondorig && firstaxis)); if(rmvhorvert) { for(std::vector::reverse_iterator it = sug2.rbegin(); it!=sug2.rend(); ++it) { if( (*it).Type == Sketcher::Horizontal || (*it).Type == Sketcher::Vertical) { sug2.erase(std::next(it).base()); it = sug2.rbegin(); // erase invalidates the iterator } } } } } /* 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 void activated() override { setCrosshairCursor("Sketcher_Pointer_Create_Line"); } protected: SelectMode Mode; std::vector EditCurve; std::vector sugConstr1, sugConstr2; }; DEF_STD_CMD_AU(CmdSketcherCreateLine) CmdSketcherCreateLine::CmdSketcherCreateLine() : Command("Sketcher_CreateLine") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create line"); sToolTipText = QT_TR_NOOP("Create a line in the sketch"); sWhatsThis = "Sketcher_CreateLine"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateLine"; sAccel = "G, L"; eType = ForEdit; } void CmdSketcherCreateLine::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerLine() ); } void CmdSketcherCreateLine::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateLine")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateLine_Constr")); break; } } bool CmdSketcherCreateLine::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /* 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateRectangle") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create rectangle"); sToolTipText = QT_TR_NOOP("Create a rectangle in the sketch"); sWhatsThis = "Sketcher_CreateRectangle"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateRectangle"; sAccel = "G, R"; eType = ForEdit; } void CmdSketcherCreateRectangle::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBox(DrawSketchHandlerBox::Diagonal) ); } void CmdSketcherCreateRectangle::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr")); break; } } bool CmdSketcherCreateRectangle::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_AU(CmdSketcherCreateRectangleCenter) CmdSketcherCreateRectangleCenter::CmdSketcherCreateRectangleCenter() : Command("Sketcher_CreateRectangle_Center") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create centered rectangle"); sToolTipText = QT_TR_NOOP("Create a centered rectangle in the sketch"); sWhatsThis = "Sketcher_CreateRectangle_Center"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateRectangle_Center"; sAccel = "G, V"; eType = ForEdit; } void CmdSketcherCreateRectangleCenter::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBox(DrawSketchHandlerBox::CenterAndCorner) ); } void CmdSketcherCreateRectangleCenter::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center_Constr")); break; } } bool CmdSketcherCreateRectangleCenter::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /* 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateOblong") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create rounded rectangle"); sToolTipText = QT_TR_NOOP("Create a rounded rectangle in the sketch"); sWhatsThis = "Sketcher_CreateOblong"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateOblong"; sAccel = "G, O"; eType = ForEdit; } void CmdSketcherCreateOblong::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong()); } void CmdSketcherCreateOblong::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr")); break; } } bool CmdSketcherCreateOblong::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /* Rectangles Comp command =========================================*/ DEF_STD_CMD_ACLU(CmdSketcherCompCreateRectangles) CmdSketcherCompCreateRectangles::CmdSketcherCompCreateRectangles() : Command("Sketcher_CompCreateRectangles") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create rectangles"); sToolTipText = QT_TR_NOOP("Creates a rectangle in the sketch"); sWhatsThis = "Sketcher_CompCreateRectangles"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherCompCreateRectangles::activated(int iMsg) { if (iMsg == 0) ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox(DrawSketchHandlerBox::Diagonal)); else if (iMsg == 1) ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBox(DrawSketchHandlerBox::CenterAndCorner)); else if (iMsg == 2) ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerOblong()); else return; // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action* CmdSketcherCompCreateRectangles::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* arc1 = pcAction->addAction(QString()); arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle")); QAction* arc2 = pcAction->addAction(QString()); arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center")); QAction* arc3 = pcAction->addAction(QString()); arc3->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong")); _pcAction = pcAction; languageChange(); pcAction->setIcon(arc1->icon()); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateRectangles::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center_Constr")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateRectangles::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* rectangle1 = a[0]; rectangle1->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rectangle")); rectangle1->setToolTip(QApplication::translate("Sketcher_CreateRectangle", "Create a rectangle")); rectangle1->setStatusTip(rectangle1->toolTip()); QAction* rectangle2 = a[1]; rectangle2->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Centered rectangle")); rectangle2->setToolTip(QApplication::translate("Sketcher_CreateRectangle_Center", "Create a centered rectangle")); rectangle2->setStatusTip(rectangle2->toolTip()); QAction* rectangle3 = a[2]; rectangle3->setText(QApplication::translate("CmdSketcherCompCreateRectangles", "Rounded rectangle")); rectangle3->setToolTip(QApplication::translate("Sketcher_CreateOblong", "Create a rounded rectangle")); rectangle3->setStatusTip(rectangle3->toolTip()); } bool CmdSketcherCompCreateRectangles::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreatePolyline") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create polyline"); sToolTipText = QT_TR_NOOP("Create a polyline in the sketch. 'M' Key cycles behaviour"); sWhatsThis = "Sketcher_CreatePolyline"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreatePolyline"; sAccel = "G, M"; eType = ForEdit; } void CmdSketcherCreatePolyline::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerLineSet() ); } void CmdSketcherCreatePolyline::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePolyline")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePolyline_Constr")); break; } } bool CmdSketcherCreatePolyline::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateArc") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create arc by center"); sToolTipText = QT_TR_NOOP("Create an arc by its center and by its end points"); sWhatsThis = "Sketcher_CreateArc"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateArc"; sAccel = "G, A"; eType = ForEdit; } void CmdSketcherCreateArc::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArc() ); } bool CmdSketcherCreateArc::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_Create3PointArc") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create arc by three points"); sToolTipText = QT_TR_NOOP("Create an arc by its end points and a point along the arc"); sWhatsThis = "Sketcher_Create3PointArc"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Create3PointArc"; sAccel = "G, 3, A"; eType = ForEdit; } void CmdSketcherCreate3PointArc::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointArc() ); } bool CmdSketcherCreate3PointArc::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_ACLU(CmdSketcherCompCreateArc) CmdSketcherCompCreateArc::CmdSketcherCompCreateArc() : Command("Sketcher_CompCreateArc") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create arc"); sToolTipText = QT_TR_NOOP("Create an arc in the sketcher"); sWhatsThis = "Sketcher_CompCreateArc"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherCompCreateArc::activated(int iMsg) { if (iMsg==0) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArc()); else if (iMsg==1) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointArc()); else return; // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateArc::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* arc1 = pcAction->addAction(QString()); arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateArc")); QAction* arc2 = pcAction->addAction(QString()); arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc")); _pcAction = pcAction; languageChange(); pcAction->setIcon(arc1->icon()); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateArc::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateArc")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateArc_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointArc_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateArc::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* arc1 = a[0]; arc1->setText(QApplication::translate("CmdSketcherCompCreateArc","Center and end points")); arc1->setToolTip(QApplication::translate("Sketcher_CreateArc","Create an arc by its center and by its end points")); arc1->setStatusTip(QApplication::translate("Sketcher_CreateArc","Create an arc by its center and by its end points")); QAction* arc2 = a[1]; arc2->setText(QApplication::translate("CmdSketcherCompCreateArc","End points and rim point")); arc2->setToolTip(QApplication::translate("Sketcher_Create3PointArc","Create an arc by its end points and a point along the arc")); arc2->setStatusTip(QApplication::translate("Sketcher_Create3PointArc","Create an arc by its end points and a point along the arc")); } bool CmdSketcherCompCreateArc::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("Sketcher_Pointer_Create_Circle"); } protected: SelectMode Mode; std::vector EditCurve; std::vector sugConstr1, sugConstr2; }; DEF_STD_CMD_A(CmdSketcherCreateCircle) CmdSketcherCreateCircle::CmdSketcherCreateCircle() : Command("Sketcher_CreateCircle") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create circle"); sToolTipText = QT_TR_NOOP("Create a circle in the sketch"); sWhatsThis = "Sketcher_CreateCircle"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateCircle"; sAccel = "G, C"; eType = ForEdit; } void CmdSketcherCreateCircle::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerCircle() ); } bool CmdSketcherCreateCircle::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== /** * @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 { setCrosshairCursor("Sketcher_Pointer_Create_Ellipse"); if (constrMethod == 0) { method = CENTER_PERIAPSIS_B; mode = STATUS_SEEK_CENTROID; } else { method = PERIAPSIS_APOAPSIS_B; mode = STATUS_SEEK_PERIAPSIS; } } 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) /** * @brief ctor */ CmdSketcherCreateEllipseByCenter::CmdSketcherCreateEllipseByCenter() : Command("Sketcher_CreateEllipseByCenter") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create ellipse by center"); sToolTipText = QT_TR_NOOP("Create an ellipse by center in the sketch"); sWhatsThis = "Sketcher_CreateEllipseByCenter"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Conics_Ellipse_Center"; sAccel = "G, E, E"; eType = ForEdit; } void CmdSketcherCreateEllipseByCenter::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(0) ); } bool CmdSketcherCreateEllipseByCenter::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseBy3Points' DEF_STD_CMD_A(CmdSketcherCreateEllipseBy3Points) /** * @brief ctor */ CmdSketcherCreateEllipseBy3Points::CmdSketcherCreateEllipseBy3Points() : Command("Sketcher_CreateEllipseBy3Points") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create ellipse by 3 points"); sToolTipText = QT_TR_NOOP("Create an ellipse by 3 points in the sketch"); sWhatsThis = "Sketcher_CreateEllipseBy3Points"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateEllipse_3points"; sAccel = "G, 3, E"; eType = ForEdit; } void CmdSketcherCreateEllipseBy3Points::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(1) ); } bool CmdSketcherCreateEllipseBy3Points::isActive(void) { return isCreateGeoActive(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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateArcOfEllipse") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create an arc of ellipse"); sToolTipText = QT_TR_NOOP("Create an arc of ellipse in the sketch"); sWhatsThis = "Sketcher_CreateArcOfEllipse"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateElliptical_Arc"; sAccel = "G, E, A"; eType = ForEdit; } void CmdSketcherCreateArcOfEllipse::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfEllipse() ); } bool CmdSketcherCreateArcOfEllipse::isActive(void) { return isCreateGeoActive(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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateArcOfHyperbola") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create an arc of hyperbola"); sToolTipText = QT_TR_NOOP("Create an arc of hyperbola in the sketch"); sWhatsThis = "Sketcher_CreateArcOfHyperbola"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateHyperbolic_Arc"; sAccel = "G, H"; eType = ForEdit; } void CmdSketcherCreateArcOfHyperbola::activated(int /*iMsg*/) { ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfHyperbola() ); } bool CmdSketcherCreateArcOfHyperbola::isActive(void) { return isCreateGeoActive(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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateArcOfParabola") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create an arc of parabola"); sToolTipText = QT_TR_NOOP("Create an arc of parabola in the sketch"); sWhatsThis = "Sketcher_CreateArcOfParabola"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateParabolic_Arc"; sAccel = "G, J"; eType = ForEdit; } void CmdSketcherCreateArcOfParabola::activated(int /*iMsg*/) { ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerArcOfParabola() ); } bool CmdSketcherCreateArcOfParabola::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateEllipse' DEF_STD_CMD_ACLU(CmdSketcherCompCreateConic) /** * @brief ctor */ CmdSketcherCompCreateConic::CmdSketcherCompCreateConic() : Command("Sketcher_CompCreateConic") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create a conic"); sToolTipText = QT_TR_NOOP("Create a conic in the sketch"); sWhatsThis = "Sketcher_CompCreateConic"; sStatusTip = sToolTipText; eType = ForEdit; } /** * @brief Instantiates the conic handler when the conic command activated * @param int iMsg */ void CmdSketcherCompCreateConic::activated(int iMsg) { if (iMsg == 0) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg)); } else if (iMsg == 1) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg)); } else if (iMsg == 2) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfEllipse()); } else if (iMsg == 3) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfHyperbola()); } else if (iMsg == 4) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerArcOfParabola()); } else { return; } // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateConic::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* ellipseByCenter = pcAction->addAction(QString()); ellipseByCenter->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse")); /// @todo replace with correct icon QAction* ellipseBy3Points = pcAction->addAction(QString()); ellipseBy3Points->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points")); QAction* arcofellipse = pcAction->addAction(QString()); arcofellipse->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc")); QAction* arcofhyperbola = pcAction->addAction(QString()); arcofhyperbola->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc")); QAction* arcofparabola = pcAction->addAction(QString()); arcofparabola->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc")); _pcAction = pcAction; languageChange(); // set ellipse by center, a, b as default method pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Conics")); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateConic::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc")); a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc")); a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateEllipse_3points_Constr")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateElliptical_Arc_Constr")); a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHyperbolic_Arc_Constr")); a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateParabolic_Arc_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateConic::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* ellipseByCenter = a[0]; ellipseByCenter->setText(QApplication::translate("CmdSketcherCompCreateConic","Ellipse by center, major radius, point")); ellipseByCenter->setToolTip(QApplication::translate("Sketcher_CreateEllipseByCenter","Create an ellipse by center, major radius and point")); ellipseByCenter->setStatusTip(QApplication::translate("Sketcher_CreateEllipseByCenter","Create an ellipse by center, major radius and point")); QAction* ellipseBy3Points = a[1]; ellipseBy3Points->setText(QApplication::translate("CmdSketcherCompCreateConic","Ellipse by periapsis, apoapsis, minor radius")); ellipseBy3Points->setToolTip(QApplication::translate("Sketcher_CreateEllipseBy3Points","Create a ellipse by periapsis, apoapsis, and minor radius")); ellipseBy3Points->setStatusTip(QApplication::translate("Sketcher_CreateEllipseBy3Points","Create a ellipse by periapsis, apoapsis, and minor radius")); QAction* arcofellipse = a[2]; arcofellipse->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of ellipse by center, major radius, endpoints")); arcofellipse->setToolTip(QApplication::translate("Sketcher_CreateArcOfEllipse","Create an arc of ellipse by its center, major radius, and endpoints")); arcofellipse->setStatusTip(QApplication::translate("Sketcher_CreateArcOfEllipse","Create an arc of ellipse by its center, major radius, and endpoints")); QAction* arcofhyperbola = a[3]; arcofhyperbola->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of hyperbola by center, major radius, endpoints")); arcofhyperbola->setToolTip(QApplication::translate("Sketcher_CreateArcOfHyperbola","Create an arc of hyperbola by its center, major radius, and endpoints")); arcofhyperbola->setStatusTip(QApplication::translate("Sketcher_CreateArcOfHyperbola","Create an arc of hyperbola by its center, major radius, and endpoints")); QAction* arcofparabola = a[4]; arcofparabola->setText(QApplication::translate("CmdSketcherCompCreateConic","Arc of parabola by focus, vertex, endpoints")); arcofparabola->setToolTip(QApplication::translate("Sketcher_CreateArcOfParabola","Create an arc of parabola by its focus, vertex, and endpoints")); arcofparabola->setStatusTip(QApplication::translate("Sketcher_CreateArcOfParabola","Create an arc of parabola by its focus, vertex, and endpoints")); } bool CmdSketcherCompCreateConic::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("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) { 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; std::vector BSplinePoles; std::vector> sugConstr; int ConstrMethod; int SplineDegree; bool IsClosed; std::vector poleGeoIds; Base::Vector2d prevCursorPosition; }; DEF_STD_CMD_A(CmdSketcherCreateBSpline) CmdSketcherCreateBSpline::CmdSketcherCreateBSpline() : Command("Sketcher_CreateBSpline") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create B-spline"); sToolTipText = QT_TR_NOOP("Create a B-spline via control points in the sketch."); sWhatsThis = "Sketcher_CreateBSpline"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateBSpline"; sAccel = "G, B, B"; eType = ForEdit; } void CmdSketcherCreateBSpline::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBSpline(0) ); } /*void CmdSketcherCreateBSpline::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline_Constr")); break; } }*/ bool CmdSketcherCreateBSpline::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateBSpline' DEF_STD_CMD_A(CmdSketcherCreatePeriodicBSpline) /** * @brief ctor */ CmdSketcherCreatePeriodicBSpline::CmdSketcherCreatePeriodicBSpline() : Command("Sketcher_CreatePeriodicBSpline") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create periodic B-spline"); sToolTipText = QT_TR_NOOP("Create a periodic B-spline via control points in the sketch."); sWhatsThis = "Sketcher_CreatePeriodicBSpline"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Create_Periodic_BSpline"; sAccel = "G, B, P"; eType = ForEdit; } void CmdSketcherCreatePeriodicBSpline::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerBSpline(1) ); } bool CmdSketcherCreatePeriodicBSpline::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateBSpline' DEF_STD_CMD_ACLU(CmdSketcherCompCreateBSpline) /** * @brief ctor */ CmdSketcherCompCreateBSpline::CmdSketcherCompCreateBSpline() : Command("Sketcher_CompCreateBSpline") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create a B-spline"); sToolTipText = QT_TR_NOOP("Create a B-spline in the sketch"); sWhatsThis = "Sketcher_CompCreateBSpline"; sStatusTip = sToolTipText; eType = ForEdit; } /** * @brief Instantiates the B-spline handler when the B-spline command activated * @param int iMsg */ void CmdSketcherCompCreateBSpline::activated(int iMsg) { if (iMsg == 0) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); } else if (iMsg == 1) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerBSpline(iMsg)); } else { return; } // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateBSpline::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* bspline = pcAction->addAction(QString()); bspline->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline")); QAction* periodicbspline = pcAction->addAction(QString()); periodicbspline->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline")); _pcAction = pcAction; languageChange(); // default pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline")); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateBSpline::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateBSpline_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create_Periodic_BSpline_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateBSpline::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* bspline = a[0]; bspline->setText(QApplication::translate("Sketcher_CreateBSpline","B-spline by control points")); bspline->setToolTip(QApplication::translate("Sketcher_CreateBSpline","Create a B-spline by control points")); bspline->setStatusTip(QApplication::translate("Sketcher_CreateBSpline","Create a B-spline by control points")); QAction* periodicbspline = a[1]; periodicbspline->setText(QApplication::translate("Sketcher_Create_Periodic_BSpline","Periodic B-spline by control points")); periodicbspline->setToolTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic B-spline by control points")); periodicbspline->setStatusTip(QApplication::translate("Sketcher_Create_Periodic_BSpline","Create a periodic B-spline by control points")); } bool CmdSketcherCompCreateBSpline::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_Create3PointCircle") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create circle by three points"); sToolTipText = QT_TR_NOOP("Create a circle by 3 perimeter points"); sWhatsThis = "Sketcher_Create3PointCircle"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Create3PointCircle"; sAccel = "G, 3, C"; eType = ForEdit; } void CmdSketcherCreate3PointCircle::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointCircle() ); } bool CmdSketcherCreate3PointCircle::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_ACLU(CmdSketcherCompCreateCircle) CmdSketcherCompCreateCircle::CmdSketcherCompCreateCircle() : Command("Sketcher_CompCreateCircle") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create circle"); sToolTipText = QT_TR_NOOP("Create a circle in the sketcher"); sWhatsThis = "Sketcher_CompCreateCircle"; sStatusTip = sToolTipText; eType = ForEdit; } void CmdSketcherCompCreateCircle::activated(int iMsg) { if (iMsg==0) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerCircle()); else if (iMsg==1) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandler3PointCircle()); else return; // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateCircle::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* arc1 = pcAction->addAction(QString()); arc1->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateCircle")); QAction* arc2 = pcAction->addAction(QString()); arc2->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle")); _pcAction = pcAction; languageChange(); pcAction->setIcon(arc1->icon()); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateCircle::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateCircle")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateCircle_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_Create3PointCircle_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateCircle::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* arc1 = a[0]; arc1->setText(QApplication::translate("CmdSketcherCompCreateCircle", "Center and rim point")); arc1->setToolTip(QApplication::translate("Sketcher_CreateCircle", "Create a circle by its center and by a rim point")); arc1->setStatusTip(QApplication::translate("Sketcher_CreateCircle", "Create a circle by its center and by a rim point")); QAction* arc2 = a[1]; arc2->setText(QApplication::translate("CmdSketcherCompCreateCircle", "3 rim points")); arc2->setToolTip(QApplication::translate("Sketcher_Create3PointCircle", "Create a circle by 3 rim points")); arc2->setStatusTip(QApplication::translate("Sketcher_Create3PointCircle", "Create a circle by 3 rim points")); } bool CmdSketcherCompCreateCircle::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== 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 void activated() override { setCrosshairCursor("Sketcher_Pointer_Create_Point"); } protected: bool selectionDone; Base::Vector2d EditPoint; std::vector sugConstr; }; DEF_STD_CMD_A(CmdSketcherCreatePoint) CmdSketcherCreatePoint::CmdSketcherCreatePoint() : Command("Sketcher_CreatePoint") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create point"); sToolTipText = QT_TR_NOOP("Create a point in the sketch"); sWhatsThis = "Sketcher_CreatePoint"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreatePoint"; sAccel = "G, Y"; eType = ForEdit; } void CmdSketcherCreatePoint::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerPoint()); } bool CmdSketcherCreatePoint::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== DEF_STD_CMD_A(CmdSketcherCreateText) CmdSketcherCreateText::CmdSketcherCreateText() : Command("Sketcher_CreateText") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create text"); sToolTipText = QT_TR_NOOP("Create text in the sketch"); sWhatsThis = "Sketcher_CreateText"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateText"; eType = ForEdit; } void CmdSketcherCreateText::activated(int iMsg) { Q_UNUSED(iMsg); } bool CmdSketcherCreateText::isActive(void) { return false; } // ====================================================================================== DEF_STD_CMD_A(CmdSketcherCreateDraftLine) CmdSketcherCreateDraftLine::CmdSketcherCreateDraftLine() : Command("Sketcher_CreateDraftLine") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create draft line"); sToolTipText = QT_TR_NOOP("Create a draft line in the sketch"); sWhatsThis = "Sketcher_CreateDraftLine"; sStatusTip = sToolTipText; sPixmap = "Sketcher_DraftLine"; eType = ForEdit; } void CmdSketcherCreateDraftLine::activated(int iMsg) { Q_UNUSED(iMsg); } bool CmdSketcherCreateDraftLine::isActive(void) { return false; } // ====================================================================================== namespace SketcherGui { class FilletSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: FilletSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), 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 void activated() override { Gui::Selection().rmvSelectionGate(); Gui::Selection().addSelectionGate(new FilletSelection(sketchgui->getObject())); setCrosshairCursor("Sketcher_Pointer_Create_Fillet"); } protected: int filletType; SelectMode Mode; int firstCurve; Base::Vector2d firstPos; }; DEF_STD_CMD_A(CmdSketcherCreateFillet) CmdSketcherCreateFillet::CmdSketcherCreateFillet() : Command("Sketcher_CreateFillet") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create fillet"); sToolTipText = QT_TR_NOOP("Create a fillet between two lines or at a coincident point"); sWhatsThis = "Sketcher_CreateFillet"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateFillet"; sAccel = "G, F, F"; eType = ForEdit; } void CmdSketcherCreateFillet::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet)); } bool CmdSketcherCreateFillet::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== DEF_STD_CMD_A(CmdSketcherCreatePointFillet) CmdSketcherCreatePointFillet::CmdSketcherCreatePointFillet() : Command("Sketcher_CreatePointFillet") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create corner-preserving fillet"); sToolTipText = QT_TR_NOOP("Fillet that preserves intersection point and most constraints"); sWhatsThis = "Sketcher_CreatePointFillet"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateFillet"; sAccel = "G, F, P"; eType = ForEdit; } void CmdSketcherCreatePointFillet::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet)); } bool CmdSketcherCreatePointFillet::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateFillets' DEF_STD_CMD_ACLU(CmdSketcherCompCreateFillets) /** * @brief ctor */ CmdSketcherCompCreateFillets::CmdSketcherCompCreateFillets() : Command("Sketcher_CompCreateFillets") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Fillets"); sToolTipText = QT_TR_NOOP("Create a fillet between two lines"); sWhatsThis = "Sketcher_CompCreateFillets"; sStatusTip = sToolTipText; eType = ForEdit; } /** * @brief Instantiates the fillet handler when the fillet command activated * @param int iMsg */ void CmdSketcherCompCreateFillets::activated(int iMsg) { if (iMsg == 0) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::SimpleFillet)); } else if (iMsg == 1) { ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerFillet(DrawSketchHandlerFillet::ConstraintPreservingFillet)); } else { return; } // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateFillets::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* oldFillet = pcAction->addAction(QString()); oldFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); QAction* pointFillet = pcAction->addAction(QString()); pointFillet->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet")); _pcAction = pcAction; languageChange(); pcAction->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); int defaultId = 0; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateFillets::updateAction(int mode) { Q_UNUSED(mode); Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFillet")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePointFillet")); getAction()->setIcon(a[index]->icon()); } void CmdSketcherCompCreateFillets::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* oldFillet = a[0]; oldFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Sketch fillet")); oldFillet->setToolTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines")); oldFillet->setStatusTip(QApplication::translate("Sketcher_CreateFillet","Creates a radius between two lines")); QAction* pointFillet = a[1]; pointFillet->setText(QApplication::translate("CmdSketcherCompCreateFillets","Constraint-preserving sketch fillet")); pointFillet->setToolTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point")); pointFillet->setStatusTip(QApplication::translate("Sketcher_CreatePointFillet","Fillet that preserves constraints and intersection point")); } bool CmdSketcherCompCreateFillets::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== namespace SketcherGui { class TrimmingSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: TrimmingSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), 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 void activated() override { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); Gui::Selection().addSelectionGate(new TrimmingSelection(sketchgui->getObject())); setCrosshairCursor("Sketcher_Pointer_Trimming"); } private: std::vector EditMarkers; }; DEF_STD_CMD_A(CmdSketcherTrimming) CmdSketcherTrimming::CmdSketcherTrimming() : Command("Sketcher_Trimming") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Trim edge"); sToolTipText = QT_TR_NOOP("Trim an edge with respect to the picked position"); sWhatsThis = "Sketcher_Trimming"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Trimming"; sAccel = "G, T"; eType = ForEdit; } void CmdSketcherTrimming::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerTrimming()); } bool CmdSketcherTrimming::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== namespace SketcherGui { class ExtendSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: ExtendSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0) , 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); setCrosshairCursor("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 CmdSketcherExtend::CmdSketcherExtend() : Command("Sketcher_Extend") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Extend edge"); sToolTipText = QT_TR_NOOP("Extend an edge with respect to the picked position"); sWhatsThis = "Sketcher_Extend"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Extend"; sAccel = "G, Q"; eType = ForEdit; } void CmdSketcherExtend::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerExtend()); } bool CmdSketcherExtend::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== namespace SketcherGui { class SplittingSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: SplittingSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), 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 void activated() override { Gui::Selection().clearSelection(); Gui::Selection().rmvSelectionGate(); Gui::Selection().addSelectionGate(new SplittingSelection(sketchgui->getObject())); setCrosshairCursor("Sketcher_Pointer_Splitting"); } }; DEF_STD_CMD_A(CmdSketcherSplit) //TODO: fix the translations for this CmdSketcherSplit::CmdSketcherSplit() : Command("Sketcher_Split") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Split edge"); sToolTipText = QT_TR_NOOP("Splits an edge into two while preserving constraints"); sWhatsThis = "Sketcher_Split"; sStatusTip = sToolTipText; sPixmap = "Sketcher_Split"; sAccel = "G, Z"; eType = ForEdit; } void CmdSketcherSplit::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSplitting()); } bool CmdSketcherSplit::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } namespace SketcherGui { class ExternalSelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: ExternalSelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), 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 == NULL) 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())); setCrosshairCursor("Sketcher_Pointer_External"); } virtual void deactivated() override { Q_UNUSED(sketchgui); setAxisPickStyle(true); } }; DEF_STD_CMD_A(CmdSketcherExternal) CmdSketcherExternal::CmdSketcherExternal() : Command("Sketcher_External") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("External geometry"); sToolTipText = QT_TR_NOOP("Create an edge linked to an external geometry"); sWhatsThis = "Sketcher_External"; sStatusTip = sToolTipText; sPixmap = "Sketcher_External"; sAccel = "G, X"; eType = ForEdit; } void CmdSketcherExternal::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerExternal()); } bool CmdSketcherExternal::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } // ====================================================================================== namespace SketcherGui { class CarbonCopySelection : public Gui::SelectionFilterGate { App::DocumentObject* object; public: CarbonCopySelection(App::DocumentObject* obj) : Gui::SelectionFilterGate((Gui::SelectionFilter*)0), 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 == NULL) 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())); setCrosshairCursor("Sketcher_Pointer_CarbonCopy"); } virtual void deactivated() override { Q_UNUSED(sketchgui); setAxisPickStyle(true); } }; DEF_STD_CMD_AU(CmdSketcherCarbonCopy) CmdSketcherCarbonCopy::CmdSketcherCarbonCopy() : Command("Sketcher_CarbonCopy") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Carbon copy"); sToolTipText = QT_TR_NOOP("Copies the geometry of another sketch"); sWhatsThis = "Sketcher_CarbonCopy"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CarbonCopy"; sAccel = "G, W"; eType = ForEdit; } void CmdSketcherCarbonCopy::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerCarbonCopy()); } bool CmdSketcherCarbonCopy::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } void CmdSketcherCarbonCopy::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CarbonCopy")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CarbonCopy_Constr")); break; } } /** * 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 void activated() override { setCrosshairCursor("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) CmdSketcherCreateSlot::CmdSketcherCreateSlot() : Command("Sketcher_CreateSlot") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create slot"); sToolTipText = QT_TR_NOOP("Create a slot in the sketch"); sWhatsThis = "Sketcher_CreateSlot"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateSlot"; sAccel = "G, S"; eType = ForEdit; } void CmdSketcherCreateSlot::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerSlot()); } void CmdSketcherCreateSlot::updateAction(int mode) { switch (mode) { case Normal: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSlot")); break; case Construction: if (getAction()) getAction()->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSlot_Constr")); break; } } bool CmdSketcherCreateSlot::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } /* 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 void activated() override { setCrosshairCursor("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() : Command("Sketcher_CreateTriangle") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create equilateral triangle"); sToolTipText = QT_TR_NOOP("Create an equilateral triangle in the sketch"); sWhatsThis = "Sketcher_CreateTriangle"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateTriangle"; sAccel = "G, P, 3"; eType = ForEdit; } void CmdSketcherCreateTriangle::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(3) ); } bool CmdSketcherCreateTriangle::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreateSquare) CmdSketcherCreateSquare::CmdSketcherCreateSquare() : Command("Sketcher_CreateSquare") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create square"); sToolTipText = QT_TR_NOOP("Create a square in the sketch"); sWhatsThis = "Sketcher_CreateSquare"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateSquare"; sAccel = "G, P, 4"; eType = ForEdit; } void CmdSketcherCreateSquare::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(4) ); } bool CmdSketcherCreateSquare::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreatePentagon) CmdSketcherCreatePentagon::CmdSketcherCreatePentagon() : Command("Sketcher_CreatePentagon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create pentagon"); sToolTipText = QT_TR_NOOP("Create a pentagon in the sketch"); sWhatsThis = "Sketcher_CreatePentagon"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreatePentagon"; sAccel = "G, P, 5"; eType = ForEdit; } void CmdSketcherCreatePentagon::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(5) ); } bool CmdSketcherCreatePentagon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreateHexagon) CmdSketcherCreateHexagon::CmdSketcherCreateHexagon() : Command("Sketcher_CreateHexagon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create hexagon"); sToolTipText = QT_TR_NOOP("Create a hexagon in the sketch"); sWhatsThis = "Sketcher_CreateHexagon"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateHexagon"; sAccel = "G, P, 6"; eType = ForEdit; } void CmdSketcherCreateHexagon::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(6) ); } bool CmdSketcherCreateHexagon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreateHeptagon) CmdSketcherCreateHeptagon::CmdSketcherCreateHeptagon() : Command("Sketcher_CreateHeptagon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create heptagon"); sToolTipText = QT_TR_NOOP("Create a heptagon in the sketch"); sWhatsThis = "Sketcher_CreateHeptagon"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateHeptagon"; sAccel = "G, P, 7"; eType = ForEdit; } void CmdSketcherCreateHeptagon::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(7) ); } bool CmdSketcherCreateHeptagon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreateOctagon) CmdSketcherCreateOctagon::CmdSketcherCreateOctagon() : Command("Sketcher_CreateOctagon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create octagon"); sToolTipText = QT_TR_NOOP("Create an octagon in the sketch"); sWhatsThis = "Sketcher_CreateOctagon"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateOctagon"; sAccel = "G, P, 8"; eType = ForEdit; } void CmdSketcherCreateOctagon::activated(int iMsg) { Q_UNUSED(iMsg); ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(8) ); } bool CmdSketcherCreateOctagon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_A(CmdSketcherCreateRegularPolygon) CmdSketcherCreateRegularPolygon::CmdSketcherCreateRegularPolygon() : Command("Sketcher_CreateRegularPolygon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create regular polygon"); sToolTipText = QT_TR_NOOP("Create a regular polygon in the sketch"); sWhatsThis = "Sketcher_CreateRegularPolygon"; sStatusTip = sToolTipText; sPixmap = "Sketcher_CreateRegularPolygon"; sAccel = "G, P, R"; eType = ForEdit; } void CmdSketcherCreateRegularPolygon::activated(int iMsg) { Q_UNUSED(iMsg); // Pop-up asking for values SketcherRegularPolygonDialog srpd; if (srpd.exec() == QDialog::Accepted) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(srpd.sides)); } bool CmdSketcherCreateRegularPolygon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } DEF_STD_CMD_ACLU(CmdSketcherCompCreateRegularPolygon) CmdSketcherCompCreateRegularPolygon::CmdSketcherCompCreateRegularPolygon() : Command("Sketcher_CompCreateRegularPolygon") { sAppModule = "Sketcher"; sGroup = "Sketcher"; sMenuText = QT_TR_NOOP("Create regular polygon"); sToolTipText = QT_TR_NOOP("Create a regular polygon in the sketcher"); sWhatsThis = "Sketcher_CompCreateRegularPolygon"; sStatusTip = sToolTipText; sAccel = "G, P, P"; eType = ForEdit; } void CmdSketcherCompCreateRegularPolygon::activated(int iMsg) { switch( iMsg ){ case 0: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(3)); break; case 1: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(4)); break; case 2: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(5)); break; case 3: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(6)); break; case 4: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(7)); break; case 5: ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(8)); break; case 6: { // Pop-up asking for values SketcherRegularPolygonDialog srpd; if (srpd.exec() == QDialog::Accepted) ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerRegularPolygon(srpd.sides)); } break; default: return; } // Since the default icon is reset when enabling/disabling the command we have // to explicitly set the icon of the used command. Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); assert(iMsg < a.size()); pcAction->setIcon(a[iMsg]->icon()); } Gui::Action * CmdSketcherCompCreateRegularPolygon::createAction(void) { Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); pcAction->setDropDownMenu(true); applyCommandData(this->className(), pcAction); QAction* triangle = pcAction->addAction(QString()); triangle->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle")); QAction* square = pcAction->addAction(QString()); square->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare")); QAction* pentagon = pcAction->addAction(QString()); pentagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon")); QAction* hexagon = pcAction->addAction(QString()); hexagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon")); QAction* heptagon = pcAction->addAction(QString()); heptagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon")); QAction* octagon = pcAction->addAction(QString()); octagon->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon")); QAction* regular = pcAction->addAction(QString()); regular->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon")); _pcAction = pcAction; languageChange(); pcAction->setIcon(hexagon->icon()); int defaultId = 3; pcAction->setProperty("defaultAction", QVariant(defaultId)); return pcAction; } void CmdSketcherCompCreateRegularPolygon::updateAction(int mode) { Gui::ActionGroup* pcAction = qobject_cast(getAction()); if (!pcAction) return; QList a = pcAction->actions(); int index = pcAction->property("defaultAction").toInt(); switch (mode) { case Normal: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon")); a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon")); a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon")); a[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon")); a[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon")); getAction()->setIcon(a[index]->icon()); break; case Construction: a[0]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateTriangle_Constr")); a[1]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateSquare_Constr")); a[2]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreatePentagon_Constr")); a[3]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHexagon_Constr")); a[4]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateHeptagon_Constr")); a[5]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOctagon_Constr")); a[6]->setIcon(Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRegularPolygon_Constr")); getAction()->setIcon(a[index]->icon()); break; } } void CmdSketcherCompCreateRegularPolygon::languageChange() { Command::languageChange(); if (!_pcAction) return; Gui::ActionGroup* pcAction = qobject_cast(_pcAction); QList a = pcAction->actions(); QAction* triangle = a[0]; triangle->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Triangle")); triangle->setToolTip(QApplication::translate("Sketcher_CreateTriangle","Create an equilateral triangle by its center and by one corner")); triangle->setStatusTip(QApplication::translate("Sketcher_CreateTriangle","Create an equilateral triangle by its center and by one corner")); QAction* square = a[1]; square->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Square")); square->setToolTip(QApplication::translate("Sketcher_CreateSquare","Create a square by its center and by one corner")); square->setStatusTip(QApplication::translate("Sketcher_CreateSquare","Create a square by its center and by one corner")); QAction* pentagon = a[2]; pentagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Pentagon")); pentagon->setToolTip(QApplication::translate("Sketcher_CreatePentagon","Create a pentagon by its center and by one corner")); pentagon->setStatusTip(QApplication::translate("Sketcher_CreatePentagon","Create a pentagon by its center and by one corner")); QAction* hexagon = a[3]; hexagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Hexagon")); hexagon->setToolTip(QApplication::translate("Sketcher_CreateHexagon","Create a hexagon by its center and by one corner")); hexagon->setStatusTip(QApplication::translate("Sketcher_CreateHexagon","Create a hexagon by its center and by one corner")); QAction* heptagon = a[4]; heptagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Heptagon")); heptagon->setToolTip(QApplication::translate("Sketcher_CreateHeptagon","Create a heptagon by its center and by one corner")); heptagon->setStatusTip(QApplication::translate("Sketcher_CreateHeptagon","Create a heptagon by its center and by one corner")); QAction* octagon = a[5]; octagon->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Octagon")); octagon->setToolTip(QApplication::translate("Sketcher_CreateOctagon","Create an octagon by its center and by one corner")); octagon->setStatusTip(QApplication::translate("Sketcher_CreateOctagon","Create an octagon by its center and by one corner")); QAction* regular = a[6]; regular->setText(QApplication::translate("CmdSketcherCompCreateRegularPolygon","Regular polygon")); regular->setToolTip(QApplication::translate("Sketcher_CreateOctagon","Create a regular polygon by its center and by one corner")); regular->setStatusTip(QApplication::translate("Sketcher_CreateOctagon","Create a regular polygon by its center and by one corner")); } bool CmdSketcherCompCreateRegularPolygon::isActive(void) { return isCreateGeoActive(getActiveGuiDocument()); } void CreateSketcherCommandsCreateGeo(void) { Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdSketcherCreatePoint()); rcCmdMgr.addCommand(new CmdSketcherCreateArc()); rcCmdMgr.addCommand(new CmdSketcherCreate3PointArc()); rcCmdMgr.addCommand(new CmdSketcherCompCreateArc()); rcCmdMgr.addCommand(new CmdSketcherCreateCircle()); rcCmdMgr.addCommand(new CmdSketcherCreate3PointCircle()); rcCmdMgr.addCommand(new CmdSketcherCompCreateCircle()); rcCmdMgr.addCommand(new CmdSketcherCreateEllipseByCenter()); rcCmdMgr.addCommand(new CmdSketcherCreateEllipseBy3Points()); rcCmdMgr.addCommand(new CmdSketcherCompCreateConic()); rcCmdMgr.addCommand(new CmdSketcherCreateArcOfEllipse()); rcCmdMgr.addCommand(new CmdSketcherCreateArcOfHyperbola()); rcCmdMgr.addCommand(new CmdSketcherCreateArcOfParabola()); rcCmdMgr.addCommand(new CmdSketcherCreateBSpline()); rcCmdMgr.addCommand(new CmdSketcherCreatePeriodicBSpline()); rcCmdMgr.addCommand(new CmdSketcherCompCreateBSpline()); rcCmdMgr.addCommand(new CmdSketcherCreateLine()); rcCmdMgr.addCommand(new CmdSketcherCreatePolyline()); rcCmdMgr.addCommand(new CmdSketcherCreateRectangle()); rcCmdMgr.addCommand(new CmdSketcherCreateRectangleCenter()); rcCmdMgr.addCommand(new CmdSketcherCreateOblong()); rcCmdMgr.addCommand(new CmdSketcherCompCreateRegularPolygon()); rcCmdMgr.addCommand(new CmdSketcherCreateTriangle()); rcCmdMgr.addCommand(new CmdSketcherCreateSquare()); rcCmdMgr.addCommand(new CmdSketcherCreatePentagon()); rcCmdMgr.addCommand(new CmdSketcherCreateHexagon()); rcCmdMgr.addCommand(new CmdSketcherCreateHeptagon()); rcCmdMgr.addCommand(new CmdSketcherCreateOctagon()); rcCmdMgr.addCommand(new CmdSketcherCreateRegularPolygon()); rcCmdMgr.addCommand(new CmdSketcherCompCreateRectangles()); rcCmdMgr.addCommand(new CmdSketcherCreateSlot()); rcCmdMgr.addCommand(new CmdSketcherCompCreateFillets()); rcCmdMgr.addCommand(new CmdSketcherCreateFillet()); rcCmdMgr.addCommand(new CmdSketcherCreatePointFillet()); //rcCmdMgr.addCommand(new CmdSketcherCreateText()); //rcCmdMgr.addCommand(new CmdSketcherCreateDraftLine()); rcCmdMgr.addCommand(new CmdSketcherTrimming()); rcCmdMgr.addCommand(new CmdSketcherExtend()); rcCmdMgr.addCommand(new CmdSketcherSplit()); rcCmdMgr.addCommand(new CmdSketcherExternal()); rcCmdMgr.addCommand(new CmdSketcherCarbonCopy()); }