diff --git a/src/App/ComplexGeoData.cpp b/src/App/ComplexGeoData.cpp index 8cd035e829..95df883b8f 100644 --- a/src/App/ComplexGeoData.cpp +++ b/src/App/ComplexGeoData.cpp @@ -185,7 +185,7 @@ bool ComplexGeoData::getCenterOfGravity(Base::Vector3d& unused) const } const std::string &ComplexGeoData::elementMapPrefix() { - static std::string prefix(";"); + static std::string prefix(ELEMENT_MAP_PREFIX); return prefix; } diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 13fe666184..ad4ef2a780 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -606,7 +606,7 @@ public: // double tol=1e-7, double atol=1e-12) const; //@} - void copyElementMap(const TopoShape &other, const char *op=nullptr); + void copyElementMap(const TopoShape & topoShape, const char *op=nullptr); bool canMapElement(const TopoShape &other) const; void mapSubElement(const TopoShape &other,const char *op=nullptr, bool forceHasher=false); void mapSubElement(const std::vector &shapes, const char *op); @@ -741,6 +741,26 @@ private: }; ShapeProtector _Shape; + +private: + // Helper methods + static std::vector + createChildMap(size_t count, const std::vector& shapes, const char* op); + + void setupChild(Data::ElementMap::MappedChildElements& child, + TopAbs_ShapeEnum elementType, + const TopoShape& topoShape, + size_t shapeCount, + const char* op); + void mapSubElementForShape(const TopoShape& other, const char* op); + void mapSubElementTypeForShape(const TopoShape& other, + TopAbs_ShapeEnum type, + const char* op, + int count, + bool forward, + bool& warned); + void mapCompoundSubElements(const std::vector& shapes, const char* op); + }; } // namespace Part diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index e26a6a43e8..d5b5b5227b 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -200,183 +200,326 @@ std::vector TopoShape::findAncestorsShapes(const TopoDS_Shape& sub return shapes; } -#define _HANDLE_NULL_SHAPE(_msg,_throw) do {\ - if(_throw) {\ - FC_THROWM(NullShapeException,_msg);\ - }\ - FC_WARN(_msg);\ -}while(0) - -#define HANDLE_NULL_SHAPE _HANDLE_NULL_SHAPE("Null shape",true) -#define HANDLE_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",true) -#define WARN_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",false) +// The following lines should be used for now to replace the original macros (in the future we can +// refactor to use std::source_location and eliminate the use of the macros entirely). +// FC_THROWM(NullShapeException, "Null shape"); +// FC_THROWM(NullShapeException, "Null input shape"); +// FC_WARN("Null input shape"); // NOLINT +// +// The original macros: +// #define HANDLE_NULL_SHAPE _HANDLE_NULL_SHAPE("Null shape",true) +// #define HANDLE_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",true) +// #define WARN_NULL_INPUT _HANDLE_NULL_SHAPE("Null input shape",false) bool TopoShape::hasPendingElementMap() const { - return !elementMap(false) - && this->_cache + return !elementMap(false) && this->_cache && (this->_parentCache || this->_cache->cachedElementMap); } -bool TopoShape::canMapElement(const TopoShape &other) const { - if(isNull() || other.isNull() || this == &other || other.Tag == -1 || Tag == -1) +bool TopoShape::canMapElement(const TopoShape& other) const +{ + if (isNull() || other.isNull() || this == &other || other.Tag == -1 || Tag == -1) { return false; - if(!other.Tag - && !other.elementMap(false) - && !other.hasPendingElementMap()) + } + if ((other.Tag == 0) && !other.elementMap(false) && !other.hasPendingElementMap()) { return false; + } initCache(); other.initCache(); _cache->relations.clear(); return true; } -void TopoShape::mapSubElement(const TopoShape &other, const char *op, bool forceHasher) { -#ifdef FC_NO_ELEMENT_MAP - return; -#endif +namespace +{ +size_t checkSubshapeCount(const TopoShape& topoShape1, + const TopoShape& topoShape2, + TopAbs_ShapeEnum elementType) +{ + auto count = topoShape1.countSubShapes(elementType); + auto other = topoShape2.countSubShapes(elementType); + if (count != other) { + FC_WARN("sub shape mismatch"); // NOLINT + if (count > other) { + count = other; + } + } + return count; +} +} // namespace - if(!canMapElement(other)) +void TopoShape::setupChild(Data::ElementMap::MappedChildElements& child, + TopAbs_ShapeEnum elementType, + const TopoShape& topoShape, + size_t shapeCount, + const char* op) +{ + child.indexedName = Data::IndexedName::fromConst(TopoShape::shapeName(elementType).c_str(), 1); + child.offset = 0; + child.count = static_cast(shapeCount); + child.elementMap = topoShape.elementMap(); + if (this->Tag != topoShape.Tag) { + child.tag = topoShape.Tag; + } + else { + child.tag = 0; + } + if (op) { + child.postfix = op; + } +} + +void TopoShape::copyElementMap(const TopoShape& topoShape, const char* op) +{ + if (topoShape.isNull() || isNull()) { return; + } + std::vector children; + std::array elementTypes = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + for (const auto elementType : elementTypes) { + auto count = checkSubshapeCount(*this, topoShape, elementType); + if (count == 0) { + continue; + } + children.emplace_back(); + auto& child = children.back(); + setupChild(child, elementType, topoShape, count, op); + } + resetElementMap(); + if (!Hasher) { + Hasher = topoShape.Hasher; + } + setMappedChildElements(children); +} - if (!getElementMapSize(false) && this->_Shape.IsPartner(other._Shape)) { - if (!this->Hasher) +namespace +{ +void warnIfLogging() +{ + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("hasher mismatch"); // NOLINT + } +}; + +void hasherMismatchError() +{ + FC_ERR("hasher mismatch"); // NOLINT +} + + +void checkAndMatchHasher(TopoShape& topoShape1, const TopoShape& topoShape2) +{ + if (topoShape1.Hasher) { + if (topoShape2.Hasher != topoShape1.Hasher) { + if (topoShape1.getElementMapSize(false) == 0U) { + warnIfLogging(); + } + else { + hasherMismatchError(); + } + topoShape1.Hasher = topoShape2.Hasher; + } + } + else { + topoShape1.Hasher = topoShape2.Hasher; + } +} +} // namespace + +void TopoShape::mapSubElementTypeForShape(const TopoShape& other, + TopAbs_ShapeEnum type, + const char* op, + int count, + bool forward, + bool& warned) +{ + auto& shapeMap = _cache->getAncestry(type); + auto& otherMap = other._cache->getAncestry(type); + const char* shapeType = shapeName(type).c_str(); + + // 1-indexed for readability (e.g. there is no "Edge0", we started at "Edge1", etc.) + for (int outerCounter = 1; outerCounter <= count; ++outerCounter) { + int innerCounter {0}; + int index {0}; + if (forward) { + innerCounter = outerCounter; + index = shapeMap.find(_Shape, otherMap.find(other._Shape, outerCounter)); + if (index == 0) { + continue; + } + } + else { + index = outerCounter; + innerCounter = otherMap.find(other._Shape, shapeMap.find(_Shape, outerCounter)); + if (innerCounter == 0) { + continue; + } + } + Data::IndexedName element = Data::IndexedName::fromConst(shapeType, index); + for (auto& mappedName : + other.getElementMappedNames(Data::IndexedName::fromConst(shapeType, innerCounter), + true)) { + auto& name = mappedName.first; + auto& sids = mappedName.second; + if (!sids.empty()) { + if (!Hasher) { + Hasher = sids[0].getHasher(); + } + else if (!sids[0].isFromSameHasher(Hasher)) { + if (!warned) { + warned = true; + FC_WARN("hasher mismatch"); // NOLINT + } + sids.clear(); + } + } + std::ostringstream ss; + char elementType {shapeName(type)[0]}; + elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); + elementMap()->setElementName(element, name, Tag, &sids); + } + } +} + +void TopoShape::mapSubElementForShape(const TopoShape& other, const char* op) +{ + bool warned = false; + static const std::array types = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + + for (auto type : types) { + auto& shapeMap = _cache->getAncestry(type); + auto& otherMap = other._cache->getAncestry(type); + if ((shapeMap.count() == 0) || (otherMap.count() == 0)) { + continue; + } + + bool forward {false}; + int count {0}; + if (otherMap.count() <= shapeMap.count()) { + forward = true; + count = otherMap.count(); + } + else { + forward = false; + count = shapeMap.count(); + } + mapSubElementTypeForShape(other, type, op, count, forward, warned); + } +} + +void TopoShape::mapSubElement(const TopoShape& other, const char* op, bool forceHasher) +{ + if (!canMapElement(other)) { + return; + } + + if ((getElementMapSize(false) == 0U) && this->_Shape.IsPartner(other._Shape)) { + if (!this->Hasher) { this->Hasher = other.Hasher; + } copyElementMap(other, op); return; } - bool warned = false; - static const std::array types = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + if (!forceHasher && other.Hasher) { + checkAndMatchHasher(*this, other); + } - auto checkHasher = [this](const TopoShape &other) { - if(Hasher) { - if(other.Hasher!=Hasher) { - if(!getElementMapSize(false)) { - if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) - FC_WARN("hasher mismatch"); - }else { - // FC_THROWM(Base::RuntimeError, "hasher mismatch"); - FC_ERR("hasher mismatch"); - } - Hasher = other.Hasher; + mapSubElementForShape(other, op); +} + +std::vector +TopoShape::createChildMap(size_t count, const std::vector& shapes, const char* op) +{ + std::vector children; + children.reserve(count * (size_t)3); + std::array types = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; + for (const auto topAbsType : types) { + size_t offset = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + continue; } - }else - Hasher = other.Hasher; - }; - - for(auto type : types) { - auto &shapeMap = _cache->getAncestry(type); - auto &otherMap = other._cache->getAncestry(type); - if(!shapeMap.count() || !otherMap.count()) - continue; - if(!forceHasher && other.Hasher) { - forceHasher = true; - checkHasher(other); - } - const char *shapetype = shapeName(type).c_str(); - std::ostringstream ss; - - bool forward; - int count; - if(otherMap.count()<=shapeMap.count()) { - forward = true; - count = otherMap.count(); - }else{ - forward = false; - count = shapeMap.count(); - } - for(int k=1;k<=count;++k) { - int i,idx; - if(forward) { - i = k; - idx = shapeMap.find(_Shape,otherMap.find(other._Shape,k)); - if(!idx) continue; - } else { - idx = k; - i = otherMap.find(other._Shape,shapeMap.find(_Shape,k)); - if(!i) continue; + auto subShapeCount = topoShape.countSubShapes(topAbsType); + if (subShapeCount == 0) { + continue; } - Data::IndexedName element = Data::IndexedName::fromConst(shapetype, idx); - for(auto &v : other.getElementMappedNames( - Data::IndexedName::fromConst(shapetype,i),true)) - { - auto &name = v.first; - auto &sids = v.second; - if(sids.size()) { - if (!Hasher) - Hasher = sids[0].getHasher(); - else if (!sids[0].isFromSameHasher(Hasher)) { - if (!warned) { - warned = true; - FC_WARN("hasher mismatch"); - } - sids.clear(); - } - } - ss.str(""); - elementMap()->encodeElementName(shapetype[0],name,ss,&sids,Tag,op,other.Tag); - elementMap()->setElementName(element,name,Tag, &sids); + children.emplace_back(); + auto& child = children.back(); + child.indexedName = + Data::IndexedName::fromConst(TopoShape::shapeName(topAbsType).c_str(), 1); + child.offset = static_cast(offset); + offset += subShapeCount; + child.count = static_cast(subShapeCount); + child.elementMap = topoShape.elementMap(); + child.tag = topoShape.Tag; + if (op) { + child.postfix = op; } } } + return children; } -void TopoShape::mapSubElement(const std::vector &shapes, const char *op) { -#ifdef FC_NO_ELEMENT_MAP - return; -#endif +void TopoShape::mapCompoundSubElements(const std::vector& shapes, const char* op) +{ + int count = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + continue; + } + ++count; + auto subshape = getSubShape(TopAbs_SHAPE, count, /*silent = */ true); + if (!subshape.IsPartner(topoShape._Shape)) { + return; // Not a partner shape, don't do any mapping at all + } + } + auto children {createChildMap(count, shapes, op)}; + setMappedChildElements(children); +} - if (shapes.empty()) +void TopoShape::mapSubElement(const std::vector& shapes, const char* op) +{ + if (shapes.empty()) { return; + } if (shapeType(true) == TopAbs_COMPOUND) { - int count = 0; - for (auto & s : shapes) { - if (s.isNull()) - continue; - if (!getSubShape(TopAbs_SHAPE, ++count, true).IsPartner(s._Shape)) { - count = 0; - break; - } - } - if (count) { - std::vector children; - children.reserve(count*3); - TopAbs_ShapeEnum types[] = {TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE}; - for (unsigned i=0; iaddChildElements(Tag, children); // Replaces the original line below - //setMappedChildElements(children); - return; + mapCompoundSubElements(shapes, op); + } + else { + for (auto& shape : shapes) { + mapSubElement(shape, op); } } - - for(auto &shape : shapes) - mapSubElement(shape,op); } -TopoShape &TopoShape::makeElementCompound(const std::vector &shapes, const char *op, bool force) +namespace { - if(!force && shapes.size()==1) { +void addShapesToBuilder(const std::vector& shapes, + BRep_Builder& builder, + TopoDS_Compound& comp) +{ + int count = 0; + for (auto& topoShape : shapes) { + if (topoShape.isNull()) { + FC_WARN("Null input shape"); // NOLINT + continue; + } + builder.Add(comp, topoShape.getShape()); + ++count; + } + if (count == 0) { + FC_THROWM(NullShapeException, "Null shape"); + } +} +} // namespace + +TopoShape& +TopoShape::makeElementCompound(const std::vector& shapes, const char* op, bool force) +{ + if (!force && shapes.size() == 1) { *this = shapes[0]; return *this; } @@ -385,26 +528,15 @@ TopoShape &TopoShape::makeElementCompound(const std::vector &shapes, TopoDS_Compound comp; builder.MakeCompound(comp); - if(shapes.empty()) { + if (shapes.empty()) { setShape(comp); return *this; } - - int count = 0; - for(auto &s : shapes) { - if(s.isNull()) { - WARN_NULL_INPUT; - continue; - } - builder.Add(comp,s.getShape()); - ++count; - } - if(!count) - HANDLE_NULL_SHAPE; + addShapesToBuilder(shapes, builder, comp); setShape(comp); initCache(); - mapSubElement(shapes,op); + mapSubElement(shapes, op); return *this; } diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 50d487d342..964f346f88 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -4,7 +4,9 @@ #include "src/App/InitApplication.h" #include +#include #include +#include #include #include @@ -115,4 +117,48 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoShapesGeneratesMap) EXPECT_EQ(4, topoShape.getMappedChildElements().size()); // two vertices and two edges } +namespace +{ + +std::pair CreateTwoCubes() +{ + auto boxMaker1 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker1.Build(); + auto box1 = boxMaker1.Shape(); + + auto boxMaker2 = BRepPrimAPI_MakeBox(1.0, 1.0, 1.0); + boxMaker2.Build(); + auto box2 = boxMaker2.Shape(); + auto transform = gp_Trsf(); + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); + box2.Location(TopLoc_Location(transform)); + + return {box1, box2}; +} +} // namespace + +TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + Part::TopoShape cube1TS {cube1}; + cube1TS.Tag = 1; + Part::TopoShape cube2TS {cube2}; + cube2TS.Tag = 2; + + // Act + Part::TopoShape topoShape; + topoShape.makeElementCompound({cube1TS, cube2TS}); + + // Assert + auto elementMap = topoShape.getElementMap(); + EXPECT_EQ(52, elementMap.size()); + // Two cubes, each consisting of: + // 8 Vertices + // 12 Edges + // 6 Faces + // ---------- + // 26 subshapes each +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)