Merge pull request #12134 from bgbsww/bgbsww-toponamingMakeElementShape

Toponaming: makeElementShape transfer and tests
This commit is contained in:
Chris Hennes
2024-01-30 14:00:59 +01:00
committed by GitHub
4 changed files with 574 additions and 37 deletions

View File

@@ -33,7 +33,12 @@
#include <TopoDS_Compound.hxx>
#include <TopoDS_Wire.hxx>
#include <TopTools_ListOfShape.hxx>
#include <BRepBuilderAPI_MakeShape.hxx>
#include <BRepBuilderAPI_Sewing.hxx>
#include <BRepOffsetAPI_ThruSections.hxx>
#include <BRepOffsetAPI_MakePipeShell.hxx>
#include <BRepFeat_MakePrism.hxx>
#include <BRepPrimAPI_MakeHalfSpace.hxx>
class gp_Ax1;
class gp_Ax2;
@@ -717,9 +722,9 @@ public:
* a self reference so that multiple operations can be carried out
* for the same shape in the same line of code.
*/
// TopoShape& makeElementShellFromWires(const std::vector<TopoShape>& wires,
// bool silent = true,
// const char* op = nullptr);
TopoShape& makeElementShellFromWires(const std::vector<TopoShape>& wires,
bool silent = true,
const char* op = nullptr);
/* Make a shell with input wires
*
* @param wires: input wires
@@ -729,10 +734,10 @@ public:
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
// TopoShape& makeElementShellFromWires(bool silent = true, const char* op = nullptr)
// {
// return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op);
// }
TopoShape& makeElementShellFromWires(bool silent = true, const char* op = nullptr)
{
return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op);
}
TopoShape& makeElementFace(const std::vector<TopoShape>& shapes,
const char* op = nullptr,
@@ -822,6 +827,222 @@ public:
CN,
};
/** Generic shape making with mapped element name from shape history
*
* @param mkShape: OCCT shape maker.
* @param sources: list of source shapes.
* @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 built by the shape maker. 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& makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const std::vector<TopoShape>& sources,
const char* op = nullptr);
/** Generic shape making with mapped element name from shape history
*
* @param mkShape: OCCT shape maker.
* @param source: source shape.
* @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 built by the shape maker. 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& makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const TopoShape& source,
const char* op = nullptr);
/** Generic shape making with mapped element name from shape history
*
* @param mkShape: OCCT shape maker.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Returns the new shape built by the shape maker with mappend element
* name generated using this shape as the source. The shape itself
* is not modified.
*/
TopoShape makeElementShape(BRepBuilderAPI_MakeShape& mkShape, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementShape(mkShape, *this, op);
}
/** Specialized shape making for BRepBuilderAPI_Sewing with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param sources: list of source shapes.
* @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 built by the shape maker. 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& makeElementShape(BRepBuilderAPI_Sewing& mkShape,
const std::vector<TopoShape>& sources,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_Sewing with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param source: source shape.
* @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 built by the shape maker. 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& makeElementShape(BRepBuilderAPI_Sewing& mkShape,
const TopoShape& source,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_Sewing with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Returns the new shape built by the shape maker with mappend element
* name generated using this shape as the source. The shape itself
* is not modified.
*/
TopoShape makeElementShape(BRepBuilderAPI_Sewing& mkShape, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementShape(mkShape, *this, op);
}
/** Specialized shape making for BRepBuilderAPI_ThruSections with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param sources: list of source shapes.
* @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 built by the shape maker. 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& makeElementShape(BRepOffsetAPI_ThruSections& mkShape,
const std::vector<TopoShape>& sources,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_Sewing with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param source: source shape.
* @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 built by the shape maker. 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& makeElementShape(BRepOffsetAPI_ThruSections& mkShape,
const TopoShape& source,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_Sewing with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Returns the new shape built by the shape maker with mappend element
* name generated using this shape as the source. The shape itself
* is not modified.
*/
TopoShape makeElementShape(BRepOffsetAPI_ThruSections& mkShape, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementShape(mkShape, *this, op);
}
/** Specialized shape making for BRepBuilderAPI_MakePipeShell with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param sources: list of source shapes.
* @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 built by the shape maker. 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& makeElementShape(BRepOffsetAPI_MakePipeShell& mkShape,
const std::vector<TopoShape>& sources,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_MakeHalfSpace with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param source: source shape.
* @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 built by the shape maker. 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& makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape,
const TopoShape& source,
const char* op = nullptr);
/** Specialized shape making for BRepBuilderAPI_MakeHalfSpace with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param op: optional string to be encoded into topo naming for indicating
* the operation
*
* @return Returns the new shape built by the shape maker with mappend element
* name generated using this shape as the source. The shape itself
* is not modified.
*/
TopoShape makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape, const char* op = nullptr) const
{
return TopoShape(0, Hasher).makeElementShape(mkShape, *this, op);
}
/** Specialized shape making for BRepBuilderAPI_MakePrism with mapped element name
*
* @param mkShape: OCCT shape maker.
* @param sources: list of source shapes.
* @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 built by the shape maker. 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& makeElementShape(BRepFeat_MakePrism& mkShape,
const std::vector<TopoShape>& sources,
const TopoShape& uptoface,
const char* op);
/** Helper class to return the generated and modified shape given an input shape
*
* Shape history information is extracted using OCCT APIs
* BRepBuilderAPI_MakeShape::Generated/Modified(). However, there is often
* some glitches in various derived class. So we use this class as an
* abstraction, and create various derived classes to deal with the glitches.
*/
friend class TopoShapeCache;
private:
@@ -956,6 +1177,22 @@ private:
};
/** Shape mapper for generic BRepBuilderAPI_MakeShape derived class
*
* Uses BRepBuilderAPI_MakeShape::Modified/Generated() function to extract
* shape history for generating mapped element names
*/
struct PartExport MapperMaker: TopoShape::Mapper
{
BRepBuilderAPI_MakeShape& maker;
explicit MapperMaker(BRepBuilderAPI_MakeShape& maker)
: maker(maker)
{}
const std::vector<TopoDS_Shape>& modified(const TopoDS_Shape& s) const override;
const std::vector<TopoDS_Shape>& generated(const TopoDS_Shape& s) const override;
};
} // namespace Part

View File

@@ -42,12 +42,11 @@
#include "TopoShape.h"
#include "TopoShapeCache.h"
#include "TopoShapeOpCode.h"
#include "FaceMaker.h"
#include "TopoShapeOpCode.h"
#include <App/ElementNamingUtils.h>
FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT
namespace Part
@@ -263,6 +262,7 @@ size_t checkSubshapeCount(const TopoShape& topoShape1,
}
return count;
}
} // namespace
void TopoShape::setupChild(Data::ElementMap::MappedChildElements& child,
@@ -316,7 +316,7 @@ void warnIfLogging()
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("hasher mismatch"); // NOLINT
}
};
}
void hasherMismatchError()
{
@@ -392,11 +392,11 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other,
sids.clear();
}
}
std::ostringstream ss;
char elementType {shapeName(type)[0]};
if (!elementMap()) {
FC_THROWM(NullShapeException, "No element map"); // NOLINT
}
std::ostringstream ss;
elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag);
elementMap()->setElementName(element, name, Tag, &sids);
}
@@ -530,7 +530,7 @@ struct ShapeInfo
, shapetype(TopoShape::shapeName(type).c_str())
{}
int count() const
[[nodiscard]] int count() const
{
return cache.count();
}
@@ -1298,6 +1298,142 @@ TopoShape::makeElementCompound(const std::vector<TopoShape>& shapes, const char*
return *this;
}
struct MapperSewing: Part::TopoShape::Mapper
{
BRepBuilderAPI_Sewing& maker;
explicit MapperSewing(BRepBuilderAPI_Sewing& maker)
: maker(maker)
{}
const std::vector<TopoDS_Shape>& modified(const TopoDS_Shape& s) const override
{
_res.clear();
try {
const auto& shape = maker.Modified(s);
if (!shape.IsNull() && !shape.IsSame(s)) {
_res.push_back(shape);
}
else {
const auto& sshape = maker.ModifiedSubShape(s);
if (!sshape.IsNull() && !sshape.IsSame(s)) {
_res.push_back(sshape);
}
}
}
catch (const Standard_Failure& e) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Exception on shape mapper: " << e.GetMessageString());
}
}
return _res;
}
};
struct MapperThruSections: MapperMaker
{
TopoShape firstProfile;
TopoShape lastProfile;
MapperThruSections(BRepOffsetAPI_ThruSections& tmaker, const std::vector<TopoShape>& profiles)
: MapperMaker(tmaker)
{
if (!tmaker.FirstShape().IsNull()) {
firstProfile = profiles.front();
}
if (!tmaker.LastShape().IsNull()) {
lastProfile = profiles.back();
}
}
const std::vector<TopoDS_Shape>& generated(const TopoDS_Shape& s) const override
{
MapperMaker::generated(s);
if ( ! _res.empty()) {
return _res;
}
try {
auto& tmaker = dynamic_cast<BRepOffsetAPI_ThruSections&>(maker);
auto shape = tmaker.GeneratedFace(s);
if (!shape.IsNull()) {
_res.push_back(shape);
}
if (firstProfile.getShape().IsSame(s) || firstProfile.findShape(s)) {
_res.push_back(tmaker.FirstShape());
}
else if (lastProfile.getShape().IsSame(s) || lastProfile.findShape(s)) {
_res.push_back(tmaker.LastShape());
}
}
catch (const Standard_Failure& e) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Exception on shape mapper: " << e.GetMessageString());
}
}
return _res;
}
};
TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const TopoShape& source,
const char* op)
{
std::vector<TopoShape> sources(1, source);
return makeElementShape(mkShape, sources, op);
}
TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape,
const std::vector<TopoShape>& shapes,
const char* op)
{
return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op);
}
TopoShape&
TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk, const TopoShape& source, const char* op)
{
if (!op) {
op = Part::OpCodes::ThruSections;
}
return makeElementShape(mk, std::vector<TopoShape>(1, source), op);
}
TopoShape& TopoShape::makeElementShape(BRepOffsetAPI_ThruSections& mk,
const std::vector<TopoShape>& sources,
const char* op)
{
if (!op) {
op = Part::OpCodes::ThruSections;
}
return makeShapeWithElementMap(mk.Shape(), MapperThruSections(mk, sources), sources, op);
}
TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mk,
const std::vector<TopoShape>& shapes,
const char* op)
{
if (!op) {
op = Part::OpCodes::Sewing;
}
return makeShapeWithElementMap(mk.SewedShape(), MapperSewing(mk), shapes, op);
}
TopoShape&
TopoShape::makeElementShape(BRepBuilderAPI_Sewing& mkShape, const TopoShape& source, const char* op)
{
if (!op) {
op = Part::OpCodes::Sewing;
}
return makeElementShape(mkShape, std::vector<TopoShape>(1, source), op);
}
TopoShape& TopoShape::makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape,
const TopoShape& source,
const char* op)
{
if (!op) {
op = Part::OpCodes::HalfSpace;
}
return makeShapeWithElementMap(mkShape.Solid(), MapperMaker(mkShape), {source}, op);
}
TopoShape& TopoShape::makeElementFace(const TopoShape& shape,
const char* op,
@@ -1395,7 +1531,7 @@ Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element
const Data::ElementIDRefs* _sids)
{
if (names.empty()) {
return Data::MappedName();
return Data::MappedName {};
}
std::string _marker;
if (!marker) {
@@ -1474,7 +1610,7 @@ TopoShape TopoShape::splitWires(std::vector<TopoShape>* inner, SplitWireReorient
tmp = BRepTools::OuterWire(TopoDS::Face(getSubShape(TopAbs_FACE, 1)));
}
if (tmp.IsNull()) {
return TopoShape();
return TopoShape {};
}
const auto& wires = getSubTopoShapes(TopAbs_WIRE);
auto it = wires.begin();
@@ -1541,7 +1677,7 @@ TopoShape TopoShape::splitWires(std::vector<TopoShape>* inner, SplitWireReorient
}
}
}
return TopoShape();
return TopoShape {};
}
struct MapperFill: Part::TopoShape::Mapper
@@ -1568,6 +1704,40 @@ struct MapperFill: Part::TopoShape::Mapper
}
};
const std::vector<TopoDS_Shape>& MapperMaker::modified(const TopoDS_Shape& s) const
{
_res.clear();
try {
TopTools_ListIteratorOfListOfShape it;
for (it.Initialize(maker.Modified(s)); it.More(); it.Next()) {
_res.push_back(it.Value());
}
}
catch (const Standard_Failure& e) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Exception on shape mapper: " << e.GetMessageString());
}
}
return _res;
}
const std::vector<TopoDS_Shape>& MapperMaker::generated(const TopoDS_Shape& s) const
{
_res.clear();
try {
TopTools_ListIteratorOfListOfShape it;
for (it.Initialize(maker.Generated(s)); it.More(); it.Next()) {
_res.push_back(it.Value());
}
}
catch (const Standard_Failure& e) {
if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
FC_WARN("Exception on shape mapper: " << e.GetMessageString());
}
}
return _res;
}
// topo naming counterpart of TopoShape::makeShell()
TopoShape& TopoShape::makeElementShell(bool silent, const char* op)
{
@@ -1659,26 +1829,26 @@ TopoShape& TopoShape::makeElementShell(bool silent, const char* op)
return *this;
}
// TopoShape& TopoShape::makeElementShellFromWires(const std::vector<TopoShape>& wires,
// bool silent,
// const char* op)
// {
// BRepFill_Generator maker;
// for (auto& w : wires) {
// if (w.shapeType(silent) == TopAbs_WIRE) {
// maker.AddWire(TopoDS::Wire(w.getShape()));
// }
// }
// if (wires.empty()) {
// if (silent) {
// _Shape.Nullify();
// return *this;
// }
// FC_THROWM(NullShapeException, "No input shapes");
// }
// maker.Perform();
// this->makeShapeWithElementMap(maker.Shell(), MapperFill(maker), wires, op);
// return *this;
// }
TopoShape& TopoShape::makeElementShellFromWires(const std::vector<TopoShape>& wires,
bool silent,
const char* op)
{
BRepFill_Generator maker;
for (auto& w : wires) {
if (w.shapeType(silent) == TopAbs_WIRE) {
maker.AddWire(TopoDS::Wire(w.getShape()));
}
}
if (wires.empty()) {
if (silent) {
_Shape.Nullify();
return *this;
}
FC_THROWM(NullShapeException, "No input shapes");
}
maker.Perform();
this->makeShapeWithElementMap(maker.Shell(), MapperFill(maker), wires, op);
return *this;
}
} // namespace Part

View File

@@ -17,4 +17,5 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShape.cpp
)

View File

@@ -0,0 +1,129 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
// Tests for the makeShape methods, extracted from the main set of tests for TopoShape
// due to length and complexity.
#include "gtest/gtest.h"
#include "src/App/InitApplication.h"
#include "PartTestHelpers.h"
#include <Mod/Part/App/TopoShape.h>
#include <BRepBuilderAPI_MakeVertex.hxx>
using namespace Data;
using namespace Part;
using namespace PartTestHelpers;
class TopoShapeMakeShapeTests: public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
_docName = App::GetApplication().getUniqueDocumentName("test");
App::GetApplication().newDocument(_docName.c_str(), "testUser");
_sids = &_sid;
}
void TearDown() override
{
App::GetApplication().closeDocument(_docName.c_str());
}
Part::TopoShape* Shape()
{
return &_shape;
}
Part::TopoShape::Mapper* Mapper()
{
return &_mapper;
}
private:
std::string _docName;
Data::ElementIDRefs _sid;
QVector<App::StringIDRef>* _sids = nullptr;
Part::TopoShape _shape;
Part::TopoShape::Mapper _mapper;
};
TEST_F(TopoShapeMakeShapeTests, nullShapeThrows)
{
// Arrange
auto [cube1, cube2] = CreateTwoCubes();
std::vector<Part::TopoShape> sources {cube1, cube2};
TopoDS_Vertex nullShape;
// Act and assert
EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShape, *Mapper(), sources),
Part::NullShapeException);
}
TEST_F(TopoShapeMakeShapeTests, shapeVertex)
{
// Arrange
BRepBuilderAPI_MakeVertex vertexMaker = BRepBuilderAPI_MakeVertex(gp_Pnt(10, 10, 10));
TopoShape topoShape(vertexMaker.Vertex(), 1L);
// Act
TopoShape& result = topoShape.makeElementShape(vertexMaker, topoShape);
auto elements = elementMap(result);
// Assert
EXPECT_EQ(elements.size(), 1);
EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1);
EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;MAK;:H:4,V"));
EXPECT_EQ(getArea(result.getShape()), 0);
}
TEST_F(TopoShapeMakeShapeTests, thruSections)
{
// Arrange
auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace();
TopoDS_Wire wire2 = wire1;
auto transform {gp_Trsf()};
transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.0, 0.5, 1.0));
wire2.Move(TopLoc_Location(transform));
TopoShape wire1ts {wire1, 1L};
TopoShape wire2ts {wire2, 2L};
BRepOffsetAPI_ThruSections thruMaker;
thruMaker.AddWire(wire1);
thruMaker.AddWire(wire2);
TopoShape topoShape {};
// Act
TopoShape& result = topoShape.makeElementShape(thruMaker, {wire1ts, wire2ts});
auto elements = elementMap(result);
// Assert
EXPECT_EQ(elements.size(), 24);
EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1);
EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;TRU;:H1:4,V"));
EXPECT_EQ(getVolume(result.getShape()), 4);
}
TEST_F(TopoShapeMakeShapeTests, sewing)
{
// Arrange
auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace();
auto face2 = face1;
auto transform {gp_Trsf()};
transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0));
face2.Move(TopLoc_Location(transform));
BRepBuilderAPI_Sewing sewer;
sewer.Add(face1);
sewer.Add(face2);
sewer.Perform();
std::vector<TopoShape> sources {{face1, 1L}, {face2, 2L}};
TopoShape topoShape {};
// Act
TopoShape& result = topoShape.makeElementShape(sewer, sources);
auto elements = elementMap(result);
// Assert
EXPECT_EQ(&result, &topoShape);
EXPECT_EQ(elements.size(), 18); // Now a single cube
EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1);
EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;SEW;:H1:4,V"));
EXPECT_EQ(getArea(result.getShape()), 12);
}