// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2022 Abdullah Tahiri * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #ifndef SKETCHERGUI_DrawSketchHandlerRectangle_H #define SKETCHERGUI_DrawSketchHandlerRectangle_H #include #include #include #include #include #include #include #include "DrawSketchDefaultWidgetController.h" #include "DrawSketchControllableHandler.h" #include "GeometryCreationMode.h" #include "Utils.h" namespace SketcherGui { extern GeometryCreationMode geometryCreationMode; // defined in CommandCreateGeo.cpp class DrawSketchHandlerRectangle; namespace ConstructionMethods { enum class RectangleConstructionMethod { Diagonal, CenterAndCorner, ThreePoints, CenterAnd3Points, End // Must be the last one }; } // namespace ConstructionMethods using DSHRectangleController = DrawSketchDefaultWidgetController< DrawSketchHandlerRectangle, StateMachines::FiveSeekEnd, /*PAutoConstraintSize =*/3, /*OnViewParametersT =*/OnViewParameters<6, 6, 8, 8>, // NOLINT /*WidgetParametersT =*/WidgetParameters<0, 0, 0, 0>, // NOLINT /*WidgetCheckboxesT =*/WidgetCheckboxes<2, 2, 2, 2>, // NOLINT /*WidgetComboboxesT =*/WidgetComboboxes<1, 1, 1, 1>, // NOLINT ConstructionMethods::RectangleConstructionMethod, /*bool PFirstComboboxIsConstructionMethod =*/true>; using DSHRectangleControllerBase = DSHRectangleController::ControllerBase; using DrawSketchHandlerRectangleBase = DrawSketchControllableHandler; class DrawSketchHandlerRectangle: public DrawSketchHandlerRectangleBase { Q_DECLARE_TR_FUNCTIONS(SketcherGui::DrawSketchHandlerRectangle) // Allow specialisations of controllers access to private members friend DSHRectangleController; friend DSHRectangleControllerBase; public: DrawSketchHandlerRectangle( ConstructionMethod constrMethod = ConstructionMethod::Diagonal, bool roundcorners = false, bool frame = false ) : DrawSketchHandlerRectangleBase(constrMethod) , roundCorners(roundcorners) , makeFrame(frame) , cornersReversed(false) , radius(0.0) , length(0.0) , width(0.0) , thickness(0.) , radiusFrame(0.0) , angle(0.0) , angle123(0.0) , angle412(0.0) , firstCurve(Sketcher::GeoEnum::GeoUndef) , constructionPointOneId(Sketcher::GeoEnum::GeoUndef) , constructionPointTwoId(Sketcher::GeoEnum::GeoUndef) , constructionPointThreeId(Sketcher::GeoEnum::GeoUndef) , centerPointId(Sketcher::GeoEnum::GeoUndef) , side(0) , lengthSign(0) , widthSign(0) {} ~DrawSketchHandlerRectangle() override = default; private: std::list getToolHints() const override { using State = std::pair; using enum Gui::InputHint::UserInput; const Gui::InputHint switchHint {.message = tr("%1 switch mode"), .sequences = {KeyM}}; const Gui::InputHint roundedCornersHint { .message = tr("%1 toggle rounded corners"), .sequences = {KeyU} }; const Gui::InputHint frameHint {.message = tr("%1 toggle frame"), .sequences = {KeyJ}}; return Gui::lookupHints( {constructionMethod(), state()}, { // Diagonal method {.state = {ConstructionMethod::Diagonal, SelectMode::SeekFirst}, .hints = { {tr("%1 pick first corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::Diagonal, SelectMode::SeekSecond}, .hints = { {tr("%1 pick opposite corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::Diagonal, SelectMode::SeekThird}, .hints = { {tr("%1 set corner radius or frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::Diagonal, SelectMode::SeekFourth}, .hints = { {tr("%1 set frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, // CenterAndCorner method {.state = {ConstructionMethod::CenterAndCorner, SelectMode::SeekFirst}, .hints = { {tr("%1 pick center"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAndCorner, SelectMode::SeekSecond}, .hints = { {tr("%1 pick corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAndCorner, SelectMode::SeekThird}, .hints = { {tr("%1 set corner radius or frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAndCorner, SelectMode::SeekFourth}, .hints = { {tr("%1 set frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, // ThreePoints method {.state = {ConstructionMethod::ThreePoints, SelectMode::SeekFirst}, .hints = { {tr("%1 pick first corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::ThreePoints, SelectMode::SeekSecond}, .hints = { {tr("%1 pick second corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::ThreePoints, SelectMode::SeekThird}, .hints = { {tr("%1 pick third corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::ThreePoints, SelectMode::SeekFourth}, .hints = { {tr("%1 set corner radius or frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, // CenterAnd3Points method {.state = {ConstructionMethod::CenterAnd3Points, SelectMode::SeekFirst}, .hints = { {tr("%1 pick center"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAnd3Points, SelectMode::SeekSecond}, .hints = { {tr("%1 pick first corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAnd3Points, SelectMode::SeekThird}, .hints = { {tr("%1 pick second corner"), {MouseLeft}}, switchHint, roundedCornersHint, frameHint, }}, {.state = {ConstructionMethod::CenterAnd3Points, SelectMode::SeekFourth}, .hints = { {tr("%1 set corner radius or frame thickness"), {MouseMove}}, switchHint, roundedCornersHint, frameHint, }}, }); } private: void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override { using std::numbers::pi; switch (state()) { case SelectMode::SeekFirst: { toolWidgetManager.drawPositionAtCursor(onSketchPos); if (constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::ThreePoints) { corner1 = onSketchPos; } else { //(constructionMethod == ConstructionMethod::CenterAndCorner) center = onSketchPos; } seekAndRenderAutoConstraint(sugConstraints[0], onSketchPos, Base::Vector2d(0.f, 0.f)); } break; case SelectMode::SeekSecond: { if (constructionMethod() == ConstructionMethod::Diagonal) { toolWidgetManager.drawDirectionAtCursor(onSketchPos, corner1); // Note : we swap corner2 and 4 to make sure the corners are CCW. // making things easier down the line. corner3 = onSketchPos; if ((corner3.x - corner1.x) * (corner3.y - corner1.y) > 0) { corner2 = Base::Vector2d(onSketchPos.x, corner1.y); corner4 = Base::Vector2d(corner1.x, onSketchPos.y); cornersReversed = false; } else { corner4 = Base::Vector2d(onSketchPos.x, corner1.y); corner2 = Base::Vector2d(corner1.x, onSketchPos.y); cornersReversed = true; } angle123 = pi / 2; angle412 = pi / 2; } else if (constructionMethod() == ConstructionMethod::CenterAndCorner) { toolWidgetManager.drawDirectionAtCursor(onSketchPos, center); corner1 = center - (onSketchPos - center); corner3 = onSketchPos; if (Base::sgn(corner3.x - corner1.x) * Base::sgn(corner3.y - corner1.y) > 0) { corner2 = Base::Vector2d(onSketchPos.x, corner1.y); corner4 = Base::Vector2d(corner1.x, onSketchPos.y); cornersReversed = false; } else { corner4 = Base::Vector2d(onSketchPos.x, corner1.y); corner2 = Base::Vector2d(corner1.x, onSketchPos.y); cornersReversed = true; } angle123 = pi / 2; angle412 = pi / 2; } else if (constructionMethod() == ConstructionMethod::ThreePoints) { toolWidgetManager.drawDirectionAtCursor(onSketchPos, corner1); corner2 = onSketchPos; Base::Vector2d perpendicular; perpendicular.x = -(corner2 - corner1).y; perpendicular.y = (corner2 - corner1).x; corner3 = corner2 + perpendicular; corner4 = corner1 + perpendicular; angle123 = pi / 2; angle412 = pi / 2; corner2Initial = corner2; side = getPointSideOfVector(corner3, corner2 - corner1, corner1); } else { toolWidgetManager.drawDirectionAtCursor(onSketchPos, center); corner1 = onSketchPos; corner3 = center - (onSketchPos - center); Base::Vector2d perpendicular; perpendicular.x = -(onSketchPos - center).y; perpendicular.y = (onSketchPos - center).x; corner2 = center + perpendicular; corner4 = center - perpendicular; angle123 = pi / 2; angle412 = pi / 2; side = getPointSideOfVector(corner2, corner3 - corner1, corner1); } if (roundCorners) { length = (corner2 - corner1).Length(); width = (corner4 - corner1).Length(); radius = std::min(length, width) / 6; // NOLINT } else { radius = 0.; } try { CreateAndDrawShapeGeometry(); toolWidgetManager.drawWidthHeightAtCursor(onSketchPos, length, width); } catch (const Base::ValueError&) { } // equal points while hovering raise an objection that can be safely ignored seekAndRenderAutoConstraint(sugConstraints[1], onSketchPos, Base::Vector2d(0.0, 0.0)); } break; case SelectMode::SeekThird: { if (constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::CenterAndCorner) { if (roundCorners) { calculateRadius(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, radius); } else { // Normal rectangle with frame. calculateThickness(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, thickness); } } else if (constructionMethod() == ConstructionMethod::ThreePoints) { corner2 = corner2Initial; corner3 = onSketchPos; if (side == getPointSideOfVector(corner3, corner2 - corner1, corner1)) { corner4 = corner1 + (corner3 - corner2); cornersReversed = false; } else { corner4 = corner2; corner2 = corner1 + (corner3 - corner4); cornersReversed = true; } Base::Vector2d a = corner1 - corner2; Base::Vector2d b = corner3 - corner2; if (fabs((sqrt(a.x * a.x + a.y * a.y) * sqrt(b.x * b.x + b.y * b.y))) > Precision::Confusion()) { angle123 = acos( (a.x * b.x + a.y * b.y) / (sqrt(a.x * a.x + a.y * a.y) * sqrt(b.x * b.x + b.y * b.y)) ); } angle412 = pi - angle123; if (roundCorners) { radius = std::min(length, width) / 6 // NOLINT * std::min( sqrt(1 - cos(angle412) * cos(angle412)), sqrt(1 - cos(angle123) * cos(angle123)) ); } else { radius = 0.; } toolWidgetManager.drawWidthHeightAtCursor(onSketchPos, length, width); } else { corner2 = onSketchPos; corner4 = center - (onSketchPos - center); cornersReversed = false; if (side != getPointSideOfVector(corner2, corner3 - corner1, corner1)) { corner4 = onSketchPos; corner2 = center - (onSketchPos - center); cornersReversed = true; } Base::Vector2d a = corner4 - corner1; Base::Vector2d b = corner2 - corner1; if (fabs((sqrt(a.x * a.x + a.y * a.y) * sqrt(b.x * b.x + b.y * b.y))) > Precision::Confusion()) { angle412 = acos( (a.x * b.x + a.y * b.y) / (sqrt(a.x * a.x + a.y * a.y) * sqrt(b.x * b.x + b.y * b.y)) ); } angle123 = pi - angle412; if (roundCorners) { radius = std::min(length, width) / 6 // NOLINT * std::min( sqrt(1 - cos(angle412) * cos(angle412)), sqrt(1 - cos(angle123) * cos(angle123)) ); } else { radius = 0.; } toolWidgetManager.drawWidthHeightAtCursor(onSketchPos, length, width); } try { CreateAndDrawShapeGeometry(); } catch (const Base::ValueError&) { } // equal points while hovering raise an objection that can be safely ignored if ((constructionMethod() == ConstructionMethod::ThreePoints || constructionMethod() == ConstructionMethod::CenterAnd3Points)) { seekAndRenderAutoConstraint(sugConstraints[2], onSketchPos, Base::Vector2d(0.0, 0.0)); } } break; case SelectMode::SeekFourth: { if (constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::CenterAndCorner) { calculateThickness(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, thickness); } else { if (roundCorners) { calculateRadius(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, radius); } else { calculateThickness(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, thickness); } } CreateAndDrawShapeGeometry(); } break; case SelectMode::SeekFifth: { calculateThickness(onSketchPos); toolWidgetManager.drawDoubleAtCursor(onSketchPos, thickness); CreateAndDrawShapeGeometry(); } break; default: break; } } void executeCommands() override { try { firstCurve = getHighestCurveIndex() + 1; createShape(false); Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch box")); commandAddShapeGeometryAndConstraints(); Gui::Command::commitCommand(); } catch (const Base::Exception&) { Gui::NotifyError( sketchgui, QT_TRANSLATE_NOOP("Notifications", "Error"), QT_TRANSLATE_NOOP("Notifications", "Failed to add box") ); Gui::Command::abortCommand(); THROWM( Base::RuntimeError, QT_TRANSLATE_NOOP( "Notifications", "Tool execution aborted" ) "\n" ) // This prevents constraints from being // applied on non existing geometry } thickness = 0.; } void generateAutoConstraints() override { if (constructionMethod() == ConstructionMethod::Diagonal) { // add auto constraints at the start of the first side if (radius > Precision::Confusion()) { if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], constructionPointOneId, Sketcher::PointPos::start ); } if (!sugConstraints[1].empty()) { generateAutoConstraintsOnElement( sugConstraints[1], constructionPointTwoId, Sketcher::PointPos::start ); } } else { if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], firstCurve, Sketcher::PointPos::start ); } if (!sugConstraints[1].empty()) { generateAutoConstraintsOnElement( sugConstraints[1], firstCurve + 1, Sketcher::PointPos::end ); } } } else if (constructionMethod() == ConstructionMethod::CenterAndCorner) { // add auto constraints at center if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], centerPointId, Sketcher::PointPos::start ); } // add auto constraints for the line segment end if (!sugConstraints[1].empty()) { if (radius > Precision::Confusion()) { generateAutoConstraintsOnElement( sugConstraints[1], constructionPointOneId, Sketcher::PointPos::start ); } else { generateAutoConstraintsOnElement( sugConstraints[1], firstCurve + 1, Sketcher::PointPos::end ); } } } else if (constructionMethod() == ConstructionMethod::ThreePoints) { if (radius > Precision::Confusion()) { if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], constructionPointOneId, Sketcher::PointPos::start ); } if (!sugConstraints[1].empty()) { generateAutoConstraintsOnElement( sugConstraints[1], constructionPointTwoId, Sketcher::PointPos::start ); } if (!sugConstraints[2].empty()) { generateAutoConstraintsOnElement( sugConstraints[2], constructionPointThreeId, Sketcher::PointPos::start ); } } else { if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], firstCurve, Sketcher::PointPos::start ); } if (!sugConstraints[1].empty()) { if (!cornersReversed) { generateAutoConstraintsOnElement( sugConstraints[1], firstCurve + 1, Sketcher::PointPos::start ); } else { generateAutoConstraintsOnElement( sugConstraints[1], firstCurve + 3, Sketcher::PointPos::start ); } } if (!sugConstraints[2].empty()) { generateAutoConstraintsOnElement( sugConstraints[2], firstCurve + 2, Sketcher::PointPos::start ); } } } else if (constructionMethod() == ConstructionMethod::CenterAnd3Points) { // add auto constraints at center if (!sugConstraints[0].empty()) { generateAutoConstraintsOnElement( sugConstraints[0], centerPointId, Sketcher::PointPos::start ); } // add auto constraints for the line segment end if (radius > Precision::Confusion()) { if (!sugConstraints[1].empty()) { generateAutoConstraintsOnElement( sugConstraints[1], constructionPointOneId, Sketcher::PointPos::start ); } if (!sugConstraints[2].empty()) { generateAutoConstraintsOnElement( sugConstraints[2], constructionPointTwoId, Sketcher::PointPos::start ); } } else { if (!sugConstraints[1].empty()) { generateAutoConstraintsOnElement( sugConstraints[1], firstCurve, Sketcher::PointPos::start ); } if (!sugConstraints[2].empty()) { if (!cornersReversed) { generateAutoConstraintsOnElement( sugConstraints[2], firstCurve + 1, Sketcher::PointPos::start ); } else { generateAutoConstraintsOnElement( sugConstraints[2], firstCurve + 3, Sketcher::PointPos::start ); } } } } // 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_Rectangle"; } QString getCrosshairCursorSVGName() const override { if (!roundCorners && !makeFrame) { if (constructionMethod() == ConstructionMethod::CenterAndCorner) { return QStringLiteral("Sketcher_Pointer_Create_Box_Center"); } else if (constructionMethod() == ConstructionMethod::ThreePoints) { return QStringLiteral("Sketcher_Pointer_Create_Box_3Points"); } else if (constructionMethod() == ConstructionMethod::CenterAnd3Points) { return QStringLiteral("Sketcher_Pointer_Create_Box_3Points_Center"); } else { return QStringLiteral("Sketcher_Pointer_Create_Box"); } } else if (roundCorners && !makeFrame) { if (constructionMethod() == ConstructionMethod::CenterAndCorner) { return QStringLiteral("Sketcher_Pointer_Oblong_Center"); } else { return QStringLiteral("Sketcher_Pointer_Oblong"); } } else if (!roundCorners && makeFrame) { if (constructionMethod() == ConstructionMethod::CenterAndCorner) { return QStringLiteral("Sketcher_Pointer_Create_Frame_Center"); } else { return QStringLiteral("Sketcher_Pointer_Create_Frame"); } } else { // both roundCorners and makeFrame if (constructionMethod() == ConstructionMethod::CenterAndCorner) { return QStringLiteral("Sketcher_Pointer_Oblong_Frame_Center"); } else { return QStringLiteral("Sketcher_Pointer_Oblong_Frame"); } } } 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_CreateRectangle"); } QString getToolWidgetText() const override { return QString(tr("Rectangle Parameters")); } void angleSnappingControl() override { if ((constructionMethod() == ConstructionMethod::ThreePoints) && state() == SelectMode::SeekSecond) { setAngleSnapping(true, corner1); } else if ((constructionMethod() == ConstructionMethod::CenterAnd3Points) && state() == SelectMode::SeekSecond) { setAngleSnapping(true, center); } else if ((constructionMethod() == ConstructionMethod::ThreePoints) && state() == SelectMode::SeekThird) { setAngleSnapping(true, cornersReversed ? corner4 : corner2); } else if ((constructionMethod() == ConstructionMethod::CenterAnd3Points) && state() == SelectMode::SeekThird) { setAngleSnapping(true, corner1); } else { setAngleSnapping(false); } } bool canGoToNextMode() override { if (state() == SelectMode::SeekSecond && (length < Precision::Confusion() || width < Precision::Confusion())) { return false; } return true; } // reimplement because if not radius then it's 2 steps void onButtonPressed(Base::Vector2d onSketchPos) override { this->updateDataAndDrawToPosition(onSketchPos); if (canGoToNextMode()) { if (constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::CenterAndCorner) { if (state() == SelectMode::SeekSecond && !roundCorners && !makeFrame) { setState(SelectMode::End); } else if ((state() == SelectMode::SeekThird && roundCorners && !makeFrame) || (state() == SelectMode::SeekThird && !roundCorners && makeFrame)) { setState(SelectMode::End); } else if (state() == SelectMode::SeekFourth) { setState(SelectMode::End); } else { this->moveToNextMode(); } } else { if (state() == SelectMode::SeekThird && !roundCorners && !makeFrame) { setState(SelectMode::End); } else if ((state() == SelectMode::SeekFourth && roundCorners && !makeFrame) || (state() == SelectMode::SeekFourth && !roundCorners && makeFrame)) { setState(SelectMode::End); } else { this->moveToNextMode(); } } } } void onReset() override { thickness = 0.; lengthSign = 0; widthSign = 0; toolWidgetManager.resetControls(); } private: Base::Vector2d center, corner1, corner2, corner3, corner4, frameCorner1, frameCorner2, frameCorner3, frameCorner4, corner2Initial; Base::Vector3d center1, center2, center3, center4; bool roundCorners, makeFrame, cornersReversed; double radius, length, width, thickness, radiusFrame, angle, angle123, angle412; int firstCurve, constructionPointOneId, constructionPointTwoId, constructionPointThreeId, centerPointId, side; // Sign tracking for OVP lock fix (issue #23459) // These store the direction sign when OVP is first set to prevent sign flipping int lengthSign, widthSign; void createShape(bool onlyeditoutline) override { ShapeGeometry.clear(); Base::Vector2d vecL = corner2 - corner1; Base::Vector2d vecW = corner4 - corner1; length = vecL.Length(); width = vecW.Length(); angle = vecL.Angle(); if (length < Precision::Confusion() || width < Precision::Confusion() || fmod(fabs(angle123), std::numbers::pi) < Precision::Confusion()) { return; } vecL = vecL / length; vecW = vecW / width; double L1 = radius; double L2 = radius; if (cos(angle123 / 2) != 1 && cos(angle412 / 2) != 1) { L1 = radius / sqrt(1 - cos(angle123 / 2) * cos(angle123 / 2)); L2 = radius / sqrt(1 - cos(angle412 / 2) * cos(angle412 / 2)); } createFirstRectangleGeometries(vecL, vecW, L1, L2); bool thicknessNotZero = fabs(thickness) > Precision::Confusion(); bool negThicknessEqualRadius = fabs(radius + thickness) < Precision::Confusion(); if (makeFrame && state() != SelectMode::SeekSecond && thicknessNotZero) { createSecondRectangleGeometries(vecL, vecW, L1, L2); } if (!onlyeditoutline) { ShapeConstraints.clear(); if (radius > Precision::Confusion()) { finishOblongCreation(thicknessNotZero, negThicknessEqualRadius); } else { // cases of normal rectangles and normal frames finishRectangleCreation(thicknessNotZero); } } } void createFirstRectangleGeometries(Base::Vector2d vecL, Base::Vector2d vecW, double L1, double L2) { createFirstRectangleLines(vecL, vecW, L1, L2); if (roundCorners && radius > Precision::Confusion()) { createFirstRectangleFillets(vecL, vecW, L1, L2); } } void createFirstRectangleLines(Base::Vector2d vecL, Base::Vector2d vecW, double L1, double L2) { addLineToShapeGeometry( toVector3d(corner1 + vecL * L2 * cos(angle412 / 2)), toVector3d(corner2 - vecL * L1 * cos(angle123 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(corner2 + vecW * L1 * cos(angle123 / 2)), toVector3d(corner3 - vecW * L2 * cos(angle412 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(corner3 - vecL * L2 * cos(angle412 / 2)), toVector3d(corner4 + vecL * L1 * cos(angle123 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(corner4 - vecW * L1 * cos(angle123 / 2)), toVector3d(corner1 + vecW * L2 * cos(angle412 / 2)), isConstructionMode() ); } void createFirstRectangleFillets(Base::Vector2d vecL, Base::Vector2d vecW, double L1, double L2) { using std::numbers::pi; // center points required later for special case of round corner frame with // radiusFrame = 0. double end = angle - pi / 2; Base::Vector2d b1 = (vecL + vecW) / (vecL + vecW).Length(); Base::Vector2d b2 = (vecL - vecW) / (vecL - vecW).Length(); center1 = toVector3d(corner1 + b1 * L2); center2 = toVector3d(corner2 - b2 * L1); center3 = toVector3d(corner3 - b1 * L2); center4 = toVector3d(corner4 + b2 * L1); addArcToShapeGeometry(center1, end - pi + angle412, end, radius, isConstructionMode()); addArcToShapeGeometry(center2, end, end - pi - angle123, radius, isConstructionMode()); addArcToShapeGeometry(center3, end + angle412, end - pi, radius, isConstructionMode()); addArcToShapeGeometry(center4, end - pi, end - angle123, radius, isConstructionMode()); } void createSecondRectangleGeometries(Base::Vector2d vecL, Base::Vector2d vecW, double L1, double L2) { using std::numbers::pi; double end = angle - pi / 2; if (radius < Precision::Confusion()) { radiusFrame = 0.; } else { radiusFrame = radius + thickness; if (radiusFrame < 0.) { radiusFrame = 0.; } } Base::Vector2d vecLF = frameCorner2 - frameCorner1; Base::Vector2d vecWF = frameCorner4 - frameCorner1; double lengthF = vecLF.Length(); double widthF = vecWF.Length(); double L1F = 0.; double L2F = 0.; if (radius > Precision::Confusion()) { L1F = L1 * radiusFrame / radius; L2F = L2 * radiusFrame / radius; } addLineToShapeGeometry( toVector3d(frameCorner1 + vecLF / lengthF * L2F * cos(angle412 / 2)), toVector3d(frameCorner2 - vecLF / lengthF * L1F * cos(angle123 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(frameCorner2 + vecWF / widthF * L1F * cos(angle123 / 2)), toVector3d(frameCorner3 - vecWF / widthF * L2F * cos(angle412 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(frameCorner3 - vecLF / lengthF * L2F * cos(angle412 / 2)), toVector3d(frameCorner4 + vecLF / lengthF * L1F * cos(angle123 / 2)), isConstructionMode() ); addLineToShapeGeometry( toVector3d(frameCorner4 - vecWF / widthF * L1F * cos(angle123 / 2)), toVector3d(frameCorner1 + vecWF / widthF * L2F * cos(angle412 / 2)), isConstructionMode() ); if (roundCorners && radiusFrame > Precision::Confusion()) { Base::Vector2d b1 = (vecL + vecW) / (vecL + vecW).Length(); Base::Vector2d b2 = (vecL - vecW) / (vecL - vecW).Length(); addArcToShapeGeometry( toVector3d(frameCorner1 + b1 * L2F), end - pi + angle412, end, radiusFrame, isConstructionMode() ); addArcToShapeGeometry( toVector3d(frameCorner2 - b2 * L1F), end, end - pi - angle123, radiusFrame, isConstructionMode() ); addArcToShapeGeometry( toVector3d(frameCorner3 - b1 * L2F), end + angle412, end - pi, radiusFrame, isConstructionMode() ); addArcToShapeGeometry( toVector3d(frameCorner4 + b2 * L1F), end - pi, end - angle123, radiusFrame, isConstructionMode() ); } } void finishOblongCreation(bool thicknessNotZero, bool negThicknessEqualRadius) { addTangentCoincidences(firstCurve); addAlignmentConstraints(); addArcEqualities(); if (thicknessNotZero) { // There are 3 cases possible: // 1 - Thickness is negative and -thickness == radius. // In this case the inner rectangle is a normal rectangle and its corner // match the centers of the outer arcs. // 2 - Thickness is negative and radius < -thickness. // In this case it's a normal rectangle but we need construction // lines to constraint it. // 3 - Thickness is either positive or negative and radius > -thickness. // In this case the second rectangle is also round-cornered if (radiusFrame < Precision::Confusion()) { addRectangleCoincidences(firstCurve + 8); // NOLINT // Case 1 if (negThicknessEqualRadius) { finishOblongFrameCase1(); } else { finishOblongFrameCase2(); } } else { // case 3: inner rectangle is rounded rectangle finishOblongFrameCase3(); } } if (constructionMethod() == ConstructionMethod::ThreePoints) { finishOblongThreePoints(thicknessNotZero, negThicknessEqualRadius); } else if (constructionMethod() == ConstructionMethod::CenterAnd3Points) { finishOblongCenterAnd3Points(thicknessNotZero, negThicknessEqualRadius); } else if (constructionMethod() == ConstructionMethod::CenterAndCorner) { finishOblongCenterAndCorner(thicknessNotZero, negThicknessEqualRadius); } else { finishOblongDiagonal(thicknessNotZero, negThicknessEqualRadius); } } void finishOblongFrameCase1() { // In this case the corners of the second rectangle are coincident // with the centers of the arcs of the first rectangle. addToShapeConstraints( Sketcher::Coincident, firstCurve + 8, // NOLINT Sketcher::PointPos::start, firstCurve + 4, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 9, // NOLINT Sketcher::PointPos::start, firstCurve + 5, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 10, // NOLINT Sketcher::PointPos::start, firstCurve + 6, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 11, // NOLINT Sketcher::PointPos::start, firstCurve + 7, // NOLINT Sketcher::PointPos::mid ); } void finishOblongFrameCase2() { // case 2: add construction lines +12, +13, +14, +15 addFrameAlignmentConstraints(firstCurve + 8); addLineToShapeGeometry(center1, Base::Vector3d(frameCorner1.x, frameCorner1.y, 0.), true); addLineToShapeGeometry(center2, Base::Vector3d(frameCorner2.x, frameCorner2.y, 0.), true); addLineToShapeGeometry(center3, Base::Vector3d(frameCorner3.x, frameCorner3.y, 0.), true); addLineToShapeGeometry(center4, Base::Vector3d(frameCorner4.x, frameCorner4.y, 0.), true); addToShapeConstraints( Sketcher::Coincident, firstCurve + 12, // NOLINT Sketcher::PointPos::start, firstCurve + 4, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 12, // NOLINT Sketcher::PointPos::end, firstCurve + 8, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 13, // NOLINT Sketcher::PointPos::start, firstCurve + 5, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 13, // NOLINT Sketcher::PointPos::end, firstCurve + 9, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 14, // NOLINT Sketcher::PointPos::start, firstCurve + 6, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 14, // NOLINT Sketcher::PointPos::end, firstCurve + 10, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 15, // NOLINT Sketcher::PointPos::start, firstCurve + 7, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 15, // NOLINT Sketcher::PointPos::end, firstCurve + 11, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 12, // NOLINT Sketcher::PointPos::none, firstCurve + 13 ); // NOLINT addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 13, // NOLINT Sketcher::PointPos::none, firstCurve + 14 ); // NOLINT addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 14, // NOLINT Sketcher::PointPos::none, firstCurve + 15 ); // NOLINT } void finishOblongFrameCase3() { addTangentCoincidences(firstCurve + 8); addToShapeConstraints( Sketcher::Coincident, firstCurve + 4, // NOLINT Sketcher::PointPos::mid, firstCurve + 12, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 5, // NOLINT Sketcher::PointPos::mid, firstCurve + 13, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 6, // NOLINT Sketcher::PointPos::mid, firstCurve + 14, // NOLINT Sketcher::PointPos::mid ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 7, // NOLINT Sketcher::PointPos::mid, firstCurve + 15, // NOLINT Sketcher::PointPos::mid ); addFrameAlignmentConstraints(firstCurve + 8, false); } void finishOblongThreePoints(bool thicknessNotZero, bool negThicknessEqualRadius) { if (thicknessNotZero) { if (negThicknessEqualRadius) { constructionPointOneId = firstCurve + 12; // NOLINT constructionPointTwoId = firstCurve + 13; // NOLINT constructionPointThreeId = firstCurve + 14; // NOLINT } else { constructionPointOneId = firstCurve + 16; // NOLINT constructionPointTwoId = firstCurve + 17; // NOLINT constructionPointThreeId = firstCurve + 18; // NOLINT } } else { constructionPointOneId = firstCurve + 8; // NOLINT constructionPointTwoId = firstCurve + 9; // NOLINT constructionPointThreeId = firstCurve + 10; // NOLINT } addPointToShapeGeometry(Base::Vector3d(corner1.x, corner1.y, 0.), true); if (!cornersReversed) { addPointToShapeGeometry(Base::Vector3d(corner2.x, corner2.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 1 ); } else { addPointToShapeGeometry(Base::Vector3d(corner4.x, corner4.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 2 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 3 ); } addPointToShapeGeometry(Base::Vector3d(corner3.x, corner3.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve + 3 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointThreeId, Sketcher::PointPos::start, firstCurve + 1 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointThreeId, Sketcher::PointPos::start, firstCurve + 2 ); } void finishOblongCenterAnd3Points(bool thicknessNotZero, bool negThicknessEqualRadius) { if (thicknessNotZero) { if (negThicknessEqualRadius) { constructionPointOneId = firstCurve + 12; // NOLINT constructionPointTwoId = firstCurve + 13; // NOLINT centerPointId = firstCurve + 14; // NOLINT } else { constructionPointOneId = firstCurve + 16; // NOLINT constructionPointTwoId = firstCurve + 17; // NOLINT centerPointId = firstCurve + 18; // NOLINT } } else { constructionPointOneId = firstCurve + 8; // NOLINT constructionPointTwoId = firstCurve + 9; // NOLINT centerPointId = firstCurve + 10; // NOLINT } addPointToShapeGeometry(Base::Vector3d(corner1.x, corner1.y, 0.), true); if (!cornersReversed) { addPointToShapeGeometry(Base::Vector3d(corner2.x, corner2.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 1 ); } else { addPointToShapeGeometry(Base::Vector3d(corner4.x, corner4.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 2 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 3 ); } addPointToShapeGeometry(Base::Vector3d(center.x, center.y, 0.), true); addToShapeConstraints( Sketcher::Symmetric, firstCurve + 2, Sketcher::PointPos::start, firstCurve, Sketcher::PointPos::start, centerPointId, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve + 3 ); // NOLINT } void finishOblongCenterAndCorner(bool thicknessNotZero, bool negThicknessEqualRadius) { if (thicknessNotZero) { if (negThicknessEqualRadius) { constructionPointOneId = firstCurve + 12; // NOLINT centerPointId = firstCurve + 13; // NOLINT } else { constructionPointOneId = firstCurve + 16; // NOLINT centerPointId = firstCurve + 17; // NOLINT } } else { constructionPointOneId = firstCurve + 8; // NOLINT centerPointId = firstCurve + 9; // NOLINT } addPointToShapeGeometry(Base::Vector3d(corner3.x, corner3.y, 0.), true); addPointToShapeGeometry(Base::Vector3d(center.x, center.y, 0.), true); addToShapeConstraints( Sketcher::Symmetric, firstCurve + 2, Sketcher::PointPos::start, firstCurve, Sketcher::PointPos::start, centerPointId, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve + 1 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve + 2 ); } void finishOblongDiagonal(bool thicknessNotZero, bool negThicknessEqualRadius) { if (thicknessNotZero) { if (negThicknessEqualRadius) { constructionPointOneId = firstCurve + 12; // NOLINT constructionPointTwoId = firstCurve + 13; // NOLINT } else { constructionPointOneId = firstCurve + 16; // NOLINT constructionPointTwoId = firstCurve + 17; // NOLINT } } else { constructionPointOneId = firstCurve + 8; // NOLINT constructionPointTwoId = firstCurve + 9; // NOLINT } addPointToShapeGeometry(Base::Vector3d(corner1.x, corner1.y, 0.), true); addPointToShapeGeometry(Base::Vector3d(corner3.x, corner3.y, 0.), true); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointOneId, Sketcher::PointPos::start, firstCurve + 3 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 1 ); addToShapeConstraints( Sketcher::PointOnObject, constructionPointTwoId, Sketcher::PointPos::start, firstCurve + 2 ); } void addTangentCoincidences(int geoId) { addToShapeConstraints( Sketcher::Tangent, geoId, Sketcher::PointPos::start, geoId + 4, // NOLINT Sketcher::PointPos::end ); addToShapeConstraints( Sketcher::Tangent, geoId, Sketcher::PointPos::end, geoId + 5, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Tangent, geoId + 1, // NOLINT Sketcher::PointPos::start, geoId + 5, // NOLINT Sketcher::PointPos::end ); addToShapeConstraints( Sketcher::Tangent, geoId + 1, // NOLINT Sketcher::PointPos::end, geoId + 6, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Tangent, geoId + 2, // NOLINT Sketcher::PointPos::start, geoId + 6, // NOLINT Sketcher::PointPos::end ); addToShapeConstraints( Sketcher::Tangent, geoId + 2, // NOLINT Sketcher::PointPos::end, geoId + 7, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Tangent, geoId + 3, // NOLINT Sketcher::PointPos::start, geoId + 7, // NOLINT Sketcher::PointPos::end ); addToShapeConstraints( Sketcher::Tangent, geoId + 3, // NOLINT Sketcher::PointPos::end, geoId + 4, // NOLINT Sketcher::PointPos::start ); } void addArcEqualities() { addToShapeConstraints( Sketcher::Equal, firstCurve + 4, // NOLINT Sketcher::PointPos::none, firstCurve + 5 ); // NOLINT addToShapeConstraints( Sketcher::Equal, firstCurve + 5, // NOLINT Sketcher::PointPos::none, firstCurve + 6 ); // NOLINT addToShapeConstraints( Sketcher::Equal, firstCurve + 6, // NOLINT Sketcher::PointPos::none, firstCurve + 7 ); // NOLINT } void finishRectangleCreation(bool thicknessNotZero) { addRectangleCoincidences(firstCurve); addAlignmentConstraints(); if (thicknessNotZero) { finishRectangleFrameCreation(); } if (constructionMethod() == ConstructionMethod::CenterAndCorner || constructionMethod() == ConstructionMethod::CenterAnd3Points) { finishCenteredRectangleCreation(thicknessNotZero); } } void addRectangleCoincidences(int geoId) { addToShapeConstraints( Sketcher::Coincident, geoId, Sketcher::PointPos::end, geoId + 1, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, geoId + 1, Sketcher::PointPos::end, geoId + 2, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, geoId + 2, Sketcher::PointPos::end, geoId + 3, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, geoId + 3, Sketcher::PointPos::end, geoId, Sketcher::PointPos::start ); } void addAlignmentConstraints() { Sketcher::ConstraintType typeA = Sketcher::Horizontal; Sketcher::ConstraintType typeB = Sketcher::Vertical; if (Base::sgn(corner3.x - corner1.x) * Base::sgn(corner3.y - corner1.y) < 0) { typeA = Sketcher::Vertical; typeB = Sketcher::Horizontal; } if (fabs(angle) < Precision::Confusion() || constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::CenterAndCorner) { addToShapeConstraints(typeA, firstCurve); addToShapeConstraints(typeA, firstCurve + 2); addToShapeConstraints(typeB, firstCurve + 1); addToShapeConstraints(typeB, firstCurve + 3); } else { addToShapeConstraints( Sketcher::Parallel, firstCurve, Sketcher::PointPos::none, firstCurve + 2 ); addToShapeConstraints( Sketcher::Parallel, firstCurve + 1, Sketcher::PointPos::none, firstCurve + 3 ); if (fabs(angle123 - std::numbers::pi / 2) < Precision::Confusion()) { addToShapeConstraints( Sketcher::Perpendicular, firstCurve, Sketcher::PointPos::none, firstCurve + 1 ); } } } void finishRectangleFrameCreation() { addRectangleCoincidences(firstCurve + 4); addFrameAlignmentConstraints(firstCurve + 4); addRectangleFrameConstructionLines(); } void addFrameAlignmentConstraints(int geoId, bool addLast = true) { Sketcher::ConstraintType typeA = Sketcher::Horizontal; Sketcher::ConstraintType typeB = Sketcher::Vertical; if (Base::sgn(corner3.x - corner1.x) * Base::sgn(corner3.y - corner1.y) < 0) { typeA = Sketcher::Vertical; typeB = Sketcher::Horizontal; } if (fabs(angle) < Precision::Confusion() || constructionMethod() == ConstructionMethod::Diagonal || constructionMethod() == ConstructionMethod::CenterAndCorner) { addToShapeConstraints(typeA, geoId); // NOLINT addToShapeConstraints(typeA, geoId + 2); // NOLINT addToShapeConstraints(typeB, geoId + 1); // NOLINT if (addLast) { addToShapeConstraints(typeB, geoId + 3); // NOLINT } } else { addToShapeConstraints( Sketcher::Parallel, geoId, // NOLINT Sketcher::PointPos::none, geoId + 2 ); // NOLINT addToShapeConstraints( Sketcher::Parallel, geoId + 1, // NOLINT Sketcher::PointPos::none, geoId + 3 ); // NOLINT addToShapeConstraints( Sketcher::Parallel, firstCurve, Sketcher::PointPos::none, geoId ); // NOLINT if (addLast) { addToShapeConstraints( Sketcher::Parallel, firstCurve + 1, // NOLINT Sketcher::PointPos::none, geoId + 1 ); // NOLINT } } } void addRectangleFrameConstructionLines() { addLineToShapeGeometry( Base::Vector3d(corner1.x, corner1.y, 0.), Base::Vector3d(frameCorner1.x, frameCorner1.y, 0.), true ); addLineToShapeGeometry( Base::Vector3d(corner2.x, corner2.y, 0.), Base::Vector3d(frameCorner2.x, frameCorner2.y, 0.), true ); addLineToShapeGeometry( Base::Vector3d(corner3.x, corner3.y, 0.), Base::Vector3d(frameCorner3.x, frameCorner3.y, 0.), true ); addLineToShapeGeometry( Base::Vector3d(corner4.x, corner4.y, 0.), Base::Vector3d(frameCorner4.x, frameCorner4.y, 0.), true ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 8, // NOLINT Sketcher::PointPos::start, firstCurve, Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 8, // NOLINT Sketcher::PointPos::end, firstCurve + 4, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 9, // NOLINT Sketcher::PointPos::start, firstCurve + 1, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 9, // NOLINT Sketcher::PointPos::end, firstCurve + 5, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 10, // NOLINT Sketcher::PointPos::start, firstCurve + 2, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 10, // NOLINT Sketcher::PointPos::end, firstCurve + 6, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 11, // NOLINT Sketcher::PointPos::start, firstCurve + 3, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Coincident, firstCurve + 11, // NOLINT Sketcher::PointPos::end, firstCurve + 7, // NOLINT Sketcher::PointPos::start ); addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 8, // NOLINT Sketcher::PointPos::none, firstCurve + 9 ); // NOLINT addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 9, // NOLINT Sketcher::PointPos::none, firstCurve + 10 ); // NOLINT addToShapeConstraints( Sketcher::Perpendicular, firstCurve + 10, // NOLINT Sketcher::PointPos::none, firstCurve + 11 ); // NOLINT } void finishCenteredRectangleCreation(bool thicknessNotZero) { if (thicknessNotZero) { centerPointId = firstCurve + 12; // NOLINT } else { centerPointId = firstCurve + 4; // NOLINT } addPointToShapeGeometry(Base::Vector3d(center.x, center.y, 0.), true); addToShapeConstraints( Sketcher::Symmetric, firstCurve + 2, // NOLINT Sketcher::PointPos::start, firstCurve, Sketcher::PointPos::start, centerPointId, Sketcher::PointPos::start ); } int getPointSideOfVector( Base::Vector2d pointToCheck, Base::Vector2d separatingVector, Base::Vector2d pointOnVector ) { Base::Vector2d secondPointOnVec = pointOnVector + separatingVector; double d = (pointToCheck.x - pointOnVector.x) * (secondPointOnVec.y - pointOnVector.y) - (pointToCheck.y - pointOnVector.y) * (secondPointOnVec.x - pointOnVector.x); if (abs(d) < Precision::Confusion()) { return 0; } else if (d < 0) { return -1; } else { return 1; } } void calculateRadius(Base::Vector2d onSketchPos) { Base::Vector2d u = (corner2 - corner1) / (corner2 - corner1).Length(); Base::Vector2d v = (corner4 - corner1) / (corner4 - corner1).Length(); Base::Vector2d e = onSketchPos - corner1; double du = (v.y * e.x - v.x * e.y) / (u.x * v.y - u.y * v.x); double dv = (-u.y * e.x + u.x * e.y) / (u.x * v.y - u.y * v.x); if (-Precision::Confusion() < du && du < 0) { du = 0.0; } if (-Precision::Confusion() < dv && dv < 0) { dv = 0.0; } if (du < 0.0 || du > length || dv < 0.0 || dv > width) { radius = 0.; } else { if (du < length - du && dv < width - dv) { radius = (du + dv + std::max( 2 * sqrt(du * dv) * sin(angle412 / 2), -2 * sqrt(du * dv) * sin(angle412 / 2) )) * tan(angle412 / 2); } else if (du > length - du && dv < width - dv) { du = length - du; radius = (du + dv + std::max( 2 * sqrt(du * dv) * sin(angle123 / 2), -2 * sqrt(du * dv) * sin(angle123 / 2) )) * tan(angle123 / 2); } else if (du < length - du && dv > width - dv) { dv = width - dv; radius = (du + dv + std::max( 2 * sqrt(du * dv) * sin(angle123 / 2), -2 * sqrt(du * dv) * sin(angle123 / 2) )) * tan(angle123 / 2); } else { du = length - du; dv = width - dv; radius = (du + dv + std::max( 2 * sqrt(du * dv) * sin(angle412 / 2), -2 * sqrt(du * dv) * sin(angle412 / 2) )) * tan(angle412 / 2); } radius = std::min( radius, std::min(length * 0.999, width * 0.999) // NOLINT / (cos(angle412 / 2) / sqrt(1 - cos(angle412 / 2) * cos(angle412 / 2)) + cos(angle123 / 2) / sqrt(1 - cos(angle123 / 2) * cos(angle123 / 2))) ); } } void calculateThickness(Base::Vector2d onSketchPos) { Base::Vector2d u = (corner2 - corner1) / (corner2 - corner1).Length(); Base::Vector2d v = (corner4 - corner1) / (corner4 - corner1).Length(); Base::Vector2d e = onSketchPos - corner1; double obliqueThickness = 0.; double du = (v.y * e.x - v.x * e.y) / (u.x * v.y - u.y * v.x); double dv = (-u.y * e.x + u.x * e.y) / (u.x * v.y - u.y * v.x); if (du > 0 && du < length && !(dv > 0 && dv < width)) { obliqueThickness = std::min(fabs(dv), fabs(width - dv)); } else if (dv > 0 && dv < width && !(du > 0 && du < length)) { obliqueThickness = std::min(fabs(du), fabs(length - du)); } else if (du > 0 && du < length && dv > 0 && dv < width) { obliqueThickness = -std::min( std::min(fabs(du), fabs(length - du)), std::min(fabs(dv), fabs(width - dv)) ); } else { obliqueThickness = std::max( std::min(fabs(du), fabs(length - du)), std::min(fabs(dv), fabs(width - dv)) ); } frameCorner1 = corner1 - u * obliqueThickness - v * obliqueThickness; frameCorner2 = corner2 + u * obliqueThickness - v * obliqueThickness; frameCorner3 = corner3 + u * obliqueThickness + v * obliqueThickness; frameCorner4 = corner4 - u * obliqueThickness + v * obliqueThickness; thickness = obliqueThickness * sin(angle412); } }; template<> auto DSHRectangleControllerBase::getState(int labelindex) const { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { switch (labelindex) { case OnViewParameter::First: case OnViewParameter::Second: return SelectMode::SeekFirst; break; case OnViewParameter::Third: case OnViewParameter::Fourth: return SelectMode::SeekSecond; break; case OnViewParameter::Fifth: if (handler->roundCorners) { return SelectMode::SeekThird; } else { return SelectMode::End; } break; case OnViewParameter::Sixth: if (handler->makeFrame) { if (!handler->roundCorners) { return SelectMode::SeekThird; } else { return SelectMode::SeekFourth; } } else { return SelectMode::End; } break; default: THROWM(Base::ValueError, "Parameter index without an associated machine state") } } else { switch (labelindex) { case OnViewParameter::First: case OnViewParameter::Second: return SelectMode::SeekFirst; break; case OnViewParameter::Third: case OnViewParameter::Fourth: return SelectMode::SeekSecond; break; case OnViewParameter::Fifth: case OnViewParameter::Sixth: return SelectMode::SeekThird; break; case OnViewParameter::Seventh: if (handler->roundCorners) { return SelectMode::SeekFourth; } else { return SelectMode::End; } break; case OnViewParameter::Eighth: if (handler->makeFrame) { if (!handler->roundCorners) { return SelectMode::SeekFourth; } else { return SelectMode::SeekFifth; } } else { return SelectMode::End; } break; default: THROWM(Base::ValueError, "Parameter index without an associated machine state") } } } template<> void DSHRectangleController::configureToolWidget() { if (!init) { // Code to be executed only upon initialisation QStringList names = { QApplication::translate("TaskSketcherTool_c1_rectangle", "Corner, width, height"), QApplication::translate("TaskSketcherTool_c1_rectangle", "Center, width, height"), QApplication::translate("TaskSketcherTool_c1_rectangle", "3 corners"), QApplication::translate("TaskSketcherTool_c1_rectangle", "Center, 2 corners") }; toolWidget->setComboboxElements(WCombobox::FirstCombo, names); toolWidget->setCheckboxLabel( WCheckbox::FirstBox, QApplication::translate("TaskSketcherTool_c1_rectangle", "Rounded corners (U)") ); toolWidget->setCheckboxToolTip( WCheckbox::FirstBox, QApplication::translate( "TaskSketcherTool_c1_rectangle", "Create a rectangle with rounded corners." ) ); syncCheckboxToHandler(WCheckbox::FirstBox, handler->roundCorners); toolWidget->setCheckboxLabel( WCheckbox::SecondBox, QApplication::translate("TaskSketcherTool_c2_rectangle", "Frame (J)") ); toolWidget->setCheckboxToolTip( WCheckbox::SecondBox, QApplication::translate( "TaskSketcherTool_c2_rectangle", "Create two rectangles with a constant offset." ) ); syncCheckboxToHandler(WCheckbox::SecondBox, handler->makeFrame); if (isConstructionMode()) { toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 0, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Constr") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 1, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center_Constr") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 2, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle3Points_Constr") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 3, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle3Points_Center_Constr") ); toolWidget->setCheckboxIcon( WCheckbox::FirstBox, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong_Constr") ); toolWidget->setCheckboxIcon( WCheckbox::SecondBox, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFrame_Constr") ); } else { toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 0, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 1, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle_Center") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 2, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle3Points") ); toolWidget->setComboboxItemIcon( WCombobox::FirstCombo, 3, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateRectangle3Points_Center") ); toolWidget->setCheckboxIcon( WCheckbox::FirstBox, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateOblong") ); toolWidget->setCheckboxIcon( WCheckbox::SecondBox, Gui::BitmapFactory().iconFromTheme("Sketcher_CreateFrame") ); } } onViewParameters[OnViewParameter::First]->setLabelType(Gui::SoDatumLabel::DISTANCEX); onViewParameters[OnViewParameter::Second]->setLabelType(Gui::SoDatumLabel::DISTANCEY); if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { onViewParameters[OnViewParameter::Third]->setLabelType( Gui::SoDatumLabel::DISTANCEX, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Fourth]->setLabelType( Gui::SoDatumLabel::DISTANCEY, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Fifth]->setLabelType( Gui::SoDatumLabel::RADIUS, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Sixth]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); } else if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { onViewParameters[OnViewParameter::Third]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Fourth]->setLabelType( Gui::SoDatumLabel::ANGLE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Fifth]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Sixth]->setLabelType( Gui::SoDatumLabel::ANGLE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Seventh]->setLabelType( Gui::SoDatumLabel::RADIUS, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Eighth]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); } else if (handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { onViewParameters[OnViewParameter::Third]->setLabelType(Gui::SoDatumLabel::DISTANCEX); onViewParameters[OnViewParameter::Fourth]->setLabelType(Gui::SoDatumLabel::DISTANCEY); onViewParameters[OnViewParameter::Fifth]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Sixth]->setLabelType( Gui::SoDatumLabel::ANGLE, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Seventh]->setLabelType( Gui::SoDatumLabel::RADIUS, Gui::EditableDatumLabel::Function::Dimensioning ); onViewParameters[OnViewParameter::Eighth]->setLabelType( Gui::SoDatumLabel::DISTANCE, Gui::EditableDatumLabel::Function::Dimensioning ); } } template<> void DSHRectangleController::adaptDrawingToCheckboxChange(int checkboxindex, bool value) { Q_UNUSED(checkboxindex); switch (checkboxindex) { case WCheckbox::FirstBox: handler->roundCorners = value; break; case WCheckbox::SecondBox: handler->makeFrame = value; break; } handler->updateCursor(); } template<> void DSHRectangleControllerBase::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: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (onViewParameters[OnViewParameter::Third]->isSet) { double length = onViewParameters[OnViewParameter::Third]->getValue(); if (fabs(length) < Precision::Confusion() && onViewParameters[OnViewParameter::Third]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); handler->lengthSign = 0; return; } if (handler->constructionMethod() == ConstructionMethod::Diagonal) { if (handler->lengthSign == 0) { handler->lengthSign = (onSketchPos.x - handler->corner1.x) >= 0 ? 1 : -1; } onSketchPos.x = handler->corner1.x + handler->lengthSign * length; } else { onSketchPos.x = handler->center.x + length / 2; } } if (onViewParameters[OnViewParameter::Fourth]->isSet) { double width = onViewParameters[OnViewParameter::Fourth]->getValue(); if (fabs(width) < Precision::Confusion() && onViewParameters[OnViewParameter::Fourth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Fourth].get()); handler->widthSign = 0; return; } if (handler->constructionMethod() == ConstructionMethod::Diagonal) { if (handler->widthSign == 0) { handler->widthSign = (onSketchPos.y - handler->corner1.y) >= 0 ? 1 : -1; } onSketchPos.y = handler->corner1.y + handler->widthSign * width; } else { onSketchPos.y = handler->center.y + width / 2; } } } else if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { Base::Vector2d dir = onSketchPos - handler->corner1; 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() && onViewParameters[OnViewParameter::Third]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); return; } onSketchPos = handler->corner1 + length * dir.Normalize(); } if (onViewParameters[OnViewParameter::Fourth]->isSet) { double angle = Base::toRadians( onViewParameters[OnViewParameter::Fourth]->getValue() ); onSketchPos.x = handler->corner1.x + cos(angle) * length; onSketchPos.y = handler->corner1.y + sin(angle) * length; } } else { if (onViewParameters[OnViewParameter::Third]->isSet) { onSketchPos.x = onViewParameters[OnViewParameter::Third]->getValue(); } if (onViewParameters[OnViewParameter::Fourth]->isSet) { onSketchPos.y = onViewParameters[OnViewParameter::Fourth]->getValue(); } if (onViewParameters[OnViewParameter::Third]->hasFinishedEditing && onViewParameters[OnViewParameter::Fourth]->hasFinishedEditing && (onSketchPos - handler->center).Length() < Precision::Confusion()) { unsetOnViewParameter(onViewParameters[OnViewParameter::Third].get()); unsetOnViewParameter(onViewParameters[OnViewParameter::Fourth].get()); } } } break; case SelectMode::SeekThird: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (handler->roundCorners) { if (onViewParameters[OnViewParameter::Fifth]->isSet) { Base::Vector2d vecL = (handler->corner2 - handler->corner1).Normalize(); onSketchPos = handler->corner1 + vecL * onViewParameters[OnViewParameter::Fifth]->getValue(); } } else { if (onViewParameters[OnViewParameter::Sixth]->isSet) { double thickness = onViewParameters[OnViewParameter::Sixth]->getValue(); if (thickness <= -std::min(handler->width, handler->length) / 2 && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Sixth].get()); return; } Base::Vector2d u = (handler->corner2 - handler->corner1).Normalize(); Base::Vector2d v = (handler->corner4 - handler->corner1).Normalize(); onSketchPos = handler->corner1 - u * thickness - v * thickness; } } } else if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { Base::Vector2d dir = onSketchPos - handler->corner2Initial; if (dir.Length() < Precision::Confusion()) { dir.x = 1.0; // if direction null, default to (1,0) } double width = dir.Length(); if (onViewParameters[OnViewParameter::Fifth]->isSet) { width = onViewParameters[OnViewParameter::Fifth]->getValue(); if (width < Precision::Confusion() && onViewParameters[OnViewParameter::Fifth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Fifth].get()); return; } onSketchPos = handler->corner2Initial + width * dir.Normalize(); } if (onViewParameters[OnViewParameter::Sixth]->isSet) { double angle = Base::toRadians( onViewParameters[OnViewParameter::Sixth]->getValue() ); if (fmod(angle, std::numbers::pi) < Precision::Confusion() && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Sixth].get()); return; } int sign1 = handler->getPointSideOfVector( onSketchPos, handler->corner2Initial - handler->corner1, handler->corner1 ); int sign = handler->side != sign1 ? 1 : -1; double angle123 = (handler->corner2Initial - handler->corner1).Angle() + std::numbers::pi + sign * angle; onSketchPos.x = handler->corner2Initial.x + cos(angle123) * width; onSketchPos.y = handler->corner2Initial.y + sin(angle123) * width; } } else { Base::Vector2d dir = onSketchPos - handler->corner1; if (dir.Length() < Precision::Confusion()) { dir.x = 1.0; // if direction null, default to (1,0) } double width = dir.Length(); if (onViewParameters[OnViewParameter::Fifth]->isSet) { width = onViewParameters[OnViewParameter::Fifth]->getValue(); if (width < Precision::Confusion() && onViewParameters[OnViewParameter::Fifth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Fifth].get()); return; } onSketchPos = handler->corner1 + width * dir.Normalize(); } if (onViewParameters[OnViewParameter::Sixth]->isSet) { double c = Base::toRadians(onViewParameters[OnViewParameter::Sixth]->getValue()); if (fmod(c, std::numbers::pi) < Precision::Confusion() && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Sixth].get()); return; } double a = asin( width * sin(std::numbers::pi - c) / (handler->corner3 - handler->corner1).Length() ); int sign1 = handler->getPointSideOfVector( onSketchPos, handler->corner3 - handler->corner1, handler->corner1 ); int sign = handler->side != sign1 ? 1 : -1; double angle = (handler->center - handler->corner1).Angle() + sign * (c - a); onSketchPos.x = handler->corner1.x + cos(angle) * width; onSketchPos.y = handler->corner1.y + sin(angle) * width; } } } break; case SelectMode::SeekFourth: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (onViewParameters[OnViewParameter::Sixth]->isSet) { double thickness = onViewParameters[OnViewParameter::Sixth]->getValue(); if (thickness <= -std::min(handler->width, handler->length) / 2 && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Sixth].get()); return; } Base::Vector2d u = (handler->corner2 - handler->corner1).Normalize(); Base::Vector2d v = (handler->corner4 - handler->corner1).Normalize(); onSketchPos = handler->corner1 - u * thickness - v * thickness; } } else { if (handler->roundCorners) { if (onViewParameters[OnViewParameter::Seventh]->isSet) { double angleToUse = handler->angle412 / 2; if (handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { angleToUse = handler->angle123 / 2; } Base::Vector2d vecL = (handler->corner2Initial - handler->corner1).Normalize(); double L2 = onViewParameters[OnViewParameter::Seventh]->getValue() / sqrt(1 - cos(angleToUse) * cos(angleToUse)); onSketchPos = handler->corner1 + vecL * L2 * cos(angleToUse); } } else { if (onViewParameters[OnViewParameter::Eighth]->isSet) { double thickness = onViewParameters[OnViewParameter::Eighth]->getValue(); if (thickness <= -std::min(handler->width, handler->length) / 2 && onViewParameters[OnViewParameter::Eighth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Eighth].get()); return; } Base::Vector2d u = (handler->corner2 - handler->corner1).Normalize(); Base::Vector2d v = (handler->corner4 - handler->corner1).Normalize(); Base::Vector2d dir = (u + v).Normalize(); double angle = u.GetAngle(v) / 2; double sinAngle = fabs(sin(angle)); if (sinAngle < Precision::Confusion()) { sinAngle = 1; // protection against division by 0 } double tr = thickness / sinAngle; onSketchPos = handler->corner1 - dir * tr; } } } } break; case SelectMode::SeekFifth: { if (onViewParameters[OnViewParameter::Eighth]->isSet) { double thickness = onViewParameters[OnViewParameter::Eighth]->getValue(); if (thickness <= -std::min(handler->width, handler->length) / 2 && onViewParameters[OnViewParameter::Eighth]->hasFinishedEditing) { unsetOnViewParameter(onViewParameters[OnViewParameter::Eighth].get()); return; } Base::Vector2d u = (handler->corner2 - handler->corner1).Normalize(); Base::Vector2d v = (handler->corner4 - handler->corner1).Normalize(); Base::Vector2d dir = (u + v).Normalize(); double angle = u.GetAngle(v) / 2; double sinAngle = fabs(sin(angle)); if (sinAngle < Precision::Confusion()) { sinAngle = 1; // protection against division by 0 } double tr = thickness / sinAngle; onSketchPos = handler->corner1 - dir * tr; } } break; default: break; } } template<> void DSHRectangleController::adaptParameters(Base::Vector2d onSketchPos) { // If checkboxes need synchronisation (they were changed by the DSH, e.g. by using 'M' to switch // construction method), synchronise them and return. if (syncCheckboxToHandler(WCheckbox::FirstBox, handler->roundCorners)) { return; } if (syncCheckboxToHandler(WCheckbox::SecondBox, handler->makeFrame)) { return; } 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: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (!onViewParameters[OnViewParameter::Third]->isSet) { double length = handler->cornersReversed ? handler->width : handler->length; setOnViewParameterValue(OnViewParameter::Third, length); } if (!onViewParameters[OnViewParameter::Fourth]->isSet) { double width = handler->cornersReversed ? handler->length : handler->width; setOnViewParameterValue(OnViewParameter::Fourth, width); } Base::Vector3d start = toVector3d(handler->corner1); Base::Vector3d vec = toVector3d(onSketchPos) - start; bool sameSign = vec.x * vec.y > 0.; onViewParameters[OnViewParameter::Third]->setLabelAutoDistanceReverse(sameSign); onViewParameters[OnViewParameter::Fourth]->setLabelAutoDistanceReverse(!sameSign); onViewParameters[OnViewParameter::Third]->setPoints( start, toVector3d(handler->cornersReversed ? handler->corner4 : handler->corner2) ); onViewParameters[OnViewParameter::Fourth]->setPoints( start, toVector3d(handler->cornersReversed ? handler->corner2 : handler->corner4) ); } else if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { if (!onViewParameters[OnViewParameter::Third]->isSet) { setOnViewParameterValue(OnViewParameter::Third, handler->length); } onViewParameters[OnViewParameter::Third]->setPoints( toVector3d(handler->corner4), toVector3d(handler->corner3) ); if (!onViewParameters[OnViewParameter::Fourth]->isSet) { setOnViewParameterValue( OnViewParameter::Fourth, Base::toDegrees(handler->angle), Base::Unit::Angle ); } onViewParameters[OnViewParameter::Fourth]->setPoints( toVector3d(handler->corner1), Base::Vector3d() ); onViewParameters[OnViewParameter::Fourth]->setLabelRange( (handler->corner2 - handler->corner1).Angle() ); } else { if (!onViewParameters[OnViewParameter::Third]->isSet) { setOnViewParameterValue(OnViewParameter::Third, onSketchPos.x); } if (!onViewParameters[OnViewParameter::Fourth]->isSet) { setOnViewParameterValue(OnViewParameter::Fourth, onSketchPos.y); } bool sameSign = onSketchPos.x * onSketchPos.y > 0.; onViewParameters[OnViewParameter::Third]->setLabelAutoDistanceReverse(!sameSign); onViewParameters[OnViewParameter::Fourth]->setLabelAutoDistanceReverse(sameSign); onViewParameters[OnViewParameter::Third]->setPoints( Base::Vector3d(), toVector3d(onSketchPos) ); onViewParameters[OnViewParameter::Fourth]->setPoints( Base::Vector3d(), toVector3d(onSketchPos) ); } } break; case SelectMode::SeekThird: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (handler->roundCorners) { if (!onViewParameters[OnViewParameter::Fifth]->isSet) { setOnViewParameterValue(OnViewParameter::Fifth, handler->radius); } Base::Vector3d center = handler->center3; Base::Vector3d end = toVector3d(handler->corner3); if (handler->radius != 0.0) { Base::Vector3d vec = (end - center).Normalize(); end = center + vec * handler->radius; } onViewParameters[OnViewParameter::Fifth]->setPoints(center, end); } else { if (!onViewParameters[OnViewParameter::Sixth]->isSet) { setOnViewParameterValue(OnViewParameter::Sixth, handler->thickness); } Base::Vector3d start = toVector3d(handler->corner3); Base::Vector3d end = Base::Vector3d(handler->corner3.x, handler->frameCorner3.y, 0.0); onViewParameters[OnViewParameter::Sixth]->setPoints(start, end); } } else if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { onViewParameters[OnViewParameter::Third]->setPoints( toVector3d(handler->corner4), toVector3d(handler->corner3) ); bool reversed = handler->cornersReversed; if (!onViewParameters[OnViewParameter::Fifth]->isSet) { setOnViewParameterValue( OnViewParameter::Fifth, reversed ? handler->length : handler->width ); } Base::Vector3d start = toVector3d(handler->corner1); onViewParameters[OnViewParameter::Fifth]->setLabelAutoDistanceReverse(reversed); onViewParameters[OnViewParameter::Fifth]->setPoints( start, toVector3d(reversed ? handler->corner2 : handler->corner4) ); if (!onViewParameters[OnViewParameter::Sixth]->isSet) { double val = Base::toDegrees(handler->angle123); setOnViewParameterValue(OnViewParameter::Sixth, val, Base::Unit::Angle); } onViewParameters[OnViewParameter::Sixth]->setPoints( toVector3d(reversed ? handler->corner4 : handler->corner2), Base::Vector3d() ); double startAngle = reversed ? (handler->corner1 - handler->corner4).Angle() : (handler->corner3 - handler->corner2).Angle(); onViewParameters[OnViewParameter::Sixth]->setLabelStartAngle(startAngle); onViewParameters[OnViewParameter::Sixth]->setLabelRange(handler->angle123); } else { bool reversed = handler->cornersReversed; if (!onViewParameters[OnViewParameter::Fifth]->isSet) { setOnViewParameterValue( OnViewParameter::Fifth, reversed ? handler->width : handler->length ); } Base::Vector3d start = toVector3d(handler->corner1); onViewParameters[OnViewParameter::Fifth]->setLabelAutoDistanceReverse(true); onViewParameters[OnViewParameter::Fifth]->setPoints( start, toVector3d(reversed ? handler->corner4 : handler->corner2) ); if (!onViewParameters[OnViewParameter::Sixth]->isSet) { double val = Base::toDegrees(handler->angle412); setOnViewParameterValue(OnViewParameter::Sixth, val, Base::Unit::Angle); } onViewParameters[OnViewParameter::Sixth]->setPoints( toVector3d(handler->corner1), Base::Vector3d() ); double startAngle = (handler->corner2 - handler->corner1).Angle(); onViewParameters[OnViewParameter::Sixth]->setLabelStartAngle(startAngle); onViewParameters[OnViewParameter::Sixth]->setLabelRange(handler->angle412); } } break; case SelectMode::SeekFourth: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (!onViewParameters[OnViewParameter::Sixth]->isSet) { setOnViewParameterValue(OnViewParameter::Sixth, handler->thickness); } Base::Vector3d start = toVector3d(handler->corner3); Base::Vector3d end = Base::Vector3d(handler->corner3.x, handler->frameCorner3.y, 0.0); onViewParameters[OnViewParameter::Sixth]->setPoints(start, end); } else { if (handler->roundCorners) { if (!onViewParameters[OnViewParameter::Seventh]->isSet) { setOnViewParameterValue(OnViewParameter::Seventh, handler->radius); } Base::Vector3d center = handler->center3; Base::Vector3d end = toVector3d(handler->corner3); if (handler->radius != 0.0) { Base::Vector3d vec = (end - center).Normalize(); end = center + vec * handler->radius; } onViewParameters[OnViewParameter::Seventh]->setPoints(center, end); } else { if (!onViewParameters[OnViewParameter::Eighth]->isSet) { setOnViewParameterValue(OnViewParameter::Eighth, handler->thickness); } Base::Vector3d start = toVector3d(handler->corner3); Base::Vector3d vec = toVector3d((handler->corner3 - handler->corner2).Normalize()); Base::Vector3d end = start + handler->thickness * vec; onViewParameters[OnViewParameter::Eighth]->setPoints(start, end); } } } break; case SelectMode::SeekFifth: { if (!onViewParameters[OnViewParameter::Eighth]->isSet) { setOnViewParameterValue(OnViewParameter::Eighth, handler->thickness); } Base::Vector3d start = toVector3d(handler->corner3); Base::Vector3d vec = toVector3d((handler->corner3 - handler->corner2).Normalize()); Base::Vector3d end = start + handler->thickness * vec; onViewParameters[OnViewParameter::Eighth]->setPoints(start, end); } break; default: break; } } template<> void DSHRectangleController::computeNextDrawSketchHandlerMode() { switch (handler->state()) { case SelectMode::SeekFirst: { if (onViewParameters[OnViewParameter::First]->hasFinishedEditing && onViewParameters[OnViewParameter::Second]->hasFinishedEditing) { handler->setNextState(SelectMode::SeekSecond); } } break; case SelectMode::SeekSecond: { if (onViewParameters[OnViewParameter::Third]->hasFinishedEditing && onViewParameters[OnViewParameter::Fourth]->hasFinishedEditing) { if (handler->roundCorners || handler->makeFrame || handler->constructionMethod() == ConstructionMethod::ThreePoints || handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { handler->setNextState(SelectMode::SeekThird); } else { handler->setNextState(SelectMode::End); } } } break; case SelectMode::SeekThird: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (handler->roundCorners && onViewParameters[OnViewParameter::Fifth]->hasFinishedEditing) { if (handler->makeFrame) { handler->setNextState(SelectMode::SeekFourth); } else { handler->setNextState(SelectMode::End); } } else if (handler->makeFrame && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { handler->setNextState(SelectMode::End); } } else { if (onViewParameters[OnViewParameter::Fifth]->hasFinishedEditing && onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { if (handler->roundCorners || handler->makeFrame) { handler->setNextState(SelectMode::SeekFourth); } else { handler->setNextState(SelectMode::End); } } } } break; case SelectMode::SeekFourth: { if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::CenterAndCorner) { if (onViewParameters[OnViewParameter::Sixth]->hasFinishedEditing) { handler->setNextState(SelectMode::End); } } else { if (handler->roundCorners && onViewParameters[OnViewParameter::Seventh]->hasFinishedEditing) { if (handler->makeFrame) { handler->setNextState(SelectMode::SeekFifth); } else { handler->setNextState(SelectMode::End); } } else if (handler->makeFrame && onViewParameters[OnViewParameter::Eighth]->hasFinishedEditing) { handler->setNextState(SelectMode::End); } } } break; case SelectMode::SeekFifth: { if (handler->makeFrame && onViewParameters[OnViewParameter::Eighth]->hasFinishedEditing) { handler->setNextState(SelectMode::End); } } break; default: break; } } template<> void DSHRectangleController::addConstraints() { using std::numbers::pi; App::DocumentObject* obj = handler->sketchgui->getObject(); int firstCurve = handler->firstCurve; bool reverse = handler->cornersReversed; if (handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { reverse = !reverse; } auto x0 = onViewParameters[OnViewParameter::First]->getValue(); auto y0 = onViewParameters[OnViewParameter::Second]->getValue(); auto length = onViewParameters[OnViewParameter::Third]->getValue(); auto width = onViewParameters[OnViewParameter::Fourth]->getValue(); auto radius = onViewParameters[OnViewParameter::Fifth]->getValue(); auto thickness = onViewParameters[OnViewParameter::Sixth]->getValue(); auto x0set = onViewParameters[OnViewParameter::First]->isSet; auto y0set = onViewParameters[OnViewParameter::Second]->isSet; auto lengthSet = onViewParameters[OnViewParameter::Third]->isSet; auto widthSet = onViewParameters[OnViewParameter::Fourth]->isSet; auto radiusSet = onViewParameters[OnViewParameter::Fifth]->isSet; auto thicknessSet = onViewParameters[OnViewParameter::Sixth]->isSet; auto corner1x = onViewParameters[OnViewParameter::Third]->getValue(); auto corner1y = onViewParameters[OnViewParameter::Fourth]->getValue(); auto angle = Base::toRadians(onViewParameters[OnViewParameter::Fourth]->getValue()); auto innerAngle = Base::toRadians(onViewParameters[OnViewParameter::Sixth]->getValue()); auto corner1xSet = onViewParameters[OnViewParameter::Third]->isSet; auto corner1ySet = onViewParameters[OnViewParameter::Fourth]->isSet; auto angleSet = onViewParameters[OnViewParameter::Fourth]->isSet; auto innerAngleSet = onViewParameters[OnViewParameter::Sixth]->isSet; if (handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { lengthSet = false; } if (handler->constructionMethod() == ConstructionMethod::ThreePoints || handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { width = onViewParameters[OnViewParameter::Fifth]->getValue(); radius = onViewParameters[OnViewParameter::Seventh]->getValue(); thickness = onViewParameters[OnViewParameter::Eighth]->getValue(); widthSet = onViewParameters[OnViewParameter::Fifth]->isSet; radiusSet = onViewParameters[OnViewParameter::Seventh]->isSet; thicknessSet = onViewParameters[OnViewParameter::Eighth]->isSet; } using namespace Sketcher; int firstPointId = firstCurve; if (handler->constructionMethod() == ConstructionMethod::Diagonal || handler->constructionMethod() == ConstructionMethod::ThreePoints) { if (handler->radius > Precision::Confusion()) { firstPointId = handler->constructionPointOneId; } } else { firstPointId = handler->centerPointId; } auto constraintx0 = [&]() { ConstraintToAttachment(GeoElementId(firstPointId, PointPos::start), GeoElementId::VAxis, x0, obj); }; auto constrainty0 = [&]() { ConstraintToAttachment(GeoElementId(firstPointId, PointPos::start), GeoElementId::HAxis, y0, obj); }; auto constraintlength = [&]() { int curveId = reverse ? firstCurve : firstCurve + 1; Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ", curveId, 1, curveId + 2, 2, fabs(length) ); }; auto constraintwidth = [&]() { int curveId = reverse ? firstCurve + 1 : firstCurve; Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%d,%f)) ", curveId, 1, curveId + 2, 2, fabs(width) ); }; // 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 (lengthSet) { constraintlength(); } if (widthSet) { constraintwidth(); } } else { // There is a valid diagnose. auto firstpointinfo = handler->getPointInfo(GeoElementId(firstPointId, PointPos::start)); if (x0set && firstpointinfo.isXDoF()) { constraintx0(); handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after // each constraint addition firstpointinfo = handler->getPointInfo( GeoElementId(firstPointId, PointPos::start) ); // get updated point position } if (y0set && firstpointinfo.isYDoF()) { constrainty0(); handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after // each constraint addition } if (lengthSet) { int curveId = reverse ? firstCurve : firstCurve + 1; auto startpointinfo = handler->getPointInfo(GeoElementId(curveId, PointPos::start)); auto endpointinfo = handler->getPointInfo(GeoElementId(curveId + 2, PointPos::end)); int DoFs = startpointinfo.getDoFs(); DoFs += endpointinfo.getDoFs(); if (DoFs > 0) { constraintlength(); } handler->diagnoseWithAutoConstraints(); } if (widthSet) { int curveId = reverse ? firstCurve + 1 : firstCurve; auto startpointinfo = handler->getPointInfo(GeoElementId(curveId, PointPos::start)); auto endpointinfo = handler->getPointInfo(GeoElementId(curveId + 2, PointPos::end)); int DoFs = startpointinfo.getDoFs(); DoFs += endpointinfo.getDoFs(); if (DoFs > 0) { constraintwidth(); } } } // NOTE: As of today, there are no autoconstraints on the radius or on the frame thickness, // therefore, they are necessarily constrainable were applicable. if (handler->constructionMethod() == ConstructionMethod::ThreePoints) { if (angleSet) { ConstraintLineByAngle(firstCurve, angle, obj); } if (innerAngleSet) { if (fabs(innerAngle - pi / 2) > Precision::Confusion()) { // if 90? then perpendicular already created. Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", firstCurve + 1, 1, firstCurve, 2, innerAngle ); } } } else if (handler->constructionMethod() == ConstructionMethod::CenterAnd3Points) { if (corner1xSet) { ConstraintToAttachment( GeoElementId(firstCurve, PointPos::start), GeoElementId::VAxis, corner1x, obj ); } if (corner1ySet) { ConstraintToAttachment( GeoElementId(firstCurve, PointPos::start), GeoElementId::HAxis, corner1y, obj ); } if (innerAngleSet) { if (fabs(innerAngle - pi / 2) > Precision::Confusion()) { // if 90? then perpendicular already created. Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Angle',%d,%d,%d,%d,%f)) ", firstCurve, 1, firstCurve + 3, 2, innerAngle ); } } } if (radiusSet && radius > Precision::Confusion()) { Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Radius',%d,%f)) ", firstCurve + 5, // NOLINT radius ); } bool negThicknessEqualRadius = fabs(radius + thickness) < Precision::Confusion(); // in the case where negative thickness = radius, the inner rectangle has its corner // constrained to the mid of the arcs of the outer rectangle. So thickness would be redundant if (thicknessSet && !negThicknessEqualRadius) { Gui::cmdAppObjectArgs( obj, "addConstraint(Sketcher.Constraint('Distance',%d,%d,%d,%f)) ", firstCurve + (handler->roundCorners == true ? 8 : 4), // NOLINT 1, firstCurve, fabs(thickness) ); } } template<> void DSHRectangleController::doConstructionMethodChanged() { handler->updateHint(); } } // namespace SketcherGui #endif // SKETCHERGUI_DrawSketchHandlerRectangle_H