diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index fbb93a68d2a6..50af07b69e17 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -24,35 +24,23 @@ #include "PreCompiled.h" #ifndef _PreComp_ # include -# include -# include # include -# include # include -# include # include # include -# include # include # include -# include # include -# include # include # include -# include -# include # include # include -# include -# include # include # include # include # include # include # include -# include # include #endif @@ -63,6 +51,7 @@ #include "Part2DObject.h" + using namespace Part; @@ -88,6 +77,7 @@ Extrusion::Extrusion() ADD_PROPERTY_TYPE(Symmetric,(false), "Extrude", App::Prop_None, "If true, extrusion is done in both directions to a total of LengthFwd. LengthRev is ignored."); ADD_PROPERTY_TYPE(TaperAngle,(0.0), "Extrude", App::Prop_None, "Sets the angle of slope (draft) to apply to the sides. The angle is for outward taper; negative value yeilds inward tapering."); ADD_PROPERTY_TYPE(TaperAngleRev,(0.0), "Extrude", App::Prop_None, "Taper angle of reverse part of extrusion."); + ADD_PROPERTY_TYPE(FaceMakerClass,("Part::FaceMakerExtrusion"), "Extrude", App::Prop_None, "If Solid is true, this sets the facemaker class to use when converting wires to faces. Otherwise, ignored."); //default for old documents. See setupObject for default for new extrusions. } short Extrusion::mustExecute() const @@ -102,7 +92,8 @@ short Extrusion::mustExecute() const Reversed.isTouched() || Symmetric.isTouched() || TaperAngle.isTouched() || - TaperAngleRev.isTouched()) + TaperAngleRev.isTouched() || + FaceMakerClass.isTouched()) return 1; return 0; } @@ -199,6 +190,8 @@ Extrusion::ExtrusionParameters Extrusion::computeFinalParameters() if (fabs(result.taperAngleRev) > M_PI * 0.5 - Precision::Angular() ) throw Base::ValueError("Magnitude of taper angle matches or exceeds 90 degrees. That is too much."); + result.faceMakerClass = this->FaceMakerClass.getValue(); + return result; } @@ -302,35 +295,20 @@ TopoShape Extrusion::extrudeShape(const TopoShape source, Extrusion::ExtrusionPa } //make faces from wires - if (params.solid && myShape.ShapeType() != TopAbs_FACE) { - std::vector wires; - TopTools_IndexedMapOfShape mapOfWires; - TopExp::MapShapes(myShape, TopAbs_WIRE, mapOfWires); - - // if there are no wires then check also for edges - if (mapOfWires.IsEmpty()) { - TopTools_IndexedMapOfShape mapOfEdges; - TopExp::MapShapes(myShape, TopAbs_EDGE, mapOfEdges); - for (int i=1; i<=mapOfEdges.Extent(); i++) { - BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(mapOfEdges.FindKey(i))); - wires.push_back(mkWire.Wire()); - } - } - else { - wires.reserve(mapOfWires.Extent()); - for (int i=1; i<=mapOfWires.Extent(); i++) { - wires.push_back(TopoDS::Wire(mapOfWires.FindKey(i))); - } - } - - if (!wires.empty()) { - try { - TopoDS_Shape res = makeFace(wires); - if (!res.IsNull()) - myShape = res; - } - catch (...) { - } + if (params.solid) { + if (myShape.ShapeType() == TopAbs_FACE && params.faceMakerClass == "Part::FaceMakerExtrusion"){ + //legacy exclusion: ignore "solid" if extruding a face. + } else { + //new strict behavior. If solid==True => make faces from wires, and if myShape not wires - fail! + std::unique_ptr fm_instance = FaceMaker::ConstructFromType(params.faceMakerClass.c_str()); + FaceMaker* mkFace = &(*(fm_instance)); + + if(myShape.ShapeType() == TopAbs_COMPOUND) + mkFace->useCompound(TopoDS::Compound(myShape)); + else + mkFace->addShape(myShape); + mkFace->Build(); + myShape = mkFace->Shape(); } } @@ -520,195 +498,79 @@ void Extrusion::makeDraft(ExtrusionParameters params, const TopoDS_Shape& shape, } } -TopoDS_Face Extrusion::validateFace(const TopoDS_Face& face) -{ - BRepCheck_Analyzer aChecker(face); - if (!aChecker.IsValid()) { - TopoDS_Wire outerwire = ShapeAnalysis::OuterWire(face); - TopTools_IndexedMapOfShape myMap; - myMap.Add(outerwire); - - TopExp_Explorer xp(face,TopAbs_WIRE); - ShapeFix_Wire fix; - fix.SetFace(face); - fix.Load(outerwire); - fix.Perform(); - BRepBuilderAPI_MakeFace mkFace(fix.WireAPIMake()); - while (xp.More()) { - if (!myMap.Contains(xp.Current())) { - fix.Load(TopoDS::Wire(xp.Current())); - fix.Perform(); - mkFace.Add(fix.WireAPIMake()); - } - xp.Next(); - } +//---------------------------------------------------------------- - aChecker.Init(mkFace.Face()); - if (!aChecker.IsValid()) { - ShapeFix_Shape fix(mkFace.Face()); - fix.SetPrecision(Precision::Confusion()); - fix.SetMaxTolerance(Precision::Confusion()); - fix.SetMaxTolerance(Precision::Confusion()); - fix.Perform(); - fix.FixWireTool()->Perform(); - fix.FixFaceTool()->Perform(); - TopoDS_Face fixedFace = TopoDS::Face(fix.Shape()); - aChecker.Init(fixedFace); - if (!aChecker.IsValid()) - Standard_Failure::Raise("Failed to validate broken face"); - return fixedFace; - } - return mkFace.Face(); - } +TYPESYSTEM_SOURCE(Part::FaceMakerExtrusion, Part::FaceMakerCheese); - return face; +std::string FaceMakerExtrusion::getUserFriendlyName() const +{ + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Part Extrude facemaker")); } -// sort bounding boxes according to diagonal length -class Extrusion::Wire_Compare : public std::binary_function { -public: - bool operator() (const TopoDS_Wire& w1, const TopoDS_Wire& w2) - { - Bnd_Box box1, box2; - if (!w1.IsNull()) { - BRepBndLib::Add(w1, box1); - box1.SetGap(0.0); - } - - if (!w2.IsNull()) { - BRepBndLib::Add(w2, box2); - box2.SetGap(0.0); - } - - return box1.SquareExtent() < box2.SquareExtent(); - } -}; - -bool Extrusion::isInside(const TopoDS_Wire& wire1, const TopoDS_Wire& wire2) +std::string FaceMakerExtrusion::getBriefExplanation() const { - Bnd_Box box1; - BRepBndLib::Add(wire1, box1); - box1.SetGap(0.0); - - Bnd_Box box2; - BRepBndLib::Add(wire2, box2); - box2.SetGap(0.0); - - if (box1.IsOut(box2)) - return false; - - double prec = Precision::Confusion(); - - BRepBuilderAPI_MakeFace mkFace(wire1); - if (!mkFace.IsDone()) - Standard_Failure::Raise("Failed to create a face from wire in sketch"); - TopoDS_Face face = validateFace(mkFace.Face()); - BRepAdaptor_Surface adapt(face); - IntTools_FClass2d class2d(face, prec); - Handle_Geom_Surface surf = new Geom_Plane(adapt.Plane()); - ShapeAnalysis_Surface as(surf); - - TopExp_Explorer xp(wire2,TopAbs_VERTEX); - while (xp.More()) { - TopoDS_Vertex v = TopoDS::Vertex(xp.Current()); - gp_Pnt p = BRep_Tool::Pnt(v); - gp_Pnt2d uv = as.ValueOfUV(p, prec); - if (class2d.Perform(uv) == TopAbs_IN) - return true; - // TODO: We can make a check to see if all points are inside or all outside - // because otherwise we have some intersections which is not allowed - else - return false; - //xp.Next(); - } - - return false; + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Supports making faces with holes, does not support nesting.")); } -TopoDS_Shape Extrusion::makeFace(std::list& wires) +void FaceMakerExtrusion::Build() { - BRepBuilderAPI_MakeFace mkFace(wires.front()); - const TopoDS_Face& face = mkFace.Face(); - if (face.IsNull()) - return face; - gp_Dir axis(0,0,1); - BRepAdaptor_Surface adapt(face); - if (adapt.GetType() == GeomAbs_Plane) { - axis = adapt.Plane().Axis().Direction(); + this->NotDone(); + this->myGenerated.Clear(); + this->myShapesToReturn.clear(); + this->myShape = TopoDS_Shape(); + TopoDS_Shape inputShape; + if (mySourceShapes.empty()) + throw Base::Exception("No input shapes!"); + if (mySourceShapes.size() == 1){ + inputShape = mySourceShapes[0]; + } else { + TopoDS_Builder builder; + TopoDS_Compound cmp; + builder.MakeCompound(cmp); + for (const TopoDS_Shape& sh: mySourceShapes){ + builder.Add(cmp, sh); + } + inputShape = cmp; } - wires.pop_front(); - for (std::list::iterator it = wires.begin(); it != wires.end(); ++it) { - BRepBuilderAPI_MakeFace mkInnerFace(*it); - const TopoDS_Face& inner_face = mkInnerFace.Face(); - if (inner_face.IsNull()) - return inner_face; // failure - gp_Dir inner_axis(0,0,1); - BRepAdaptor_Surface adapt(inner_face); - if (adapt.GetType() == GeomAbs_Plane) { - inner_axis = adapt.Plane().Axis().Direction(); + std::vector wires; + TopTools_IndexedMapOfShape mapOfWires; + TopExp::MapShapes(inputShape, TopAbs_WIRE, mapOfWires); + + // if there are no wires then check also for edges + if (mapOfWires.IsEmpty()) { + TopTools_IndexedMapOfShape mapOfEdges; + TopExp::MapShapes(inputShape, TopAbs_EDGE, mapOfEdges); + for (int i=1; i<=mapOfEdges.Extent(); i++) { + BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(mapOfEdges.FindKey(i))); + wires.push_back(mkWire.Wire()); } - // It seems that orientation is always 'Forward' and we only have to reverse - // if the underlying plane have opposite normals. - if (axis.Dot(inner_axis) < 0) - it->Reverse(); - mkFace.Add(*it); } - return validateFace(mkFace.Face()); -} - -TopoDS_Shape Extrusion::makeFace(const std::vector& w) -{ - if (w.empty()) - return TopoDS_Shape(); - - //FIXME: Need a safe method to sort wire that the outermost one comes last - // Currently it's done with the diagonal lengths of the bounding boxes - std::vector wires = w; - std::sort(wires.begin(), wires.end(), Wire_Compare()); - std::list wire_list; - wire_list.insert(wire_list.begin(), wires.rbegin(), wires.rend()); - - // separate the wires into several independent faces - std::list< std::list > sep_wire_list; - while (!wire_list.empty()) { - std::list sep_list; - TopoDS_Wire wire = wire_list.front(); - wire_list.pop_front(); - sep_list.push_back(wire); - - std::list::iterator it = wire_list.begin(); - while (it != wire_list.end()) { - if (isInside(wire, *it)) { - sep_list.push_back(*it); - it = wire_list.erase(it); - } - else { - ++it; - } + else { + wires.reserve(mapOfWires.Extent()); + for (int i=1; i<=mapOfWires.Extent(); i++) { + wires.push_back(TopoDS::Wire(mapOfWires.FindKey(i))); } - - sep_wire_list.push_back(sep_list); } - if (sep_wire_list.size() == 1) { - std::list& wires = sep_wire_list.front(); - return makeFace(wires); - } - else if (sep_wire_list.size() > 1) { - TopoDS_Compound comp; - BRep_Builder builder; - builder.MakeCompound(comp); - for (std::list< std::list >::iterator it = sep_wire_list.begin(); it != sep_wire_list.end(); ++it) { - TopoDS_Shape aFace = makeFace(*it); - if (!aFace.IsNull()) - builder.Add(comp, aFace); - } + if (!wires.empty()) { + //try { + TopoDS_Shape res = FaceMakerCheese::makeFace(wires); + if (!res.IsNull()) + this->myShape = res; + //} + //catch (...) { - return comp; - } - else { - return TopoDS_Shape(); // error + //} } + + this->Done(); + +} + + +void Part::Extrusion::setupObject() +{ + Part::Feature::setupObject(); + this->FaceMakerClass.setValue("Part::FaceMakerBullseye"); //default for newly created features } diff --git a/src/Mod/Part/App/FeatureExtrusion.h b/src/Mod/Part/App/FeatureExtrusion.h index 7cd4b46d4373..1037d8fc15e8 100644 --- a/src/Mod/Part/App/FeatureExtrusion.h +++ b/src/Mod/Part/App/FeatureExtrusion.h @@ -27,6 +27,7 @@ #include #include #include "PartFeature.h" +#include "FaceMakerCheese.h" #include namespace Part @@ -50,6 +51,7 @@ class PartExport Extrusion : public Part::Feature App::PropertyBool Symmetric; App::PropertyAngle TaperAngle; App::PropertyAngle TaperAngleRev; + App::PropertyString FaceMakerClass; /** @@ -64,6 +66,7 @@ class PartExport Extrusion : public Part::Feature bool solid; double taperAngleFwd; //in radians double taperAngleRev; + std::string faceMakerClass; ExtrusionParameters(): lengthFwd(0), lengthRev(0), solid(false), taperAngleFwd(0), taperAngleRev(0) {}// constructor to keep garbage out }; @@ -117,15 +120,29 @@ class PartExport Extrusion : public Part::Feature }; static const char* eDirModeStrings[]; -private: - static bool isInside(const TopoDS_Wire&, const TopoDS_Wire&); - static TopoDS_Face validateFace(const TopoDS_Face&); - static TopoDS_Shape makeFace(const std::vector&); - static TopoDS_Shape makeFace(std::list&); // for internal use only +protected: static void makeDraft(ExtrusionParameters params, const TopoDS_Shape&, std::list&); -private: - class Wire_Compare; + +protected: + virtual void setupObject() override; +}; + +/** + * @brief FaceMakerExtrusion provides legacy compounding-structure-ignorant behavior of facemaker of Part Extrude. + * Strengths: makes faces with holes + * Weaknesses: can't make islands in holes. Ignores compounding nesting. All faces must be on same plane. + */ +class FaceMakerExtrusion: public FaceMakerCheese +{ + TYPESYSTEM_HEADER(); +public: + virtual std::string getUserFriendlyName() const override; + virtual std::string getBriefExplanation() const override; + + virtual void Build() override; +protected: + virtual void Build_Essence() override {}; }; } //namespace Part