diff --git a/src/Mod/Part/App/FeatureMirroring.cpp b/src/Mod/Part/App/FeatureMirroring.cpp index 9cd077c72c..3ceea6ea11 100644 --- a/src/Mod/Part/App/FeatureMirroring.cpp +++ b/src/Mod/Part/App/FeatureMirroring.cpp @@ -252,10 +252,11 @@ App::DocumentObjectExecReturn *Mirroring::execute() Base::Vector3d norm = Normal.getValue(); try { + gp_Ax2 ax2(gp_Pnt(base.x,base.y,base.z), gp_Dir(norm.x,norm.y,norm.z)); +#ifndef FC_USE_TNP_FIX const TopoDS_Shape& shape = Feature::getShape(link); if (shape.IsNull()) Standard_Failure::Raise(std::string(std::string(this->getFullLabel()) + ": Cannot mirror empty shape").c_str()); - gp_Ax2 ax2(gp_Pnt(base.x,base.y,base.z), gp_Dir(norm.x,norm.y,norm.z)); gp_Trsf mat; mat.SetMirror(ax2); TopLoc_Location loc = shape.Location(); @@ -264,6 +265,13 @@ App::DocumentObjectExecReturn *Mirroring::execute() BRepBuilderAPI_Transform mkTrf(shape, mat); this->Shape.setValue(mkTrf.Shape()); return App::DocumentObject::StdReturn; +#else + auto shape = Feature::getTopoShape(link); + if (shape.isNull()) + Standard_Failure::Raise("Cannot mirror empty shape"); + this->Shape.setValue(TopoShape(0).makeElementMirror(shape,ax2)); + return Part::Feature::execute(); +#endif } catch (Standard_Failure& e) { return new App::DocumentObjectExecReturn(e.GetMessageString()); diff --git a/src/Mod/Part/App/FeatureOffset.cpp b/src/Mod/Part/App/FeatureOffset.cpp index 095d75dd49..103b44b232 100644 --- a/src/Mod/Part/App/FeatureOffset.cpp +++ b/src/Mod/Part/App/FeatureOffset.cpp @@ -83,13 +83,22 @@ App::DocumentObjectExecReturn *Offset::execute() bool inter = Intersection.getValue(); bool self = SelfIntersection.getValue(); short mode = (short)Mode.getValue(); - short join = (short)Join.getValue(); bool fill = Fill.getValue(); +#ifndef FC_USE_TNP_FIX + short join = (short)Join.getValue(); const TopoShape& shape = Feature::getShape(source); if (fabs(offset) > 2*tol) this->Shape.setValue(shape.makeOffsetShape(offset, tol, inter, self, mode, join, fill)); else this->Shape.setValue(shape); +#else + auto shape = Feature::getTopoShape(source); + if(shape.isNull()) + return new App::DocumentObjectExecReturn("Invalid source link"); + auto join = static_cast(Join.getValue()); + this->Shape.setValue(TopoShape(0).makeElementOffset( + shape,offset,tol,inter,self,mode,join,fill ? FillType::fill : FillType::noFill)); +#endif return App::DocumentObject::StdReturn; } diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 1ed7a48601..6f47a52112 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -9,6 +9,8 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/FeatureCompound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeatureExtrusion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeatureFillet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureMirroring.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/FeatureOffset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartBoolean.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCommon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCut.cpp diff --git a/tests/src/Mod/Part/App/FeatureMirroring.cpp b/tests/src/Mod/Part/App/FeatureMirroring.cpp new file mode 100644 index 0000000000..1e4bd70a2f --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureMirroring.cpp @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include +#include + +#include "PartTestHelpers.h" + +using namespace PartTestHelpers; + +// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) +class FeatureMirroringTest: public ::testing::Test, public PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + createTestDoc(); + _mirror = dynamic_cast(_doc->addObject("Part::Mirroring")); + _mirror->Source.setValue(_boxes[0]); + _mirror->Base.setValue(1, 0, 0); + _mirror->execute(); + } + + void TearDown() override + {} + + Part::Mirroring* _mirror = nullptr; // NOLINT Can't be private in a test framework +}; + +TEST_F(FeatureMirroringTest, testXMirror) +{ + // Arrange + Base::BoundBox3d bb = _mirror->Shape.getShape().getBoundBox(); + // Assert size and position + EXPECT_EQ(getVolume(_mirror->Shape.getShape().getShape()), 6); + // Mirrored it around X from 0,0,0 -> 1,2,3 to 0,0,-3 -> 1,2,0 + EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(0, 0, -3, 1, 2, 0))); + // Assert correct element Map +#ifdef FC_USE_TNP_FIX + EXPECT_TRUE(allElementsMatch( + _mirror->Shape.getShape(), + { + "Edge10;:M;MIR;:H70c:7,E", "Edge11;:M;MIR;:H70c:7,E", "Edge12;:M;MIR;:H70c:7,E", + "Edge1;:M;MIR;:H70c:7,E", "Edge2;:M;MIR;:H70c:7,E", "Edge3;:M;MIR;:H70c:7,E", + "Edge4;:M;MIR;:H70c:7,E", "Edge5;:M;MIR;:H70c:7,E", "Edge6;:M;MIR;:H70c:7,E", + "Edge7;:M;MIR;:H70c:7,E", "Edge8;:M;MIR;:H70c:7,E", "Edge9;:M;MIR;:H70c:7,E", + "Face1;:M;MIR;:H70c:7,F", "Face2;:M;MIR;:H70c:7,F", "Face3;:M;MIR;:H70c:7,F", + "Face4;:M;MIR;:H70c:7,F", "Face5;:M;MIR;:H70c:7,F", "Face6;:M;MIR;:H70c:7,F", + "Vertex1;:M;MIR;:H70c:7,V", "Vertex2;:M;MIR;:H70c:7,V", "Vertex3;:M;MIR;:H70c:7,V", + "Vertex4;:M;MIR;:H70c:7,V", "Vertex5;:M;MIR;:H70c:7,V", "Vertex6;:M;MIR;:H70c:7,V", + "Vertex7;:M;MIR;:H70c:7,V", "Vertex8;:M;MIR;:H70c:7,V", + })); +#else + EXPECT_EQ(_mirror->Shape.getShape().getElementMapSize(), 0); +#endif +} + +TEST_F(FeatureMirroringTest, testYMirrorWithExistingElementMap) +{ + // Arrange + Part::Fuse* _fuse = nullptr; // NOLINT Can't be private in a test framework + _fuse = dynamic_cast(_doc->addObject("Part::Fuse")); + _fuse->Base.setValue(_boxes[0]); + _fuse->Tool.setValue(_boxes[1]); + // Act + _fuse->execute(); + _mirror->Source.setValue(_fuse); + _mirror->Base.setValue(0, 1, 0); // Y Axis + Part::TopoShape ts = _fuse->Shape.getValue(); + double volume = getVolume(ts.getShape()); + Base::BoundBox3d bb = _mirror->Shape.getShape().getBoundBox(); + // Assert size and position + EXPECT_EQ(getVolume(_mirror->Shape.getShape().getShape()), volume); + // Mirrored it around X from 0,0,0 -> 1,2,3 to 0,0,-3 -> 1,2,0 + EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(0, 0, -3, 1, 3, 0))); + // Assert correct element Map +#ifdef FC_USE_TNP_FIX + EXPECT_TRUE(elementsMatch( + _mirror->Shape.getShape(), + { + "Edge10;:M;FUS;:H30a:7,E;:M;MIR;:H310:7,E", + "Edge11;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge12;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge1;:M;FUS;:H30a:7,E;:M;MIR;:H310:7,E", + "Edge1;:M;FUS;:H30a:7,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge1;:M;FUS;:H30a:7,E;:U;FUS;:H30a:7,V;:M;MIR;:H310:7,V", + "Edge2;:M2(Edge2;:H30a,E);FUS;:H309:17,E;:M;MIR;:H310:7,E", + "Edge2;:M2(Edge2;:H30a,E);FUS;:H309:17,E;:U2;FUS;:H309:8,V;:M;MIR;:H310:7,V", + "Edge2;:M2;FUS;:H30a:8,E;:M;MIR;:H310:7,E", + "Edge2;:M2;FUS;:H30a:8,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge2;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge2;:M;FUS;:H309:7,E;:U;FUS;:H309:7,V;:M;MIR;:H310:7,V", + "Edge3;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge3;:M;FUS;:H309:7,E;:U2;FUS;:H309:8,V;:M;MIR;:H310:7,V", + "Edge4;:M2(Edge4;:H30a,E);FUS;:H309:17,E;:M;MIR;:H310:7,E", + "Edge4;:M2;FUS;:H30a:8,E;:M;MIR;:H310:7,E", + "Edge4;:M2;FUS;:H30a:8,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge4;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge4;:M;FUS;:H309:7,E;:U;FUS;:H309:7,V;:M;MIR;:H310:7,V", + "Edge5;:M;FUS;:H30a:7,E;:M;MIR;:H310:7,E", + "Edge5;:M;FUS;:H30a:7,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge5;:M;FUS;:H30a:7,E;:U;FUS;:H30a:7,V;:M;MIR;:H310:7,V", + "Edge6;:M2(Edge6;:H30a,E);FUS;:H309:17,E;:M;MIR;:H310:7,E", + "Edge6;:M2(Edge6;:H30a,E);FUS;:H309:17,E;:U2;FUS;:H309:8,V;:M;MIR;:H310:7,V", + "Edge6;:M2;FUS;:H30a:8,E;:M;MIR;:H310:7,E", + "Edge6;:M2;FUS;:H30a:8,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge6;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge6;:M;FUS;:H309:7,E;:U;FUS;:H309:7,V;:M;MIR;:H310:7,V", + "Edge7;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge7;:M;FUS;:H309:7,E;:U2;FUS;:H309:8,V;:M;MIR;:H310:7,V", + "Edge8;:M2(Edge8;:H30a,E);FUS;:H309:17,E;:M;MIR;:H310:7,E", + "Edge8;:M2;FUS;:H30a:8,E;:M;MIR;:H310:7,E", + "Edge8;:M2;FUS;:H30a:8,E;:U2;FUS;:H30a:8,V;:M;MIR;:H310:7,V", + "Edge8;:M;FUS;:H309:7,E;:M;MIR;:H310:7,E", + "Edge8;:M;FUS;:H309:7,E;:U;FUS;:H309:7,V;:M;MIR;:H310:7,V", + "Edge9;:M;FUS;:H30a:7,E;:M;MIR;:H310:7,E", + // TODO: Testing the Faces here was non-deterministic from run to run. Is that okay? + })); +#else + EXPECT_EQ(_mirror->Shape.getShape().getElementMapSize(), 0); +#endif +} + +// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/FeatureOffset.cpp b/tests/src/Mod/Part/App/FeatureOffset.cpp new file mode 100644 index 0000000000..5085cbeef2 --- /dev/null +++ b/tests/src/Mod/Part/App/FeatureOffset.cpp @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include + +#include "PartTestHelpers.h" +#include "Mod/Part/App/FeatureOffset.h" + +using namespace PartTestHelpers; + +// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) +class FeatureOffsetTest: public ::testing::Test, public PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + createTestDoc(); + _offset = dynamic_cast(_doc->addObject("Part::Offset")); + _offset->Source.setValue(_boxes[0]); + _offset->Value.setValue(1); + _offset->Join.setValue((int)JoinType::intersection); + _offset->execute(); + } + + void TearDown() override + {} + + Part::Offset* _offset = nullptr; // NOLINT Can't be private in a test framework +}; + +TEST_F(FeatureOffsetTest, testOffset3D) +{ + // Arrange + Base::BoundBox3d bb = _offset->Shape.getShape().getBoundBox(); + // Assert size and position + // a 1x2x3 box 3doffset by 1 becomes a 3x4x5 box, so volume is 60. + EXPECT_EQ(getVolume(_offset->Shape.getShape().getShape()), 60); + EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(-1, -1, -1, 2, 3, 4))); + // Assert correct element Map +#ifdef FC_USE_TNP_FIX + EXPECT_TRUE(allElementsMatch( + _offset->Shape.getShape(), + { + "Edge10;:G;OFS;:H47b:7,E", "Edge11;:G;OFS;:H47b:7,E", "Edge12;:G;OFS;:H47b:7,E", + "Edge1;:G;OFS;:H47b:7,E", "Edge2;:G;OFS;:H47b:7,E", "Edge3;:G;OFS;:H47b:7,E", + "Edge4;:G;OFS;:H47b:7,E", "Edge5;:G;OFS;:H47b:7,E", "Edge6;:G;OFS;:H47b:7,E", + "Edge7;:G;OFS;:H47b:7,E", "Edge8;:G;OFS;:H47b:7,E", "Edge9;:G;OFS;:H47b:7,E", + "Face1;:G;OFS;:H47b:7,F", "Face2;:G;OFS;:H47b:7,F", "Face3;:G;OFS;:H47b:7,F", + "Face4;:G;OFS;:H47b:7,F", "Face5;:G;OFS;:H47b:7,F", "Face6;:G;OFS;:H47b:7,F", + "Vertex1;:G;OFS;:H47b:7,V", "Vertex2;:G;OFS;:H47b:7,V", "Vertex3;:G;OFS;:H47b:7,V", + "Vertex4;:G;OFS;:H47b:7,V", "Vertex5;:G;OFS;:H47b:7,V", "Vertex6;:G;OFS;:H47b:7,V", + "Vertex7;:G;OFS;:H47b:7,V", "Vertex8;:G;OFS;:H47b:7,V", + })); +#else + EXPECT_EQ(_offset->Shape.getShape().getElementMapSize(), 0); +#endif +} + +TEST_F(FeatureOffsetTest, testOffset3DWithExistingElementMap) +{ + // Arrange + Part::Fuse* _fuse = nullptr; // NOLINT Can't be private in a test framework + _fuse = dynamic_cast(_doc->addObject("Part::Fuse")); + _fuse->Base.setValue(_boxes[0]); + _fuse->Tool.setValue(_boxes[1]); + _fuse->Refine.setValue(true); + // Act + _fuse->execute(); + _offset->Source.setValue(_fuse); + _offset->Value.setValue(2); + _offset->execute(); + Base::BoundBox3d bb = _offset->Shape.getShape().getBoundBox(); + // Assert size and position + // A 1x3x3 box 3doffset by 2 becomes a 5x7x7 box with volume of 245 + EXPECT_EQ(getVolume(_fuse->Shape.getShape().getShape()), 9); + EXPECT_EQ(getVolume(_offset->Shape.getShape().getShape()), 245); + EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(-2, -2, -2, 3, 5, 5))); + // Assert correct element Map +#ifdef FC_USE_TNP_FIX + EXPECT_TRUE(elementsMatch( + _offset->Shape.getShape(), + { + "Edge2;:M2(Edge2;:H366,E);FUS;:H365:17,E;:G(Edge2;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge2;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:G;OFS;:H36c:7,E;SLD;:H36c:4,E", + "Edge2;:M2(Edge2;:H366,E);FUS;:H365:17,E;:G(Edge2;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge2;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U2;RFI;:H365:8,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge2;:M2(Edge2;:H366,E);FUS;:H365:17,E;:G(Edge2;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge2;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U;RFI;:H365:7,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge4;:M2(Edge4;:H366,E);FUS;:H365:17,E;:G(Edge4;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge4;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:G;OFS;:H36c:7,E;SLD;:H36c:4,E", + "Edge4;:M2(Edge4;:H366,E);FUS;:H365:17,E;:G(Edge4;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge4;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U2;RFI;:H365:8,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge4;:M2(Edge4;:H366,E);FUS;:H365:17,E;:G(Edge4;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge4;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U;RFI;:H365:7,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge6;:M2(Edge6;:H366,E);FUS;:H365:17,E;:G(Edge6;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge6;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:G;OFS;:H36c:7,E;SLD;:H36c:4,E", + "Edge6;:M2(Edge6;:H366,E);FUS;:H365:17,E;:G(Edge6;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge6;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U2;RFI;:H365:8,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge6;:M2(Edge6;:H366,E);FUS;:H365:17,E;:G(Edge6;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge6;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U;RFI;:H365:7,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge8;:M2(Edge8;:H366,E);FUS;:H365:17,E;:G(Edge8;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge8;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:G;OFS;:H36c:7,E;SLD;:H36c:4,E", + "Edge8;:M2(Edge8;:H366,E);FUS;:H365:17,E;:G(Edge8;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge8;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U2;RFI;:H365:8,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + "Edge8;:M2(Edge8;:H366,E);FUS;:H365:17,E;:G(Edge8;:M2;FUS;:H366:8,E;K-1;:H366:4,E|" + "Edge8;:M;FUS;:H365:7,E;K-1;:H365:4,E);RFI;:H365:53,E;:U;RFI;:H365:7,V;:G;OFS;:H36c:7," + "V;SLD;:H36c:4,V", + // TODO: Testing the Faces here was non-deterministic from run to run. Is that okay? + })); +#else + EXPECT_EQ(_offset->Shape.getShape().getElementMapSize(), 0); +#endif +} + +TEST_F(FeatureOffsetTest, testOffset2D) +{ + // Arrange + Part::Offset2D* _offset2 = dynamic_cast(_doc->addObject("Part::Offset2D")); + Part::Plane* _pln = dynamic_cast(_doc->addObject("Part::Plane")); + _pln->Length.setValue(2); + _pln->Width.setValue(3); + _offset2->Source.setValue(_pln); + _offset2->Value.setValue(1); + _offset2->Join.setValue((int)JoinType::intersection); + // Act + _offset2->execute(); + Base::BoundBox3d bb = _offset2->Shape.getShape().getBoundBox(); + // Assert size and position + // a 2x3 face 2doffset by 1 becomes a 4x5 face, so area is 20. + EXPECT_EQ(getArea(_offset2->Shape.getShape().getShape()), 20); + EXPECT_TRUE(boxesMatch(bb, Base::BoundBox3d(-1, -1, 0, 3, 4, 0))); + // Assert correct element Map + EXPECT_EQ(_offset2->Shape.getShape().getElementMapSize(), 0); +} + +// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index 76a8d6284a..a837d16207 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -175,7 +175,13 @@ testing::AssertionResult elementsMatch(const TopoShape& shape, [&, name](const Data::MappedElement& element) { return matchStringsWithoutClause(element.name.toString(), name, - ";D[a-fA-F0-9]+"); + "(;D|;:H|;K)-?[a-fA-F0-9]+"); + // ;D ;:H and ;K are the sections of an encoded name for + // Duplicate, Tag and a Face name in slices. All three of these + // can vary from run to run or platform to platform, as they are + // based on either explicit random numbers or memory addresses. + // Thus we remove the value from comparisons and just check that + // they exist. }) == elements.end()) { return testing::AssertionFailure() << mappedElementVectorToString(elements);