From 4688de023931ccd6a6761f2af32ce089950f8693 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Mon, 19 Feb 2024 22:05:42 -0500 Subject: [PATCH 1/4] Toposhape/Part: Clean GeneralFuse, Fuse, Cut; add tests; tweak other tests --- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 5d765d953e..60a5a99129 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1359,7 +1359,9 @@ TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) // Assert TopoShape result = topoShape1.makeElementShellFromWires(shapes); #if OCC_VERSION_HEX >= 0x070400 - EXPECT_EQ(result.getShape().NbChildren(), 20); // 6 TODO: VERSION DEPENDENT? + EXPECT_EQ(result.getShape().NbChildren(), 20); +#else + EXPECT_EQ(result.getShape().NbChildren(), 6); #endif EXPECT_EQ(result.countSubElements("Vertex"), 8); EXPECT_EQ(result.countSubElements("Edge"), 32); @@ -1765,6 +1767,7 @@ TEST_F(TopoShapeExpansionTest, makeElementCut) "CUT;:H1:7,V);CUT;:H1:3c,E|Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E);CUT;:H1:cb,F")); } +<<<<<<< HEAD TEST_F(TopoShapeExpansionTest, makeElementChamfer) { // Arrange @@ -2642,4 +2645,6 @@ TEST_F(TopoShapeExpansionTest, traceElement) })); } +======= +>>>>>>> f6b3402577 (Toposhape/Part: Clean GeneralFuse, Fuse, Cut; add tests; tweak other tests) // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) From 582d015eaf5b009a4aefd7871fea3c1e5b163ef1 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Tue, 20 Feb 2024 14:23:01 -0500 Subject: [PATCH 2/4] Clean and add tests for makeElementSolid --- src/Mod/Part/App/TopoShapeExpansion.cpp | 1 + tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 0ce927e884..fe8c7a52aa 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 60a5a99129..bf15036f4e 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1767,7 +1767,6 @@ TEST_F(TopoShapeExpansionTest, makeElementCut) "CUT;:H1:7,V);CUT;:H1:3c,E|Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E);CUT;:H1:cb,F")); } -<<<<<<< HEAD TEST_F(TopoShapeExpansionTest, makeElementChamfer) { // Arrange @@ -2645,6 +2644,4 @@ TEST_F(TopoShapeExpansionTest, traceElement) })); } -======= ->>>>>>> f6b3402577 (Toposhape/Part: Clean GeneralFuse, Fuse, Cut; add tests; tweak other tests) // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) From 201d48659f199f24f5b6bf6f9c50ddfab12a44bb Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 20 Feb 2024 12:32:36 -0500 Subject: [PATCH 3/4] Toposhape/Part: Transfer in makEOffset, makEOffsetFace, makEOffset2d --- src/Mod/Part/App/TopoShape.h | 131 +++++++ src/Mod/Part/App/TopoShapeExpansion.cpp | 433 ++++++++++++++++++++++++ 2 files changed, 564 insertions(+) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index a705b15742..b704c5edfe 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -882,6 +882,137 @@ public: return TopoShape(0,Hasher).makeElementThickSolid(*this,faces,offset,tol,intersection,selfInter, offsetMode,join,op); } + /** Make a 3D offset of a given shape + * + * @param source: source shape + * @param offset: distance to offset + * @param tol: tolerance criterion for coincidence in generated shapes + * @param intersection: whether to check intersection in all generated parallel + * (OCCT document states the option is not fully implemented) + * @param selfInter: whether to eliminate self intersection + * (OCCT document states the option is not implemented) + * @param offsetMode: defines the construction type of parallels applied to free edges + * (OCCT document states the option is not implemented) + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param fill: whether to build a solid by fill the offset + * @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 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 &makEOffset(const TopoShape &source, double offset, double tol, + bool intersection = false, bool selfInter = false, short offsetMode = 0, + JoinType join = JoinType::Arc, bool fill = false, const char *op=nullptr); + + /** Make a 3D offset of this shape + * + * @param offset: distance to offset + * @param tol: tolerance criterion for coincidence in generated shapes + * @param intersection: whether to check intersection in all generated parallel + * (OCCT document states the option is not fully implemented) + * @param selfInter: whether to eliminate self intersection + * (OCCT document states the option is not implemented) + * @param offsetMode: defines the construction type of parallels applied to free edges + * (OCCT document states the option is not implemented) + * @param fill: whether to build a solid by fill the offset + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape makEOffset(double offset, double tol, bool intersection = false, bool selfInter = false, + short offsetMode=0, JoinType join=JoinType::Arc, bool fill=false, const char *op=nullptr) const { + return TopoShape(0,Hasher).makEOffset(*this,offset,tol,intersection,selfInter, + offsetMode,join,fill,op); + } + + /** Make a 2D offset of a given shape + * + * @param source: source shape of edge, wire, face, or compound + * @param offset: distance to offset + * @param allowOpenResult: whether to allow open edge/wire + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param intersection: if true, then offset all non-compound shape + * together to deal with possible intersection after + * expanding the shape. If false, then offset each + * shape separately. + * @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 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 &makEOffset2D(const TopoShape &source, double offset, JoinType join=JoinType::Arc, bool fill=false, + bool allowOpenResult=false, bool intersection=false, const char *op=nullptr); + /** Make a 2D offset of a given shape + * + * @param source: source shape of edge, wire, face, or compound + * @param offset: distance to offset + * @param allowOpenResult: whether to allow open edge/wire + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param intersection: if true, then offset all non-compound shape + * together to deal with possible intersection after + * expanding the shape. If false, then offset each + * shape separately. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape makEOffset2D(double offset, JoinType join=JoinType::Arc, bool fill=false, bool allowOpenResult=false, + bool intersection=false, const char *op=nullptr) const { + return TopoShape(0,Hasher).makEOffset2D(*this,offset,join,fill,allowOpenResult,intersection,op); + } + + /** Make a 2D offset of face with separate control for outer and inner (hole) wires + * + * @param source: source shape of any type, but only faces inside will be used + * @param offset: distance to offset for outer wires of the faces + * @param innerOffset: distance to offset for inner wires of the faces + * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. + * @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection. + * @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 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 &makEOffsetFace(const TopoShape &source, + double offset, + double innerOffset, + JoinType join = JoinType::Arc, + JoinType innerJoin = JoinType::Arc, + const char *op = nullptr); + + /** Make a 2D offset of face with separate control for outer and inner (hole) wires + * + * @param source: source shape of any type, but only faces inside will be used + * @param offset: distance to offset for outer wires of the faces + * @param innerOffset: distance to offset for inner wires of the faces + * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. + * @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape makEOffsetFace(double offset, + double innerOffset, + JoinType join = JoinType::Arc, + JoinType innerJoin = JoinType::Arc, + const char *op = nullptr) const + { + return TopoShape(0,Hasher).makEOffsetFace(*this,offset,innerOffset,join,innerJoin,op); + } + /** Make revolved shell around a basis shape diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index fe8c7a52aa..220363d9ea 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2271,6 +2271,439 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector& shapes, return makeElementShape(mkPipeShell, shapes, op); } +TopoShape &TopoShape::makEOffset(const TopoShape &shape, + double offset, double tol, bool intersection, bool selfInter, + short offsetMode, JoinType join, bool fill, const char *op) +{ + if(!op) op = Part::OpCodes::Offset; + +#if OCC_VERSION_HEX < 0x070200 + BRepOffsetAPI_MakeOffsetShape mkOffset(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode), + intersection ? Standard_True : Standard_False, + selfInter ? Standard_True : Standard_False, + GeomAbs_JoinType(join)); +#else + BRepOffsetAPI_MakeOffsetShape mkOffset; + mkOffset.PerformByJoin(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode), + intersection ? Standard_True : Standard_False, + selfInter ? Standard_True : Standard_False, + GeomAbs_JoinType(join)); +#endif + + if (!mkOffset.IsDone()) + FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffsetShape not done"); + + TopoShape res(Tag,Hasher); + res.makEShape(mkOffset,shape,op); + if(shape.hasSubShape(TopAbs_SOLID) && !res.hasSubShape(TopAbs_SOLID)) { + try { + res = res.makESolid(); + }catch (Standard_Failure &e) { + FC_WARN("failed to make solid: " << e.GetMessageString()); + } + } + if (!fill) { + *this = res; + return *this; + } + + //get perimeter wire of original shape. + //Wires returned seem to have edges in connection order. + ShapeAnalysis_FreeBoundsProperties freeCheck(shape.getShape()); + freeCheck.Perform(); + if (freeCheck.NbClosedFreeBounds() < 1) + { + FC_THROWM(Base::CADKernelError,"no closed bounds"); + } + + BRep_Builder builder; + std::vector shapes; + for (int index = 1; index <= freeCheck.NbClosedFreeBounds(); ++index) + { + TopoShape originalWire(shape.Tag,shape.Hasher,freeCheck.ClosedFreeBound(index)->FreeBound()); + originalWire.mapSubElement(shape); + const BRepAlgo_Image& img = mkOffset.MakeOffset().OffsetEdgesFromShapes(); + + //build offset wire. + TopoDS_Wire offsetWire; + builder.MakeWire(offsetWire); + for(const auto &s : originalWire.getSubShapes(TopAbs_EDGE)) { + if (!img.HasImage(s)) + { + FC_THROWM(Base::CADKernelError,"no image for shape"); + } + const TopTools_ListOfShape& currentImage = img.Image(s); + TopTools_ListIteratorOfListOfShape listIt; + int edgeCount(0); + TopoDS_Edge mappedEdge; + for (listIt.Initialize(currentImage); listIt.More(); listIt.Next()) + { + if (listIt.Value().ShapeType() != TopAbs_EDGE) + continue; + edgeCount++; + mappedEdge = TopoDS::Edge(listIt.Value()); + } + + if (edgeCount != 1) + { + std::ostringstream stream; + stream << "wrong edge count: " << edgeCount << std::endl; + FC_THROWM(Base::CADKernelError,stream.str().c_str()); + } + builder.Add(offsetWire, mappedEdge); + } + std::vector wires; + wires.push_back(originalWire); + wires.push_back(TopoShape(Tag,Hasher,offsetWire)); + wires.back().mapSubElement(res); + + //It would be nice if we could get thruSections to build planar faces + //in all areas possible, so we could run through refine. I tried setting + //ruled to standard_true, but that didn't have the desired affect. + BRepOffsetAPI_ThruSections aGenerator; + aGenerator.AddWire(TopoDS::Wire(originalWire.getShape())); + aGenerator.AddWire(offsetWire); + aGenerator.Build(); + if (!aGenerator.IsDone()) + { + FC_THROWM(Base::CADKernelError,"ThruSections failed"); + } + + shapes.push_back(TopoShape(Tag,Hasher).makEShape(aGenerator,wires)); + } + + TopoShape perimeterCompound(Tag,Hasher); + perimeterCompound.makECompound(shapes,op); + + //still had to sew. not using the passed in parameter for sew. + //Sew has it's own default tolerance. Opinions? + BRepBuilderAPI_Sewing sewTool; + sewTool.Add(shape.getShape()); + sewTool.Add(perimeterCompound.getShape()); + sewTool.Add(res.getShape()); + sewTool.Perform(); //Perform Sewing + + TopoDS_Shape outputShape = sewTool.SewedShape(); + if ((outputShape.ShapeType() == TopAbs_SHELL) && (outputShape.Closed())) + { + BRepBuilderAPI_MakeSolid solidMaker(TopoDS::Shell(outputShape)); + if (solidMaker.IsDone()) + { + TopoDS_Solid temp = solidMaker.Solid(); + //contrary to the occ docs the return value OrientCloseSolid doesn't + //indicate whether the shell was open or not. It returns true with an + //open shell and we end up with an invalid solid. + if (BRepLib::OrientClosedSolid(temp)) + outputShape = temp; + } + } + + shapes.clear(); + shapes.push_back(shape); + shapes.push_back(res); + shapes.push_back(perimeterCompound); + *this = TopoShape(Tag,Hasher).makESHAPE(outputShape,MapperSewing(sewTool),shapes,op); + return *this; +} + +TopoShape &TopoShape::makEOffsetFace(const TopoShape &shape, + double offset, + double innerOffset, + JoinType joinType, + JoinType innerJoinType, + const char *op) +{ + if (std::abs(innerOffset) < Precision::Confusion() + && std::abs(offset) < Precision::Confusion()) { + *this = shape; + return *this; + } + + if (shape.isNull()) + FC_THROWM(Base::ValueError, "makeOffsetFace: input shape is null!"); + if (!shape.hasSubShape(TopAbs_FACE)) + FC_THROWM(Base::ValueError, "makeOffsetFace: no face found"); + + std::vector res; + for (auto & face : shape.getSubTopoShapes(TopAbs_FACE)) { + std::vector wires; + TopoShape outerWire = face.splitWires(&wires, ReorientForward); + if (wires.empty()) { + res.push_back(makEOffset2D(face, offset, joinType, false, false, false, op)); + continue; + } + if (outerWire.isNull()) + FC_THROWM(Base::CADKernelError, "makeOffsetFace: missing outer wire!"); + + if (std::abs(offset) > Precision::Confusion()) + outerWire = outerWire.makEOffset2D(offset, joinType, false, false, false, op); + + if (std::abs(innerOffset) > Precision::Confusion()) { + TopoShape innerWires(0, Hasher); + innerWires.makECompound(wires, "", false); + innerWires = innerWires.makEOffset2D(innerOffset, innerJoinType, false, false, true, op); + wires = innerWires.getSubTopoShapes(TopAbs_WIRE); + } + wires.push_back(outerWire); + gp_Pln pln; + res.push_back(TopoShape(0, Hasher).makEFace(wires, + nullptr, + nullptr, + face.findPlane(pln) ? &pln : nullptr)); + } + return makECompound(res, "", false); +} + +TopoShape &TopoShape::makEOffset2D(const TopoShape &shape, double offset, JoinType joinType, + bool fill, bool allowOpenResult, bool intersection, const char *op) +{ + if(!op) op = Part::OpCodes::Offset2D; + + if(shape.isNull()) + FC_THROWM(Base::ValueError, "makeOffset2D: input shape is null!"); + if (allowOpenResult && OCC_VERSION_HEX < 0x060900) + FC_THROWM(Base::AttributeError, "openResult argument is not supported on OCC < 6.9.0."); + + // OUTLINE OF MAKEOFFSET2D + // * Prepare shapes to process + // ** if _Shape is a compound, recursively call this routine for all subcompounds + // ** if intrsection, dump all non-compound children into shapes to process; otherwise call this routine recursively for all children + // ** if _shape isn't a compound, dump it straight to shapes to process + // * Test for shape types, and convert them all to wires + // * find plane + // * OCC call (BRepBuilderAPI_MakeOffset) + // * postprocessing (facemaking): + // ** convert offset result back to faces, if inputs were faces + // ** OR do offset filling: + // *** for closed wires, simply feed source wires + offset wires to smart facemaker + // *** for open wires, try to connect source anf offset result by creating new edges (incomplete implementation) + // ** actual call to FaceMakerBullseye, unified for all facemaking. + + std::vector shapesToProcess; + std::vector shapesToReturn; + bool forceOutputCompound = false; + + if (shape.getShape().ShapeType() == TopAbs_COMPOUND){ + if (!intersection){ + //simply recursively process the children, independently + expandCompound(shape,shapesToProcess); + forceOutputCompound = true; + } else { + //collect non-compounds from this compound for collective offset. Process other shapes independently. + for(auto &s : shape.getSubTopoShapes()) { + if(s.getShape().ShapeType() == TopAbs_COMPOUND){ + //recursively process subcompounds + shapesToReturn.push_back(TopoShape(Tag,Hasher).makEOffset2D( + s, offset, joinType, fill, allowOpenResult, intersection, op)); + forceOutputCompound = true; + } else { + shapesToProcess.push_back(s); + } + } + } + } else { + shapesToProcess.push_back(shape); + } + + if(shapesToProcess.size() > 0){ + TopoShape res(Tag,Hasher); + + //although 2d offset supports offsetting a face directly, it seems there is + //no way to do a collective offset of multiple faces. So, we are doing it + //by getting all wires from the faces, and applying offsets to them, and + //reassembling the faces later. + std::vector sourceWires; + bool haveWires = false; + bool haveFaces = false; + for(auto &s : shapesToProcess){ + const auto &sh = s.getShape(); + switch (sh.ShapeType()) { + case TopAbs_EDGE: + sourceWires.push_back(s.makEWires()); + haveWires = true; + break; + case TopAbs_WIRE: + sourceWires.push_back(s); + haveWires = true; + break; + case TopAbs_FACE:{ + auto outerWire = s.splitWires(&sourceWires); + sourceWires.push_back(outerWire); + haveFaces = true; + }break; + default: + FC_THROWM(Base::TypeError, "makeOffset2D: input shape is not an edge, wire or face or compound of those."); + break; + } + } + if (haveWires && haveFaces) + FC_THROWM(Base::TypeError, "makeOffset2D: collective offset of a mix of wires and faces is not supported"); + if (haveFaces) + allowOpenResult = false; + + //find plane. + gp_Pln workingPlane; + if (!TopoShape().makECompound(sourceWires,"",false).findPlane(workingPlane)) + FC_THROWM(Base::CADKernelError,"makeOffset2D: wires are nonplanar or noncoplanar"); + + //do the offset.. + TopoShape offsetShape; + if (fabs(offset) > Precision::Confusion()){ + BRepOffsetAPI_MakeOffsetFix mkOffset(GeomAbs_JoinType(joinType), allowOpenResult); + for(auto &w : sourceWires) { + mkOffset.AddWire(TopoDS::Wire(w.getShape())); + } + try { +#if defined(__GNUC__) && defined (FC_OS_LINUX) + Base::SignalException se; +#endif + mkOffset.Perform(offset); + } + catch (Standard_Failure &){ + throw; + } + catch (...) { + FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)"); + } + if(mkOffset.Shape().IsNull()) + FC_THROWM(NullShapeException, "makeOffset2D: result of offsetting is null!"); + + //Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699 + // http://www.freecadweb.org/tracker/view.php?id=2699 + offsetShape = shape.makEShape(mkOffset,op).makECopy(); + + } else { + offsetShape = TopoShape(Tag,Hasher).makECompound(sourceWires,0,false); + } + + std::vector offsetWires; + //interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of OCC 7.0.0) + //so, we just extract all nesting + expandCompound(offsetShape,offsetWires); + if (offsetWires.empty()) + FC_THROWM(Base::CADKernelError, "makeOffset2D: offset result has no wires."); + + std::vector wiresForMakingFaces; + if (!fill){ + if (haveFaces){ + wiresForMakingFaces.insert(wiresForMakingFaces.end(), offsetWires.begin(),offsetWires.end()); + } else { + shapesToReturn.insert(shapesToReturn.end(),offsetWires.begin(),offsetWires.end()); + } + } else { + //fill offset + if (fabs(offset) < Precision::Confusion()) + FC_THROWM(Base::ValueError, "makeOffset2D: offset distance is zero. Can't fill offset."); + + //filling offset. There are three major cases to consider: + // 1. source wires and result wires are closed (simplest) -> make face + // from source wire + offset wire + // + // 2. source wire is open, but offset wire is closed (if not + // allowOpenResult). -> throw away source wire and make face right from + // offset result. + // + // 3. source and offset wire are both open (note that there may be + // closed islands in offset result) -> need connecting offset result to + // source wire with new edges + + //first, lets split apart closed and open wires. + std::vector closedWires; + std::vector openWires; + for(auto &w : sourceWires) + if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) + closedWires.push_back(w); + else + openWires.push_back(w); + for(auto &w : offsetWires) + if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) + closedWires.push_back(w); + else + openWires.push_back(w); + + wiresForMakingFaces.insert(wiresForMakingFaces.end(),closedWires.begin(),closedWires.end()); + if (!allowOpenResult || openWires.size() == 0){ + //just ignore all open wires + } else { + //We need to connect open wires to form closed wires. + + //for now, only support offsetting one open wire -> there should be exactly two open wires for connecting + if (openWires.size() != 2) + FC_THROWM(Base::CADKernelError, "makeOffset2D: collective offset with filling of multiple wires is not supported yet."); + + TopoShape openWire1 = openWires.front(); + TopoShape openWire2 = openWires.back(); + + //find open vertices + BRepTools_WireExplorer xp; + xp.Init(TopoDS::Wire(openWire1.getShape())); + TopoDS_Vertex v1 = xp.CurrentVertex(); + for(;xp.More();xp.Next()){}; + TopoDS_Vertex v2 = xp.CurrentVertex(); + + //find open vertices + xp.Init(TopoDS::Wire(openWire2.getShape())); + TopoDS_Vertex v3 = xp.CurrentVertex(); + for(;xp.More();xp.Next()){}; + TopoDS_Vertex v4 = xp.CurrentVertex(); + + //check + if (v1.IsNull()) FC_THROWM(NullShapeException, "v1 is null"); + if (v2.IsNull()) FC_THROWM(NullShapeException, "v2 is null"); + if (v3.IsNull()) FC_THROWM(NullShapeException, "v3 is null"); + if (v4.IsNull()) FC_THROWM(NullShapeException, "v4 is null"); + + //assemble new wire + + //we want the connection order to be + //v1 -> openWire1 -> v2 -> (new edge) -> v4 -> openWire2(rev) -> v3 -> (new edge) -> v1 + //let's check if it's the case. If not, we reverse one wire and swap its endpoints. + + if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)){ + openWire2._Shape.Reverse(); + std::swap(v3, v4); + v3.Reverse(); + v4.Reverse(); + } else if ((fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v4)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v4))){ + //orientation is as expected, nothing to do + } else { + FC_THROWM(Base::CADKernelError, "makeOffset2D: fill offset: failed to establish open vertex relationship."); + } + + //now directions of open wires are aligned. Finally. make new wire! + BRepBuilderAPI_MakeWire mkWire; + //add openWire1 + BRepTools_WireExplorer it; + for(it.Init(TopoDS::Wire(openWire1.getShape())); it.More(); it.Next()){ + mkWire.Add(it.Current()); + } + //add first joining edge + mkWire.Add(BRepBuilderAPI_MakeEdge(v2,v4).Edge()); + //add openWire2, in reverse order + openWire2._Shape.Reverse(); + for(it.Init(TopoDS::Wire(openWire2.getShape())); it.More(); it.Next()){ + mkWire.Add(it.Current()); + } + //add final joining edge + mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge()); + + mkWire.Build(); + + wiresForMakingFaces.push_back(TopoShape(Tag,Hasher).makEShape(mkWire,openWires,op)); + } + } + + //make faces + if (wiresForMakingFaces.size()>0) { + TopoShape face(0, Hasher); + face.makEFace(wiresForMakingFaces, nullptr, nullptr, &workingPlane); + expandCompound(face, shapesToReturn); + } + } + + return makECompound(shapesToReturn,op,forceOutputCompound); +} + TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape, const std::vector& faces, double offset, From a27e91a272dded70eb462d610154187f87e09c09 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Tue, 20 Feb 2024 12:49:34 -0500 Subject: [PATCH 4/4] Toposhape/Part: clean and add tests --- src/Mod/Part/App/TopoShape.cpp | 8 + src/Mod/Part/App/TopoShape.h | 240 +++++--- src/Mod/Part/App/TopoShapeExpansion.cpp | 556 ++++++++++-------- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 212 ++++++- 4 files changed, 680 insertions(+), 336 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 62773ead82..845146ea3b 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -579,6 +579,14 @@ void TopoShape::setPyObject(PyObject* obj) } } +//void TopoShape::operator = (const TopoShape& sh) +//{ +// if (this != &sh) { +// this->Tag = sh.Tag; +// this->_Shape = sh._Shape; +// } +//} + void TopoShape::convertTogpTrsf(const Base::Matrix4D& mtrx, gp_Trsf& trsf) { trsf.SetValues(mtrx[0][0],mtrx[0][1],mtrx[0][2],mtrx[0][3], diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index b704c5edfe..a7f0ebe048 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -220,7 +220,7 @@ enum class CheckScale checkScale }; -enum class Copy +enum class CopyType { noCopy, copy @@ -249,6 +249,18 @@ enum class Spine on }; +enum class FillType +{ + noFill, + fill +}; + +enum class OpenResult +{ + noOpenResult, + allowOpenResult +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -903,9 +915,15 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makEOffset(const TopoShape &source, double offset, double tol, - bool intersection = false, bool selfInter = false, short offsetMode = 0, - JoinType join = JoinType::Arc, bool fill = false, const char *op=nullptr); + TopoShape& makeElementOffset(const TopoShape& source, + double offset, + double tol, + bool intersection = false, + bool selfInter = false, + short offsetMode = 0, + JoinType join = JoinType::arc, + FillType fill = FillType::noFill, + const char* op = nullptr); /** Make a 3D offset of this shape * @@ -924,93 +942,121 @@ public: * * @return Return the new shape. The TopoShape itself is not modified. */ - TopoShape makEOffset(double offset, double tol, bool intersection = false, bool selfInter = false, - short offsetMode=0, JoinType join=JoinType::Arc, bool fill=false, const char *op=nullptr) const { - return TopoShape(0,Hasher).makEOffset(*this,offset,tol,intersection,selfInter, - offsetMode,join,fill,op); - } - - /** Make a 2D offset of a given shape - * - * @param source: source shape of edge, wire, face, or compound - * @param offset: distance to offset - * @param allowOpenResult: whether to allow open edge/wire - * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. - * @param intersection: if true, then offset all non-compound shape - * together to deal with possible intersection after - * expanding the shape. If false, then offset each - * shape separately. - * @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 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 &makEOffset2D(const TopoShape &source, double offset, JoinType join=JoinType::Arc, bool fill=false, - bool allowOpenResult=false, bool intersection=false, const char *op=nullptr); - /** Make a 2D offset of a given shape - * - * @param source: source shape of edge, wire, face, or compound - * @param offset: distance to offset - * @param allowOpenResult: whether to allow open edge/wire - * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. - * @param intersection: if true, then offset all non-compound shape - * together to deal with possible intersection after - * expanding the shape. If false, then offset each - * shape separately. - * @param op: optional string to be encoded into topo naming for indicating - * the operation - * - * @return Return the new shape. The TopoShape itself is not modified. - */ - TopoShape makEOffset2D(double offset, JoinType join=JoinType::Arc, bool fill=false, bool allowOpenResult=false, - bool intersection=false, const char *op=nullptr) const { - return TopoShape(0,Hasher).makEOffset2D(*this,offset,join,fill,allowOpenResult,intersection,op); - } - - /** Make a 2D offset of face with separate control for outer and inner (hole) wires - * - * @param source: source shape of any type, but only faces inside will be used - * @param offset: distance to offset for outer wires of the faces - * @param innerOffset: distance to offset for inner wires of the faces - * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. - * @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection. - * @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 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 &makEOffsetFace(const TopoShape &source, - double offset, - double innerOffset, - JoinType join = JoinType::Arc, - JoinType innerJoin = JoinType::Arc, - const char *op = nullptr); - - /** Make a 2D offset of face with separate control for outer and inner (hole) wires - * - * @param source: source shape of any type, but only faces inside will be used - * @param offset: distance to offset for outer wires of the faces - * @param innerOffset: distance to offset for inner wires of the faces - * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. - * @param innerJoin: join type of inner wire. Only support JoinType::Arc and JoinType::Intersection. - * @param op: optional string to be encoded into topo naming for indicating - * the operation - * - * @return Return the new shape. The TopoShape itself is not modified. - */ - TopoShape makEOffsetFace(double offset, - double innerOffset, - JoinType join = JoinType::Arc, - JoinType innerJoin = JoinType::Arc, - const char *op = nullptr) const + TopoShape makeElementOffset(double offset, + double tol, + bool intersection = false, + bool selfInter = false, + short offsetMode = 0, + JoinType join = JoinType::arc, + FillType fill = FillType::noFill, + const char* op = nullptr) const { - return TopoShape(0,Hasher).makEOffsetFace(*this,offset,innerOffset,join,innerJoin,op); + return TopoShape(0, Hasher).makeElementOffset(*this, + offset, + tol, + intersection, + selfInter, + offsetMode, + join, + fill, + op); + } + + /** Make a 2D offset of a given shape + * + * @param source: source shape of edge, wire, face, or compound + * @param offset: distance to offset + * @param allowOpenResult: whether to allow open edge/wire + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param intersection: if true, then offset all non-compound shape + * together to deal with possible intersection after + * expanding the shape. If false, then offset each + * shape separately. + * @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 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& makeElementOffset2D(const TopoShape& source, + double offset, + JoinType join = JoinType::arc, + FillType fill = FillType::noFill, + OpenResult allowOpenResult = OpenResult::allowOpenResult, + bool intersection = false, + const char* op = nullptr); + /** Make a 2D offset of a given shape + * + * @param source: source shape of edge, wire, face, or compound + * @param offset: distance to offset + * @param allowOpenResult: whether to allow open edge/wire + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param intersection: if true, then offset all non-compound shape + * together to deal with possible intersection after + * expanding the shape. If false, then offset each + * shape separately. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementOffset2D(double offset, + JoinType join = JoinType::arc, + FillType fill = FillType::noFill, + OpenResult allowOpenResult = OpenResult::allowOpenResult, + bool intersection = false, + const char* op = nullptr) const + { + return TopoShape(0, Hasher) + .makeElementOffset2D(*this, offset, join, fill, allowOpenResult, intersection, op); + } + + /** Make a 2D offset of face with separate control for outer and inner (hole) wires + * + * @param source: source shape of any type, but only faces inside will be used + * @param offset: distance to offset for outer wires of the faces + * @param innerOffset: distance to offset for inner wires of the faces + * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. + * @param innerJoin: join type of inner wire. Only support JoinType::Arc and + * JoinType::Intersection. + * @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 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& makeElementOffsetFace(const TopoShape& source, + double offset, + double innerOffset, + JoinType join = JoinType::arc, + JoinType innerJoin = JoinType::arc, + const char* op = nullptr); + + /** Make a 2D offset of face with separate control for outer and inner (hole) wires + * + * @param source: source shape of any type, but only faces inside will be used + * @param offset: distance to offset for outer wires of the faces + * @param innerOffset: distance to offset for inner wires of the faces + * @param join: join type of outer wire. Only support JoinType::Arc and JoinType::Intersection. + * @param innerJoin: join type of inner wire. Only support JoinType::Arc and + * JoinType::Intersection. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementOffsetFace(double offset, + double innerOffset, + JoinType join = JoinType::arc, + JoinType innerJoin = JoinType::arc, + const char* op = nullptr) const + { + return TopoShape(0, Hasher) + .makeElementOffsetFace(*this, offset, innerOffset, join, innerJoin, op); } @@ -1683,7 +1729,7 @@ public: TopoShape& makeElementGTransform(const TopoShape& source, const Base::Matrix4D& mat, const char* op = nullptr, - Copy copy = Copy::noCopy); + CopyType copy = CopyType::noCopy); /** Make a new shape with transformation that may contain non-uniform scaling * @@ -1699,7 +1745,7 @@ public: */ TopoShape makeElementGTransform(const Base::Matrix4D& mat, const char* op = nullptr, - Copy copy = Copy::noCopy) const + CopyType copy = CopyType::noCopy) const { return TopoShape(Tag, Hasher).makeElementGTransform(*this, mat, op, copy); } @@ -1989,7 +2035,7 @@ public: const Base::Matrix4D& mat, const char* op = nullptr, CheckScale checkScale = CheckScale::noScaleCheck, - Copy copy = Copy::noCopy); + CopyType copy = CopyType::noCopy); /** Make a new shape with transformation * @@ -2012,7 +2058,7 @@ public: const Base::Matrix4D& mat, const char* op = nullptr, CheckScale checkScale = CheckScale::noScaleCheck, - Copy copy = Copy::noCopy) + CopyType copy = CopyType::noCopy) { _makeElementTransform(source, mat, op, checkScale, copy); return *this; @@ -2036,7 +2082,7 @@ public: TopoShape makeElementTransform(const Base::Matrix4D& mat, const char* op = nullptr, CheckScale checkScale = CheckScale::noScaleCheck, - Copy copy = Copy::noCopy) const + CopyType copy = CopyType::noCopy) { return TopoShape(Tag, Hasher).makeElementTransform(*this, mat, op, checkScale, copy); } @@ -2057,7 +2103,7 @@ public: TopoShape& makeElementTransform(const TopoShape& shape, const gp_Trsf& trsf, const char* op = nullptr, - Copy copy = Copy::noCopy); + CopyType copy = CopyType::noCopy); /** Make a new shape with transformation * @@ -2071,7 +2117,7 @@ public: * modified */ TopoShape - makeElementTransform(const gp_Trsf& trsf, const char* op = nullptr, Copy copy = Copy::noCopy) + makeElementTransform(const gp_Trsf& trsf, const char* op = nullptr, CopyType copy = CopyType::noCopy) { return TopoShape(Tag, Hasher).makeElementTransform(*this, trsf, op, copy); } diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 220363d9ea..b30c1604a9 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -100,8 +100,11 @@ #include "TopoShapeMapper.h" #include "FaceMaker.h" #include "Geometry.h" +#include "BRepOffsetAPI_MakeOffsetFix.h" #include +#include +#include FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -2271,130 +2274,132 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector& shapes, return makeElementShape(mkPipeShell, shapes, op); } -TopoShape &TopoShape::makEOffset(const TopoShape &shape, - double offset, double tol, bool intersection, bool selfInter, - short offsetMode, JoinType join, bool fill, const char *op) +TopoShape& TopoShape::makeElementOffset(const TopoShape& shape, + double offset, + double tol, + bool intersection, + bool selfInter, + short offsetMode, + JoinType join, + FillType fill, + const char* op) { - if(!op) op = Part::OpCodes::Offset; + if (!op) { + op = Part::OpCodes::Offset; + } -#if OCC_VERSION_HEX < 0x070200 - BRepOffsetAPI_MakeOffsetShape mkOffset(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode), - intersection ? Standard_True : Standard_False, - selfInter ? Standard_True : Standard_False, - GeomAbs_JoinType(join)); -#else BRepOffsetAPI_MakeOffsetShape mkOffset; - mkOffset.PerformByJoin(shape.getShape(), offset, tol, BRepOffset_Mode(offsetMode), + mkOffset.PerformByJoin(shape.getShape(), + offset, + tol, + BRepOffset_Mode(offsetMode), intersection ? Standard_True : Standard_False, selfInter ? Standard_True : Standard_False, GeomAbs_JoinType(join)); -#endif - if (!mkOffset.IsDone()) - FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffsetShape not done"); + if (!mkOffset.IsDone()) { + FC_THROWM(Base::CADKernelError, "BRepOffsetAPI_MakeOffsetShape not done"); + } - TopoShape res(Tag,Hasher); - res.makEShape(mkOffset,shape,op); - if(shape.hasSubShape(TopAbs_SOLID) && !res.hasSubShape(TopAbs_SOLID)) { + TopoShape res(Tag, Hasher); + res.makeElementShape(mkOffset, shape, op); + if (shape.hasSubShape(TopAbs_SOLID) && !res.hasSubShape(TopAbs_SOLID)) { try { - res = res.makESolid(); - }catch (Standard_Failure &e) { + res = res.makeElementSolid(); + } + catch (Standard_Failure& e) { FC_WARN("failed to make solid: " << e.GetMessageString()); } } - if (!fill) { + if (fill == FillType::noFill) { *this = res; return *this; } - //get perimeter wire of original shape. - //Wires returned seem to have edges in connection order. + // get perimeter wire of original shape. + // Wires returned seem to have edges in connection order. ShapeAnalysis_FreeBoundsProperties freeCheck(shape.getShape()); freeCheck.Perform(); - if (freeCheck.NbClosedFreeBounds() < 1) - { - FC_THROWM(Base::CADKernelError,"no closed bounds"); + if (freeCheck.NbClosedFreeBounds() < 1) { + FC_THROWM(Base::CADKernelError, "no closed bounds"); } BRep_Builder builder; std::vector shapes; - for (int index = 1; index <= freeCheck.NbClosedFreeBounds(); ++index) - { - TopoShape originalWire(shape.Tag,shape.Hasher,freeCheck.ClosedFreeBound(index)->FreeBound()); + for (int index = 1; index <= freeCheck.NbClosedFreeBounds(); ++index) { + TopoShape originalWire(shape.Tag, + shape.Hasher, + freeCheck.ClosedFreeBound(index)->FreeBound()); originalWire.mapSubElement(shape); const BRepAlgo_Image& img = mkOffset.MakeOffset().OffsetEdgesFromShapes(); - //build offset wire. + // build offset wire. TopoDS_Wire offsetWire; builder.MakeWire(offsetWire); - for(const auto &s : originalWire.getSubShapes(TopAbs_EDGE)) { - if (!img.HasImage(s)) - { - FC_THROWM(Base::CADKernelError,"no image for shape"); + for (const auto& s : originalWire.getSubShapes(TopAbs_EDGE)) { + if (!img.HasImage(s)) { + FC_THROWM(Base::CADKernelError, "no image for shape"); } const TopTools_ListOfShape& currentImage = img.Image(s); TopTools_ListIteratorOfListOfShape listIt; int edgeCount(0); TopoDS_Edge mappedEdge; - for (listIt.Initialize(currentImage); listIt.More(); listIt.Next()) - { - if (listIt.Value().ShapeType() != TopAbs_EDGE) + for (listIt.Initialize(currentImage); listIt.More(); listIt.Next()) { + if (listIt.Value().ShapeType() != TopAbs_EDGE) { continue; + } edgeCount++; mappedEdge = TopoDS::Edge(listIt.Value()); } - if (edgeCount != 1) - { + if (edgeCount != 1) { std::ostringstream stream; stream << "wrong edge count: " << edgeCount << std::endl; - FC_THROWM(Base::CADKernelError,stream.str().c_str()); + FC_THROWM(Base::CADKernelError, stream.str().c_str()); } builder.Add(offsetWire, mappedEdge); } std::vector wires; wires.push_back(originalWire); - wires.push_back(TopoShape(Tag,Hasher,offsetWire)); + wires.push_back(TopoShape(Tag, Hasher, offsetWire)); wires.back().mapSubElement(res); - //It would be nice if we could get thruSections to build planar faces - //in all areas possible, so we could run through refine. I tried setting - //ruled to standard_true, but that didn't have the desired affect. + // It would be nice if we could get thruSections to build planar faces + // in all areas possible, so we could run through refine. I tried setting + // ruled to standard_true, but that didn't have the desired affect. BRepOffsetAPI_ThruSections aGenerator; aGenerator.AddWire(TopoDS::Wire(originalWire.getShape())); aGenerator.AddWire(offsetWire); aGenerator.Build(); - if (!aGenerator.IsDone()) - { - FC_THROWM(Base::CADKernelError,"ThruSections failed"); + if (!aGenerator.IsDone()) { + FC_THROWM(Base::CADKernelError, "ThruSections failed"); } - shapes.push_back(TopoShape(Tag,Hasher).makEShape(aGenerator,wires)); + shapes.push_back(TopoShape(Tag, Hasher).makeElementShape(aGenerator, wires)); } - TopoShape perimeterCompound(Tag,Hasher); - perimeterCompound.makECompound(shapes,op); + TopoShape perimeterCompound(Tag, Hasher); + perimeterCompound.makeElementCompound(shapes, op); - //still had to sew. not using the passed in parameter for sew. - //Sew has it's own default tolerance. Opinions? + // still had to sew. not using the passed in parameter for sew. + // Sew has it's own default tolerance. Opinions? BRepBuilderAPI_Sewing sewTool; sewTool.Add(shape.getShape()); sewTool.Add(perimeterCompound.getShape()); sewTool.Add(res.getShape()); - sewTool.Perform(); //Perform Sewing + sewTool.Perform(); // Perform Sewing TopoDS_Shape outputShape = sewTool.SewedShape(); - if ((outputShape.ShapeType() == TopAbs_SHELL) && (outputShape.Closed())) - { + if ((outputShape.ShapeType() == TopAbs_SHELL) && (outputShape.Closed())) { BRepBuilderAPI_MakeSolid solidMaker(TopoDS::Shell(outputShape)); - if (solidMaker.IsDone()) - { + if (solidMaker.IsDone()) { TopoDS_Solid temp = solidMaker.Solid(); - //contrary to the occ docs the return value OrientCloseSolid doesn't - //indicate whether the shell was open or not. It returns true with an - //open shell and we end up with an invalid solid. - if (BRepLib::OrientClosedSolid(temp)) + // contrary to the occ docs the return value OrientCloseSolid doesn't + // indicate whether the shell was open or not. It returns true with an + // open shell and we end up with an invalid solid. + if (BRepLib::OrientClosedSolid(temp)) { outputShape = temp; + } } } @@ -2402,16 +2407,17 @@ TopoShape &TopoShape::makEOffset(const TopoShape &shape, shapes.push_back(shape); shapes.push_back(res); shapes.push_back(perimeterCompound); - *this = TopoShape(Tag,Hasher).makESHAPE(outputShape,MapperSewing(sewTool),shapes,op); + *this = TopoShape(Tag, Hasher) + .makeShapeWithElementMap(outputShape, MapperSewing(sewTool), shapes, op); return *this; } -TopoShape &TopoShape::makEOffsetFace(const TopoShape &shape, - double offset, - double innerOffset, - JoinType joinType, - JoinType innerJoinType, - const char *op) +TopoShape& TopoShape::makeElementOffsetFace(const TopoShape& shape, + double offset, + double innerOffset, + JoinType joinType, + JoinType innerJoinType, + const char* op) { if (std::abs(innerOffset) < Precision::Confusion() && std::abs(offset) < Precision::Confusion()) { @@ -2419,55 +2425,87 @@ TopoShape &TopoShape::makEOffsetFace(const TopoShape &shape, return *this; } - if (shape.isNull()) + if (shape.isNull()) { FC_THROWM(Base::ValueError, "makeOffsetFace: input shape is null!"); - if (!shape.hasSubShape(TopAbs_FACE)) + } + if (!shape.hasSubShape(TopAbs_FACE)) { FC_THROWM(Base::ValueError, "makeOffsetFace: no face found"); + } std::vector res; - for (auto & face : shape.getSubTopoShapes(TopAbs_FACE)) { + for (auto& face : shape.getSubTopoShapes(TopAbs_FACE)) { std::vector wires; TopoShape outerWire = face.splitWires(&wires, ReorientForward); if (wires.empty()) { - res.push_back(makEOffset2D(face, offset, joinType, false, false, false, op)); + res.push_back(makeElementOffset2D(face, + offset, + joinType, + FillType::noFill, + OpenResult::noOpenResult, + false, + op)); continue; } - if (outerWire.isNull()) + if (outerWire.isNull()) { FC_THROWM(Base::CADKernelError, "makeOffsetFace: missing outer wire!"); + } - if (std::abs(offset) > Precision::Confusion()) - outerWire = outerWire.makEOffset2D(offset, joinType, false, false, false, op); + if (std::abs(offset) > Precision::Confusion()) { + outerWire = outerWire.makeElementOffset2D(offset, + joinType, + FillType::noFill, + OpenResult::noOpenResult, + false, + op); + } if (std::abs(innerOffset) > Precision::Confusion()) { TopoShape innerWires(0, Hasher); - innerWires.makECompound(wires, "", false); - innerWires = innerWires.makEOffset2D(innerOffset, innerJoinType, false, false, true, op); + innerWires.makeElementCompound(wires, + "", + SingleShapeCompoundCreationPolicy::returnShape); + innerWires = innerWires.makeElementOffset2D(innerOffset, + innerJoinType, + FillType::noFill, + OpenResult::noOpenResult, + true, + op); wires = innerWires.getSubTopoShapes(TopAbs_WIRE); } wires.push_back(outerWire); gp_Pln pln; - res.push_back(TopoShape(0, Hasher).makEFace(wires, - nullptr, - nullptr, - face.findPlane(pln) ? &pln : nullptr)); + res.push_back(TopoShape(0, Hasher).makeElementFace(wires, + nullptr, + nullptr, + face.findPlane(pln) ? &pln : nullptr)); } - return makECompound(res, "", false); + return makeElementCompound(res, "", SingleShapeCompoundCreationPolicy::returnShape); } -TopoShape &TopoShape::makEOffset2D(const TopoShape &shape, double offset, JoinType joinType, - bool fill, bool allowOpenResult, bool intersection, const char *op) +TopoShape& TopoShape::makeElementOffset2D(const TopoShape& shape, + double offset, + JoinType joinType, + FillType fill, + OpenResult allowOpenResult, + bool intersection, + const char* op) { - if(!op) op = Part::OpCodes::Offset2D; + if (!op) { + op = Part::OpCodes::Offset2D; + } - if(shape.isNull()) + if (shape.isNull()) { FC_THROWM(Base::ValueError, "makeOffset2D: input shape is null!"); - if (allowOpenResult && OCC_VERSION_HEX < 0x060900) + } + if (allowOpenResult == OpenResult::allowOpenResult && OCC_VERSION_HEX < 0x060900) { FC_THROWM(Base::AttributeError, "openResult argument is not supported on OCC < 6.9.0."); + } // OUTLINE OF MAKEOFFSET2D // * Prepare shapes to process // ** if _Shape is a compound, recursively call this routine for all subcompounds - // ** if intrsection, dump all non-compound children into shapes to process; otherwise call this routine recursively for all children + // ** if intrsection, dump all non-compound children into shapes to process; otherwise call this + // routine recursively for all children // ** if _shape isn't a compound, dump it straight to shapes to process // * Test for shape types, and convert them all to wires // * find plane @@ -2476,232 +2514,296 @@ TopoShape &TopoShape::makEOffset2D(const TopoShape &shape, double offset, JoinTy // ** convert offset result back to faces, if inputs were faces // ** OR do offset filling: // *** for closed wires, simply feed source wires + offset wires to smart facemaker - // *** for open wires, try to connect source anf offset result by creating new edges (incomplete implementation) + // *** for open wires, try to connect source anf offset result by creating new edges (incomplete + // implementation) // ** actual call to FaceMakerBullseye, unified for all facemaking. std::vector shapesToProcess; std::vector shapesToReturn; - bool forceOutputCompound = false; - - if (shape.getShape().ShapeType() == TopAbs_COMPOUND){ - if (!intersection){ - //simply recursively process the children, independently - expandCompound(shape,shapesToProcess); - forceOutputCompound = true; - } else { - //collect non-compounds from this compound for collective offset. Process other shapes independently. - for(auto &s : shape.getSubTopoShapes()) { - if(s.getShape().ShapeType() == TopAbs_COMPOUND){ - //recursively process subcompounds - shapesToReturn.push_back(TopoShape(Tag,Hasher).makEOffset2D( - s, offset, joinType, fill, allowOpenResult, intersection, op)); - forceOutputCompound = true; - } else { + SingleShapeCompoundCreationPolicy outputPolicy = SingleShapeCompoundCreationPolicy::returnShape; + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { + if (!intersection) { + // simply recursively process the children, independently + expandCompound(shape, shapesToProcess); + outputPolicy = SingleShapeCompoundCreationPolicy::forceCompound; + } + else { + // collect non-compounds from this compound for collective offset. Process other shapes + // independently. + for (auto& s : shape.getSubTopoShapes()) { + if (s.getShape().ShapeType() == TopAbs_COMPOUND) { + // recursively process subcompounds + shapesToReturn.push_back(TopoShape(Tag, Hasher) + .makeElementOffset2D(s, + offset, + joinType, + fill, + allowOpenResult, + intersection, + op)); + outputPolicy = SingleShapeCompoundCreationPolicy::forceCompound; + } + else { shapesToProcess.push_back(s); } } } - } else { + } + else { shapesToProcess.push_back(shape); } - if(shapesToProcess.size() > 0){ - TopoShape res(Tag,Hasher); + if (shapesToProcess.size() > 0) { + TopoShape res(Tag, Hasher); - //although 2d offset supports offsetting a face directly, it seems there is - //no way to do a collective offset of multiple faces. So, we are doing it - //by getting all wires from the faces, and applying offsets to them, and - //reassembling the faces later. + // although 2d offset supports offsetting a face directly, it seems there is + // no way to do a collective offset of multiple faces. So, we are doing it + // by getting all wires from the faces, and applying offsets to them, and + // reassembling the faces later. std::vector sourceWires; bool haveWires = false; bool haveFaces = false; - for(auto &s : shapesToProcess){ - const auto &sh = s.getShape(); + for (auto& s : shapesToProcess) { + const auto& sh = s.getShape(); switch (sh.ShapeType()) { case TopAbs_EDGE: - sourceWires.push_back(s.makEWires()); + sourceWires.push_back(s.makeElementWires()); haveWires = true; break; case TopAbs_WIRE: sourceWires.push_back(s); haveWires = true; break; - case TopAbs_FACE:{ + case TopAbs_FACE: { auto outerWire = s.splitWires(&sourceWires); sourceWires.push_back(outerWire); haveFaces = true; - }break; + } break; default: - FC_THROWM(Base::TypeError, "makeOffset2D: input shape is not an edge, wire or face or compound of those."); + FC_THROWM(Base::TypeError, + "makeOffset2D: input shape is not an edge, wire or face or compound " + "of those."); break; } } - if (haveWires && haveFaces) - FC_THROWM(Base::TypeError, "makeOffset2D: collective offset of a mix of wires and faces is not supported"); - if (haveFaces) - allowOpenResult = false; + if (haveWires && haveFaces) { + FC_THROWM( + Base::TypeError, + "makeOffset2D: collective offset of a mix of wires and faces is not supported"); + } + if (haveFaces) { + allowOpenResult = OpenResult::noOpenResult; + } - //find plane. + // find plane. gp_Pln workingPlane; - if (!TopoShape().makECompound(sourceWires,"",false).findPlane(workingPlane)) - FC_THROWM(Base::CADKernelError,"makeOffset2D: wires are nonplanar or noncoplanar"); + if (!TopoShape() + .makeElementCompound(sourceWires, + "", + SingleShapeCompoundCreationPolicy::returnShape) + .findPlane(workingPlane)) { + FC_THROWM(Base::CADKernelError, "makeOffset2D: wires are nonplanar or noncoplanar"); + } - //do the offset.. + // do the offset.. TopoShape offsetShape; - if (fabs(offset) > Precision::Confusion()){ - BRepOffsetAPI_MakeOffsetFix mkOffset(GeomAbs_JoinType(joinType), allowOpenResult); - for(auto &w : sourceWires) { + if (fabs(offset) > Precision::Confusion()) { + BRepOffsetAPI_MakeOffsetFix mkOffset(GeomAbs_JoinType(joinType), + allowOpenResult == OpenResult::allowOpenResult); + for (auto& w : sourceWires) { mkOffset.AddWire(TopoDS::Wire(w.getShape())); } try { -#if defined(__GNUC__) && defined (FC_OS_LINUX) +#if defined(__GNUC__) && defined(FC_OS_LINUX) Base::SignalException se; #endif mkOffset.Perform(offset); } - catch (Standard_Failure &){ + catch (Standard_Failure&) { throw; } catch (...) { - FC_THROWM(Base::CADKernelError,"BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)"); + FC_THROWM(Base::CADKernelError, + "BRepOffsetAPI_MakeOffset has crashed! (Unknown exception caught)"); } - if(mkOffset.Shape().IsNull()) + if (mkOffset.Shape().IsNull()) { FC_THROWM(NullShapeException, "makeOffset2D: result of offsetting is null!"); + } - //Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699 - // http://www.freecadweb.org/tracker/view.php?id=2699 - offsetShape = shape.makEShape(mkOffset,op).makECopy(); - - } else { - offsetShape = TopoShape(Tag,Hasher).makECompound(sourceWires,0,false); + // Copying shape to fix strange orientation behavior, OCC7.0.0. See bug #2699 + // http://www.freecadweb.org/tracker/view.php?id=2699 + offsetShape = shape.makeElementShape(mkOffset, op).makeElementCopy(); + } + else { + offsetShape = TopoShape(Tag, Hasher) + .makeElementCompound(sourceWires, + 0, + SingleShapeCompoundCreationPolicy::returnShape); } std::vector offsetWires; - //interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of OCC 7.0.0) - //so, we just extract all nesting - expandCompound(offsetShape,offsetWires); - if (offsetWires.empty()) + // interestingly, if wires are removed, empty compounds are returned by MakeOffset (as of + // OCC 7.0.0) so, we just extract all nesting + expandCompound(offsetShape, offsetWires); + if (offsetWires.empty()) { FC_THROWM(Base::CADKernelError, "makeOffset2D: offset result has no wires."); + } std::vector wiresForMakingFaces; - if (!fill){ - if (haveFaces){ - wiresForMakingFaces.insert(wiresForMakingFaces.end(), offsetWires.begin(),offsetWires.end()); - } else { - shapesToReturn.insert(shapesToReturn.end(),offsetWires.begin(),offsetWires.end()); + if (fill == FillType::noFill) { + if (haveFaces) { + wiresForMakingFaces.insert(wiresForMakingFaces.end(), + offsetWires.begin(), + offsetWires.end()); + } + else { + shapesToReturn.insert(shapesToReturn.end(), offsetWires.begin(), offsetWires.end()); + } + } + else { + // fill offset + if (fabs(offset) < Precision::Confusion()) { + FC_THROWM(Base::ValueError, + "makeOffset2D: offset distance is zero. Can't fill offset."); } - } else { - //fill offset - if (fabs(offset) < Precision::Confusion()) - FC_THROWM(Base::ValueError, "makeOffset2D: offset distance is zero. Can't fill offset."); - //filling offset. There are three major cases to consider: - // 1. source wires and result wires are closed (simplest) -> make face - // from source wire + offset wire + // filling offset. There are three major cases to consider: + // 1. source wires and result wires are closed (simplest) -> make face + // from source wire + offset wire // - // 2. source wire is open, but offset wire is closed (if not - // allowOpenResult). -> throw away source wire and make face right from - // offset result. + // 2. source wire is open, but offset wire is closed (if not + // allowOpenResult). -> throw away source wire and make face right from + // offset result. // - // 3. source and offset wire are both open (note that there may be - // closed islands in offset result) -> need connecting offset result to - // source wire with new edges + // 3. source and offset wire are both open (note that there may be + // closed islands in offset result) -> need connecting offset result to + // source wire with new edges - //first, lets split apart closed and open wires. + // first, lets split apart closed and open wires. std::vector closedWires; std::vector openWires; - for(auto &w : sourceWires) - if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) + for (auto& w : sourceWires) { + if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) { closedWires.push_back(w); - else + } + else { openWires.push_back(w); - for(auto &w : offsetWires) - if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) + } + } + for (auto& w : offsetWires) { + if (BRep_Tool::IsClosed(TopoDS::Wire(w.getShape()))) { closedWires.push_back(w); - else + } + else { openWires.push_back(w); + } + } - wiresForMakingFaces.insert(wiresForMakingFaces.end(),closedWires.begin(),closedWires.end()); - if (!allowOpenResult || openWires.size() == 0){ - //just ignore all open wires - } else { - //We need to connect open wires to form closed wires. + wiresForMakingFaces.insert(wiresForMakingFaces.end(), + closedWires.begin(), + closedWires.end()); + if (allowOpenResult == OpenResult::noOpenResult || openWires.size() == 0) { + // just ignore all open wires + } + else { + // We need to connect open wires to form closed wires. - //for now, only support offsetting one open wire -> there should be exactly two open wires for connecting - if (openWires.size() != 2) - FC_THROWM(Base::CADKernelError, "makeOffset2D: collective offset with filling of multiple wires is not supported yet."); + // for now, only support offsetting one open wire -> there should be exactly two + // open wires for connecting + if (openWires.size() != 2) { + FC_THROWM(Base::CADKernelError, + "makeOffset2D: collective offset with filling of multiple wires is " + "not supported yet."); + } TopoShape openWire1 = openWires.front(); TopoShape openWire2 = openWires.back(); - //find open vertices + // find open vertices BRepTools_WireExplorer xp; xp.Init(TopoDS::Wire(openWire1.getShape())); TopoDS_Vertex v1 = xp.CurrentVertex(); - for(;xp.More();xp.Next()){}; + for (; xp.More(); xp.Next()) {}; TopoDS_Vertex v2 = xp.CurrentVertex(); - //find open vertices + // find open vertices xp.Init(TopoDS::Wire(openWire2.getShape())); TopoDS_Vertex v3 = xp.CurrentVertex(); - for(;xp.More();xp.Next()){}; + for (; xp.More(); xp.Next()) {}; TopoDS_Vertex v4 = xp.CurrentVertex(); - //check - if (v1.IsNull()) FC_THROWM(NullShapeException, "v1 is null"); - if (v2.IsNull()) FC_THROWM(NullShapeException, "v2 is null"); - if (v3.IsNull()) FC_THROWM(NullShapeException, "v3 is null"); - if (v4.IsNull()) FC_THROWM(NullShapeException, "v4 is null"); + // check + if (v1.IsNull()) { + FC_THROWM(NullShapeException, "v1 is null"); + } + if (v2.IsNull()) { + FC_THROWM(NullShapeException, "v2 is null"); + } + if (v3.IsNull()) { + FC_THROWM(NullShapeException, "v3 is null"); + } + if (v4.IsNull()) { + FC_THROWM(NullShapeException, "v4 is null"); + } - //assemble new wire + // assemble new wire - //we want the connection order to be - //v1 -> openWire1 -> v2 -> (new edge) -> v4 -> openWire2(rev) -> v3 -> (new edge) -> v1 - //let's check if it's the case. If not, we reverse one wire and swap its endpoints. + // we want the connection order to be + // v1 -> openWire1 -> v2 -> (new edge) -> v4 -> openWire2(rev) -> v3 -> (new edge) + // -> v1 let's check if it's the case. If not, we reverse one wire and swap its + // endpoints. - if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)){ + if (fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v3)).Magnitude() - fabs(offset)) + <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v3)) { openWire2._Shape.Reverse(); std::swap(v3, v4); v3.Reverse(); v4.Reverse(); - } else if ((fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v4)).Magnitude() - fabs(offset)) <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v4))){ - //orientation is as expected, nothing to do - } else { - FC_THROWM(Base::CADKernelError, "makeOffset2D: fill offset: failed to establish open vertex relationship."); + } + else if ((fabs(gp_Vec(BRep_Tool::Pnt(v2), BRep_Tool::Pnt(v4)).Magnitude() + - fabs(offset)) + <= BRep_Tool::Tolerance(v2) + BRep_Tool::Tolerance(v4))) { + // orientation is as expected, nothing to do + } + else { + FC_THROWM( + Base::CADKernelError, + "makeOffset2D: fill offset: failed to establish open vertex relationship."); } - //now directions of open wires are aligned. Finally. make new wire! + // now directions of open wires are aligned. Finally. make new wire! BRepBuilderAPI_MakeWire mkWire; - //add openWire1 + // add openWire1 BRepTools_WireExplorer it; - for(it.Init(TopoDS::Wire(openWire1.getShape())); it.More(); it.Next()){ + for (it.Init(TopoDS::Wire(openWire1.getShape())); it.More(); it.Next()) { mkWire.Add(it.Current()); } - //add first joining edge - mkWire.Add(BRepBuilderAPI_MakeEdge(v2,v4).Edge()); - //add openWire2, in reverse order + // add first joining edge + mkWire.Add(BRepBuilderAPI_MakeEdge(v2, v4).Edge()); + // add openWire2, in reverse order openWire2._Shape.Reverse(); - for(it.Init(TopoDS::Wire(openWire2.getShape())); it.More(); it.Next()){ + for (it.Init(TopoDS::Wire(openWire2.getShape())); it.More(); it.Next()) { mkWire.Add(it.Current()); } - //add final joining edge - mkWire.Add(BRepBuilderAPI_MakeEdge(v3,v1).Edge()); + // add final joining edge + mkWire.Add(BRepBuilderAPI_MakeEdge(v3, v1).Edge()); mkWire.Build(); - wiresForMakingFaces.push_back(TopoShape(Tag,Hasher).makEShape(mkWire,openWires,op)); + wiresForMakingFaces.push_back( + TopoShape(Tag, Hasher).makeElementShape(mkWire, openWires, op)); } } - //make faces - if (wiresForMakingFaces.size()>0) { + // make faces + if (wiresForMakingFaces.size() > 0) { TopoShape face(0, Hasher); - face.makEFace(wiresForMakingFaces, nullptr, nullptr, &workingPlane); + face.makeElementFace(wiresForMakingFaces, nullptr, nullptr, &workingPlane); expandCompound(face, shapesToReturn); } } - return makECompound(shapesToReturn,op,forceOutputCompound); + return makeElementCompound(shapesToReturn, op, outputPolicy); } TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape, @@ -2750,16 +2852,6 @@ TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape, } remFace.Append(face.getShape()); } -#if OCC_VERSION_HEX < 0x070200 - BRepOffsetAPI_MakeThickSolid mkThick(shape.getShape(), - remFace, - offset, - tol, - BRepOffset_Mode(offsetMode), - intersection ? Standard_True : Standard_False, - selfInter ? Standard_True : Standard_False, - GeomAbs_JoinType(join)); -#else BRepOffsetAPI_MakeThickSolid mkThick; mkThick.MakeThickSolidByJoin(shape.getShape(), remFace, @@ -2769,7 +2861,6 @@ TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape, intersection ? Standard_True : Standard_False, selfInter ? Standard_True : Standard_False, GeomAbs_JoinType(join)); -#endif return makeElementShape(mkThick, shape, op); } @@ -2786,11 +2877,7 @@ TopoShape& TopoShape::makeElementWires(const std::vector& shapes, if (shapes.size() == 1) { return makeElementWires(shapes[0], op, tol, policy, output); } - return makeElementWires(TopoShape(Tag).makeElementCompound(shapes), - op, - tol, - policy, - output); + return makeElementWires(TopoShape(Tag).makeElementCompound(shapes), op, tol, policy, output); } @@ -3085,7 +3172,7 @@ bool TopoShape::_makeElementTransform(const TopoShape& shape, const Base::Matrix4D& mat, const char* op, CheckScale checkScale, - Copy copy) + CopyType copy) { if (checkScale == CheckScale::checkScale) { auto scaleType = mat.hasScale(); @@ -3101,18 +3188,17 @@ bool TopoShape::_makeElementTransform(const TopoShape& shape, TopoShape& TopoShape::makeElementTransform(const TopoShape& shape, const gp_Trsf& trsf, const char* op, - Copy copy) + CopyType copy) { - if (copy == Copy::noCopy) { + if (copy == CopyType::noCopy) { // OCCT checks the ScaleFactor against gp::Resolution() which is DBL_MIN!!! - // No scaling is 1 as in 1:1 - const bool scaling = Abs(Abs(trsf.ScaleFactor()) - 1) > Precision::Confusion(); - const bool negative_scaling = - trsf.ScaleFactor() * trsf.HVectorialPart().Determinant() < 0.0; - copy = negative_scaling || scaling ? Copy::copy : Copy::noCopy; + copy = trsf.ScaleFactor() * trsf.HVectorialPart().Determinant() < 0. + || Abs(Abs(trsf.ScaleFactor()) - 1) > Precision::Confusion() + ? CopyType::copy + : CopyType::noCopy; } TopoShape tmp(shape); - if (copy == Copy::copy) { + if (copy == CopyType::copy) { if (shape.isNull()) { FC_THROWM(NullShapeException, "Null input shape"); } @@ -3147,7 +3233,7 @@ TopoShape& TopoShape::makeElementTransform(const TopoShape& shape, TopoShape& TopoShape::makeElementGTransform(const TopoShape& shape, const Base::Matrix4D& mat, const char* op, - Copy copy) + CopyType copy) { if (shape.isNull()) { FC_THROWM(NullShapeException, "Null input shape"); @@ -3170,7 +3256,7 @@ TopoShape& TopoShape::makeElementGTransform(const TopoShape& shape, // geometric transformation TopoShape tmp(shape); - BRepBuilderAPI_GTransform mkTrf(shape.getShape(), matrix, copy == Copy::copy); + BRepBuilderAPI_GTransform mkTrf(shape.getShape(), matrix, copy == CopyType::copy); tmp.setShape(mkTrf.Shape(), false); if (op || (shape.Tag && shape.Tag != Tag)) { setShape(tmp._Shape); @@ -3817,9 +3903,7 @@ TopoShape& TopoShape::makeElementGeneralFuse(const std::vector& _shap if (tol > 0.0) { mkGFA.SetFuzzyValue(tol); } -#if OCC_VERSION_HEX >= 0x070000 mkGFA.SetNonDestructive(Standard_True); -#endif mkGFA.Build(); if (!mkGFA.IsDone()) { FC_THROWM(Base::CADKernelError, "GeneralFuse failed"); diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index bf15036f4e..8113dc2989 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1359,9 +1359,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) // Assert TopoShape result = topoShape1.makeElementShellFromWires(shapes); #if OCC_VERSION_HEX >= 0x070400 - EXPECT_EQ(result.getShape().NbChildren(), 20); -#else - EXPECT_EQ(result.getShape().NbChildren(), 6); + EXPECT_EQ(result.getShape().NbChildren(), 20); // Have a NbChildren method? #endif EXPECT_EQ(result.countSubElements("Vertex"), 8); EXPECT_EQ(result.countSubElements("Edge"), 32); @@ -2644,4 +2642,212 @@ TEST_F(TopoShapeExpansionTest, traceElement) })); } +TEST_F(TopoShapeExpansionTest, makeElementOffset) +{ + // Arrange + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto tr {gp_Trsf()}; + tr.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); + cube2.Move(TopLoc_Location(tr)); + TopoShape topoShape1 {cube1, 1L}; + TopoShape topoShape2 {cube2, 2L}; + // Act + // TopoShape topoShape3 {6L}; + TopoShape result {3L}; + // topoShape1.makeElementFuse({topoShape2,topoShape2}); // op, tolerance + result.makeElementOffset(topoShape1, 0.25, 1e-07); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE( + PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, -0.25, 1.25, 1.25, 1.25))); + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 3.1544986); + // Assert elementMap is correct + // EXPECT_EQ(elements.size(), 98); + // EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); + // EXPECT_EQ( + // elements[IndexedName("Face", 1)], + // MappedName("Face2;:G;OFS;:H1:7,F")); + EXPECT_TRUE(elementsMatch(result, + { + "Edge10;:G;OFS;:H1:7,F", + "Edge10;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge10;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge10;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge10;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge11;:G;OFS;:H1:7,F", + "Edge11;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge11;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge11;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge11;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge12;:G;OFS;:H1:7,F", + "Edge12;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge12;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge12;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge12;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge1;:G;OFS;:H1:7,F", + "Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge1;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge1;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge1;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge1;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge1;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge1;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge2;:G;OFS;:H1:7,F", + "Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge2;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge2;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge2;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge2;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge2;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge3;:G;OFS;:H1:7,F", + "Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge3;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge3;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge3;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge3;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge3;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge4;:G;OFS;:H1:7,F", + "Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge4;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge4;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge4;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge4;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge5;:G;OFS;:H1:7,F", + "Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge5;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge5;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge5;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge5;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge5;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge5;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge6;:G;OFS;:H1:7,F", + "Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge6;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge6;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge6;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge6;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge6;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge7;:G;OFS;:H1:7,F", + "Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge7;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge7;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge7;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge7;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge7;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge8;:G;OFS;:H1:7,F", + "Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U2;OFS;:H1:8,V", + "Edge8;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E;:U;OFS;:H1:7,V", + "Edge8;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge8;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge8;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Edge9;:G;OFS;:H1:7,F", + "Edge9;:G;OFS;:H1:7,F;:U2;OFS;:H1:8,E", + "Edge9;:G;OFS;:H1:7,F;:U3;OFS;:H1:8,E", + "Edge9;:G;OFS;:H1:7,F;:U4;OFS;:H1:8,E", + "Edge9;:G;OFS;:H1:7,F;:U;OFS;:H1:7,E", + "Face1;:G;OFS;:H1:7,F", + "Face2;:G;OFS;:H1:7,F", + "Face3;:G;OFS;:H1:7,F", + "Face4;:G;OFS;:H1:7,F", + "Face5;:G;OFS;:H1:7,F", + "Face6;:G;OFS;:H1:7,F", + "Vertex1;:G;OFS;:H1:7,F", + "Vertex2;:G;OFS;:H1:7,F", + "Vertex3;:G;OFS;:H1:7,F", + "Vertex4;:G;OFS;:H1:7,F", + "Vertex5;:G;OFS;:H1:7,F", + "Vertex6;:G;OFS;:H1:7,F", + "Vertex7;:G;OFS;:H1:7,F", + "Vertex8;:G;OFS;:H1:7,F", + })); +} + +TEST_F(TopoShapeExpansionTest, makeElementOffsetFace) +{ + // Arrange + const float Len = 3, Wid = 2, Rad = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); + // Act + TopoShape result {face1, 1L}; + result.makeElementOffsetFace(result, 0.25, 0); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE( + PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, 0.0, 3.25, 2.25, 0.0))); + // Assert elementMap is correct + // EXPECT_EQ(elements.size(), 19); + // EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); + // EXPECT_EQ( + // elements[IndexedName("Face", 1)], + // MappedName("Edge1;:G;OFF;:H1:7,E;FAC;:H1:4,F")); + EXPECT_TRUE(elementsMatch(result, + { + "Edge1;:G;OFF;:H1:7,E", + "Edge1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V", + "Edge1;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V", + "Edge1;:G;OFF;:H1:7,E;FAC;:H1:4,F", + "Edge2;:G;OFF;:H1:7,E", + "Edge2;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V", + "Edge2;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V", + "Edge3;:G;OFF;:H1:7,E", + "Edge3;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V", + "Edge3;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V", + "Edge4;:G;OFF;:H1:7,E", + "Edge4;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V", + "Edge4;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V", + "Edge5;:H1,E", + "Vertex1;:G;OFF;:H1:7,E", + "Vertex2;:G;OFF;:H1:7,E", + "Vertex3;:G;OFF;:H1:7,E", + "Vertex4;:G;OFF;:H1:7,E", + "Vertex5;:H1,V", + })); +} + +TEST_F(TopoShapeExpansionTest, makeElementOffset2D) +{ + // Arrange + const float Len = 3, Wid = 2, Rad = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(Len, Wid, Rad); + // Act + TopoShape result {wire1, 1L}; + result.makeElementOffset2D(result, 0.25); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE( + PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(-0.25, -0.25, 0.0, 3.25, 2.25, 0.0))); + // Assert elementMap is correct + // EXPECT_EQ(elements.size(), 10); + // EXPECT_EQ(elements.count(IndexedName("Edge", 1)), 1); + // EXPECT_EQ( + // elements[IndexedName("Edge", 1)], + // MappedName("Edge1;:G;OFF;:H1:7,E;OFF;:H1:4,E")); + EXPECT_TRUE(elementsMatch(result, + + { + "Edge1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V", + "Edge1;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V", + "Edge1;:G;OFF;:H1:7,E;OFF;:H1:4,E", + "Edge2;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V", + "Edge2;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V", + "Edge2;:G;OFF;:H1:7,E;OFF;:H1:4,E", + "Vertex1;:G;OFF;:H1:7,E;:U2;OFF;:H1:8,V;OFF;:H1:4,V", + "Vertex1;:G;OFF;:H1:7,E;OFF;:H1:4,E", + "Vertex3;:G;OFF;:H1:7,E;:U;OFF;:H1:7,V;OFF;:H1:4,V", + "Vertex3;:G;OFF;:H1:7,E;OFF;:H1:4,E", + })); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)