Merge pull request #12553 from bgbsww/bgbsww-toponamingMakeElementSliceMirror

Toponaming: Part: make element slice mirror
This commit is contained in:
Chris Hennes
2024-02-25 23:36:32 -06:00
committed by GitHub
6 changed files with 432 additions and 19 deletions

View File

@@ -44,6 +44,7 @@
#endif
#include "CrossSection.h"
#include "TopoShapeOpCode.h"
using namespace Part;
@@ -218,3 +219,111 @@ TopoDS_Wire CrossSection::fixWire(const TopoDS_Wire& wire) const
aFix.FixClosed();
return aFix.Wire();
}
TopoCrossSection::TopoCrossSection(double a, double b, double c, const TopoShape& s, const char *op)
: a(a), b(b), c(c), shape(s), op(op?op:Part::OpCodes::Slice)
{
}
void TopoCrossSection::slice(int idx, double d, std::vector<TopoShape>& wires) const
{
// Fixes: 0001228: Cross section of Torus in Part Workbench fails or give wrong results
// Fixes: 0001137: Incomplete slices when using Part.slice on a torus
bool found = false;
for (auto& s : shape.getSubTopoShapes(TopAbs_SOLID)) {
sliceSolid(idx, d, s, wires);
found = true;
}
if (!found) {
for (auto& s : shape.getSubTopoShapes(TopAbs_SHELL)) {
sliceNonSolid(idx, d, s, wires);
found = true;
}
if (!found) {
for (auto& s : shape.getSubTopoShapes(TopAbs_FACE)) {
sliceNonSolid(idx, d, s, wires);
}
}
}
}
TopoShape TopoCrossSection::slice(int idx, double d) const
{
std::vector<TopoShape> wires;
slice(idx, d, wires);
return TopoShape().makeElementCompound(
wires,
0,
TopoShape::SingleShapeCompoundCreationPolicy::returnShape);
}
void TopoCrossSection::sliceNonSolid(int idx,
double d,
const TopoShape& shape,
std::vector<TopoShape>& wires) const
{
BRepAlgoAPI_Section cs(shape.getShape(), gp_Pln(a, b, c, -d));
if (cs.IsDone()) {
std::string prefix(op);
if (idx > 1) {
prefix += '_';
prefix += std::to_string(idx);
}
auto res = TopoShape()
.makeElementShape(cs, shape, prefix.c_str())
.makeElementWires()
.getSubTopoShapes(TopAbs_WIRE);
wires.insert(wires.end(), res.begin(), res.end());
}
}
void TopoCrossSection::sliceSolid(int idx,
double d,
const TopoShape& shape,
std::vector<TopoShape>& wires) const
{
gp_Pln slicePlane(a, b, c, -d);
BRepBuilderAPI_MakeFace mkFace(slicePlane);
TopoShape face(idx);
face.setShape(mkFace.Face());
// Make sure to choose a point that does not lie on the plane (fixes #0001228)
gp_Vec tempVector(a, b, c);
tempVector.Normalize(); // just in case.
tempVector *= (d + 1.0);
gp_Pnt refPoint(0.0, 0.0, 0.0);
refPoint.Translate(tempVector);
BRepPrimAPI_MakeHalfSpace mkSolid(TopoDS::Face(face.getShape()), refPoint);
TopoShape solid(idx);
std::string prefix(op);
if (idx > 1) {
prefix += '_';
prefix += std::to_string(idx);
}
solid.makeElementShape(mkSolid, face, prefix.c_str());
BRepAlgoAPI_Cut mkCut(shape.getShape(), solid.getShape());
if (mkCut.IsDone()) {
TopoShape res(shape.Tag, shape.Hasher);
std::vector<TopoShape> shapes;
shapes.push_back(shape);
shapes.push_back(solid);
res.makeElementShape(mkCut, shapes, prefix.c_str());
for (auto& face : res.getSubTopoShapes(TopAbs_FACE)) {
BRepAdaptor_Surface adapt(TopoDS::Face(face.getShape()));
if (adapt.GetType() == GeomAbs_Plane) {
gp_Pln plane = adapt.Plane();
if (plane.Axis().IsParallel(slicePlane.Axis(), Precision::Confusion())
&& plane.Distance(slicePlane.Location()) < Precision::Confusion()) {
auto repaired_wires = TopoShape(face.Tag)
.makeElementWires(face.getSubTopoShapes(TopAbs_EDGE),
prefix.c_str(),
true)
.getSubTopoShapes(TopAbs_WIRE);
wires.insert(wires.end(), repaired_wires.begin(), repaired_wires.end());
}
}
}
}
}

View File

@@ -26,6 +26,7 @@
#include <list>
#include <TopTools_IndexedMapOfShape.hxx>
#include <Mod/Part/PartGlobal.h>
#include "TopoShape.h"
class TopoDS_Shape;
@@ -52,6 +53,23 @@ private:
const TopoDS_Shape& s;
};
}
class PartExport TopoCrossSection
{
public:
TopoCrossSection(double a, double b, double c, const TopoShape& s, const char* op = 0);
void slice(int idx, double d, std::vector<TopoShape>& wires) const;
TopoShape slice(int idx, double d) const;
private:
void sliceNonSolid(int idx, double d, const TopoShape&, std::vector<TopoShape>& wires) const;
void sliceSolid(int idx, double d, const TopoShape&, std::vector<TopoShape>& wires) const;
private:
double a, b, c;
const TopoShape& shape;
const char* op;
};
} // namespace Part
#endif // PART_CROSSSECTION_H

View File

@@ -1378,6 +1378,100 @@ public:
return TopoShape(0, Hasher).makeElementBoolean(maker, *this, op, tol);
}
/** Make a mirrored shape
*
* @param source: the source shape
* @param axis: the axis for mirroring
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape&
makeElementMirror(const TopoShape& source, const gp_Ax2& axis, const char* op = nullptr);
/** Make a mirrored shape
*
* @param source: the source shape
* @param axis: the axis for mirroring
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementMirror(const gp_Ax2& ax, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementMirror(*this, ax, op);
}
/** Make a cross section slice
*
* @param source: the source shape
* @param dir: direction of the normal of the section plane
* @param distance: distance to move the section plane
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape& makeElementSlice(const TopoShape& source,
const Base::Vector3d& dir,
double distance,
const char* op = nullptr);
/** Make a cross section slice
*
* @param source: the source shape
* @param dir: direction of the normal of the section plane
* @param distance: distance to move the section plane
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementSlice(const Base::Vector3d& dir, double distance, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementSlice(*this, dir, distance, op);
}
/** Make multiple cross section slices
*
* @param source: the source shape
* @param dir: direction of the normal of the section plane
* @param distances: distances to move the section plane for making slices
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return The original content of this TopoShape is discarded and replaced
* with the new shape. The function returns the TopoShape itself as
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
TopoShape& makeElementSlices(const TopoShape& source,
const Base::Vector3d& dir,
const std::vector<double>& distances,
const char* op = nullptr);
/** Make multiple cross section slices
*
* @param source: the source shape
* @param dir: direction of the normal of the section plane
* @param distances: distances to move the section plane for making slices
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape makeElementSlices(const Base::Vector3d& dir,
const std::vector<double>& distances,
const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementSlices(*this, dir, distances, op);
}
/* Make fillet shape
*
* @param source: the source shape

View File

@@ -50,6 +50,7 @@
#include <BRepBuilderAPI_Copy.hxx>
#include <BRepBuilderAPI_GTransform.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
#include <BRepFilletAPI_MakeChamfer.hxx>
#include <BRepFilletAPI_MakeFillet.hxx>
@@ -78,6 +79,7 @@
#endif
#include "modelRefine.h"
#include "CrossSection.h"
#include "TopoShape.h"
#include "TopoShapeOpCode.h"
#include "TopoShapeCache.h"
@@ -2760,6 +2762,54 @@ TopoShape& TopoShape::makeElementSolid(const TopoShape& shape, const char* op)
return *this;
}
TopoShape& TopoShape::makeElementMirror(const TopoShape& shape, const gp_Ax2& ax2, const char* op)
{
if (!op) {
op = Part::OpCodes::Mirror;
}
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
gp_Trsf mat;
mat.SetMirror(ax2);
TopLoc_Location loc = shape.getShape().Location();
gp_Trsf placement = loc.Transformation();
mat = placement * mat;
BRepBuilderAPI_Transform mkTrf(shape.getShape(), mat);
return makeElementShape(mkTrf, shape, op);
}
TopoShape& TopoShape::makeElementSlice(const TopoShape& shape,
const Base::Vector3d& dir,
double distance,
const char* op)
{
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
TopoCrossSection cs(dir.x, dir.y, dir.z, shape, op);
TopoShape res = cs.slice(1, distance);
setShape(res._Shape);
Hasher = res.Hasher;
resetElementMap(res.elementMap());
return *this;
}
TopoShape& TopoShape::makeElementSlices(const TopoShape& shape,
const Base::Vector3d& dir,
const std::vector<double>& distances,
const char* op)
{
std::vector<TopoShape> wires;
TopoCrossSection cs(dir.x, dir.y, dir.z, shape, op);
int index = 0;
for (auto& distance : distances) {
cs.slice(++index, distance, wires);
}
return makeElementCompound(wires, op, SingleShapeCompoundCreationPolicy::returnShape);
}
TopoShape& TopoShape::makeElementFillet(const TopoShape& shape,
const std::vector<TopoShape>& edges,
double radius1,
@@ -2914,7 +2964,14 @@ TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const std::vector<TopoShape>& shapes,
const char* op)
{
return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op);
TopoDS_Shape shape;
// OCCT 7.3.x requires calling Solid() and not Shape() to function correctly
if ( typeid(mkShape) == typeid(BRepPrimAPI_MakeHalfSpace) ) {
shape = static_cast<BRepPrimAPI_MakeHalfSpace&>(mkShape).Solid();
} else {
shape = mkShape.Shape();
}
return makeShapeWithElementMap(shape, MapperMaker(mkShape), shapes, op);
}
TopoShape& TopoShape::makeElementLoft(const std::vector<TopoShape>& shapes,

View File

@@ -1,5 +1,6 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <regex>
#include "PartTestHelpers.h"
// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)
@@ -137,25 +138,49 @@ std::map<IndexedName, MappedName> elementMap(const TopoShape& shape)
return result;
}
std::string mappedElementVectorToString(std::vector<MappedElement>& elements)
{
std::stringstream output;
output << "{";
for (const auto& element : elements) {
output << "\"" << element.name.toString() << "\", ";
}
output << "}";
return output.str();
}
bool matchStringsWithoutClause(std::string first, std::string second, std::string regex)
{
first = std::regex_replace(first, std::regex(regex), "");
second = std::regex_replace(second, std::regex(regex), "");
return first == second;
}
/**
* Check to see if the elementMap in a shape contains all the names in a list
* The "Duplicate" clause in a name - ";Dnnn" can contain a random number, so we need to
* exclude those.
* @param shape The Shape
* @param names The vector of names
* @return An assertion usable by the gtest framework
*/
testing::AssertionResult elementsMatch(const TopoShape& shape,
const std::vector<std::string>& names)
{
auto elements = shape.getElementMap();
if (std::find_first_of(elements.begin(),
elements.end(),
names.begin(),
names.end(),
[&](const Data::MappedElement& element, const std::string& name) {
return element.name.toString() == name;
})
== elements.end()) {
std::stringstream output;
output << "{";
for (const auto& element : elements) {
output << "\"" << element.name.toString() << "\", ";
if (!elements.empty() || !names.empty()) {
if (std::find_first_of(elements.begin(),
elements.end(),
names.begin(),
names.end(),
[&](const Data::MappedElement& element, const std::string& name) {
return matchStringsWithoutClause(element.name.toString(),
name,
";D[a-fA-F0-9]+");
})
== elements.end()) {
return testing::AssertionFailure() << mappedElementVectorToString(elements);
}
output << "}";
return testing::AssertionFailure() << output.str();
}
return testing::AssertionSuccess();
}
@@ -166,7 +191,8 @@ testing::AssertionResult allElementsMatch(const TopoShape& shape,
auto elements = shape.getElementMap();
if (elements.size() != names.size()) {
return testing::AssertionFailure()
<< elements.size() << " != " << names.size() << " elements in map";
<< elements.size() << " != " << names.size()
<< " elements: " << mappedElementVectorToString(elements);
}
return elementsMatch(shape, names);
}

View File

@@ -1611,7 +1611,7 @@ TEST_F(TopoShapeExpansionTest, makeElementChamfer)
// Act
cube1TS.makeElementChamfer({cube1TS}, edges, .05, .05);
auto elements = elementMap(cube1TS);
// Assert
// Assert shape is correct
EXPECT_EQ(cube1TS.countSubElements("Wire"), 26);
EXPECT_FLOAT_EQ(getArea(cube1TS.getShape()), 5.640996);
// Assert that we're creating a correct element map
@@ -1728,7 +1728,7 @@ TEST_F(TopoShapeExpansionTest, makeElementFillet)
// Act
cube1TS.makeElementFillet({cube1TS}, edges, .05, .05);
auto elements = elementMap(cube1TS);
// Assert
// Assert shape is correct
EXPECT_EQ(cube1TS.countSubElements("Wire"), 26);
EXPECT_FLOAT_EQ(getArea(cube1TS.getShape()), 5.739646);
// Assert that we're creating a correct element map
@@ -1844,6 +1844,115 @@ TEST_F(TopoShapeExpansionTest, makeElementFillet)
}));
}
TEST_F(TopoShapeExpansionTest, makeElementSlice)
{
// Arrange
auto [cube1, cube2] = CreateTwoCubes(); // TopoShape version works too
TopoShape cube1TS {cube1}; // Adding a tag here only adds text in each mapped name
auto faces = cube1TS.getSubShapes(TopAbs_FACE);
TopoShape slicer {faces[0]};
Base::Vector3d direction {1.0, 0.0, 0.0};
// Act
auto& result = slicer.makeElementSlice(cube1TS, direction, 0.5);
// Assert shape is correct
EXPECT_FLOAT_EQ(getLength(result.getShape()), 4);
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_WIRE, result.getShape().ShapeType());
// Assert that we're creating a correct element map
EXPECT_TRUE(result.getMappedChildElements().empty());
EXPECT_TRUE(elementsMatch(result,
{
"Edge1;SLC;D1;MAK",
"Edge1;SLC;D2;MAK",
"Edge1;SLC;D3;MAK",
"Edge1;SLC;MAK",
"Vertex1;SLC;D1;MAK",
"Vertex1;SLC;D2;MAK",
"Vertex1;SLC;MAK",
"Vertex2;SLC;D1;MAK",
"Vertex2;SLC;D2;MAK",
"Vertex2;SLC;MAK",
}));
}
TEST_F(TopoShapeExpansionTest, makeElementSlices)
{
// Arrange
auto [cube1, cube2] = CreateTwoCubes();
TopoShape cube1TS {cube1, 1L};
auto faces = cube1TS.getSubShapes(TopAbs_FACE);
TopoShape slicer {faces[0]};
Base::Vector3d direction {1.0, 0.0, 0.0};
// Act
auto& result = slicer.makeElementSlices(cube1TS, direction, {0.25, 0.5, 0.75});
auto subTopoShapes = result.getSubTopoShapes(TopAbs_WIRE);
// Assert shape is correct
EXPECT_EQ(result.countSubElements("Wire"), 3);
EXPECT_FLOAT_EQ(getLength(result.getShape()), 12);
EXPECT_FLOAT_EQ(getLength(subTopoShapes[0].getShape()), 4);
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_COMPOUND, result.getShape().ShapeType());
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_WIRE, subTopoShapes[0].getShape().ShapeType());
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_WIRE, subTopoShapes[1].getShape().ShapeType());
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_WIRE, subTopoShapes[2].getShape().ShapeType());
// Assert that we're creating a correct element map
EXPECT_TRUE(result.getMappedChildElements().empty());
EXPECT_TRUE(elementsMatch(result, {"Edge1;SLC;:H1:4,E;D1;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC;:H1:4,E;D2;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC;:H1:4,E;D3;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC;:H1:4,E;MAK;:H1:4,E",
"Edge1;SLC_2;:H1:6,E;D1;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_2;:H1:6,E;D2;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_2;:H1:6,E;D3;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_2;:H1:6,E;MAK;:H1:4,E",
"Edge1;SLC_3;:H1:6,E;D1;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_3;:H1:6,E;D2;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_3;:H1:6,E;D3;:H1:3,E;MAK;:H1:4,E",
"Edge1;SLC_3;:H1:6,E;MAK;:H1:4,E",
"Vertex1;SLC;:H1:4,V;D2;:H1:3,V;MAK;:H1:4,V",
"Vertex1;SLC;:H1:4,V;MAK;:H1:4,V",
"Vertex1;SLC_2;:H1:6,V;D2;:H1:3,V;MAK;:H1:4,V",
"Vertex1;SLC_2;:H1:6,V;MAK;:H1:4,V",
"Vertex1;SLC_3;:H1:6,V;D2;:H1:3,V;MAK;:H1:4,V",
"Vertex1;SLC_3;:H1:6,V;MAK;:H1:4,V",
"Vertex2;SLC;:H1:4,V;D1;:H1:3,V;MAK;:H1:4,V",
"Vertex2;SLC;:H1:4,V;MAK;:H1:4,V",
"Vertex2;SLC_2;:H1:6,V;D1;:H1:3,V;MAK;:H1:4,V",
"Vertex2;SLC_2;:H1:6,V;MAK;:H1:4,V",
"Vertex2;SLC_3;:H1:6,V;D1;:H1:3,V;MAK;:H1:4,V",
"Vertex2;SLC_3;:H1:6,V;MAK;:H1:4,V"}));
EXPECT_TRUE(subTopoShapes[0].getElementMap().empty());
}
TEST_F(TopoShapeExpansionTest, makeElementMirror)
{
// Arrange
auto [cube1, cube2] = CreateTwoCubes();
TopoShape cube1TS {cube1, 1L};
auto edges = cube1TS.getSubTopoShapes(TopAbs_EDGE);
gp_Ax2 axis {gp_Pnt {0, 0, 0}, gp_Dir {1, 0, 0}};
// Act
auto& result = cube1TS.makeElementMirror(cube1TS, axis);
auto elements = elementMap(cube1TS);
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-1, 0, 0, 0, 1, 1)));
EXPECT_EQ(result.countSubElements("Wire"), 6);
EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1);
EXPECT_EQ(TopAbs_ShapeEnum::TopAbs_SOLID, result.getShape().ShapeType());
// Assert that we're creating a correct element map
EXPECT_TRUE(result.getMappedChildElements().empty());
EXPECT_TRUE(
elementsMatch(result,
{"Edge10;:M;MIR;:H1:7,E", "Edge11;:M;MIR;:H1:7,E", "Edge12;:M;MIR;:H1:7,E",
"Edge1;:M;MIR;:H1:7,E", "Edge2;:M;MIR;:H1:7,E", "Edge3;:M;MIR;:H1:7,E",
"Edge4;:M;MIR;:H1:7,E", "Edge5;:M;MIR;:H1:7,E", "Edge6;:M;MIR;:H1:7,E",
"Edge7;:M;MIR;:H1:7,E", "Edge8;:M;MIR;:H1:7,E", "Edge9;:M;MIR;:H1:7,E",
"Face1;:M;MIR;:H1:7,F", "Face2;:M;MIR;:H1:7,F", "Face3;:M;MIR;:H1:7,F",
"Face4;:M;MIR;:H1:7,F", "Face5;:M;MIR;:H1:7,F", "Face6;:M;MIR;:H1:7,F",
"Vertex1;:M;MIR;:H1:7,V", "Vertex2;:M;MIR;:H1:7,V", "Vertex3;:M;MIR;:H1:7,V",
"Vertex4;:M;MIR;:H1:7,V", "Vertex5;:M;MIR;:H1:7,V", "Vertex6;:M;MIR;:H1:7,V",
"Vertex7;:M;MIR;:H1:7,V", "Vertex8;:M;MIR;:H1:7,V"}));
}
TEST_F(TopoShapeExpansionTest, makeElementTransformWithoutMap)
{
// Arrange