diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 573e4eb322..b04caf52ed 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -638,7 +638,6 @@ public: void mapSubElement(const std::vector &shapes, const char *op=nullptr); bool hasPendingElementMap() const; - /** Helper class to return the generated and modified shape given an input shape * * Shape history information is extracted using OCCT APIs @@ -660,6 +659,24 @@ public: } }; + /** Core function to generate mapped element names from shape history + * + * @param shape: the new shape + * @param mapper: for mapping input shapes to generated/modified shapes + * @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 given 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 &makeShapeWithElementMap(const TopoDS_Shape &shape, + const Mapper &mapper, + const std::vector &sources, + const char *op=nullptr); + /** Make a compound shape * * @param shapes: input shapes diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index e7554ebaa8..eb6b03be29 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -25,23 +25,28 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include + #include -#include -#include - -#include -#include - -#endif - #include #include +#include +#include +#include +#include +#include #include +#include + +#include +#endif + #include "TopoShape.h" #include "TopoShapeCache.h" +#include "TopoShapeOpCode.h" #include "FaceMaker.h" +#include + FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -338,6 +343,8 @@ void checkAndMatchHasher(TopoShape& topoShape1, const TopoShape& topoShape2) } } // namespace + +// TODO: Refactor mapSubElementTypeForShape to reduce complexity void TopoShape::mapSubElementTypeForShape(const TopoShape& other, TopAbs_ShapeEnum type, const char* op, @@ -387,8 +394,8 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other, } std::ostringstream ss; char elementType {shapeName(type)[0]}; - if ( ! elementMap() ) { - FC_THROWM(NullShapeException, "No element map"); + if (!elementMap()) { + FC_THROWM(NullShapeException, "No element map"); // NOLINT } elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag); elementMap()->setElementName(element, name, Tag, &sids); @@ -509,6 +516,743 @@ void TopoShape::mapSubElement(const std::vector& shapes, const char* } } +struct ShapeInfo +{ + const TopoDS_Shape& shape; + TopoShapeCache::Ancestry& cache; + TopAbs_ShapeEnum type; + const char* shapetype; + + ShapeInfo(const TopoDS_Shape& shape, TopAbs_ShapeEnum type, TopoShapeCache::Ancestry& cache) + : shape(shape) + , cache(cache) + , type(type) + , shapetype(TopoShape::shapeName(type).c_str()) + {} + + int count() const + { + return cache.count(); + } + + TopoDS_Shape find(int index) + { + return cache.find(shape, index); + } + + int find(const TopoDS_Shape& subshape) + { + return cache.find(shape, subshape); + } +}; + +//////////////////////////////////////// +// makESHAPE -> makeShapeWithElementMap +/////////////////////////////////////// + +struct NameKey +{ + Data::MappedName name; + long tag = 0; + int shapetype = 0; + + NameKey() + = default; + explicit NameKey(Data::MappedName n) + : name(std::move(n)) + {} + NameKey(int type, Data::MappedName n) + : name(std::move(n)) + { + // Order the shape type from vertex < edge < face < other. We'll rely + // on this for sorting when we name the geometry element. + switch (type) { + case TopAbs_VERTEX: + shapetype = 0; + break; + case TopAbs_EDGE: + shapetype = 1; + break; + case TopAbs_FACE: + shapetype = 2; + break; + default: + shapetype = 3; + } + } + bool operator<(const NameKey& other) const + { + if (shapetype < other.shapetype) { + return true; + } + if (shapetype > other.shapetype) { + return false; + } + if (tag < other.tag) { + return true; + } + if (tag > other.tag) { + return false; + } + return name < other.name; + } +}; + +struct NameInfo +{ + int index {}; + Data::ElementIDRefs sids; + const char* shapetype {}; +}; + + +const std::string& modPostfix() +{ + static std::string postfix(Data::POSTFIX_MOD); + return postfix; +} + +const std::string& modgenPostfix() +{ + static std::string postfix(Data::POSTFIX_MODGEN); + return postfix; +} + +const std::string& genPostfix() +{ + static std::string postfix(Data::POSTFIX_GEN); + return postfix; +} + +const std::string& upperPostfix() +{ + static std::string postfix(Data::POSTFIX_UPPER); + return postfix; +} + +const std::string& lowerPostfix() +{ + static std::string postfix(Data::POSTFIX_LOWER); + return postfix; +} + +// TODO: Refactor checkForParallelOrCoplanar to reduce complexity +void checkForParallelOrCoplanar(const TopoDS_Shape& newShape, + const ShapeInfo& newInfo, + std::vector& newShapes, + const gp_Pln& pln, + int parallelFace, + int& coplanarFace, + int& checkParallel) +{ + for (TopExp_Explorer xp(newShape, newInfo.type); xp.More(); xp.Next()) { + newShapes.push_back(xp.Current()); + + if ((parallelFace < 0 || coplanarFace < 0) && checkParallel > 0) { + // Specialized checking for high level mapped + // face that are either coplanar or parallel + // with the source face, which are common in + // operations like extrusion. Once found, the + // first coplanar face will assign an index of + // INT_MIN+1, and the first parallel face + // INT_MIN. The purpose of these special + // indexing is to make the name more stable for + // those generated faces. + // + // For example, the top or bottom face of an + // extrusion will be named using the extruding + // face. With a fixed index, the name is no + // longer affected by adding/removing of holes + // inside the extruding face/sketch. + gp_Pln plnOther; + if (TopoShape(newShapes.back()).findPlane(plnOther)) { + if (pln.Axis().IsParallel(plnOther.Axis(), Precision::Angular())) { + if (coplanarFace < 0) { + gp_Vec vec(pln.Axis().Location(), plnOther.Axis().Location()); + Standard_Real D1 = gp_Vec(pln.Axis().Direction()).Dot(vec); + if (D1 < 0) { + D1 = -D1; + } + Standard_Real D2 = gp_Vec(plnOther.Axis().Direction()).Dot(vec); + if (D2 < 0) { + D2 = -D2; + } + if (D1 <= Precision::Confusion() && D2 <= Precision::Confusion()) { + coplanarFace = (int)newShapes.size(); + continue; + } + } + if (parallelFace < 0) { + parallelFace = (int)newShapes.size(); + } + } + } + } + } +} + +// TODO: Refactor makeShapeWithElementMap to reduce complexity +TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, + const Mapper& mapper, + const std::vector& shapes, + const char* op) +{ + setShape(shape); + if (shape.IsNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + + if (shapes.empty()) { + return *this; + } + + size_t canMap = 0; + for (auto& incomingShape : shapes) { + if (canMapElement(incomingShape)) { + ++canMap; + } + } + if (canMap == 0U) { + return *this; + } + if (canMap != shapes.size() && FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Not all input shapes are mappable"); // NOLINT + } + + if (!op) { + op = Part::OpCodes::Maker; + } + std::string _op = op; + _op += '_'; + + initCache(); + ShapeInfo vertexInfo(_Shape, TopAbs_VERTEX, _cache->getAncestry(TopAbs_VERTEX)); + ShapeInfo edgeInfo(_Shape, TopAbs_EDGE, _cache->getAncestry(TopAbs_EDGE)); + ShapeInfo faceInfo(_Shape, TopAbs_FACE, _cache->getAncestry(TopAbs_FACE)); + mapSubElement(shapes, op); + + std::array infos = {&vertexInfo, &edgeInfo, &faceInfo}; + + std::array infoMap {}; + infoMap[TopAbs_VERTEX] = &vertexInfo; + infoMap[TopAbs_EDGE] = &edgeInfo; + infoMap[TopAbs_WIRE] = &edgeInfo; + infoMap[TopAbs_FACE] = &faceInfo; + infoMap[TopAbs_SHELL] = &faceInfo; + infoMap[TopAbs_SOLID] = &faceInfo; + infoMap[TopAbs_COMPOUND] = &faceInfo; + infoMap[TopAbs_COMPSOLID] = &faceInfo; + + std::ostringstream ss; + std::string postfix; + Data::MappedName newName; + + std::map> newNames; + + // First, collect names from other shapes that generates or modifies the + // new shape + for (auto& pinfo : infos) { + auto& info = *pinfo; + for (const auto & incomingShape : shapes) { + if (!canMapElement(incomingShape)) { + continue; + } + auto& otherMap = incomingShape._cache->getAncestry(info.type); + if (otherMap.count() == 0) { + continue; + } + + for (int i = 1; i <= otherMap.count(); i++) { + const auto& otherElement = otherMap.find(incomingShape._Shape, i); + // Find all new objects that are a modification of the old object + Data::ElementIDRefs sids; + NameKey key(info.type, + incomingShape.getMappedName(Data::IndexedName::fromConst(info.shapetype, i), + true, + &sids)); + + int newShapeCounter = 0; + for (auto& newShape : mapper.modified(otherElement)) { + ++newShapeCounter; + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown modified shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + auto& newInfo = *infoMap.at(newShape.ShapeType()); + if (newInfo.type != newShape.ShapeType()) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // TODO: it seems modified shape may report higher + // level shape type just like generated shape below. + // Maybe we shall do the same for name construction. + // NOLINTNEXTLINE + FC_WARN("modified shape type " << shapeName(newShape.ShapeType()) + << " mismatch with " << info.shapetype + << i); + } + continue; + } + int newShapeIndex = newInfo.find(newShape); + if (newShapeIndex == 0) { + // This warning occurs in makERevolve. It generates + // some shape from a vertex that never made into the + // final shape. There may be incomingShape cases there. + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("Cannot find " << op << " modified " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = Data::IndexedName::fromConst(newInfo.shapetype, newShapeIndex); + if (getMappedName(element)) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + name_info.index = newShapeCounter; + name_info.shapetype = info.shapetype; + } + + int checkParallel = -1; + gp_Pln pln; + + // Find all new objects that were generated from an old object + // (e.g. a face generated from an edge) + newShapeCounter = 0; + for (auto& newShape : mapper.generated(otherElement)) { + if (newShape.ShapeType() >= TopAbs_SHAPE) { + // NOLINTNEXTLINE + FC_ERR("unknown generated shape type " << newShape.ShapeType() << " from " + << info.shapetype << i); + continue; + } + + int parallelFace = -1; + int coplanarFace = -1; + auto& newInfo = *infoMap.at(newShape.ShapeType()); + std::vector newShapes; + int shapeOffset = 0; + if (newInfo.type == newShape.ShapeType()) { + newShapes.push_back(newShape); + } + else { + // It is possible for the maker to report generating a + // higher level shape, such as shell or solid. For + // example, when extruding, OCC will report the + // extruding face generating the entire solid. However, + // it will also report the edges of the extruding face + // generating the side faces. In this case, too much + // information is bad for us. We don't want the name of + // the side face (and its edges) to be coupled with + // incomingShape (unrelated) edges in the extruding face. + // + // shapeOffset below is used to make sure the higher + // level mapped names comes late after sorting. We'll + // ignore those names if there are more precise mapping + // available. + shapeOffset = 3; + + if (info.type == TopAbs_FACE && checkParallel < 0) { + if (!TopoShape(otherElement).findPlane(pln)) { + checkParallel = 0; + } + else { + checkParallel = 1; + } + } + checkForParallelOrCoplanar(newShape, + newInfo, + newShapes, + pln, + parallelFace, + coplanarFace, + checkParallel); + } + key.shapetype += shapeOffset; + for (auto& workingShape : newShapes) { + ++newShapeCounter; + int workingShapeIndex = newInfo.find(workingShape); + if (workingShapeIndex == 0) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + // NOLINTNEXTLINE + FC_WARN("Cannot find " << op << " generated " << newInfo.shapetype + << " from " << info.shapetype << i); + } + continue; + } + + Data::IndexedName element = + Data::IndexedName::fromConst(newInfo.shapetype, workingShapeIndex); + auto mapped = getMappedName(element); + if (mapped) { + continue; + } + + key.tag = incomingShape.Tag; + auto& name_info = newNames[element][key]; + name_info.sids = sids; + if (newShapeCounter == parallelFace) { + name_info.index = std::numeric_limits::min(); + } + else if (newShapeCounter == coplanarFace) { + name_info.index = std::numeric_limits::min() + 1; + } + else { + name_info.index = -newShapeCounter; + } + name_info.shapetype = info.shapetype; + } + key.shapetype -= shapeOffset; + } + } + } + } + + // We shall first exclude those names generated from high level mapping. If + // there are still any unnamed elements left after we go through the process + // below, we set delayed=true, and start using those excluded names. + bool delayed = false; + + while (true) { + + // Construct the names for modification/generation info collected in + // the previous step + for (auto itName = newNames.begin(), itNext = itName; itNext != newNames.end(); + itName = itNext) { + // We treat the first modified/generated source shape name specially. + // If case there are more than one source shape. We hash the first + // source name separately, and then obtain the second string id by + // hashing all the source names together. We then use the second + // string id as the postfix for our name. + // + // In this way, we can associate the same source that are modified by + // multiple other shapes. + + ++itNext; + + auto& element = itName->first; + auto& names = itName->second; + const auto& first_key = names.begin()->first; + auto& first_info = names.begin()->second; + + if (!delayed && first_key.shapetype >= 3 && first_info.index > INT_MIN + 1) { + // This name is mapped from high level (shell, solid, etc.) + // Delay till next round. + // + // index>INT_MAX+1 is for checking generated coplanar and + // parallel face mapping, which has special fixed index to make + // name stable. These names are not delayed. + continue; + } + if (!delayed && getMappedName(element)) { + newNames.erase(itName); + continue; + } + + int name_type = + first_info.index > 0 ? 1 : 2; // index>0 means modified, or else generated + Data::MappedName first_name = first_key.name; + + Data::ElementIDRefs sids(first_info.sids); + + postfix.clear(); + if (names.size() > 1) { + ss.str(""); + ss << '('; + bool first = true; + auto it = names.begin(); + int count = 0; + for (++it; it != names.end(); ++it) { + auto& other_key = it->first; + if (other_key.shapetype >= 3 && first_key.shapetype < 3) { + // shapetype>=3 means it's a high level mapping (e.g. a face + // generates a solid). We don't want that if there are more + // precise low level mapping available. See comments above + // for more details. + break; + } + if (first) { + first = false; + } + else { + ss << '|'; + } + auto& other_info = it->second; + std::ostringstream ss2; + if (other_info.index != 1) { + // 'K' marks the additional source shape of this + // generate (or modified) shape. + ss2 << elementMapPrefix() << 'K'; + if (other_info.index == INT_MIN) { + ss2 << '0'; + } + else if (other_info.index == INT_MIN + 1) { + ss2 << "00"; + } + else { + // The same source shape may generate or modify + // more than one shape. The index here marks the + // position it is reported by OCC. Including the + // index here is likely to degrade name stablilty, + // but is unfortunately a necessity to avoid + // duplicate names. + ss2 << other_info.index; + } + } + Data::MappedName other_name = other_key.name; + elementMap()->encodeElementName(*other_info.shapetype, + other_name, + ss2, + &sids, + Tag, + nullptr, + other_key.tag); + ss << other_name; + if ((name_type == 1 && other_info.index < 0) + || (name_type == 2 && other_info.index > 0)) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("element is both generated and modified"); // NOLINT + } + name_type = 0; + } + sids += other_info.sids; + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + if (!first) { + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << sids.back().toString(); + } + postfix = ss.str(); + } + } + + ss.str(""); + if (name_type == 2) { + ss << genPostfix(); + } + else if (name_type == 1) { + ss << modPostfix(); + } + else { + ss << modgenPostfix(); + } + if (first_info.index == INT_MIN) { + ss << '0'; + } + else if (first_info.index == INT_MIN + 1) { + ss << "00"; + } + else if (abs(first_info.index) > 1) { + ss << abs(first_info.index); + } + ss << postfix; + elementMap() + ->encodeElementName(element[0], first_name, ss, &sids, Tag, op, first_key.tag); + elementMap()->setElementName(element, first_name, Tag, &sids); + + if (!delayed && first_key.shapetype < 3) { + newNames.erase(itName); + } + } + + // The reverse pass. Starting from the highest level element, i.e. + // Face, for any element that are named, assign names for its lower unnamed + // elements. For example, if Edge1 is named E1, and its vertexes are not + // named, then name them as E1;U1, E1;U2, etc. + // + // In order to make the name as stable as possible, we may assign multiple + // names (which must be sorted, because we may use the first one to name + // upper element in the final pass) to lower element if it appears in + // multiple higher elements, e.g. same edge in multiple faces. + + for (size_t infoIndex = infos.size() - 1; infoIndex != 0; --infoIndex) { + std::map> + names; + auto& info = *infos.at(infoIndex); + auto& next = *infos.at(infoIndex - 1); + int elementCounter = 1; + auto it = newNames.end(); + if (delayed) { + it = newNames.upper_bound(Data::IndexedName::fromConst(info.shapetype, 0)); + } + for (;; ++elementCounter) { + Data::IndexedName element; + if (!delayed) { + if (elementCounter > info.count()) { + break; + } + element = Data::IndexedName::fromConst(info.shapetype, elementCounter); + if (newNames.count(element) != 0U) { + continue; + } + } + else if (it == newNames.end() + || !boost::starts_with(it->first.getType(), info.shapetype)) { + break; + } + else { + element = it->first; + ++it; + elementCounter = element.getIndex(); + if (elementCounter == 0 || elementCounter > info.count()) { + continue; + } + } + Data::ElementIDRefs sids; + Data::MappedName mapped = getMappedName(element, false, &sids); + if (!mapped) { + continue; + } + + TopTools_IndexedMapOfShape submap; + TopExp::MapShapes(info.find(elementCounter), next.type, submap); + for (int submapIndex = 1, infoCounter = 1; submapIndex <= submap.Extent(); ++submapIndex) { + ss.str(""); + int elementIndex = next.find(submap(submapIndex)); + assert(elementIndex); + Data::IndexedName indexedName = Data::IndexedName::fromConst(next.shapetype, elementIndex); + if (getMappedName(indexedName)) { + continue; + } + auto& infoRef = names[indexedName][mapped]; + infoRef.index = infoCounter++; + infoRef.sids = sids; + } + } + // Assign the actual names + for (auto& [indexedName, nameInfoMap] : names) { + // Do we really want multiple names for an element in this case? + // If not, we just pick the name in the first sorting order here. + auto& nameInfoMapEntry = *nameInfoMap.begin(); + { + auto& nameInfo = nameInfoMapEntry.second; + auto& sids = nameInfo.sids; + newName = nameInfoMapEntry.first; + ss.str(""); + ss << upperPostfix(); + if (nameInfo.index > 1) { + ss << nameInfo.index; + } + elementMap()->encodeElementName(indexedName[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(indexedName, newName, Tag, &sids); + } + } + } + + // The forward pass. For any elements that are not named, try construct its + // name from the lower elements + bool hasUnnamed = false; + for (size_t ifo = 1; ifo < infos.size(); ++ifo) { + auto& info = *infos.at(ifo); + auto& prev = *infos.at(ifo-1); + for (int i = 1; i <= info.count(); ++i) { + Data::IndexedName element = Data::IndexedName::fromConst(info.shapetype, i); + if (getMappedName(element)) { + continue; + } + + Data::ElementIDRefs sids; + std::map names; + TopExp_Explorer xp; + if (info.type == TopAbs_FACE) { + xp.Init(BRepTools::OuterWire(TopoDS::Face(info.find(i))), TopAbs_EDGE); + } + else { + xp.Init(info.find(i), prev.type); + } + for (; xp.More(); xp.Next()) { + int previousElementIndex = prev.find(xp.Current()); + assert(previousElementIndex); + Data::IndexedName prevElement = Data::IndexedName::fromConst(prev.shapetype, previousElementIndex); + if (!delayed && (newNames.count(prevElement) != 0U)) { + names.clear(); + break; + } + Data::ElementIDRefs sid; + Data::MappedName name = getMappedName(prevElement, false, &sid); + if (!name) { + // only assign name if all lower elements are named + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("unnamed lower element " << prevElement); // NOLINT + } + names.clear(); + break; + } + auto res = names.emplace(name, prevElement); + if (res.second) { + sids += sid; + } + else if (prevElement != res.first->second) { + // The seam edge will appear twice, which is normal. We + // only warn if the mapped element names are different. + // NOLINTNEXTLINE + FC_WARN("lower element " << prevElement << " and " << res.first->second + << " has duplicated name " << name << " for " + << info.shapetype << i); + } + } + if (names.empty()) { + hasUnnamed = true; + continue; + } + auto it = names.begin(); + newName = it->first; + if (names.size() == 1) { + ss << lowerPostfix(); + } + else { + bool first = true; + ss.str(""); + if (!Hasher) { + ss << lowerPostfix(); + } + ss << '('; + int count = 0; + for (++it; it != names.end(); ++it) { + if (first) { + first = false; + } + else { + ss << '|'; + } + ss << it->first; + + // To avoid the name becoming to long, just put some limit here + if (++count == 4) { + break; + } + } + ss << ')'; + if (Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << lowerPostfix() << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + elementMap()->setElementName(element, newName, Tag, &sids); + } + } + if (!hasUnnamed || delayed || newNames.empty()) { + break; + } + delayed = true; + } + return *this; +} + namespace { void addShapesToBuilder(const std::vector& shapes, @@ -634,7 +1378,8 @@ TopoShape& TopoShape::makeElementFace(const std::vector& shapes, /** * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. * - * @param element The element name(type) that provides 1 one character suffix to the name IF . + * @param element The element name(type) that provides 1 one character suffix to the name IF + * . * @param names The subnames to build the name from. If empty, return the TopoShape MappedName. * @param marker The elementMap name or suffix to start the name with. If null, use the * elementMapPrefix. diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 9c642e65e2..f239b3a27b 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -15,5 +15,6 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp ) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index 8b0390b7e5..36dc63f067 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -1,5 +1,7 @@ // SPDX-License-Identifier: LGPL-2.1-or-later +#include + #include "PartTestHelpers.h" // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) @@ -137,6 +139,22 @@ std::map elementMap(const TopoShape& shape) return result; } +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 PartTestHelpers // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index 79147f6531..8b829e4ba6 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -57,4 +57,6 @@ boxesMatch(const Base::BoundBox3d& b1, const Base::BoundBox3d& b2, double prec = std::map elementMap(const TopoShape& shape); +std::pair CreateTwoCubes(); + } // namespace PartTestHelpers diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index ff976589d6..f1b82a9822 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -8,16 +8,15 @@ #include "PartTestHelpers.h" #include -#include #include #include #include #include -#include #include // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) +using namespace PartTestHelpers; class TopoShapeExpansionTest: public ::testing::Test { @@ -31,7 +30,6 @@ protected: { _docName = App::GetApplication().getUniqueDocumentName("test"); App::GetApplication().newDocument(_docName.c_str(), "testUser"); - _sids = &_sid; _hasher = Base::Reference(new App::StringHasher); ASSERT_EQ(_hasher.getRefCount(), 1); } @@ -45,7 +43,6 @@ protected: private: std::string _docName; Data::ElementIDRefs _sid; - QVector* _sids = nullptr; App::StringHasherRef _hasher; }; @@ -125,26 +122,6 @@ 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 @@ -169,42 +146,20 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) // 26 subshapes each } -std::tuple -CreateRectFace(float len = 2.0, float wid = 3.0) -{ - auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(len, 0.0, 0.0)).Edge(); - auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, 0.0, 0.0), gp_Pnt(len, wid, 0.0)).Edge(); - auto edge3 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, wid, 0.0), gp_Pnt(0.0, wid, 0.0)).Edge(); - auto edge4 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, wid, 0.0), gp_Pnt(0.0, 0.0, 0.0)).Edge(); - auto wire1 = BRepBuilderAPI_MakeWire({edge1, edge2, edge3, edge4}).Wire(); - auto face1 = BRepBuilderAPI_MakeFace(wire1).Face(); - return {face1, wire1, edge1, edge2, edge3, edge4}; -} - -std::tuple -CreateFaceWithRoundHole(float len = 2.0, float wid = 3.0, float radius = 1.0) -{ - auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(len, wid); - auto circ1 = - GC_MakeCircle(gp_Pnt(len / 2.0, wid / 2.0, 0), gp_Dir(0.0, 0.0, 1.0), radius).Value(); - auto edge5 = BRepBuilderAPI_MakeEdge(circ1).Edge(); - auto wire2 = BRepBuilderAPI_MakeWire(edge5).Wire(); - auto face2 = BRepBuilderAPI_MakeFace(face1, wire2).Face(); - return {face2, wire1, wire2}; -} - TEST_F(TopoShapeExpansionTest, makeElementFaceNull) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_FALSE(face1.IsEqual(newFace.getShape())); EXPECT_FLOAT_EQ(area, Len * Wid + M_PI * Rad * Rad); @@ -217,15 +172,17 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceNull) TEST_F(TopoShapeExpansionTest, makeElementFaceSimple) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -239,16 +196,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceSimple) TEST_F(TopoShapeExpansionTest, makeElementFaceParams) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -262,16 +221,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceParams) TEST_F(TopoShapeExpansionTest, makeElementFaceFromFace) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(face1, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -286,15 +247,17 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceFromFace) TEST_F(TopoShapeExpansionTest, makeElementFaceOpenWire) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {wire1, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire1, "Cut", nullptr, nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -309,16 +272,18 @@ TEST_F(TopoShapeExpansionTest, makeElementFaceOpenWire) TEST_F(TopoShapeExpansionTest, makeElementFaceClosedWire) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {wire2, 1L}; - double area = PartTestHelpers::getArea(face1); - double area1 = PartTestHelpers::getArea(topoShape.getShape()); + double area = getArea(face1); + double area1 = getArea(topoShape.getShape()); // Act Part::TopoShape newFace = topoShape.makeElementFace(wire2, "Cut", "Part::FaceMakerBullseye", nullptr); - double area2 = PartTestHelpers::getArea(newFace.getShape()); - double area3 = PartTestHelpers::getArea(topoShape.getShape()); + double area2 = getArea(newFace.getShape()); + double area3 = getArea(topoShape.getShape()); // Assert EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered EXPECT_FALSE(face1.IsEqual(newFace.getShape())); @@ -414,7 +379,9 @@ TEST_F(TopoShapeExpansionTest, setElementComboNameCompound) TEST_F(TopoShapeExpansionTest, splitWires) { // Arrange - const double Len = 3, Wid = 2, Rad = 1; + const float Len = 3; + const float Wid = 2; + const float Rad = 1; auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); Part::TopoShape topoShape {face1, 1L}; std::vector inner; @@ -424,8 +391,8 @@ TEST_F(TopoShapeExpansionTest, splitWires) topoShape.splitWires(&inner, Part::TopoShape::SplitWireReorient::ReorientReversed); // Assert EXPECT_EQ(inner.size(), 1); - EXPECT_FLOAT_EQ(PartTestHelpers::getLength(wire.getShape()), 2 + 2 + 3 + 3); - EXPECT_FLOAT_EQ(PartTestHelpers::getLength(inner.front().getShape()), M_PI * Rad * 2); + EXPECT_FLOAT_EQ(getLength(wire.getShape()), 2 + 2 + 3 + 3); + EXPECT_FLOAT_EQ(getLength(inner.front().getShape()), M_PI * Rad * 2); EXPECT_EQ(wire.getShape().Orientation(), TopAbs_REVERSED); for (Part::TopoShape& shape : inner) { EXPECT_EQ(shape.getShape().Orientation(), TopAbs_FORWARD); @@ -434,8 +401,8 @@ TEST_F(TopoShapeExpansionTest, splitWires) // Possible future tests: // splitWires without inner Wires -// splitWires with allfour reorientation values NoReorient, ReOrient, ReorientForward, -// ReorientRevesed +// splitWires with all four reorientation values NoReorient, ReOrient, ReorientForward, +// ReorientReversed TEST_F(TopoShapeExpansionTest, mapSubElementInvalidParm) { @@ -461,7 +428,8 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindShapeByNames) Part::TopoShape cube2TS {cube2}; cube1TS.Tag = 1; cube2TS.Tag = 2; - Part::TopoShape topoShape, topoShape1; + Part::TopoShape topoShape; + Part::TopoShape topoShape1; // Act int fs1 = topoShape1.findShape(cube1); @@ -546,8 +514,13 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors) cube2TS.Tag = 2; cube3TS.Tag = 3; cube4TS.Tag = 4; - Part::TopoShape topoShape, topoShape1, topoShape2; - Part::TopoShape topoShape3, topoShape4, topoShape5, topoShape6; + Part::TopoShape topoShape; + Part::TopoShape topoShape1; + Part::TopoShape topoShape2; + Part::TopoShape topoShape3; + Part::TopoShape topoShape4; + Part::TopoShape topoShape5; + Part::TopoShape topoShape6; topoShape.makeElementCompound({cube1TS, cube2TS}); topoShape1.makeElementCompound({cube3TS, cube4TS}); topoShape2.makeElementCompound({cube1TS, cube3TS}); @@ -595,7 +568,8 @@ TEST_F(TopoShapeExpansionTest, makeElementShellInvalid) TEST_F(TopoShapeExpansionTest, makeElementShellSingle) { // Arrange - const double Len = 3, Wid = 2; + const float Len = 3; + const float Wid = 2; auto [face1, wire1, edge1, edge2, edge3, _] = CreateRectFace(Len, Wid); Part::TopoShape topoShape {face1, 1L}; // Act @@ -613,7 +587,8 @@ TEST_F(TopoShapeExpansionTest, makeElementShellSingle) TEST_F(TopoShapeExpansionTest, makeElementShellOpen) { // Arrange - const double Len = 3, Wid = 2; + const float Len = 3; + const float Wid = 2; auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); auto transform {gp_Trsf()}; transform.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI / 2); @@ -644,7 +619,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellClosed) Part::TopoShape topoShape {cube1}; std::vector shapes; for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } // Act Part::TopoShape topoShape1 {1L}; @@ -671,11 +646,11 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) Part::TopoShape topoShape {cube1}; std::vector shapes; for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } topoShape.setShape(cube2); for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { - shapes.push_back(Part::TopoShape {face}); + shapes.emplace_back(face); } // Act Part::TopoShape topoShape1 {1L}; diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp new file mode 100644 index 0000000000..d7827ff781 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeMakeShapeWithElementMap.cpp @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +// Tests for the makeShapeWithElementMap method, 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 +#include +// #include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Part; +using namespace Data; + +class TopoShapeMakeShapeWithElementMapTests: public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + _docName = App::GetApplication().getUniqueDocumentName("test"); + App::GetApplication().newDocument(_docName.c_str(), "testUser"); + } + + void TearDown() override + { + App::GetApplication().closeDocument(_docName.c_str()); + } + + Part::TopoShape* Shape() + { + return &_shape; + } + + Part::TopoShape::Mapper* Mapper() + { + return &_mapper; + } + + void testFindSourceSubShapesInElementMapForSource(const std::vector& sources, + const TopoShape& source); + +private: + std::string _docName; + Data::ElementIDRefs _sid; + Part::TopoShape _shape; + Part::TopoShape::Mapper _mapper; +}; + +TEST_F(TopoShapeMakeShapeWithElementMapTests, nullShapeThrows) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + TopoDS_Vertex nullVertex; + TopoDS_Edge nullEdge; + TopoDS_Wire nullWire; + TopoDS_Face nullFace; + TopoDS_Shell nullShell; + TopoDS_Solid nullSolid; + TopoDS_CompSolid nullCompSolid; + TopoDS_Compound nullCompound; + + // Act and assert + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullVertex, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullEdge, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullWire, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullFace, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullShell, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullSolid, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullCompSolid, *Mapper(), sources), + Part::NullShapeException); + EXPECT_THROW(Shape()->makeShapeWithElementMap(nullCompound, *Mapper(), sources), + Part::NullShapeException); +} + +using Data::IndexedName, Data::MappedName; +using Part::TopoShape; + +std::map elementMap(const TopoShape& shape) +{ + std::map result {}; + auto elements = shape.getElementMap(); + for (auto const& entry : elements) { + result[entry.index] = entry.name; + } + return result; +} + +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapVertex) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapEdge) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapWire) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapFace) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapShell) +// TEST_F(TopoShapeMakeShapeWithElementMapTests, mapSolid) + +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundCount) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; + sources[1].Tag = 2; + TopoShape compound = TopoShape(); + compound.makeElementCompound(sources); + auto preElements = elementMap(compound); // Map before mapping. + // Act + compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); + auto postElements = elementMap(compound); // Map after mapping + // Assert + EXPECT_EQ(preElements.size(), 52); // Check the before map. + EXPECT_EQ(postElements.size(), 52); // 12 Edges, 8 Vertexes, 6 Faces per each Cube + EXPECT_EQ(postElements.count(IndexedName("Edge", 24)), 1); + EXPECT_EQ(postElements.count(IndexedName("Edge", 25)), 0); + EXPECT_EQ(postElements.count(IndexedName("Vertex", 16)), 1); + EXPECT_EQ(postElements.count(IndexedName("Vertex", 17)), 0); + EXPECT_EQ(postElements.count(IndexedName("Face", 12)), 1); + EXPECT_EQ(postElements.count(IndexedName("Face", 13)), 0); + EXPECT_STREQ(sources[0].shapeName().c_str(), "Solid"); + EXPECT_STREQ(sources[1].shapeName().c_str(), "Solid"); + EXPECT_STREQ(compound.shapeName().c_str(), "Compound"); +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, mapCompoundMap) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; + sources[1].Tag = 2; + // Map only one of the two sources to test different names. + sources[0].makeShapeWithElementMap(sources[0].getShape(), *Mapper(), {sources[0]}); + TopoShape compound = TopoShape(); + compound.makeElementCompound(sources); + auto preElements = elementMap(compound); // Map before mapping. + // Act + compound.makeShapeWithElementMap(compound.getShape(), *Mapper(), sources); + auto postElements = elementMap(compound); // Map after mapping + // Assert + EXPECT_EQ(preElements[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;:H1:b,E")); + EXPECT_EQ(preElements[IndexedName("Edge", 13)], MappedName("Edge1;:H2,E")); + EXPECT_EQ(postElements[IndexedName("Edge", 1)], MappedName("Edge1;MAK;:H:4,E;MAK;:H1:f,E")); + EXPECT_EQ(postElements[IndexedName("Edge", 13)], MappedName("Edge1;MAK;:H2:4,E")); +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, emptySourceShapes) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector emptySources; + std::vector nonEmptySources {cube1, cube2}; + + // Act and assert + for (auto& source : nonEmptySources) { + Part::TopoShape& modifiedShape = source; + Part::TopoShape& originalShape = source; + + EXPECT_EQ( + &originalShape, + &modifiedShape.makeShapeWithElementMap(source.getShape(), *Mapper(), emptySources)); + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, nonMappableSources) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + + // Act and assert + for (auto& source : sources) { + size_t canMap = 0; + for (const auto& mappableSource : sources) { + if (source.canMapElement(mappableSource)) { + ++canMap; + } + } + + if (canMap == 0U) { + EXPECT_EQ(&source, + &source.makeShapeWithElementMap(source.getShape(), *Mapper(), sources)); + } + } +} + +void testFindSourceShapesInSingleShape(const Part::TopoShape& cmpdShape, + const Part::TopoShape& source, + const std::vector& sources, + const TopoShape::Mapper& mapper) +{ + std::vector tmpSources {source}; + for (const auto& subSource : sources) { + Part::TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), mapper, tmpSources); + if (&source == &subSource) { + EXPECT_NE(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube1 than + // we should find it + } + else { + EXPECT_EQ(tmpShape.findShape(subSource.getShape()), + 0); // if tmpShape uses, for example, cube1 and we search for cube2 than + // we shouldn't find it + } + } + EXPECT_NE(cmpdShape.findShape(source.getShape()), + 0); // as cmpdShape is made with cube1 and cube2 we should find both of them +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceShapesInShape) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + Part::TopoShape cmpdShape; + cmpdShape.makeElementCompound(sources); + + // Act and assert + for (const auto& source : sources) { + testFindSourceShapesInSingleShape(cmpdShape, source, sources, *Mapper()); + } +} + +void testFindSubShapesForSourceWithTypeAndIndex(const std::string& shapeTypeStr, + std::map& elementStdMap, + unsigned long shapeIndex) +{ + std::string shapeIndexStr = std::to_string(shapeIndex); + std::string shapeName {shapeTypeStr + shapeIndexStr}; + + IndexedName indexedName {shapeTypeStr.c_str(), (int)shapeIndex}; + MappedName mappedName {elementStdMap[indexedName]}; + const char shapeTypePrefix {indexedName.toString()[0]}; + + EXPECT_NO_THROW(elementStdMap.at(indexedName)); // We check that the IndexedName + // is one of the keys... + EXPECT_NE(mappedName.find(shapeName.c_str()), + -1); // ... that the element name is in the MappedName... + EXPECT_EQ(mappedName.toString().back(), shapeTypePrefix); +} + +void testFindSubShapesForSourceWithType(const TopoShape& source, + const char* shapeType, + std::map& elementStdMap) +{ + std::string shapeTypeStr {shapeType}; + + // ... and all the elements of the various types in the source TopoShape ... + for (unsigned long shapeIndex = 1U; shapeIndex <= source.countSubElements(shapeType); + shapeIndex++) { + testFindSubShapesForSourceWithTypeAndIndex(shapeTypeStr, elementStdMap, shapeIndex); + } + + // ... we also check that we don't find shapes that don't exist and therefore don't + // have either an IndexedName or a MappedName + IndexedName fakeIndexedName {shapeTypeStr.c_str(), (int)source.countSubElements(shapeType) + 1}; + EXPECT_THROW(elementStdMap.at(fakeIndexedName), std::out_of_range); +} + +void TopoShapeMakeShapeWithElementMapTests::testFindSourceSubShapesInElementMapForSource( + const std::vector& sources, + const TopoShape& source) +{ + TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); + + // First we create a map with the IndexedNames and MappedNames + std::map elementStdMap; + for (const auto& mappedElement : tmpShape.getElementMap()) { + elementStdMap.emplace(mappedElement.index, mappedElement.name); + } + + // Then for all the elements types (Vertex, Edge, Face) ... + for (const auto& shapeType : source.getElementTypes()) { + testFindSubShapesForSourceWithType(source, shapeType, elementStdMap); + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findSourceSubShapesInElementMap) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + + // Act and assert + // Testing with all the source TopoShapes + for (const auto& source : sources) { + testFindSourceSubShapesInElementMapForSource(sources, source); + } +} + +TEST_F(TopoShapeMakeShapeWithElementMapTests, findMakerOpInElementMap) +{ + // Arrange + auto [cube1, cube2] = PartTestHelpers::CreateTwoCubes(); + std::vector sources {cube1, cube2}; + sources[0].Tag = 1; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + sources[1].Tag = 2; // setting Tag explicitly otherwise it is likely that this test will be + // more or less the same of nonMappableSources + + // Act and assert + // Testing with all the source TopoShapes + for (const auto& source : sources) { + TopoShape tmpShape {source.getShape()}; + tmpShape.makeShapeWithElementMap(source.getShape(), *Mapper(), sources); + + // For all the mappedElements ... + for (const auto& mappedElement : tmpShape.getElementMap()) { + EXPECT_NE(mappedElement.name.find(OpCodes::Maker), + -1); // ... we check that there's the "MAK" OpCode + } + } +}