Merge branch 'FreeCAD:main' into fem_ccx_incrementation

This commit is contained in:
FEA-eng
2024-02-29 09:17:47 +01:00
committed by GitHub
10 changed files with 627 additions and 25 deletions

View File

@@ -25,6 +25,7 @@
#include <App/GeoFeaturePy.h>
#include "ComplexGeoData.h"
#include "GeoFeature.h"
#include "GeoFeatureGroupExtension.h"
#include "ElementNamingUtils.h"
@@ -78,17 +79,52 @@ PyObject* GeoFeature::getPyObject()
return Py::new_reference_to(PythonObject);
}
std::pair<std::string,std::string> GeoFeature::getElementName(
const char *name, ElementNameType type) const
std::pair<std::string,std::string>
GeoFeature::getElementName(const char *name, ElementNameType type) const
{
(void)type;
std::pair<std::string,std::string> ret;
if(!name)
return ret;
auto prop = getPropertyOfGeometry();
if (!prop) {
return std::make_pair("", name);
}
auto geo = prop->getComplexData();
if (!geo) {
return std::make_pair("", name);
}
return _getElementName(name, geo->getElementName(name));
}
std::pair<std::string, std::string>
GeoFeature::_getElementName(const char* name, const Data::MappedElement& mapped) const
{
std::pair<std::string, std::string> ret;
if (mapped.index && mapped.name) {
std::ostringstream ss;
ss << Data::ComplexGeoData::elementMapPrefix() << mapped.name << '.' << mapped.index;
ret.first = ss.str();
mapped.index.appendToStringBuffer(ret.second);
}
else if (mapped.name) {
// FC_TRACE("element mapped name " << name << " not found in " << getFullName());
ret.first = name;
const char* dot = strrchr(name, '.');
if (dot) {
// deliberately mangle the old style element name to signal a
// missing reference
ret.second = Data::MISSING_PREFIX;
ret.second += dot + 1;
}
}
else {
mapped.index.appendToStringBuffer(ret.second);
}
ret.second = name;
return ret;
}

View File

@@ -26,7 +26,7 @@
#include "DocumentObject.h"
#include "PropertyGeo.h"
#include "MappedElement.h"
namespace App
{
@@ -120,6 +120,10 @@ public:
* @return Base::Placement The transformation from the global reference coordinate system
*/
Base::Placement globalPlacement() const;
protected:
std::pair<std::string, std::string> _getElementName(const char* name,
const Data::MappedElement& mapped) const;
};
} //namespace App

View File

@@ -757,3 +757,235 @@ bool Part::checkIntersection(const TopoDS_Shape& first, const TopoDS_Shape& seco
}
}
/**
* Override getElementName to support the Export type. Other calls are passed to the original
* method
* @param name The name to search for, or if non existent, name of current Feature is returned
* @param type An element type name.
* @return The element name located, of
*/
std::pair<std::string, std::string> Feature::getElementName(const char* name,
ElementNameType type) const
{
if (type != ElementNameType::Export) {
return App::GeoFeature::getElementName(name, type);
}
// This function is overridden to provide higher level shape topo names that
// are generated on demand, e.g. Wire, Shell, Solid, etc.
auto prop = Base::freecad_dynamic_cast<PropertyPartShape>(getPropertyOfGeometry());
if (!prop) {
return App::GeoFeature::getElementName(name, type);
}
TopoShape shape = prop->getShape();
Data::MappedElement mapped = shape.getElementName(name);
auto res = shape.shapeTypeAndIndex(mapped.index);
static const int MinLowerTopoNames = 3;
static const int MaxLowerTopoNames = 10;
if (res.second && !mapped.name) {
// Here means valid index name, but no mapped name, check to see if
// we shall generate the high level topo name.
//
// The general idea of the algorithm is to find the minimum number of
// lower elements that can identify the given higher element, and
// combine their names to generate the name for the higher element.
//
// In theory, all it takes to find one lower element that only appear
// in the given higher element. To make the algorithm more robust
// against model changes, we shall take minimum MinLowerTopoNames lower
// elements.
//
// On the other hand, it may be possible to take too many elements for
// disambiguation. We shall limit to maximum MaxLowerTopoNames. If the
// chosen elements are not enough to disambiguate the higher element,
// we'll include an index for disambiguation.
auto subshape = shape.getSubTopoShape(res.first, res.second, true);
TopAbs_ShapeEnum lower;
Data::IndexedName idxName;
if (!subshape.isNull()) {
switch (res.first) {
case TopAbs_WIRE:
lower = TopAbs_EDGE;
idxName = Data::IndexedName::fromConst("Edge", 1);
break;
case TopAbs_SHELL:
case TopAbs_SOLID:
case TopAbs_COMPOUND:
case TopAbs_COMPSOLID:
lower = TopAbs_FACE;
idxName = Data::IndexedName::fromConst("Face", 1);
break;
default:
lower = TopAbs_SHAPE;
}
if (lower != TopAbs_SHAPE) {
typedef std::pair<size_t, std::vector<int>> NameEntry;
std::vector<NameEntry> indices;
std::vector<Data::MappedName> names;
std::vector<int> ancestors;
int count = 0;
for (auto& ss : subshape.getSubTopoShapes(lower)) {
auto name = ss.getMappedName(idxName);
if (!name) {
continue;
}
indices.emplace_back(name.size(),
shape.findAncestors(ss.getShape(), res.first));
names.push_back(name);
if (indices.back().second.size() == 1 && ++count >= MinLowerTopoNames) {
break;
}
}
if (names.size() >= MaxLowerTopoNames) {
std::stable_sort(indices.begin(),
indices.end(),
[](const NameEntry& a, const NameEntry& b) {
return a.second.size() < b.second.size();
});
std::vector<Data::MappedName> sorted;
auto pos = 0;
sorted.reserve(names.size());
for (auto& v : indices) {
size_t size = ancestors.size();
if (size == 0) {
ancestors = v.second;
}
else if (size > 1) {
for (auto it = ancestors.begin(); it != ancestors.end();) {
if (std::find(v.second.begin(), v.second.end(), *it)
== v.second.end()) {
it = ancestors.erase(it);
if (ancestors.size() == 1) {
break;
}
}
else {
++it;
}
}
}
auto itPos = sorted.end();
if (size == 1 || size != ancestors.size()) {
itPos = sorted.begin() + pos;
++pos;
}
sorted.insert(itPos, names[v.first]);
if (size == 1 && sorted.size() >= MinLowerTopoNames) {
break;
}
}
}
names.resize(std::min((int)names.size(), MaxLowerTopoNames));
if (names.size()) {
std::string op;
if (ancestors.size() > 1) {
// The current chosen elements are not enough to
// identify the higher element, generate an index for
// disambiguation.
auto it = std::find(ancestors.begin(), ancestors.end(), res.second);
if (it == ancestors.end()) {
assert(0 && "ancestor not found"); // this shouldn't happened
}
else {
op = Data::POSTFIX_TAG + std::to_string(it - ancestors.begin());
}
}
// Note: setting names to shape will change its underlying
// shared element name table. This actually violates the
// const'ness of this function.
//
// To be const correct, we should have made the element
// name table to be implicit sharing (i.e. copy on change).
//
// Not sure if there is any side effect of indirectly
// change the element map inside the Shape property without
// recording the change in undo stack.
//
mapped.name = shape.setElementComboName(mapped.index,
names,
mapped.index.getType(),
op.c_str());
}
}
}
return App::GeoFeature::_getElementName(name, mapped);
}
if (!res.second && mapped.name) {
const char* dot = strchr(name, '.');
if (dot) {
++dot;
// Here means valid mapped name, but cannot find the corresponding
// indexed name. This usually means the model has been changed. The
// original indexed name is usually appended to the mapped name
// separated by a dot. We use it as a clue to decode the combo name
// set above, and try to single out one sub shape that has all the
// lower elements encoded in the combo name. But since we don't
// always use all the lower elements for encoding, this can only be
// consider a heuristics.
if (Data::hasMissingElement(dot)) {
dot += strlen(Data::MISSING_PREFIX);
}
std::pair<TopAbs_ShapeEnum, int> occindex = shape.shapeTypeAndIndex(dot);
if (occindex.second > 0) {
auto idxName = Data::IndexedName::fromConst(shape.shapeName(occindex.first).c_str(),
occindex.second);
std::string postfix;
auto names =
shape.decodeElementComboName(idxName, mapped.name, idxName.getType(), &postfix);
std::vector<int> ancestors;
for (auto& name : names) {
auto index = shape.getIndexedName(name);
if (!index) {
ancestors.clear();
break;
}
auto oidx = shape.shapeTypeAndIndex(index);
auto subshape = shape.getSubShape(oidx.first, oidx.second);
if (subshape.IsNull()) {
ancestors.clear();
break;
}
auto current = shape.findAncestors(subshape, occindex.first);
if (ancestors.empty()) {
ancestors = std::move(current);
}
else {
for (auto it = ancestors.begin(); it != ancestors.end();) {
if (std::find(current.begin(), current.end(), *it) == current.end()) {
it = ancestors.erase(it);
}
else {
++it;
}
}
if (ancestors.empty()) { // model changed beyond recognition, bail!
break;
}
}
}
if (ancestors.size() > 1 && boost::starts_with(postfix, Data::POSTFIX_INDEX)) {
std::istringstream iss(postfix.c_str() + strlen(Data::POSTFIX_INDEX));
int idx;
if (iss >> idx && idx >= 0 && idx < (int)ancestors.size()) {
ancestors.resize(1, ancestors[idx]);
}
}
if (ancestors.size() == 1) {
idxName.setIndex(ancestors.front());
mapped.index = idxName;
return App::GeoFeature::_getElementName(name, mapped);
}
}
}
}
return App::GeoFeature::getElementName(name, type);
}

View File

@@ -64,6 +64,9 @@ public:
PyObject* getPyObject() override;
std::pair<std::string,std::string> getElementName(
const char *name, ElementNameType type=Normal) const override;
TopLoc_Location getLocation() const;
DocumentObject *getSubObject(const char *subname, PyObject **pyObj,

View File

@@ -477,6 +477,22 @@ std::pair<TopAbs_ShapeEnum,int> TopoShape::shapeTypeAndIndex(const char *name) {
return std::make_pair(type,idx);
}
std::pair<TopAbs_ShapeEnum, int> TopoShape::shapeTypeAndIndex(const Data::IndexedName& element)
{
if (!element) {
return std::make_pair(TopAbs_SHAPE, 0);
}
static const std::string _subshape("SubShape");
if (boost::equals(element.getType(), _subshape)) {
return std::make_pair(TopAbs_SHAPE, element.getIndex());
}
TopAbs_ShapeEnum shapetype = shapeType(element.getType(), true);
if (shapetype == TopAbs_SHAPE) {
return std::make_pair(TopAbs_SHAPE, 0);
}
return std::make_pair(shapetype, element.getIndex());
}
TopAbs_ShapeEnum TopoShape::shapeType(const char *type, bool silent) {
if(type) {
initShapeNameMap();

View File

@@ -1040,7 +1040,52 @@ public:
const char* op = nullptr,
double tol3d = 0.0,
double tolBound = 0.0,
double tolAngluar = 0.0);
double tolAngular = 0.0);
/* Make a shape with some subshapes replaced.
*
* @param source: the source shape
* @param s: replacement mapping the existing sub shape of source to new shapes
*
* @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& replaceElementShape(const TopoShape& source,
const std::vector<std::pair<TopoShape, TopoShape>>& s);
/* Make a new shape using this shape with some subshapes replaced by others
*
* @param s: replacement mapping the existing sub shape of source to new shapes
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape replaceElementShape(const std::vector<std::pair<TopoShape, TopoShape>>& s) const
{
return TopoShape(0, Hasher).replaceElementShape(*this, s);
}
/* Make a shape with some subshapes removed
*
* @param source: the source shape
* @param s: the subshapes to be removed
*
* @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& removeElementShape(const TopoShape& source, const std::vector<TopoShape>& s);
/* Make a new shape using this shape with some subshapes removed
*
* @param s: the subshapes to be removed
*
* @return Return the new shape. The TopoShape itself is not modified.
*/
TopoShape removeElementShape(const std::vector<TopoShape>& s) const
{
return TopoShape(0, Hasher).removeElementShape(*this, s);
}
/** Make shape using generalized fusion and return the modified sub shapes
*
@@ -1131,13 +1176,19 @@ public:
static const std::string& shapeName(TopAbs_ShapeEnum type, bool silent = false);
const std::string& shapeName(bool silent = false) const;
static std::pair<TopAbs_ShapeEnum, int> shapeTypeAndIndex(const char* name);
static std::pair<TopAbs_ShapeEnum, int> shapeTypeAndIndex(const Data::IndexedName &name);
Data::MappedName setElementComboName(const Data::IndexedName & element,
Data::MappedName setElementComboName(const Data::IndexedName & element,
const std::vector<Data::MappedName> &names,
const char *marker=nullptr,
const char *op=nullptr,
const Data::ElementIDRefs *sids=nullptr);
std::vector<Data::MappedName> decodeElementComboName(const Data::IndexedName& element,
const Data::MappedName& name,
const char* marker = nullptr,
std::string* postfix = nullptr) const;
/** @name sub shape cached functions
*
* Mapped element names introduces some overhead when getting sub shapes

View File

@@ -3059,7 +3059,7 @@ 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) {
@@ -3108,6 +3108,48 @@ TopoShape& TopoShape::makeElementSlices(const TopoShape& shape,
return makeElementCompound(wires, op, SingleShapeCompoundCreationPolicy::returnShape);
}
TopoShape& TopoShape::replaceElementShape(const TopoShape& shape,
const std::vector<std::pair<TopoShape, TopoShape>>& s)
{
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
BRepTools_ReShape reshape;
std::vector<TopoShape> shapes;
shapes.reserve(s.size() + 1);
for (auto& v : s) {
if (v.first.isNull() || v.second.isNull()) {
FC_THROWM(NullShapeException, "Null input shape");
}
reshape.Replace(v.first.getShape(), v.second.getShape());
shapes.push_back(v.second);
}
// TODO: This does not work when replacing a shape in a compound. Should we replace with
// something else?
// Note that remove works with a compound.
shapes.push_back(shape);
setShape(reshape.Apply(shape.getShape(), TopAbs_SHAPE));
mapSubElement(shapes);
return *this;
}
TopoShape& TopoShape::removeElementShape(const TopoShape& shape, const std::vector<TopoShape>& s)
{
if (shape.isNull()) {
FC_THROWM(NullShapeException, "Null shape");
}
BRepTools_ReShape reshape;
for (auto& sh : s) {
if (sh.isNull()) {
FC_THROWM(NullShapeException, "Null input shape");
}
reshape.Remove(sh.getShape());
}
setShape(reshape.Apply(shape.getShape(), TopAbs_SHAPE));
mapSubElement(shape);
return *this;
}
TopoShape& TopoShape::makeElementFillet(const TopoShape& shape,
const std::vector<TopoShape>& edges,
double radius1,
@@ -4054,6 +4096,97 @@ Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element
return elementMap()->setElementName(element, newName, Tag, &sids);
}
std::vector<Data::MappedName> TopoShape::decodeElementComboName(const Data::IndexedName& element,
const Data::MappedName& name,
const char* marker,
std::string* postfix) const
{
std::vector<Data::MappedName> names;
if (!element) {
return names;
}
if (!marker) {
marker = "";
}
int plen = (int)elementMapPrefix().size();
int markerLen = strlen(marker);
int len;
int pos = name.findTagInElementName(nullptr, &len);
if (pos < 0) {
// It is possible to encode combo name without using a tag, e.g.
// Sketcher object creates wire using edges that are created by itself,
// so there will be no tag to encode.
//
// In this case, just search for the brackets
len = name.find("(");
if (len < 0) {
// No bracket is also possible, if there is only one name in the combo
pos = len = name.size();
}
else {
pos = name.find(")");
if (pos < 0) {
// non closing bracket?
return {};
}
++pos;
}
if (len <= (int)markerLen) {
return {};
}
len -= markerLen + plen;
}
if (name.find(elementMapPrefix(), len) != len || name.find(marker, len + plen) != len + plen) {
return {};
}
names.emplace_back(name, 0, len);
std::string text;
len += plen + markerLen;
name.appendToBuffer(text, len, pos - len);
if (this->Hasher) {
if (auto id = App::StringID::fromString(names.back().toRawBytes())) {
if (App::StringIDRef sid = this->Hasher->getID(id)) {
names.pop_back();
names.emplace_back(sid);
}
else {
return names;
}
}
if (auto id = App::StringID::fromString(text.c_str())) {
if (App::StringIDRef sid = this->Hasher->getID(id)) {
text = sid.dataToText();
}
else {
return names;
}
}
}
if (text.empty() || text[0] != '(') {
return names;
}
auto endPos = text.rfind(')');
if (endPos == std::string::npos) {
return names;
}
if (postfix) {
*postfix = text.substr(endPos + 1);
}
text.resize(endPos);
std::istringstream iss(text.c_str() + 1);
std::string token;
while (std::getline(iss, token, '|')) {
names.emplace_back(token);
}
return names;
}
/**
* Reorient the outer and inner wires of the TopoShape
*

View File

@@ -11,6 +11,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartCut.cpp
${CMAKE_CURRENT_SOURCE_DIR}/FeaturePartFuse.cpp
${CMAKE_CURRENT_SOURCE_DIR}/FeatureRevolution.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PartFeature.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PartFeatures.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PartTestHelpers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "gtest/gtest.h"
#include "Mod/Part/App/FeaturePartCommon.h"
#include <src/App/InitApplication.h>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include "PartTestHelpers.h"
using namespace Part;
using namespace PartTestHelpers;
class FeaturePartTest: public ::testing::Test, public PartTestHelperClass
{
protected:
static void SetUpTestSuite()
{
tests::initApplication();
}
void SetUp() override
{
createTestDoc();
_common = dynamic_cast<Common*>(_doc->addObject("Part::Common"));
}
void TearDown() override
{}
Common* _common = nullptr; // NOLINT Can't be private in a test framework
};
TEST_F(FeaturePartTest, testGetElementName)
{
// Arrange
_boxes[0]->Shape.getShape().Tag = 1L;
_boxes[1]->Shape.getShape().Tag = 2L;
_common->Base.setValue(_boxes[0]);
_common->Tool.setValue(_boxes[1]);
// Act
_common->execute();
const TopoShape& ts = _common->Shape.getShape();
auto namePair = _common->getElementName("test");
auto namePairExport = _common->getElementName("test", App::GeoFeature::Export);
auto namePairSelf = _common->getElementName(nullptr);
// Assert
EXPECT_STREQ(namePair.first.c_str(), "");
EXPECT_STREQ(namePair.second.c_str(), "test");
EXPECT_STREQ(namePairExport.first.c_str(), "");
EXPECT_STREQ(namePairExport.second.c_str(), "test");
EXPECT_STREQ(namePairSelf.first.c_str(), "");
EXPECT_STREQ(namePairSelf.second.c_str(), "");
#ifndef FC_USE_TNP_FIX
EXPECT_EQ(ts.getElementMap().size(), 0);
#else
EXPECT_EQ(ts.getElementMap().size(), 26); // Value and code TBD
#endif
// TBD
}

View File

@@ -172,24 +172,24 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes)
EXPECT_TRUE(
allElementsMatch(topoShape,
{
"Edge1;:H1,E;:H7,E", "Edge2;:H1,E;:H7,E", "Edge3;:H1,E;:H7,E",
"Edge4;:H1,E;:H7,E", "Edge1;:H2,E;:H7,E", "Edge2;:H2,E;:H7,E",
"Edge3;:H2,E;:H7,E", "Edge4;:H2,E;:H7,E", "Edge1;:H3,E;:H7,E",
"Edge2;:H3,E;:H7,E", "Edge3;:H3,E;:H7,E", "Edge4;:H3,E;:H7,E",
"Edge1;:H8,E;:He,E", "Edge2;:H8,E;:He,E", "Edge3;:H8,E;:He,E",
"Edge4;:H8,E;:He,E", "Edge1;:H9,E;:He,E", "Edge2;:H9,E;:He,E",
"Edge3;:H9,E;:He,E", "Edge4;:H9,E;:He,E", "Edge1;:Ha,E;:He,E",
"Edge2;:Ha,E;:He,E", "Edge3;:Ha,E;:He,E", "Edge4;:Ha,E;:He,E",
"Vertex1;:H8,V;:He,V", "Vertex2;:H8,V;:He,V", "Vertex3;:H8,V;:He,V",
"Vertex4;:H8,V;:He,V", "Vertex1;:H9,V;:He,V", "Vertex2;:H9,V;:He,V",
"Vertex3;:H9,V;:He,V", "Vertex4;:H9,V;:He,V", "Face1;:H1,F;:H7,F",
"Face1;:H2,F;:H7,F", "Face1;:H3,F;:H7,F", "Face1;:H4,F;:H7,F",
"Face1;:H5,F;:H7,F", "Face1;:H6,F;:H7,F", "Face1;:H8,F;:He,F",
"Vertex1;:H1,V;:H7,V", "Vertex2;:H1,V;:H7,V", "Vertex3;:H1,V;:H7,V",
"Vertex4;:H1,V;:H7,V", "Vertex1;:H2,V;:H7,V", "Vertex2;:H2,V;:H7,V",
"Vertex3;:H2,V;:H7,V", "Vertex4;:H2,V;:H7,V", "Face1;:H8,F;:He,F",
"Face1;:H9,F;:He,F", "Face1;:Ha,F;:He,F", "Face1;:Hb,F;:He,F",
"Face1;:Hc,F;:He,F", "Face1;:Hd,F;:He,F", "Vertex1;:H1,V;:H7,V",
"Vertex2;:H1,V;:H7,V", "Vertex3;:H1,V;:H7,V", "Vertex4;:H1,V;:H7,V",
"Vertex1;:H2,V;:H7,V", "Vertex2;:H2,V;:H7,V", "Vertex3;:H2,V;:H7,V",
"Vertex4;:H2,V;:H7,V",
"Face1;:Hc,F;:He,F", "Face1;:Hd,F;:He,F", "Edge1;:H8,E;:He,E",
"Edge2;:H8,E;:He,E", "Edge3;:H8,E;:He,E", "Edge4;:H8,E;:He,E",
"Edge1;:H9,E;:He,E", "Edge2;:H9,E;:He,E", "Edge3;:H9,E;:He,E",
"Edge4;:H9,E;:He,E", "Edge1;:Ha,E;:He,E", "Edge2;:Ha,E;:He,E",
"Edge3;:Ha,E;:He,E", "Edge4;:Ha,E;:He,E", "Vertex1;:H8,V;:He,V",
"Vertex2;:H8,V;:He,V", "Vertex3;:H8,V;:He,V", "Vertex4;:H8,V;:He,V",
"Vertex1;:H9,V;:He,V", "Vertex2;:H9,V;:He,V", "Vertex3;:H9,V;:He,V",
"Vertex4;:H9,V;:He,V", "Edge1;:H1,E;:H7,E", "Edge2;:H1,E;:H7,E",
"Edge3;:H1,E;:H7,E", "Edge4;:H1,E;:H7,E", "Edge1;:H2,E;:H7,E",
"Edge2;:H2,E;:H7,E", "Edge3;:H2,E;:H7,E", "Edge4;:H2,E;:H7,E",
"Edge1;:H3,E;:H7,E", "Edge2;:H3,E;:H7,E", "Edge3;:H3,E;:H7,E",
"Edge4;:H3,E;:H7,E", "Face1;:H1,F;:H7,F", "Face1;:H2,F;:H7,F",
"Face1;:H3,F;:H7,F", "Face1;:H4,F;:H7,F", "Face1;:H5,F;:H7,F",
"Face1;:H6,F;:H7,F",
}));
}
@@ -2282,4 +2282,68 @@ TEST_F(TopoShapeExpansionTest, makeElementBSplineFace)
}));
}
TEST_F(TopoShapeExpansionTest, replaceElementShape)
{
// Arrange
auto [cube1, cube2] = CreateTwoTopoShapeCubes();
// We can't use a compound in replaceElementShape, so we'll make a replacement wire and a shell
auto wire {BRepBuilderAPI_MakeWire(
BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)),
BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 0.0, 0.0), gp_Pnt(1.0, 1.0, 0.0)),
BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 1.0, 0.0), gp_Pnt(0.0, 0.0, 0.0)))
.Wire()};
auto shell = cube1.makeElementShell();
auto wires = shell.getSubTopoShapes(TopAbs_WIRE);
// Act
TopoShape& result = shell.replaceElementShape(shell, {{wires[0], wire}});
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)));
EXPECT_FLOAT_EQ(getArea(result.getShape()), 5);
EXPECT_EQ(result.countSubElements("Wire"), 6);
// Assert that we're creating a correct element map
EXPECT_TRUE(result.getMappedChildElements().empty());
EXPECT_TRUE(elementsMatch(
result,
{
"Edge1", "Edge1;:H1,E", "Edge1;:H2,E", "Edge1;:H3,E", "Edge2",
"Edge2;:H1,E", "Edge2;:H2,E", "Edge2;:H3,E", "Edge3", "Edge3;:H1,E",
"Edge3;:H2,E", "Edge3;:H3,E", "Edge4;:H1,E", "Edge4;:H2,E", "Edge4;:H3,E",
"Face1;:H2,F", "Face1;:H3,F", "Face1;:H4,F", "Face1;:H5,F", "Face1;:H6,F",
"Vertex1", "Vertex1;:H1,V", "Vertex1;:H2,V", "Vertex2", "Vertex2;:H1,V",
"Vertex2;:H2,V", "Vertex3", "Vertex3;:H1,V", "Vertex3;:H2,V", "Vertex4;:H1,V",
"Vertex4;:H2,V",
}));
}
TEST_F(TopoShapeExpansionTest, removeElementShape)
{
// Arrange
auto [cube1, cube2] = CreateTwoTopoShapeCubes();
auto faces = cube1.getSubTopoShapes(TopAbs_FACE);
// Act
TopoShape result = cube1.removeElementShape({faces[0]});
Base::BoundBox3d bb = result.getBoundBox();
// Assert shape is correct
EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)));
EXPECT_FLOAT_EQ(getArea(result.getShape()), 5);
EXPECT_EQ(result.countSubShapes("Compound"), 1);
EXPECT_EQ(result.countSubShapes("Face"), 5);
// Assert that we're creating a correct element map
EXPECT_TRUE(result.getMappedChildElements().empty());
EXPECT_TRUE(
elementsMatch(result,
{
"Edge1;:H1,E;:H7,E", "Edge1;:H2,E;:H7,E", "Edge1;:H3,E;:H7,E",
"Edge2;:H1,E;:H7,E", "Edge2;:H2,E;:H7,E", "Edge2;:H3,E;:H7,E",
"Edge3;:H1,E;:H7,E", "Edge3;:H2,E;:H7,E", "Edge3;:H3,E;:H7,E",
"Edge4;:H1,E;:H7,E", "Edge4;:H2,E;:H7,E", "Edge4;:H3,E;:H7,E",
"Face1;:H2,F;:H7,F", "Face1;:H3,F;:H7,F", "Face1;:H4,F;:H7,F",
"Face1;:H5,F;:H7,F", "Face1;:H6,F;:H7,F", "Vertex1;:H1,V;:H7,V",
"Vertex1;:H2,V;:H7,V", "Vertex2;:H1,V;:H7,V", "Vertex2;:H2,V;:H7,V",
"Vertex3;:H1,V;:H7,V", "Vertex3;:H2,V;:H7,V", "Vertex4;:H1,V;:H7,V",
"Vertex4;:H2,V;:H7,V",
}));
}
// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)