From e425b5b2db4a5d4de44d698a372b47055bc5b67d Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 17 Jan 2024 20:38:50 -0500 Subject: [PATCH] Toponaming move makEFace as makeElementFace and dependencies --- src/App/ComplexGeoData.h | 4 + src/App/MappedElement.cpp | 109 +++++++++++++ src/App/MappedElement.h | 17 ++ src/Mod/Part/App/FaceMaker.cpp | 108 ++++++++++++- src/Mod/Part/App/FaceMaker.h | 35 ++-- src/Mod/Part/App/FeatureExtrusion.cpp | 6 +- src/Mod/Part/App/TopoShape.h | 80 ++++++++- src/Mod/Part/App/TopoShapeExpansion.cpp | 206 ++++++++++++++++++++++++ 8 files changed, 543 insertions(+), 22 deletions(-) diff --git a/src/App/ComplexGeoData.h b/src/App/ComplexGeoData.h index 7bf68b3bea..d5630c3560 100644 --- a/src/App/ComplexGeoData.h +++ b/src/App/ComplexGeoData.h @@ -247,6 +247,10 @@ public: } // NOTE: getElementHistory is now in ElementMap + long getElementHistory(const MappedName & name, + MappedName *original=nullptr, std::vector *history=nullptr) const { + return _elementMap->getElementHistory(name, Tag, original, history); + }; void setMappedChildElements(const std::vector & children); std::vector getMappedChildElements() const; diff --git a/src/App/MappedElement.cpp b/src/App/MappedElement.cpp index 14eb6f99ba..41a99f2e02 100644 --- a/src/App/MappedElement.cpp +++ b/src/App/MappedElement.cpp @@ -20,5 +20,114 @@ * * **************************************************************************************************/ +// NOLINTNEXTLINE #include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + #include "MappedElement.h" + +using namespace Data; + +bool ElementNameComp::operator()(const MappedName &a, const MappedName &b) const { + size_t size = std::min(a.size(),b.size()); + if(!size) + return a.size()bc) + res = 1; + } + }else if(std::isxdigit(ac)) + return false; + else + break; + } + if(res < 0) + return true; + else if(res > 0) + return false; + + for (; i bc) + return false; + } + return a.size()bc) + return false; + } else if(!std::isdigit(ac)) { + return false; + } else + break; + } + + // Then compare the following digits part by integer value + int res = 0; + for(;ibc) + res = 1; + } + }else if(std::isdigit(ac)) + return false; + else + break; + } + if(res < 0) + return true; + else if(res > 0) + return false; + + // Finally, compare the remaining tail using lexical order + for (; i bc) + return false; + } + return a.size() #include "FaceMaker.h" +#include #include "TopoShape.h" +#include "TopoShapeOpCode.h" TYPESYSTEM_SOURCE_ABSTRACT(Part::FaceMaker, Base::BaseClass) @@ -46,6 +48,11 @@ void Part::FaceMaker::addWire(const TopoDS_Wire& w) void Part::FaceMaker::addShape(const TopoDS_Shape& sh) { + addTopoShape(sh); +} + +void Part::FaceMaker::addTopoShape(const TopoShape& shape) { + const TopoDS_Shape &sh = shape.getShape(); if(sh.IsNull()) throw Base::ValueError("Input shape is null."); switch(sh.ShapeType()){ @@ -58,11 +65,14 @@ void Part::FaceMaker::addShape(const TopoDS_Shape& sh) case TopAbs_EDGE: this->myWires.push_back(BRepBuilderAPI_MakeWire(TopoDS::Edge(sh)).Wire()); break; + case TopAbs_FACE: + this->myInputFaces.push_back(sh); + break; default: throw Base::TypeError("Shape must be a wire, edge or compound. Something else was supplied."); break; } - this->mySourceShapes.push_back(sh); + this->mySourceShapes.push_back(shape); } void Part::FaceMaker::useCompound(const TopoDS_Compound& comp) @@ -73,14 +83,29 @@ void Part::FaceMaker::useCompound(const TopoDS_Compound& comp) } } +void Part::FaceMaker::useTopoCompound(const TopoShape& comp) +{ + for(auto &s : comp.getSubTopoShapes()) + this->addTopoShape(s); +} + const TopoDS_Face& Part::FaceMaker::Face() { - const TopoDS_Shape &sh = this->Shape(); - if(sh.IsNull()) + return TopoDS::Face(TopoFace().getShape()); +} + +const Part::TopoShape &Part::FaceMaker::TopoFace() const{ + if(this->myTopoShape.isNull()) throw NullShapeException("Part::FaceMaker: result shape is null."); - if (sh.ShapeType() != TopAbs_FACE) + if (this->myTopoShape.getShape().ShapeType() != TopAbs_FACE) throw Base::TypeError("Part::FaceMaker: return shape is not a single face."); - return TopoDS::Face(sh); + return this->myTopoShape; +} + +const Part::TopoShape &Part::FaceMaker::getTopoShape() const{ + if(this->myTopoShape.isNull()) + throw NullShapeException("Part::FaceMaker: result shape is null."); + return this->myTopoShape; } #if OCC_VERSION_HEX >= 0x070600 @@ -90,7 +115,7 @@ void Part::FaceMaker::Build() #endif { this->NotDone(); - this->myShapesToReturn.clear(); + this->myShapesToReturn = this->myInputFaces; this->myGenerated.Clear(); this->Build_Essence();//adds stuff to myShapesToReturn @@ -118,6 +143,7 @@ void Part::FaceMaker::Build() if(this->myShapesToReturn.empty()){ //nothing to do, null shape will be returned. + this->myShape = TopoDS_Shape(); } else if (this->myShapesToReturn.size() == 1){ this->myShape = this->myShapesToReturn[0]; } else { @@ -129,6 +155,72 @@ void Part::FaceMaker::Build() } this->myShape = cmp_res; } + + postBuild(); +} + +struct ElementName { + long tag; + Data::MappedName name; + Data::ElementIDRefs sids; + + ElementName(long t, const Data::MappedName &n, const Data::ElementIDRefs &sids) + :tag(t),name(n), sids(sids) + {} + + inline bool operator<(const ElementName &other) const { + if(tagother.tag) + return false; + return Data::ElementNameComp()(name,other.name); + } +}; + +void Part::FaceMaker::postBuild() { + this->myTopoShape.setShape(this->myShape); + this->myTopoShape.Hasher = this->MyHasher; + this->myTopoShape.mapSubElement(this->mySourceShapes); + int i = 0; + const char *op = this->MyOp; + if(!op) + op = Part::OpCodes::Face; + const auto &faces = this->myTopoShape.getSubTopoShapes(TopAbs_FACE); + // name the face using the edges of its outer wire + for(auto &face : faces) { + ++i; + TopoShape wire = face.splitWires(); + wire.mapSubElement(face); + std::set edgeNames; + int count = wire.countSubShapes(TopAbs_EDGE); + for(int i=1;i<=count;++i) { + Data::ElementIDRefs sids; + Data::MappedName name = face.getMappedName( + Data::IndexedName::fromConst("Edge",i), false, &sids); + if(!name) + continue; + edgeNames.emplace(wire.getElementHistory(name),name,sids); + } + if(edgeNames.empty()) + continue; + + std::vector names; + Data::ElementIDRefs sids; +#if 0 + for (auto &e : edgeNames) { + names.insert(e.name); + sids += e.sids; + } +#else + // We just use the first source element name to make the face name more + // stable + names.push_back(edgeNames.begin()->name); + sids = edgeNames.begin()->sids; +#endif + this->myTopoShape.setElementComboName( + Data::IndexedName::fromConst("Face",i),names,op,nullptr,&sids); + } + this->myTopoShape.initCache(true); this->Done(); } @@ -172,12 +264,12 @@ TYPESYSTEM_SOURCE(Part::FaceMakerSimple, Part::FaceMakerPublic) std::string Part::FaceMakerSimple::getUserFriendlyName() const { - return {QT_TRANSLATE_NOOP("Part_FaceMaker","Simple")}; + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Simple")); } std::string Part::FaceMakerSimple::getBriefExplanation() const { - return {QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes.")}; + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Makes separate plane face from every wire independently. No support for holes; wires can be on different planes.")); } void Part::FaceMakerSimple::Build_Essence() diff --git a/src/Mod/Part/App/FaceMaker.h b/src/Mod/Part/App/FaceMaker.h index aaf289c2a0..3f80639165 100644 --- a/src/Mod/Part/App/FaceMaker.h +++ b/src/Mod/Part/App/FaceMaker.h @@ -33,6 +33,8 @@ #include #include +#include +#include "TopoShape.h" namespace Part { @@ -47,11 +49,16 @@ namespace Part */ class PartExport FaceMaker: public BRepBuilderAPI_MakeShape, public Base::BaseClass { - TYPESYSTEM_HEADER_WITH_OVERRIDE(); + TYPESYSTEM_HEADER(); public: - FaceMaker() = default; - ~FaceMaker() override = default; + FaceMaker() {} + virtual ~FaceMaker() {} + + void addTopoShape(const TopoShape &s); + void useTopoCompound(const TopoShape &comp); + const TopoShape &getTopoShape() const; + const TopoShape &TopoFace() const; virtual void addWire(const TopoDS_Wire& w); /** @@ -69,6 +76,8 @@ public: */ virtual void useCompound(const TopoDS_Compound &comp); + virtual void setPlane(const gp_Pln &) {} + /** * @brief Face: returns the face (result). If result is not a single face, * throws Base::TypeError. (hint: use .Shape() instead) @@ -77,9 +86,9 @@ public: virtual const TopoDS_Face& Face(); #if OCC_VERSION_HEX >= 0x070600 - void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) override; + virtual void Build(const Message_ProgressRange& theRange = Message_ProgressRange()); #else - void Build() override; + virtual void Build(); #endif //fails to compile, huh! @@ -90,11 +99,16 @@ public: static std::unique_ptr ConstructFromType(const char* className); static std::unique_ptr ConstructFromType(Base::Type type); + const char *MyOp = 0; + App::StringHasherRef MyHasher; + protected: - std::vector mySourceShapes; //wire or compound + std::vector mySourceShapes; //wire or compound std::vector myWires; //wires from mySourceShapes std::vector myCompounds; //compounds, for recursive processing std::vector myShapesToReturn; + std::vector myInputFaces; + TopoShape myTopoShape; /** * @brief Build_Essence: build routine that can assume there is no nesting. @@ -106,6 +120,7 @@ protected: * whole Build(). */ virtual void Build_Essence() = 0; + void postBuild(); static void throwNotImplemented(); }; @@ -115,7 +130,7 @@ protected: */ class PartExport FaceMakerPublic : public FaceMaker { - TYPESYSTEM_HEADER_WITH_OVERRIDE(); + TYPESYSTEM_HEADER(); public: virtual std::string getUserFriendlyName() const = 0; virtual std::string getBriefExplanation() const = 0; @@ -141,10 +156,10 @@ class PartExport FaceMakerSimple : public FaceMakerPublic { TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - std::string getUserFriendlyName() const override; - std::string getBriefExplanation() const override; + virtual std::string getUserFriendlyName() const override; + virtual std::string getBriefExplanation() const override; protected: - void Build_Essence() override; + virtual void Build_Essence() override; }; diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index c3fb5ba21f..bca055c84a 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -356,14 +356,14 @@ void FaceMakerExtrusion::Build() if (mySourceShapes.empty()) throw Base::ValueError("No input shapes!"); if (mySourceShapes.size() == 1) { - inputShape = mySourceShapes[0]; + inputShape = mySourceShapes[0].getShape(); } else { TopoDS_Builder builder; TopoDS_Compound cmp; builder.MakeCompound(cmp); - for (const TopoDS_Shape& sh : mySourceShapes) { - builder.Add(cmp, sh); + for (const auto &sh : mySourceShapes) { + builder.Add(cmp, sh.getShape()); } inputShape = cmp; } diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index ad4ef2a780..7a5faa1156 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -387,6 +387,27 @@ public: TopoDS_Shape defeaturing(const std::vector& s) const; TopoDS_Shape makeShell(const TopoDS_Shape&) const; //@} + + /// Wire re-orientation when calling splitWires() + enum SplitWireReorient { + /// Keep original reorientation + NoReorient, + /// Make outer wire forward, and inner wires reversed + Reorient, + /// Make both outer and inner wires forward + ReorientForward, + /// Make both outer and inner wires reversed + ReorientReversed, + }; + /** Return the outer and inner wires of a face + * + * @param inner: optional output of inner wires + * @param reorient: wire reorientation, see SplitWireReorient + * + * @return Return the outer wire + */ + TopoShape splitWires(std::vector *inner = nullptr, + SplitWireReorient reorient = Reorient) const; /** @name Element name mapping aware shape maker * @@ -571,6 +592,11 @@ public: const std::string& shapeName(bool silent = false) const; static std::pair shapeTypeAndIndex(const char* name); + Data::MappedName setElementComboName(const Data::IndexedName & element, + const std::vector &names, + const char *marker=nullptr, + const char *op=nullptr, + const Data::ElementIDRefs *sids=nullptr); /** @name sub shape cached functions * @@ -609,7 +635,7 @@ public: void copyElementMap(const TopoShape & topoShape, const char *op=nullptr); bool canMapElement(const TopoShape &other) const; void mapSubElement(const TopoShape &other,const char *op=nullptr, bool forceHasher=false); - void mapSubElement(const std::vector &shapes, const char *op); + void mapSubElement(const std::vector &shapes, const char *op=nullptr); bool hasPendingElementMap() const; @@ -629,6 +655,58 @@ public: */ TopoShape &makeElementCompound(const std::vector &shapes, const char *op=nullptr, bool force=true); + TopoShape &makeElementFace(const std::vector &shapes, + const char *op = nullptr, + const char *maker = nullptr, + const gp_Pln *pln = nullptr); + /** Make a planar face with the input wire or edge + * + * @param shape: input shape. Can be either edge, wire, or compound of + * those two types + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param maker: optional type name of the face maker. If not given, + * default to "Part::FaceMakerBullseye" + * @param pln: optional plane of the face. + * + * @return The function creates a planar face. The original content of this + * TopoShape is discarded and replaced with the new shape. The + * function returns the TopoShape itself as a reference so that + * multiple operations can be carried out for the same shape in the + * same line of code. + */ + TopoShape &makeElementFace(const TopoShape &shape, + const char *op = nullptr, + const char *maker = nullptr, + const gp_Pln *pln = nullptr); + /** Make a planar face using this shape + * + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param maker: optional type name of the face maker. If not given, + * default to "Part::FaceMakerBullseye" + * @param pln: optional plane of the face. + * + * @return The function returns a new planar face made using the wire or edge + * inside this shape. The shape itself is not modified. + */ + TopoShape makeElementFace(const char *op = nullptr, + const char *maker = nullptr, + const gp_Pln *pln = nullptr) const { + return TopoShape(0,Hasher).makeElementFace(*this,op,maker,pln); + } + + /// Filling style when making a BSpline face + enum FillingStyle { + /// The style with the flattest patches + FillingStyle_Strech, + /// A rounded style of patch with less depth than those of Curved + FillingStyle_Coons, + /// The style with the most rounded patches + FillingStyle_Curved, + }; + + friend class TopoShapeCache; private: diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index d5b5b5227b..7bf252fdf0 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -26,11 +26,19 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include +# include #include +#include + +# include +# include + #endif #include "TopoShape.h" #include "TopoShapeCache.h" +#include "FaceMaker.h" + FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -540,4 +548,202 @@ TopoShape::makeElementCompound(const std::vector& shapes, const char* return *this; } + +TopoShape &TopoShape::makeElementFace(const TopoShape &shape, + const char *op, + const char *maker, + const gp_Pln *pln) +{ + std::vector shapes; + if (shape.isNull()) + FC_THROWM(NullShapeException, "Null shape"); + if(shape.getShape().ShapeType() == TopAbs_COMPOUND) + shapes = shape.getSubTopoShapes(); + else + shapes.push_back(shape); + return makeElementFace(shapes,op,maker,pln); +} + +TopoShape &TopoShape::makeElementFace(const std::vector &shapes, + const char *op, + const char *maker, + const gp_Pln *pln) +{ + if(!maker || !maker[0]) maker = "Part::FaceMakerBullseye"; + std::unique_ptr mkFace = FaceMaker::ConstructFromType(maker); + mkFace->MyHasher = Hasher; + mkFace->MyOp = op; + if (pln) + mkFace->setPlane(*pln); + + for(auto &s : shapes) { + if (s.getShape().ShapeType() == TopAbs_COMPOUND) + mkFace->useTopoCompound(s); + else + mkFace->addTopoShape(s); + } + mkFace->Build(); + + const auto &ret = mkFace->getTopoShape(); + setShape(ret._Shape); + Hasher = ret.Hasher; + resetElementMap(ret.elementMap()); + if (!isValid()) { + ShapeFix_ShapeTolerance aSFT; + aSFT.LimitTolerance(getShape(), + Precision::Confusion(), Precision::Confusion(), TopAbs_SHAPE); + + // In some cases, the OCC reports the returned shape having invalid + // tolerance. Not sure about the real cause. + // + // Update: one of the cause is related to OCC bug in + // BRepBuilder_FindPlane, A possible call sequence is, + // + // makEOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface + // + // See code comments in findPlane() for the description of the bug and + // work around. + + ShapeFix_Shape fixer(getShape()); + fixer.Perform(); + setShape(fixer.Shape(), false); + + if (!isValid()) + FC_WARN("makeElementFace: resulting face is invalid"); + } + return *this; +} + +Data::MappedName TopoShape::setElementComboName(const Data::IndexedName & element, + const std::vector &names, + const char *marker, + const char *op, + const Data::ElementIDRefs *_sids) +{ + if(names.empty()) + return Data::MappedName(); + std::string _marker; + if(!marker) + marker = elementMapPrefix().c_str(); + else if(!boost::starts_with(marker,elementMapPrefix())){ + _marker = elementMapPrefix() + marker; + marker = _marker.c_str(); + } + auto it = names.begin(); + Data::MappedName newName = *it; + std::ostringstream ss; + Data::ElementIDRefs sids; + if (_sids) + sids = *_sids; + if(names.size() == 1) + ss << marker; + else { + bool first = true; + ss.str(""); + if(!Hasher) + ss << marker; + ss << '('; + for(++it;it!=names.end();++it) { + if(first) + first = false; + else + ss << '|'; + ss << *it; + } + ss << ')'; + if(Hasher) { + sids.push_back(Hasher->getID(ss.str().c_str())); + ss.str(""); + ss << marker << sids.back().toString(); + } + } + elementMap()->encodeElementName(element[0],newName,ss,&sids,Tag,op); + return elementMap()->setElementName(element,newName, Tag, &sids); +} + +TopoShape TopoShape::splitWires(std::vector *inner, + SplitWireReorient reorient) const +{ + // ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source + // code shows it works by creating face using each wire, and then test using + // BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an out + // bound wire. And practice shows it sometimes returns the incorrect + // result. Need more investigation. Note that this may be related to + // unreliable solid face orientation + // (https://forum.freecadweb.org/viewtopic.php?p=446006#p445674) + // + // Use BrepTools::OuterWire() instead. OCC source code shows it is + // implemented using simple bound box checking. This should be a + // reliable method, especially so for a planar face. + + TopoDS_Shape tmp; + if (shapeType(true) == TopAbs_FACE) + tmp = BRepTools::OuterWire(TopoDS::Face(_Shape)); + else if (countSubShapes(TopAbs_FACE) == 1) + tmp = BRepTools::OuterWire( + TopoDS::Face(getSubShape(TopAbs_FACE, 1))); + if (tmp.IsNull()) + return TopoShape(); + const auto & wires = getSubTopoShapes(TopAbs_WIRE); + auto it = wires.begin(); + + TopAbs_Orientation orientOuter, orientInner; + switch(reorient) { + case ReorientReversed: + orientOuter = orientInner = TopAbs_REVERSED; + break; + case ReorientForward: + orientOuter = orientInner = TopAbs_FORWARD; + break; + default: + orientOuter = TopAbs_FORWARD; + orientInner = TopAbs_REVERSED; + break; + } + + auto doReorient = [](TopoShape &s, TopAbs_Orientation orient) { + // Special case of single edge wire. Make sure the edge is in the + // required orientation. This is necessary because BRepFill_OffsetWire + // has special handling of circular edge offset, which seem to only + // respect the edge orientation and disregard the wire orientation. The + // orientation is used to determine whether to shrink or expand. + if (s.countSubShapes(TopAbs_EDGE) == 1) { + TopoDS_Shape e = s.getSubShape(TopAbs_EDGE, 1); + if (e.Orientation() == orient) { + if (s._Shape.Orientation() == orient) + return; + } else + e = e.Oriented(orient); + BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(e)); + s.setShape(mkWire.Shape(), false); + } + else if (s._Shape.Orientation() != orient) + s.setShape(s._Shape.Oriented(orient), false); + }; + + for (; it != wires.end(); ++it) { + auto & wire = *it; + if (wire.getShape().IsSame(tmp)) { + if (inner) { + for (++it; it != wires.end(); ++it) { + inner->push_back(*it); + if (reorient) + doReorient(inner->back(), orientInner); + } + } + auto res = wire; + if (reorient) + doReorient(res, orientOuter); + return res; + } + if (inner) { + inner->push_back(wire); + if (reorient) + doReorient(inner->back(), orientInner); + } + } + return TopoShape(); +} + + } // namespace Part