From 75a347490e632f545ce5919005806f0ddfba12a6 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Tue, 18 Jun 2024 18:20:49 +0530 Subject: [PATCH] [Sketcher] Add tests for `split` and `trim` Includes: * Tests for splitting and trimming of various types of curves * Assertions on constraints expected to be added on these curves --- src/Mod/Sketcher/App/SketchObject.cpp | 33 + src/Mod/Sketcher/App/SketchObject.h | 8 + tests/src/Mod/Sketcher/App/SketchObject.cpp | 731 ++++++++++++++++++++ 3 files changed, 772 insertions(+) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 4ea2398388..704d6d76be 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -6967,6 +6967,39 @@ int SketchObject::deleteUnusedInternalGeometry(int GeoId, bool delgeoid) } } +int SketchObject::deleteUnusedInternalGeometryAndUpdateGeoId(int& GeoId, bool delgeoid) +{ + const Part::Geometry* geo = getGeometry(GeoId); + + if (!hasInternalGeometry(geo)) { + return -1; + } + // We need to remove the internal geometry of the BSpline, as BSplines change in number + // of poles and knots We save the tags of the relevant geometry to retrieve the new + // GeoIds later on. + boost::uuids::uuid GeoIdTag; + + GeoIdTag = geo->getTag(); + + int returnValue = deleteUnusedInternalGeometry(GeoId, delgeoid); + + if (delgeoid) { + GeoId = GeoEnum::GeoUndef; + return returnValue; + } + + auto vals = getCompleteGeometry(); + + for (size_t i = 0; i < vals.size(); i++) { + if (vals[i]->getTag() == GeoIdTag) { + GeoId = getGeoIdFromCompleteGeometryIndex(i); + break; + } + } + + return returnValue; +} + bool SketchObject::convertToNURBS(int GeoId) { // no need to check input data validity as this is an sketchobject managed operation. diff --git a/src/Mod/Sketcher/App/SketchObject.h b/src/Mod/Sketcher/App/SketchObject.h index d16dfa3ef6..3a462d5a82 100644 --- a/src/Mod/Sketcher/App/SketchObject.h +++ b/src/Mod/Sketcher/App/SketchObject.h @@ -460,6 +460,14 @@ public: geometry \retval int - returns -1 on error, otherwise the number of deleted elements */ int deleteUnusedInternalGeometry(int GeoId, bool delgeoid = false); + /*! + \brief Same as `deleteUnusedInternalGeometry`, but changes `GeoId` to the new Id of the + geometry, or to `GeoEnum::GeoUndef` if the geometry is deleted as well. \param GeoId - the + geometry having the internal geometry to delete \param delgeoid - if true in addition to the + unused internal geometry also deletes the GeoId geometry \retval int - returns -1 on error, + otherwise the number of deleted elements + */ + int deleteUnusedInternalGeometryAndUpdateGeoId(int& GeoId, bool delgeoid = false); /*! \brief Approximates the given geometry with a B-spline \param GeoId - the geometry to approximate diff --git a/tests/src/Mod/Sketcher/App/SketchObject.cpp b/tests/src/Mod/Sketcher/App/SketchObject.cpp index f3a0cd26af..1e1fcd4a3c 100644 --- a/tests/src/Mod/Sketcher/App/SketchObject.cpp +++ b/tests/src/Mod/Sketcher/App/SketchObject.cpp @@ -12,6 +12,108 @@ #include #include +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 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; +} + +// TODO: How to set up B-splines here? +// It's not straightforward to change everything from a "default" one. + class SketchObjectTest: public ::testing::Test { protected: @@ -453,6 +555,635 @@ TEST_F(SketchObjectTest, testGetPointFromGeomBSplineCurvePeriodic) EXPECT_DOUBLE_EQ(ptStart[1], ptEnd[1]); } +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); +} + +TEST_F(SketchObjectTest, testTrimLineSegmentEnd) +{ + // Arrange + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(lineSeg.pointAtParameter( + lineSeg.getFirstParameter() + + (lineSeg.getLastParameter() - lineSeg.getFirstParameter()) * 0.2)); + Base::Vector3d p1(lineSeg.pointAtParameter( + lineSeg.getFirstParameter() + + (lineSeg.getLastParameter() - lineSeg.getFirstParameter()) * 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 numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimLineSegmentMid) +{ + // Arrange + Part::GeomLineSegment lineSeg; + setupLineSegment(lineSeg); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(lineSeg.pointAtParameter( + lineSeg.getFirstParameter() + + (lineSeg.getLastParameter() - lineSeg.getFirstParameter()) * 0.5)); + Base::Vector3d p1(lineSeg.pointAtParameter( + lineSeg.getFirstParameter() + + (lineSeg.getLastParameter() - lineSeg.getFirstParameter()) * 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(lineSeg.pointAtParameter( + lineSeg.getFirstParameter() + + (lineSeg.getLastParameter() - lineSeg.getFirstParameter()) * 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + 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, 2); + // 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( + circle.pointAtParameter(circle.getFirstParameter() + + (circle.getLastParameter() - circle.getFirstParameter()) * 0.2)); + Base::Vector3d p1( + circle.pointAtParameter(circle.getFirstParameter() + + (circle.getLastParameter() - circle.getFirstParameter()) * 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( + circle.pointAtParameter(circle.getFirstParameter() + + (circle.getLastParameter() - circle.getFirstParameter()) * 0.5)); + Base::Vector3d p1( + circle.pointAtParameter(circle.getFirstParameter() + + (circle.getLastParameter() - circle.getFirstParameter()) * 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( + circle.pointAtParameter(circle.getFirstParameter() + + (circle.getLastParameter() - circle.getFirstParameter()) * 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 + // FIXME: For closed curves, lineSegCut1 creates a coincident constraint but only a + // point-on-object for non-closed curves. Is that by design? + 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(arcOfCircle.pointAtParameter( + arcOfCircle.getFirstParameter() + + (arcOfCircle.getLastParameter() - arcOfCircle.getFirstParameter()) * 0.2)); + Base::Vector3d p1(arcOfCircle.pointAtParameter( + arcOfCircle.getFirstParameter() + + (arcOfCircle.getLastParameter() - arcOfCircle.getFirstParameter()) * 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 numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimArcOfCircleMid) +{ + // Arrange + Part::GeomArcOfCircle arcOfCircle; + setupArcOfCircle(arcOfCircle); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(arcOfCircle.pointAtParameter( + arcOfCircle.getFirstParameter() + + (arcOfCircle.getLastParameter() - arcOfCircle.getFirstParameter()) * 0.5)); + Base::Vector3d p1(arcOfCircle.pointAtParameter( + arcOfCircle.getFirstParameter() + + (arcOfCircle.getLastParameter() - arcOfCircle.getFirstParameter()) * 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(arcOfCircle.pointAtParameter( + arcOfCircle.getFirstParameter() + + (arcOfCircle.getLastParameter() - arcOfCircle.getFirstParameter()) * 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + 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, 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(ellipse.pointAtParameter( + ellipse.getFirstParameter() + + (ellipse.getLastParameter() - ellipse.getFirstParameter()) * 0.2)); + Base::Vector3d p1(ellipse.pointAtParameter( + ellipse.getFirstParameter() + + (ellipse.getLastParameter() - ellipse.getFirstParameter()) * 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(ellipse.pointAtParameter( + ellipse.getFirstParameter() + + (ellipse.getLastParameter() - ellipse.getFirstParameter()) * 0.5)); + Base::Vector3d p1(ellipse.pointAtParameter( + ellipse.getFirstParameter() + + (ellipse.getLastParameter() - ellipse.getFirstParameter()) * 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(ellipse.pointAtParameter( + ellipse.getFirstParameter() + + (ellipse.getLastParameter() - ellipse.getFirstParameter()) * 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(periodicBSpline->pointAtParameter( + periodicBSpline->getFirstParameter() + + (periodicBSpline->getLastParameter() - periodicBSpline->getFirstParameter()) * 0.2)); + Base::Vector3d p1(periodicBSpline->pointAtParameter( + periodicBSpline->getFirstParameter() + + (periodicBSpline->getLastParameter() - periodicBSpline->getFirstParameter()) * 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(periodicBSpline->pointAtParameter( + periodicBSpline->getFirstParameter() + + (periodicBSpline->getLastParameter() - periodicBSpline->getFirstParameter()) * 0.5)); + Base::Vector3d p1(periodicBSpline->pointAtParameter( + periodicBSpline->getFirstParameter() + + (periodicBSpline->getLastParameter() - periodicBSpline->getFirstParameter()) * 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(periodicBSpline->pointAtParameter( + periodicBSpline->getFirstParameter() + + (periodicBSpline->getLastParameter() - periodicBSpline->getFirstParameter()) * 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(nonPeriodicBSpline->pointAtParameter( + nonPeriodicBSpline->getFirstParameter() + + (nonPeriodicBSpline->getLastParameter() - nonPeriodicBSpline->getFirstParameter()) + * 0.2)); + Base::Vector3d p1(nonPeriodicBSpline->pointAtParameter( + nonPeriodicBSpline->getFirstParameter() + + (nonPeriodicBSpline->getLastParameter() - nonPeriodicBSpline->getFirstParameter()) + * 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 numberOfPointOnObjectConstraints = + countConstraintsOfType(getObject(), Sketcher::PointOnObject); + EXPECT_EQ(numberOfPointOnObjectConstraints, 1); +} + +TEST_F(SketchObjectTest, testTrimNonPeriodicBSplineMid) +{ + // Arrange + auto nonPeriodicBSpline = createTypicalNonPeriodicBSpline(); + assert(nonPeriodicBSpline); + // TODO: create curves intersecting at the right spots + Base::Vector3d trimPoint(nonPeriodicBSpline->pointAtParameter( + nonPeriodicBSpline->getFirstParameter() + + (nonPeriodicBSpline->getLastParameter() - nonPeriodicBSpline->getFirstParameter()) + * 0.5)); + Base::Vector3d p1(nonPeriodicBSpline->pointAtParameter( + nonPeriodicBSpline->getFirstParameter() + + (nonPeriodicBSpline->getLastParameter() - nonPeriodicBSpline->getFirstParameter()) + * 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(nonPeriodicBSpline->pointAtParameter( + nonPeriodicBSpline->getFirstParameter() + + (nonPeriodicBSpline->getLastParameter() - nonPeriodicBSpline->getFirstParameter()) + * 0.7)); + Base::Vector3d p4(p3.x + 0.1, p3.y + 0.1, p3.z); + 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, 2); + // TODO: Ensure shape is preserved +} + TEST_F(SketchObjectTest, testReverseAngleConstraintToSupplementaryExpressionNoUnits1) { std::string expr = Sketcher::SketchObject::reverseAngleConstraintExpression("180 - 60");