Skip to content

Commit

Permalink
Merge pull request FreeCAD#12982 from bgbsww/bgbsww-toponamingFeature…
Browse files Browse the repository at this point in the history
…Mirroring

Toponaming/Part feature mirroring and offset
  • Loading branch information
chennes authored Mar 20, 2024
2 parents 46c32a8 + 7a520a4 commit 161b0fb
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 3 deletions.
10 changes: 9 additions & 1 deletion src/Mod/Part/App/FeatureMirroring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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());
Expand Down
11 changes: 10 additions & 1 deletion src/Mod/Part/App/FeatureOffset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<JoinType>(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;
}

Expand Down
2 changes: 2 additions & 0 deletions tests/src/Mod/Part/App/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
130 changes: 130 additions & 0 deletions tests/src/Mod/Part/App/FeatureMirroring.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "gtest/gtest.h"

#include <src/App/InitApplication.h>
#include <Mod/Part/App/FeatureMirroring.h>

#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<Part::Mirroring*>(_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<Part::Fuse*>(_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)
149 changes: 149 additions & 0 deletions tests/src/Mod/Part/App/FeatureOffset.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: LGPL-2.1-or-later

#include "gtest/gtest.h"

#include <src/App/InitApplication.h>

#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<Part::Offset*>(_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<Part::Fuse*>(_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<Part::Offset2D*>(_doc->addObject("Part::Offset2D"));
Part::Plane* _pln = dynamic_cast<Part::Plane*>(_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)
8 changes: 7 additions & 1 deletion tests/src/Mod/Part/App/PartTestHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 161b0fb

Please sign in to comment.