diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 51d2bbd924..e1f331638e 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -140,6 +140,7 @@ Area::Area(const AreaParams *params) ,myHaveFace(false) ,myHaveSolid(false) ,myShapeDone(false) +,myProjecting(false) { if(params) setParams(*params); @@ -426,6 +427,16 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { myParams.Coplanar==CoplanarNone?NULL:&plane, myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, myParams.OpenMode==OpenModeEdges,myParams.Reorient); + + if(myProjecting) { + // when projecting, we force all wires to be CCW in order to remove + // inner holes + for(auto &c : area.m_curves) { + if(c.IsClosed() && c.IsClockwise()) + c.Reverse(); + } + } + if(areaOpen.m_curves.size()) { if(&area == myArea.get() || myParams.OpenMode == OpenModeNone) myAreaOpen->m_curves.splice(myAreaOpen->m_curves.end(),areaOpen.m_curves); @@ -530,9 +541,8 @@ struct WireJoiner { BRep_Builder builder; TopoDS_Compound comp; - gp_Dir dir; - WireJoiner(const gp_Dir &dir = gp_Dir()):dir(dir) { + WireJoiner() { builder.MakeCompound(comp); } @@ -552,19 +562,17 @@ struct WireJoiner { } void add(const TopoDS_Edge &e, bool bbox=false) { - if(BRep_Tool::IsClosed(e)){ - BRepBuilderAPI_MakeWire mkWire; - mkWire.Add(e); - TopoDS_Wire wire = mkWire.Wire(); - if(bbox) - Area::setWireOrientation(wire,dir,true); - builder.Add(comp,wire); - return; - } + // if(BRep_Tool::IsClosed(e)){ + // BRepBuilderAPI_MakeWire mkWire; + // mkWire.Add(e); + // TopoDS_Wire wire = mkWire.Wire(); + // builder.Add(comp,wire); + // return; + // } gp_Pnt p1,p2; getEndPoints(e,p1,p2); - if(p1.SquareDistance(p2) < Precision::SquareConfusion()) - return; + // if(p1.SquareDistance(p2) < Precision::SquareConfusion()) + // return; edges.emplace_front(e,p1,p2,bbox); add(edges.begin()); } @@ -721,8 +729,14 @@ struct WireJoiner { // in more than one closed wires if it connects to more than one edges. int findClosedWires() { std::map edgesToVisit; - for(const auto &info : edges) - edgesToVisit[info.edge.HashCode(INT_MAX)] = info.edge; + int count = 0; + for(const auto &info : edges) { + if(BRep_Tool::IsClosed(info.edge)){ + builder.Add(comp,BRepBuilderAPI_MakeWire(info.edge).Wire()); + ++count; + }else + edgesToVisit[info.edge.HashCode(INT_MAX)] = info.edge; + } std::deque stack; int skips = 0; @@ -796,11 +810,12 @@ struct WireJoiner { mkWire.Add(TopoDS::Edge(info.edge.Reversed())); } TopoDS_Wire wire = mkWire.Wire(); - Area::setWireOrientation(wire,dir,true); builder.Add(comp,wire); + ++count; break; } } + AREA_TRACE("found " << count << " closed wires, skipped " << skips); return skips; } }; @@ -833,8 +848,10 @@ void Area::explode(const TopoDS_Shape &shape) { } } +// enable this to show intermediate shapes during projection in FC, for +// debugging. #if 0 -static void show(const TopoDS_Shape &shape, const char *name) { +static inline void showShape(const TopoDS_Shape &shape, const char *name) { App::Document *pcDoc = App::GetApplication().getActiveDocument(); if (!pcDoc) pcDoc = App::GetApplication().newDocument(); @@ -844,12 +861,21 @@ static void show(const TopoDS_Shape &shape, const char *name) { pcFeature->Shape.setValue(shape); //pcDoc->recompute(); } +#else +#define showShape(s,name) do{(void)s;}while(0) #endif template static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) { bool haveShape = false; switch(type) { + case TopAbs_SOLID: + for(TopExp_Explorer it(shape,TopAbs_SOLID); it.More(); it.Next()) { + haveShape = true; + func(it.Current(),TopAbs_SOLID); + } + if(haveShape) return TopAbs_SOLID; + //fall through case TopAbs_FACE: for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) { haveShape = true; @@ -926,29 +952,32 @@ TopoDS_Shape Area::findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf) return plane; } -std::list Area::project(const TopoDS_Shape &solid) +int Area::project(TopoDS_Shape &shape_out, + const TopoDS_Shape &shape_in, const AreaParams *params) { TIME_INIT2(t,t1); Handle_HLRBRep_Algo brep_hlr = NULL; gp_Dir dir(0,0,1); try { brep_hlr = new HLRBRep_Algo(); - brep_hlr->Add(solid, 0); + brep_hlr->Add(shape_in, 0); HLRAlgo_Projector projector(gp_Ax2(gp_Pnt(),dir)); brep_hlr->Projector(projector); brep_hlr->Update(); brep_hlr->Hide(); } catch (...) { AREA_ERR("error occurred while projecting shape"); + return -1; } TIME_PRINT(t1,"HLRBrep_Algo"); - WireJoiner joiner(dir); + WireJoiner joiner; try { #define ADD_HLR_SHAPE(_name) \ shape = hlrToShape._name##Compound();\ if(!shape.IsNull()){\ BRepLib::BuildCurves3d(shape);\ joiner.add(shape,true);\ + showShape(shape,"raw_" #_name);\ } TopoDS_Shape shape; HLRBRep_HLRToShape hlrToShape(brep_hlr); @@ -959,40 +988,40 @@ std::list Area::project(const TopoDS_Shape &solid) } catch (...) { AREA_ERR("error occurred while extracting edges"); + return -1; } TIME_PRINT(t1,"WireJoiner init"); joiner.splitEdges(); TIME_PRINT(t1,"WireJoiner splitEdges"); - // for(const auto &v : joiner.edges) { - // joiner.builder.Add(joiner.comp,BRepBuilderAPI_MakeWire(v.edge).Wire()); - // } + for(const auto &v : joiner.edges) { + // joiner.builder.Add(joiner.comp,BRepBuilderAPI_MakeWire(v.edge).Wire()); + showShape(v.edge,"split"); + } int skips = joiner.findClosedWires(); TIME_PRINT(t1,"WireJoiner findClosedWires"); - if(skips) AREA_WARN("skipped " << skips << " open edges"); - std::list wires; - - Area area(&myParams); + Area area(params); area.myParams.Explode = false; area.myParams.FitArcs = false; area.myParams.Reorient = false; - area.myParams.Outline = false; + area.myParams.Outline = true; + area.myParams.Fill = TopExp_Explorer(shape_in,TopAbs_FACE).More()?FillFace:FillNone; area.myParams.Coplanar = CoplanarNone; + area.myProjecting = true; area.add(joiner.comp, OperationUnion); - TopoDS_Shape shape = area.getShape(); + const TopoDS_Shape &shape = area.getShape(); + showShape(shape,"projected"); TIME_PRINT(t1,"Clipper wire union"); + TIME_PRINT(t,"project total"); if(shape.IsNull()) { AREA_ERR("poject failed"); - }else{ - for(TopExp_Explorer xp(shape, TopAbs_WIRE); xp.More(); xp.Next()) - wires.push_back(TopoDS::Wire(xp.Current())); + return -1; } - - TIME_PRINT(t,"project total"); - return std::move(wires); + shape_out = shape; + return skips; } std::vector > Area::makeSections( @@ -1123,14 +1152,20 @@ std::vector > Area::makeSections( std::vector > sections; sections.reserve(heights.size()); + + std::list projectedShapes; + if(project) { + projectedShapes = getProjectedShapes(trsf,false); + if(projectedShapes.empty()) { + AREA_ERR("empty projection"); + return sections; + } + } + tolerance *= 2.0; bool can_retry = fabs(tolerance)>Precision::Confusion(); TopLoc_Location locInverse(loc.Inverted()); - std::vector projectedShapes; - if(project) projectedShapes.reserve(myShapes.size()); - - bool aborted = false; for(double z : heights) { bool retried = !can_retry; while(true) { @@ -1140,10 +1175,11 @@ std::vector > Area::makeSections( BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax); const TopoDS_Shape &face = mkFace.Face(); - shared_ptr area(new Area(&myParams)); + shared_ptr area(std::make_shared(&myParams)); + area->myParams.Outline = false; area->setPlane(face.Moved(locInverse)); - if(projectedShapes.size()) { + if(project) { for(const auto &s : projectedShapes) { gp_Trsf t; t.SetTranslation(gp_Vec(0,0,-d)); @@ -1152,100 +1188,81 @@ std::vector > Area::makeSections( } sections.push_back(area); break; - }else{ - for(auto it=myShapes.begin();it!=myShapes.end();++it) { - const auto &s = *it; - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); + } - for(TopExp_Explorer xp(s.shape.Moved(loc), TopAbs_SOLID); xp.More(); xp.Next()) { - std::list wires; - if(project) { - wires = this->project(xp.Current()); - }else{ - Part::CrossSection section(a,b,c,xp.Current()); - wires = section.slice(-d); - } + for(auto it=myShapes.begin();it!=myShapes.end();++it) { + const auto &s = *it; + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); - if(wires.empty()) { - AREA_LOG("Section returns no wires"); - continue; - } + for(TopExp_Explorer xp(s.shape.Moved(loc), TopAbs_SOLID); xp.More(); xp.Next()) { + std::list wires; + Part::CrossSection section(a,b,c,xp.Current()); + wires = section.slice(-d); + if(wires.empty()) { + AREA_LOG("Section returns no wires"); + continue; + } - // always try to make face to normalize wire orientation - Part::FaceMakerBullseye mkFace; - mkFace.setPlane(pln); - for(const TopoDS_Wire &wire : wires) { - if(BRep_Tool::IsClosed(wire)) - mkFace.addWire(wire); - } - try { - mkFace.Build(); - const TopoDS_Shape &shape = mkFace.Shape(); - if (shape.IsNull()) - AREA_WARN("FaceMakerBullseye return null shape on section"); - else { - for(auto it=wires.begin(),itNext=it;it!=wires.end();it=itNext) { - ++itNext; - if(BRep_Tool::IsClosed(*it)) - wires.erase(it); - } - for(TopExp_Explorer xp(shape,myParams.Fill==FillNone?TopAbs_WIRE:TopAbs_FACE); - xp.More();xp.Next()) - { - builder.Add(comp,xp.Current()); - } + // always try to make face to normalize wire orientation + Part::FaceMakerBullseye mkFace; + mkFace.setPlane(pln); + for(const TopoDS_Wire &wire : wires) { + if(BRep_Tool::IsClosed(wire)) + mkFace.addWire(wire); + } + try { + mkFace.Build(); + const TopoDS_Shape &shape = mkFace.Shape(); + if (shape.IsNull()) + AREA_WARN("FaceMakerBullseye return null shape on section"); + else { + for(auto it=wires.begin(),itNext=it;it!=wires.end();it=itNext) { + ++itNext; + if(BRep_Tool::IsClosed(*it)) + wires.erase(it); + } + for(TopExp_Explorer xp(shape,myParams.Fill==FillNone?TopAbs_WIRE:TopAbs_FACE); + xp.More();xp.Next()) + { + builder.Add(comp,xp.Current()); } - }catch (Base::Exception &e){ - AREA_WARN("FaceMakerBullseye failed on section: " << e.what()); } - for(const TopoDS_Wire &wire : wires) - builder.Add(comp,wire); + }catch (Base::Exception &e){ + AREA_WARN("FaceMakerBullseye failed on section: " << e.what()); } + for(const TopoDS_Wire &wire : wires) + builder.Add(comp,wire); + } - // Make sure the compound has at least one edge - TopExp_Explorer xp(comp,TopAbs_EDGE); - if(xp.More()) { - if(project){ - projectedShapes.push_back(Shape(s.op,comp)); - - gp_Trsf t; - t.SetTranslation(gp_Vec(0,0,-d)); - TopLoc_Location wloc(t); - area->add(comp.Moved(wloc).Moved(locInverse),s.op); - }else - area->add(comp.Moved(locInverse),s.op); - }else if(area->myShapes.empty()){ - auto itNext = it; - if(++itNext != myShapes.end() && - (itNext->op==OperationIntersection || - itNext->op==OperationDifference)) - { - break; - } + // Make sure the compound has at least one edge + if(TopExp_Explorer(comp,TopAbs_EDGE).More()) + area->add(comp.Moved(locInverse),s.op); + else if(area->myShapes.empty()){ + auto itNext = it; + if(++itNext != myShapes.end() && + (itNext->op==OperationIntersection || + itNext->op==OperationDifference)) + { + break; } } - if(area->myShapes.size()){ - sections.push_back(area); - break; - } - if(project) { - AREA_ERR("empty projection"); - aborted = true; - } - if(retried) { - AREA_WARN("Discard empty section"); - break; - }else{ - AREA_TRACE("retry section " <"<myShapes.size()){ + sections.push_back(area); + TIME_PRINT(t1,"makeSection " << z); + break; + } + if(retried) { + AREA_WARN("Discard empty section"); + break; + }else{ + AREA_TRACE("retry section " <"< Area::getProjectedShapes(const gp_Trsf &trsf, bool inverse) const +{ + std::list ret; + TopLoc_Location loc(trsf); + TopLoc_Location locInverse(loc.Inverted()); + + mySkippedShapes = 0; + for(auto &s : myShapes) { + TopoDS_Shape out; + int skipped = Area::project(out,s.shape.Moved(loc),&myParams); + if(skipped < 0) { + ++mySkippedShapes; + continue; + }else + mySkippedShapes += skipped; + if(!out.IsNull()) + ret.emplace_back(s.op,inverse?out.Moved(locInverse):out); + } + if(mySkippedShapes) + AREA_WARN("skipped " << mySkippedShapes << " sub shapes during projection"); + return ret; +} void Area::build() { if(isBuilt()) return; @@ -1289,7 +1328,8 @@ void Area::build() { } TIME_INIT(t); - getPlane(); + gp_Trsf trsf; + getPlane(&trsf); try { myArea.reset(new CArea()); @@ -1302,7 +1342,8 @@ void Area::build() { short op = OperationUnion; bool pending = false; bool exploding = myParams.Explode; - for(const Shape &s : myShapes) { + const auto &shapes = (myParams.Outline&&!myProjecting)?getProjectedShapes(trsf):myShapes; + for(const Shape &s : shapes) { if(exploding) { exploding = false; explode(s.shape); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 938df1ba76..281d963f41 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -125,6 +125,7 @@ inline std::chrono::TIME_UNIT getDuration(TIME_POINT &t) class CArea; class CCurve; +class Bnd_Box; namespace Path { @@ -213,7 +214,8 @@ protected: bool myHaveFace; bool myHaveSolid; bool myShapeDone; - int mySkippedShapes; + bool myProjecting; + mutable int mySkippedShapes; static bool s_aborting; static AreaStaticParams s_params; @@ -246,7 +248,7 @@ protected: TopoDS_Shape findPlane(const TopoDS_Shape &shape, gp_Trsf &trsf); - std::list project(const TopoDS_Shape &solid); + std::list getProjectedShapes(const gp_Trsf &trsf, bool inverse=true) const; public: /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ @@ -279,7 +281,7 @@ public: * If no workplane is set using setPlane(), the active workplane is derived from * the added children shapes using the same algorithm empolyed by setPlane(). */ - TopoDS_Shape getPlane(gp_Trsf *trsf = NULL); + TopoDS_Shape getPlane(gp_Trsf *trsf=0); /** Add a child shape with given operation code * @@ -446,6 +448,8 @@ public: const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + static int project(TopoDS_Shape &out, const TopoDS_Shape &in, const AreaParams *params=0); + static void setWireOrientation(TopoDS_Wire& wire, const gp_Dir &dir, bool ccw); PARAM_ENUM_DECLARE(AREA_PARAMS_PATH)