From fd67538dec97fc1b8bfc4cdfabd0550cbfab9a40 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Sun, 12 Jan 2025 17:57:03 +0530 Subject: [PATCH] [Sketcher][test] Divide `SketchObject` tests into multiple files ...for managability. --- tests/src/Mod/Sketcher/App/CMakeLists.txt | 2 + tests/src/Mod/Sketcher/App/SketchObject.cpp | 946 ------------------ .../Mod/Sketcher/App/SketchObjectChanges.cpp | 920 +++++++++++++++++ .../Mod/Sketcher/App/SketcherTestHelpers.cpp | 155 +++ .../Mod/Sketcher/App/SketcherTestHelpers.h | 142 +-- 5 files changed, 1112 insertions(+), 1053 deletions(-) create mode 100644 tests/src/Mod/Sketcher/App/SketchObjectChanges.cpp create mode 100644 tests/src/Mod/Sketcher/App/SketcherTestHelpers.cpp diff --git a/tests/src/Mod/Sketcher/App/CMakeLists.txt b/tests/src/Mod/Sketcher/App/CMakeLists.txt index 7bcb006997..75dc0501a2 100644 --- a/tests/src/Mod/Sketcher/App/CMakeLists.txt +++ b/tests/src/Mod/Sketcher/App/CMakeLists.txt @@ -1,7 +1,9 @@ target_sources( Sketcher_tests_run PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/SketcherTestHelpers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/SketchObject.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/SketchObjectChanges.cpp ) add_subdirectory(planegcs) diff --git a/tests/src/Mod/Sketcher/App/SketchObject.cpp b/tests/src/Mod/Sketcher/App/SketchObject.cpp index 98b1a5c837..30e4432387 100644 --- a/tests/src/Mod/Sketcher/App/SketchObject.cpp +++ b/tests/src/Mod/Sketcher/App/SketchObject.cpp @@ -1,7 +1,5 @@ // SPDX-License-Identifier: LGPL-2.1-or-later -#include - #include #include @@ -10,50 +8,10 @@ #include #include #include -#include #include "SketcherTestHelpers.h" using namespace SketcherTestHelpers; -class SketchObjectTest: public ::testing::Test -{ -protected: - static void SetUpTestSuite() - { - tests::initApplication(); - } - - void SetUp() override - { - _docName = App::GetApplication().getUniqueDocumentName("test"); - auto _doc = App::GetApplication().newDocument(_docName.c_str(), "testUser"); - // TODO: Do we add a body newName, or is just adding sketch sufficient for this test? - _sketchobj = - static_cast(_doc->addObject("Sketcher::SketchObject")); - } - - void TearDown() override - { - App::GetApplication().closeDocument(_docName.c_str()); - } - - Sketcher::SketchObject* getObject() - { - return _sketchobj; - } - -private: - // TODO: use shared_ptr or something else here? - Sketcher::SketchObject* _sketchobj; - std::string _docName; - std::vector allowedTypes {"Vertex", - "Edge", - "ExternalEdge", - "H_Axis", - "V_Axis", - "RootPoint"}; -}; - TEST_F(SketchObjectTest, createSketchObject) // NOLINT { // Arrange @@ -752,910 +710,6 @@ TEST_F(SketchObjectTest, testDeleteOnlyUnusedInternalGeometryOfBSpline) EXPECT_EQ(getObject()->getHighestCurveIndex(), 2); } -TEST_F(SketchObjectTest, testSplitLineSegment) -{ - // Arrange - Base::Vector3d splitPoint(2.0, 3.1, 0.0); - Part::GeomLineSegment lineSeg; - setupLineSegment(lineSeg); - int geoId = getObject()->addGeometry(&lineSeg); - - // Act - int result = getObject()->split(geoId, splitPoint); - - // Assert - EXPECT_EQ(result, 0); - // One additional curve should be added - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); - // TODO: Expect the resultant curves are line segments and shape is conserved - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testSplitCircle) -{ - // Arrange - Base::Vector3d splitPoint(2.0, 3.1, 0.0); - Part::GeomCircle circle; - setupCircle(circle); - int geoId = getObject()->addGeometry(&circle); - - // Act - int result = getObject()->split(geoId, splitPoint); - - // Assert - EXPECT_EQ(result, 0); - // The circle should be split into an arc now - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); -} - -TEST_F(SketchObjectTest, testSplitEllipse) -{ - // Arrange - Base::Vector3d splitPoint(2.0, 3.1, 0.0); - Part::GeomEllipse ellipse; - setupEllipse(ellipse); - int geoId = getObject()->addGeometry(&ellipse); - - // Act - int result = getObject()->split(geoId, splitPoint); - - // Assert - EXPECT_EQ(result, 0); - // TODO: The ellipse should be split into an arc of ellipse now - // FIXME: Internal geometries may be added or removed which may cause some issues - // EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); -} - -TEST_F(SketchObjectTest, testSplitArcOfCircle) -{ - // Arrange - Base::Vector3d splitPoint(-2.0, 3.1, 0.0); - Part::GeomArcOfCircle arcOfCircle; - setupArcOfCircle(arcOfCircle); - int geoId = getObject()->addGeometry(&arcOfCircle); - - // Act - int result = getObject()->split(geoId, splitPoint); - - // Assert - EXPECT_EQ(result, 0); - // The arcOfCircle should be split into an arc now - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); - // Expect the end points and centers of the resultant curve are coincident. - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 2); -} - -TEST_F(SketchObjectTest, testSplitArcOfConic) -{ - // Arrange - // TODO: Define a parabola/hyperbola as reference - Base::Vector3d splitPoint(1.0, -1.1, 0.0); - Part::GeomArcOfParabola arcOfConic; - setupArcOfParabola(arcOfConic); - int geoId = getObject()->addGeometry(&arcOfConic); - - // Act - // TODO: Sample random points from both sides of the split - int result = getObject()->split(geoId, splitPoint); - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - // The arcOfConic should be split into two arcs of the same conic now - EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); - // TODO: Expect the end points of the resultant curve are coincident. - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testSplitNonPeriodicBSpline) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - Base::Vector3d splitPoint(-0.5, 1.1, 0.0); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - // TODO: Put a point on this - - // Act - // TODO: sample before point(s) at a random parameter - int result = getObject()->split(geoId, splitPoint); - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); - // TODO: confirm sampled point(s) is/are at the same place - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testSplitPeriodicBSpline) -{ - // Arrange - auto PeriodicBSpline = createTypicalPeriodicBSpline(); - Base::Vector3d splitPoint(-0.5, 1.1, 0.0); - int geoId = getObject()->addGeometry(PeriodicBSpline.get()); - // TODO: Put a point on this - - // Act - // TODO: sample before point(s) at a random parameter - int result = getObject()->split(geoId, splitPoint); - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); - // TODO: confirm sampled point(s) is/are at the same place -} - -TEST_F(SketchObjectTest, testTrimWithoutIntersection) -{ - // Arrange - Part::GeomLineSegment lineSeg; - setupLineSegment(lineSeg); - int geoId = getObject()->addGeometry(&lineSeg); - Base::Vector3d trimPoint(2.0, 3.1, 0.0); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // Once this line segment is trimmed, nothing should remain - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId - 1); -} - -// TODO: There are other combinations of constraints we may want to test with trim. - -TEST_F(SketchObjectTest, testTrimLineSegmentEnd) -{ - // Arrange - Part::GeomLineSegment lineSeg; - setupLineSegment(lineSeg); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(lineSeg, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(lineSeg, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(&lineSeg); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // TODO: Once this line segment is trimmed, the curve should be "smaller" - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); - // TODO: There should be a "point-on-object" constraint on the intersecting curves - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testTrimLineSegmentMid) -{ - // Arrange - Part::GeomLineSegment lineSeg; - setupLineSegment(lineSeg); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(lineSeg, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(lineSeg, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(lineSeg, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y - 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y += 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(&lineSeg); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // TODO: Once this line segment is trimmed, there should be two "smaller" curves in its place - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); - // TODO: There should be a "point-on-object" constraint on the intersecting curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); - // TODO: Ensure shape is preserved -} - -TEST_F(SketchObjectTest, testTrimCircleEnd) -{ - // Arrange - Part::GeomCircle circle; - setupCircle(circle); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(circle, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(circle, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(&circle); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // TODO: Once this circle is trimmed, the circle should be deleted. - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId - 1); -} - -TEST_F(SketchObjectTest, testTrimCircleMid) -{ - // Arrange - Part::GeomCircle circle; - setupCircle(circle); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(circle, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(circle, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(circle, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y -= 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(&circle); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // TODO: Once this circle is trimmed, there should be one arc. - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); - // There should be one "coincident" and one "point-on-object" constraint on the intersecting - // curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); - // TODO: Ensure shape is preserved -} - -TEST_F(SketchObjectTest, testTrimArcOfCircleEnd) -{ - // This should also cover as a representative of arc of conic - - // Arrange - Part::GeomArcOfCircle arcOfCircle; - setupArcOfCircle(arcOfCircle); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(arcOfCircle, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(arcOfCircle, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(&arcOfCircle); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); - // There should be a "point-on-object" constraint on the intersecting curves - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testTrimArcOfCircleMid) -{ - // Arrange - Part::GeomArcOfCircle arcOfCircle; - setupArcOfCircle(arcOfCircle); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(arcOfCircle, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(arcOfCircle, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(arcOfCircle, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y -= 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(&arcOfCircle); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); - // There should be a "point-on-object" constraint on the intersecting curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - // There should be 2 coincident constraints: one with lineSegCut1 and one between centers of the - // new arcs - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 2); - // TODO: Ensure shape is preserved -} - -TEST_F(SketchObjectTest, testTrimEllipseEnd) -{ - // Arrange - Part::GeomEllipse ellipse; - setupEllipse(ellipse); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(ellipse, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(ellipse, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(&ellipse); - - // Act - int result = getObject()->trim(geoId, trimPoint); - // remove all internal geometry - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - // Once this ellipse is trimmed, the ellipse should be deleted. - // Only remaining: line segment - EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); -} - -TEST_F(SketchObjectTest, testTrimEllipseMid) -{ - // Arrange - Part::GeomEllipse ellipse; - setupEllipse(ellipse); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(ellipse, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(ellipse, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(ellipse, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y -= 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(&ellipse); - // FIXME: Doing this to avoid trimming only until minor/major axes. Should not be needed. - getObject()->deleteUnusedInternalGeometry(geoId); - - // Act - int result = getObject()->trim(geoId, trimPoint); - // remove all internal geometry - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - // Once this ellipse is trimmed, there should be one arc and line segments. - EXPECT_EQ(getObject()->getHighestCurveIndex(), 2); - // There should be one "coincident" and one "point-on-object" constraint on the intersecting - // curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); - // TODO: Ensure shape is preserved -} - -// TODO: Tests for other arcs of conics? - -TEST_F(SketchObjectTest, testTrimPeriodicBSplineEnd) -{ - // Arrange - auto periodicBSpline = createTypicalPeriodicBSpline(); - assert(periodicBSpline); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(*periodicBSpline, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(*periodicBSpline, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(periodicBSpline.get()); - - // Act - int result = getObject()->trim(geoId, trimPoint); - - // Assert - EXPECT_EQ(result, 0); - // FIXME: This will fail because of deleted internal geometry - // Once this periodicBSpline is trimmed, the periodicBSpline should be deleted, leaving only the - // line segment. - EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); - // TODO: There should be a "point-on-object" constraint on the intersecting curves -} - -TEST_F(SketchObjectTest, testTrimPeriodicBSplineMid) -{ - // Arrange - auto periodicBSpline = createTypicalPeriodicBSpline(); - assert(periodicBSpline); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(*periodicBSpline, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(*periodicBSpline, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(*periodicBSpline, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y -= 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(periodicBSpline.get()); - - // Act - int result = getObject()->trim(geoId, trimPoint); - // remove all internal geometry - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - // Only remaining: Two line segments and the B-spline - EXPECT_EQ(getObject()->getHighestCurveIndex(), 2); - // There should be one "coincident" and one "point-on-object" constraint on the intersecting - // curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); - // TODO: Ensure shape is preserved -} - -TEST_F(SketchObjectTest, testTrimNonPeriodicBSplineEnd) -{ - // This should also cover as a representative of arc of conic - - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - // create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.2)); - Base::Vector3d p1(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.5)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - - // Act - int result = getObject()->trim(geoId, trimPoint); - // remove all internal geometry - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - - // Assert - EXPECT_EQ(result, 0); - // Only remaining: one line segment and the trimmed B-spline - EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); - // FIXME: There should be a "point-on-object" constraint on the intersecting curves - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); -} - -TEST_F(SketchObjectTest, testTrimNonPeriodicBSplineMid) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - // TODO: create curves intersecting at the right spots - Base::Vector3d trimPoint(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.5)); - Base::Vector3d p1(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.3)); - Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); - Part::GeomLineSegment lineSegCut1; - lineSegCut1.setPoints(p1, p2); - getObject()->addGeometry(&lineSegCut1); - Base::Vector3d p3(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.7)); - Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); - // to ensure that this line clearly intersects the curve, not just have a point on object - // without explicit constraint - p3.x -= 0.1; - p3.y -= 0.1; - Part::GeomLineSegment lineSegCut2; - lineSegCut2.setPoints(p3, p4); - getObject()->addGeometry(&lineSegCut2); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - - // Act - int result = getObject()->trim(geoId, trimPoint); - // remove all internal geometry - for (int i = 0; i < getObject()->getHighestCurveIndex(); ++i) { - if (getObject()->getGeometry(i)->is()) { - getObject()->deleteUnusedInternalGeometry(i); - } - } - - // Assert - EXPECT_EQ(result, 0); - // Only remaining: one line segment and the trimmed B-spline - EXPECT_EQ(getObject()->getHighestCurveIndex(), 3); - // There should be a "point-on-object" constraint on the intersecting curves - int numberOfPointOnObjectConstraints = - countConstraintsOfType(getObject(), Sketcher::PointOnObject); - EXPECT_EQ(numberOfPointOnObjectConstraints, 1); - int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); - EXPECT_EQ(numberOfCoincidentConstraints, 1); - // TODO: Ensure shape is preserved -} - -TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSplineToZero) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - auto bsp1 = static_cast(getObject()->getGeometry(geoId)); - int oldKnotCount = bsp1->countKnots(); - - // Act - // Try decreasing mult to zero. - // NOTE: we still use OCCT notation of knot index starting with 1 (not 0). - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); - // Assert - // Knot should disappear. We start with 3 (unique) knots, so expect 2. - auto bsp2 = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp2->countKnots(), oldKnotCount - 1); -} - -TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSplineToDisallowed) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - - // Act and Assert - // TODO: Try modifying such that resultant multiplicity > degree - // TODO: This should immediately throw exception - EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 3), Base::ValueError); - // TODO: Try modifying such that resultant multiplicity < 0 - // TODO: This should immediately throw exception - EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -2), Base::ValueError); -} - -TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSpline) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - - auto bsp = static_cast(getObject()->getGeometry(geoId)); - int oldKnotsNum = bsp->countKnots(); - int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; - - // Act - // TODO: Increase/decrease knot multiplicity normally - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum); - // This should increment the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot + 1); - // This should still be a non-periodic spline - EXPECT_FALSE(bsp->isPeriodic()); - // TODO: Expect shape is preserved - - // Act - // TODO: Increase/decrease knot multiplicity normally - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum); - // This should increment the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot); - // This should still be a non-periodic spline - EXPECT_FALSE(bsp->isPeriodic()); -} - -TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSplineToZero) -{ - // Arrange - auto PeriodicBSpline = createTypicalPeriodicBSpline(); - assert(PeriodicBSpline); - int geoId = getObject()->addGeometry(PeriodicBSpline.get()); - auto bsp1 = static_cast(getObject()->getGeometry(geoId)); - int oldKnotCount = bsp1->countKnots(); - - // Act - // Try decreasing mult to zero. - // NOTE: we still use OCCT notation of knot index starting with 1 (not 0). - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); - // Assert - // Knot should disappear. - auto bsp2 = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp2->countKnots(), oldKnotCount - 1); -} - -TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSplineToDisallowed) -{ - // Arrange - auto PeriodicBSpline = createTypicalPeriodicBSpline(); - assert(PeriodicBSpline); - int geoId = getObject()->addGeometry(PeriodicBSpline.get()); - - // Act and Assert - // TODO: Try modifying such that resultant multiplicity > degree - // TODO: This should immediately throw exception - EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 3), Base::ValueError); - // TODO: Try modifying such that resultant multiplicity < 0 - // TODO: This should immediately throw exception - EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -2), Base::ValueError); -} - -TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSpline) -{ - // Arrange - auto PeriodicBSpline = createTypicalPeriodicBSpline(); - assert(PeriodicBSpline); - int geoId = getObject()->addGeometry(PeriodicBSpline.get()); - - auto bsp = static_cast(getObject()->getGeometry(geoId)); - int oldKnotsNum = bsp->countKnots(); - int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; - - // Act - // TODO: Increase/decrease knot multiplicity normally - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum); - // This should increment the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot + 1); - // This should still be a periodic spline - EXPECT_TRUE(bsp->isPeriodic()); - // TODO: Expect shape is preserved - - // Act - // TODO: Increase/decrease knot multiplicity normally - getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum); - // This should decrement the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot); - // This should still be a non-periodic spline - EXPECT_TRUE(bsp->isPeriodic()); -} - -TEST_F(SketchObjectTest, testInsertKnotInNonPeriodicBSpline) -{ - // Arrange - auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); - assert(nonPeriodicBSpline); - int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); - - // Act and Assert - // Try inserting knot with zero multiplicity - // zero multiplicity knot should immediately throw exception - EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 0), Base::ValueError); - - // Act and Assert - // Try inserting knot with multiplicity > degree - // This should immediately throw exception - EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 4), Base::ValueError); - - // Act and Assert - // TODO: Try inserting at an existing knot with resultant multiplicity > degree - // TODO: This should immediately throw exception - // FIXME: Not happening. May be ignoring existing values. - // EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 1.0, 3), Base::ValueError); - - auto bsp = static_cast(getObject()->getGeometry(geoId)); - int oldKnotsNum = bsp->countKnots(); - int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; - - // Act - // Add at a general position (where no knot exists) - getObject()->insertBSplineKnot(geoId, 0.5, 1); - // Assert - // This should add to both the knot and multiplicity "vectors" - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); - // This should still be a non-periodic spline - EXPECT_FALSE(bsp->isPeriodic()); - - // Act - // Add a knot at an existing knot - getObject()->insertBSplineKnot(geoId, 1.0, 1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - // (Since we previously added a knot, this means the total is still one more than original) - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); - // This should increment the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[2], oldMultiplicityOfTargetKnot + 1); - // This should still be a non-periodic spline - EXPECT_FALSE(bsp->isPeriodic()); -} - -TEST_F(SketchObjectTest, testInsertKnotInPeriodicBSpline) -{ - // This should also cover as a representative of arc of conic - - // Arrange - auto PeriodicBSpline = createTypicalPeriodicBSpline(); - assert(PeriodicBSpline); - int geoId = getObject()->addGeometry(PeriodicBSpline.get()); - - // Act and Assert - // Try inserting knot with zero multiplicity - // zero multiplicity knot should immediately throw exception - EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 0), Base::ValueError); - - // Act and Assert - // Try inserting knot with multiplicity > degree - // This should immediately throw exception - EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 4), Base::ValueError); - - // Act and Assert - // TODO: Try inserting at an existing knot with resultant multiplicity > degree - // TODO: This should immediately throw exception - - auto bsp = static_cast(getObject()->getGeometry(geoId)); - int oldKnotsNum = bsp->countKnots(); - int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[2]; - - // Act - // Add at a general position (where no knot exists) - getObject()->insertBSplineKnot(geoId, 0.5, 1); - // Assert - // This should add to both the knot and multiplicity "vectors" - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); - // This should still be a periodic spline - EXPECT_TRUE(bsp->isPeriodic()); - - // Act - // Add a knot at an existing knot - getObject()->insertBSplineKnot(geoId, 1.0, 1); - // Assert - // This should not alter the sizes of knot and multiplicity vectors. - bsp = static_cast(getObject()->getGeometry(geoId)); - EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); - // This should increment the multiplicity. - EXPECT_EQ(bsp->getMultiplicities()[3], oldMultiplicityOfTargetKnot + 1); - // This should still be a periodic spline - EXPECT_TRUE(bsp->isPeriodic()); -} - -TEST_F(SketchObjectTest, testJoinCurves) -{ - // Arrange - // Make two curves - Base::Vector3d coordsCenter(0.0, 0.0, 0.0); - double radius = 3.0, startParam = M_PI / 2, endParam = M_PI; - Part::GeomArcOfCircle arcOfCircle; - arcOfCircle.setCenter(coordsCenter); - arcOfCircle.setRadius(radius); - arcOfCircle.setRange(startParam, endParam, true); - int geoId1 = getObject()->addGeometry(&arcOfCircle); - - Base::Vector3d coords1(0.1, 0.0, 0.0); - Base::Vector3d coords2(3.0, 4.0, 0.0); - Part::GeomLineSegment lineSeg; - lineSeg.setPoints(coords1, coords2); - int geoId2 = getObject()->addGeometry(&lineSeg); - - // Act - // Join these curves - getObject()->join(geoId1, Sketcher::PointPos::start, geoId2, Sketcher::PointPos::start); - - // Assert - // Check they are replaced (here it means there is only one curve left after internal - // geometries are removed) - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); -} - -TEST_F(SketchObjectTest, testJoinCurvesWhenTangent) -{ - // Arrange - // Make two curves - Base::Vector3d coordsCenter(0.0, 0.0, 0.0); - double radius = 3.0, startParam = M_PI / 2, endParam = M_PI; - Part::GeomArcOfCircle arcOfCircle; - arcOfCircle.setCenter(coordsCenter); - arcOfCircle.setRadius(radius); - arcOfCircle.setRange(startParam, endParam, true); - int geoId1 = getObject()->addGeometry(&arcOfCircle); - - Base::Vector3d coords1(0.0, 0.0, 0.0); - Base::Vector3d coords2(3.0, 0.0, 0.0); - Part::GeomLineSegment lineSeg; - lineSeg.setPoints(coords1, coords2); - int geoId2 = getObject()->addGeometry(&lineSeg); - - // Add end-to-end tangent between these - auto constraint = new Sketcher::Constraint(); // Ownership will be transferred to the sketch - constraint->Type = Sketcher::ConstraintType::Tangent; - constraint->First = geoId1; - constraint->FirstPos = Sketcher::PointPos::start; - constraint->Second = geoId2; - constraint->SecondPos = Sketcher::PointPos::start; - getObject()->addConstraint(constraint); - - // Act - // Join these curves - getObject()->join(geoId1, Sketcher::PointPos::start, geoId2, Sketcher::PointPos::start, 1); - - // Assert - // Check they are replaced (here it means there is only one curve left after internal - // geometries are removed) - for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { - getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); - } - EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); - // TODO: Check the shape is conserved (how?) - // Check there is no C-0 knot (should be possible for the chosen example) - auto mults = static_cast(getObject()->getGeometry(0)) - ->getMultiplicities(); - EXPECT_TRUE(std::all_of(mults.begin(), mults.end(), [](auto mult) { - return mult >= 1; - })); -} - TEST_F(SketchObjectTest, testReverseAngleConstraintToSupplementaryExpressionNoUnits1) { std::string expr = Sketcher::SketchObject::reverseAngleConstraintExpression("180 - 60"); diff --git a/tests/src/Mod/Sketcher/App/SketchObjectChanges.cpp b/tests/src/Mod/Sketcher/App/SketchObjectChanges.cpp new file mode 100644 index 0000000000..2b0f69aa76 --- /dev/null +++ b/tests/src/Mod/Sketcher/App/SketchObjectChanges.cpp @@ -0,0 +1,920 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include "SketcherTestHelpers.h" + +using namespace SketcherTestHelpers; + +TEST_F(SketchObjectTest, testSplitLineSegment) +{ + // Arrange + Base::Vector3d splitPoint(2.0, 3.1, 0.0); + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + int geoId = getObject()->addGeometry(&lineSeg); + + // Act + int result = getObject()->split(geoId, splitPoint); + + // Assert + EXPECT_EQ(result, 0); + // One additional curve should be added + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); + // TODO: Expect the resultant curves are line segments and shape is conserved + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testSplitCircle) +{ + // Arrange + Base::Vector3d splitPoint(2.0, 3.1, 0.0); + Part::GeomCircle circle; + setupCircle(circle); + int geoId = getObject()->addGeometry(&circle); + + // Act + int result = getObject()->split(geoId, splitPoint); + + // Assert + EXPECT_EQ(result, 0); + // The circle should be split into an arc now + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); +} + +TEST_F(SketchObjectTest, testSplitEllipse) +{ + // Arrange + Base::Vector3d splitPoint(2.0, 3.1, 0.0); + Part::GeomEllipse ellipse; + setupEllipse(ellipse); + int geoId = getObject()->addGeometry(&ellipse); + + // Act + int result = getObject()->split(geoId, splitPoint); + + // Assert + EXPECT_EQ(result, 0); + // TODO: The ellipse should be split into an arc of ellipse now + // FIXME: Internal geometries may be added or removed which may cause some issues + // EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); +} + +TEST_F(SketchObjectTest, testSplitArcOfCircle) +{ + // Arrange + Base::Vector3d splitPoint(-2.0, 3.1, 0.0); + Part::GeomArcOfCircle arcOfCircle; + setupArcOfCircle(arcOfCircle); + int geoId = getObject()->addGeometry(&arcOfCircle); + + // Act + int result = getObject()->split(geoId, splitPoint); + + // Assert + EXPECT_EQ(result, 0); + // The arcOfCircle should be split into an arc now + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); + // Expect the end points and centers of the resultant curve are coincident. + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 2); +} + +TEST_F(SketchObjectTest, testSplitArcOfConic) +{ + // Arrange + // TODO: Define a parabola/hyperbola as reference + Base::Vector3d splitPoint(1.0, -1.1, 0.0); + Part::GeomArcOfParabola arcOfConic; + setupArcOfParabola(arcOfConic); + int geoId = getObject()->addGeometry(&arcOfConic); + + // Act + // TODO: Sample random points from both sides of the split + int result = getObject()->split(geoId, splitPoint); + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + // The arcOfConic should be split into two arcs of the same conic now + EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); + // TODO: Expect the end points of the resultant curve are coincident. + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testSplitNonPeriodicBSpline) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + Base::Vector3d splitPoint(-0.5, 1.1, 0.0); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + // TODO: Put a point on this + + // Act + // TODO: sample before point(s) at a random parameter + int result = getObject()->split(geoId, splitPoint); + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); + // TODO: confirm sampled point(s) is/are at the same place + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testSplitPeriodicBSpline) +{ + // Arrange + auto PeriodicBSpline = createTypicalPeriodicBSpline(); + Base::Vector3d splitPoint(-0.5, 1.1, 0.0); + int geoId = getObject()->addGeometry(PeriodicBSpline.get()); + // TODO: Put a point on this + + // Act + // TODO: sample before point(s) at a random parameter + int result = getObject()->split(geoId, splitPoint); + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); + // TODO: confirm sampled point(s) is/are at the same place +} + +TEST_F(SketchObjectTest, testTrimWithoutIntersection) +{ + // Arrange + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + int geoId = getObject()->addGeometry(&lineSeg); + Base::Vector3d trimPoint(2.0, 3.1, 0.0); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // Once this line segment is trimmed, nothing should remain + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId - 1); +} + +// TODO: There are other combinations of constraints we may want to test with trim. + +TEST_F(SketchObjectTest, testTrimLineSegmentEnd) +{ + // Arrange + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(lineSeg, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(lineSeg, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(&lineSeg); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // TODO: Once this line segment is trimmed, the curve should be "smaller" + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); + // TODO: There should be a "point-on-object" constraint on the intersecting curves + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimLineSegmentMid) +{ + // Arrange + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(lineSeg, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(lineSeg, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(lineSeg, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y - 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y += 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(&lineSeg); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // TODO: Once this line segment is trimmed, there should be two "smaller" curves in its place + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); + // TODO: There should be a "point-on-object" constraint on the intersecting curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); + // TODO: Ensure shape is preserved +} + +TEST_F(SketchObjectTest, testTrimCircleEnd) +{ + // Arrange + Part::GeomCircle circle; + setupCircle(circle); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(circle, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(circle, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(&circle); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // TODO: Once this circle is trimmed, the circle should be deleted. + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId - 1); +} + +TEST_F(SketchObjectTest, testTrimCircleMid) +{ + // Arrange + Part::GeomCircle circle; + setupCircle(circle); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(circle, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(circle, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(circle, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y -= 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(&circle); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // TODO: Once this circle is trimmed, there should be one arc. + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); + // There should be one "coincident" and one "point-on-object" constraint on the intersecting + // curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); + // TODO: Ensure shape is preserved +} + +TEST_F(SketchObjectTest, testTrimArcOfCircleEnd) +{ + // This should also cover as a representative of arc of conic + + // Arrange + Part::GeomArcOfCircle arcOfCircle; + setupArcOfCircle(arcOfCircle); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(arcOfCircle, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(arcOfCircle, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(&arcOfCircle); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId); + // There should be a "point-on-object" constraint on the intersecting curves + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimArcOfCircleMid) +{ + // Arrange + Part::GeomArcOfCircle arcOfCircle; + setupArcOfCircle(arcOfCircle); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(arcOfCircle, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(arcOfCircle, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(arcOfCircle, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y -= 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(&arcOfCircle); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + EXPECT_EQ(getObject()->getHighestCurveIndex(), geoId + 1); + // There should be a "point-on-object" constraint on the intersecting curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + // There should be 2 coincident constraints: one with lineSegCut1 and one between centers of the + // new arcs + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 2); + // TODO: Ensure shape is preserved +} + +TEST_F(SketchObjectTest, testTrimEllipseEnd) +{ + // Arrange + Part::GeomEllipse ellipse; + setupEllipse(ellipse); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(ellipse, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(ellipse, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(&ellipse); + + // Act + int result = getObject()->trim(geoId, trimPoint); + // remove all internal geometry + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + // Once this ellipse is trimmed, the ellipse should be deleted. + // Only remaining: line segment + EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); +} + +TEST_F(SketchObjectTest, testTrimEllipseMid) +{ + // Arrange + Part::GeomEllipse ellipse; + setupEllipse(ellipse); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(ellipse, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(ellipse, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(ellipse, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y -= 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(&ellipse); + // FIXME: Doing this to avoid trimming only until minor/major axes. Should not be needed. + getObject()->deleteUnusedInternalGeometry(geoId); + + // Act + int result = getObject()->trim(geoId, trimPoint); + // remove all internal geometry + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + // Once this ellipse is trimmed, there should be one arc and line segments. + EXPECT_EQ(getObject()->getHighestCurveIndex(), 2); + // There should be one "coincident" and one "point-on-object" constraint on the intersecting + // curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); + // TODO: Ensure shape is preserved +} + +// TODO: Tests for other arcs of conics? + +TEST_F(SketchObjectTest, testTrimPeriodicBSplineEnd) +{ + // Arrange + auto periodicBSpline = createTypicalPeriodicBSpline(); + assert(periodicBSpline); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(*periodicBSpline, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(*periodicBSpline, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(periodicBSpline.get()); + + // Act + int result = getObject()->trim(geoId, trimPoint); + + // Assert + EXPECT_EQ(result, 0); + // FIXME: This will fail because of deleted internal geometry + // Once this periodicBSpline is trimmed, the periodicBSpline should be deleted, leaving only the + // line segment. + EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); + // TODO: There should be a "point-on-object" constraint on the intersecting curves +} + +TEST_F(SketchObjectTest, testTrimPeriodicBSplineMid) +{ + // Arrange + auto periodicBSpline = createTypicalPeriodicBSpline(); + assert(periodicBSpline); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(*periodicBSpline, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(*periodicBSpline, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(*periodicBSpline, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y -= 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(periodicBSpline.get()); + + // Act + int result = getObject()->trim(geoId, trimPoint); + // remove all internal geometry + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + // Only remaining: Two line segments and the B-spline + EXPECT_EQ(getObject()->getHighestCurveIndex(), 2); + // There should be one "coincident" and one "point-on-object" constraint on the intersecting + // curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); + // TODO: Ensure shape is preserved +} + +TEST_F(SketchObjectTest, testTrimNonPeriodicBSplineEnd) +{ + // This should also cover as a representative of arc of conic + + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + // create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.2)); + Base::Vector3d p1(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.5)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + + // Act + int result = getObject()->trim(geoId, trimPoint); + // remove all internal geometry + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + + // Assert + EXPECT_EQ(result, 0); + // Only remaining: one line segment and the trimmed B-spline + EXPECT_EQ(getObject()->getHighestCurveIndex(), 1); + // FIXME: There should be a "point-on-object" constraint on the intersecting curves + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimNonPeriodicBSplineMid) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.5)); + Base::Vector3d p1(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.3)); + Base::Vector3d p2(p1.x + 0.1, p1.y + 0.1, p1.z); + Part::GeomLineSegment lineSegCut1; + lineSegCut1.setPoints(p1, p2); + getObject()->addGeometry(&lineSegCut1); + Base::Vector3d p3(getPointAtNormalizedParameter(*nonPeriodicBSpline, 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + // to ensure that this line clearly intersects the curve, not just have a point on object + // without explicit constraint + p3.x -= 0.1; + p3.y -= 0.1; + Part::GeomLineSegment lineSegCut2; + lineSegCut2.setPoints(p3, p4); + getObject()->addGeometry(&lineSegCut2); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + + // Act + int result = getObject()->trim(geoId, trimPoint); + // remove all internal geometry + for (int i = 0; i < getObject()->getHighestCurveIndex(); ++i) { + if (getObject()->getGeometry(i)->is()) { + getObject()->deleteUnusedInternalGeometry(i); + } + } + + // Assert + EXPECT_EQ(result, 0); + // Only remaining: one line segment and the trimmed B-spline + EXPECT_EQ(getObject()->getHighestCurveIndex(), 3); + // There should be a "point-on-object" constraint on the intersecting curves + int numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); + int numberOfCoincidentConstraints = countConstraintsOfType(getObject(), Sketcher::Coincident); + EXPECT_EQ(numberOfCoincidentConstraints, 1); + // TODO: Ensure shape is preserved +} + +TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSplineToZero) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + auto bsp1 = static_cast(getObject()->getGeometry(geoId)); + int oldKnotCount = bsp1->countKnots(); + + // Act + // Try decreasing mult to zero. + // NOTE: we still use OCCT notation of knot index starting with 1 (not 0). + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); + // Assert + // Knot should disappear. We start with 3 (unique) knots, so expect 2. + auto bsp2 = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp2->countKnots(), oldKnotCount - 1); +} + +TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSplineToDisallowed) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + + // Act and Assert + // TODO: Try modifying such that resultant multiplicity > degree + // TODO: This should immediately throw exception + EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 3), Base::ValueError); + // TODO: Try modifying such that resultant multiplicity < 0 + // TODO: This should immediately throw exception + EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -2), Base::ValueError); +} + +TEST_F(SketchObjectTest, testModifyKnotMultInNonPeriodicBSpline) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + + auto bsp = static_cast(getObject()->getGeometry(geoId)); + int oldKnotsNum = bsp->countKnots(); + int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; + + // Act + // TODO: Increase/decrease knot multiplicity normally + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum); + // This should increment the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot + 1); + // This should still be a non-periodic spline + EXPECT_FALSE(bsp->isPeriodic()); + // TODO: Expect shape is preserved + + // Act + // TODO: Increase/decrease knot multiplicity normally + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum); + // This should increment the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot); + // This should still be a non-periodic spline + EXPECT_FALSE(bsp->isPeriodic()); +} + +TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSplineToZero) +{ + // Arrange + auto PeriodicBSpline = createTypicalPeriodicBSpline(); + assert(PeriodicBSpline); + int geoId = getObject()->addGeometry(PeriodicBSpline.get()); + auto bsp1 = static_cast(getObject()->getGeometry(geoId)); + int oldKnotCount = bsp1->countKnots(); + + // Act + // Try decreasing mult to zero. + // NOTE: we still use OCCT notation of knot index starting with 1 (not 0). + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); + // Assert + // Knot should disappear. + auto bsp2 = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp2->countKnots(), oldKnotCount - 1); +} + +TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSplineToDisallowed) +{ + // Arrange + auto PeriodicBSpline = createTypicalPeriodicBSpline(); + assert(PeriodicBSpline); + int geoId = getObject()->addGeometry(PeriodicBSpline.get()); + + // Act and Assert + // TODO: Try modifying such that resultant multiplicity > degree + // TODO: This should immediately throw exception + EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 3), Base::ValueError); + // TODO: Try modifying such that resultant multiplicity < 0 + // TODO: This should immediately throw exception + EXPECT_THROW(getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -2), Base::ValueError); +} + +TEST_F(SketchObjectTest, testModifyKnotMultInPeriodicBSpline) +{ + // Arrange + auto PeriodicBSpline = createTypicalPeriodicBSpline(); + assert(PeriodicBSpline); + int geoId = getObject()->addGeometry(PeriodicBSpline.get()); + + auto bsp = static_cast(getObject()->getGeometry(geoId)); + int oldKnotsNum = bsp->countKnots(); + int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; + + // Act + // TODO: Increase/decrease knot multiplicity normally + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, 1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum); + // This should increment the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot + 1); + // This should still be a periodic spline + EXPECT_TRUE(bsp->isPeriodic()); + // TODO: Expect shape is preserved + + // Act + // TODO: Increase/decrease knot multiplicity normally + getObject()->modifyBSplineKnotMultiplicity(geoId, 2, -1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum); + // This should decrement the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[1], oldMultiplicityOfTargetKnot); + // This should still be a non-periodic spline + EXPECT_TRUE(bsp->isPeriodic()); +} + +TEST_F(SketchObjectTest, testInsertKnotInNonPeriodicBSpline) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + int geoId = getObject()->addGeometry(nonPeriodicBSpline.get()); + + // Act and Assert + // Try inserting knot with zero multiplicity + // zero multiplicity knot should immediately throw exception + EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 0), Base::ValueError); + + // Act and Assert + // Try inserting knot with multiplicity > degree + // This should immediately throw exception + EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 4), Base::ValueError); + + // Act and Assert + // TODO: Try inserting at an existing knot with resultant multiplicity > degree + // TODO: This should immediately throw exception + // FIXME: Not happening. May be ignoring existing values. + // EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 1.0, 3), Base::ValueError); + + auto bsp = static_cast(getObject()->getGeometry(geoId)); + int oldKnotsNum = bsp->countKnots(); + int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[1]; + + // Act + // Add at a general position (where no knot exists) + getObject()->insertBSplineKnot(geoId, 0.5, 1); + // Assert + // This should add to both the knot and multiplicity "vectors" + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); + // This should still be a non-periodic spline + EXPECT_FALSE(bsp->isPeriodic()); + + // Act + // Add a knot at an existing knot + getObject()->insertBSplineKnot(geoId, 1.0, 1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + // (Since we previously added a knot, this means the total is still one more than original) + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); + // This should increment the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[2], oldMultiplicityOfTargetKnot + 1); + // This should still be a non-periodic spline + EXPECT_FALSE(bsp->isPeriodic()); +} + +TEST_F(SketchObjectTest, testInsertKnotInPeriodicBSpline) +{ + // This should also cover as a representative of arc of conic + + // Arrange + auto PeriodicBSpline = createTypicalPeriodicBSpline(); + assert(PeriodicBSpline); + int geoId = getObject()->addGeometry(PeriodicBSpline.get()); + + // Act and Assert + // Try inserting knot with zero multiplicity + // zero multiplicity knot should immediately throw exception + EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 0), Base::ValueError); + + // Act and Assert + // Try inserting knot with multiplicity > degree + // This should immediately throw exception + EXPECT_THROW(getObject()->insertBSplineKnot(geoId, 0.5, 4), Base::ValueError); + + // Act and Assert + // TODO: Try inserting at an existing knot with resultant multiplicity > degree + // TODO: This should immediately throw exception + + auto bsp = static_cast(getObject()->getGeometry(geoId)); + int oldKnotsNum = bsp->countKnots(); + int oldMultiplicityOfTargetKnot = bsp->getMultiplicities()[2]; + + // Act + // Add at a general position (where no knot exists) + getObject()->insertBSplineKnot(geoId, 0.5, 1); + // Assert + // This should add to both the knot and multiplicity "vectors" + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); + // This should still be a periodic spline + EXPECT_TRUE(bsp->isPeriodic()); + + // Act + // Add a knot at an existing knot + getObject()->insertBSplineKnot(geoId, 1.0, 1); + // Assert + // This should not alter the sizes of knot and multiplicity vectors. + bsp = static_cast(getObject()->getGeometry(geoId)); + EXPECT_EQ(bsp->countKnots(), oldKnotsNum + 1); + // This should increment the multiplicity. + EXPECT_EQ(bsp->getMultiplicities()[3], oldMultiplicityOfTargetKnot + 1); + // This should still be a periodic spline + EXPECT_TRUE(bsp->isPeriodic()); +} + +TEST_F(SketchObjectTest, testJoinCurves) +{ + // Arrange + // Make two curves + Base::Vector3d coordsCenter(0.0, 0.0, 0.0); + double radius = 3.0, startParam = M_PI / 2, endParam = M_PI; + Part::GeomArcOfCircle arcOfCircle; + arcOfCircle.setCenter(coordsCenter); + arcOfCircle.setRadius(radius); + arcOfCircle.setRange(startParam, endParam, true); + int geoId1 = getObject()->addGeometry(&arcOfCircle); + + Base::Vector3d coords1(0.1, 0.0, 0.0); + Base::Vector3d coords2(3.0, 4.0, 0.0); + Part::GeomLineSegment lineSeg; + lineSeg.setPoints(coords1, coords2); + int geoId2 = getObject()->addGeometry(&lineSeg); + + // Act + // Join these curves + getObject()->join(geoId1, Sketcher::PointPos::start, geoId2, Sketcher::PointPos::start); + + // Assert + // Check they are replaced (here it means there is only one curve left after internal + // geometries are removed) + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); +} + +TEST_F(SketchObjectTest, testJoinCurvesWhenTangent) +{ + // Arrange + // Make two curves + Base::Vector3d coordsCenter(0.0, 0.0, 0.0); + double radius = 3.0, startParam = M_PI / 2, endParam = M_PI; + Part::GeomArcOfCircle arcOfCircle; + arcOfCircle.setCenter(coordsCenter); + arcOfCircle.setRadius(radius); + arcOfCircle.setRange(startParam, endParam, true); + int geoId1 = getObject()->addGeometry(&arcOfCircle); + + Base::Vector3d coords1(0.0, 0.0, 0.0); + Base::Vector3d coords2(3.0, 0.0, 0.0); + Part::GeomLineSegment lineSeg; + lineSeg.setPoints(coords1, coords2); + int geoId2 = getObject()->addGeometry(&lineSeg); + + // Add end-to-end tangent between these + auto constraint = new Sketcher::Constraint(); // Ownership will be transferred to the sketch + constraint->Type = Sketcher::ConstraintType::Tangent; + constraint->First = geoId1; + constraint->FirstPos = Sketcher::PointPos::start; + constraint->Second = geoId2; + constraint->SecondPos = Sketcher::PointPos::start; + getObject()->addConstraint(constraint); + + // Act + // Join these curves + getObject()->join(geoId1, Sketcher::PointPos::start, geoId2, Sketcher::PointPos::start, 1); + + // Assert + // Check they are replaced (here it means there is only one curve left after internal + // geometries are removed) + for (int iterGeoId = 0; iterGeoId < getObject()->getHighestCurveIndex(); ++iterGeoId) { + getObject()->deleteUnusedInternalGeometryAndUpdateGeoId(iterGeoId); + } + EXPECT_EQ(getObject()->getHighestCurveIndex(), 0); + // TODO: Check the shape is conserved (how?) + // Check there is no C-0 knot (should be possible for the chosen example) + auto mults = static_cast(getObject()->getGeometry(0)) + ->getMultiplicities(); + EXPECT_TRUE(std::all_of(mults.begin(), mults.end(), [](auto mult) { + return mult >= 1; + })); +} diff --git a/tests/src/Mod/Sketcher/App/SketcherTestHelpers.cpp b/tests/src/Mod/Sketcher/App/SketcherTestHelpers.cpp new file mode 100644 index 0000000000..ae4fb2414e --- /dev/null +++ b/tests/src/Mod/Sketcher/App/SketcherTestHelpers.cpp @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include +#include +#include +#include +#include +#include +#include "SketcherTestHelpers.h" + +void SketchObjectTest::SetUpTestSuite() +{ + tests::initApplication(); +} + +void SketchObjectTest::SetUp() +{ + _docName = App::GetApplication().getUniqueDocumentName("test"); + auto _doc = App::GetApplication().newDocument(_docName.c_str(), "testUser"); + // TODO: Do we add a body newName, or is just adding sketch sufficient for this test? + _sketchobj = static_cast(_doc->addObject("Sketcher::SketchObject")); +} + +void SketchObjectTest::TearDown() +{ + App::GetApplication().closeDocument(_docName.c_str()); +} + +Sketcher::SketchObject* SketchObjectTest::getObject() +{ + return _sketchobj; +} + +namespace SketcherTestHelpers +{ + +using namespace Sketcher; + +void setupLineSegment(Part::GeomLineSegment& lineSeg) +{ + Base::Vector3d coords1(1.0, 2.0, 0.0); + Base::Vector3d coords2(3.0, 4.0, 0.0); + lineSeg.setPoints(coords1, coords2); +} + +void setupCircle(Part::GeomCircle& circle) +{ + Base::Vector3d coordsCenter(1.0, 2.0, 0.0); + Base::Vector3d splitPoint(2.0, 3.1, 0.0); + double radius = 3.0; + circle.setCenter(coordsCenter); + circle.setRadius(radius); +} + +void setupArcOfCircle(Part::GeomArcOfCircle& arcOfCircle) +{ + Base::Vector3d coordsCenter(1.0, 2.0, 0.0); + double radius = 3.0; + double startParam = M_PI / 3, endParam = M_PI * 1.5; + arcOfCircle.setCenter(coordsCenter); + arcOfCircle.setRadius(radius); + arcOfCircle.setRange(startParam, endParam, true); +} + +void setupEllipse(Part::GeomEllipse& ellipse) +{ + Base::Vector3d coordsCenter(1.0, 2.0, 0.0); + double majorRadius = 4.0; + double minorRadius = 3.0; + ellipse.setCenter(coordsCenter); + ellipse.setMajorRadius(majorRadius); + ellipse.setMinorRadius(minorRadius); +} + +void setupArcOfHyperbola(Part::GeomArcOfHyperbola& arcOfHyperbola) +{ + Base::Vector3d coordsCenter(1.0, 2.0, 0.0); + double majorRadius = 4.0; + double minorRadius = 3.0; + double startParam = M_PI / 3, endParam = M_PI * 1.5; + arcOfHyperbola.setCenter(coordsCenter); + arcOfHyperbola.setMajorRadius(majorRadius); + arcOfHyperbola.setMinorRadius(minorRadius); + arcOfHyperbola.setRange(startParam, endParam, true); +} + +void setupArcOfParabola(Part::GeomArcOfParabola& aop) +{ + Base::Vector3d coordsCenter(1.0, 2.0, 0.0); + double focal = 3.0; + double startParam = -M_PI * 1.5, endParam = M_PI * 1.5; + aop.setCenter(coordsCenter); + aop.setFocal(focal); + aop.setRange(startParam, endParam, true); +} + +std::unique_ptr createTypicalNonPeriodicBSpline() +{ + int degree = 3; + std::vector poles; + poles.emplace_back(1, 0, 0); + poles.emplace_back(1, 1, 0); + poles.emplace_back(1, 0.5, 0); + poles.emplace_back(0, 1, 0); + poles.emplace_back(0, 0, 0); + std::vector weights(5, 1.0); + std::vector knotsNonPeriodic = {0.0, 1.0, 2.0}; + std::vector multiplicitiesNonPeriodic = {degree + 1, 1, degree + 1}; + return std::make_unique(poles, + weights, + knotsNonPeriodic, + multiplicitiesNonPeriodic, + degree, + false); +} + +std::unique_ptr createTypicalPeriodicBSpline() +{ + int degree = 3; + std::vector poles; + poles.emplace_back(1, 0, 0); + poles.emplace_back(1, 1, 0); + poles.emplace_back(1, 0.5, 0); + poles.emplace_back(0, 1, 0); + poles.emplace_back(0, 0, 0); + std::vector weights(5, 1.0); + std::vector knotsPeriodic = {0.0, 0.3, 1.0, 1.5, 1.8, 2.0}; + std::vector multiplicitiesPeriodic(6, 1); + return std::make_unique(poles, + weights, + knotsPeriodic, + multiplicitiesPeriodic, + degree, + true); +} + +int countConstraintsOfType(const Sketcher::SketchObject* obj, const Sketcher::ConstraintType cType) +{ + const std::vector& constraints = obj->Constraints.getValues(); + + int result = std::count_if(constraints.begin(), + constraints.end(), + [&cType](const Sketcher::Constraint* constr) { + return constr->Type == cType; + }); + + return result; +} + +Base::Vector3d getPointAtNormalizedParameter(const Part::GeomCurve& curve, double param) +{ + return curve.pointAtParameter(curve.getFirstParameter() + + (curve.getLastParameter() - curve.getFirstParameter()) * param); +} +} // namespace SketcherTestHelpers diff --git a/tests/src/Mod/Sketcher/App/SketcherTestHelpers.h b/tests/src/Mod/Sketcher/App/SketcherTestHelpers.h index e03655e628..5047a3962a 100644 --- a/tests/src/Mod/Sketcher/App/SketcherTestHelpers.h +++ b/tests/src/Mod/Sketcher/App/SketcherTestHelpers.h @@ -1,5 +1,10 @@ // SPDX-License-Identifier: LGPL-2.1-or-later +#include + +#include + + #include #include #include @@ -8,128 +13,51 @@ #include #include +class SketchObjectTest: public ::testing::Test +{ +protected: + static void SetUpTestSuite(); + void SetUp() override; + void TearDown() override; + Sketcher::SketchObject* getObject(); + +private: + // TODO: use shared_ptr or something else here? + Sketcher::SketchObject* _sketchobj; + std::string _docName; + std::vector allowedTypes {"Vertex", + "Edge", + "ExternalEdge", + "H_Axis", + "V_Axis", + "RootPoint"}; +}; + namespace SketcherTestHelpers { using namespace Sketcher; -void setupLineSegment(Part::GeomLineSegment& lineSeg) -{ - Base::Vector3d coords1(1.0, 2.0, 0.0); - Base::Vector3d coords2(3.0, 4.0, 0.0); - lineSeg.setPoints(coords1, coords2); -} +void setupLineSegment(Part::GeomLineSegment& lineSeg); -void setupCircle(Part::GeomCircle& circle) -{ - Base::Vector3d coordsCenter(1.0, 2.0, 0.0); - Base::Vector3d splitPoint(2.0, 3.1, 0.0); - double radius = 3.0; - circle.setCenter(coordsCenter); - circle.setRadius(radius); -} +void setupCircle(Part::GeomCircle& circle); -void setupArcOfCircle(Part::GeomArcOfCircle& arcOfCircle) -{ - Base::Vector3d coordsCenter(1.0, 2.0, 0.0); - double radius = 3.0; - double startParam = M_PI / 3, endParam = M_PI * 1.5; - arcOfCircle.setCenter(coordsCenter); - arcOfCircle.setRadius(radius); - arcOfCircle.setRange(startParam, endParam, true); -} +void setupArcOfCircle(Part::GeomArcOfCircle& arcOfCircle); -void setupEllipse(Part::GeomEllipse& ellipse) -{ - Base::Vector3d coordsCenter(1.0, 2.0, 0.0); - double majorRadius = 4.0; - double minorRadius = 3.0; - ellipse.setCenter(coordsCenter); - ellipse.setMajorRadius(majorRadius); - ellipse.setMinorRadius(minorRadius); -} +void setupEllipse(Part::GeomEllipse& ellipse); -void setupArcOfHyperbola(Part::GeomArcOfHyperbola& arcOfHyperbola) -{ - Base::Vector3d coordsCenter(1.0, 2.0, 0.0); - double majorRadius = 4.0; - double minorRadius = 3.0; - double startParam = M_PI / 3, endParam = M_PI * 1.5; - arcOfHyperbola.setCenter(coordsCenter); - arcOfHyperbola.setMajorRadius(majorRadius); - arcOfHyperbola.setMinorRadius(minorRadius); - arcOfHyperbola.setRange(startParam, endParam, true); -} +void setupArcOfHyperbola(Part::GeomArcOfHyperbola& arcOfHyperbola); -void setupArcOfParabola(Part::GeomArcOfParabola& aop) -{ - Base::Vector3d coordsCenter(1.0, 2.0, 0.0); - double focal = 3.0; - double startParam = -M_PI * 1.5, endParam = M_PI * 1.5; - aop.setCenter(coordsCenter); - aop.setFocal(focal); - aop.setRange(startParam, endParam, true); -} +void setupArcOfParabola(Part::GeomArcOfParabola& aop); -std::unique_ptr createTypicalNonPeriodicBSpline() -{ - int degree = 3; - std::vector poles; - poles.emplace_back(1, 0, 0); - poles.emplace_back(1, 1, 0); - poles.emplace_back(1, 0.5, 0); - poles.emplace_back(0, 1, 0); - poles.emplace_back(0, 0, 0); - std::vector weights(5, 1.0); - std::vector knotsNonPeriodic = {0.0, 1.0, 2.0}; - std::vector multiplicitiesNonPeriodic = {degree + 1, 1, degree + 1}; - return std::make_unique(poles, - weights, - knotsNonPeriodic, - multiplicitiesNonPeriodic, - degree, - false); -} +std::unique_ptr createTypicalNonPeriodicBSpline(); -std::unique_ptr createTypicalPeriodicBSpline() -{ - int degree = 3; - std::vector poles; - poles.emplace_back(1, 0, 0); - poles.emplace_back(1, 1, 0); - poles.emplace_back(1, 0.5, 0); - poles.emplace_back(0, 1, 0); - poles.emplace_back(0, 0, 0); - std::vector weights(5, 1.0); - std::vector knotsPeriodic = {0.0, 0.3, 1.0, 1.5, 1.8, 2.0}; - std::vector multiplicitiesPeriodic(6, 1); - return std::make_unique(poles, - weights, - knotsPeriodic, - multiplicitiesPeriodic, - degree, - true); -} +std::unique_ptr createTypicalPeriodicBSpline(); -int countConstraintsOfType(const Sketcher::SketchObject* obj, const Sketcher::ConstraintType cType) -{ - const std::vector& constraints = obj->Constraints.getValues(); - - int result = std::count_if(constraints.begin(), - constraints.end(), - [&cType](const Sketcher::Constraint* constr) { - return constr->Type == cType; - }); - - return result; -} +int countConstraintsOfType(const Sketcher::SketchObject* obj, const Sketcher::ConstraintType cType); // Get point at the parameter after scaling the range to [0, 1]. -Base::Vector3d getPointAtNormalizedParameter(const Part::GeomCurve& curve, double param) -{ - return curve.pointAtParameter(curve.getFirstParameter() - + (curve.getLastParameter() - curve.getFirstParameter()) * param); -} +Base::Vector3d getPointAtNormalizedParameter(const Part::GeomCurve& curve, double param); // TODO: How to set up B-splines here? // It's not straightforward to change everything from a "default" one.