From aee93b01b9b262a76ff73f38f4a965b00a64ade1 Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Wed, 22 Nov 2023 13:59:50 +0200 Subject: [PATCH] Optimize U-notch shape. --- ChangeLog.txt | 1 + src/libs/vgeometry/vabstractcurve.cpp | 54 ++++++++ src/libs/vgeometry/vabstractcurve.h | 2 + src/libs/vgeometry/varc.cpp | 28 ++++ src/libs/vgeometry/varc.h | 2 + src/libs/vpatterndb/vpassmark.cpp | 14 +- src/test/ValentinaTest/tst_varc.cpp | 180 ++++++++++++++++++++++++++ src/test/ValentinaTest/tst_varc.h | 2 + 8 files changed, 277 insertions(+), 6 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 444cf5149..25f5b2ba4 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -57,6 +57,7 @@ - [smart-pattern/valentina#188] Label %mFileName% file name punctuation. - Adding removing nodes of curved path. - New tools: Arc start point, Arc end point. +- Optimize U-notch shape. # Valentina 0.7.52 September 12, 2022 - Fix crash when default locale is ru. diff --git a/src/libs/vgeometry/vabstractcurve.cpp b/src/libs/vgeometry/vabstractcurve.cpp index 89d1f78ad..30221133f 100644 --- a/src/libs/vgeometry/vabstractcurve.cpp +++ b/src/libs/vgeometry/vabstractcurve.cpp @@ -45,6 +45,39 @@ constexpr qreal VAbstractCurve::minLength; // NOLINT(readability-redundant-declaration) #endif +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +double NodeCurvature(const QPointF &p1, const QPointF &p2, const QPointF &p3, double length) +{ + QLineF l1(p2, p1); + l1.setAngle(l1.angle() + 180); + + QLineF l2(p2, p3); + double angle = qDegreesToRadians(l2.angleTo(l1)); + + return std::sin(angle / 2.0) / length; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MinimalLength(const QVector &points) -> double +{ + vsizetype numPoints = points.size(); + double smallestDistance = std::numeric_limits::max(); + + for (int i = 0; i < numPoints - 1; ++i) + { + double distance = QLineF(points[i], points[i + 1]).length(); + if (!qFuzzyIsNull(distance)) + { + smallestDistance = std::min(smallestDistance, distance); + } + } + + return smallestDistance; +} +} // namespace + //--------------------------------------------------------------------------------------------------------------------- VAbstractCurve::VAbstractCurve(const GOType &type, const quint32 &idObject, const Draw &mode) : VGObject(type, idObject, mode), @@ -687,6 +720,27 @@ void VAbstractCurve::SetAliasSuffix(const QString &aliasSuffix) CreateAlias(); } +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractCurve::Curvature(const QVector &vertices) -> double +{ + vsizetype numVertices = vertices.size(); + if (numVertices < 3) + { + // A polygonal chain needs at least 3 vertices + return 0.0; + } + + qreal minLength = MinimalLength(vertices); + + double sumCurvature = 0.0; + for (vsizetype i = 1; i < vertices.size() - 1; ++i) + { + sumCurvature += NodeCurvature(vertices[i - 1], vertices[i], vertices[i + 1], minLength); + } + + return sumCurvature / static_cast(vertices.size() - 2); +} + //--------------------------------------------------------------------------------------------------------------------- auto VAbstractCurve::PathLength(const QVector &path) -> qreal { diff --git a/src/libs/vgeometry/vabstractcurve.h b/src/libs/vgeometry/vabstractcurve.h index f6d72dbaa..add23119a 100644 --- a/src/libs/vgeometry/vabstractcurve.h +++ b/src/libs/vgeometry/vabstractcurve.h @@ -114,6 +114,8 @@ public: static constexpr qreal minLength = MmToPixel(1.); + static auto Curvature(const QVector &vertices) -> double; + protected: virtual void CreateName() = 0; virtual void CreateAlias() = 0; diff --git a/src/libs/vgeometry/varc.cpp b/src/libs/vgeometry/varc.cpp index 5ccdb7d25..df9921e84 100644 --- a/src/libs/vgeometry/varc.cpp +++ b/src/libs/vgeometry/varc.cpp @@ -36,6 +36,7 @@ #include "../ifc/ifcdef.h" #include "../vmisc/compatibility.h" #include "../vmisc/def.h" +#include "../vmisc/defglobal.h" #include "../vmisc/vabstractapplication.h" #include "../vmisc/vmath.h" #include "vabstractcurve.h" @@ -405,6 +406,33 @@ auto VArc::CutArc(qreal length, const QString &pointName) const -> QPointF return CutArc(length, arc1, arc2, pointName); } +//--------------------------------------------------------------------------------------------------------------------- +auto VArc::OptimalApproximationScale(qreal radius, qreal f1, qreal f2, qreal tolerance) -> qreal +{ + if (qFuzzyIsNull(radius)) + { + return maxCurveApproximationScale; + } + + const qreal expectedCurvature = 1 / qAbs(radius); + qreal scale = minCurveApproximationScale; + + do + { + VArc arc(VPointF(), radius, f1, f2); + arc.SetApproximationScale(scale); + qreal curvature = Curvature(arc.GetPoints()); + + if (expectedCurvature - curvature <= expectedCurvature * tolerance) + { + return scale; + } + scale += 0.1; + } while (scale <= maxCurveApproximationScale); + + return maxCurveApproximationScale; +} + //--------------------------------------------------------------------------------------------------------------------- void VArc::CreateName() { diff --git a/src/libs/vgeometry/varc.h b/src/libs/vgeometry/varc.h index 1ffe6f30c..24a4cc1b4 100644 --- a/src/libs/vgeometry/varc.h +++ b/src/libs/vgeometry/varc.h @@ -83,6 +83,8 @@ public: auto CutArc(qreal length, VArc &arc1, VArc &arc2, const QString &pointName) const -> QPointF; auto CutArc(qreal length, const QString &pointName) const -> QPointF; + static auto OptimalApproximationScale(qreal radius, qreal f1, qreal f2, qreal tolerance) -> qreal; + protected: void CreateName() override; void CreateAlias() override; diff --git a/src/libs/vpatterndb/vpassmark.cpp b/src/libs/vpatterndb/vpassmark.cpp index 834597ad3..d37bfc56a 100644 --- a/src/libs/vpatterndb/vpassmark.cpp +++ b/src/libs/vpatterndb/vpassmark.cpp @@ -458,9 +458,10 @@ auto CreateUMarkPassmark(const VPiecePassmarkData &passmarkData, const QLineF &l points.append(seg.p1()); points.append(seg.p2()); - VArc arc(VPointF(baseLine.p2()), radius, QLineF(baseLine.p2(), l1p2).angle(), - QLineF(baseLine.p2(), l2p2).angle()); - arc.SetApproximationScale(10); + const qreal f1 = QLineF(baseLine.p2(), l1p2).angle(); + const qreal f2 = QLineF(baseLine.p2(), l2p2).angle(); + VArc arc(VPointF(baseLine.p2()), radius, f1, f2); + arc.SetApproximationScale(VArc::OptimalApproximationScale(radius, f1, f2, 0.3)); points += arc.GetPoints(); seg = VPassmark::FindIntersection(QLineF(l2p2, l2p1), seamAllowance); @@ -469,9 +470,10 @@ auto CreateUMarkPassmark(const VPiecePassmarkData &passmarkData, const QLineF &l } else { - VArc arc(VPointF(baseLine.p1()), radius, QLineF(baseLine.p1(), l1p1).angle(), - QLineF(baseLine.p1(), l2p1).angle()); - arc.SetApproximationScale(10); + const qreal f1 = QLineF(baseLine.p1(), l1p1).angle(); + const qreal f2 = QLineF(baseLine.p1(), l2p1).angle(); + VArc arc(VPointF(baseLine.p1()), radius, f1, f2); + arc.SetApproximationScale(VArc::OptimalApproximationScale(radius, f1, f2, 0.3)); points += arc.GetPoints(); } diff --git a/src/test/ValentinaTest/tst_varc.cpp b/src/test/ValentinaTest/tst_varc.cpp index 3bdd9f908..febdbf5a2 100644 --- a/src/test/ValentinaTest/tst_varc.cpp +++ b/src/test/ValentinaTest/tst_varc.cpp @@ -27,6 +27,7 @@ *************************************************************************/ #include "tst_varc.h" +#include "../vgeometry/vabstractcurve.h" #include "../vgeometry/varc.h" #include "../vlayout/vabstractpiece.h" @@ -596,3 +597,182 @@ void TST_VArc::EmptyArc() ComparePathsDistance(empty.GetPoints(), {QPointF()}); QCOMPARE(empty.GetLength(), 0.); } + +//--------------------------------------------------------------------------------------------------------------------- +void TST_VArc::TestCurvature_data() +{ + QTest::addColumn("radius"); + QTest::addColumn("startAngle"); + QTest::addColumn("endAngle"); + + QTest::newRow("Full circle: radius 10") << 10.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius -10") << -10.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius 150") << 150.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius -150") << -150.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius 1500") << 1500.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius -1500") << -1500.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius 50000") << 50000.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius -50000") << -50000.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius 90000") << 90000.0 << 0.0 << 360.0; + QTest::newRow("Full circle: radius -90000") << -90000.0 << 0.0 << 360.0; + + QTest::newRow("Arc less than 45 degree, radius 100") << 100.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius -100") << -100.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius 150") << 150.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius -150") << -150.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius 1500") << 1500.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius -1500") << -1500.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius 50000") << 50000.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius -50000") << -50000.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius 90000") << 90000.0 << 0.0 << 10.5; + QTest::newRow("Arc less than 45 degree, radius -90000") << -90000.0 << 0.0 << 10.5; + + QTest::newRow("Arc 45 degree, radius 100") << 100.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius -100") << -100.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius 150") << 150.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius -150") << -150.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius 1500") << 1500.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius -1500") << -1500.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius 50000") << 50000.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius -50000") << -50000.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius 90000") << 90000.0 << 0.0 << 45.0; + QTest::newRow("Arc 45 degree, radius -90000") << -90000.0 << 0.0 << 45.0; + + QTest::newRow("Arc less than 90 degree, radius 100") << 100.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius -100") << -100.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius 150") << 150.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius -150") << -150.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius 1500") << 1500.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius -1500") << -1500.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius 50000") << 50000.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius -50000") << -50000.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius 90000") << 90000.0 << 0.0 << 75.0; + QTest::newRow("Arc less than 90 degree, radius -90000") << -90000.0 << 0.0 << 75.0; + + QTest::newRow("Arc 90 degree, radius 100") << 100.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius -100") << -100.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius 150") << 150.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius -150") << -150.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius 1500") << 1500.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius -1500") << -1500.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius 50000") << 50000.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius -50000") << -50000.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius 90000") << 90000.0 << 0.0 << 90.0; + QTest::newRow("Arc 90 degree, radius -90000") << -90000.0 << 0.0 << 90.0; + + QTest::newRow("Arc less than 135 degree, radius 100") << 100.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius -100") << -100.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius 150") << 150.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius -150") << -150.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius 1500") << 1500.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius -1500") << -1500.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius 50000") << 50000.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius -50000") << -50000.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius 90000") << 90000.0 << 0.0 << 110.6; + QTest::newRow("Arc less than 135 degree, radius -90000") << -90000.0 << 0.0 << 110.6; + + QTest::newRow("Arc 135 degree, radius 100") << 100.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius -100") << -100.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius 150") << 150.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius -150") << -150.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius 1500") << 1500.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius -1500") << -1500.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius 50000") << 50000.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius -50000") << -50000.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius 90000") << 90000.0 << 0.0 << 135.0; + QTest::newRow("Arc 135 degree, radius -90000") << -90000.0 << 0.0 << 135.0; + + QTest::newRow("Arc less than 180 degree, radius 100") << 100.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius -100") << -100.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius 150") << 150.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius -150") << -150.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius 1500") << 1500.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius -1500") << -1500.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius 50000") << 50000.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius -50000") << -50000.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius 90000") << 90000.0 << 0.0 << 160.7; + QTest::newRow("Arc less than 180 degree, radius -90000") << -90000.0 << 0.0 << 160.7; + + QTest::newRow("Arc 180 degree, radius 100") << 100.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius -100") << -100.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius 150") << 150.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius -150") << -150.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius 1500") << 1500.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius -1500") << -1500.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius 50000") << 50000.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius -50000") << -50000.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius 90000") << 90000.0 << 0.0 << 180.0; + QTest::newRow("Arc 180 degree, radius -90000") << -90000.0 << 0.0 << 180.0; + + QTest::newRow("Arc less than 270 degree, radius 100") << 100.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius -100") << -100.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius 150") << 150.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius -150") << -150.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius 1500") << 1500.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius -1500") << -1500.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius 50000") << 50000.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius -50000") << -50000.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius 90000") << 90000.0 << 0.0 << 150.3; + QTest::newRow("Arc less than 270 degree, radius -90000") << -90000.0 << 0.0 << 150.3; + + QTest::newRow("Arc 270 degree, radius 100") << 100.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius -100") << -100.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius 150") << 150.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius -150") << -150.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius 1500") << 1500.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius -1500") << -1500.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius 50000") << 50000.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius -50000") << -50000.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius 90000") << 90000.0 << 0.0 << 270.0; + QTest::newRow("Arc 270 degree, radius -90000") << -90000.0 << 0.0 << 270.0; + + QTest::newRow("Arc less than 360 degree, radius 100") << 100.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius -100") << -100.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius 150") << 150.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius -150") << -150.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius 1500") << 1500.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius -1500") << -1500.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius 50000") << 50000.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius -50000") << -50000.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius 90000") << 90000.0 << 0.0 << 340.0; + QTest::newRow("Arc less than 360 degree, radius -90000") << -90000.0 << 0.0 << 340.0; + + QTest::newRow("Arc start 90 degree, angle 45 degree, radius 100") << 100.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius -100") << -100.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius 150") << 150.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius -150") << -150.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius 1500") << 1500.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius -1500") << -1500.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius 50000") << 50000.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius -50000") << -50000.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius 90000") << 90000.0 << 90.0 << 135.0; + QTest::newRow("Arc start 90 degree, angle 45 degree, radius -90000") << -90000.0 << 90.0 << 135.0; +} + +//--------------------------------------------------------------------------------------------------------------------- +void TST_VArc::TestCurvature() +{ + QFETCH(qreal, radius); + QFETCH(qreal, startAngle); + QFETCH(qreal, endAngle); + + const qreal tolerance = 0.1; + qreal expectedCurvature = 1. / qAbs(radius); + qreal scale = VArc::OptimalApproximationScale(radius, startAngle, endAngle, 0.1); + + const VPointF center; + VArc arc(center, radius, startAngle, endAngle); + arc.SetApproximationScale(scale); + + qreal curvature = VAbstractCurve::Curvature(arc.GetPoints()); + + if (scale < 10) + { + QVERIFY(expectedCurvature - curvature <= expectedCurvature * tolerance); + + QVector p1 = arc.GetPoints(); + arc.SetApproximationScale(10); + QVector p2 = arc.GetPoints(); + QVERIFY(p1.size() <= p2.size()); + } +} diff --git a/src/test/ValentinaTest/tst_varc.h b/src/test/ValentinaTest/tst_varc.h index 11eb27ead..b3bc11225 100644 --- a/src/test/ValentinaTest/tst_varc.h +++ b/src/test/ValentinaTest/tst_varc.h @@ -53,6 +53,8 @@ private slots: void TestCurveIntersectAxis_data(); void TestCurveIntersectAxis(); void EmptyArc(); + void TestCurvature_data(); + void TestCurvature(); }; #endif // TST_VARC_H