diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index eab95306cd..86664beb21 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -3146,6 +3146,59 @@ void TopoShape::sewShape(double tolerance) this->_Shape = sew.SewedShape(); } +bool TopoShape::fix() +{ + if (this->_Shape.IsNull()) { + return false; + } + + // First, we do fix regardless if the current shape is valid or not, + // because not all problems that are handled by ShapeFix_Shape can be + // recognized by BRepCheck_Analyzer. + // + // Second, for some reason, a failed fix (i.e. a fix that produces invalid shape) + // will affect the input shape. (See // https://github.com/realthunder/FreeCAD/issues/585, + // BTW, the file attached in the issue also shows that ShapeFix_Shape may + // actually make a valid input shape invalid). So, it actually change the + // underlying shape data. Therefore, we try with a copy first. + auto copy = makeElementCopy(); + ShapeFix_Shape fix(copy._Shape); + fix.Perform(); + + if (fix.Shape().IsSame(copy._Shape)) { + return false; + } + + BRepCheck_Analyzer aChecker(fix.Shape()); + if (!aChecker.IsValid()) { + return false; + } + + // If the above fix produces a valid shape, then we fix the original shape, + // because BRepBuilderAPI_Copy has some undesired side effect (e.g. flatten + // underlying shape, and thus break internal shape sharing). + ShapeFix_Shape fixThis(this->_Shape); + fixThis.Perform(); + + aChecker.Init(fixThis.Shape()); + if (aChecker.IsValid()) { + // Must call makESHAPE() (which calls mapSubElement()) to remap element + // names because ShapeFix_Shape may delete (e.g. small edges) or modify + // the input shape. + // + // See https://github.com/realthunder/FreeCAD/issues/595. Sketch001 + // has small edges. Simply recompute the sketch to trigger call of fix() + // through makEWires(), and it will remove those edges. Without + // remapping, there will be invalid index jumpping in reference in + // Sketch002.ExternalEdge5. + makeShapeWithElementMap(fixThis.Shape(), MapperHistory(fixThis), {*this}); + } + else { + makeShapeWithElementMap(fix.Shape(), MapperHistory(fix), {copy}); + } + return true; +} + bool TopoShape::fix(double precision, double mintol, double maxtol) { if (this->_Shape.IsNull()) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 870e891125..5ba19be764 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -39,6 +39,9 @@ #include #include #include +#include +#include +#include class gp_Ax1; class gp_Ax2; @@ -53,7 +56,10 @@ class Color; namespace Part { +struct ShapeHasher; +class TopoShape; class TopoShapeCache; +typedef std::unordered_map TopoShapeMap; /* A special sub-class to indicate null shapes */ @@ -393,6 +399,7 @@ public: TopoDS_Shape replaceShape(const std::vector>& s) const; TopoDS_Shape removeShape(const std::vector& s) const; void sewShape(double tolerance = 1.0e-06); + bool fix(); bool fix(double, double, double); bool fixSolidOrientation(); bool removeInternalWires(double); @@ -687,6 +694,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=nullptr); + void mapSubElementsTo(std::vector& shapes, const char* op = nullptr) const; bool hasPendingElementMap() const; /** Helper class to return the generated and modified shape given an input shape @@ -727,22 +735,181 @@ public: const Mapper &mapper, const std::vector &sources, const char *op=nullptr); + /** + * When given a single shape to create a compound, two results are possible: either to simply + * return the shape as given, or to force it to be placed in a Compound. + */ + enum class SingleShapeCompoundCreationPolicy { + returnShape, + forceCompound + }; /** Make a compound shape * * @param shapes: input shapes * @param op: optional string to be encoded into topo naming for indicating * the operation - * @param force: if true and there is only one input shape, then return - * that shape instead. If false, then always return a - * compound, even if there is no input shape. + * @param policy: set behavior when only a single shape is given * * @return 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 &makeElementCompound(const std::vector &shapes, const char *op=nullptr, bool force=true); + TopoShape& makeElementCompound(const std::vector& shapes, + const char* op = nullptr, + SingleShapeCompoundCreationPolicy policy = + SingleShapeCompoundCreationPolicy::forceCompound); + + + enum class ConnectionPolicy + { + requireSharedVertex, + mergeWithTolerance + }; + + /** 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, + ConnectionPolicy policy = ConnectionPolicy::mergeWithTolerance, + 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 policy: if requireSharedVertex, then only connect edges if they shared the same + * vertex. If mergeWithTolerance 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, + ConnectionPolicy policy = ConnectionPolicy::mergeWithTolerance, + 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, + ConnectionPolicy policy = ConnectionPolicy::mergeWithTolerance, + TopoShapeMap* output = nullptr) const + { + return TopoShape(0, Hasher).makeElementWires(*this, op, tol, policy, output); + } + + + /** Make a deep copy of the shape + * + * @param source: input shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param copyGeom: whether to copy internal geometry of the shape + * @param copyMesh: whether to copy internal meshes of the shape + * + * @return The original content of this TopoShape is discarded and replaced + * with a deep copy of the input 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& makeElementCopy(const TopoShape& source, + const char* op = nullptr, + bool copyGeom = true, + bool copyMesh = false); + + /** Make a deep copy of the shape + * + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param copyGeom: whether to copy internal geometry of the shape + * @param copyMesh: whether to copy internal meshes of the shape + * + * @return Return a deep copy of the shape. The shape itself is not + * modified + */ + TopoShape + makeElementCopy(const char* op = nullptr, bool copyGeom = true, bool copyMesh = false) const + { + return TopoShape(Tag, Hasher).makeElementCopy(*this, op, copyGeom, copyMesh); + } /* Make a shell using this shape * @param silent: whether to throw exception on failure @@ -1223,9 +1390,22 @@ 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); + static TopoShape reverseEdge(const TopoShape& edge); }; - /** Shape mapper for generic BRepBuilderAPI_MakeShape derived class * * Uses BRepBuilderAPI_MakeShape::Modified/Generated() function to extract @@ -1241,7 +1421,21 @@ struct PartExport MapperMaker: TopoShape::Mapper const std::vector& generated(const TopoDS_Shape& s) const override; }; +/** Shape mapper for BRepTools_History + * + * Uses BRepTools_History::Modified/Generated() function to extract + * shape history for generating mapped element names + */ +struct PartExport MapperHistory: TopoShape::Mapper +{ + Handle(BRepTools_History) history; + explicit MapperHistory(const Handle(BRepTools_History) & history); + explicit MapperHistory(const Handle(BRepTools_ReShape) & reshape); + explicit MapperHistory(ShapeFix_Root& fix); + const std::vector& modified(const TopoDS_Shape& s) const override; + const std::vector& generated(const TopoDS_Shape& s) const override; +}; + } // namespace Part - #endif // PART_TOPOSHAPE_H diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index a8b0cc2b68..0aa0353e7f 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -25,6 +25,8 @@ #include "PreCompiled.h" #ifndef _PreComp_ +#include + #include #include @@ -33,12 +35,17 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include #endif #include "TopoShape.h" @@ -453,6 +460,13 @@ 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) { @@ -1277,10 +1291,11 @@ void addShapesToBuilder(const std::vector& shapes, } } // namespace -TopoShape& -TopoShape::makeElementCompound(const std::vector& shapes, const char* op, bool force) +TopoShape& TopoShape::makeElementCompound(const std::vector& shapes, + const char* op, + SingleShapeCompoundCreationPolicy policy) { - if (!force && shapes.size() == 1) { + if (policy == SingleShapeCompoundCreationPolicy::returnShape && shapes.size() == 1) { *this = shapes[0]; return *this; } @@ -1301,6 +1316,337 @@ TopoShape::makeElementCompound(const std::vector& shapes, const char* return *this; } +TopoShape& TopoShape::makeElementWires(const std::vector& shapes, + const char* op, + double tol, + ConnectionPolicy policy, + TopoShapeMap* output) +{ + if (shapes.empty()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (shapes.size() == 1) { + return makeElementWires(shapes[0], op, tol, policy, output); + } + return makeElementWires(TopoShape(Tag).makeElementCompound(shapes), op, tol, policy, output); +} + + +TopoShape& TopoShape::makeElementWires(const TopoShape& shape, + const char* op, + double tol, + ConnectionPolicy policy, + TopoShapeMap* output) +{ + if (!op) { + op = Part::OpCodes::Wire; + } + if (tol < Precision::Confusion()) { + tol = Precision::Confusion(); + } + + if (policy == ConnectionPolicy::requireSharedVertex) { + // Can't use ShapeAnalysis_FreeBounds if not shared. It seems the output + // edges are modified somehow, and it is not obvious how to map the + // resulting edges. + Handle(TopTools_HSequenceOfShape) hEdges = new TopTools_HSequenceOfShape(); + Handle(TopTools_HSequenceOfShape) hWires = new TopTools_HSequenceOfShape(); + for (TopExp_Explorer xp(shape.getShape(), TopAbs_EDGE); xp.More(); xp.Next()) { + hEdges->Append(xp.Current()); + } + if (hEdges->Length() == 0) { + FC_THROWM(NullShapeException, "Null shape"); + } + ShapeAnalysis_FreeBounds::ConnectEdgesToWires(hEdges, tol, Standard_True, hWires); + if (hWires->Length() == 0) { + FC_THROWM(NullShapeException, "Null shape"); + } + + std::vector wires; + for (int i = 1; i <= hWires->Length(); i++) { + auto wire = hWires->Value(i); + wires.emplace_back(Tag, Hasher, wire); + } + shape.mapSubElementsTo(wires, op); + return makeElementCompound(wires, "", SingleShapeCompoundCreationPolicy::returnShape); + } + + std::vector wires; + std::list edgeList; + + for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) { + edgeList.emplace_back(edge); + } + + std::vector edges; + edges.reserve(edgeList.size()); + wires.reserve(edgeList.size()); + + // sort them together to wires + while (!edgeList.empty()) { + BRepBuilderAPI_MakeWire mkWire; + // add and erase first edge + edges.clear(); + edges.push_back(edgeList.front()); + mkWire.Add(TopoDS::Edge(edges.back().getShape())); + edges.back().setShape(mkWire.Edge(), false); + if (output) { + (*output)[edges.back()] = edgeList.front(); + } + edgeList.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 = true; + while (found) { + found = false; + for (auto it = edgeList.begin(); it != edgeList.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; + } + edgeList.erase(it); + new_wire = mkWire.Wire(); + break; + } + } + } + + wires.emplace_back(new_wire); + wires.back().mapSubElement(edges, op); + wires.back().fix(); + } + return makeElementCompound(wires, nullptr, SingleShapeCompoundCreationPolicy::returnShape); +} + + +struct EdgePoints +{ + gp_Pnt v1, v2; + std::list::iterator it; + const TopoShape* edge; + bool closed {false}; + + EdgePoints(std::list::iterator it, double tol) + : it(it) + , edge(&*it) + { + 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; + } + } +}; + +TopoShape TopoShape::reverseEdge(const TopoShape& edge) +{ + Standard_Real first = NAN; + Standard_Real last = NAN; + 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, res.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, res.Tag); + res.elementMap()->setElementName(v2Name, v1, res.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, res.Tag); + } + else if (v1) { + res.elementMap()->setElementName(v2Name, v1, res.Tag); + } + else if (v2) { + res.elementMap()->setElementName(v1Name, v2, res.Tag); + } + return res; +}; + +std::deque TopoShape::sortEdges(std::list& edges, bool keepOrder, double tol) +{ + if (tol < Precision::Confusion()) { + tol = Precision::Confusion(); + } + double tol3d = tol * tol; + + std::list edgePoints; + for (auto it = edges.begin(); it != edges.end(); ++it) { + edgePoints.emplace_back(it, tol3d); + } + + std::deque sorted; + if (edgePoints.empty()) { + return sorted; + } + + gp_Pnt first; + gp_Pnt last; + first = edgePoints.front().v1; + last = edgePoints.front().v2; + + sorted.push_back(*edgePoints.front().edge); + edges.erase(edgePoints.front().it); + if (edgePoints.front().closed) { + return sorted; + } + + edgePoints.erase(edgePoints.begin()); + + while (!edgePoints.empty()) { + // search for adjacent edge + std::list::iterator pEI; + for (pEI = edgePoints.begin(); pEI != edgePoints.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); + edgePoints.erase(pEI); + pEI = edgePoints.begin(); + break; + } + if (pEI->v2.SquareDistance(first) <= tol3d) { + sorted.push_front(*pEI->edge); + first = pEI->v1; + edges.erase(pEI->it); + edgePoints.erase(pEI); + pEI = edgePoints.begin(); + break; + } + if (pEI->v2.SquareDistance(last) <= tol3d) { + last = pEI->v1; + sorted.push_back(reverseEdge(*pEI->edge)); + edges.erase(pEI->it); + edgePoints.erase(pEI); + pEI = edgePoints.begin(); + break; + } + if (pEI->v1.SquareDistance(first) <= tol3d) { + first = pEI->v2; + sorted.push_front(reverseEdge(*pEI->edge)); + edges.erase(pEI->it); + edgePoints.erase(pEI); + pEI = edgePoints.begin(); + break; + } + } + + if ((pEI == edgePoints.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 < Precision::Confusion()) { + tol = Precision::Confusion(); + } + + std::vector wires; + std::list edgeList; + + auto shape = + TopoShape().makeElementCompound(shapes, "", SingleShapeCompoundCreationPolicy::returnShape); + for (auto& edge : shape.getSubTopoShapes(TopAbs_EDGE)) { + edgeList.push_back(edge); + } + + while (!edgeList.empty()) { + BRepBuilderAPI_MakeWire mkWire; + std::vector edges; + for (auto& edge : sortEdges(edgeList, 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.emplace_back(mkWire.Wire()); + wires.back().mapSubElement(edges, op); + } + return makeElementCompound(wires, nullptr, SingleShapeCompoundCreationPolicy::returnShape); +} + + +TopoShape& +TopoShape::makeElementCopy(const TopoShape& shape, const char* op, bool copyGeom, bool copyMesh) +{ + if (shape.isNull()) { + return *this; + } + + TopoShape tmp(shape); +#if OCC_VERSION_HEX >= 0x070000 + tmp.setShape(BRepBuilderAPI_Copy(shape.getShape(), copyGeom, copyMesh).Shape(), false); +#else + tmp.setShape(BRepBuilderAPI_Copy(shape.getShape()).Shape(), false); +#endif + if (op || (shape.Tag && shape.Tag != Tag)) { + setShape(tmp._Shape); + initCache(); + if (!Hasher) { + Hasher = tmp.Hasher; + } + copyElementMap(tmp, op); + } + else { + *this = tmp; + } + return *this; +} + struct MapperSewing: Part::TopoShape::Mapper { BRepBuilderAPI_Sewing& maker; @@ -1792,6 +2138,62 @@ const std::vector& MapperMaker::generated(const TopoDS_Shape& s) c return _res; } +MapperHistory::MapperHistory(const Handle(BRepTools_History) & history) + : history(history) +{} + +MapperHistory::MapperHistory(const Handle(BRepTools_ReShape) & reshape) +{ + if (reshape) { + history = reshape->History(); + } +} + +MapperHistory::MapperHistory(ShapeFix_Root& fix) +{ + if (fix.Context()) { + history = fix.Context()->History(); + } +} + +const std::vector& MapperHistory::modified(const TopoDS_Shape& s) const +{ + _res.clear(); + try { + if (history) { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(history->Modified(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; +} + +const std::vector& MapperHistory::generated(const TopoDS_Shape& s) const +{ + _res.clear(); + try { + if (history) { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(history->Generated(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; +} + // topo naming counterpart of TopoShape::makeShell() TopoShape& TopoShape::makeElementShell(bool silent, const char* op) { diff --git a/src/Mod/Part/App/TopoShapeMapper.h b/src/Mod/Part/App/TopoShapeMapper.h index dd613b2aa5..5ec165efab 100644 --- a/src/Mod/Part/App/TopoShapeMapper.h +++ b/src/Mod/Part/App/TopoShapeMapper.h @@ -112,6 +112,7 @@ enum class MappingStatus Generated, Modified }; + /** Shape mapper for user defined shape mapping */ struct PartExport ShapeMapper: TopoShape::Mapper diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index a03ad2bd03..f92da3d9c0 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -9,7 +9,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -54,7 +57,10 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundOneShapeReturnsShape) std::vector shapes {topoShape}; // Act - topoShape.makeElementCompound(shapes, "C", false /*Don't force the creation*/); + topoShape.makeElementCompound(shapes, + "C", + Part::TopoShape::SingleShapeCompoundCreationPolicy:: + returnShape /*Don't force the creation*/); // Assert EXPECT_EQ(edge.ShapeType(), topoShape.getShape().ShapeType()); // NOT a Compound @@ -68,7 +74,10 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundOneShapeForceReturnsCompound) std::vector shapes {topoShape}; // Act - topoShape.makeElementCompound(shapes, "C", true /*Force the creation*/); + topoShape.makeElementCompound( + shapes, + "C", + Part::TopoShape::SingleShapeCompoundCreationPolicy::forceCompound /*Force the creation*/); // Assert EXPECT_NE(edge.ShapeType(), topoShape.getShape().ShapeType()); // No longer the same thing @@ -146,6 +155,125 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) // 26 subshapes each } +TEST_F(TopoShapeExpansionTest, MapperMakerModified) +{ + // Arrange + // Definition of all the objects needed for a Transformation + // (https://dev.opencascade.org/doc/refman/html/class_b_rep_builder_a_p_i___transform.html) + auto translation {gp_Trsf()}; + auto transform {BRepBuilderAPI_Transform(translation)}; + auto transformMprMkr {MapperMaker(transform)}; + + // Definition of all the objects needed for a Shape Splitting + // (https://dev.opencascade.org/doc/refman/html/class_b_rep_feat___split_shape.html) + auto splitMkr {BRepFeat_SplitShape()}; + auto splitMprMkr {MapperMaker(splitMkr)}; + + // Creating a Wire, used later to create a Face + auto wireMkr {BRepBuilderAPI_MakeWire( + BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)), + BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 0.0, 0.0), gp_Pnt(1.0, 1.0, 0.0)), + BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 1.0, 0.0), gp_Pnt(0.0, 0.0, 0.0)))}; + auto wire = wireMkr.Wire(); + + // Creating a Face using the Wire created before + auto faceMkr {BRepBuilderAPI_MakeFace(wire)}; + auto face = faceMkr.Face(); + + // Creating an Edge to split the Face and the Wire + auto edgeMkr {BRepBuilderAPI_MakeEdge(gp_Pnt(0.5, 1.0, 0.0), gp_Pnt(0.5, -1.0, 0.0))}; + auto edge = edgeMkr.Edge(); + + // Act + // Performing the Transformation + translation.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)); + transform.Perform(wire); + + // Initializing and performing the Split + splitMkr.Init(face); + splitMkr.Add(edge, face); + splitMkr.Build(); + + // Assert + // Check that all the shapes and operations have been performed + EXPECT_TRUE(wireMkr.IsDone()); + EXPECT_TRUE(faceMkr.IsDone()); + EXPECT_TRUE(edgeMkr.IsDone()); + EXPECT_TRUE(splitMkr.IsDone()); + EXPECT_TRUE(transform.IsDone()); + + // Check the result of the operations + EXPECT_EQ(transformMprMkr.modified(wire).size(), 1); // The Transformation acts on the Wire... + EXPECT_EQ(transformMprMkr.modified(face).size(), 1); // ... and therefor on the created Face... + EXPECT_EQ(transformMprMkr.modified(edge).size(), 1); // ... and on the Edge added to the Face + + EXPECT_EQ(splitMprMkr.modified(edge).size(), 0); // The Split doesn't modify the Edge + EXPECT_EQ(splitMprMkr.modified(wire).size(), 2); // The Split modifies the Wire into 2 Wires + EXPECT_EQ(splitMprMkr.modified(face).size(), 2); // The Split modifies the Face into 2 Faces +} + +TEST_F(TopoShapeExpansionTest, MapperMakerGenerated) +{ + // Arrange + // Creating tree Edges, used later in the Fuse operations + auto edge1 {BRepBuilderAPI_MakeEdge(gp_Pnt(-1.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge()}; + auto edge2 {BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, -1.0, 0.0), gp_Pnt(0.0, 1.0, 0.0)).Edge()}; + auto edge3 {BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, -1.0), gp_Pnt(0.0, 0.0, 1.0)).Edge()}; + + // Definition of all the objects needed for the Fuses + // (https://dev.opencascade.org/doc/refman/html/class_b_rep_algo_a_p_i___fuse.html) + // The Fuse operation, like other Boolean operations derived from BRepAlgoAPI_BuilderAlgo, + // supports the generation history + // (https://dev.opencascade.org/doc/refman/html/class_b_rep_algo_a_p_i___builder_algo.html) + auto fuse1Mkr {BRepAlgoAPI_Fuse(edge1, edge2)}; + auto fuse1MprMkr {MapperMaker(fuse1Mkr)}; + auto fuse2Mkr {BRepAlgoAPI_Fuse(edge1, edge3)}; + auto fuse2MprMkr {MapperMaker(fuse2Mkr)}; + + // Act + fuse1Mkr.Build(); + fuse2Mkr.Build(); + + // Assert + // Check that all the shapes and operations have been performed + EXPECT_TRUE(fuse1Mkr.IsDone()); + EXPECT_TRUE(fuse2Mkr.IsDone()); + + // Check the result of the operations + EXPECT_EQ(fuse1MprMkr.generated(edge1).size(), 1); // fuse1 has a new vertex generated by edge1 + EXPECT_EQ(fuse1MprMkr.generated(edge2).size(), 1); // fuse1 has a new vertex generated by edge2 + EXPECT_EQ(fuse1MprMkr.generated(edge3).size(), + 0); // fuse1 doesn't have a new vertex generated by edge3 + + EXPECT_EQ(fuse2MprMkr.generated(edge1).size(), 1); // fuse2 has a new vertex generated by edge1 + EXPECT_EQ(fuse2MprMkr.generated(edge2).size(), + 0); // fuse2 doesn't have a new vertex generated by edge2 + EXPECT_EQ(fuse2MprMkr.generated(edge3).size(), 1); // fuse2 has a new vertex generated by edge3 +} + +// ================================================================================================ +// The following test has been disabled to avoid the CI failing +// will be enabled again in following PRs +// ================================================================================================ + +// TEST_F(TopoShapeExpansionTest, makeElementWiresCombinesAdjacent) +// { +// // Arrange +// auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge(); +// auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(1.0, 0.0, 0.0), gp_Pnt(2.0, 0.0, 0.0)).Edge(); +// Part::TopoShape topoShape; +// std::vector shapes {edge1, edge2}; + +// // Act +// topoShape.makeElementWires(shapes); + +// // Assert +// auto elementMap = topoShape.getElementMap(); +// EXPECT_EQ(6, elementMap.size()); +// } + +// ================================================================================================ + TEST_F(TopoShapeExpansionTest, makeElementFaceNull) { // Arrange