From 74cd977ed9127f1ef60dfc5a26ed0e271d726bde Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 23 Feb 2024 12:13:47 -0500 Subject: [PATCH 1/2] Toponaming/Part: Transfer in makERevolve, makEPrism, makEPrismUntil --- src/Mod/Part/App/TopoShape.h | 160 ++++++++++++ src/Mod/Part/App/TopoShapeExpansion.cpp | 318 ++++++++++++++++++++++++ 2 files changed, 478 insertions(+) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index d0a552127e..26f1245e0c 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -258,6 +258,12 @@ public: void operator=(const TopoShape&); + bool operator == (const TopoShape &other) const { + return _Shape.IsEqual(other._Shape); + } + + virtual bool isSame (const Data::ComplexGeoData &other) const; + /** @name Placement control */ //@{ /// set the transformation of the CasCade Shape @@ -858,6 +864,144 @@ public: offsetMode,join,op); } + + /** Make revolved shell around a basis shape + * + * @param base: the base shape + * @param axis: the revolving axis + * @param d: rotation angle in degree + * @param face_maker: optional type name of the the maker used to make a + * face from basis shape + * @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 &makeElementRevolve(const TopoShape &base, const gp_Ax1& axis, double d, + const char *face_maker=0, const char *op=nullptr); + + /** Make revolved shell around a basis shape + * + * @param base: the basis shape + * @param axis: the revolving axis + * @param d: rotation angle in degree + * @param face_maker: optional type name of the the maker used to make a + * face from basis shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementRevolve(const gp_Ax1& axis, double d, + const char *face_maker=nullptr, const char *op=nullptr) const { + return TopoShape(0,Hasher).makeElementRevolve(*this,axis,d,face_maker,op); + } + + + /** Make a prism that is a linear sweep of a basis shape + * + * @param base: the basis shape + * @param vec: vector defines the sweep direction + * @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 &makeElementPrism(const TopoShape &base, const gp_Vec& vec, const char *op=nullptr); + + /** Make a prism that is a linear sweep of this shape + * + * @param vec: vector defines the sweep direction + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementPrism(const gp_Vec& vec, const char *op=nullptr) const { + return TopoShape(0,Hasher).makeElementPrism(*this,vec,op); + } + + /// Operation mode for makeElementPrismUntil() + enum PrismMode { + /// Remove the generated prism shape from the base shape with boolean cut + CutFromBase = 0, + /// Add generated prism shape to the base shape with fusion + FuseWithBase = 1, + /// Return the generated prism shape without base shape + None = 2 + }; + /** Make a prism that is either depression or protrusion of a profile shape up to a given face + * + * @param base: the base shape + * @param profile: profile shape used for sweeping to make the prism + * @param supportFace: optional face serves to determining the type of + * operation. If it is inside the basis shape, a local + * operation such as glueing can be performed. + * @param upToFace: sweep the profile up until this give face. + * @param direction: the direction to sweep the profile + * @param mode: defines what shape to return. @sa PrismMode + * @param checkLimits: If true, then remove limit (boundary) of up to face. + * If false, then the generate prism may go beyond the + * boundary of the up to face. + * @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 &makeElementPrismUntil(const TopoShape &base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char *op=nullptr); + + /** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face + * + * @param profile: profile shape used for sweeping to make the prism + * @param supportFace: optional face serves to determining the type of + * operation. If it is inside the basis shape, a local + * operation such as glueing can be performed. + * @param upToFace: sweep the profile up until this give face. + * @param direction: the direction to sweep the profile + * @param mode: defines what shape to return. @sa PrismMode + * @param checkLimits: If true, then remove limit (boundary) of up to face. + * If false, then the generate prism may go beyond the + * boundary of the up to face. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementPrismUntil(const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& upToFace, + const gp_Dir& direction, + PrismMode mode, + Standard_Boolean checkLimits = Standard_True, + const char *op=nullptr) const + { + return TopoShape(0,Hasher).makeElementPrismUntil(*this, + profile, + supportFace, + upToFace, + direction, + mode, + checkLimits, + op); + } + + /* Make a shell or solid by sweeping profile wire along a spine * * @params sources: source shapes. The first shape is used as spine. The @@ -1838,6 +1982,22 @@ public: return TopoShape(0, Hasher).makeElementShape(mkShape, *this, op); } + /** Specialized shape making for BRepBuilderAPI_MakePrism with mapped element name + * + * @param mkShape: OCCT shape maker. + * @param sources: list of source shapes. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape built by the shape maker. 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 &makeElementShape(BRepFeat_MakePrism &mkShape, + const std::vector &sources, const TopoShape &uptoface, const char *op); + /* Toponaming migration, February 2014: * Note that the specialized versions of makeElementShape for operations that do not * inherit from BRepBuilderAPI_MakeShape ( like BRepBuilderAPI_Sewing ) have been removed. diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 81dd40f310..ff52f8edbc 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -86,6 +86,10 @@ #include "Geometry.h" #include +#include +#include +#include +#include FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -2701,6 +2705,103 @@ struct MapperThruSections: MapperMaker } }; +struct MapperPrism: MapperMaker { + std::unordered_map vertexMap; + ShapeMapper::ShapeMap edgeMap; + + MapperPrism(BRepFeat_MakePrism &maker, const TopoShape &upTo) + :MapperMaker(maker) + { + (void)upTo; + + std::vector shapes; + for(TopTools_ListIteratorOfListOfShape it(maker.FirstShape());it.More();it.Next()) + shapes.push_back(it.Value()); + + if (shapes.size()) { + // It seems that BRepFeat_MakePrism::newEdges() does not return + // edges generated by extruding the profile vertices. The following + // code assumes BRepFeat_MakePrism::myFShape is the profile, and + // FirstShape() returns the corresponding faces in the new shape, + // i.e. the bottom profile, and add all edges that shares a + // vertex with the profiles as new edges. + + std::unordered_set edgeSet; + TopoShape bottom; + bottom.makeElementCompound(shapes, nullptr, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + TopoShape shape(maker.Shape()); + for (auto &vertex : bottom.getSubShapes(TopAbs_VERTEX)) { + for (auto &e : shape.findAncestorsShapes(vertex, TopAbs_EDGE)) { + // Make sure to not visit the the same edge twice. + // And check only edge that are not found in the bottom profile + if (!edgeSet.insert(e).second && !bottom.findShape(e)) { + auto otherVertex = TopExp::FirstVertex(TopoDS::Edge(e)); + if (otherVertex.IsSame(vertex)) + otherVertex = TopExp::LastVertex(TopoDS::Edge(e)); + vertexMap[vertex] = otherVertex; + } + } + } + + // Now map each edge in the bottom profile to the extrueded top + // profile. vertexMap created above gives us each pair of vertexes + // of the bottom and top profile. We use it to find the + // corresponding edges in the top profile, what an extra criteria + // for disambiguation. That is, the pair of edges (bottom and top) + // must belong to the same face. + for (auto &edge : bottom.getSubShapes(TopAbs_EDGE)) { + std::vector indices; + auto first = TopExp::FirstVertex(TopoDS::Edge(edge)); + auto last = TopExp::LastVertex(TopoDS::Edge(edge)); + auto itFirst = vertexMap.find(first); + auto itLast = vertexMap.find(last); + if (itFirst == vertexMap.end() || itLast ==vertexMap.end()) + continue; + std::vector faces; + for (int idx : shape.findAncestors(edge, TopAbs_FACE)) + faces.push_back(shape.getSubTopoShape(TopAbs_FACE, idx)); + if (faces.empty()) + continue; + for (int idx : shape.findAncestors(itFirst->second, TopAbs_EDGE)) { + auto e = shape.getSubTopoShape(TopAbs_EDGE, idx); + if (!e.findShape(itLast->second)) + continue; + for (auto &face : faces) { + if (!face.findShape(e.getShape())) + continue; + auto &entry = edgeMap[edge]; + if (entry.shapeSet.insert(e.getShape()).second) + entry.shapes.push_back(e.getShape()); + } + } + } + } + } + virtual const std::vector &generated(const TopoDS_Shape &s) const override { + _res.clear(); + switch(s.ShapeType()) { + case TopAbs_VERTEX: { + auto it = vertexMap.find(s); + if (it != vertexMap.end()) { + _res.push_back(it->second); + return _res; + } + break; + } + case TopAbs_EDGE: { + auto it = edgeMap.find(s); + if (it != edgeMap.end()) + return it->second.shapes; + break; + } + default: + break; + } + MapperMaker::generated(s); + return _res; + } +}; + // TODO: This method does not appear to ever be called in the codebase, and it is probably // broken, because using TopoShape() with no parameters means the result will not have an // element Map. @@ -2917,6 +3018,18 @@ TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), shapes, op); } +TopoShape &TopoShape::makeElementShape(BRepFeat_MakePrism &mkShape, + const std::vector &sources, + const TopoShape &upTo, + const char *op) +{ + if(!op) op = Part::OpCodes::Prism; + MapperPrism mapper(mkShape, upTo); + makeShapeWithElementMap(mkShape.Shape(),mapper,sources,op); + return *this; +} + + TopoShape& TopoShape::makeElementLoft(const std::vector& shapes, IsSolid isSolid, IsRuled isRuled, @@ -2984,6 +3097,200 @@ TopoShape& TopoShape::makeElementLoft(const std::vector& shapes, op); } +TopoShape &TopoShape::makeElementPrism(const TopoShape &base, const gp_Vec& vec, const char *op) { + if(!op) op = Part::OpCodes::Extrude; + if(base.isNull()) + FC_THROWM(NullShapeException, "Null shape"); + BRepPrimAPI_MakePrism mkPrism(base.getShape(), vec); + return makeElementShape(mkPrism,base,op); +} + +TopoShape &TopoShape::makeElementPrismUntil(const TopoShape &_base, + const TopoShape& profile, + const TopoShape& supportFace, + const TopoShape& __uptoface, + const gp_Dir& direction, + PrismMode Mode, + Standard_Boolean checkLimits, + const char *op) +{ + if(!op) op = Part::OpCodes::Prism; + + BRepFeat_MakePrism PrismMaker; + + TopoShape _uptoface(__uptoface); + if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE + && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { + // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) + // then the algorithm expects that the 'NaturalRestriction' flag is set in order + // to work as expected. + BRep_Builder builder; + _uptoface = _uptoface.makeElementCopy(); + builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); + } + + TopoShape uptoface(_uptoface); + TopoShape base(_base); + + if (base.isNull()) { + Mode = PrismMode::None; + base = profile; + } + + // Check whether the face has limits or not. Unlimited faces have no wire + // Note: Datum planes are always unlimited + if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { + TopoDS_Face face = TopoDS::Face(uptoface.getShape()); + bool remove_limits = false; + // Remove the limits of the upToFace so that the extrusion works even if profile is larger + // than the upToFace + for (auto &sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { + // Get outermost wire of sketch face + TopoShape outerWire = sketchface.splitWires(); + BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); + if (!proj.More() || !proj.Current().Closed()) { + remove_limits = true; + break; + } + } + + // It must also be checked that all projected inner wires of the upToFace + // lie outside the sketch shape. If this is not the case then the sketch + // shape is not completely covered by the upToFace. See #0003141 + if (!remove_limits) { + std::vector wires; + uptoface.splitWires(&wires); + for (auto & w : wires) { + BRepProj_Projection proj(TopoDS::Wire(w.getShape()), profile.getShape(), -direction); + if (proj.More()) { + remove_limits = true; + break; + } + } + } + + if (remove_limits) { + // Note: Using an unlimited face every time gives unnecessary failures for concave faces + TopLoc_Location loc = face.Location(); + BRepAdaptor_Surface adapt(face, Standard_False); + // use the placement of the adapter, not of the upToFace + loc = TopLoc_Location(adapt.Trsf()); + BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface() +#if OCC_VERSION_HEX >= 0x060502 + , Precision::Confusion() +#endif + ); + if (!mkFace.IsDone()) + remove_limits = false; + else + uptoface.setShape(located(mkFace.Shape(),loc), false); + } + } + + TopoShape uptofaceCopy = uptoface; + bool checkBase = false; + auto retry = [&]() { + if (!uptoface.isSame(_uptoface)) { + // retry using the original up to face in case unnecessary failure + // due to removing the limits + uptoface = _uptoface; + return true; + } + if ((!_base.isNull() && base.isSame(_base)) + || (_base.isNull() && base.isSame(profile))) { + // It is unclear under exactly what condition extrude up to face + // can fail. Either the support face or the up to face must be part + // of the base, or maybe some thing else. + // + // To deal with it, we retry again by disregard the supplied base, + // and use up to face to extrude our own base. Later on, use the + // supplied base (i.e. _base) to calculate the final shape if the + // mode is FuseWithBase or CutWithBase. + checkBase = true; + uptoface = uptofaceCopy; + base.makeElementPrism(_uptoface, direction); + return true; + } + return false; + }; + + std::vector srcShapes; + TopoShape result; + for (;;) { + try { + result = base; + + // We do not rely on BRepFeat_MakePrism to perform fuse or cut for + // us because of its poor support of shape history. + auto mode = PrismMode::None; + + for (auto &face : profile.getSubTopoShapes( + profile.hasSubShape(TopAbs_FACE)?TopAbs_FACE:TopAbs_WIRE)) { + srcShapes.clear(); + if (!profile.isNull() && !result.findShape(profile.getShape())) + srcShapes.push_back(profile); + if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) + srcShapes.push_back(supportFace); + + // DO NOT include uptoface for element mapping. Because OCCT + // BRepFeat_MakePrism will report all top extruded face being + // modified by the uptoface. If there are more than one face in + // the profile, this will cause uncessary duplicated element + // mapped name. And will also disrupte element history tracing + // back to the profile sketch. + // + // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) + // srcShapes.push_back(uptoface); + + srcShapes.push_back(result); + + PrismMaker.Init(result.getShape(), face.getShape(), + TopoDS::Face(supportFace.getShape()), direction, mode, Standard_False); + mode = PrismMode::FuseWithBase; + + PrismMaker.Perform(uptoface.getShape()); + + if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) + FC_THROWM(Base::CADKernelError,"BRepFeat_MakePrism: extrusion failed"); + + result.makeElementShape(PrismMaker, srcShapes, uptoface, op); + } + break; + } catch (Base::Exception &) { + if (!retry()) throw; + } catch (Standard_Failure &) { + if (!retry()) throw; + } + } + + if (!_base.isNull() && Mode != PrismMode::None) { + if (Mode == PrismMode::FuseWithBase) + result.makeElementFuse({_base, result}); + else + result.makeElementCut({_base, result}); + } + + *this = result; + return *this; +} + +TopoShape &TopoShape::makeElementRevolve(const TopoShape &_base, const gp_Ax1& axis, + double d, const char *face_maker, const char *op) +{ + if(!op) op = Part::OpCodes::Revolve; + + TopoShape base(_base); + if(base.isNull()) + FC_THROWM(NullShapeException, "Null shape"); + if(face_maker && !base.hasSubShape(TopAbs_FACE)) { + if(!base.hasSubShape(TopAbs_WIRE)) + base = base.makeElementWires(); + base = base.makeElementFace(nullptr,face_maker, nullptr); + } + BRepPrimAPI_MakeRevol mkRevol(base.getShape(), axis,d); + return makeElementShape(mkRevol,base,op); +} + TopoShape& TopoShape::makeElementDraft(const TopoShape& shape, const std::vector& _faces, const gp_Dir& pullDirection, @@ -3898,4 +4205,15 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker, return *this; } +bool TopoShape::isSame(const Data::ComplexGeoData &_other) const +{ + if(!_other.isDerivedFrom(TopoShape::getClassTypeId())) + return false; + + const auto &other = static_cast(_other); + return Tag == other.Tag + && Hasher == other.Hasher + && _Shape.IsEqual(other._Shape); +} + } // namespace Part From 2840ed04abd40464e806282683b07f415f8f8332 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Fri, 23 Feb 2024 14:51:27 -0500 Subject: [PATCH 2/2] Toponaming/Part: Cleaning and tests for makeElementRevolve, makeElementPrism, makeElementPrismUntil --- src/Mod/Part/App/TopoShape.h | 54 +-- src/Mod/Part/App/TopoShapeExpansion.cpp | 391 ++++++++++-------- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 129 ++++++ 3 files changed, 368 insertions(+), 206 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 26f1245e0c..3c9a54202a 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -957,14 +957,16 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makeElementPrismUntil(const TopoShape &base, - const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr); + // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be + // unused. It is potentially useful if debugged. +// TopoShape &makeElementPrismUntil(const TopoShape &base, +// const TopoShape& profile, +// const TopoShape& supportFace, +// const TopoShape& upToFace, +// const gp_Dir& direction, +// PrismMode mode, +// Standard_Boolean checkLimits = Standard_True, +// const char *op=nullptr); /** Make a prism based on this shape that is either depression or protrusion of a profile shape up to a given face * @@ -983,23 +985,25 @@ public: * * @return Return the generated new shape. The TopoShape itself is not modified. */ - TopoShape makeElementPrismUntil(const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& upToFace, - const gp_Dir& direction, - PrismMode mode, - Standard_Boolean checkLimits = Standard_True, - const char *op=nullptr) const - { - return TopoShape(0,Hasher).makeElementPrismUntil(*this, - profile, - supportFace, - upToFace, - direction, - mode, - checkLimits, - op); - } + // TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be + // unused. It is potentially useful if debugged. +// TopoShape makeElementPrismUntil(const TopoShape& profile, +// const TopoShape& supportFace, +// const TopoShape& upToFace, +// const gp_Dir& direction, +// PrismMode mode, +// Standard_Boolean checkLimits = Standard_True, +// const char *op=nullptr) const +// { +// return TopoShape(0,Hasher).makeElementPrismUntil(*this, +// profile, +// supportFace, +// upToFace, +// direction, +// mode, +// checkLimits, +// op); +// } /* Make a shell or solid by sweeping profile wire along a spine diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index ff52f8edbc..7b5171dd61 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -3097,198 +3097,227 @@ TopoShape& TopoShape::makeElementLoft(const std::vector& shapes, op); } -TopoShape &TopoShape::makeElementPrism(const TopoShape &base, const gp_Vec& vec, const char *op) { - if(!op) op = Part::OpCodes::Extrude; - if(base.isNull()) - FC_THROWM(NullShapeException, "Null shape"); - BRepPrimAPI_MakePrism mkPrism(base.getShape(), vec); - return makeElementShape(mkPrism,base,op); -} - -TopoShape &TopoShape::makeElementPrismUntil(const TopoShape &_base, - const TopoShape& profile, - const TopoShape& supportFace, - const TopoShape& __uptoface, - const gp_Dir& direction, - PrismMode Mode, - Standard_Boolean checkLimits, - const char *op) +TopoShape& TopoShape::makeElementPrism(const TopoShape& base, const gp_Vec& vec, const char* op) { - if(!op) op = Part::OpCodes::Prism; - - BRepFeat_MakePrism PrismMaker; - - TopoShape _uptoface(__uptoface); - if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE - && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { - // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) - // then the algorithm expects that the 'NaturalRestriction' flag is set in order - // to work as expected. - BRep_Builder builder; - _uptoface = _uptoface.makeElementCopy(); - builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); + if (!op) { + op = Part::OpCodes::Extrude; } - - TopoShape uptoface(_uptoface); - TopoShape base(_base); - if (base.isNull()) { - Mode = PrismMode::None; - base = profile; + FC_THROWM(NullShapeException, "Null shape"); } - - // Check whether the face has limits or not. Unlimited faces have no wire - // Note: Datum planes are always unlimited - if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { - TopoDS_Face face = TopoDS::Face(uptoface.getShape()); - bool remove_limits = false; - // Remove the limits of the upToFace so that the extrusion works even if profile is larger - // than the upToFace - for (auto &sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { - // Get outermost wire of sketch face - TopoShape outerWire = sketchface.splitWires(); - BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); - if (!proj.More() || !proj.Current().Closed()) { - remove_limits = true; - break; - } - } - - // It must also be checked that all projected inner wires of the upToFace - // lie outside the sketch shape. If this is not the case then the sketch - // shape is not completely covered by the upToFace. See #0003141 - if (!remove_limits) { - std::vector wires; - uptoface.splitWires(&wires); - for (auto & w : wires) { - BRepProj_Projection proj(TopoDS::Wire(w.getShape()), profile.getShape(), -direction); - if (proj.More()) { - remove_limits = true; - break; - } - } - } - - if (remove_limits) { - // Note: Using an unlimited face every time gives unnecessary failures for concave faces - TopLoc_Location loc = face.Location(); - BRepAdaptor_Surface adapt(face, Standard_False); - // use the placement of the adapter, not of the upToFace - loc = TopLoc_Location(adapt.Trsf()); - BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface() -#if OCC_VERSION_HEX >= 0x060502 - , Precision::Confusion() -#endif - ); - if (!mkFace.IsDone()) - remove_limits = false; - else - uptoface.setShape(located(mkFace.Shape(),loc), false); - } - } - - TopoShape uptofaceCopy = uptoface; - bool checkBase = false; - auto retry = [&]() { - if (!uptoface.isSame(_uptoface)) { - // retry using the original up to face in case unnecessary failure - // due to removing the limits - uptoface = _uptoface; - return true; - } - if ((!_base.isNull() && base.isSame(_base)) - || (_base.isNull() && base.isSame(profile))) { - // It is unclear under exactly what condition extrude up to face - // can fail. Either the support face or the up to face must be part - // of the base, or maybe some thing else. - // - // To deal with it, we retry again by disregard the supplied base, - // and use up to face to extrude our own base. Later on, use the - // supplied base (i.e. _base) to calculate the final shape if the - // mode is FuseWithBase or CutWithBase. - checkBase = true; - uptoface = uptofaceCopy; - base.makeElementPrism(_uptoface, direction); - return true; - } - return false; - }; - - std::vector srcShapes; - TopoShape result; - for (;;) { - try { - result = base; - - // We do not rely on BRepFeat_MakePrism to perform fuse or cut for - // us because of its poor support of shape history. - auto mode = PrismMode::None; - - for (auto &face : profile.getSubTopoShapes( - profile.hasSubShape(TopAbs_FACE)?TopAbs_FACE:TopAbs_WIRE)) { - srcShapes.clear(); - if (!profile.isNull() && !result.findShape(profile.getShape())) - srcShapes.push_back(profile); - if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) - srcShapes.push_back(supportFace); - - // DO NOT include uptoface for element mapping. Because OCCT - // BRepFeat_MakePrism will report all top extruded face being - // modified by the uptoface. If there are more than one face in - // the profile, this will cause uncessary duplicated element - // mapped name. And will also disrupte element history tracing - // back to the profile sketch. - // - // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) - // srcShapes.push_back(uptoface); - - srcShapes.push_back(result); - - PrismMaker.Init(result.getShape(), face.getShape(), - TopoDS::Face(supportFace.getShape()), direction, mode, Standard_False); - mode = PrismMode::FuseWithBase; - - PrismMaker.Perform(uptoface.getShape()); - - if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) - FC_THROWM(Base::CADKernelError,"BRepFeat_MakePrism: extrusion failed"); - - result.makeElementShape(PrismMaker, srcShapes, uptoface, op); - } - break; - } catch (Base::Exception &) { - if (!retry()) throw; - } catch (Standard_Failure &) { - if (!retry()) throw; - } - } - - if (!_base.isNull() && Mode != PrismMode::None) { - if (Mode == PrismMode::FuseWithBase) - result.makeElementFuse({_base, result}); - else - result.makeElementCut({_base, result}); - } - - *this = result; - return *this; + BRepPrimAPI_MakePrism mkPrism(base.getShape(), vec); + return makeElementShape(mkPrism, base, op); } -TopoShape &TopoShape::makeElementRevolve(const TopoShape &_base, const gp_Ax1& axis, - double d, const char *face_maker, const char *op) +// TODO: This code was transferred in Feb 2024 as part of the toponaming project, but appears to be +// unused. It is potentially useful if debugged. +//TopoShape& TopoShape::makeElementPrismUntil(const TopoShape& _base, +// const TopoShape& profile, +// const TopoShape& supportFace, +// const TopoShape& __uptoface, +// const gp_Dir& direction, +// PrismMode Mode, +// Standard_Boolean checkLimits, +// const char* op) +//{ +// if (!op) { +// op = Part::OpCodes::Prism; +// } +// +// BRepFeat_MakePrism PrismMaker; +// +// TopoShape _uptoface(__uptoface); +// if (checkLimits && _uptoface.shapeType(true) == TopAbs_FACE +// && !BRep_Tool::NaturalRestriction(TopoDS::Face(_uptoface.getShape()))) { +// // When using the face with BRepFeat_MakePrism::Perform(const TopoDS_Shape& Until) +// // then the algorithm expects that the 'NaturalRestriction' flag is set in order +// // to work as expected. +// BRep_Builder builder; +// _uptoface = _uptoface.makeElementCopy(); +// builder.NaturalRestriction(TopoDS::Face(_uptoface.getShape()), Standard_True); +// } +// +// TopoShape uptoface(_uptoface); +// TopoShape base(_base); +// +// if (base.isNull()) { +// Mode = PrismMode::None; +// base = profile; +// } +// +// // Check whether the face has limits or not. Unlimited faces have no wire +// // Note: Datum planes are always unlimited +// if (checkLimits && uptoface.hasSubShape(TopAbs_WIRE)) { +// TopoDS_Face face = TopoDS::Face(uptoface.getShape()); +// bool remove_limits = false; +// // Remove the limits of the upToFace so that the extrusion works even if profile is larger +// // than the upToFace +// for (auto& sketchface : profile.getSubTopoShapes(TopAbs_FACE)) { +// // Get outermost wire of sketch face +// TopoShape outerWire = sketchface.splitWires(); +// BRepProj_Projection proj(TopoDS::Wire(outerWire.getShape()), face, direction); +// if (!proj.More() || !proj.Current().Closed()) { +// remove_limits = true; +// break; +// } +// } +// +// // It must also be checked that all projected inner wires of the upToFace +// // lie outside the sketch shape. If this is not the case then the sketch +// // shape is not completely covered by the upToFace. See #0003141 +// if (!remove_limits) { +// std::vector wires; +// uptoface.splitWires(&wires); +// for (auto& w : wires) { +// BRepProj_Projection proj(TopoDS::Wire(w.getShape()), +// profile.getShape(), +// -direction); +// if (proj.More()) { +// remove_limits = true; +// break; +// } +// } +// } +// +// if (remove_limits) { +// // Note: Using an unlimited face every time gives unnecessary failures for concave faces +// TopLoc_Location loc = face.Location(); +// BRepAdaptor_Surface adapt(face, Standard_False); +// // use the placement of the adapter, not of the upToFace +// loc = TopLoc_Location(adapt.Trsf()); +// BRepBuilderAPI_MakeFace mkFace(adapt.Surface().Surface(), Precision::Confusion()); +// if (!mkFace.IsDone()) { +// remove_limits = false; +// } +// else { +// uptoface.setShape(located(mkFace.Shape(), loc), false); +// } +// } +// } +// +// TopoShape uptofaceCopy = uptoface; +// bool checkBase = false; +// auto retry = [&]() { +// if (!uptoface.isSame(_uptoface)) { +// // retry using the original up to face in case unnecessary failure +// // due to removing the limits +// uptoface = _uptoface; +// return true; +// } +// if ((!_base.isNull() && base.isSame(_base)) || (_base.isNull() && base.isSame(profile))) { +// // It is unclear under exactly what condition extrude up to face +// // can fail. Either the support face or the up to face must be part +// // of the base, or maybe some thing else. +// // +// // To deal with it, we retry again by disregard the supplied base, +// // and use up to face to extrude our own base. Later on, use the +// // supplied base (i.e. _base) to calculate the final shape if the +// // mode is FuseWithBase or CutWithBase. +// checkBase = true; +// uptoface = uptofaceCopy; +// base.makeElementPrism(_uptoface, direction); +// return true; +// } +// return false; +// }; +// +// std::vector srcShapes; +// TopoShape result; +// for (;;) { +// try { +// result = base; +// +// // We do not rely on BRepFeat_MakePrism to perform fuse or cut for +// // us because of its poor support of shape history. +// auto mode = PrismMode::None; +// +// for (auto& face : profile.getSubTopoShapes( +// profile.hasSubShape(TopAbs_FACE) ? TopAbs_FACE : TopAbs_WIRE)) { +// srcShapes.clear(); +// if (!profile.isNull() && !result.findShape(profile.getShape())) { +// srcShapes.push_back(profile); +// } +// if (!supportFace.isNull() && !result.findShape(supportFace.getShape())) { +// srcShapes.push_back(supportFace); +// } +// +// // DO NOT include uptoface for element mapping. Because OCCT +// // BRepFeat_MakePrism will report all top extruded face being +// // modified by the uptoface. If there are more than one face in +// // the profile, this will cause uncessary duplicated element +// // mapped name. And will also disrupte element history tracing +// // back to the profile sketch. +// // +// // if (!uptoface.isNull() && !this->findShape(uptoface.getShape())) +// // srcShapes.push_back(uptoface); +// +// srcShapes.push_back(result); +// +// PrismMaker.Init(result.getShape(), +// face.getShape(), +// TopoDS::Face(supportFace.getShape()), +// direction, +// mode, +// Standard_False); +// mode = PrismMode::FuseWithBase; +// +// PrismMaker.Perform(uptoface.getShape()); +// +// if (!PrismMaker.IsDone() || PrismMaker.Shape().IsNull()) { +// FC_THROWM(Base::CADKernelError, "BRepFeat_MakePrism: extrusion failed"); +// } +// +// result.makeElementShape(PrismMaker, srcShapes, uptoface, op); +// } +// break; +// } +// catch (Base::Exception&) { +// if (!retry()) { +// throw; +// } +// } +// catch (Standard_Failure&) { +// if (!retry()) { +// throw; +// } +// } +// } +// +// if (!_base.isNull() && Mode != PrismMode::None) { +// if (Mode == PrismMode::FuseWithBase) { +// result.makeElementFuse({_base, result}); +// } +// else { +// result.makeElementCut({_base, result}); +// } +// } +// +// *this = result; +// return *this; +//} + +TopoShape& TopoShape::makeElementRevolve(const TopoShape& _base, + const gp_Ax1& axis, + double d, + const char* face_maker, + const char* op) { - if(!op) op = Part::OpCodes::Revolve; + if (!op) { + op = Part::OpCodes::Revolve; + } TopoShape base(_base); - if(base.isNull()) + if (base.isNull()) { FC_THROWM(NullShapeException, "Null shape"); - if(face_maker && !base.hasSubShape(TopAbs_FACE)) { - if(!base.hasSubShape(TopAbs_WIRE)) - base = base.makeElementWires(); - base = base.makeElementFace(nullptr,face_maker, nullptr); } - BRepPrimAPI_MakeRevol mkRevol(base.getShape(), axis,d); - return makeElementShape(mkRevol,base,op); + if (face_maker && !base.hasSubShape(TopAbs_FACE)) { + if (!base.hasSubShape(TopAbs_WIRE)) { + base = base.makeElementWires(); + } + base = base.makeElementFace(nullptr, face_maker, nullptr); + } + BRepPrimAPI_MakeRevol mkRevol(base.getShape(), axis, d); + return makeElementShape(mkRevol, base, op); } TopoShape& TopoShape::makeElementDraft(const TopoShape& shape, diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index c57363806d..399312f483 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1966,4 +1966,133 @@ TEST_F(TopoShapeExpansionTest, makeElementSolid) EXPECT_EQ(elements[IndexedName("Face", 1)], MappedName("Face1;SLD;:H1:4,F")); } +TEST_F(TopoShapeExpansionTest, makeElementRevolve) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape topoShape1 {cube1, 1L}; + gp_Ax1 axis {gp_Pnt {0, 0, 0}, gp_Dir {0, 1, 0}}; + double angle = 45; + auto subTopoFaces = topoShape1.getSubTopoShapes(TopAbs_FACE); + subTopoFaces[0].Tag = 2L; + // Act + TopoShape result = subTopoFaces[0].makeElementRevolve(axis, angle); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch( + bb, + Base::BoundBox3d(0.0, 0.0, 0.0, 0.85090352453411933, 1.0, 1.0))); + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.50885141); + // Assert elementMap is correct + EXPECT_TRUE( + elementsMatch(result, + { + "Edge1;:G;RVL;:H2:7,F", + "Edge1;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E", + "Edge1;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E;:L(Edge2;:G;RVL;:H2:7,F;:U;RVL;:H2:" + "7,E|Edge3;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E|Edge4;RVL;:H2:4,E);RVL;:H2:62,F", + "Edge1;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E;:U;RVL;:H2:7,V", + "Edge1;RVL;:H2:4,E", + "Edge2;:G;RVL;:H2:7,F", + "Edge2;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E", + "Edge2;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E;:U;RVL;:H2:7,V", + "Edge2;RVL;:H2:4,E", + "Edge3;:G;RVL;:H2:7,F", + "Edge3;:G;RVL;:H2:7,F;:U;RVL;:H2:7,E", + "Edge3;RVL;:H2:4,E", + "Edge4;RVL;:H2:4,E", + "Face1;RVL;:H2:4,F", + "Vertex1;:G;RVL;:H2:7,E", + "Vertex1;RVL;:H2:4,V", + "Vertex2;RVL;:H2:4,V", + "Vertex3;:G;RVL;:H2:7,E", + "Vertex3;RVL;:H2:4,V", + "Vertex4;RVL;:H2:4,V", + })); +} + +TEST_F(TopoShapeExpansionTest, makeElementPrism) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape topoShape1 {cube1, 1L}; + auto subTopoFaces = topoShape1.getSubTopoShapes(TopAbs_FACE); + subTopoFaces[0].Tag = 2L; + // Act + TopoShape& result = topoShape1.makeElementPrism(subTopoFaces[0], {0.75, 0, 0}); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, 0.0, 0.0, 0.75, 1.0, 1.0))); + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.75); + // Assert elementMap is correct + EXPECT_TRUE(elementsMatch( + result, + { + "Edge1;:G;XTR;:H2:7,F", + "Edge1;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E", + "Edge1;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E;:L(Edge2;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E|Edge3;:G;" + "XTR;:H2:7,F;:U;XTR;:H2:7,E|Edge4;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E);XTR;:H2:74,F", + "Edge1;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E;:U2;XTR;:H2:8,V", + "Edge1;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E;:U;XTR;:H2:7,V", + "Edge1;XTR;:H2:4,E", + "Edge2;:G;XTR;:H2:7,F", + "Edge2;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E", + "Edge2;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E;:U;XTR;:H2:7,V", + "Edge2;XTR;:H2:4,E", + "Edge3;:G;XTR;:H2:7,F", + "Edge3;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E", + "Edge3;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E;:U2;XTR;:H2:8,V", + "Edge3;XTR;:H2:4,E", + "Edge4;:G;XTR;:H2:7,F", + "Edge4;:G;XTR;:H2:7,F;:U;XTR;:H2:7,E", + "Edge4;XTR;:H2:4,E", + "Face1;XTR;:H2:4,F", + "Vertex1;:G;XTR;:H2:7,E", + "Vertex1;XTR;:H2:4,V", + "Vertex2;:G;XTR;:H2:7,E", + "Vertex2;XTR;:H2:4,V", + "Vertex3;:G;XTR;:H2:7,E", + "Vertex3;XTR;:H2:4,V", + "Vertex4;:G;XTR;:H2:7,E", + "Vertex4;XTR;:H2:4,V", + }) + + ); +} + +// TODO: This code was written in Feb 2024 as part of the toponaming project, but appears to be +// unused. It is potentially useful if debugged. +// +// TEST_F(TopoShapeExpansionTest, makeElementPrismUntil) +//{ +// // Arrange +// auto [cube1, cube2] = CreateTwoCubes(); +// TopoShape cube1TS {cube1, 1L}; +// auto subFaces = cube1TS.getSubShapes(TopAbs_FACE); +// auto subTopoFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); +// subTopoFaces[0].Tag = 2L; +// subTopoFaces[1].Tag = 3L; +// auto tr {gp_Trsf()}; +// auto direction = gp_Vec(gp_XYZ(0.0, 0.0, 0.25)); +// tr.SetTranslation(direction); +// auto support = subFaces[0].Moved(TopLoc_Location(tr)); +// auto upto = support.Moved(TopLoc_Location(tr)); +// // Act +// TopoShape result = cube1TS.makeElementPrismUntil(subTopoFaces[0], +// TopoShape(support, 4L), +// TopoShape(upto, 5L), +// direction, +// TopoShape::PrismMode::CutFromBase); +// auto elements = elementMap(result); +// Base::BoundBox3d bb = result.getBoundBox(); +// // Assert shape is correct +// EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, -0.5, 0.0, 1.5, 1.0, 1.0))); +// EXPECT_FLOAT_EQ(getVolume(result.getShape()), 2); +// // Assert elementMap is correct +// EXPECT_TRUE(elementsMatch(result, +// {"Edge1;:G;XTR;:H2:7,F",})); +//} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)