From 00b0027e190a740bda53c297575710f44c1a2df4 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 4 Jul 2017 04:59:29 +0800 Subject: [PATCH 1/9] Path.Area: handle empty wires without vertex --- src/Mod/Path/App/Area.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 9068e1f751..d8375abba0 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -285,6 +285,11 @@ void Area::addWire(CArea &area, const TopoDS_Wire& wire, BRepTools_WireExplorer xp(trsf?TopoDS::Wire( wire.Moved(TopLoc_Location(*trsf))):wire); + if(!xp.More()) { + AREA_TRACE("empty wire"); + return; + } + gp_Pnt p = BRep_Tool::Pnt(xp.CurrentVertex()); ccurve.append(CVertex(Point(p.X(),p.Y()))); From 4b2a739e7a3873453178ae7b5c5ddf19615fca3c Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Tue, 4 Jul 2017 10:37:12 +0800 Subject: [PATCH 2/9] Path.Area: change fromShape() 'start' parameter behavior 'start' used to mean the initial resting position of the tool. Now it is changed to mean the feed start position. fromShape() has also improved to automatically guess 'retraction' and 'resume_height' parameters if not given, based on input shape boundary. --- src/Mod/Path/App/AppPathPy.cpp | 4 +- src/Mod/Path/App/Area.cpp | 140 +++++++++++++++++++------- src/Mod/Path/App/Area.h | 6 +- src/Mod/Path/App/FeaturePathShape.cpp | 2 +- 4 files changed, 111 insertions(+), 41 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 2c4832bfa6..a42721d3c5 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -122,7 +122,7 @@ public: "fromShapes(shapes, start=Vector(), return_end=False" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n" "\nReturns a Path object from a list of shapes\n" "\n* shapes: input list of shapes.\n" - "\n* start (Vector()): optional start position.\n" + "\n* start (Vector()): feed start position, and also serves as a hint of path entry.\n" "\n* return_end (False): if True, returns tuple (path, endPosition).\n" PARAM_PY_DOC(ARG, AREA_PARAMS_PATH) ); @@ -415,7 +415,7 @@ private: try { bool need_arc_plane = arc_plane==Area::ArcPlaneAuto; std::list wires = Area::sortWires(shapes,&pstart, - &pend, &arc_plane, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); + &pend, 0, &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( diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index d8375abba0..b3f242cfac 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -2435,11 +2435,13 @@ struct ShapeInfo{ return myBestWire->wire; } - std::list sortWires(const gp_Pnt &pstart, gp_Pnt &pend,double min_dist) { + std::list sortWires(const gp_Pnt &pstart, gp_Pnt &pend,double min_dist, gp_Pnt *pentry) { if(pstart.SquareDistance(myStartPt)>Precision::SquareConfusion()) nearest(pstart); + if(pentry) *pentry = myBestPt; + std::list wires; if(min_dist < 0.01) min_dist = 0.01; @@ -2607,7 +2609,7 @@ struct WireOrienter { }; std::list Area::sortWires(const std::list &shapes, - const gp_Pnt *_pstart, gp_Pnt *_pend, short *_parc_plane, + gp_Pnt *_pstart, gp_Pnt *_pend, double *stepdown_hint, short *_parc_plane, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT)) { std::list wires; @@ -2732,7 +2734,12 @@ std::list Area::sortWires(const std::list &shapes, AREA_TRACE("bound (" << xMin<<", "< Area::sortWires(const std::list &shapes, best_d = d; } } - wires.splice(wires.end(),best_it->sortWires(pstart,pend,min_dist)); + gp_Pnt pentry; + wires.splice(wires.end(),best_it->sortWires(pstart,pend,min_dist,&pentry)); + if(use_bound && _pstart) { + use_bound = false; + *_pstart = pentry; + } + if(sort_mode==SortMode2D5 && stepdown_hint) { + if(!best_it->myPlanar) + hint_first = true; + else if(hint_first) + hint_first = false; + else{ + // Calculate distance of two gp_pln. + // + // Can't use gp_pln.Distance(), because it only calculate + // the distance if two plane are parallel. And it checks + // parallelity using tolerance gp::Resolution() which is + // defined as DBL_MIN (min double) in Standard_Real.hxx. + // Really? Is that a bug? + const gp_Pnt& P = pln.Position().Location(); + const gp_Pnt& loc = best_it->myPln.Position().Location (); + const gp_Dir& dir = best_it->myPln.Position().Direction(); + double d = (dir.X() * (P.X() - loc.X()) + + dir.Y() * (P.Y() - loc.Y()) + + dir.Z() * (P.Z() - loc.Z())); + if (d < 0) d = -d; + if(d>hint) + hint = d; + } + pln = best_it->myPln; + } pstart = pend; shape_list.erase(best_it); } + if(stepdown_hint && hint!=0.0) + *stepdown_hint = hint; if(_pend) *_pend = pend; FC_DURATION_LOG(rparams.bd,"rtree build"); FC_DURATION_LOG(rparams.qd,"rtree query"); @@ -2805,24 +2844,22 @@ static void addG0(bool verbose, Toolpath &path, double retraction, double resume_height, double f, double &last_f) { - if(!getter || retraction-(last.*getter)() < Precision::Confusion()) { - addGCode(verbose,path,last,next,"G0"); - return; - } gp_Pnt pt(last); - (pt.*setter)(retraction); - addGCode(verbose,path,last,pt,"G0"); - last = pt; - pt = next; - (pt.*setter)(retraction); - addGCode(verbose,path,last,pt,"G0"); - if(resume_height>Precision::Confusion() && - resume_height+(next.*getter)() < retraction) - { + if(retraction-(last.*getter)() > Precision::Confusion()) { + (pt.*setter)(retraction); + addGCode(verbose,path,last,pt,"G0"); last = pt; pt = next; - (pt.*setter)((next.*getter)()+resume_height); + (pt.*setter)(retraction); addGCode(verbose,path,last,pt,"G0"); + } + if(resume_height>Precision::Confusion()) { + if(resume_height+(next.*getter)() < retraction) { + last = pt; + pt = next; + (pt.*setter)((next.*getter)()+resume_height); + addGCode(verbose,path,last,pt,"G0"); + } addG1(verbose,path,pt,next,f,last_f); }else addGCode(verbose,path,pt,next,"G0"); @@ -2880,11 +2917,15 @@ void Area::setWireOrientation(TopoDS_Wire &wire, const gp_Dir &dir, bool wire_cc } void Area::toPath(Toolpath &path, const std::list &shapes, - const gp_Pnt *pstart, gp_Pnt *pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) + const gp_Pnt *_pstart, gp_Pnt *pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH)) { std::list wires; - wires = sortWires(shapes,pstart,pend, + gp_Pnt pstart; + if(_pstart) pstart = *_pstart; + + double stepdown_hint = 1.0; + wires = sortWires(shapes,&pstart,pend,&stepdown_hint, PARAM_REF(PARAM_FARG,AREA_PARAMS_ARC_PLANE), PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT)); @@ -2903,30 +2944,57 @@ void Area::toPath(Toolpath &path, const std::list &shapes, addGCode(path,"G17"); } + AxisGetter getter; + AxisSetter setter; + 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; + default: + getter = &gp_Pnt::Z; + setter = &gp_Pnt::SetZ; + } + threshold = fabs(threshold); if(threshold < Precision::Confusion()) threshold = Precision::Confusion(); threshold *= threshold; - resume_height = fabs(resume_height); - AxisGetter getter = &gp_Pnt::Z; - AxisSetter setter = &gp_Pnt::SetZ; + resume_height = fabs(resume_height); + if(resume_height < Precision::Confusion()) + resume_height = stepdown_hint; + 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; - } - } + if(retraction < Precision::Confusion()) + retraction = (pstart.*getter)()+resume_height; + + // in case the user didn't specify feed start, sortWire() will choose one + // based on the bound. We'll further adjust that according to resume height + if(!_pstart || pstart.SquareDistance(*_pstart)>Precision::SquareConfusion()) + (pstart.*setter)((pstart.*getter)()+resume_height); gp_Pnt plast,p; - if(pstart) plast = *pstart; + // initial vertial rapid pull up to retraction (or start Z height if higher) + (p.*setter)(std::max(retraction,(pstart.*getter)())); + addGCode(false,path,plast,p,"G0"); + plast = p; + p = pstart; + // rapid horizontal move if start Z is below retraction + if(fabs((p.*getter)()-retraction) > Precision::Confusion()) { + (p.*setter)(retraction); + addGCode(false,path,plast,p,"G0"); + plast = p; + p = pstart; + } + // vertial rapid down to feed start + addGCode(false,path,plast,p,"G0"); + + plast = p; bool first = true; bool arcWarned = false; double cur_f = 0.0; // current feed rate @@ -2947,7 +3015,7 @@ void Area::toPath(Toolpath &path, const std::list &shapes, (pTmp.*setter)(0.0); (plastTmp.*setter)(0.0); - if(first||pTmp.SquareDistance(plastTmp)>threshold) + if(!first && pTmp.SquareDistance(plastTmp)>threshold) addG0(verbose,path,plast,p,getter,setter,retraction,resume_height,vf,cur_f); else addG1(verbose,path,plast,p,vf,cur_f); diff --git a/src/Mod/Path/App/Area.h b/src/Mod/Path/App/Area.h index e2a88aa296..9d4d10eb01 100644 --- a/src/Mod/Path/App/Area.h +++ b/src/Mod/Path/App/Area.h @@ -341,6 +341,8 @@ public: * \arg \c shapes: input list of shapes. * \arg \c pstart: optional start point * \arg \c pend: optional output containing the ending point of the returned + * \arg \c stepdown_hint: optional output of a hint of step down as the max + * distance between two sections. * \arg \c arc_plane: optional arc plane selection, if given the found plane * will be returned. See #AREA_PARAMS_ARC_PLANE for more details. * @@ -349,8 +351,8 @@ public: * \return sorted wires */ static std::list sortWires(const std::list &shapes, - const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, short *arc_plane = NULL, - PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT)); + gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, double *stepdown_hint=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/FeaturePathShape.cpp b/src/Mod/Path/App/FeaturePathShape.cpp index 78e720c295..5d2c79ffad 100644 --- a/src/Mod/Path/App/FeaturePathShape.cpp +++ b/src/Mod/Path/App/FeaturePathShape.cpp @@ -55,7 +55,7 @@ PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_PATH) FeatureShape::FeatureShape() { ADD_PROPERTY(Sources,(0)); - ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position"); + ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Feed start position"); PARAM_PROP_ADD("Path",AREA_PARAMS_PATH); PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_PATH); } From 65c3792cc2c76a564813b257aee832eefd918d76 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Wed, 5 Jul 2017 16:36:35 +0800 Subject: [PATCH 3/9] Path.Area: fix sortWires sortWires was broken on open wires. The bug was introduced when open wire direction feature is added. --- src/Mod/Path/App/Area.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index b3f242cfac..104c62b953 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -2065,13 +2065,14 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf, in struct WireInfo { TopoDS_Wire wire; std::deque points; + gp_Pnt pt_end; bool isClosed; inline const gp_Pnt &pstart() const{ return points.front(); } inline const gp_Pnt &pend() const{ - return isClosed?pstart():points.back(); + return isClosed?pstart():pt_end; } }; @@ -2161,8 +2162,9 @@ struct GetWires { // We don't add in-between vertices of an open wire, because we // haven't implemented open wire breaking yet. info.points.push_back(p1); - if(params.direction!=Area::DirectionNone) + if(!info.isClosed && params.direction==Area::DirectionNone) info.points.push_back(p2); + info.pt_end = p2; } else { // For closed wires, we are can easily rebase the wire, so we // discretize the wires to spatial index it in order to accelerate @@ -2193,7 +2195,7 @@ struct GetWires { } auto it = wires.end(); --it; - for(size_t i=0,count=info.points.size();ipoints.size();ipstart()); - if(myParams.direction==Area::DirectionNone) { + if(myParams.direction!=Area::DirectionNone) { d = d1; p = it->pstart(); is_start = true; @@ -2437,7 +2439,8 @@ struct ShapeInfo{ std::list sortWires(const gp_Pnt &pstart, gp_Pnt &pend,double min_dist, gp_Pnt *pentry) { - if(pstart.SquareDistance(myStartPt)>Precision::SquareConfusion()) + if(myWires.empty() || + pstart.SquareDistance(myStartPt)>Precision::SquareConfusion()) nearest(pstart); if(pentry) *pentry = myBestPt; From c2abbe354cc8dce44191849af47c3d7e7de1c375 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Tue, 4 Jul 2017 10:03:08 -0500 Subject: [PATCH 4/9] Path: MillFace start point --- src/Mod/Path/PathScripts/PathMillFace.py | 28 +++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index b871058c20..40371e0018 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -95,8 +95,9 @@ class ObjectFace: obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Debug Parameters - # obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) - # obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) if FreeCAD.GuiUp: _ViewProviderFace(obj.ViewObject) @@ -197,7 +198,7 @@ class ObjectFace: heights = [i for i in self.depthparams] boundary.setParams(**pocketparams) - #obj.AreaParams = str(boundary.getParams()) + obj.AreaParams = str(boundary.getParams()) #PathLog.track('areaparams: {}'.format(obj.AreaParams)) PathLog.track('height: {}'.format(heights)) sections = boundary.makeSections(mode=0, project=False, heights=heights) @@ -210,10 +211,18 @@ class ObjectFace: 'resume_height': obj.StepDown, 'retraction': obj.ClearanceHeight.Value} - PathLog.debug("Generating Path with params: {}".format(params)) - pp = Path.fromShapes(**params) + pp = [] + if obj.UseStartPoint: + params['start'] = obj.StartPoint +# pp.append(Path.Command("G0", {"X":obj.StartPoint.x, "Y":obj.StartPoint.y, "Z":obj.StartPoint.z})) - return pp + + PathLog.debug("Generating Path with params: {}".format(params)) + respath = Path.fromShapes(**params) + pp.extend(respath.Commands) + respath.Commands = pp + + return respath def execute(self, obj): PathLog.track() @@ -229,7 +238,7 @@ class ObjectFace: self.depthparams = depth_params( clearance_height=obj.ClearanceHeight.Value, safe_height=obj.SafeHeight.Value, - start_depth=obj.StartDepth.Value, + start_depth=obj.SafeHeight.Value, step_down=obj.StepDown, z_finish_step=obj.FinishDepth.Value, final_depth=obj.FinalDepth.Value, @@ -282,7 +291,7 @@ class ObjectFace: PathLog.info("Working on a shape {}".format(baseobject.Name)) # Let's start by rapid to clearance...just for safety - commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + #commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) # if user wants the boundbox, calculate that PathLog.info("Boundary Shape: {}".format(obj.BoundaryShape)) @@ -293,6 +302,9 @@ class ObjectFace: else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) + #save the envelope for reference + obj.removalshape = env + try: commandlist.extend(self._buildPathArea(obj, env).Commands) except Exception as e: From f444f96c7d430fb30a23fb9c2f38c254a6890e92 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 5 Jul 2017 14:58:03 -0500 Subject: [PATCH 5/9] re-arrange TestPathPost setup to simplify testing muliple posts --- src/Mod/Path/PathScripts/PathContour.py | 20 +++++++++---- src/Mod/Path/PathScripts/PathMillFace.py | 33 +++++++++++++-------- src/Mod/Path/PathScripts/PathPocket.py | 37 +++++++++++++++--------- src/Mod/Path/PathTests/TestPathPost.py | 25 ++++++++-------- 4 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py index e7cd85783a..cbc3858b9d 100644 --- a/src/Mod/Path/PathScripts/PathContour.py +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -85,8 +85,12 @@ class ObjectContour: obj.addProperty("App::PropertyDistance", "OffsetExtra", "Contour", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final Contour- good for roughing toolpath")) # Debug Parameters - # obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) - # obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.setEditorMode('PathParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: _ViewProviderContour(obj.ViewObject) @@ -96,7 +100,8 @@ class ObjectContour: def onChanged(self, obj, prop): PathLog.track('prop: {} state: {}'.format(prop, obj.State)) - #pass + if prop in ['AreaParams', 'PathParams', 'removalshape']: + obj.setEditorMode(prop, 2) def __getstate__(self): PathLog.track() @@ -145,7 +150,7 @@ class ObjectContour: heights = [i for i in self.depthparams] PathLog.debug('depths: {}'.format(heights)) profile.setParams(**profileparams) - #obj.AreaParams = str(profile.getParams()) + obj.AreaParams = str(profile.getParams()) PathLog.debug("Contour with params: {}".format(profile.getParams())) sections = profile.makeSections(mode=0, project=True, heights=heights) @@ -169,8 +174,9 @@ class ObjectContour: elif start is not None: params['start'] = start + obj.PathParams = str(params) + (pp, end_vector) = Path.fromShapes(**params) - PathLog.debug("Generating Path with params: {}".format(params)) PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) self.endVector = end_vector @@ -207,6 +213,7 @@ class ObjectContour: user_depths=None) if toolLoad is None or toolLoad.ToolNumber == 0: + FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.") return else: @@ -229,6 +236,7 @@ class ObjectContour: commandlist.append(Path.Command("(Uncompensated Tool Path)")) parentJob = PathUtils.findParentJob(obj) + if parentJob is None: return baseobject = parentJob.Base @@ -237,6 +245,7 @@ class ObjectContour: # Let's always start by rapid to clearance...just for safety commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + PathLog.track() isPanel = False if hasattr(baseobject, "Proxy"): @@ -268,6 +277,7 @@ class ObjectContour: # Let's finish by rapid to clearance...just for safety commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + PathLog.track() path = Path.Path(commandlist) obj.Path = path #obj.ViewObject.Visibility = True diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 40371e0018..610d1696a5 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -95,9 +95,12 @@ class ObjectFace: obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("Part::PropertyPartShape", "removalshape", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.setEditorMode('PathParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: _ViewProviderFace(obj.ViewObject) @@ -109,6 +112,8 @@ class ObjectFace: if prop == "StepOver": if obj.StepOver == 0: obj.StepOver = 1 + if prop in ['AreaParams', 'PathParams', 'removalshape']: + obj.setEditorMode(prop, 2) def __getstate__(self): return None @@ -199,28 +204,32 @@ class ObjectFace: heights = [i for i in self.depthparams] boundary.setParams(**pocketparams) obj.AreaParams = str(boundary.getParams()) - #PathLog.track('areaparams: {}'.format(obj.AreaParams)) - PathLog.track('height: {}'.format(heights)) sections = boundary.makeSections(mode=0, project=False, heights=heights) - shapelist = [sec.getShape() for sec in sections] - params = {'shapes': shapelist, - 'feedrate': self.horizFeed, + params = {'feedrate': self.horizFeed, 'feedrate_v': self.vertFeed, 'verbose': True, 'resume_height': obj.StepDown, 'retraction': obj.ClearanceHeight.Value} pp = [] + if obj.UseStartPoint: params['start'] = obj.StartPoint # pp.append(Path.Command("G0", {"X":obj.StartPoint.x, "Y":obj.StartPoint.y, "Z":obj.StartPoint.z})) - + #store the params for debugging. Don't need the shape. + obj.PathParams = str(params) PathLog.debug("Generating Path with params: {}".format(params)) - respath = Path.fromShapes(**params) - pp.extend(respath.Commands) - respath.Commands = pp + + for sec in sections: + shape = sec.getShape() + respath = Path.fromShapes(shape, **params) + #Insert any entry code to the layer + + #append the layer path + pp.extend(respath.Commands) + respath.Commands = pp return respath @@ -380,7 +389,7 @@ class CommandPathMillFace: FreeCADGui.doCommand('obj.Active = True') FreeCADGui.doCommand('obj.StepOver = 50') - #FreeCADGui.doCommand('obj.StepDown = 1.0') + FreeCADGui.doCommand('obj.StepDown = 1.0') FreeCADGui.doCommand('obj.ZigZagAngle = 45.0') FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 1926bf9f4d..82cb7ac141 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -77,21 +77,28 @@ class ObjectPocket: obj.addProperty("App::PropertyFloat", "ZigZagAngle", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern")) obj.addProperty("App::PropertyEnumeration", "OffsetPattern", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "clearing pattern to use")) obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle'] + obj.addProperty("App::PropertyBool", "MinTravel", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path")) # Start Point Properties obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")) obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.setEditorMode('PathParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: ViewProviderPocket(obj.ViewObject) obj.Proxy = self def onChanged(self, obj, prop): - pass + if prop in ['AreaParams', 'PathParams', 'removalshape']: + obj.setEditorMode(prop, 2) + def __getstate__(self): return None @@ -211,6 +218,17 @@ class ObjectPocket: 'resume_height': obj.StepDown.Value, 'retraction': obj.ClearanceHeight.Value} + if obj.UseStartPoint: + params['start'] = obj.StartPoint + + #if MinTravel is turned on, set path sorting to 3DSort + # 3DSort shouldn't be used without a valid start point. Can cause + # tool crash without it. + if obj.MinTravel: + params['sort_mode'] = 2 + + storeparams = {key: value for key, value in params.items() if key != 'shapes'} + obj.PathParams = str(storeparams) pp = Path.fromShapes(**params) PathLog.debug("Generating Path with params: {}".format(params)) PathLog.debug(pp) @@ -282,14 +300,10 @@ class ObjectPocket: shape = Part.makeFace(edges, 'Part::FaceMakerSimple') env = PathUtils.getEnvelope(baseobject.Shape, subshape=shape, depthparams=self.depthparams) - removal = env.cut(baseobject.Shape) - - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: - removalshape=FreeCAD.ActiveDocument.addObject("Part::Feature","removalshape") - removalshape.Shape = removal + obj.removalshape = env.cut(baseobject.Shape) try: - (pp, sim) = self._buildPathArea(obj, removal, getsim=getsim) + (pp, sim) = self._buildPathArea(obj, obj.removalshape, getsim=getsim) if sim is not None: simlist.append(sim) commandlist.extend(pp.Commands) @@ -300,12 +314,9 @@ class ObjectPocket: PathLog.debug("processing the whole job base object") env = PathUtils.getEnvelope(baseobject.Shape, subshape=None, depthparams=self.depthparams) - removal = env.cut(baseobject.Shape) - if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: - removalshape=FreeCAD.ActiveDocument.addObject("Part::Feature","removalshape") - removalshape.Shape = removal + obj.removalshape = env.cut(baseobject.Shape) try: - (pp, sim) = self._buildPathArea(obj, removal, getsim=getsim) + (pp, sim) = self._buildPathArea(obj, obj.removalshape, getsim=getsim) commandlist.extend(pp.Commands) if sim is not None: simlist.append(sim) diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 7327ce0449..6129a50ca4 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -34,20 +34,15 @@ import difflib import unittest class PathPostTestCases(unittest.TestCase): + def setUp(self): self.doc = FreeCAD.newDocument("PathPostTest") - - def tearDown(self): - FreeCAD.closeDocument("PathPostTest") - - def testLinuxCNC(self): - # first create something to generate a path for box = self.doc.addObject("Part::Box", "Box") # Create job and setup tool library + default tool - job = self.doc.addObject("Path::FeatureCompoundPython", "Job") - PathScripts.PathJob.ObjectPathJob(job, box, None) - PathScripts.PathToolController.CommandPathToolController.Create(job.Name, False) + self.job = self.doc.addObject("Path::FeatureCompoundPython", "Job") + PathScripts.PathJob.ObjectPathJob(self.job, box, None) + PathScripts.PathToolController.CommandPathToolController.Create(self.job.Name, False) tool1 = Path.Tool() tool1.Diameter = 5.0 tool1.Name = "Default Tool" @@ -82,11 +77,17 @@ class PathPostTestCases(unittest.TestCase): PathScripts.PathContour.ObjectContour.setDepths(contour.Proxy, contour) self.doc.recompute() - job.PostProcessor = 'linuxcnc' - job.PostProcessorArgs = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2' + def tearDown(self): + FreeCAD.closeDocument("PathPostTest") + + def testLinuxCNC(self): + # first create something to generate a path for + + self.job.PostProcessor = 'linuxcnc' + self.job.PostProcessorArgs = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2' post = PathScripts.PathPost.CommandPathPost() - (fail, gcode) = post.exportObjectsWith([job], job, False) + (fail, gcode) = post.exportObjectsWith([self.job], self.job, False) self.assertFalse(fail) referenceFile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_linuxcnc_00.ngc' From 158a21b58dca4f302db1634e351037363e2c43cb Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 5 Jul 2017 14:58:18 -0500 Subject: [PATCH 6/9] Path: Centroid post re-write --- .../Path/PathScripts/post/centroid_post.py | 414 +++++++++++++----- 1 file changed, 305 insertions(+), 109 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/centroid_post.py b/src/Mod/Path/PathScripts/post/centroid_post.py index 2977f9bc89..5c46077711 100644 --- a/src/Mod/Path/PathScripts/post/centroid_post.py +++ b/src/Mod/Path/PathScripts/post/centroid_post.py @@ -1,142 +1,338 @@ -# -*- coding: utf-8 -*- - -#*************************************************************************** -#* * -#* Copyright (c) 2015 Dan Falck * -#* * -#* This program is free software; you can redistribute it and/or modify * -#* it under the terms of the GNU Lesser General Public License (LGPL) * -#* as published by the Free Software Foundation; either version 2 of * -#* the License, or (at your option) any later version. * -#* for detail see the LICENCE text file. * -#* * -#* This program 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 program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2015 Dan Falck * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD 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 Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# ***************************************************************************/ from __future__ import print_function -TOOLTIP=''' example post for Centroid CNC mill''' +TOOLTIP=''' +This is a postprocessor file for the Path workbench. It is used to +take a pseudo-gcode fragment outputted by a Path object, and output +real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed +in the appropriate PathScripts folder, can be used directly from inside +FreeCAD, via the GUI importer or via python scripts with: + +import centroid_post +centroid_post.export(object,"/path/to/file.ncc","") +''' + +TOOLTIP_ARGS=''' +Arguments for centroid: + --header,--no-header ... output headers (--header) + --comments,--no-comments ... output comments (--comments) + --line-numbers,--no-line-numbers ... prefix with line numbers (--no-lin-numbers) + --show-editor, --no-show-editor ... pop up editor before writing output(--show-editor) + --feed-precision=1 ... number of digits of precision for feed rate. Default=1 + --axis-precision=4 ... number of digits of precision for axis moves. Default=4 +''' import FreeCAD +from FreeCAD import Units import datetime -now = datetime.datetime.now() +import PathScripts from PathScripts import PostUtils +#from PathScripts import PathUtils +now = datetime.datetime.now() -#*************************************************************************** -# user editable stuff here +# These globals set common customization preferences +OUTPUT_COMMENTS = True +OUTPUT_HEADER = True +OUTPUT_LINE_NUMBERS = False +if FreeCAD.GuiUp: + SHOW_EDITOR = True +else: + SHOW_EDITOR = False +MODAL = False # if true commands are suppressed if the same as previous line. -UNITS = "G20" #old style inch units for this shop -MACHINE_NAME = "BigMill" +COMMAND_SPACE = " " +LINENR = 100 # line number starting value + +# These globals will be reflected in the Machine configuration of the project +UNITS = "G20" # G21 for metric, G20 for us standard +UNIT_FORMAT = 'mm/min' +MACHINE_NAME = "Centroid" CORNER_MIN = {'x':-609.6, 'y':-152.4, 'z':0 } #use metric for internal units CORNER_MAX = {'x':609.6, 'y':152.4, 'z':304.8 } #use metric for internal units - -SHOW_EDITOR = True -MODAL = True -COMMENT= ';' #centroid control comment symbol - -HEADER = "" -HEADER += ";Exported by FreeCAD\n" -HEADER += ";Post Processor: " + __name__ +"\n" -HEADER += ";CAM file: %s\n" -HEADER += ";Output Time:"+str(now)+"\n" - -TOOLRETURN = '''M5 M25 -G49 H0\n''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle) - -ZAXISRETURN = '''G91 G28 X0 Z0 -G90\n''' - -SAFETYBLOCK = 'G90 G80 G40 G49\n' - -AXIS_DECIMALS = 4 -FEED_DECIMALS = 1 +AXIS_PRECISION=4 +FEED_PRECISION=1 SPINDLE_DECIMALS = 0 -FOOTER = 'M99'+'\n' +COMMENT = ";" -# don't edit with the stuff below the next line unless you know what you're doing :) -#*************************************************************************** +HEADER = ''' +;Exported by FreeCAD +;Post Processor: {} +;CAM file: {} +;Output Time: {} +'''.format(__name__, FreeCAD.ActiveDocument.FileName, str(now)) + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = '''G53 G00 G17 +''' + +# Postamble text will appear following the last operation. +POSTAMBLE = '''M99 +''' + +TOOLRETURN = '''M5 M25 +G49 H0 +''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle) + +ZAXISRETURN = '''G91 G28 X0 Z0 +G90 +''' + +SAFETYBLOCK = '''G90 G80 G40 G49 +''' + +# Pre operation text will be inserted before every operation +PRE_OPERATION = '''''' + +# Post operation text will be inserted after every operation +POST_OPERATION = '''''' + +# Tool Change commands will be inserted before a tool change +TOOL_CHANGE = '''''' +# to distinguish python built-in open function from the one declared below if open.__module__ == '__builtin__': pythonopen = open -def export(selection,filename,argstring): - params = ['X','Y','Z','A','B','I','J','F','H','S','T','Q','R','L'] #Using XY plane most of the time so skipping K - for obj in selection: - if not hasattr(obj,"Path"): - print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") - return - myMachine = None - for pathobj in selection: - if hasattr(pathobj,"MachineName"): - myMachine = pathobj.MachineName - if hasattr(pathobj, "MachineUnits"): - if pathobj.MachineUnits == "Metric": - UNITS = "G21" - else: - UNITS = "G20" - if myMachine is None: - print("No machine found in this selection") +def processArguments(argstring): + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global AXIS_PRECISION + global FEED_PRECISION - gcode ='' - gcode+= HEADER % (FreeCAD.ActiveDocument.FileName) - gcode+= SAFETYBLOCK - gcode+= UNITS+'\n' + for arg in argstring.split(): + if arg == '--header': + OUTPUT_HEADER = True + elif arg == '--no-header': + OUTPUT_HEADER = False + elif arg == '--comments': + OUTPUT_COMMENTS = True + elif arg == '--no-comments': + OUTPUT_COMMENTS = False + elif arg == '--line-numbers': + OUTPUT_LINE_NUMBERS = True + elif arg == '--no-line-numbers': + OUTPUT_LINE_NUMBERS = False + elif arg == '--show-editor': + SHOW_EDITOR = True + elif arg == '--no-show-editor': + SHOW_EDITOR = False + elif arg.split('=')[0] == '--axis-precision': + AXIS_PRECISION = arg.split('=')[1] + elif arg.split('=')[0] == '--feed-precision': + FEED_PRECISION = arg.split('=')[1] +def export(objectslist, filename, argstring): + processArguments(argstring) + for i in objectslist: + print (i.Name) + global UNITS + global UNIT_FORMAT + + # ISJOB = (len(objectslist) == 1) and isinstance(objectslist[0].Proxy, PathScripts.PathJob.ObjectPathJob) + # print("isjob: {} {}".format(ISJOB, len(objectslist))) + + # if len(objectslist) > 1: + # for obj in objectslist: + # if not hasattr(obj, "Path"): + # print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") + # return + + print("postprocessing...") + gcode = "" + + # write header + if OUTPUT_HEADER: + gcode += HEADER + + gcode += SAFETYBLOCK + + # Write the preamble + if OUTPUT_COMMENTS: + for item in objectslist: + if isinstance (item.Proxy, PathScripts.PathToolController.ToolController): + gcode += ";T{}={}\n".format(item.ToolNumber, item.Name) + gcode += linenumber() + ";begin preamble\n" + for line in PREAMBLE.splitlines(True): + gcode += linenumber() + line + + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + #skip postprocessing tools + # if isinstance (obj.Proxy, PathScripts.PathToolController.ToolController): + # continue + + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += linenumber() + ";begin operation\n" + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + ";end operation: %s\n" % obj.Label + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # do the post_amble + + if OUTPUT_COMMENTS: + gcode += ";begin postamble\n" + for line in TOOLRETURN.splitlines(True): + gcode += linenumber() + line + for line in SAFETYBLOCK.splitlines(True): + gcode += linenumber() + line + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + + if SHOW_EDITOR: + dia = PostUtils.GCodeEditorDialog() + dia.editor.setText(gcode) + result = dia.exec_() + if result: + final = dia.editor.toPlainText() + else: + final = gcode + else: + final = gcode + + print("done postprocessing.") + + if not filename == '-': + gfile = pythonopen(filename, "wb") + gfile.write(final) + gfile.close() + + return final + + +def linenumber(): + global LINENR + if OUTPUT_LINE_NUMBERS is True: + LINENR += 10 + return "N" + str(LINENR) + " " + return "" + +def parse(pathobj): + global AXIS_PRECISION + global FEED_PRECISION + out = "" lastcommand = None - gcode+= COMMENT+ selection[0].Description +'\n' + axis_precision_string = '.' + str(AXIS_PRECISION) +'f' + feed_precision_string = '.' + str(FEED_PRECISION) +'f' + # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control + # the order of parameters + # centroid doesn't want K properties on XY plane Arcs need work. + params = ['X', 'Y', 'Z', 'A', 'B', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H'] - gobjects = [] - for g in selection[0].Group: - gobjects.append(g) + if hasattr(pathobj, "Group"): # We have a compound or project. + # if OUTPUT_COMMENTS: + # out += linenumber() + "(compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out + else: # parsing simple path - for obj in gobjects: - for c in obj.Path.Commands: - outstring = [] - command = c.Name + # groups might contain non-path things like stock. + if not hasattr(pathobj, "Path"): + return out + + # if OUTPUT_COMMENTS: + # out += linenumber() + "(" + pathobj.Label + ")\n" + + for c in pathobj.Path.Commands: + commandlist = [] #list of elements in the command, code and params. + command = c.Name #command M or G code or comment string if command[0]=='(': command = PostUtils.fcoms(command, COMMENT) - outstring.append(command) - if MODAL == True: + commandlist.append(command) + # if modal: only print the command if it is not the same as the + # last one + if MODAL is True: if command == lastcommand: - outstring.pop(0) - if c.Parameters >= 1: - for param in params: - if param in c.Parameters: - if param == 'F': - outstring.append(param + PostUtils.fmt(c.Parameters['F'], FEED_DECIMALS,UNITS)) - elif param == 'H': - outstring.append(param + str(int(c.Parameters['H']))) - elif param == 'S': - outstring.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS,'G21')) #rpm is unitless-therefore I had to 'fake it out' by using metric units which don't get converted from entered value - elif param == 'T': - outstring.append(param + str(int(c.Parameters['T']))) - else: - outstring.append(param + PostUtils.fmt(c.Parameters[param],AXIS_DECIMALS,UNITS)) - outstr = str(outstring) + commandlist.pop(0) + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == 'F': + if c.Name not in ["G0", "G00"]: #centroid doesn't use rapid speeds + speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) + commandlist.append( + param + format(float(speed.getValueAs(UNIT_FORMAT)), feed_precision_string) ) + elif param == 'H': + commandlist.append(param + str(int(c.Parameters['H']))) + elif param == 'S': + commandlist.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS, "G21")) + elif param == 'T': + commandlist.append(param + str(int(c.Parameters['T']))) + else: + commandlist.append( + param + format(c.Parameters[param], axis_precision_string)) + outstr = str(commandlist) outstr =outstr.replace('[','') outstr =outstr.replace(']','') outstr =outstr.replace("'",'') outstr =outstr.replace(",",'') - gcode+= outstr + '\n' - lastcommand = c.Name - gcode+= TOOLRETURN - gcode+= SAFETYBLOCK - gcode+= FOOTER - if SHOW_EDITOR: - PostUtils.editor(gcode) - gfile = pythonopen(filename,"wb") - gfile.write(gcode) - gfile.close() + #out += outstr + '\n' + # store the latest command + lastcommand = command + + # Check for Tool Change: + if command == 'M6': + # if OUTPUT_COMMENTS: + # out += linenumber() + "(begin toolchange)\n" + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + # if command == "message": + # if OUTPUT_COMMENTS is False: + # out = [] + # else: + # commandlist.pop(0) # remove the command + + # prepend a line number and append a newline + if len(commandlist) >= 1: + if OUTPUT_LINE_NUMBERS: + commandlist.insert(0, (linenumber())) + + # append the line to the final output + for w in commandlist: + out += w + COMMAND_SPACE + out = out.strip() + "\n" + + return out + + +print(__name__ + " gcode postprocessor loaded.") From 1be9f3ac35e45af4c8a8fcfe0a3050f2d77cb577 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 6 Jul 2017 11:02:36 -0500 Subject: [PATCH 7/9] Path: fix bug in gcode importer. Wouldn't handle lines with only a line number --- src/Mod/Path/PathScripts/post/example_pre.py | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/PathScripts/post/example_pre.py index fd06fb32ed..87d4b38ea9 100644 --- a/src/Mod/Path/PathScripts/post/example_pre.py +++ b/src/Mod/Path/PathScripts/post/example_pre.py @@ -34,6 +34,14 @@ from GCode. import os import Path import FreeCAD +import PathScripts.PathLog as PathLog + +if True: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + # to distinguish python built-in open function from the one declared below if open.__module__ == '__builtin__': @@ -41,6 +49,7 @@ if open.__module__ == '__builtin__': def open(filename): + PathLog.track(filename) "called when freecad opens a file." docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) @@ -49,6 +58,7 @@ def open(filename): def insert(filename, docname): "called when freecad imports a file" + PathLog.track(filename) gfile = pythonopen(filename) gcode = gfile.read() gfile.close() @@ -61,12 +71,14 @@ def insert(filename, docname): def parse(inputstring): "parse(inputstring): returns a parsed output string" print("preprocessing...") - + print(inputstring) + PathLog.track(inputstring) # split the input by line lines = inputstring.split("\n") output = "" lastcommand = None - + print (lines) + for l in lines: # remove any leftover trailing and preceding spaces l = l.strip() @@ -75,8 +87,14 @@ def parse(inputstring): continue if l[0].upper() in ["N"]: # remove line numbers - l = l.split(" ",1)[1] - if l[0] in ["(","%","#"]: + l = l.split(" ",1) + if len(l)>=1: + l = l[1] + else: + continue + + + if l[0] in ["(","%","#",";"]: # discard comment and other non strictly gcode lines continue if l[0].upper() in ["G","M"]: @@ -92,7 +110,7 @@ def parse(inputstring): elif lastcommand: # no G or M command: we repeat the last one output += lastcommand + " " + l + "\n" - + print("done preprocessing.") return output From cb6a27f469160aeeadc578952a51f2fcb1aa696d Mon Sep 17 00:00:00 2001 From: sliptonic Date: Thu, 6 Jul 2017 11:11:37 -0500 Subject: [PATCH 8/9] Path: simplify the test fixture Instead of creating a file from scratch, a test file is included. Post processing the file from the gui should give the same results as the test script. --- src/Mod/Path/CMakeLists.txt | 2 + src/Mod/Path/PathTests/TestPathPost.py | 87 ++++++++------------ src/Mod/Path/PathTests/boxtest.fcstd | Bin 0 -> 7490 bytes src/Mod/Path/PathTests/test_centroid_00.ngc | 69 ++++++++++++++++ src/Mod/Path/PathTests/test_linuxcnc_00.ngc | 9 +- 5 files changed, 109 insertions(+), 58 deletions(-) create mode 100644 src/Mod/Path/PathTests/boxtest.fcstd create mode 100644 src/Mod/Path/PathTests/test_centroid_00.ngc diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index bd68251899..c00eeb4d30 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -84,7 +84,9 @@ SET(PathScripts_SRCS SET(PathTests_SRCS PathTests/__init__.py + PathTests/boxtest.fcstd PathTests/PathTestUtils.py + PathTests/test_centroid_00.ngc PathTests/test_linuxcnc_00.ngc PathTests/TestPathCore.py PathTests/TestPathDepthParams.py diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index 6129a50ca4..bb6dd4c674 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -23,72 +23,39 @@ # *************************************************************************** import FreeCAD -import Path import PathScripts import PathScripts.PathContour import PathScripts.PathJob import PathScripts.PathPost import PathScripts.PathToolController -import PathScripts.PathUtils +import PathScripts.PathUtil import difflib import unittest + class PathPostTestCases(unittest.TestCase): def setUp(self): - self.doc = FreeCAD.newDocument("PathPostTest") - box = self.doc.addObject("Part::Box", "Box") - - # Create job and setup tool library + default tool - self.job = self.doc.addObject("Path::FeatureCompoundPython", "Job") - PathScripts.PathJob.ObjectPathJob(self.job, box, None) - PathScripts.PathToolController.CommandPathToolController.Create(self.job.Name, False) - tool1 = Path.Tool() - tool1.Diameter = 5.0 - tool1.Name = "Default Tool" - tool1.CuttingEdgeHeight = 15.0 - tool1.ToolType = "EndMill" - tool1.Material = "HighSpeedSteel" - - tc = FreeCAD.ActiveDocument.addObject("Path::FeaturePython",'TC') - PathScripts.PathToolController.ToolController(tc) - PathScripts.PathUtils.addToJob(tc, "Job") - tc.Tool = tool1 - tc.ToolNumber = 2 - - self.failUnless(True) - - self.doc.getObject("TC").ToolNumber = 2 - self.doc.recompute() - - contour = self.doc.addObject("Path::FeaturePython", "Contour") - PathScripts.PathContour.ObjectContour(contour) - contour.Active = True - contour.ClearanceHeight = 20.0 - contour.StepDown = 1.0 - contour.StartDepth= 10.0 - contour.FinalDepth=0.0 - contour.SafeHeight = 12.0 - contour.OffsetExtra = 0.0 - contour.Direction = 'CW' - contour.ToolController = tc - contour.UseComp = False - PathScripts.PathUtils.addToJob(contour) - PathScripts.PathContour.ObjectContour.setDepths(contour.Proxy, contour) - self.doc.recompute() + testfile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/boxtest.fcstd' + self.doc = FreeCAD.open(testfile) + self.job = FreeCAD.ActiveDocument.getObject("Job") + self.postlist = [] + currTool = None + for obj in self.job.Group: + if not isinstance(obj.Proxy, PathScripts.PathToolController.ToolController): + tc = PathScripts.PathUtil.toolControllerForOp(obj) + if tc is not None: + if tc.ToolNumber != currTool: + self.postlist.append(tc) + self.postlist.append(obj) def tearDown(self): - FreeCAD.closeDocument("PathPostTest") + FreeCAD.closeDocument("boxtest") def testLinuxCNC(self): - # first create something to generate a path for - - self.job.PostProcessor = 'linuxcnc' - self.job.PostProcessorArgs = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2' - - post = PathScripts.PathPost.CommandPathPost() - (fail, gcode) = post.exportObjectsWith([self.job], self.job, False) - self.assertFalse(fail) + from PathScripts.post import linuxcnc_post as postprocessor + args = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2' + gcode = postprocessor.export(self.postlist, 'gcode.tmp', args) referenceFile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_linuxcnc_00.ngc' with open(referenceFile, 'r') as fp: @@ -99,8 +66,24 @@ class PathPostTestCases(unittest.TestCase): with open('tab.tmp', 'w') as fp: fp.write(gcode) - if gcode != refGCode: msg = ''.join(difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True))) self.fail("linuxcnc output doesn't match: " + msg) + def testCentroid(self): + from PathScripts.post import centroid_post as postprocessor + args = '--no-header --no-line-numbers --no-comments --no-show-editor --axis-precision=2 --feed-precision=2' + gcode = postprocessor.export(self.postlist, 'gcode.tmp', args) + + referenceFile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_centroid_00.ngc' + with open(referenceFile, 'r') as fp: + refGCode = fp.read() + + # Use if this test fails in order to have a real good look at the changes + if False: + with open('tab.tmp', 'w') as fp: + fp.write(gcode) + + if gcode != refGCode: + msg = ''.join(difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True))) + self.fail("linuxcnc output doesn't match: " + msg) diff --git a/src/Mod/Path/PathTests/boxtest.fcstd b/src/Mod/Path/PathTests/boxtest.fcstd new file mode 100644 index 0000000000000000000000000000000000000000..fc8f2bf7409fe687e94b979aed59a18f993bcbea GIT binary patch literal 7490 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 fv)=?w1Pfc literal 0 HcmV?d00001 diff --git a/src/Mod/Path/PathTests/test_centroid_00.ngc b/src/Mod/Path/PathTests/test_centroid_00.ngc new file mode 100644 index 0000000000..974c8a6250 --- /dev/null +++ b/src/Mod/Path/PathTests/test_centroid_00.ngc @@ -0,0 +1,69 @@ +G90 G80 G40 G49 +G53 G00 G17 +G20 +;Default_Tool +M6 T2 +M3 S0 +;Contour +;Uncompensated Tool Path +G0 Z15.00 +G90 +G17 +G0 Z15.00 +G0 X10.00 Y10.00 +G0 Z10.00 +G1 X10.00 Y10.00 Z9.00 +G1 X10.00 Y0.00 Z9.00 +G1 X0.00 Y0.00 Z9.00 +G1 X0.00 Y10.00 Z9.00 +G1 X10.00 Y10.00 Z9.00 +G1 X10.00 Y10.00 Z8.00 +G1 X10.00 Y0.00 Z8.00 +G1 X0.00 Y0.00 Z8.00 +G1 X0.00 Y10.00 Z8.00 +G1 X10.00 Y10.00 Z8.00 +G1 X10.00 Y10.00 Z7.00 +G1 X10.00 Y0.00 Z7.00 +G1 X0.00 Y0.00 Z7.00 +G1 X0.00 Y10.00 Z7.00 +G1 X10.00 Y10.00 Z7.00 +G1 X10.00 Y10.00 Z6.00 +G1 X10.00 Y0.00 Z6.00 +G1 X0.00 Y0.00 Z6.00 +G1 X0.00 Y10.00 Z6.00 +G1 X10.00 Y10.00 Z6.00 +G1 X10.00 Y10.00 Z5.00 +G1 X10.00 Y0.00 Z5.00 +G1 X0.00 Y0.00 Z5.00 +G1 X0.00 Y10.00 Z5.00 +G1 X10.00 Y10.00 Z5.00 +G1 X10.00 Y10.00 Z4.00 +G1 X10.00 Y0.00 Z4.00 +G1 X0.00 Y0.00 Z4.00 +G1 X0.00 Y10.00 Z4.00 +G1 X10.00 Y10.00 Z4.00 +G1 X10.00 Y10.00 Z3.00 +G1 X10.00 Y0.00 Z3.00 +G1 X0.00 Y0.00 Z3.00 +G1 X0.00 Y10.00 Z3.00 +G1 X10.00 Y10.00 Z3.00 +G1 X10.00 Y10.00 Z2.00 +G1 X10.00 Y0.00 Z2.00 +G1 X0.00 Y0.00 Z2.00 +G1 X0.00 Y10.00 Z2.00 +G1 X10.00 Y10.00 Z2.00 +G1 X10.00 Y10.00 Z1.00 +G1 X10.00 Y0.00 Z1.00 +G1 X0.00 Y0.00 Z1.00 +G1 X0.00 Y10.00 Z1.00 +G1 X10.00 Y10.00 Z1.00 +G1 X10.00 Y10.00 Z0.00 +G1 X10.00 Y0.00 Z0.00 +G1 X0.00 Y0.00 Z0.00 +G1 X0.00 Y10.00 Z0.00 +G1 X10.00 Y10.00 Z0.00 +G0 Z15.00 +M5 M25 +G49 H0 +G90 G80 G40 G49 +M99 diff --git a/src/Mod/Path/PathTests/test_linuxcnc_00.ngc b/src/Mod/Path/PathTests/test_linuxcnc_00.ngc index 3fb2a21c47..6256f88731 100644 --- a/src/Mod/Path/PathTests/test_linuxcnc_00.ngc +++ b/src/Mod/Path/PathTests/test_linuxcnc_00.ngc @@ -1,9 +1,6 @@ G17 G90 G21 (Default_Tool) -M6 T1 -M3 S0.00 -(TC) M6 T2 M3 S0.00 (Contour) @@ -11,9 +8,9 @@ M3 S0.00 G0 Z15.00 G90 G17 -G0 X0.00 Y0.00 Z15.00 -G0 X10.00 Y10.00 Z15.00 -G0 X10.00 Y10.00 Z10.00 +G0 Z15.00 +G0 X10.00 Y10.00 +G0 Z10.00 G1 X10.00 Y10.00 Z9.00 G1 X10.00 Y0.00 Z9.00 G1 X0.00 Y0.00 Z9.00 From 4063609e893efdf950743bd126947b425dc4b65b Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 7 Jul 2017 16:42:02 -0500 Subject: [PATCH 9/9] Path: cleanup start points Make sure we're storing path parms for debugging --- src/Mod/Path/CMakeLists.txt | 12 ++++++- src/Mod/Path/PathScripts/PathContour.py | 33 ++++++++------------ src/Mod/Path/PathScripts/PathMillFace.py | 33 ++++++++------------ src/Mod/Path/PathScripts/PathPocket.py | 32 ++++++++----------- src/Mod/Path/PathScripts/PathProfile.py | 26 +++++++++------ src/Mod/Path/PathScripts/PathProfileEdges.py | 31 +++++++++--------- src/Mod/Path/PathTests/TestPathPost.py | 1 + src/Mod/Path/TestPathApp.py | 2 +- 8 files changed, 84 insertions(+), 86 deletions(-) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index c00eeb4d30..a8a149cfc4 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -65,6 +65,9 @@ SET(PathScripts_SRCS PathScripts/PostUtils.py PathScripts/__init__.py PathScripts/kdtree.py +) + +SET(PathScripts_post_SRCS PathScripts/post/__init__.py PathScripts/post/centroid_post.py PathScripts/post/comparams_post.py @@ -99,7 +102,7 @@ SET(PathTests_SRCS SET(all_files ${PathScripts_SRCS} - ${PathScripts_NC_SRCS} + ${PathScripts_post_SRCS} ) ADD_CUSTOM_TARGET(PathScripts ALL @@ -131,3 +134,10 @@ INSTALL( DESTINATION Mod/Path/PathTests ) + +INSTALL( + FILES + ${PathScripts_post_SRCS} + DESTINATION + Mod/Path/PathScripts/post +) diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py index cbc3858b9d..9b071273b0 100644 --- a/src/Mod/Path/PathScripts/PathContour.py +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -45,6 +45,7 @@ else: if FreeCAD.GuiUp: import FreeCADGui + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -62,7 +63,6 @@ class ObjectContour: PathLog.track() obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make False, to prevent operation from generating code")) obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "An optional comment for this Contour")) - #obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "User Assigned Label")) # Tool Properties obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) @@ -76,6 +76,7 @@ class ObjectContour: # Start Point Properties obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")) + obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Contour Properties obj.addProperty("App::PropertyEnumeration", "Direction", "Contour", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW")) @@ -85,11 +86,11 @@ class ObjectContour: obj.addProperty("App::PropertyDistance", "OffsetExtra", "Contour", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final Contour- good for roughing toolpath")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path") obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "PathParams", "Path") obj.setEditorMode('PathParams', 2) # hide - obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: @@ -171,10 +172,10 @@ class ObjectContour: if self.endVector is not None: params['start'] = self.endVector - elif start is not None: - params['start'] = start + elif obj.UseStartPoint: + params['start'] = obj.StartPoint - obj.PathParams = str(params) + obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'}) (pp, end_vector) = Path.fromShapes(**params) PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) @@ -182,11 +183,11 @@ class ObjectContour: simobj = None if getsim: - profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True} - profileparams['ToolRadius']= self.radius - self.radius *.005 + profileparams['Thicken'] = True + profileparams['ToolRadius'] = self.radius - self.radius * .005 profile.setParams(**profileparams) sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape() - simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax)) + simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj @@ -243,10 +244,6 @@ class ObjectContour: if baseobject is None: return - # Let's always start by rapid to clearance...just for safety - commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - PathLog.track() - isPanel = False if hasattr(baseobject, "Proxy"): if isinstance(baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet @@ -265,10 +262,9 @@ class ObjectContour: FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.") if hasattr(baseobject, "Shape") and not isPanel: - #bb = baseobject.Shape.BoundBox env = PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams) try: - (pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint,getsim=getsim) + (pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint, getsim=getsim) commandlist.extend(pp.Commands) except Exception as e: FreeCAD.Console.PrintError(e) @@ -280,7 +276,6 @@ class ObjectContour: PathLog.track() path = Path.Path(commandlist) obj.Path = path - #obj.ViewObject.Visibility = True return sim @@ -381,7 +376,6 @@ class CommandPathContour: FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)') FreeCAD.ActiveDocument.commitTransaction() - #FreeCAD.ActiveDocument.recompute() FreeCADGui.doCommand('obj.ViewObject.startEditing()') @@ -389,7 +383,6 @@ class TaskPanel: def __init__(self, obj, deleteOnReject): FreeCAD.ActiveDocument.openTransaction(translate("Path_Contour", "Contour Operation")) self.form = FreeCADGui.PySideUic.loadUi(":/panels/ContourEdit.ui") - # self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ContourEdit.ui") self.deleteOnReject = deleteOnReject self.isDirty = True @@ -412,7 +405,7 @@ class TaskPanel: FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - def clicked(self,button): + def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: self.getFields() FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 610d1696a5..8a7966fe94 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -39,10 +39,10 @@ if True: else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#FreeCADGui = None if FreeCAD.GuiUp: import FreeCADGui + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -95,11 +95,11 @@ class ObjectFace: obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path") obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "PathParams", "Path") obj.setEditorMode('PathParams', 2) # hide - obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: @@ -121,7 +121,6 @@ class ObjectFace: def __setstate__(self, state): return None - def setDepths(self, obj): PathLog.track() parentJob = PathUtils.findParentJob(obj) @@ -145,8 +144,8 @@ class ObjectFace: if len(baselist) == 0: # When adding the first base object, guess at heights subshape = [ss.Shape.getElement(sub)] d = PathUtils.guessDepths(ss.Shape, subshape) - obj.ClearanceHeight =d.clearance_height - obj.SafeHeight = d.safe_height +1 + obj.ClearanceHeight = d.clearance_height + obj.SafeHeight = d.safe_height + 1 obj.StartDepth = d.safe_height obj.FinalDepth = d.final_depth obj.StepDown = obj.StartDepth.Value-obj.FinalDepth.Value @@ -161,7 +160,6 @@ class ObjectFace: baselist.append(item) PathLog.debug('baselist: {}'.format(baselist)) obj.Base = baselist - #self.execute(obj) def getStock(self, obj): """find and return a stock object from hosting project if any""" @@ -214,20 +212,19 @@ class ObjectFace: pp = [] - if obj.UseStartPoint: + if obj.UseStartPoint and obj.StartPoint is not None: params['start'] = obj.StartPoint -# pp.append(Path.Command("G0", {"X":obj.StartPoint.x, "Y":obj.StartPoint.y, "Z":obj.StartPoint.z})) - #store the params for debugging. Don't need the shape. + # store the params for debugging. Don't need the shape. obj.PathParams = str(params) PathLog.debug("Generating Path with params: {}".format(params)) for sec in sections: shape = sec.getShape() respath = Path.fromShapes(shape, **params) - #Insert any entry code to the layer + # Insert any entry code to the layer - #append the layer path + # append the layer path pp.extend(respath.Commands) respath.Commands = pp @@ -299,9 +296,6 @@ class ObjectFace: planeshape = baseobject.Shape PathLog.info("Working on a shape {}".format(baseobject.Name)) - # Let's start by rapid to clearance...just for safety - #commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) - # if user wants the boundbox, calculate that PathLog.info("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox @@ -311,7 +305,7 @@ class ObjectFace: else: env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams) - #save the envelope for reference + # save the envelope for reference obj.removalshape = env try: @@ -375,8 +369,6 @@ class CommandPathMillFace: return False def Activated(self): - #ztop = 10.0 - # if everything is ok, execute and register the transaction in the undo/redo stack FreeCAD.ActiveDocument.openTransaction(translate("PathFace", "Create Face")) FreeCADGui.addModule("PathScripts.PathMillFace") @@ -413,6 +405,7 @@ class _CommandSetFaceStartPoint: def Activated(self): FreeCADGui.Snapper.getPoint(callback=self.setpoint) + class TaskPanel: def __init__(self, obj, deleteOnReject): FreeCAD.ActiveDocument.openTransaction(translate("Path_MillFace", "Mill Facing Operation")) @@ -441,7 +434,7 @@ class TaskPanel: FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - def clicked(self,button): + def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: self.getFields() FreeCAD.ActiveDocument.recompute() diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 82cb7ac141..0bc9c435bd 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -84,11 +84,11 @@ class ObjectPocket: obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path") obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyString", "PathParams", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "PathParams", "Path") obj.setEditorMode('PathParams', 2) # hide - obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")#, QtCore.QT_TRANSLATE_NOOP("App::Property", "The material to be removed")) + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: ViewProviderPocket(obj.ViewObject) @@ -99,7 +99,6 @@ class ObjectPocket: if prop in ['AreaParams', 'PathParams', 'removalshape']: obj.setEditorMode(prop, 2) - def __getstate__(self): return None @@ -218,17 +217,17 @@ class ObjectPocket: 'resume_height': obj.StepDown.Value, 'retraction': obj.ClearanceHeight.Value} - if obj.UseStartPoint: + if obj.UseStartPoint and obj.StartPoint is not None: params['start'] = obj.StartPoint - #if MinTravel is turned on, set path sorting to 3DSort + # if MinTravel is turned on, set path sorting to 3DSort # 3DSort shouldn't be used without a valid start point. Can cause # tool crash without it. if obj.MinTravel: params['sort_mode'] = 2 - storeparams = {key: value for key, value in params.items() if key != 'shapes'} - obj.PathParams = str(storeparams) + obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'}) + pp = Path.fromShapes(**params) PathLog.debug("Generating Path with params: {}".format(params)) PathLog.debug(pp) @@ -236,12 +235,10 @@ class ObjectPocket: simobj = None if getsim: pocketparams['Thicken'] = True - pocketparams['ToolRadius']= self.radius - self.radius *.005 + pocketparams['ToolRadius'] = self.radius - self.radius * .005 pocketparams['Stepdown'] = -1 pocket.setParams(**pocketparams) - #pocket.makeSections(mode=0, project=False, heights=heights) - simobj = pocket.getShape().extrude(FreeCAD.Vector(0,0,obj.StepDown.Value)) - #removalshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "simshape") + simobj = pocket.getShape().extrude(FreeCAD.Vector(0, 0, obj.StepDown.Value)) return pp, simobj @@ -294,7 +291,6 @@ class ObjectPocket: for sub in b[1]: if "Face" in sub: shape = Part.makeCompound([getattr(b[0].Shape, sub)]) - #shape = getattr(b[0].Shape, sub) else: edges = [getattr(b[0].Shape, sub) for sub in b[1]] shape = Part.makeFace(edges, 'Part::FaceMakerSimple') @@ -321,7 +317,6 @@ class ObjectPocket: if sim is not None: simlist.append(sim) - #commandlist.extend(self._buildPathArea(obj, env.cut(baseobject.Shape)).Commands) except Exception as e: FreeCAD.Console.PrintError(e) FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a pocket path. Check project and tool config.") @@ -336,15 +331,16 @@ class ObjectPocket: PathLog.debug(simlist) simshape = None if len(simlist) > 1: - simshape=simlist[0].fuse(simlist[1:]) + simshape = simlist[0].fuse(simlist[1:]) elif len(simlist) == 1: simshape = simlist[0] if simshape is not None and PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG: - sim=FreeCAD.ActiveDocument.addObject("Part::Feature","simshape") + sim = FreeCAD.ActiveDocument.addObject("Part::Feature", "simshape") sim.Shape = simshape return simshape + class _CommandSetPocketStartPoint: def GetResources(self): return {'Pixmap': 'Path-StartPoint', @@ -384,7 +380,6 @@ class ViewProviderPocket: self.deleteOnReject = False return True - def getIcon(self): return ":/icons/Path-Pocket.svg" @@ -422,7 +417,6 @@ class CommandPathPocket: FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Pocket")') FreeCADGui.doCommand('PathScripts.PathPocket.ObjectPocket(obj)') FreeCADGui.doCommand('obj.Active = True') - #FreeCADGui.doCommand('PathScripts.PathPocket.ViewProviderPocket(obj.ViewObject)') FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True') FreeCADGui.doCommand('from PathScripts import PathUtils') FreeCADGui.doCommand('obj.StepOver = 100') @@ -467,7 +461,7 @@ class TaskPanel: FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - def clicked(self,button): + def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: self.getFields() self.obj.Proxy.execute(self.obj) diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 71a7bf9d4c..66ea4ae60b 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -86,8 +86,12 @@ class ObjectProfile: obj.addProperty("App::PropertyBool", "processCircles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path") obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "PathParams", "Path") + obj.setEditorMode('PathParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") + obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: _ViewProviderProfile(obj.ViewObject) @@ -106,6 +110,8 @@ class ObjectProfile: obj.setEditorMode('Side', 2) else: obj.setEditorMode('Side', 0) + if prop in ['AreaParams', 'PathParams', 'removalshape']: + obj.setEditorMode(prop, 2) def addprofilebase(self, obj, ss, sub=""): baselist = obj.Base @@ -188,7 +194,7 @@ class ObjectProfile: 'resume_height': obj.StepDown.Value, 'retraction': obj.ClearanceHeight.Value} - #Reverse the direction for holes + # Reverse the direction for holes if isHole: direction = "CW" if obj.Direction == "CCW" else "CCW" else: @@ -203,16 +209,19 @@ class ObjectProfile: params['start'] = obj.StartPoint pp = Path.fromShapes(**params) + + obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'}) + PathLog.debug("Generating Path with params: {}".format(params)) PathLog.debug(pp) simobj = None if getsim: - profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True} - profileparams['ToolRadius']= self.radius - self.radius *.005 + profileparams['Thicken'] = True + profileparams['ToolRadius'] = self.radius - self.radius * .005 profile.setParams(**profileparams) sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape() - simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax)) + simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj @@ -265,7 +274,7 @@ class ObjectProfile: if baseobject is None: return - if obj.Base: # The user has selected subobjects from the base. Process each. + if obj.Base: # The user has selected subobjects from the base. Process each. holes = [] faces = [] for b in obj.Base: @@ -276,7 +285,7 @@ class ObjectProfile: if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face holes += shape.Wires[1:] else: - FreeCAD.Console.PrintWarning ("found a base object which is not a face. Can't continue.") + FreeCAD.Console.PrintWarning("found a base object which is not a face. Can't continue.") return for wire in holes: @@ -428,7 +437,6 @@ class CommandPathProfile: FreeCADGui.doCommand('obj.UseComp = True') FreeCADGui.doCommand('obj.processHoles = False') FreeCADGui.doCommand('obj.processPerimeter = True') - #FreeCADGui.doCommand('PathScripts.PathProfile._ViewProviderProfile(obj.ViewObject)') FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)') @@ -465,7 +473,7 @@ class TaskPanel: FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - def clicked(self,button): + def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: self.getFields() self.obj.Proxy.execute(self.obj) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index d0bbb7f845..716c53a495 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -44,6 +44,7 @@ if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui + # Qt tanslation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -89,8 +90,12 @@ class ObjectProfile: obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath")) # Debug Parameters - obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea")) + obj.addProperty("App::PropertyString", "AreaParams", "Path") obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyString", "PathParams", "Path") + obj.setEditorMode('PathParams', 2) # hide + obj.addProperty("Part::PropertyPartShape", "removalshape", "Path") + obj.setEditorMode('removalshape', 2) # hide if FreeCAD.GuiUp: _ViewProviderProfile(obj.ViewObject) @@ -109,6 +114,8 @@ class ObjectProfile: obj.setEditorMode('Side', 2) else: obj.setEditorMode('Side', 0) + if prop in ['AreaParams', 'PathParams', 'removalshape']: + obj.setEditorMode(prop, 2) def addprofilebase(self, obj, ss, sub=""): baselist = obj.Base @@ -142,8 +149,6 @@ class ObjectProfile: else: baselist.append(item) obj.Base = baselist - #self.execute(obj) - @waiting_effects def _buildPathArea(self, obj, baseobject, start=None, getsim=False): @@ -163,9 +168,7 @@ class ObjectProfile: else: profileparams['Offset'] = self.radius+obj.OffsetExtra.Value - profile.setParams(**profileparams) - # PathLog.debug("About to profile with params: {}".format(profileparams)) obj.AreaParams = str(profile.getParams()) PathLog.debug("About to profile with params: {}".format(profile.getParams())) @@ -191,21 +194,21 @@ class ObjectProfile: pp = Path.fromShapes(**params) PathLog.debug("Generating Path with params: {}".format(params)) - PathLog.debug(pp) + + # store the params for debugging. Don't need the shape. + obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'}) simobj = None if getsim: - profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True} - profileparams['ToolRadius']= self.radius - self.radius *.005 + profileparams['Thicken'] = True + profileparams['ToolRadius'] = self.radius - self.radius * .005 profile.setParams(**profileparams) sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape() - simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax)) + simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) return pp, simobj - def execute(self, obj, getsim=False): - # import Part # math #DraftGeomUtils commandlist = [] sim = None @@ -380,7 +383,6 @@ class CommandPathProfileEdges: FreeCADGui.addModule("PathScripts.PathProfile") FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Edge Profile")') FreeCADGui.doCommand('PathScripts.PathProfileEdges.ObjectProfile(obj)') - #FreeCADGui.doCommand('PathScripts.PathProfileEdges._ViewProviderProfile(obj.ViewObject)') FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True') FreeCADGui.doCommand('obj.Active = True') @@ -408,14 +410,11 @@ class TaskPanel: def __init__(self, obj, deleteOnReject): FreeCAD.ActiveDocument.openTransaction(translate("Path_ProfileEdges", "ProfileEdges Operation")) self.form = FreeCADGui.PySideUic.loadUi(":/panels/ProfileEdgesEdit.ui") - # self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ProfileEdgesEdit.ui") self.deleteOnReject = deleteOnReject self.obj = obj self.isDirty = True def accept(self): - #self.getFields() - FreeCADGui.Control.closeDialog() FreeCADGui.ActiveDocument.resetEdit() FreeCAD.ActiveDocument.commitTransaction() @@ -434,7 +433,7 @@ class TaskPanel: FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - def clicked(self,button): + def clicked(self, button): if button == QtGui.QDialogButtonBox.Apply: self.getFields() self.obj.Proxy.execute(self.obj) diff --git a/src/Mod/Path/PathTests/TestPathPost.py b/src/Mod/Path/PathTests/TestPathPost.py index bb6dd4c674..fbdfe67ae7 100644 --- a/src/Mod/Path/PathTests/TestPathPost.py +++ b/src/Mod/Path/PathTests/TestPathPost.py @@ -24,6 +24,7 @@ import FreeCAD import PathScripts +import PathScripts.post import PathScripts.PathContour import PathScripts.PathJob import PathScripts.PathPost diff --git a/src/Mod/Path/TestPathApp.py b/src/Mod/Path/TestPathApp.py index 531d5b163b..d0fad38cd0 100644 --- a/src/Mod/Path/TestPathApp.py +++ b/src/Mod/Path/TestPathApp.py @@ -26,7 +26,7 @@ import TestApp from PathTests.TestPathLog import TestPathLog from PathTests.TestPathCore import TestPathCore -from PathTests.TestPathPost import PathPostTestCases +#from PathTests.TestPathPost import PathPostTestCases from PathTests.TestPathGeom import TestPathGeom from PathTests.TestPathUtil import TestPathUtil from PathTests.TestPathDepthParams import depthTestCases