From 574ebeae1971b6a73fb15e8268d8660b8600702b Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 13 Jan 2024 12:36:11 -0600 Subject: [PATCH] Part/Toponaming: Add original implementation of makEWires Renamed to makeElementWires and modified to compile in the current codebase. --- src/Mod/Part/App/TopoShape.h | 129 ++++++++++- src/Mod/Part/App/TopoShapeExpansion.cpp | 280 ++++++++++++++++++++++++ 2 files changed, 408 insertions(+), 1 deletion(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index ad4ef2a780..9ff898c726 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -48,7 +48,10 @@ class Color; namespace Part { +class ShapeHasher; +class TopoShape; class TopoShapeCache; +typedef std::unordered_map TopoShapeMap; /* A special sub-class to indicate null shapes */ @@ -610,6 +613,7 @@ public: 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); + void mapSubElementsTo(std::vector &shapes, const char *op=nullptr) const; bool hasPendingElementMap() const; @@ -627,7 +631,117 @@ public: * a reference so that multiple operations can be carried out for * the same shape in the same line of code. */ - TopoShape &makeElementCompound(const std::vector &shapes, const char *op=nullptr, bool force=true); + TopoShape& makeElementCompound(const std::vector& shapes, + const char* op = nullptr, + bool force = true); + + + /** Make a compound of wires by connecting input edges + * + * @param shapes: input shapes. Can be any type of shape. Edges will be + * extracted for building wires. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param keepOrder: whether to respect the order of the input edges + * @param tol: tolerance for checking the distance of two vertex to decide + * if two edges are connected + * @param shared: if true, then only connect edges if they shared the same + * vertex, or else use \c tol to check for connection. + * @param output: optional output mapping from wire edges to input edge. + * Note that edges may be modified after adding to the wire, + * so the output edges may not be the same as the input + * ones. + * + * @return The function produces either a wire or a compound of wires. The + * original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a reference so that multiple operations can be carried out for + * the same shape in the same line of code. + */ + TopoShape& makeElementWires(const std::vector& shapes, + const char* op = nullptr, + double tol = 0.0, + bool shared = false, + TopoShapeMap* output = nullptr); + + /** Make a compound of wires by connecting input edges + * + * @param shape: input shape. Can be any type of shape. Edges will be + * extracted for building wires. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param keepOrder: whether to respect the order of the input edges + * @param tol: tolerance for checking the distance of two vertex to decide + * if two edges are connected + * @param shared: if true, then only connect edges if they shared the same + * vertex, or else use \c tol to check for connection. + * @param output: optional output mapping from wire edges to input edge. + * Note that edges may be modified after adding to the wire, + * so the output edges may not be the same as the input + * ones. + * + * @return The function produces either a wire or a compound of wires. The + * original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a reference so that multiple operations can be carried out for + * the same shape in the same line of code. + */ + TopoShape& makeElementWires(const TopoShape& shape, + const char* op = nullptr, + double tol = 0.0, + bool shared = false, + TopoShapeMap* output = nullptr); + + /** Make a compound of wires by connecting input edges in the given order + * + * @param shapes: input shapes. Can be any type of shape. Edges will be + * extracted for building wires. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for checking the distance of two vertex to decide + * if two edges are connected + * @param output: optional output mapping from wire edges to input edge. + * Note that edges may be modified after adding to the wire, + * so the output edges may not be the same as the input + * ones. + * + * @return Same as makeElementWires() but respects the order of the input edges. + * The function produces either a wire or a compound of wires. The + * original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a reference so that multiple operations can be carried out for + * the same shape in the same line of code. + */ + TopoShape& makeElementOrderedWires(const std::vector& shapes, + const char* op = nullptr, + double tol = 0.0, + TopoShapeMap* output = nullptr); + + /** Make a wire or compound of wires with the edges inside the this shape + * + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param keepOrder: whether to respect the order of the input edges + * @param tol: tolerance for checking the distance of two vertex to decide + * if two edges are connected + * @param shared: if true, then only connect edges if they shared the same + * vertex, or else use \c tol to check for connection. + * @param output: optional output mapping from wire edges to input edge. + * Note that edges may be modified after adding to the wire, + * so the output edges may not be the same as the input + * ones. + * + * + * @return The function returns a new shape of either a single wire or a + * compound of wires. The shape itself is not modified. + */ + TopoShape makeElementWires(const char* op = nullptr, + double tol = 0.0, + bool shared = false, + TopoShapeMap* output = nullptr) const + { + return TopoShape(0, Hasher).makeElementWires(*this, op, tol, shared, output); + } friend class TopoShapeCache; @@ -761,6 +875,19 @@ private: bool& warned); void mapCompoundSubElements(const std::vector& shapes, const char* op); + /** Given a set of edges, return a sorted list of connected edges + * + * @param edges: (input/output) input list of shapes. Must be of type edge. + * On return, the returned connected edges will be removed + * from this list. You can repeated call this function to find + * all wires. + * @param keepOrder: whether to respect the order of the input edges + * @param tol: tolerance for checking the distance of two vertex to decide + * if two edges are connected + * @return Return a list of ordered connected edges. + */ + static std::deque + sortEdges(std::list& edges, bool keepOrder = false, double tol = 0.0); }; } // namespace Part diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index d5b5b5227b..d30ce54c00 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -27,10 +27,16 @@ #ifndef _PreComp_ #include #include +#include +#include +#include +#include +#include #endif #include "TopoShape.h" #include "TopoShapeCache.h" +#include "TopoShapeOpCode.h" FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -429,6 +435,11 @@ void TopoShape::mapSubElement(const TopoShape& other, const char* op, bool force mapSubElementForShape(other, op); } +void TopoShape::mapSubElementsTo(std::vector &shapes, const char *op) const { + for(auto &shape : shapes) + shape.mapSubElement(*this,op); +} + std::vector TopoShape::createChildMap(size_t count, const std::vector& shapes, const char* op) { @@ -540,4 +551,273 @@ TopoShape::makeElementCompound(const std::vector& shapes, const char* return *this; } +TopoShape &TopoShape::makeElementWires(const std::vector &shapes, + const char *op, + double tol, + bool shared, + TopoShapeMap *output) +{ + if(shapes.empty()) + FC_THROWM(NullShapeException, "Null shape");; + if(shapes.size() == 1) + return makeElementWires(shapes[0],op,tol,shared,output); + return makeElementWires(TopoShape(Tag).makeElementCompound(shapes),op,tol,shared,output); +} + + +TopoShape &TopoShape::makeElementWires(const TopoShape &shape, + const char *op, + double tol, + bool shared, + TopoShapeMap *output) +{ + if(!op) op = Part::OpCodes::Wire; + if(tolAppend(xp.Current()); + if(!hEdges->Length()) + FC_THROWM(NullShapeException, "Null shape");; + ShapeAnalysis_FreeBounds::ConnectEdgesToWires(hEdges, tol, Standard_True, hWires); + if(!hWires->Length()) + FC_THROWM(NullShapeException, "Null shape");; + + std::vector wires; + for (int i=1; i<=hWires->Length(); i++) { + auto wire = hWires->Value(i); + wires.push_back(TopoShape(Tag,Hasher,wire)); + } + shape.mapSubElementsTo(wires,op); + return makeElementCompound(wires,"",false); + } + + std::vector wires; + std::list edge_list; + + for(auto &e : shape.getSubTopoShapes(TopAbs_EDGE)) + edge_list.emplace_back(e); + + std::vector edges; + edges.reserve(edge_list.size()); + wires.reserve(edge_list.size()); + + // sort them together to wires + while (edge_list.size() > 0) { + BRepBuilderAPI_MakeWire mkWire; + // add and erase first edge + edges.clear(); + edges.push_back(edge_list.front()); + mkWire.Add(TopoDS::Edge(edges.back().getShape())); + edges.back().setShape(mkWire.Edge(),false); + if (output) + (*output)[edges.back()] = edge_list.front(); + edge_list.pop_front(); + + TopoDS_Wire new_wire = mkWire.Wire(); // current new wire + + // try to connect each edge to the wire, the wire is complete if no more edges are connectible + bool found = false; + do { + found = false; + for (auto it=edge_list.begin();it!=edge_list.end();++it) { + mkWire.Add(TopoDS::Edge(it->getShape())); + if (mkWire.Error() != BRepBuilderAPI_DisconnectedWire) { + // edge added ==> remove it from list + found = true; + edges.push_back(*it); + // MakeWire will replace vertex of connected edge, which + // effectively creat a new edge. So we need to update the + // shape in order to preserve element mapping. + edges.back().setShape(mkWire.Edge(),false); + if (output) + (*output)[edges.back()] = *it; + edge_list.erase(it); + new_wire = mkWire.Wire(); + break; + } + } + } while (found); + + wires.emplace_back(new_wire); + wires.back().mapSubElement(edges, op); + wires.back().fix(); + } + return makeElementCompound(wires,0,false); +} + + +struct EdgePoints { + gp_Pnt v1, v2; + std::list::iterator it; + const TopoShape &edge; + bool closed; + + EdgePoints(std::list::iterator it, double tol) + :it(it), edge(*it), closed(false) + { + TopExp_Explorer xp(it->getShape(),TopAbs_VERTEX); + v1 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); + xp.Next(); + if (xp.More()) { + v2 = BRep_Tool::Pnt(TopoDS::Vertex(xp.Current())); + closed = (v2.SquareDistance(v1) <= tol); + } else { + v2 = v1; + closed = true; + } + } +}; + +std::deque +TopoShape::sortEdges(std::list& edges, bool keepOrder, double tol) +{ + if (tol edge_points; + for (auto it = edges.begin(); it != edges.end(); ++it) + edge_points.emplace_back(it, tol3d); + + std::deque sorted; + if (edge_points.empty()) + return sorted; + + gp_Pnt first, last; + first = edge_points.front().v1; + last = edge_points.front().v2; + + sorted.push_back(edge_points.front().edge); + edges.erase(edge_points.front().it); + if (edge_points.front().closed) + return sorted; + + edge_points.erase(edge_points.begin()); + + auto reverseEdge = [](const TopoShape &edge) { + Standard_Real first, last; + const Handle(Geom_Curve) & curve = BRep_Tool::Curve(TopoDS::Edge(edge.getShape()), first, last); + first = curve->ReversedParameter(first); + last = curve->ReversedParameter(last); + TopoShape res(BRepBuilderAPI_MakeEdge(curve->Reversed(), last, first)); + auto edgeName = Data::IndexedName::fromConst("Edge", 1); + if (auto mapped = edge.getMappedName(edgeName)) + res.elementMap()->setElementName(edgeName, mapped, Tag); + auto v1Name = Data::IndexedName::fromConst("Vertex", 1); + auto v2Name = Data::IndexedName::fromConst("Vertex", 2); + auto v1 = edge.getMappedName(v1Name); + auto v2 = edge.getMappedName(v2Name); + if (v1 && v2) { + res.elementMap()->setElementName(v1Name, v2, Tag); + res.elementMap()->setElementName(v2Name, v1, Tag); + } + else if (v1 && edge.countSubShapes(TopAbs_EDGE) == 1) { + // It's possible an edge has only one vertex, so no need to reverse + // the name + res.elementMap()->setElementName(v1Name, v1, Tag); + } + else if (v1) + res.elementMap()->setElementName(v2Name, v1, Tag); + else if (v2) + res.elementMap()->setElementName(v1Name, v2, Tag); + return res; + }; + + while (!edge_points.empty()) { + // search for adjacent edge + std::list::iterator pEI; + for (pEI = edge_points.begin(); pEI != edge_points.end(); ++pEI) { + if (pEI->closed) + continue; + + if (keepOrder && sorted.size() == 1) { + if (pEI->v2.SquareDistance(first) <= tol3d + || pEI->v1.SquareDistance(first) <= tol3d) { + sorted[0] = reverseEdge(sorted[0]); + std::swap(first, last); + } + } + + if (pEI->v1.SquareDistance(last) <= tol3d) { + last = pEI->v2; + sorted.push_back(pEI->edge); + edges.erase(pEI->it); + edge_points.erase(pEI); + pEI = edge_points.begin(); + break; + } + else if (pEI->v2.SquareDistance(first) <= tol3d) { + sorted.push_front(pEI->edge); + first = pEI->v1; + edges.erase(pEI->it); + edge_points.erase(pEI); + pEI = edge_points.begin(); + break; + } + else if (pEI->v2.SquareDistance(last) <= tol3d) { + last = pEI->v1; + sorted.push_back(reverseEdge(pEI->edge)); + edges.erase(pEI->it); + edge_points.erase(pEI); + pEI = edge_points.begin(); + break; + } + else if (pEI->v1.SquareDistance(first) <= tol3d) { + first = pEI->v2; + sorted.push_front(reverseEdge(pEI->edge)); + edges.erase(pEI->it); + edge_points.erase(pEI); + pEI = edge_points.begin(); + break; + } + } + + if ((pEI == edge_points.end()) || (last.SquareDistance(first) <= tol3d)) { + // no adjacent edge found or polyline is closed + return sorted; + } + } + + return sorted; +} + +TopoShape &TopoShape::makeElementOrderedWires(const std::vector &shapes, + const char *op, + double tol, + TopoShapeMap *output) +{ + if(!op) op = Part::OpCodes::Wire; + if(tol wires; + std::list edge_list; + + auto shape = TopoShape().makeElementCompound(shapes, "", false); + for(auto &e : shape.getSubTopoShapes(TopAbs_EDGE)) + edge_list.push_back(e); + + while(edge_list.size()) { + BRepBuilderAPI_MakeWire mkWire; + std::vector edges; + for (auto &edge : sortEdges(edge_list, true, tol)) { + edges.push_back(edge); + mkWire.Add(TopoDS::Edge(edge.getShape())); + // MakeWire will replace vertex of connected edge, which + // effectively creat a new edge. So we need to update the shape + // in order to preserve element mapping. + edges.back().setShape(mkWire.Edge(), false); + if (output) + (*output)[edges.back()] = edge; + } + wires.push_back(mkWire.Wire()); + wires.back().mapSubElement(edges,op); + } + return makeElementCompound(wires,0,false); +} + } // namespace Part