diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index f78ba5e7df..b6bbd06133 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -118,6 +118,12 @@ enum class RefineFail throwException }; +/// Behavior of findSubShapesWithSharedVertex. +enum class CheckGeometry +{ + ignoreGeometry, + checkGeometry +}; /** The representation for a CAD Shape */ class PartExport TopoShape: public Data::ComplexGeoData @@ -703,10 +709,13 @@ public: std::vector findAncestors(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const; std::vector findAncestorsShapes(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const; - /** Search sub shape + /** Find sub shapes with shared Vertexes. * + * Renamed: searchSubShape -> findSubShapesWithSharedVertex + * * unlike findShape(), the input shape does not have to be an actual * sub-shape of this shape. The sub-shape is searched by shape geometry + * Note that subshape must be a Vertex, Edge, or Face. * * @param subshape: a sub shape to search * @param names: optional output of found sub shape indexed based name @@ -714,11 +723,10 @@ public: * @param tol: tolerance to check coincident vertices * @param atol: tolerance to check for same angles */ - // TODO: Implement this method and its tests later in Toponaming Phase 3. - // std::vector searchSubShape(const TopoShape &subshape, - // std::vector *names=nullptr, - // bool checkGeometry=true, - // double tol=1e-7, double atol=1e-12) const; + std::vector findSubShapesWithSharedVertex(const TopoShape &subshape, + std::vector *names=nullptr, + CheckGeometry checkGeometry=CheckGeometry::checkGeometry, + double tol=1e-7, double atol=1e-12) const; //@} void copyElementMap(const TopoShape & topoShape, const char *op=nullptr); diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index d5a505162b..c7c7081cbf 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -68,6 +68,7 @@ #include "TopoShapeCache.h" #include "TopoShapeMapper.h" #include "FaceMaker.h" +#include "Geometry.h" #include @@ -221,6 +222,248 @@ TopoDS_Shape TopoShape::findShape(TopAbs_ShapeEnum type, int idx) const return _cache->findShape(_Shape, type, idx); } +std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& subshape, + std::vector* names, + CheckGeometry checkGeometry, + double tol, + double atol) const +{ + std::vector res; + if (subshape.isNull() || this->isNull()) { + return res; + } + double tol2 = tol * tol; + int i = 0; + TopAbs_ShapeEnum shapeType = subshape.shapeType(); + switch (shapeType) { + case TopAbs_VERTEX: + // Vertex search will do comparison with tolerance to account for + // rounding error inccured through transformation. + for (auto& s : getSubTopoShapes(TopAbs_VERTEX)) { + ++i; + if (BRep_Tool::Pnt(TopoDS::Vertex(s.getShape())) + .SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(subshape.getShape()))) + <= tol2) { + if (names) { + names->push_back(std::string("Vertex") + std::to_string(i)); + } + res.push_back(s); + } + } + break; + case TopAbs_EDGE: + case TopAbs_FACE: { + std::unique_ptr g; + bool isLine = false; + bool isPlane = false; + + std::vector vertices; + TopoShape wire; + if (shapeType == TopAbs_FACE) { + wire = subshape.splitWires(); + vertices = wire.getSubShapes(TopAbs_VERTEX); + } + else { + vertices = subshape.getSubShapes(TopAbs_VERTEX); + } + + if (vertices.empty() || checkGeometry == CheckGeometry::checkGeometry) { + g = Geometry::fromShape(subshape.getShape()); + if (!g) { + return res; + } + if (shapeType == TopAbs_EDGE) { + isLine = (g->isDerivedFrom(GeomLine::getClassTypeId()) + || g->isDerivedFrom(GeomLineSegment::getClassTypeId())); + } + else { + isPlane = g->isDerivedFrom(GeomPlane::getClassTypeId()); + } + } + + auto compareGeometry = [&](const TopoShape& s, bool strict) { + std::unique_ptr g2(Geometry::fromShape(s.getShape())); + if (!g2) { + return false; + } + if (isLine && !strict) { + // For lines, don't compare geometry, just check the + // vertices below instead, because the exact same edge + // may have different geometrical representation. + if (!g2->isDerivedFrom(GeomLine::getClassTypeId()) + && !g2->isDerivedFrom(GeomLineSegment::getClassTypeId())) { + return false; + } + } + else if (isPlane && !strict) { + // For planes, don't compare geometry either, so that + // we don't need to worry about orientation and so on. + // Just check the edges. + if (!g2->isDerivedFrom(GeomPlane::getClassTypeId())) { + return false; + } + } + else if (!g2 || !g2->isSame(*g, tol, atol)) { + return false; + } + return true; + }; + + if (vertices.empty()) { + // Probably an infinite shape, so we have to search by geometry + int idx = 0; + for (auto& s : getSubTopoShapes(shapeType)) { + ++idx; + if (!s.countSubShapes(TopAbs_VERTEX) && compareGeometry(s, true)) { + if (names) { + names->push_back(shapeName(shapeType) + std::to_string(idx)); + } + res.push_back(s); + } + } + break; + } + + // The basic idea of shape search is about the same for both edge and face. + // * Search the first vertex, which is done with tolerance. + // * Find the ancestor shape of the found vertex + // * Compare each vertex of the ancestor shape and the input shape + // * Perform geometry comparison of the ancestor and input shape. + // * For face, perform addition geometry comparison of each edges. + std::unordered_set shapeSet; + for (auto& v : findSubShapesWithSharedVertex(vertices[0], nullptr, checkGeometry, tol, atol)) { + for (auto idx : findAncestors(v.getShape(), shapeType)) { + auto s = getSubTopoShape(shapeType, idx); + if (!shapeSet.insert(s).second) { + continue; + } + TopoShape otherWire; + std::vector otherVertices; + if (shapeType == TopAbs_FACE) { + otherWire = s.splitWires(); + if (wire.countSubShapes(TopAbs_EDGE) + != otherWire.countSubShapes(TopAbs_EDGE)) { + continue; + } + otherVertices = otherWire.getSubShapes(TopAbs_VERTEX); + } + else { + otherVertices = s.getSubShapes(TopAbs_VERTEX); + } + if (otherVertices.size() != vertices.size()) { + continue; + } + if (checkGeometry == CheckGeometry::checkGeometry && !compareGeometry(s, false)) { + continue; + } + unsigned i = 0; + bool matched = true; + for (auto& v : vertices) { + bool found = false; + for (unsigned j = 0; j < otherVertices.size(); ++j) { + auto& v1 = otherVertices[i]; + if (++i == otherVertices.size()) { + i = 0; + } + if (BRep_Tool::Pnt(TopoDS::Vertex(v)) + .SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(v1))) + <= tol2) { + found = true; + break; + } + } + if (!found) { + matched = false; + break; + } + } + if (!matched) { + continue; + } + + if (shapeType == TopAbs_FACE && checkGeometry == CheckGeometry::checkGeometry) { + // Is it really necessary to check geometries of each edge of a face? + // Right now we only do outer wire check + auto otherEdges = otherWire.getSubShapes(TopAbs_EDGE); + std::vector> geos; + geos.resize(otherEdges.size()); + bool matched = true; + unsigned i = 0; + auto edges = wire.getSubShapes(TopAbs_EDGE); + for (auto& e : edges) { + std::unique_ptr g(Geometry::fromShape(e)); + if (!g) { + matched = false; + break; + } + bool isLine = false; + gp_Pnt pt1, pt2; + if (g->isDerivedFrom(GeomLine::getClassTypeId()) + || g->isDerivedFrom(GeomLineSegment::getClassTypeId())) { + pt1 = BRep_Tool::Pnt(TopExp::FirstVertex(TopoDS::Edge(e))); + pt2 = BRep_Tool::Pnt(TopExp::LastVertex(TopoDS::Edge(e))); + isLine = true; + } + // We will tolerate on edge reordering + bool found = false; + for (unsigned j = 0; j < otherEdges.size(); j++) { + auto& e1 = otherEdges[i]; + auto& g1 = geos[i]; + if (++i >= otherEdges.size()) { + i = 0; + } + if (!g1) { + g1 = Geometry::fromShape(e1); + if (!g1) { + break; + } + } + if (isLine) { + if (g1->isDerivedFrom(GeomLine::getClassTypeId()) + || g1->isDerivedFrom(GeomLineSegment::getClassTypeId())) { + auto p1 = + BRep_Tool::Pnt(TopExp::FirstVertex(TopoDS::Edge(e1))); + auto p2 = + BRep_Tool::Pnt(TopExp::LastVertex(TopoDS::Edge(e1))); + if ((p1.SquareDistance(pt1) <= tol2 + && p2.SquareDistance(pt2) <= tol2) + || (p1.SquareDistance(pt2) <= tol2 + && p2.SquareDistance(pt1) <= tol2)) { + found = true; + break; + } + } + continue; + } + + if (g1->isSame(*g, tol, atol)) { + found = true; + break; + } + } + if (!found) { + matched = false; + break; + } + } + if (!matched) { + continue; + } + } + if (names) { + names->push_back(shapeName(shapeType) + std::to_string(idx)); + } + res.push_back(s); + } + } + break; + } + default: + break; + } + return res; +} + int TopoShape::findAncestor(const TopoDS_Shape& subshape, TopAbs_ShapeEnum type) const { initCache(); diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 0eb8818880..84279ed41a 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -746,6 +746,129 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors) EXPECT_TRUE(ancestorShapeList.back().IsEqual(topoShape6.getShape())); } +TEST_F(TopoShapeExpansionTest, findSubShapesWithSharedVertexEverything) +{ + // Arrange + auto [box1, box2] = CreateTwoCubes(); + TopoShape box1TS {box1}; + std::vector names; + std::vector names1; + std::vector names2; + double tol {2}; // Silly big tolerance to get everything + double atol {2}; + + TopExp_Explorer exp(box1, TopAbs_FACE); + auto face = exp.Current(); + exp.Init(box1, TopAbs_EDGE); + auto edge = exp.Current(); + exp.Init(box1, TopAbs_VERTEX); + auto vertex = exp.Current(); + // Act + auto shapes = + box1TS.findSubShapesWithSharedVertex(face, &names, CheckGeometry::checkGeometry, tol, atol); + auto shapes1 = box1TS.findSubShapesWithSharedVertex(edge, + &names1, + CheckGeometry::checkGeometry, + tol, + atol); + auto shapes2 = box1TS.findSubShapesWithSharedVertex(vertex, + &names2, + CheckGeometry::checkGeometry, + tol, + atol); + // Assert + EXPECT_EQ(shapes.size(), 6); + EXPECT_EQ(names.size(), 6); + EXPECT_STREQ(names[0].c_str(), "Face1"); + EXPECT_STREQ(names[1].c_str(), "Face3"); + EXPECT_STREQ(names[2].c_str(), "Face6"); + EXPECT_STREQ(names[3].c_str(), "Face5"); + EXPECT_STREQ(names[4].c_str(), "Face4"); + EXPECT_STREQ(names[5].c_str(), "Face2"); + EXPECT_EQ(shapes1.size(), 12); + EXPECT_EQ(names1.size(), 12); + EXPECT_EQ(shapes2.size(), 8); + EXPECT_EQ(names2.size(), 8); +} + +TEST_F(TopoShapeExpansionTest, findSubShapesWithSharedVertexMid) +{ + // Arrange + auto [box1, box2] = CreateTwoCubes(); + TopoShape box1TS {box1}; + std::vector names; + std::vector names1; + std::vector names2; + double tol {1e-0}; + double atol {1e-04}; + + TopExp_Explorer exp(box1, TopAbs_FACE); + auto face = exp.Current(); + exp.Init(box1, TopAbs_EDGE); + auto edge = exp.Current(); + exp.Init(box1, TopAbs_VERTEX); + auto vertex = exp.Current(); + // Act + auto shapes = + box1TS.findSubShapesWithSharedVertex(face, &names, CheckGeometry::checkGeometry, tol, atol); + auto shapes1 = box1TS.findSubShapesWithSharedVertex(edge, + &names1, + CheckGeometry::checkGeometry, + tol, + atol); + auto shapes2 = box1TS.findSubShapesWithSharedVertex(vertex, + &names2, + CheckGeometry::checkGeometry, + tol, + atol); + // Assert + EXPECT_EQ(shapes.size(), 6); + EXPECT_EQ(names.size(), 6); + EXPECT_EQ(shapes1.size(), 7); + EXPECT_EQ(names1.size(), 7); + EXPECT_EQ(shapes2.size(), 4); + EXPECT_EQ(names2.size(), 4); +} + +TEST_F(TopoShapeExpansionTest, findSubShapesWithSharedVertexClose) +{ + // Arrange + auto [box1, box2] = CreateTwoCubes(); + TopoShape box1TS {box1}; + std::vector names; + std::vector names1; + std::vector names2; + double tol {1e-02}; + double atol {1e-04}; + + TopExp_Explorer exp(box1, TopAbs_FACE); + auto face = exp.Current(); + exp.Init(box1, TopAbs_EDGE); + auto edge = exp.Current(); + exp.Init(box1, TopAbs_VERTEX); + auto vertex = exp.Current(); + // Act + auto shapes = + box1TS.findSubShapesWithSharedVertex(face, &names, CheckGeometry::checkGeometry, tol, atol); + auto shapes1 = box1TS.findSubShapesWithSharedVertex(edge, + &names1, + CheckGeometry::checkGeometry, + tol, + atol); + auto shapes2 = box1TS.findSubShapesWithSharedVertex(vertex, + &names2, + CheckGeometry::checkGeometry, + tol, + atol); + // Assert + EXPECT_EQ(shapes.size(), 1); + EXPECT_EQ(names.size(), 1); + EXPECT_EQ(shapes1.size(), 1); + EXPECT_EQ(names1.size(), 1); + EXPECT_EQ(shapes2.size(), 1); + EXPECT_EQ(names2.size(), 1); +} + TEST_F(TopoShapeExpansionTest, makeElementShellInvalid) { // Arrange