diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 51d69089029a..602789709e45 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ #endif +#include "boost/date_time/posix_time/posix_time.hpp" #include #include @@ -52,22 +53,27 @@ #include #include #include -#include +#include #include #include #include -#include +#include #include #include #include +#include +#include #include +#include #include +#include #include "Area.h" #include "../libarea/Area.h" using namespace Path; +using namespace boost::posix_time; CAreaParams::CAreaParams() :PARAM_INIT(PARAM_FNAME,AREA_PARAMS_CAREA) @@ -78,17 +84,17 @@ AreaParams::AreaParams() {} CAreaConfig::CAreaConfig(const CAreaParams &p, bool noFitArcs) - :params(p) { - // Arc fitting is lossy. we shall reduce the number of unecessary fit - if(noFitArcs) - params.FitArcs=false; - #define AREA_CONF_SAVE_AND_APPLY(_param) \ PARAM_FNAME(_param) = BOOST_PP_CAT(CArea::get_,PARAM_FARG(_param))();\ - BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(params.PARAM_FNAME(_param)); + BOOST_PP_CAT(CArea::set_,PARAM_FARG(_param))(p.PARAM_FNAME(_param)); PARAM_FOREACH(AREA_CONF_SAVE_AND_APPLY,AREA_PARAMS_CAREA) + + // Arc fitting is lossy. we shall reduce the number of unecessary fit + if(noFitArcs) + CArea::set_fit_arcs(false); + } CAreaConfig::~CAreaConfig() { @@ -117,15 +123,16 @@ Area::Area(const Area &other, bool deep_copy) ,myShapes(other.myShapes) ,myTrsf(other.myTrsf) ,myParams(other.myParams) +,myShapePlane(other.myShapePlane) ,myWorkPlane(other.myWorkPlane) ,myHaveFace(other.myHaveFace) ,myHaveSolid(other.myHaveSolid) ,myShapeDone(false) { - if(!deep_copy) return; + if(!deep_copy || !other.isBuilt()) + return; if(other.myArea) myArea.reset(new CArea(*other.myArea)); - myShapePlane = other.myShapePlane; myShape = other.myShape; myShapeDone = other.myShapeDone; mySections.reserve(other.mySections.size()); @@ -142,16 +149,18 @@ void Area::setPlane(const TopoDS_Shape &shape) { myWorkPlane.Nullify(); return; } - BRepLib_FindSurface planeFinder(shape,-1,Standard_True); - if (!planeFinder.Found()) + TopoDS_Shape plane; + gp_Trsf trsf; + findPlane(shape,plane,trsf); + if (plane.IsNull()) throw Base::ValueError("shape is not planar"); - myWorkPlane = shape; - myTrsf.SetTransformation(GeomAdaptor_Surface( - planeFinder.Surface()).Plane().Position()); + myWorkPlane = plane; + myTrsf = trsf; clean(); } bool Area::isCoplanar(const TopoDS_Shape &s1, const TopoDS_Shape &s2) { + if(s1.IsNull() || s2.IsNull()) return false; if(s1.IsEqual(s2)) return true; TopoDS_Builder builder; TopoDS_Compound comp; @@ -234,7 +243,8 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, ccurve.append(CVertex(Point(p.X(),p.Y()))); for (;xp.More();xp.Next()) { - BRepAdaptor_Curve curve(xp.Current()); + const TopoDS_Edge &edge = TopoDS::Edge(xp.Current()); + BRepAdaptor_Curve curve(edge); bool reversed = (xp.Current().Orientation()==TopAbs_REVERSED); p = curve.Value(reversed?curve.FirstParameter():curve.LastParameter()); @@ -270,18 +280,31 @@ void Area::add(CArea &area, const TopoDS_Wire& wire, //fall through } default: { // Discretize all other type of curves - GCPnts_UniformDeflection discretizer(curve, deflection, + GCPnts_QuasiUniformDeflection discretizer(curve, deflection, curve.FirstParameter(), curve.LastParameter()); - if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { int nbPoints = discretizer.NbPoints (); - for (int i=1; i<=nbPoints; i++) { - gp_Pnt pt = discretizer.Value (i); - ccurve.append(CVertex(Point(pt.X(),pt.Y()))); - if(to_edges) { - area.append(ccurve); - ccurve.m_vertices.pop_front(); + //strangly OCC discretizer points are one-based, not zero-based, why? + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + ccurve.append(CVertex(Point(pt.X(),pt.Y()))); + if(to_edges) { + area.append(ccurve); + ccurve.m_vertices.pop_front(); + } } } + }else Standard_Failure::Raise("Curve discretization failed"); }} @@ -371,13 +394,10 @@ void Area::addToBuild(CArea &area, const TopoDS_Shape &shape) { TopExp_Explorer it(shape, TopAbs_FACE); myHaveFace = it.More(); } - const TopoDS_Shape *plane; - if(myParams.Coplanar == CoplanarNone) - plane = NULL; - else - plane = myWorkPlane.IsNull()?&myShapePlane:&myWorkPlane; + TopoDS_Shape plane = getPlane(); CArea areaOpen; - mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection,plane, + mySkippedShapes += add(area,shape,&myTrsf,myParams.Deflection, + myParams.Coplanar==CoplanarNone?NULL:&plane, myHaveSolid||myParams.Coplanar==CoplanarForce,&areaOpen, myParams.OpenMode==OpenModeEdges,myParams.Reorient); if(areaOpen.m_curves.size()) { @@ -392,153 +412,286 @@ namespace Part { extern PartExport std::list sort_Edges(double tol3d, std::list& edges); } -void Area::build() { - if(myArea || mySections.size()) return; +void Area::explode(const TopoDS_Shape &shape) { + const TopoDS_Shape &plane = getPlane(); + bool haveShape = false; + for(TopExp_Explorer it(shape, TopAbs_FACE); it.More(); it.Next()) { + haveShape = true; + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + for(TopExp_Explorer itw(it.Current(), TopAbs_WIRE); itw.More(); itw.Next()) { + for(BRepTools_WireExplorer xp(TopoDS::Wire(itw.Current()));xp.More();xp.Next()) + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(xp.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } + } + if(haveShape) return; + for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) { + if(myParams.Coplanar!=CoplanarNone && !isCoplanar(it.Current(),plane)){ + ++mySkippedShapes; + if(myParams.Coplanar == CoplanarForce) + continue; + } + add(*myArea,BRepBuilderAPI_MakeWire( + TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true); + } +} - if(myShapes.empty()) - throw Base::ValueError("no shape added"); +#if 0 +static void show(const TopoDS_Shape &shape, const char *name) { + App::Document *pcDoc = App::GetApplication().getActiveDocument(); + if (!pcDoc) + pcDoc = App::GetApplication().newDocument(); + Part::Feature *pcFeature = (Part::Feature *)pcDoc->addObject("Part::Feature", name); + // copy the data + //TopoShape* shape = new MeshObject(*pShape->getTopoShapeObjectPtr()); + pcFeature->Shape.setValue(shape); + //pcDoc->recompute(); +} +#endif -#define AREA_SRC(_param) myParams.PARAM_FNAME(_param) - PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); +bool Area::findPlane(const TopoDS_Shape &shape, + TopoDS_Shape &plane, gp_Trsf &trsf) +{ + return findPlane(shape,TopAbs_FACE,plane,trsf) || + findPlane(shape,TopAbs_WIRE,plane,trsf) || + findPlane(shape,TopAbs_EDGE,plane,trsf); +} - if(myWorkPlane.IsNull()) { - myShapePlane.Nullify(); - for(const Shape &s : myShapes) { - bool haveShape = false; - bool done = false; - TopoDS_Shape shapePlane; - gp_Trsf trsf; - gp_Ax3 pos; -#define AREA_CHECK_PLANE(_type) \ - shapePlane.Nullify();\ - for(TopExp_Explorer it(s.shape, TopAbs_##_type); it.More(); it.Next()) {\ - haveShape = true;\ - BRepLib_FindSurface planeFinder(it.Current(),-1,Standard_True);\ - if (!planeFinder.Found())\ - continue;\ - shapePlane = it.Current();\ - pos = GeomAdaptor_Surface(planeFinder.Surface()).Plane().Position();\ - trsf.SetTransformation(pos);\ - gp_Dir dir(pos.Direction());\ - if(fabs(dir.X()) origin.Z()) + continue; + top_found = true; + top_z = origin.Z(); + }else if(!dst.IsNull()) + continue; + dst = plane; + + //Some how the plane returned by BRepLib_FindSurface has Z always set to 0. + //We need to manually translate Z to its actual value + gp_Trsf trsf2; + trsf2.SetTranslationPart(gp_XYZ(0,0,-origin.Z())); + dst_trsf = trsf.Multiplied(trsf2); } + return haveShape; +} - if(myHaveSolid && myParams.SectionCount) { +std::vector > Area::makeSections( + PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights, + const TopoDS_Shape &_plane) +{ + TopoDS_Shape plane; + gp_Trsf trsf; - if(myParams.SectionOffset < 0) - throw Base::ValueError("invalid section offset"); - if(myParams.SectionCount>1 && myParams.Stepdown heights; + if(_heights.empty()) { + if(mode != SectionModeAbsolute && myParams.SectionOffset<0) + throw Base::ValueError("only positive section offset is allowed in non-absolute mode"); + if(myParams.SectionCount>1 && myParams.Stepdown zMax-zMin) { count = ceil((zMax-zMin)/myParams.Stepdown); if((count-1)*myParams.Stepdown < zMax-zMin) ++count; } + heights.reserve(count); for(int i=0;i area(new Area(&myParams)); - area->setPlane(face); - for(const Shape &s : myShapes) { - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); - for(TopExp_Explorer it(s.shape, TopAbs_SOLID); it.More(); it.Next()) { - BRepAlgoAPI_Section section(it.Current().Moved(loc),face); - if(!section.IsDone()) { - ++error; + if(zMax < zMin) { + hit_bottom = true; + break; + } + heights.push_back(zMax); + } + }else{ + heights.reserve(_heights.size()); + for(double z : _heights) { + switch(mode) { + case SectionModeAbsolute: { + gp_Pnt pt(0,0,z); + z = pt.Transformed(loc).Z(); + break; + }case SectionModeBoundBox: + z = zMax - z; + break; + case SectionModeWorkplane: + z = -z; + break; + default: + throw Base::ValueError("invalid section mode"); + } + if((zMin-z)>Precision::Confusion()) { + hit_bottom = true; + continue; + }else if ((z-zMax)>Precision::Confusion()) + continue; + heights.push_back(z); + } + } + + if(hit_bottom) + heights.push_back(zMin); + else if(heights.empty()) + heights.push_back(zMax); + + std::vector > sections; + sections.reserve(heights.size()); + for(double z : heights) { + gp_Pln pln(gp_Pnt(0,0,z),gp_Dir(0,0,1)); + Standard_Real a,b,c,d; + pln.Coefficients(a,b,c,d); + BRepLib_MakeFace mkFace(pln,xMin,xMax,yMin,yMax); + const TopoDS_Shape &face = mkFace.Face(); + + shared_ptr area(new Area(&myParams)); + area->setPlane(face); + for(const Shape &s : myShapes) { + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(TopExp_Explorer it(s.shape.Moved(loc), TopAbs_SOLID); it.More(); it.Next()) { + Part::CrossSection section(a,b,c,it.Current()); + std::list wires = section.slice(-d); + if(wires.empty()) { + Base::Console().Warning("Section return no wires\n"); + continue; + } + + Part::FaceMakerBullseye mkFace; + mkFace.setPlane(pln); + for(const TopoDS_Wire &wire : wires) + mkFace.addWire(wire); + try { + mkFace.Build(); + if (mkFace.Shape().IsNull()) + Base::Console().Warning("FaceMakerBullseye return null shape on section\n"); + else { + builder.Add(comp,mkFace.Shape()); continue; } - const TopoDS_Shape &shape = section.Shape(); - if(shape.IsNull()) continue; - - Part::FaceMakerBullseye mkFace; - mkFace.setPlane(pln); - - std::list edges; - for(TopExp_Explorer it(shape, TopAbs_EDGE); it.More(); it.Next()) - edges.push_back(TopoDS::Edge(it.Current())); - bool open = false; - std::list wires; - while(edges.size()) { - const std::list sorted = - Part::sort_Edges(Precision::Confusion(),edges); - BRepBuilderAPI_MakeWire mkWire; - for(const TopoDS_Edge &e : sorted) - mkWire.Add(e); - const TopoDS_Wire &wire = mkWire.Wire(); - if(!BRep_Tool::IsClosed(wire)) - open = true; - wires.push_back(wire); - } - if(!open) { - for(const TopoDS_Wire &wire : wires) - mkFace.addWire(wire); - try { - mkFace.Build(); - if (mkFace.Shape().IsNull()) - continue; - builder.Add(comp,mkFace.Shape()); - continue; - }catch (Base::Exception &e){ - Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); - } - } - //Shouldn't have any open wire, so count as error - ++error; - for(const TopoDS_Wire &wire : wires) - builder.Add(comp,wire); + }catch (Base::Exception &e){ + Base::Console().Warning("FaceMakerBullseye failed on section: %s\n", e.what()); } - if(comp.IsNull()) continue; + for(const TopoDS_Wire &wire : wires) + builder.Add(comp,wire); + } + + // Make sure the compound has at least one edge + for(TopExp_Explorer it(comp,TopAbs_EDGE);it.More();) { area->add(comp,s.op); + break; } - mySections.push_back(area); } - if(error) - Base::Console().Warning("Some errors occured during operation\n"); + if(area->myShapes.size()) + sections.push_back(area); + else + Base::Console().Warning("Discard empty section\n"); + } + return std::move(sections); +} + +TopoDS_Shape Area::getPlane(gp_Trsf *trsf) { + if(!myWorkPlane.IsNull()) { + if(trsf) *trsf = myTrsf; + return myWorkPlane; + } + if(!isBuilt()) { + myShapePlane.Nullify(); + for(const Shape &s : myShapes) + findPlane(s.shape,myShapePlane,myTrsf); + if(myShapePlane.IsNull()) + throw Base::ValueError("shapes are not planar"); + } + if(trsf) *trsf = myTrsf; + return myShapePlane; +} + +bool Area::isBuilt() const { + return (myArea || mySections.size()); +} + + +void Area::build() { + if(isBuilt()) return; + + if(myShapes.empty()) + throw Base::ValueError("no shape added"); + +#define AREA_SRC(_param) myParams.PARAM_FNAME(_param) + PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); + + if(myHaveSolid && myParams.SectionCount) { + mySections = makeSections(myParams.SectionMode); return; } + getPlane(); + try { myArea.reset(new CArea()); myAreaOpen.reset(new CArea()); @@ -549,13 +702,11 @@ void Area::build() { mySkippedShapes = 0; short op = OperationUnion; bool pending = false; - bool explode = myParams.Explode; + bool exploding = myParams.Explode; for(const Shape &s : myShapes) { - if(explode) { - explode = false; - for (TopExp_Explorer it(s.shape, TopAbs_EDGE); it.More(); it.Next()) - add(*myArea,BRepBuilderAPI_MakeWire( - TopoDS::Edge(it.Current())).Wire(),&myTrsf,myParams.Deflection,true); + if(exploding) { + exploding = false; + explode(s.shape); continue; }else if(op!=s.op) { if(myParams.OpenMode!=OpenModeNone) @@ -604,8 +755,7 @@ void Area::build() { Area area(&myParams); area.myParams.Explode = false; area.myParams.Coplanar = CoplanarNone; - area.myWorkPlane = myWorkPlane.IsNull()?myShapePlane:myWorkPlane; - area.myTrsf = myTrsf; + area.myWorkPlane = getPlane(&area.myTrsf); while(edges.size()) { BRepBuilderAPI_MakeWire mkWire; for(const auto &e : Part::sort_Edges(myParams.Tolerance,edges)) @@ -706,6 +856,7 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { return toShape(area,bFill,&trsf); } + #define AREA_SECTION(_op,_index,...) do {\ if(mySections.size()) {\ if(_index>=(int)mySections.size())\ @@ -720,9 +871,14 @@ TopoDS_Shape Area::toShape(CArea &area, short fill) { if(s.IsNull()) continue;\ builder.Add(compound,s.Moved(loc));\ }\ - return compound;\ + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();)\ + return compound;\ + return TopoDS_Shape();\ }\ - return mySections[_index]->_op(-1, ## __VA_ARGS__).Moved(loc);\ + const TopoDS_Shape &shape = mySections[_index]->_op(-1, ## __VA_ARGS__);\ + if(!shape.IsNull())\ + return shape.Moved(loc);\ + return shape;\ }\ }while(0) @@ -732,6 +888,8 @@ TopoDS_Shape Area::getShape(int index) { if(myShapeDone) return myShape; + if(!myArea) return TopoDS_Shape(); + CAreaConfig conf(myParams); #define AREA_MY(_param) myParams.PARAM_FNAME(_param) @@ -793,9 +951,13 @@ TopoDS_Shape Area::getShape(int index) { const TopoDS_Shape &shape = toShape(*area,fill); builder.Add(compound,toShape(*area,fill)); } - builder.Add(compound,areaPocket.makePocket( - -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); - myShape = compound; + // make sure the compound has at least one edge + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { + builder.Add(compound,areaPocket.makePocket( + -1,PARAM_FIELDS(AREA_MY,AREA_PARAMS_POCKET))); + myShape = compound; + break; + } myShapeDone = true; return myShape; } @@ -826,9 +988,13 @@ TopoDS_Shape Area::makeOffset(int index,PARAM_ARGS(PARAM_FARG,AREA_PARAMS_OFFSET fill = myParams.Fill; else fill = FillNone; - builder.Add(compound,toShape(*area,fill)); + const TopoDS_Shape &shape = toShape(*area,fill); + if(shape.IsNull()) continue; + builder.Add(compound,shape); } - return compound; + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) + return compound; + return TopoDS_Shape(); } void Area::makeOffset(list > &areas, @@ -855,7 +1021,7 @@ void Area::makeOffset(list > &areas, #ifdef AREA_OFFSET_ALGO PARAM_ENUM_CONVERT(AREA_SRC,PARAM_FNAME,PARAM_ENUM_EXCEPT,AREA_PARAMS_CLIPPER_FILL); #endif - + for(int i=0;count<0||i()); CArea &area = *areas.back(); @@ -1032,23 +1198,25 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf) { if(!wire.IsNull()) builder.Add(compound,wire); } - - if(!compound.IsNull() && fill) { - try{ - Part::FaceMakerBullseye mkFace; - if(trsf) - mkFace.setPlane(gp_Pln().Transformed(*trsf)); - for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next()) - mkFace.addWire(TopoDS::Wire(it.Current())); - mkFace.Build(); - if (mkFace.Shape().IsNull()) - Base::Console().Warning("FaceMakerBullseye returns null shape\n"); - return mkFace.Shape(); - }catch (Base::Exception &e){ - Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); + for(TopExp_Explorer it(compound,TopAbs_EDGE);it.More();) { + if(fill) { + try{ + Part::FaceMakerBullseye mkFace; + if(trsf) + mkFace.setPlane(gp_Pln().Transformed(*trsf)); + for(TopExp_Explorer it(compound, TopAbs_WIRE); it.More(); it.Next()) + mkFace.addWire(TopoDS::Wire(it.Current())); + mkFace.Build(); + if (mkFace.Shape().IsNull()) + Base::Console().Warning("FaceMakerBullseye returns null shape\n"); + return mkFace.Shape(); + }catch (Base::Exception &e){ + Base::Console().Warning("FaceMakerBullseye failed: %s\n", e.what()); + } } + return compound; } - return compound; + return TopoDS_Shape(); } std::list Area::sortWires(const std::list &shapes, @@ -1220,13 +1388,20 @@ void Area::toPath(Toolpath &path, const std::list &shapes, break; } default: { // Discretize all other type of curves - GCPnts_UniformDeflection discretizer(curve, deflection, + GCPnts_QuasiUniformDeflection discretizer(curve, deflection, curve.FirstParameter(), curve.LastParameter()); - if (discretizer.IsDone () && discretizer.NbPoints () > 0) { + if (discretizer.IsDone () && discretizer.NbPoints () > 1) { int nbPoints = discretizer.NbPoints (); - for (int i=1; i<=nbPoints; i++) { - gp_Pnt pt = discretizer.Value (i); - addCommand(path,pt); + if(reversed) { + for (int i=nbPoints-1; i>=1; --i) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } + }else{ + for (int i=2; i<=nbPoints; i++) { + gp_Pnt pt = discretizer.Value (i); + addCommand(path,pt); + } } }else Standard_Failure::Raise("Curve discretization failed"); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index ef7175532d3e..3bad23531255 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -54,7 +54,8 @@ struct PathExport AreaParams: CAreaParams { bool operator==(const AreaParams &other) const { #define AREA_COMPARE(_param) \ if(PARAM_FIELD(NAME,_param)!=other.PARAM_FIELD(NAME,_param)) return false; - PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CONF) + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_CAREA) + PARAM_FOREACH(AREA_COMPARE,AREA_PARAMS_AREA) return true; } bool operator!=(const AreaParams &other) const { @@ -71,12 +72,9 @@ struct PathExport AreaParams: CAreaParams { */ struct PathExport CAreaConfig { - /** Stores current libarea settings */ + /** For saving current libarea settings */ PARAM_DECLARE(PARAM_FNAME,AREA_PARAMS_CAREA) - /** Stores user defined setting */ - CAreaParams params; - /** The constructor automatically saves current setting and apply user defined ones * * \arg \c p user defined configurations @@ -96,8 +94,7 @@ class PathExport Area: public Base::BaseClass { TYPESYSTEM_HEADER(); -protected: - +public: struct Shape { short op; TopoDS_Shape shape; @@ -108,6 +105,7 @@ class PathExport Area: public Base::BaseClass { {} }; +protected: std::list myShapes; std::unique_ptr myArea; std::unique_ptr myAreaOpen; @@ -146,6 +144,10 @@ class PathExport Area: public Base::BaseClass { */ TopoDS_Shape makePocket(); + void explode(const TopoDS_Shape &shape); + + bool isBuilt() const; + public: /** Declare all parameters defined in #AREA_PARAMS_ALL as member variable */ PARAM_ENUM_DECLARE(AREA_PARAMS_ALL) @@ -155,19 +157,28 @@ class PathExport Area: public Base::BaseClass { virtual ~Area(); /** Set a working plane + * + * \arg \c shape: a shape defining a working plane. + * + * The supplied shape does not need to be planar. Area will try to find planar + * sub-shape (face, wire or edge). If more than one planar sub-shape is found, + * it will prefer the top plane parallel to XY0 plane. * * If no working plane are set, Area will try to find a working plane from - * individual children faces, wires or edges. By right, we should create a - * compound of all shapes and then findplane on it. However, because we - * supports solid, and also because OCC may hang for a long time if - * something goes a bit off, we opt to find plane on each individual shape. - * If you intend to pass individual edges, you must supply a workplane shape - * manually - * - * \arg \c shape: a shape defining a working plane + * the added children shape using the same algorithm */ void setPlane(const TopoDS_Shape &shape); + /** Return the current active workplane + * + * \arg \c trsf: optional return of a transformation matrix that will bring the + * found plane to XY0 plane. + * + * 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); + /** Add a child shape with given operation code * * No validation is done at this point. Exception will be thrown when asking @@ -195,10 +206,19 @@ class PathExport Area: public Base::BaseClass { TopoDS_Shape makePocket(int index=-1, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_POCKET)); + std::vector > makeSections( + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + const std::vector &_heights = std::vector(), + const TopoDS_Shape &plane = TopoDS_Shape()); + /** Config this Area object */ void setParams(const AreaParams ¶ms); + const std::list getChildren() const { + return myShapes; + } + /** Get the current configuration */ const AreaParams &getParams() const { return myParams; @@ -330,6 +350,42 @@ class PathExport Area: public Base::BaseClass { static void toPath(Toolpath &path, const std::list &shapes, const gp_Pnt *pstart=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_PATH)); + + /** Explore the shape to find a planar element, and return its transformation + * + * \arg \c shape: shape to explore + * \arg \c type: OCC shape type (TopAbs_ShapeEnum) to explore + * \arg \c plane: returns the sub planar shape found + * \arg \c trsf: the transformation of the plane which will transform the + * plane into XY0 plane. + * + * If there are multiple subshapes on different planes. It will prefer the + * top XY plane. If there is no XY parallel plane, the first planar shape + * encountered will be returned + * + * \return Returns true is there is any subshape of the give type found. + * You should use plane.IsNull() to see if there is any planar shape found. + */ + static bool findPlane(const TopoDS_Shape &shape, int type, + TopoDS_Shape &plane, gp_Trsf &trsf); + + /** Explore the shape with subtype FACE, WIRE and EDGE to find a planar + * subshape + * + * \arg \c shape: shape to explore + * \arg \c plane: returns the sub planar shape found + * \arg \c trsf: the transformation of the plane which will transform the + * plane into XY0 plane. + * + * If there are multiple subshapes on different planes. It will prefer the + * top XY plane. If there is no XY parallel plane, the first planar shape + * encountered will be returned + * + * \return Returns true is there is any subshape is found. You should use + * plane.IsNull() to see if there is any planar shape found. + */ + static bool findPlane(const TopoDS_Shape &shape, + TopoDS_Shape &plane, gp_Trsf &trsf); }; } //namespace Path diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 0f38db3a6aba..0f10d923be2c 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -104,9 +104,9 @@ /** Operation code */ #define AREA_PARAMS_OPCODE \ - ((enum,op,Operation,0,\ - "Boolean operation. For the first four operation, see https://goo.gl/Gj8RUu.\n"\ - "'Compound' means no operation, normal used to do Area.sortWires().",\ + ((enum,op,Operation,0,"Boolean operation.\n"\ + "For the first four operations, see https://goo.gl/Gj8RUu.\n"\ + "'Compound' means no operation, normally used to do Area.sortWires().",\ (Union)(Difference)(Intersection)(Xor)(Compound))) /** Offset parameters */ @@ -115,11 +115,22 @@ ((long,extra_pass,ExtraPass,0,"Number of extra offset pass to generate."))\ ((double,stepover,Stepover,0.0,"Cutter diameter to step over on each pass. If =0, use Offset")) +#define AREA_PARAMS_SECTION_EXTRA \ + ((enum,mode,SectionMode,2,"Section offset coordinate mode.\n"\ + "'Absolute' means the absolute Z height to start section.\n"\ + "'BoundBox' means relative Z height to the bounding box of all the children shape. Only\n"\ + "positive value is allowed, which specifies the offset below the top Z of the bounding box.\n"\ + "Note that OCC has trouble getting the minimumi bounding box of some solids, particually\n"\ + "those with non-planar surface.\n"\ + "'Workplane' means relative to workplane.",\ + (Absolute)(BoundBox)(Workplane))) + /** Section parameters */ #define AREA_PARAMS_SECTION \ ((long,count,SectionCount,0,"Number of sections to generate. -1 means full sections."))\ ((double,stepdown,Stepdown,1.0,"Step down distance for each section"))\ - ((double,offset,SectionOffset,0.0,"Offset for the first section")) + ((double,offset,SectionOffset,0.0,"Offset for the first section"))\ + AREA_PARAMS_SECTION_EXTRA #ifdef AREA_OFFSET_ALGO # define AREA_PARAMS_OFFSET_ALGO \ diff --git a/src/Mod/Path/App/AreaPy.xml b/src/Mod/Path/App/AreaPy.xml index 062b82935bca..170288ed3ce4 100644 --- a/src/Mod/Path/App/AreaPy.xml +++ b/src/Mod/Path/App/AreaPy.xml @@ -25,8 +25,12 @@ All arguments are optional. - setPlane(shape): Set the working plane. The shape will not be used for -any operation + setPlane(shape): Set the working plane.\n +The supplied shape does not need to be planar. Area will try to find planar +sub-shape (face, wire or edge). If more than one planar sub-shape is found, it +will prefer the top plane parallel to XY0 plane. If no working plane are set, +Area will try to find a working plane from the added children shape using the +same algorithm @@ -46,6 +50,11 @@ any operation + + + + + @@ -74,5 +83,17 @@ any operation + + + The current workplane. If no plane is set, it is derived from the added shapes. + + + + + + A list of tuple: [(shape,op), ...] containing the added shapes together with their operation code + + + diff --git a/src/Mod/Path/App/AreaPyImp.cpp b/src/Mod/Path/App/AreaPyImp.cpp index 75e370d2d061..3f44a6c98d46 100644 --- a/src/Mod/Path/App/AreaPyImp.cpp +++ b/src/Mod/Path/App/AreaPyImp.cpp @@ -54,11 +54,11 @@ static const AreaDoc myDocs[] = { "add((shape...)," PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_OPCODE) "):\n" "Add TopoShape(s) with given operation code\n" PARAM_PY_DOC(ARG,AREA_PARAMS_OPCODE) - "\nThe first shape's wires will be fused together regardless of the op code given.\n" - "Subsequent shape's wire will be combined using the op code. All shape wires\n" - "shall be coplanar, and are used to determine a working plane for face making and\n" - "offseting. You can call setPlane() to supply a reference shape to determin the\n" - "working plane in case the added shapes are all colinear lines.\n", + "\nThe first shape's wires will be unioned together regardless of the op code given\n" + "(except for 'Compound'). Subsequent shape's wire will be combined using the op code.\n" + "All shape wires shall be coplanar, and are used to determine a working plane for face\n" + "making and offseting. You can call setPlane() to supply a reference shape to determin\n" + "the workplane in case the added shapes are all colinear lines.\n", }, { @@ -77,6 +77,17 @@ static const AreaDoc myDocs[] = { "\n* index (-1): the index of the section. -1 means all sections. No effect on planar shape.\n" PARAM_PY_DOC(ARG,AREA_PARAMS_POCKET), }, + { + "makeSections", + + "makeSections(" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) ", heights=[], plane=None):\n" + "Make a list of area holding the sectioned children shapes on given heights\n" + PARAM_PY_DOC(ARG,AREA_PARAMS_SECTION_EXTRA) + "\n* heights ([]): a list of section heights, the meaning of the value is determined by 'mode'.\n" + "If not specified, the current SectionCount, and SectionOffset of this Area is used.\n" + "\n* plane (None): optional shape to specify a section plane. If not give, the current workplane\n" + "of this Area is used.", + }, { "sortWires", @@ -270,6 +281,53 @@ PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) return Py::new_reference_to(Part::shape2pyshape(resultShape)); } +PyObject* AreaPy::makeSections(PyObject *args, PyObject *keywds) +{ + static char *kwlist[] = {PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SECTION_EXTRA), + "heights", "plane", NULL}; + PyObject *heights = NULL; + PyObject *plane = NULL; + + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA) + + if (!PyArg_ParseTupleAndKeywords(args, keywds, + "|" PARAM_PY_KWDS(AREA_PARAMS_SECTION_EXTRA) "OO!", kwlist, + PARAM_REF(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + &heights, &(Part::TopoShapePy::Type), &plane)) + return 0; + + std::vector h; + if(heights) { + if (PyObject_TypeCheck(heights, &(PyFloat_Type))) + h.push_back(PyFloat_AsDouble(heights)); + else if (PyObject_TypeCheck(heights, &(PyList_Type)) || + PyObject_TypeCheck(heights, &(PyTuple_Type))) { + Py::Sequence shapeSeq(heights); + h.reserve(shapeSeq.size()); + for (Py::Sequence::iterator it = shapeSeq.begin(); it != shapeSeq.end(); ++it) { + PyObject* item = (*it).ptr(); + if(!PyObject_TypeCheck(item, &(PyFloat_Type))) { + PyErr_SetString(PyExc_TypeError, "heights must only contain float type"); + return 0; + } + h.push_back(PyFloat_AsDouble(item)); + } + }else{ + PyErr_SetString(PyExc_TypeError, "heights must be of type float or list/tuple of float"); + return 0; + } + } + + std::vector > sections = getAreaPtr()->makeSections( + PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SECTION_EXTRA), + h,plane?GET_TOPOSHAPE(plane):TopoDS_Shape()); + + Py::List ret; + for(auto &area : sections) + ret.append(Py::asObject(new AreaPy(new Area(*area,false)))); + return Py::new_reference_to(ret); +} + PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { static char *kwlist[] = {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),NULL}; @@ -337,6 +395,20 @@ Py::List AreaPy::getSections(void) const { return ret; } +Py::List AreaPy::getShapes(void) const { + Py::List ret; + Area *area = getAreaPtr(); + const std::list &shapes = area->getChildren(); + for(auto &s : shapes) + ret.append(Py::TupleN(Part::shape2pyshape(s.shape),Py::Int(s.op))); + return ret; +} + +Py::Object AreaPy::getWorkplane(void) const { + return Part::shape2pyshape(getAreaPtr()->getPlane()); +} + + // custom attributes get/set PyObject *AreaPy::getCustomAttributes(const char* /*attr*/) const diff --git a/src/Mod/Path/App/FeatureArea.cpp b/src/Mod/Path/App/FeatureArea.cpp index 97379ed806a4..f016a4611f22 100644 --- a/src/Mod/Path/App/FeatureArea.cpp +++ b/src/Mod/Path/App/FeatureArea.cpp @@ -49,11 +49,11 @@ FeatureArea::FeatureArea() PARAM_PROP_ADD("Area",AREA_PARAMS_OPCODE); PARAM_PROP_ADD("Area",AREA_PARAMS_BASE); PARAM_PROP_ADD("Offset",AREA_PARAMS_OFFSET); + PARAM_PROP_ADD("Offset", AREA_PARAMS_OFFSET_CONF); PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET); PARAM_PROP_ADD("Pocket",AREA_PARAMS_POCKET_CONF); PARAM_PROP_ADD("Section",AREA_PARAMS_SECTION); - PARAM_PROP_ADD("Offset Settings", AREA_PARAMS_OFFSET_CONF); - PARAM_PROP_ADD("libarea Settings",AREA_PARAMS_CAREA); + PARAM_PROP_ADD("libarea",AREA_PARAMS_CAREA); PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_ALL); PocketMode.setValue((long)0);