From 4b26988cb7b4a476f332066b094545b39e13b37f Mon Sep 17 00:00:00 2001 From: DeepSOIC Date: Sun, 25 Sep 2016 18:22:57 +0300 Subject: [PATCH] Part: implement facemakers: FaceMakerCheese, FaceMakerBullseye FaceMakerCheese: based on code extracted from Part FeatureExtrude, exactly the same as facemaking code in PartDesign. FaceMakerBullseye: new facemaker, that supports nesting like hole inside a face inside a hole of another face... --- src/Mod/Part/App/CMakeLists.txt | 4 + src/Mod/Part/App/FaceMakerBullseye.cpp | 201 +++++++++++++++++++ src/Mod/Part/App/FaceMakerBullseye.h | 105 ++++++++++ src/Mod/Part/App/FaceMakerCheese.cpp | 264 +++++++++++++++++++++++++ src/Mod/Part/App/FaceMakerCheese.h | 73 +++++++ 5 files changed, 647 insertions(+) create mode 100644 src/Mod/Part/App/FaceMakerBullseye.cpp create mode 100644 src/Mod/Part/App/FaceMakerBullseye.h create mode 100644 src/Mod/Part/App/FaceMakerCheese.cpp create mode 100644 src/Mod/Part/App/FaceMakerCheese.h diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index ed14f393f1..bee0cdcee0 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -284,6 +284,10 @@ SET(Part_SRCS FT2FC.h FaceMaker.cpp FaceMaker.h + FaceMakerCheese.cpp + FaceMakerCheese.h + FaceMakerBullseye.cpp + FaceMakerBullseye.h ) SET(Part_Scripts diff --git a/src/Mod/Part/App/FaceMakerBullseye.cpp b/src/Mod/Part/App/FaceMakerBullseye.cpp new file mode 100644 index 0000000000..7f21ad77dd --- /dev/null +++ b/src/Mod/Part/App/FaceMakerBullseye.cpp @@ -0,0 +1,201 @@ +/*************************************************************************** + * Copyright (c) 2016 Victor Titov (DeepSOIC) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#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 +#endif + +#include "FaceMakerBullseye.h" +#include "FaceMakerCheese.h" + +#include + +#include + +using namespace Part; + +TYPESYSTEM_SOURCE(Part::FaceMakerBullseye, Part::FaceMakerPublic); + +void FaceMakerBullseye::setPlane(const gp_Pln &plane) +{ + this->myPlane = gp_Pln(plane); + this->planeSupplied = true; +} + +std::string FaceMakerBullseye::getUserFriendlyName() const +{ + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Bull's-eye facemaker")); +} + +std::string FaceMakerBullseye::getBriefExplanation() const +{ + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Supports making planar faces with holes with islands.")); +} + +void FaceMakerBullseye::Build_Essence() +{ + if(myWires.empty()) + return; + + //validity check + for(TopoDS_Wire &w : myWires){ + if (!BRep_Tool::IsClosed(w)) + throw Base::ValueError("Wire is not closed."); + } + + + //find plane (at the same time, test that all wires are on the same plane) + gp_Pln plane; + if(this->planeSupplied){ + plane = this->myPlane; + } else { + TopoDS_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(TopoDS_Wire &w : myWires){ + builder.Add(comp, w); + } + BRepLib_FindSurface planeFinder(comp,-1, /*OnlyPlane=*/Standard_True); + if (!planeFinder.Found()) + throw Base::ValueError("Wires are not coplanar."); + plane = GeomAdaptor_Surface(planeFinder.Surface()).Plane(); + } + + //sort wires by length of diagonal of bounding box. + std::vector wires = this->myWires; + std::stable_sort(wires.begin(), wires.end(), FaceMakerCheese::Wire_Compare()); + + //add wires one by one to current set of faces. + //We go from last to first, to make it so that outer wires come before inner wires. + std::vector< std::unique_ptr > faces; + for( int i = wires.size()-1 ; i >= 0 ; --i){ + TopoDS_Wire &w = wires[i]; + + //test if this wire is on any of existing faces (if yes, it's a hole; + // if no, it's a beginning of a new face). + //Since we are assuming the wires do not intersect, testing if one vertex of wire is in a face is enough. + gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(TopExp_Explorer(w, TopAbs_VERTEX).Current())); + FaceDriller* foundFace = nullptr; + for(std::unique_ptr &ff : faces){ + if(ff->hitTest(p)){ + foundFace = &(*ff); + break; + } + } + + if(foundFace){ + //wire is on a face. + foundFace->addHole(w); + } else { + //wire is not on a face. Start a new face. + faces.push_back(std::unique_ptr( + new FaceDriller(plane, w) + )); + } + } + + //and we are done! + for(std::unique_ptr &ff : faces){ + this->myShapesToReturn.push_back(ff->Face()); + } +} + + +FaceMakerBullseye::FaceDriller::FaceDriller(gp_Pln plane, TopoDS_Wire outerWire) +{ + this->myPlane = plane; + this->myFace = TopoDS_Face(); + + //Ensure correct orientation of the wire. + if (getWireDirection(myPlane, outerWire) < 0) + outerWire.Reverse(); + + myHPlane = new Geom_Plane(this->myPlane); + BRep_Builder builder; + builder.MakeFace(this->myFace, myHPlane, Precision::Confusion()); + builder.Add(this->myFace, outerWire); +} + +bool FaceMakerBullseye::FaceDriller::hitTest(gp_Pnt point) const +{ + double u,v; + GeomAPI_ProjectPointOnSurf(point, myHPlane).LowerDistanceParameters(u,v); + BRepClass_FaceClassifier cl(myFace, gp_Pnt2d(u,v), Precision::Confusion()); + TopAbs_State ret = cl.State(); + switch(ret){ + case TopAbs_UNKNOWN: + throw Base::Exception("FaceMakerBullseye::FaceDriller::hitTest: result unknown."); + break; + default: + return ret == TopAbs_IN || ret == TopAbs_ON; + } + +} + +void FaceMakerBullseye::FaceDriller::addHole(TopoDS_Wire w) +{ + //Ensure correct orientation of the wire. + if (getWireDirection(myPlane, w) > 0) //if wire is CCW.. + w.Reverse(); //.. we want CW! + + BRep_Builder builder; + builder.Add(this->myFace, w); +} + +int FaceMakerBullseye::FaceDriller::getWireDirection(const gp_Pln& plane, const TopoDS_Wire& wire) +{ + //make a test face + BRepBuilderAPI_MakeFace mkFace(wire, /*onlyplane=*/Standard_True); + TopoDS_Face tmpFace = mkFace.Face(); + //compare face surface normal with our plane's one + BRepAdaptor_Surface surf(tmpFace); + bool normal_co = surf.Plane().Axis().Direction().Dot(plane.Axis().Direction()) > 0; + + //unlikely, but just in case OCC decided to reverse our wire for the face... take that into account! + TopoDS_Iterator it(tmpFace, /*CumOri=*/Standard_False); + normal_co ^= it.Value().Orientation() != wire.Orientation(); + + return normal_co ? 1 : -1; +} diff --git a/src/Mod/Part/App/FaceMakerBullseye.h b/src/Mod/Part/App/FaceMakerBullseye.h new file mode 100644 index 0000000000..d2f8226ff2 --- /dev/null +++ b/src/Mod/Part/App/FaceMakerBullseye.h @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (c) 2016 Victor Titov (DeepSOIC) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef PART_FACEMAKER_BULLSEYE_H +#define PART_FACEMAKER_BULLSEYE_H + +#include "FaceMaker.h" +#include + +#include +#include + +namespace Part +{ + + +/** + * @brief The FaceMakerBullseye class is a tool to make planar faces with holes, + * where there can be additional faces inside holes and they can have holes too + * and so on. + * + * Strengths: makes faces with holes with islands + * + * Weaknesses: faces of one compound must be on same plane. TBD + */ +class PartExport FaceMakerBullseye: public FaceMakerPublic +{ + TYPESYSTEM_HEADER(); +public: + FaceMakerBullseye() + :planeSupplied(false){} + /** + * @brief setPlane: sets the plane to use when making faces. This is + * optional. If the plane was set, it is not tested that the wires are + * planar or on the supplied plane, potentially speeding things up. + * @param plane FIXME: the plane is not propagated if processing compounds. + */ + void setPlane(const gp_Pln& plane); + + virtual std::string getUserFriendlyName() const override; + virtual std::string getBriefExplanation() const override; + +protected: + virtual void Build_Essence() override; + +protected: + gp_Pln myPlane; //externally supplied plane (if any) + bool planeSupplied; + + /** + * @brief The FaceDriller class is similar to BRepBuilderAPI_MakeFace, + * except that it is tolerant to wire orientation (wires are oriented as + * needed automatically). + */ + class FaceDriller + { + public: + FaceDriller(gp_Pln plane, TopoDS_Wire outerWire); + + /** + * @brief hitTest: returns True if point is on the face + * @param point + */ + bool hitTest(gp_Pnt point) const; + + void addHole(TopoDS_Wire w); + + TopoDS_Face Face() {return myFace;} + public: + /** + * @brief wireDirection: determines direction of wire with respect to + * myPlane. + * @param w + * @return 1 = CCW (suits as outer wire), -1 = CW (suits as hole) + */ + static int getWireDirection(const gp_Pln &plane, const TopoDS_Wire &w); + private: + gp_Pln myPlane; + TopoDS_Face myFace; + Handle_Geom_Surface myHPlane; + }; +}; + + +}//namespace Part +#endif // PART_FACEMAKER_BULLSEYE_H diff --git a/src/Mod/Part/App/FaceMakerCheese.cpp b/src/Mod/Part/App/FaceMakerCheese.cpp new file mode 100644 index 0000000000..2943f61211 --- /dev/null +++ b/src/Mod/Part/App/FaceMakerCheese.cpp @@ -0,0 +1,264 @@ +/*************************************************************************** + * Copyright (c) 2016 Victor Titov (DeepSOIC) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#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 +#endif + +#include "FaceMakerCheese.h" + +#include + +using namespace Part; + +TYPESYSTEM_SOURCE(Part::FaceMakerCheese, Part::FaceMakerPublic); + + +TopoDS_Face FaceMakerCheese::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(); + } + + return face; +} + +bool FaceMakerCheese::Wire_Compare::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 FaceMakerCheese::isInside(const TopoDS_Wire& wire1, const TopoDS_Wire& wire2) +{ + 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; +} + +TopoDS_Shape FaceMakerCheese::makeFace(std::list& wires) +{ + 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(); + } + + 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(); + } + // 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 FaceMakerCheese::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; + } + } + + 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); + } + + return comp; + } + else { + return TopoDS_Shape(); // error + } +} + + +std::string FaceMakerCheese::getUserFriendlyName() const +{ + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Cheese facemaker")); +} + +std::string FaceMakerCheese::getBriefExplanation() const +{ + return std::string(QT_TRANSLATE_NOOP("Part_FaceMaker","Supports making planar faces with holes, but no islands inside holes.")); +} + +void FaceMakerCheese::Build_Essence() +{ + TopoDS_Shape faces = makeFace(this->myWires); + ShapeExtend_Explorer xp; + Handle_TopTools_HSequenceOfShape seq = xp.SeqFromCompound(faces, Standard_True); + for(int i = 0 ; i < seq->Length() ; i++){ + this->myShapesToReturn.push_back(seq->Value(i+1)); + } +} diff --git a/src/Mod/Part/App/FaceMakerCheese.h b/src/Mod/Part/App/FaceMakerCheese.h new file mode 100644 index 0000000000..0a0cdd342a --- /dev/null +++ b/src/Mod/Part/App/FaceMakerCheese.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2016 Victor Titov (DeepSOIC) * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef PART_FACEMAKER_CHEESE_H +#define PART_FACEMAKER_CHEESE_H + +#include "FaceMaker.h" +#include +#include + +namespace Part +{ + + +/** + * @brief The FaceMakerCheese class is a legacy face maker that was extracted + * from Part Extrude. It is used by almost all PartDesign. + * + * Strengths: makes faces with holes + * + * Weaknesses: can't make islands in holes. All faces must be on same plane. + */ +class PartExport FaceMakerCheese: public FaceMakerPublic +{ + TYPESYSTEM_HEADER(); +public: + virtual std::string getUserFriendlyName() const override; + virtual std::string getBriefExplanation() const override; + +public: //in Extrusion, they used to be private. but they are also used by PartDesign, so made public. + /** + * @brief The Wire_Compare class is for sorting wires by bounding box diagonal length + */ + class Wire_Compare : public std::binary_function + { + public: + bool operator() (const TopoDS_Wire& w1, const TopoDS_Wire& w2); + }; + + static TopoDS_Shape makeFace(const std::vector&); + static TopoDS_Face validateFace(const TopoDS_Face&); + static bool isInside(const TopoDS_Wire&, const TopoDS_Wire&); + +private: + static TopoDS_Shape makeFace(std::list&); // for internal use only + +protected: + virtual void Build_Essence() override; +}; + + +}//namespace Part +#endif // PART_FACEMAKER_CHEESE_H