From de275eba0cca3108e582a6f2a669371c6a2c8a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loke=20Str=C3=B8m?= Date: Mon, 16 Mar 2026 17:49:34 +0100 Subject: [PATCH] Sketcher: Fix: Arc of Hyperbola (#27656) * Sketcher: Fix: Arc of Hyperbola * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added minor grater than major radius support * Fixed merge errors * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Improved comments and added a confused points check for S1 and S2 distance. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added compile check for occ version and check before getting minor radius. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review OCC version check Co-authored-by: Chris Hennes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris Hennes (cherry picked from commit 9abe7b4af1b719347a82e143dfe25ac2e0eef743) --- src/Mod/Part/App/HyperbolaPyImp.cpp | 30 +- .../Gui/DrawSketchHandlerArcOfHyperbola.h | 332 +++++++++--------- 2 files changed, 190 insertions(+), 172 deletions(-) diff --git a/src/Mod/Part/App/HyperbolaPyImp.cpp b/src/Mod/Part/App/HyperbolaPyImp.cpp index 86af38978a..9289b9c44c 100644 --- a/src/Mod/Part/App/HyperbolaPyImp.cpp +++ b/src/Mod/Part/App/HyperbolaPyImp.cpp @@ -24,7 +24,7 @@ #include #include - +#include #include #include @@ -97,11 +97,39 @@ int HyperbolaPy::PyInit(PyObject* args, PyObject* kwds) Base::Vector3d v1 = static_cast(pV1)->value(); Base::Vector3d v2 = static_cast(pV2)->value(); Base::Vector3d v3 = static_cast(pV3)->value(); + + +#if OCC_VERSION_HEX < 0x080000 + // This is a workaround do to fault in OCCT. + // It is fixed in OCCT 8.0.0, + gp_Pnt S1(v1.x, v1.y, v1.z); + gp_Pnt S2(v2.x, v2.y, v2.z); + gp_Pnt Center(v3.x, v3.y, v3.z); + + if (S1.Distance(Center) < gp::Resolution() || S2.Distance(Center) < gp::Resolution() + || S1.Distance(S2) < gp::Resolution()) { + PyErr_SetString(PartExceptionOCCError, gce_ErrorStatusText(gce_ConfusedPoints)); + return -1; + } + + gp_Dir XAxis(S1.XYZ() - Center.XYZ()); + gp_Lin centerLine(Center, XAxis); + double majorRadius = S1.Distance(Center); + double minorRadius = centerLine.Distance(S2); + if (minorRadius < gp::Resolution()) { + PyErr_SetString(PartExceptionOCCError, gce_ErrorStatusText(gce_ColinearPoints)); + return -1; + } + gp_Dir norm(XAxis.Crossed(gp_Dir(S2.XYZ() - Center.XYZ()))); + GC_MakeHyperbola me(gp_Ax2(Center, norm, XAxis), majorRadius, minorRadius); +#else GC_MakeHyperbola me( gp_Pnt(v1.x, v1.y, v1.z), gp_Pnt(v2.x, v2.y, v2.z), gp_Pnt(v3.x, v3.y, v3.z) ); +#endif + if (!me.IsDone()) { PyErr_SetString(PartExceptionOCCError, gce_ErrorStatusText(me.Status())); return -1; diff --git a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h index bd5fa1b1c0..1ecbf07bbe 100644 --- a/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h +++ b/src/Mod/Sketcher/Gui/DrawSketchHandlerArcOfHyperbola.h @@ -23,8 +23,6 @@ #ifndef SKETCHERGUI_DrawSketchHandlerArcOfHyperbola_H #define SKETCHERGUI_DrawSketchHandlerArcOfHyperbola_H -#include - #include #include #include @@ -50,31 +48,30 @@ class DrawSketchHandlerArcOfHyperbola: public DrawSketchHandler public: DrawSketchHandlerArcOfHyperbola() - : Mode(STATUS_SEEK_First) + : Mode(SelectMode::First) , EditCurve(34) , arcAngle(0) - , arcAngle_t(0) {} ~DrawSketchHandlerArcOfHyperbola() override = default; /// mode table - enum SelectMode + enum class SelectMode { - STATUS_SEEK_First, - STATUS_SEEK_Second, - STATUS_SEEK_Third, - STATUS_SEEK_Fourth, - STATUS_Close + First, + Second, + Third, + Fourth, + End }; void mouseMove(SnapManager::SnapHandle snapHandle) override { Base::Vector2d onSketchPos = snapHandle.compute(); - if (Mode == STATUS_SEEK_First) { + if (Mode == SelectMode::First) { setPositionText(onSketchPos); seekAndRenderAutoConstraint(sugConstr1, onSketchPos, Base::Vector2d(0.f, 0.f)); } - else if (Mode == STATUS_SEEK_Second) { + else if (Mode == SelectMode::Second) { EditCurve[1] = onSketchPos; // Display radius for user @@ -94,103 +91,115 @@ public: AutoConstraint::CURVE ); } - else if (Mode == STATUS_SEEK_Third) { + else if (Mode == SelectMode::Third) { // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint - centerPoint).Length(); - double phi = atan2(axisPoint.y - centerPoint.y, axisPoint.x - centerPoint.x); + Base::Vector2d delta12 = axisPoint - centerPoint; - // This is the angle at cursor point - double angleatpoint = acosh( - ((onSketchPos.x - centerPoint.x) * cos(phi) - + (onSketchPos.y - centerPoint.y) * sin(phi)) - / a + double a = delta12.Length(); + assert( + a > Precision::Confusion() + && "DrawSketchHandlerArcOfHyperbola: First and second point are at the same place" ); - double b = ((onSketchPos.y - centerPoint.y) * cos(phi) - - (onSketchPos.x - centerPoint.x) * sin(phi)) - / sinh(angleatpoint); - if (!boost::math::isnan(b)) { - for (int i = 15; i >= -15; i--) { - // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir - // double angle = i*std::numbers::pi/16.0; - double angle = i * angleatpoint / 15; - double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); - double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); - EditCurve[15 + i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); - } + Base::Vector2d aDir = delta12.Normalize(); + Base::Vector2d bDir(-aDir.y, aDir.x); - // Display radius for user - if (showCursorCoords()) { - SbString text; - std::string aString = lengthToDisplayFormat(a, 1); - std::string bString = lengthToDisplayFormat(b, 1); - text.sprintf(" (R%s, R%s)", aString.c_str(), bString.c_str()); - setPositionText(onSketchPos, text); - } + Base::Vector2d delta13 = onSketchPos - centerPoint; + Base::Vector2d delta13Prime( + delta13.x * aDir.x + delta13.y * aDir.y, + delta13.x * bDir.x + delta13.y * bDir.y + ); - drawEdit(EditCurve); - seekAndRenderAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f, 0.f)); + // 0 if less than Precision::Confusion() + double b = getMinorRadius(onSketchPos); + + double angleatpoint = 0; + if (b != 0) { + angleatpoint = atanh((delta13Prime.y * a) / (delta13Prime.x * b)); } + + for (int i = 16; i >= -16; i--) { + // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir + double angle = i * angleatpoint / 16.0; + double rx = a * cosh(angle) * aDir.x + b * sinh(angle) * bDir.x; + double ry = a * cosh(angle) * aDir.y + b * sinh(angle) * bDir.y; + EditCurve[16 + i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); + } + + // Display radius for user + if (showCursorCoords()) { + SbString text; + std::string aString = lengthToDisplayFormat(a, 1); + std::string bString = lengthToDisplayFormat(b, 1); + text.sprintf(" (R%s, R%s)", aString.c_str(), bString.c_str()); + setPositionText(onSketchPos, text); + } + + if (b != 0) { + drawEdit(EditCurve); + } + else { + drawEdit(std::vector()); + } + + seekAndRenderAutoConstraint(sugConstr3, onSketchPos, Base::Vector2d(0.f, 0.f)); } - else if (Mode == STATUS_SEEK_Fourth) { + else if (Mode == SelectMode::Fourth) { // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint - centerPoint).Length(); - double phi = atan2(axisPoint.y - centerPoint.y, axisPoint.x - centerPoint.x); + Base::Vector2d delta12 = axisPoint - centerPoint; - // This is the angle at cursor point - double angleatstartingpoint = acosh( - ((startingPoint.x - centerPoint.x) * cos(phi) - + (startingPoint.y - centerPoint.y) * sin(phi)) - / a - ); - double b = ((startingPoint.y - centerPoint.y) * cos(phi) - - (startingPoint.x - centerPoint.x) * sin(phi)) - / sinh(angleatstartingpoint); - - double startAngle = angleatstartingpoint; - - // double angleatpoint = - // acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); - - double angleatpoint = atanh( - (((onSketchPos.y - centerPoint.y) * cos(phi) - - (onSketchPos.x - centerPoint.x) * sin(phi)) - * a) - / (((onSketchPos.x - centerPoint.x) * cos(phi) - + (onSketchPos.y - centerPoint.y) * sin(phi)) - * b) + double a = delta12.Length(); + assert( + a > Precision::Confusion() + && "DrawSketchHandlerArcOfHyperbola: First and second point are at the same place" ); - /*double angle1 = angleatpoint - startAngle; + Base::Vector2d aDir = delta12.Normalize(); + Base::Vector2d bDir(-aDir.y, aDir.x); - double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * std::numbers::pi ; - arcAngle = abs(angle1-arcAngle) < abs(angle2-arcAngle) ? angle1 : angle2;*/ + Base::Vector2d delta13 = startingPoint - centerPoint; + Base::Vector2d delta13Prime( + delta13.x * aDir.x + delta13.y * aDir.y, + delta13.x * bDir.x + delta13.y * bDir.y + ); + + double b = getMinorRadius(startingPoint); + assert( + b > Precision::Confusion() && "DrawSketchHandlerArcOfHyperbola: Minor radius was unexpectedly set to invalid value" + ); + + double startAngle = atanh((delta13Prime.y * a) / (delta13Prime.x * b)); + + Base::Vector2d delta14 = onSketchPos - centerPoint; + Base::Vector2d delta14Prime( + delta14.x * aDir.x + delta14.y * aDir.y, + delta14.x * bDir.x + delta14.y * bDir.y + ); + + double angleatpoint = atanh((delta14Prime.y * a) / (delta14Prime.x * b)); arcAngle = angleatpoint - startAngle; - // if(!boost::math::isnan(angle1) && !boost::math::isnan(angle2)){ - if (!boost::math::isnan(arcAngle)) { - EditCurve.resize(33); - for (int i = 0; i < 33; i++) { - // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir - // double angle=i*angleatpoint/16; - double angle = startAngle + i * arcAngle / 32.0; - double rx = a * cosh(angle) * cos(phi) - b * sinh(angle) * sin(phi); - double ry = a * cosh(angle) * sin(phi) + b * sinh(angle) * cos(phi); - EditCurve[i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); - } - - // Display radius for user - if (showCursorCoords()) { - SbString text; - std::string aString = lengthToDisplayFormat(a, 1); - std::string bString = lengthToDisplayFormat(b, 1); - text.sprintf(" (R%s, R%s)", aString.c_str(), bString.c_str()); - setPositionText(onSketchPos, text); - } + if (std::abs((delta14Prime.y * a) / (delta14Prime.x * b)) > 1) { + arcAngle = 0; } - else { - arcAngle = 0.; + + for (int i = 0; i < 33; i++) { + // P(U) = O + MajRad*Cosh(U)*XDir + MinRad*Sinh(U)*YDir + double angle = startAngle + i * arcAngle / 32.0; + double rx = a * cosh(angle) * aDir.x + b * sinh(angle) * bDir.x; + double ry = a * cosh(angle) * aDir.y + b * sinh(angle) * bDir.y; + EditCurve[i] = Base::Vector2d(centerPoint.x + rx, centerPoint.y + ry); + } + + // Display radius for user + if (showCursorCoords()) { + SbString text; + std::string aString = lengthToDisplayFormat(a, 1); + std::string bString = lengthToDisplayFormat(b, 1); + std::string arcAngleString = angleToDisplayFormat(Base::toDegrees(arcAngle), 1); + text.sprintf(" (R%s, R%s, %s)", aString.c_str(), bString.c_str(), arcAngleString.c_str()); + setPositionText(onSketchPos, text); } drawEdit(EditCurve); @@ -200,28 +209,28 @@ public: bool pressButton(Base::Vector2d onSketchPos) override { - if (Mode == STATUS_SEEK_First) { + if (Mode == SelectMode::First) { EditCurve[0] = onSketchPos; centerPoint = onSketchPos; EditCurve.resize(2); - Mode = STATUS_SEEK_Second; + Mode = SelectMode::Second; } - else if (Mode == STATUS_SEEK_Second) { + else if (Mode == SelectMode::Second + && (centerPoint - onSketchPos).Length() > Precision::Confusion()) { EditCurve[1] = onSketchPos; axisPoint = onSketchPos; - EditCurve.resize(31); - Mode = STATUS_SEEK_Third; + EditCurve.resize(33); + Mode = SelectMode::Third; } - else if (Mode == STATUS_SEEK_Third) { + else if (Mode == SelectMode::Third && getMinorRadius(onSketchPos) > Precision::Confusion()) { startingPoint = onSketchPos; arcAngle = 0.; - arcAngle_t = 0.; - Mode = STATUS_SEEK_Fourth; + Mode = SelectMode::Fourth; } - else { // Fourth + else if (Mode == SelectMode::Fourth && arcAngle != 0) { endPoint = onSketchPos; - Mode = STATUS_Close; + Mode = SelectMode::End; } updateHint(); @@ -230,88 +239,47 @@ public: bool releaseButton(Base::Vector2d /*onSketchPos*/) override { - if (Mode == STATUS_Close) { + if (Mode == SelectMode::End) { unsetCursor(); resetPositionText(); + Base::Vector2d delta12 = axisPoint - centerPoint; - // angle between the major axis of the hyperbola and the X axis - double a = (axisPoint - centerPoint).Length(); - double phi = atan2(axisPoint.y - centerPoint.y, axisPoint.x - centerPoint.x); + double a = delta12.Length(); - // This is the angle at cursor point - double angleatstartingpoint = acosh( - ((startingPoint.x - centerPoint.x) * cos(phi) - + (startingPoint.y - centerPoint.y) * sin(phi)) - / a + Base::Vector2d aDir = delta12.Normalize(); + Base::Vector2d bDir(-aDir.y, aDir.x); + + Base::Vector2d delta13 = startingPoint - centerPoint; + Base::Vector2d delta13Prime( + delta13.x * aDir.x + delta13.y * aDir.y, + delta13.x * bDir.x + delta13.y * bDir.y ); - double b = ((startingPoint.y - centerPoint.y) * cos(phi) - - (startingPoint.x - centerPoint.x) * sin(phi)) - / sinh(angleatstartingpoint); + double b = getMinorRadius(startingPoint); - double startAngle = angleatstartingpoint; + double startAngle = atanh((delta13Prime.y * a) / (delta13Prime.x * b)); - // double angleatpoint = - // acosh(((onSketchPos.x-centerPoint.x)*cos(phi)+(onSketchPos.y-centerPoint.y)*sin(phi))/a); - - double endAngle = atanh( - (((endPoint.y - centerPoint.y) * cos(phi) - (endPoint.x - centerPoint.x) * sin(phi)) - * a) - / (((endPoint.x - centerPoint.x) * cos(phi) + (endPoint.y - centerPoint.y) * sin(phi)) - * b) + Base::Vector2d delta14 = endPoint - centerPoint; + Base::Vector2d delta14Prime( + delta14.x * aDir.x + delta14.y * aDir.y, + delta14.x * bDir.x + delta14.y * bDir.y ); - - if (boost::math::isnan(startAngle) || boost::math::isnan(endAngle)) { - Gui::NotifyError( - sketchgui, - QT_TRANSLATE_NOOP("Notifications", "Error"), - QT_TRANSLATE_NOOP( - "Notifications", - "Cannot create arc of hyperbola from invalid angles, try again!" - ) - ); - sketchgui->purgeHandler(); - return false; - } - + double endAngle = atanh((delta14Prime.y * a) / (delta14Prime.x * b)); bool isOriginalArcCCW = true; - if (arcAngle > 0) { - endAngle = startAngle + arcAngle; - } - else { - endAngle = startAngle; - startAngle += arcAngle; + if (endAngle < startAngle) { + std::swap(startAngle, endAngle); isOriginalArcCCW = false; } - Base::Vector2d majAxisDir, minAxisDir, minAxisPoint, majAxisPoint; + Base::Vector2d minAxisPoint, majAxisPoint; // We always create a CCW hyperbola, because we want our XY reference system to be in // the +X +Y direction Our normal will then always be in the +Z axis (local +Z axis of // the sketcher) - - if (a > b) { - // force second semidiameter to be perpendicular to first semidiamater - majAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(-majAxisDir.y, majAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - minAxisPoint = centerPoint + perp; - majAxisPoint = centerPoint + majAxisDir; - } - else { - // force second semidiameter to be perpendicular to first semidiamater - minAxisDir = axisPoint - centerPoint; - Base::Vector2d perp(minAxisDir.y, -minAxisDir.x); - perp.Normalize(); - perp.Scale(abs(b)); - majAxisPoint = centerPoint + perp; - minAxisPoint = centerPoint + minAxisDir; - endAngle += std::numbers::pi / 2; - startAngle += std::numbers::pi / 2; - } + majAxisPoint = centerPoint + (aDir * a); + minAxisPoint = centerPoint + (bDir * b); int currentgeoid = getHighestCurveIndex(); @@ -398,7 +366,7 @@ public: if (continuousMode) { // This code enables the continuous creation mode. - Mode = STATUS_SEEK_First; + Mode = SelectMode::First; EditCurve.clear(); drawEdit(EditCurve); EditCurve.resize(34); @@ -430,22 +398,22 @@ private: return Gui::lookupHints( Mode, { - {.state = STATUS_SEEK_First, + {.state = SelectMode::First, .hints = { {tr("%1 pick center point"), {MouseLeft}}, }}, - {.state = STATUS_SEEK_Second, + {.state = SelectMode::Second, .hints = { {tr("%1 pick axis point"), {MouseLeft}}, }}, - {.state = STATUS_SEEK_Third, + {.state = SelectMode::Third, .hints = { {tr("%1 pick arc start point"), {MouseLeft}}, }}, - {.state = STATUS_SEEK_Fourth, + {.state = SelectMode::Fourth, .hints = { {tr("%1 pick arc end point"), {MouseLeft}}, @@ -453,11 +421,33 @@ private: }); } + double getMinorRadius(const Base::Vector2d& onSketchPos) + { + Base::Vector2d delta12 = axisPoint - centerPoint; + + double a = delta12.Length(); + + Base::Vector2d aDir = delta12.Normalize(); + Base::Vector2d bDir(-aDir.y, aDir.x); + + Base::Vector2d delta13 = onSketchPos - centerPoint; + Base::Vector2d delta13Prime( + delta13.x * aDir.x + delta13.y * aDir.y, + delta13.x * bDir.x + delta13.y * bDir.y + ); + + double denom = (delta13Prime.x * delta13Prime.x) / (a * a) - 1.0; + if (denom <= Precision::Confusion()) { + return 0; + } + return std::sqrt((delta13Prime.y * delta13Prime.y) / denom); + } + protected: SelectMode Mode; std::vector EditCurve; Base::Vector2d centerPoint, axisPoint, startingPoint, endPoint; - double arcAngle, arcAngle_t; + double arcAngle; std::vector sugConstr1, sugConstr2, sugConstr3, sugConstr4; };