diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index dc2ca1c841..dd33b48158 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -127,11 +127,15 @@ public: "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" ); add_keyword_method("sortWires",&Module::sortWires, - "sortWires(shapes, start=Vector(), " PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ", key=value...)\n" + "sortWires(shapes, start=Vector(), " + PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_ARC_PLANE) + PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_SORT) ", key=value...)\n" "\nReturns (wires,end), where 'wires' is sorted across Z value and with optimized travel distance,\n" - "and 'end' is the ending position of the whole wires\n" + "and 'end' is the ending position of the whole wires. If arc_plane==1, it returns (wires,end,arc_plane),\n" + "where arc_plane is the found plane if any, or unchanged.\n" "\n* shapes: input shape list\n" "\n* start (Vector()): optional start position.\n" + PARAM_PY_DOC(ARG, AREA_PARAMS_ARC_PLANE) PARAM_PY_DOC(ARG, AREA_PARAMS_SORT) "\n* : any key supported by Path.Area, see Path.Area.getParamDesc() for description" ); @@ -373,16 +377,22 @@ private: Py::Object sortWires(const Py::Tuple& args, const Py::Dict &kwds) { + PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_ARC_PLANE) PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_SORT) PARAM_PY_DECLARE_INIT(PARAM_FNAME,AREA_PARAMS_CONF) PyObject *pShapes=NULL; PyObject *start=NULL; static char* kwd_list[] = {"shapes", "start", + PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_ARC_PLANE), PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_SORT), PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF), NULL}; if (!PyArg_ParseTupleAndKeywords(args.ptr(), kwds.ptr(), - "O|O!" PARAM_PY_KWDS(AREA_PARAMS_SORT) PARAM_PY_KWDS(AREA_PARAMS_CONF), + "O|O!" + PARAM_PY_KWDS(AREA_PARAMS_ARC_PLANE) + PARAM_PY_KWDS(AREA_PARAMS_SORT) + PARAM_PY_KWDS(AREA_PARAMS_CONF), kwd_list, &pShapes, &(Base::VectorPy::Type), &start, + PARAM_REF(PARAM_FARG,AREA_PARAMS_ARC_PLANE), PARAM_REF(PARAM_FARG,AREA_PARAMS_SORT), PARAM_REF(PARAM_FNAME,AREA_PARAMS_CONF))) throw Py::Exception(); @@ -413,16 +423,19 @@ private: } try { + bool need_arc_plane = arc_plane==Area::ArcPlaneAuto; std::list wires = Area::sortWires(shapes,¶ms,&pstart, - &pend, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + &pend, &arc_plane, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); PyObject *list = PyList_New(0); for(auto &wire : wires) PyList_Append(list,Py::new_reference_to( Part::shape2pyshape(TopoDS::Wire(wire)))); - PyObject *ret = PyTuple_New(2); + PyObject *ret = PyTuple_New(need_arc_plane?3:2); PyTuple_SetItem(ret,0,list); PyTuple_SetItem(ret,1,new Base::VectorPy( Base::Vector3d(pend.X(),pend.Y(),pend.Z()))); + if(need_arc_plane) + PyTuple_SetItem(ret,2,PyInt_FromLong(arc_plane)); return Py::asObject(ret); } PATH_CATCH } diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 9f8c74f82a..ddce83d6da 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -262,19 +262,19 @@ void Area::addWire(CArea &area, const TopoDS_Wire& wire, double first = curve.FirstParameter(); double last = curve.LastParameter(); gp_Circ circle = curve.Circle(); - gp_Ax1 axis = circle.Axis(); - int dir = axis.Direction().Z()<0?-1:1; - if(reversed) dir = -dir; - gp_Pnt loc = axis.Location(); + gp_Dir dir = circle.Axis().Direction(); + gp_Pnt center = circle.Location(); + int type = dir.Z()<0?-1:1; + if(reversed) type = -type; if(fabs(first-last)>M_PI) { // Split arc(circle) larger than half circle. Because gcode // can't handle full circle? gp_Pnt mid = curve.Value((last-first)*0.5+first); - ccurve.append(CVertex(dir,Point(mid.X(),mid.Y()), - Point(loc.X(),loc.Y()))); + ccurve.append(CVertex(type,Point(mid.X(),mid.Y()), + Point(center.X(),center.Y()))); } - ccurve.append(CVertex(dir,Point(p.X(),p.Y()), - Point(loc.X(),loc.Y()))); + ccurve.append(CVertex(type,Point(p.X(),p.Y()), + Point(center.X(),center.Y()))); break; } //fall through @@ -494,14 +494,18 @@ struct FindPlane { if (!finder.Found()) return; - // NOTE: It seemed that FindSurface disregardge shape's transformation, - // so we have to transformed the found plane manually - gp_Ax3 pos = GeomAdaptor_Surface(finder.Surface()).Plane().Position().Transformed( - shape.Location().Transformation()); + gp_Ax3 pos = GeomAdaptor_Surface(finder.Surface()).Plane().Position(); + + // It seemed that FindSurface disregard shape's transformation, + // so we have to transformed the found plane manually, or is it?? + // pos.Transform(shape.Location().Transformation()); - //force plane to be right handed + // We only use right hand coordinate, hence gp_Ax2 instead of gp_Ax3 + // This means that no matter what the work plane face oriented, we + // will treat it as face upward in a right hand coordinate system. if(!pos.Direct()) pos = gp_Ax3(pos.Ax2()); + gp_Dir dir(pos.Direction()); trsf.SetTransformation(pos); @@ -1178,7 +1182,7 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_POCKE Stepover = -stepover; return makeOffset(index,PARAM_FIELDS(PARAM_FNAME,AREA_PARAMS_OFFSET)); }case Area::PocketModeZigZagOffset: - pm = ZigZagThenSingleOffsetPocketMode; + pm = ZigZagThenSingleOffsetPocketMode; break; default: throw Base::ValueError("unknown poket mode"); @@ -1530,24 +1534,79 @@ struct ShapeInfo{ struct ShapeInfoBuilder { std::list &myList; + gp_Trsf &myTrsf; + short *myArcPlane; + bool &myArcPlaneFound; - ShapeInfoBuilder(std::list &list) - :myList(list) + ShapeInfoBuilder(bool &plane_found, short *arc_plane, gp_Trsf &trsf, std::list &list) + :myList(list) ,myTrsf(trsf) ,myArcPlane(arc_plane), myArcPlaneFound(plane_found) {} - void operator()(const TopoDS_Shape &shape, int) { + void operator()(const TopoDS_Shape &shape, int type) { BRepLib_FindSurface finder(shape,-1,Standard_True); - if(finder.Found()) - myList.push_back(ShapeInfo(finder,shape)); - else + if(!finder.Found()) { myList.push_back(ShapeInfo(shape)); + return; + } + myList.push_back(ShapeInfo(finder,shape)); + if(myArcPlane==NULL || *myArcPlane==Area::ArcPlaneNone || myArcPlaneFound) + return; + + if(type == TopAbs_EDGE) { + BRepAdaptor_Curve curve(TopoDS::Edge(shape)); + if(curve.GetType()!=GeomAbs_Circle) return; + }else{ + for(TopExp_Explorer it(shape,TopAbs_EDGE);it.More();it.Next()) { + BRepAdaptor_Curve curve(TopoDS::Edge(it.Current())); + if(curve.GetType()==GeomAbs_Circle) goto NEXT; + } + return; + } +NEXT: + gp_Ax3 pos = myList.back().myPln.Position(); + if(!pos.Direct()) pos = gp_Ax3(pos.Ax2()); + const gp_Dir &dir = pos.Direction(); + gp_Ax3 dstPos; + switch(*myArcPlane) { + case Area::ArcPlaneAuto: { + bool x0 = fabs(dir.X()) Area::sortWires(const std::list &shapes, const AreaParams *params, const gp_Pnt *_pstart, gp_Pnt *_pend, - PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) + short *arc_plane, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) { std::list wires; @@ -1575,6 +1634,9 @@ std::list Area::sortWires(const std::list &shapes, #define SORT_WIRE_TIME(_msg) \ TIME_PRINT(t1,"sortWires "<< _msg) + gp_Trsf trsf; + bool arcPlaneFound = false; + if(sort_mode == SortMode3D) { for(auto &shape : shapes) shape_list.push_back(ShapeInfo(shape)); @@ -1582,13 +1644,14 @@ std::list Area::sortWires(const std::list &shapes, //first pass, find plane of each shape for(auto &shape : shapes) { //explode the shape - foreachSubshape(shape,ShapeInfoBuilder(shape_list)); + foreachSubshape(shape,ShapeInfoBuilder( + arcPlaneFound,arc_plane,trsf,shape_list)); } if(shape_list.empty()) return wires; - SORT_WIRE_TIME("plan finding"); + SORT_WIRE_TIME("plane finding"); } Bnd_Box bounds; @@ -1603,6 +1666,10 @@ std::list Area::sortWires(const std::list &shapes, //Second stage, group shape by its plane, and find overall boundary for(auto itNext=shape_list.begin(),it=itNext;it!=shape_list.end();it=itNext) { ++itNext; + if(arcPlaneFound) { + it->myShape.Move(trsf); + if(it->myPlanar) it->myPln.Transform(trsf); + } if(use_bound) BRepBndLib::Add(it->myShape, bounds, Standard_False); if(!it->myPlanar) continue; @@ -1694,28 +1761,54 @@ std::list Area::sortWires(const std::list &shapes, return std::move(wires); } -static void addCommand(Toolpath &path, const gp_Pnt &p, - bool g0=false, double g0height=0.0, double clearance=0.0) +static inline void addParameter(Command &cmd, const char *name, + double last, double next, bool relative=false) { + double d = next-last; + if(fabs(d)Precision::Confusion()) { - cmd.Parameters["Z"] = g0height; - path.addCommand(cmd); - cmd.Parameters["X"] = p.X(); - cmd.Parameters["Y"] = p.Y(); - path.addCommand(cmd); - if(fabs(clearance)>Precision::Confusion()) { - cmd.Parameters["Z"] = p.Z()+clearance; - path.addCommand(cmd); - cmd.Name = "G1"; - } - }else{ - cmd.Parameters["X"] = p.X(); - cmd.Parameters["Y"] = p.Y(); - } - cmd.Parameters["Z"] = p.Z(); + cmd.Name = name; + addParameter(cmd,"X",last.X(),next.X()); + addParameter(cmd,"Y",last.Y(),next.Y()); + addParameter(cmd,"Z",last.Z(),next.Z()); path.addCommand(cmd); + return; +} + +typedef Standard_Real (gp_Pnt::*AxisGetter)() const; +typedef void (gp_Pnt::*AxisSetter)(Standard_Real); + +static void addCommand(Toolpath &path, + gp_Pnt last, const gp_Pnt &next, + AxisGetter getter, AxisSetter setter, + double retraction, double clearance) +{ + if(!getter || retraction-(last.*getter)() < Precision::Confusion()) { + addCommand(path,last,next,"G0"); + return; + } + gp_Pnt pt(last); + (pt.*setter)(retraction); + addCommand(path,last,pt,"G0"); + last = pt; + pt = next; + (pt.*setter)(retraction); + addCommand(path,last,pt,"G0"); + if(clearance>Precision::Confusion() && + clearance+(next.*getter)() < retraction) + { + last = pt; + pt = next; + (pt.*setter)((next.*getter)()+clearance); + addCommand(path,last,pt,"G0"); + addCommand(path,pt,next); + }else + addCommand(path,pt,next,"G0"); } static void addCommand(Toolpath &path, @@ -1724,12 +1817,18 @@ static void addCommand(Toolpath &path, { Command cmd; cmd.Name = clockwise?"G2":"G3"; - cmd.Parameters["I"] = center.X()-pstart.X(); - cmd.Parameters["J"] = center.Y()-pstart.Y(); - cmd.Parameters["K"] = center.Z()-pstart.Z(); - cmd.Parameters["X"] = pend.X(); - cmd.Parameters["Y"] = pend.Y(); - cmd.Parameters["Z"] = pend.Z(); + addParameter(cmd,"I",pstart.X(),center.X(),true); + addParameter(cmd,"J",pstart.Y(),center.Y(),true); + addParameter(cmd,"K",pstart.Z(),center.Z(),true); + addParameter(cmd,"X",pstart.X(),pend.X()); + addParameter(cmd,"Y",pstart.Y(),pend.Y()); + addParameter(cmd,"Z",pstart.Z(),pend.Z()); + path.addCommand(cmd); +} + +static inline void addCommand(Toolpath &path, const char *name) { + Command cmd; + cmd.Name = name; path.addCommand(cmd); } @@ -1742,10 +1841,42 @@ void Area::toPath(Toolpath &path, const std::list &shapes, AreaParams params; if(_params) params =*_params; wires = sortWires(shapes,¶ms,pstart,pend, + PARAM_REF(PARAM_FARG,AREA_PARAMS_ARC_PLANE), PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + + // absolute mode + addCommand(path,"G90"); + + if(arc_plane==ArcPlaneZX) + addCommand(path,"G18"); + else if(arc_plane==ArcPlaneYZ) + addCommand(path,"G19"); + threshold = fabs(threshold); if(threshold < Precision::Confusion()) threshold = Precision::Confusion(); + clearance = fabs(clearance); + + AxisGetter getter = NULL; + AxisSetter setter = NULL; + retraction = fabs(retraction); + if(retraction>Precision::Confusion()) { + switch(retract_axis) { + case RetractAxisX: + getter = &gp_Pnt::X; + setter = &gp_Pnt::SetX; + break; + case RetractAxisY: + getter = &gp_Pnt::Y; + setter = &gp_Pnt::SetY; + break; + case RetractAxisZ: + getter = &gp_Pnt::Z; + setter = &gp_Pnt::SetZ; + break; + } + } + gp_Pnt plast,p; if(pstart) plast = *pstart; bool first = true; @@ -1753,9 +1884,9 @@ void Area::toPath(Toolpath &path, const std::list &shapes, BRepTools_WireExplorer xp(TopoDS::Wire(wire)); p = BRep_Tool::Pnt(xp.CurrentVertex()); if(first||p.Distance(plast)>threshold) - addCommand(path,p,true,height,clearance); + addCommand(path,plast,p,getter,setter,retraction,clearance); else - addCommand(path,p); + addCommand(path,plast,p); plast = p; first = false; for(;xp.More();xp.Next(),plast=p) { @@ -1773,27 +1904,33 @@ void Area::toPath(Toolpath &path, const std::list &shapes, if(reversed) { for (int i=nbPoints-1; i>=1; --i) { gp_Pnt pt = curve.Value(discretizer.Parameter(i)); - addCommand(path,pt); + addCommand(path,plast,pt); + plast = pt; } }else{ for (int i=2; i<=nbPoints; i++) { gp_Pnt pt = curve.Value(discretizer.Parameter(i)); - addCommand(path,pt); + addCommand(path,plast,pt); + plast = pt; } } break; } } - addCommand(path,p); + addCommand(path,plast,p); break; } case GeomAbs_Circle:{ double first = curve.FirstParameter(); double last = curve.LastParameter(); - gp_Circ circle = curve.Circle(); - gp_Ax1 axis = circle.Axis(); - bool clockwise = axis.Direction().Z()<0; + const gp_Circ &circle = curve.Circle(); + // Get the normal vector after trasform the circle to the XY0 + // plane, in order to judge if the circle is facing upward or + // downward + const gp_Ax1 &axis = circle.Axis(); + const gp_Dir &dir = axis.Direction().Transformed(curve.Trsf().Inverted()); + bool clockwise = dir.Z()<0; if(reversed) clockwise = !clockwise; - gp_Pnt center = axis.Location(); + gp_Pnt center = circle.Location(); if(segmentation > Precision::Confusion()) { GCPnts_UniformAbscissa discretizer(curve, segmentation, curve.FirstParameter(), curve.LastParameter()); @@ -1832,12 +1969,14 @@ void Area::toPath(Toolpath &path, const std::list &shapes, if(reversed) { for (int i=nbPoints-1; i>=1; --i) { gp_Pnt pt = discretizer.Value (i); - addCommand(path,pt); + addCommand(path,plast,pt); + plast = pt; } }else{ for (int i=2; i<=nbPoints; i++) { gp_Pnt pt = discretizer.Value (i); - addCommand(path,pt); + addCommand(path,plast,pt); + plast = pt; } } }else diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index 79c843b326..cfc89c6f16 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -440,6 +440,8 @@ public: * used for sorting * \arg \c pstart: optional start point * \arg \c pend: optional output containing the ending point of the returned + * \arg \c arc_plane: optional arc plane selection, if given the found plane + * will be returned. See #AREA_PARAMS_ARC_PLANE for more details. * * See #AREA_PARAMS_SORT for other arguments * @@ -447,7 +449,8 @@ public: */ static std::list sortWires(const std::list &shapes, const AreaParams *params = NULL, const gp_Pnt *pstart=NULL, - gp_Pnt *pend=NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT)); + gp_Pnt *pend=NULL, short *arc_plane = NULL, + PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT)); /** Convert a list of wires to gcode * diff --git a/src/Mod/Path/App/AreaParams.h b/src/Mod/Path/App/AreaParams.h index 161fffbb26..3e5fb87b99 100644 --- a/src/Mod/Path/App/AreaParams.h +++ b/src/Mod/Path/App/AreaParams.h @@ -173,6 +173,15 @@ "minimum distance for the generated new wires. Wires maybe broken if the\n"\ "algorithm see fits. Set to zero to disable wire breaking.",App::PropertyLength)) +/** Arc plane */ +#define AREA_PARAMS_ARC_PLANE \ + ((enum, arc_plane, ArcPlane, 1, "Arc drawing plane, corresponding to G17, G18, and G19.\n"\ + "If not 'None', the output wires will be transformed to align with the selected plane,\n"\ + "and the corresponding GCode will be inserted.\n"\ + "'Auto' means the plane is determined by the first encountered arc plane. If the found\n"\ + "plane does not align to any GCode plane, XY plane is used.",\ + (None)(Auto)(XY)(ZX)(YZ))) + /** Area wire sorting parameters */ #define AREA_PARAMS_SORT \ ((enum, sort_mode, SortMode, 1, "Wire sorting mode to optimize travel distance.\n"\ @@ -185,12 +194,15 @@ /** Area path generation parameters */ #define AREA_PARAMS_PATH \ + AREA_PARAMS_ARC_PLANE \ AREA_PARAMS_SORT \ ((double, threshold, RetractThreshold, 0.0,\ "If two wire's end points are separated within this threshold, they are consider\n"\ "as connected. You may want to set this to the tool diameter to keep the tool down.",\ App::PropertyLength))\ - ((double, height, RetractHeight, 0.0,"Tool retraction absolute height",App::PropertyLength))\ + ((double, retraction, Retraction, 0.0,"Tool retraction absolute coordinate along retraction axis",\ + App::PropertyLength))\ + ((enum, retract_axis, RetractAxis, 2,"Tool retraction axis",(X)(Y)(Z)))\ ((double, clearance, Clearance, 0.0,\ "When return from last retraction, this gives the pause height relative to the Z\n"\ "value of the next move",App::PropertyLength))\