/*************************************************************************** * Copyright (c) 2022 Abdullah Tahiri * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #ifndef SKETCHERGUI_DrawSketchHandlerPolygon_H #define SKETCHERGUI_DrawSketchHandlerPolygon_H #include #include #include #include #include #include #include "GeometryCreationMode.h" #include "Utils.h" #include "DrawSketchDefaultWidgetController.h" #include "DrawSketchControllableHandler.h" #include "SketcherRegularPolygonDialog.h" namespace SketcherGui { extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp class DrawSketchHandlerPolygon; using DSHPolygonController = DrawSketchDefaultWidgetController, /*WidgetParametersT =*/WidgetParameters<1>, /*WidgetCheckboxesT =*/WidgetCheckboxes<0>, /*WidgetComboboxesT =*/WidgetComboboxes<0>>; using DSHPolygonControllerBase = DSHPolygonController::ControllerBase; using DrawSketchHandlerPolygonBase = DrawSketchControllableHandler; class DrawSketchHandlerPolygon: public DrawSketchHandlerPolygonBase { friend DSHPolygonController; friend DSHPolygonControllerBase; public: explicit DrawSketchHandlerPolygon(int corners = 6) // NOLINT : numberOfCorners(corners) , radius(0.0) {} ~DrawSketchHandlerPolygon() override = default; private: void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override { switch (state()) { case SelectMode::SeekFirst: { toolWidgetManager.drawPositionAtCursor(onSketchPos); centerPoint = onSketchPos; seekAndRenderAutoConstraint(sugConstraints[0], onSketchPos, Base::Vector2d(0.f, 0.f)); } break; case SelectMode::SeekSecond: { toolWidgetManager.drawDirectionAtCursor(onSketchPos, centerPoint); firstCorner = onSketchPos; CreateAndDrawShapeGeometry(); seekAndRenderAutoConstraint(sugConstraints[1], onSketchPos, Base::Vector2d(0.f, 0.f)); } break; default: break; } } void executeCommands() override { unsetCursor(); resetPositionText(); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add polygon")); 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(), numberOfCorners, centerPoint.x, centerPoint.y, firstCorner.x, firstCorner.y, constructionModeAsBooleanText()); Gui::Command::commitCommand(); tryAutoRecomputeIfNotSolve( static_cast(sketchgui->getObject())); } catch (const Base::Exception&) { Gui::NotifyError(sketchgui, QT_TRANSLATE_NOOP("Notifications", "Error"), QT_TRANSLATE_NOOP("Notifications", "Failed to add polygon")); Gui::Command::abortCommand(); THROWM(Base::RuntimeError, QT_TRANSLATE_NOOP( "Notifications", "Tool execution aborted") "\n") // This prevents constraints from being // applied on non existing geometry } } void generateAutoConstraints() override { // add auto constraints at the center of the polygon int circlegeoid = getHighestCurveIndex(); int lastsidegeoid = getHighestCurveIndex() - 1; if (sugConstraints[0].size() > 0) { generateAutoConstraintsOnElement(sugConstraints[0], circlegeoid, Sketcher::PointPos::mid); } // add auto constraints to the last side of the polygon if (sugConstraints[1].size() > 0) { generateAutoConstraintsOnElement(sugConstraints[1], lastsidegeoid, Sketcher::PointPos::end); } // Ensure temporary autoconstraints do not generate a redundancy and that the geometry // parameters are accurate This is particularly important for adding widget mandated // constraints. removeRedundantAutoConstraints(); } void createAutoConstraints() override { createGeneratedAutoConstraints(true); sugConstraints[0].clear(); sugConstraints[1].clear(); } std::string getToolName() const override { return "DSH_Polygon"; } QString getCrosshairCursorSVGName() const override { return QString::fromLatin1("Sketcher_Pointer_Regular_Polygon"); } std::unique_ptr createWidget() const override { return std::make_unique(); } bool isWidgetVisible() const override { return true; }; QPixmap getToolIcon() const override { return Gui::BitmapFactory().pixmap("Sketcher_CreateRegularPolygon"); } QString getToolWidgetText() const override { return QString(QObject::tr("Polygon parameters")); } bool canGoToNextMode() override { if (state() == SelectMode::SeekSecond && radius < Precision::Confusion()) { // Prevent validation of null shape. return false; } return true; } void angleSnappingControl() override { if (state() == SelectMode::SeekSecond) { setAngleSnapping(true, centerPoint); } else { setAngleSnapping(false); } } private: unsigned int numberOfCorners; Base::Vector2d centerPoint, firstCorner; double radius; void createShape(bool onlyeditoutline) override { Q_UNUSED(onlyeditoutline) ShapeGeometry.clear(); Base::Vector2d prevCorner = firstCorner; Base::Vector2d dV = firstCorner - centerPoint; radius = dV.Length(); if (radius < Precision::Confusion()) { return; } double angleOfSeparation = 2.0 * M_PI / static_cast(numberOfCorners); // NOLINT double cos_v = cos(angleOfSeparation); double sin_v = sin(angleOfSeparation); double rx = dV.x; double ry = dV.y; for (int i = 1; i <= static_cast(numberOfCorners); i++) { const double old_rx = rx; rx = cos_v * rx - sin_v * ry; ry = cos_v * ry + sin_v * old_rx; Base::Vector2d newCorner = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); addLineToShapeGeometry(toVector3d(prevCorner), toVector3d(newCorner), isConstructionMode()); prevCorner = newCorner; } } }; template<> auto DSHPolygonControllerBase::getState(int labelindex) const { switch (labelindex) { case OnViewParameter::First: case OnViewParameter::Second: return SelectMode::SeekFirst; break; case OnViewParameter::Third: case OnViewParameter::Fourth: return SelectMode::SeekSecond; break; default: THROWM(Base::ValueError, "OnViewParameter index without an associated machine state") } } template<> void DSHPolygonController::firstKeyShortcut() { auto value = toolWidget->getParameter(WParameter::First); toolWidget->setParameterWithoutPassingFocus(WParameter::First, value + 1); } template<> void DSHPolygonController::secondKeyShortcut() { auto value = toolWidget->getParameter(WParameter::First); if (value > 3.0) { // NOLINT toolWidget->setParameterWithoutPassingFocus(WParameter::First, value - 1); } } template<> void DSHPolygonController::configureToolWidget() { toolWidget->setParameterLabel( WParameter::First, QApplication::translate("ToolWidgetManager_p4", "Sides (+'U'/ -'J')")); toolWidget->setParameter(WParameter::First, handler->numberOfCorners); // unconditionally set toolWidget->configureParameterUnit(WParameter::First, Base::Unit()); toolWidget->configureParameterMin(WParameter::First, 3.0); // NOLINT // We set a reasonable max to avoid the spinbox from being very large toolWidget->configureParameterMax(WParameter::First, 9999.0); // NOLINT toolWidget->configureParameterDecimals(WParameter::First, 0); onViewParameters[OnViewParameter::First]->setLabelType(Gui::SoDatumLabel::DISTANCEX); onViewParameters[OnViewParameter::Second]->setLabelType(Gui::SoDatumLabel::DISTANCEY); onViewParameters[OnViewParameter::Third]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning); onViewParameters[OnViewParameter::Fourth]->setLabelType( Gui::SoDatumLabel::ANGLE, Gui::EditableDatumLabel::Function::Dimensioning); } template<> void DSHPolygonController::adaptDrawingToParameterChange(int parameterindex, double value) { switch (parameterindex) { case WParameter::First: handler->numberOfCorners = std::max(3, static_cast(value)); break; } } template<> void DSHPolygonControllerBase::doEnforceControlParameters(Base::Vector2d& onSketchPos) { switch (handler->state()) { case SelectMode::SeekFirst: { if (onViewParameters[OnViewParameter::First]->isSet) { onSketchPos.x = onViewParameters[OnViewParameter::First]->getValue(); } if (onViewParameters[OnViewParameter::Second]->isSet) { onSketchPos.y = onViewParameters[OnViewParameter::Second]->getValue(); } } break; case SelectMode::SeekSecond: { Base::Vector2d dir = onSketchPos - handler->centerPoint; if (dir.Length() < Precision::Confusion()) { dir.x = 1.0; // if direction null, default to (1,0) } double length = dir.Length(); if (onViewParameters[OnViewParameter::Third]->isSet) { length = onViewParameters[OnViewParameter::Third]->getValue(); if (length < Precision::Confusion()) { unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); return; } onSketchPos = handler->centerPoint + length * dir.Normalize(); } if (onViewParameters[OnViewParameter::Fourth]->isSet) { double angle = Base::toRadians(onViewParameters[OnViewParameter::Fourth]->getValue()); onSketchPos.x = handler->centerPoint.x + cos(angle) * length; onSketchPos.y = handler->centerPoint.y + sin(angle) * length; } } break; default: break; } } template<> void DSHPolygonController::adaptParameters(Base::Vector2d onSketchPos) { switch (handler->state()) { case SelectMode::SeekFirst: { if (!onViewParameters[OnViewParameter::First]->isSet) { setOnViewParameterValue(OnViewParameter::First, onSketchPos.x); } if (!onViewParameters[OnViewParameter::Second]->isSet) { setOnViewParameterValue(OnViewParameter::Second, onSketchPos.y); } bool sameSign = onSketchPos.x * onSketchPos.y > 0.; onViewParameters[OnViewParameter::First]->setLabelAutoDistanceReverse(!sameSign); onViewParameters[OnViewParameter::Second]->setLabelAutoDistanceReverse(sameSign); onViewParameters[OnViewParameter::First]->setPoints(Base::Vector3d(), toVector3d(onSketchPos)); onViewParameters[OnViewParameter::Second]->setPoints(Base::Vector3d(), toVector3d(onSketchPos)); } break; case SelectMode::SeekSecond: { Base::Vector3d start = toVector3d(handler->centerPoint); Base::Vector3d end = toVector3d(handler->firstCorner); Base::Vector3d vec = end - start; if (!onViewParameters[OnViewParameter::Third]->isSet) { setOnViewParameterValue(OnViewParameter::Third, vec.Length()); } double range = (handler->firstCorner - handler->centerPoint).Angle(); if (!onViewParameters[OnViewParameter::Fourth]->isSet) { setOnViewParameterValue(OnViewParameter::Fourth, Base::toDegrees(range), Base::Unit::Angle); } onViewParameters[OnViewParameter::Third]->setPoints(start, end); onViewParameters[OnViewParameter::Fourth]->setPoints(start, Base::Vector3d()); onViewParameters[OnViewParameter::Fourth]->setLabelRange(range); } break; default: break; } } template<> void DSHPolygonController::doChangeDrawSketchHandlerMode() { switch (handler->state()) { case SelectMode::SeekFirst: { if (onViewParameters[OnViewParameter::First]->isSet && onViewParameters[OnViewParameter::Second]->isSet) { handler->setState(SelectMode::SeekSecond); } } break; case SelectMode::SeekSecond: { if (onViewParameters[OnViewParameter::Third]->isSet && onViewParameters[OnViewParameter::Fourth]->isSet) { handler->setState(SelectMode::End); } } break; default: break; } } template<> void DSHPolygonController::addConstraints() { int lastCurve = handler->getHighestCurveIndex(); auto x0 = onViewParameters[OnViewParameter::First]->getValue(); auto y0 = onViewParameters[OnViewParameter::Second]->getValue(); auto radius = onViewParameters[OnViewParameter::Third]->getValue(); auto x0set = onViewParameters[OnViewParameter::First]->isSet; auto y0set = onViewParameters[OnViewParameter::Second]->isSet; auto radiusSet = onViewParameters[OnViewParameter::Third]->isSet; using namespace Sketcher; auto constraintx0 = [&]() { ConstraintToAttachment(GeoElementId(lastCurve, PointPos::mid), GeoElementId::VAxis, x0, handler->sketchgui->getObject()); }; auto constrainty0 = [&]() { ConstraintToAttachment(GeoElementId(lastCurve, PointPos::mid), GeoElementId::HAxis, y0, handler->sketchgui->getObject()); }; auto constraintradius = [&]() { Gui::cmdAppObjectArgs(handler->sketchgui->getObject(), "addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", lastCurve, radius); }; // NOTE: if AutoConstraints is empty, we can add constraints directly without any diagnose. No // diagnose was run. if (handler->AutoConstraints.empty()) { if (x0set) { constraintx0(); } if (y0set) { constrainty0(); } if (radiusSet) { constraintradius(); } } else { // There is a valid diagnose. auto startpointinfo = handler->getPointInfo(GeoElementId(lastCurve, PointPos::mid)); // if Autoconstraints is empty we do not have a diagnosed system and the parameter will // always be set if (x0set && startpointinfo.isXDoF()) { constraintx0(); handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after // each constraint addition startpointinfo = handler->getPointInfo( GeoElementId(lastCurve, PointPos::mid)); // get updated point position } // if Autoconstraints is empty we do not have a diagnosed system and the parameter will // always be set if (y0set && startpointinfo.isYDoF()) { constrainty0(); handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after // each constraint addition } auto edgeinfo = handler->getEdgeInfo(lastCurve); auto circle = static_cast(edgeinfo); // if Autoconstraints is empty we do not have a diagnosed system and the parameter will // always be set if (radiusSet && circle.isRadiusDoF()) { constraintradius(); } } } } // namespace SketcherGui #endif // SKETCHERGUI_DrawSketchHandlerPolygon_H