From c3d152540c3fcb05eb93fa9a11db463832633f77 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 23 Feb 2024 12:13:47 -0500 Subject: [PATCH] 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