From e166c8ccfd4bb59768d4846fb93cdb67b0ba03a2 Mon Sep 17 00:00:00 2001 From: bgbsww <120601209+bgbsww@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:25:44 -0500 Subject: [PATCH] Adding additional TNP tests (#11829) * Initial tests for Chamfer, Fillet, Compound * Lint cleanup, new tests * Outline of Extrusion and Revolution * Use python to define a 2d object to extrude and test * Refactor; start filling in revolution tests * Example of parameterized tests in Extrusion, cleanups * Use gtest framework for parameterised tests * Rearrange for clarity * WIP with TEST_P use for posterity * Switch from parameters to individual tests * Guess at test failures on other platforms * Cleanups and Revolution Tests * Remove temp code * Switch Revolutions to boundbox test; add Compound subshape count test * Calculate test volume correctly; lint fixes Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris Hennes --- tests/src/Mod/Part/App/CMakeLists.txt | 10 + tests/src/Mod/Part/App/FeatureChamfer.cpp | 136 ++++++++ tests/src/Mod/Part/App/FeatureCompound.cpp | 59 ++++ tests/src/Mod/Part/App/FeatureExtrusion.cpp | 300 ++++++++++++++++++ tests/src/Mod/Part/App/FeatureFillet.cpp | 159 ++++++++++ tests/src/Mod/Part/App/FeaturePartBoolean.cpp | 33 ++ tests/src/Mod/Part/App/FeatureRevolution.cpp | 192 +++++++++++ tests/src/Mod/Part/App/PartTestHelpers.cpp | 71 ++++- tests/src/Mod/Part/App/PartTestHelpers.h | 18 +- 9 files changed, 971 insertions(+), 7 deletions(-) create mode 100644 tests/src/Mod/Part/App/FeatureChamfer.cpp create mode 100644 tests/src/Mod/Part/App/FeatureCompound.cpp create mode 100644 tests/src/Mod/Part/App/FeatureExtrusion.cpp create mode 100644 tests/src/Mod/Part/App/FeatureFillet.cpp create mode 100644 tests/src/Mod/Part/App/FeaturePartBoolean.cpp create mode 100644 tests/src/Mod/Part/App/FeatureRevolution.cpp diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 35796ab770..1acd3509aa 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -3,6 +3,16 @@ target_sources( Part_tests_run PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureChamfer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureCompound.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureExtrusion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureFillet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartBoolean.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCommon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCut.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartFuse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureRevolution.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/PartTestHelpers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCommon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCut.cpp diff --git a/tests/src/Mod/Part/App/FeatureChamfer.cpp b/tests/src/Mod/Part/App/FeatureChamfer.cpp new file mode 100644 index 0000000000..1f9f5027e6 --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureChamfer.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include + +#include "PartTestHelpers.h" +#include "Mod/Part/App/FeatureChamfer.h" + +class FeatureChamferTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + createTestDoc(); + _boxes[0]->Length.setValue(length); + _boxes[0]->Width.setValue(width); + _boxes[0]->Height.setValue(height); + _boxes[0]->Placement.setValue( + Base::Placement(Base::Vector3d(), Base::Rotation(), Base::Vector3d())); + _boxes[1]->Placement.setValue( + Base::Placement(Base::Vector3d(0, 1, height), Base::Rotation(), Base::Vector3d())); + _boxes[1]->Length.setValue(1); + _boxes[1]->Width.setValue(2); + _boxes[1]->Height.setValue(3); + _fused = dynamic_cast(_doc->addObject("Part::Fuse")); + _fused->Base.setValue(_boxes[0]); + _fused->Tool.setValue(_boxes[1]); + _fused->execute(); + _chamfer = dynamic_cast(_doc->addObject("Part::Chamfer")); + } + + void TearDown() override + {} + + // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes) + const double length = 4.0; + const double width = 5.0; + const double height = 6.0; + const double chamfer = 0.5; + Part::Fuse* _fused = nullptr; + Part::Chamfer* _chamfer = nullptr; + // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes) +}; + +// Unfortunately for these next two tests, there are upstream errors in OCCT +// at least until 7.5.2 that cause some chamfers that intersect each other to +// fail. Until that's fixed, test subsets of the complete chamfer list. + +TEST_F(FeatureChamferTest, testOther) +{ + const double baseVolume = + _boxes[0]->Length.getValue() * _boxes[0]->Width.getValue() * _boxes[0]->Height.getValue() + + _boxes[1]->Length.getValue() * _boxes[1]->Width.getValue() * _boxes[1]->Height.getValue(); + // Arrange + _chamfer->Base.setValue(_fused); + Part::TopoShape ts = _fused->Shape.getValue(); + unsigned long sec = ts.countSubElements("Edge"); + // Assert + EXPECT_EQ(sec, 25); + // Act + _fused->Refine.setValue(true); + _fused->execute(); + ts = _fused->Shape.getValue(); + sec = ts.countSubElements("Edge"); + // Assert + EXPECT_EQ(sec, 24); + // Act + // _chamfer->Edges.setValues(PartTestHelpers::_getFilletEdges({1, 2}, chamfer, chamfer)); + _chamfer->Edges.setValues(PartTestHelpers::_getFilletEdges({1, 2}, chamfer, chamfer)); + double fusedVolume = PartTestHelpers::getVolume(_fused->Shape.getValue()); + double chamferVolume = PartTestHelpers::getVolume(_chamfer->Shape.getValue()); + // Assert + EXPECT_DOUBLE_EQ(fusedVolume, baseVolume); + EXPECT_DOUBLE_EQ(chamferVolume, 0.0); + // Act + _chamfer->execute(); + chamferVolume = PartTestHelpers::getVolume(_chamfer->Shape.getValue()); + double cv = (_boxes[0]->Length.getValue()) * chamfer * chamfer / 2 + + (_boxes[0]->Height.getValue()) * chamfer * chamfer / 2 - chamfer * chamfer * chamfer / 3; + // Assert + EXPECT_FLOAT_EQ(chamferVolume, baseVolume - cv); +} + +TEST_F(FeatureChamferTest, testMost) +{ + // Arrange + _fused->Refine.setValue(true); + _fused->execute(); + _chamfer->Base.setValue(_fused); + _chamfer->Edges.setValues(PartTestHelpers::_getFilletEdges( + {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // NOLINT magic number + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, // NOLINT magic number + 0.4, // NOLINT magic number + 0.4)); // NOLINT magic number + // Act + _chamfer->execute(); + double chamferVolume = PartTestHelpers::getVolume(_chamfer->Shape.getValue()); + // Assert + EXPECT_FLOAT_EQ(chamferVolume, 121.46667); // This is calculable, but painful. +} + +// Worth noting that FeaturePartCommon with insufficient parameters says MustExecute false, +// but FeatureChamfer says MustExecute true. Not a condition that should ever really be hit. + +TEST_F(FeatureChamferTest, testMustExecute) +{ + // Assert + EXPECT_TRUE(_chamfer->mustExecute()); + // Act + _chamfer->Base.setValue(_boxes[0]); + // Assert + EXPECT_TRUE(_chamfer->mustExecute()); + // Act + _chamfer->Edges.setValues(PartTestHelpers::_getFilletEdges({1}, chamfer, chamfer)); + // Assert + EXPECT_TRUE(_chamfer->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_chamfer->mustExecute()); +} + +TEST_F(FeatureChamferTest, testGetProviderName) +{ + // Act + _chamfer->execute(); + const char* name = _chamfer->getViewProviderName(); + // Assert + EXPECT_STREQ(name, "PartGui::ViewProviderChamfer"); +} diff --git a/tests/src/Mod/Part/App/FeatureCompound.cpp b/tests/src/Mod/Part/App/FeatureCompound.cpp new file mode 100644 index 0000000000..3a353e5217 --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureCompound.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include "Mod/Part/App/FeatureCompound.h" +#include + +#include "PartTestHelpers.h" + +class FeatureCompoundTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + + void SetUp() override + { + createTestDoc(); + _compound = dynamic_cast(_doc->addObject("Part::Compound")); + } + + void TearDown() override + {} + + Part::Compound* _compound = nullptr; // NOLINT Can't be private in a test framework +}; + +TEST_F(FeatureCompoundTest, testIntersecting) +{ + // Arrange + _compound->Links.setValues({_boxes[0], _boxes[1]}); + // Act + _compound->execute(); + Part::TopoShape ts = _compound->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_DOUBLE_EQ(volume, 12.0); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 3.0, 3.0))); + EXPECT_EQ(ts.countSubShapes(TopAbs_SHAPE), 2); +} + +TEST_F(FeatureCompoundTest, testNonIntersecting) +{ + // Arrange + _compound->Links.setValues({_boxes[0], _boxes[2]}); + // Act + _compound->execute(); + Part::TopoShape ts = _compound->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_DOUBLE_EQ(volume, 12.0); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 5.0, 3.0))); + EXPECT_EQ(ts.countSubShapes(TopAbs_SHAPE), 2); +} diff --git a/tests/src/Mod/Part/App/FeatureExtrusion.cpp b/tests/src/Mod/Part/App/FeatureExtrusion.cpp new file mode 100644 index 0000000000..92fcb4909d --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureExtrusion.cpp @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include +#include "Mod/Part/App/FeatureExtrusion.h" +#include + +#include "BRepBuilderAPI_MakeEdge.hxx" + +#include "PartTestHelpers.h" + +class FeatureExtrusionTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + + void SetUp() override + { + createTestDoc(); + _extrusion = dynamic_cast(_doc->addObject("Part::Extrusion")); + PartTestHelpers::rectangle(len, wid, "Rect1"); + _extrusion->Base.setValue(_doc->getObjects().back()); + _extrusion->LengthFwd.setValue(ext1); + } + + void TearDown() override + {} + + // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes) + Part::Extrusion* _extrusion = nullptr; + // Arbtitrary constants for testing. Named here for clarity. + const double len = 3.0; + const double wid = 4.0; + const double ext1 = 10.0; + // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes) +}; + +TEST_F(FeatureExtrusionTest, testMustExecute) +{ + // Assert + EXPECT_TRUE(_extrusion->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_extrusion->mustExecute()); + // Act + _extrusion->Base.setValue(_extrusion->Base.getValue()); + // Assert + EXPECT_TRUE(_extrusion->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_extrusion->mustExecute()); + // Act + _extrusion->Solid.setValue(Standard_True); + // Assert + EXPECT_TRUE(_extrusion->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_extrusion->mustExecute()); +} + +TEST_F(FeatureExtrusionTest, testGetProviderName) +{ + // Act + _extrusion->execute(); + const char* name = _extrusion->getViewProviderName(); + // Assert + EXPECT_STREQ(name, "PartGui::ViewProviderExtrusion"); +} + +// Not clear if there is test value in this one. + +TEST_F(FeatureExtrusionTest, testFetchAxisLink) +{ + // static bool fetchAxisLink(const App::PropertyLinkSub& axisLink, + // Base::Vector3d& basepoint, + // Base::Vector3d& dir); +} + +// Filling in these next two tests seems very redundant, since they are used in execute() +// and thus tested by the results there. In the event that ever went funny, then maybe +// implementation here would make sense. + +TEST_F(FeatureExtrusionTest, testExtrudeShape) +{ + // static TopoShape extrudeShape(const TopoShape& source, const ExtrusionParameters& params); +} + +TEST_F(FeatureExtrusionTest, testComputeFinalParameters) +{ + // ExtrusionParameters computeFinalParameters(); +} + +TEST_F(FeatureExtrusionTest, testExecuteSimple) +{ + // Arrange + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, 0, len, wid, ext1))); +} + +TEST_F(FeatureExtrusionTest, testExecuteSimpleRev) +{ + const double ext2 = 9; + // Arrange + _extrusion->LengthFwd.setValue(0); + _extrusion->LengthRev.setValue(ext2); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext2); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, -ext2, len, wid, 0))); +} + +TEST_F(FeatureExtrusionTest, testExecuteSolid) +{ + // Arrange + _extrusion->Solid.setValue(true); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, 0, len, wid, ext1))); +} + +TEST_F(FeatureExtrusionTest, testExecuteReverse) +{ + // Arrange + _extrusion->Reversed.setValue(true); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, -ext1, len, wid, 0))); +} + +TEST_F(FeatureExtrusionTest, testExecuteSymmetric) +{ + // Arrange + _extrusion->Symmetric.setValue(true); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1); + EXPECT_TRUE( + PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, -ext1 / 2, len, wid, ext1 / 2))); +} + +TEST_F(FeatureExtrusionTest, testExecuteAngled) +{ + // Arrange + const double ang = 30; + const double tangent = tan(ang / 180.0 * M_PI); + + // The shape is a truncated pyramid elongated by a truncated triangular prism in the middle. + // Calc the volume of full size pyramid and prism, and subtract top volumes to truncate. + const double shorterSide = len > wid ? wid : len; + const double longerSide = len < wid ? wid : len; + const double centerWidth = longerSide - shorterSide; // Width of the triang prism. + const double topHeight = shorterSide / tangent / 2; // Height of the truncation + const double fullHeight = ext1 + topHeight; + const double fullPrismVol = + fullHeight * (shorterSide + ext1 * tangent * 2.0) / 2.0 * centerWidth; + const double fullPyrVol = pow(shorterSide + ext1 * tangent * 2.0, 2.0) / 3.0 * fullHeight; + const double topPrismVol = topHeight * shorterSide / 2.0 * centerWidth; + const double topPyrVol = pow(shorterSide, 2.0) / 3.0 * topHeight; + const double targetVol = (fullPyrVol + fullPrismVol) - (topPyrVol + topPrismVol); + _extrusion->Solid.setValue(true); + _extrusion->TaperAngle.setValue(ang); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, targetVol); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, + Base::BoundBox3d(-ext1 * tangent, + -ext1 * tangent, + 0, + len + ext1 * tangent, + wid + ext1 * tangent, + ext1))); +} + +TEST_F(FeatureExtrusionTest, testExecuteAngledRev) +{ + // Arrange + const double ang = 30; + const double tangent = tan(ang / 180.0 * M_PI); + // The shape is a truncated pyramid elongated by a truncated triangular prism in the middle, + // plus a rectangular prism. + // Calc the volume of full size pyramid and prism, and subtract top volumes to truncate. + const double shorterSide = len > wid ? wid : len; + const double longerSide = len < wid ? wid : len; + const double centerWidth = longerSide - shorterSide; // Width of the triang prism. + const double topHeight = shorterSide / tangent / 2; // Height of the truncation + const double fullHeight = ext1 / 2 + topHeight; + const double fullPrismVol = + fullHeight * (shorterSide + ext1 / 2 * tangent * 2.0) / 2.0 * centerWidth; + const double fullPyrVol = pow(shorterSide + ext1 / 2 * tangent * 2.0, 2.0) / 3.0 * fullHeight; + const double topPrismVol = topHeight * shorterSide / 2.0 * centerWidth; + const double topPyrVol = pow(shorterSide, 2.0) / 3.0 * topHeight; + const double targetVol = + (fullPyrVol + fullPrismVol) - (topPyrVol + topPrismVol) + len * wid * ext1 / 2; + + _extrusion->Solid.setValue(true); + _extrusion->Symmetric.setValue(true); + _extrusion->TaperAngleRev.setValue(ang); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, targetVol); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, + Base::BoundBox3d(-ext1 * tangent / 2, + -ext1 * tangent / 2, + -ext1 / 2, + len + ext1 * tangent / 2, + wid + ext1 * tangent / 2, + ext1 / 2))); +} + +TEST_F(FeatureExtrusionTest, testExecuteEdge) +{ + // Arrange + const double ang = 30; + const double tangent = tan(ang / 180.0 * M_PI); + BRepBuilderAPI_MakeEdge e1(gp_Pnt(0, 0, 0), gp_Pnt(ext1, ext1, ext1)); + auto edge = dynamic_cast(_doc->addObject("Part::Feature", "Edge")); + edge->Shape.setValue(e1); + _extrusion->DirLink.setValue(edge); + _extrusion->DirMode.setValue(1); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1 * tangent); + EXPECT_TRUE(PartTestHelpers::boxesMatch( + bb, + Base::BoundBox3d(0, 0, 0, len + ext1 * tangent, wid + ext1 * tangent, ext1 * tangent))); +} + +TEST_F(FeatureExtrusionTest, testExecuteDir) +{ + // Arrange + const double sin45 = sin(45 / 180.0 * M_PI); + _extrusion->Dir.setValue(Base::Vector3d(0, 1, 1)); + _extrusion->DirMode.setValue((long)0); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1 * sin45); + EXPECT_TRUE(PartTestHelpers::boxesMatch( + bb, + Base::BoundBox3d(0, 0, 0, len, wid + ext1 * sin45, ext1 * sin45))); +} + +TEST_F(FeatureExtrusionTest, testExecuteFaceMaker) +{ + // Arrange + _extrusion->FaceMakerClass.setValue("Part::FaceMakerCheese"); + // Act + _extrusion->execute(); + Part::TopoShape ts = _extrusion->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, len * wid * ext1); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, 0, len, wid, ext1))); +} diff --git a/tests/src/Mod/Part/App/FeatureFillet.cpp b/tests/src/Mod/Part/App/FeatureFillet.cpp new file mode 100644 index 0000000000..4138bd6f9e --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureFillet.cpp @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include + +#include "PartTestHelpers.h" + +// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) +class FeatureFilletTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + createTestDoc(); + _boxes[0]->Length.setValue(4); + _boxes[0]->Width.setValue(5); + _boxes[0]->Height.setValue(6); + _boxes[0]->Placement.setValue( + Base::Placement(Base::Vector3d(), Base::Rotation(), Base::Vector3d())); + _boxes[1]->Placement.setValue( + Base::Placement(Base::Vector3d(0, 1, 6), Base::Rotation(), Base::Vector3d())); + _boxes[1]->Length.setValue(1); + _boxes[1]->Width.setValue(2); + _boxes[1]->Height.setValue(3); + _fused = dynamic_cast(_doc->addObject("Part::Fuse")); + _fused->Base.setValue(_boxes[0]); + _fused->Tool.setValue(_boxes[1]); + _fused->execute(); + _fillet = dynamic_cast(_doc->addObject("Part::Fillet")); + } + + void TearDown() override + {} + + Part::Fuse* _fused = nullptr; // NOLINT Can't be private in a test framework + Part::Fillet* _fillet = nullptr; // NOLINT Can't be private in a test framework +}; + +// Unfortunately for these next two tests, there are upstream errors in OCCT +// at least until 7.5.2 that cause some fillets that intersect each other to +// fail. Until that's fixed, test subsets of the complete fillet list. + +TEST_F(FeatureFilletTest, testOtherEdges) +{ + const double baseVolume = + _boxes[0]->Length.getValue() * _boxes[0]->Width.getValue() * _boxes[0]->Height.getValue() + + _boxes[1]->Length.getValue() * _boxes[1]->Width.getValue() * _boxes[1]->Height.getValue(); + // Arrange + _fillet->Base.setValue(_fused); + Part::TopoShape ts = _fused->Shape.getValue(); + unsigned long sec = ts.countSubElements("Edge"); + // Assert + EXPECT_EQ(sec, 25); + // Act + _fused->Refine.setValue(true); + _fused->execute(); + ts = _fused->Shape.getValue(); + sec = ts.countSubElements("Edge"); + // Assert + EXPECT_EQ(sec, 24); + + // Act + _fillet->Edges.setValues(PartTestHelpers::_getFilletEdges({15, 17}, 0.5, 0.5)); + double fusedVolume = PartTestHelpers::getVolume(_fused->Shape.getValue()); + double filletVolume = PartTestHelpers::getVolume(_fillet->Shape.getValue()); + // Assert + EXPECT_DOUBLE_EQ(fusedVolume, baseVolume); + EXPECT_DOUBLE_EQ(filletVolume, 0.0); + // Act + _fillet->execute(); + filletVolume = PartTestHelpers::getVolume(_fillet->Shape.getValue()); + // Assert + EXPECT_FLOAT_EQ(filletVolume, 125.57079); +} + +TEST_F(FeatureFilletTest, testMostEdges) +{ + // Arrange + _fused->Refine.setValue(true); + // _fused->execute(); + _fillet->Base.setValue(_fused); + _fillet->Edges.setValues(PartTestHelpers::_getFilletEdges( + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 19, 20, 22, 23, 24}, + 0.4, + 0.4)); + // Act + _fillet->execute(); + double filletVolume = PartTestHelpers::getVolume(_fillet->Shape.getValue()); + // Assert + EXPECT_FLOAT_EQ(filletVolume, 118.38763); +} + +// Worth noting that FeaturePartCommon with insufficient parameters says MustExecute false, +// but FeatureFillet says MustExecute true. Not a condition that should ever really be hit. + +TEST_F(FeatureFilletTest, testMustExecute) +{ + // Assert + EXPECT_TRUE(_fillet->mustExecute()); + // Act + _fillet->Base.setValue(_boxes[0]); + // Assert + EXPECT_TRUE(_fillet->mustExecute()); + // Act + _fillet->Edges.setValues(PartTestHelpers::_getFilletEdges({1}, 0.5, 0.5)); + // Assert + EXPECT_TRUE(_fillet->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_fillet->mustExecute()); +} + +TEST_F(FeatureFilletTest, testGetProviderName) +{ + // Act + _fillet->execute(); + const char* name = _fillet->getViewProviderName(); + // Assert + EXPECT_STREQ(name, "PartGui::ViewProviderFillet"); +} + +// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) + +// void PrintTo(const TopoDS_Shape& ds, std::ostream* os) +// { +// *os << "TopoDS_Shape "; +// for (TopExp_Explorer ex(ds, TopAbs_VERTEX); ex.More(); ex.Next()) { +// gp_Pnt point = BRep_Tool::Pnt(TopoDS::Vertex(ex.Current())); +// *os << "(" << point.X() << "," << point.Y() << "," << point.Z() << ") "; +// } +// *os << std::endl; +// } + +// void edgesAtBusyVertexes(Part::TopoShape ts) +// { +// const char* categories[] = { "Face", "Wire", "Edge", "Vertex" }; + +// for (auto category : categories ) { +// int count = ts.countSubShapes(category); +// for ( int index=1; index <= count; index++ ) { +// std::string name = category + std::to_string(index); +// const char * cname = name.c_str(); +// TopoDS_Shape ss = ts.getSubShape(cname); +// os << cname << ": "; +// for (TopExp_Explorer ex(ss, TopAbs_VERTEX); ex.More(); ex.Next()) { +// gp_Pnt point = BRep_Tool::Pnt(TopoDS::Vertex(ex.Current())); +// os << "(" << point.X() << "," << point.Y() << "," << point.Z() << ") "; +// } +// os << std::endl; +// } +// } +// } diff --git a/tests/src/Mod/Part/App/FeaturePartBoolean.cpp b/tests/src/Mod/Part/App/FeaturePartBoolean.cpp new file mode 100644 index 0000000000..77913e3f03 --- /dev/null +++ b/tests/src/Mod/Part/App/FeaturePartBoolean.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include "Mod/Part/App/FeaturePartBoolean.h" +#include + +#include "PartTestHelpers.h" + +class FeaturePartBooleanTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + + void SetUp() override + { + // createTestDoc(); + // _boolean = dynamic_cast(_doc->addObject("Part::Boolean")); + } + + void TearDown() override + {} + + Part::Boolean* _boolean; // NOLINT Can't be private in a test framework +}; + +// This is completely tested in the FeaturePartCommon, FeaturePartCut, and FeaturePartFuse +// subclasses. This class is unfortunately not usable unless initialized in one of those +// forms, so no testing at this level. diff --git a/tests/src/Mod/Part/App/FeatureRevolution.cpp b/tests/src/Mod/Part/App/FeatureRevolution.cpp new file mode 100644 index 0000000000..7655cca68d --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureRevolution.cpp @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include "Mod/Part/App/FeatureRevolution.h" +#include + +#include "Base/Interpreter.h" + +#include "BRepBuilderAPI_MakeEdge.hxx" + +#include "TopoDS_Iterator.hxx" + +#include "PartTestHelpers.h" + +class FeatureRevolutionTest: public ::testing::Test, public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + + void SetUp() override + { + createTestDoc(); + _revolution = dynamic_cast(_doc->addObject("Part::Revolution")); + PartTestHelpers::rectangle(len, wid, "Rect1"); + _revolution->Source.setValue(_doc->getObjects().back()); + _revolution->Axis.setValue(0, 1, 0); + } + + void TearDown() override + {} + + // NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes) + Part::Revolution* _revolution = nullptr; + // Arbtitrary constants for testing. Named here for clarity. + const double len = 3; + const double wid = 4; + const double ext1 = 10; + // NOLINTEND(cppcoreguidelines-non-private-member-variables-in-classes) +}; + +TEST_F(FeatureRevolutionTest, testExecute) +{ + // Arrange + double puckVolume = len * len * M_PI * wid; // Area is PIr2; apply height + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, puckVolume); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-len, 0, -len, len, wid, len))); +} + +TEST_F(FeatureRevolutionTest, testExecuteBase) +{ + // Arrange + double rad = len + 1.0; + double rad2 = 1.0; + double outerPuckVolume = rad * rad * M_PI * wid; // Area is PIr2; apply height + double innerPuckVolume = rad2 * rad2 * M_PI * wid; // Area is PIr2; apply height + _revolution->Base.setValue(Base::Vector3d(len + 1, 0, 0)); + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, outerPuckVolume - innerPuckVolume); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, -wid, wid * 2, wid, wid))); +} + + +TEST_F(FeatureRevolutionTest, testAxis) +{ + // Arrange + double puckVolume = wid * wid * M_PI * len; // Area is PIr2 times height + _revolution->Axis.setValue(Base::Vector3d(1, 0, 0)); + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, puckVolume); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, -wid, -wid, len, wid, wid))); +} + +TEST_F(FeatureRevolutionTest, testAxisLink) +{ + // Arrange + BRepBuilderAPI_MakeEdge e1(gp_Pnt(0, 0, 0), gp_Pnt(0, 0, ext1)); + auto edge = dynamic_cast(_doc->addObject("Part::Feature", "Edge")); + edge->Shape.setValue(e1); + _revolution->AxisLink.setValue(edge); + // double puckVolume = wid * wid * M_PI * len; // Area is PIr2; apply height + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + double puckVolume = 0; // Someday make this test use a more interesting edge angle + EXPECT_FLOAT_EQ(volume, puckVolume); + EXPECT_TRUE(PartTestHelpers::boxesMatch( + bb, + Base::BoundBox3d(-ext1 / 2, -ext1 / 2, 0, ext1 / 2, ext1 / 2, 0))); +} + +TEST_F(FeatureRevolutionTest, testSymmetric) +{ + // Arrange + double puckVolume = len * len * M_PI * wid; // Area is PIr2 times height + _revolution->Symmetric.setValue(true); + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, puckVolume); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-len, 0, -len, len, wid, len))); +} + +TEST_F(FeatureRevolutionTest, testAngle) +{ + // Arrange + double puckVolume = len * len * M_PI * wid; // Area is PIr2 times height + _revolution->Angle.setValue(90); // NOLINT magic number + // Act + _revolution->execute(); + Part::TopoShape ts = _revolution->Shape.getValue(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_FLOAT_EQ(volume, puckVolume / 4); + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0, 0, -len, len, wid, 0))); +} + +TEST_F(FeatureRevolutionTest, testMustExecute) +{ + // Assert + EXPECT_TRUE(_revolution->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_revolution->mustExecute()); + // Act + _revolution->Base.setValue(_revolution->Base.getValue()); + // Assert + EXPECT_TRUE(_revolution->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_revolution->mustExecute()); + // Act + _revolution->Solid.setValue(Standard_True); + // Assert + EXPECT_TRUE(_revolution->mustExecute()); + // Act + _doc->recompute(); + // Assert + EXPECT_FALSE(_revolution->mustExecute()); +} + +// TEST_F(FeatureRevolutionTest, testOnChanged) +// { +// // void onChanged(const App::Property* prop) override; +// } + +TEST_F(FeatureRevolutionTest, testGetProviderName) +{ + // Act + _revolution->execute(); + const char* name = _revolution->getViewProviderName(); + // Assert + EXPECT_STREQ(name, "PartGui::ViewProviderRevolution"); +} + +// Tested by execute above +// TEST_F(FeatureRevolutionTest, testFetchAxisLink) +// { +// // static bool fetchAxisLink(const App::PropertyLinkSub& axisLink, +// // Base::Vector3d ¢er, +// // Base::Vector3d &dir, +// // double &angle); +// } diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index cdcfa44e95..02a3d33d04 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -1,10 +1,13 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later #include "PartTestHelpers.h" +// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) + namespace PartTestHelpers { -double getVolume(const TopoDS_Shape shape) +double getVolume(const TopoDS_Shape& shape) { GProp_GProps prop; BRepGProp::VolumeProperties(shape, prop); @@ -16,7 +19,6 @@ void PartTestHelperClass::createTestDoc() _docName = App::GetApplication().getUniqueDocumentName("test"); _doc = App::GetApplication().newDocument(_docName.c_str(), "testUser"); std::array box_origins = { - // NOLINT magic number Base::Vector3d(), // First box at 0,0,0 Base::Vector3d(0, 1, 0), // Overlap with first box Base::Vector3d(0, 3, 0), // Don't Overlap with first box @@ -25,14 +27,73 @@ void PartTestHelperClass::createTestDoc() // For the Just Inside Of Touching case, go enough that we exceed precision rounding Base::Vector3d(0, 2 - minimalDistance, 0)}; - for (int i = 0; i < _boxes.size(); i++) { - auto box = _boxes[i] = static_cast(_doc->addObject("Part::Box")); + for (unsigned i = 0; i < _boxes.size(); i++) { + auto box = _boxes[i] = dynamic_cast(_doc->addObject("Part::Box")); // NOLINT box->Length.setValue(1); box->Width.setValue(2); box->Height.setValue(3); box->Placement.setValue( - Base::Placement(box_origins[i], Base::Rotation(), Base::Vector3d())); + Base::Placement(box_origins[i], Base::Rotation(), Base::Vector3d())); // NOLINT } } +std::vector +_getFilletEdges(const std::vector& edges, double startRadius, double endRadius) +{ + std::vector filletElements; + for (auto edge : edges) { + Part::FilletElement fe = {edge, startRadius, endRadius}; + filletElements.push_back(fe); + } + return filletElements; +} + +void executePython(const std::vector& python) +{ + Base::InterpreterSingleton is = Base::InterpreterSingleton(); + + for (auto const& line : python) { + is.runInteractiveString(line.c_str()); + } +} + + +void rectangle(double height, double width, char* name) +{ + std::vector rectstring { + "import FreeCAD, Part", + "V1 = FreeCAD.Vector(0, 0, 0)", + boost::str(boost::format("V2 = FreeCAD.Vector(%d, 0, 0)") % height), + boost::str(boost::format("V3 = FreeCAD.Vector(%d, %d, 0)") % height % width), + boost::str(boost::format("V4 = FreeCAD.Vector(0, %d, 0)") % width), + "P1 = Part.makePolygon([V1, V2, V3, V4],True)", + "F1 = Part.Face(P1)", // Make the face or the volume calc won't work right. + // "L1 = Part.LineSegment(V1, V2)", + // "L2 = Part.LineSegment(V2, V3)", + // "L3 = Part.LineSegment(V3, V4)", + // "L4 = Part.LineSegment(V4, V1)", + // "S1 = Part.Shape([L1,L2,L3,L4])", + // "W1 = Part.Wire(S1.Edges)", + // "F1 = Part.Face(W1)", // Make the face or the volume calc won't work right. + boost::str(boost::format("Part.show(F1,'%s')") % name), + }; + executePython(rectstring); +} + +testing::AssertionResult +boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec) +{ + if (abs(b1.MinX - b2.MinX) < prec && abs(b1.MinY - b2.MinY) < prec + && abs(b1.MinZ - b2.MinZ) < prec && abs(b1.MaxX - b2.MaxX) < prec + && abs(b1.MaxY - b2.MaxY) < prec && abs(b1.MaxZ - b2.MaxZ) < prec) { + return testing::AssertionSuccess(); + } + return testing::AssertionFailure() + << "(" << b1.MinX << "," << b1.MinY << "," << b1.MinZ << " ; " + << "(" << b1.MaxX << "," << b1.MaxY << "," << b1.MaxZ << ") != (" << b2.MinX << "," + << b2.MinY << "," << b2.MinZ << " ; " << b2.MaxX << "," << b2.MaxY << "," << b2.MaxZ << ")"; +} + } // namespace PartTestHelpers + +// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index cc2be6e1c6..d651951c2f 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" #include #include #include @@ -5,21 +8,32 @@ #include "Mod/Part/App/FeaturePartFuse.h" #include "Mod/Part/App/FeatureFillet.h" #include +#include "Base/Interpreter.h" +#include namespace PartTestHelpers { -double getVolume(TopoDS_Shape shape); +double getVolume(const TopoDS_Shape& shape); + +std::vector +_getFilletEdges(const std::vector& edges, double startRadius, double endRadius); class PartTestHelperClass { public: App::Document* _doc; std::string _docName; - std::array _boxes; + std::array _boxes; // NOLINT magic number void createTestDoc(); }; const double minimalDistance = Base::Precision::Confusion() * 1000; +void executePython(const std::vector& python); + +void rectangle(double height, double width, char* name); + +testing::AssertionResult +boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec = 1e-05); // NOLINT } // namespace PartTestHelpers