diff --git a/src/Mod/Path/PathScripts/PathSimulatorGui.py b/src/Mod/Path/PathScripts/PathSimulatorGui.py index 24c11e5024..8be9536621 100644 --- a/src/Mod/Path/PathScripts/PathSimulatorGui.py +++ b/src/Mod/Path/PathScripts/PathSimulatorGui.py @@ -1,6 +1,4 @@ import os -_filePath = os.path.dirname(os.path.abspath(__file__)) - import FreeCAD import Path import Part @@ -10,12 +8,15 @@ import math from FreeCAD import Vector, Base from PathScripts.PathGeom import PathGeom +_filePath = os.path.dirname(os.path.abspath(__file__)) + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui, QtCore -#compiled with pyrcc4 -py3 Resources\CAM_Sim.qrc -o CAM_Sim_rc.py - +# compiled with pyrcc4 -py3 Resources\CAM_Sim.qrc -o CAM_Sim_rc.py + + class CAMSimTaskUi: def __init__(self, parent): # this will create a Qt widget from our ui file @@ -30,20 +31,17 @@ class CAMSimTaskUi: self.parent.cancel() FreeCADGui.Control.closeDialog() -#for cmd in obj.Path.Commands: -# if cmd.Name[0] == 'G': -# e1 = -# print cmd.Name -def TSError(msg): - QtGui.QMessageBox.information(None,"Path Simulation",msg) +def TSError(msg): + QtGui.QMessageBox.information(None, "Path Simulation", msg) + class PathSimulation: def __init__(self): self.debug = False self.timer = QtCore.QTimer() QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.PerformCut) - self.stdrot = FreeCAD.Rotation(Vector(0,0,1),0) + self.stdrot = FreeCAD.Rotation(Vector(0, 0, 1), 0) self.iprogress = 0 self.numCommands = 0 self.simperiod = 20 @@ -71,23 +69,25 @@ class PathSimulation: form.comboJobs.currentIndexChanged.connect(self.onJobChange) jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*") form.comboJobs.clear() - self.jobs = [] + self.jobs = [] for j in jobList: self.jobs.append(j) form.comboJobs.addItem(j.ViewObject.Icon, j.Label) FreeCADGui.Control.showDialog(self.taskForm) self.disableAnim = False self.isVoxel = True + self.firstDrill = True self.voxSim = PathSimulator.PathSim() self.SimulateMill() def SetupSimulation(self): - form = self.taskForm.form + form = self.taskForm.form self.activeOps = [] self.numCommands = 0 self.ioperation = 0 for i in range(form.listOperations.count()): if form.listOperations.item(i).checkState() == QtCore.Qt.CheckState.Checked: + self.firstDrill = True self.activeOps.append(self.operations[i]) self.numCommands += len(self.operations[i].Path.Commands) if len(self.activeOps) == 0: @@ -104,48 +104,47 @@ class PathSimulation: self.busy = False self.tool = None for i in range(len(self.activeOps)): - self.SetupOperation(0) - if (self.tool is not None): - break + self.SetupOperation(0) + if (self.tool is not None): + break self.iprogress = 0 self.UpdateProgress() def SetupOperation(self, itool): self.operation = self.activeOps[itool] if hasattr(self.operation, "ToolController"): - self.tool = self.operation.ToolController.Tool + self.tool = self.operation.ToolController.Tool if (self.tool is not None): - toolProf = self.CreateToolProfile(self.tool, Vector(0,1,0), Vector(0,0,0), self.tool.Diameter / 2.0) - self.cutTool.Shape = Part.makeSolid(toolProf.revolve(Vector(0,0,0), Vector(0,0,1))) - self.cutTool.ViewObject.show() - self.voxSim.SetCurrentTool(self.tool) + toolProf = self.CreateToolProfile(self.tool, Vector(0, 1, 0), Vector(0, 0, 0), self.tool.Diameter / 2.0) + self.cutTool.Shape = Part.makeSolid(toolProf.revolve(Vector(0, 0, 0), Vector(0, 0, 1))) + self.cutTool.ViewObject.show() + self.voxSim.SetCurrentTool(self.tool) self.icmd = 0 self.curpos = FreeCAD.Placement(self.initialPos, self.stdrot) - #self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot) + # self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot) self.cutTool.Placement = self.curpos - def SimulateMill(self): self.job = self.jobs[self.taskForm.form.comboJobs.currentIndex()] self.busy = False - #self.timer.start(100) + # self.timer.start(100) self.height = 10 self.skipStep = False self.initialPos = Vector(0, 0, self.job.Stock.Shape.BoundBox.ZMax) # Add cut tool - self.cutTool = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","CutTool") + self.cutTool = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutTool") self.cutTool.ViewObject.Proxy = 0 self.cutTool.ViewObject.hide() # Add cut material if self.isVoxel: - self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython","CutMaterial") - self.cutMaterialIn = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython","CutMaterialIn") + self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterial") + self.cutMaterialIn = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterialIn") self.cutMaterialIn.ViewObject.Proxy = 0 self.cutMaterialIn.ViewObject.show() self.cutMaterialIn.ViewObject.ShapeColor = (1.0, 0.85, 0.45, 0.0) else: - self.cutMaterial = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","CutMaterial") + self.cutMaterial = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutMaterial") self.cutMaterial.Shape = self.job.Stock.Shape self.cutMaterial.ViewObject.Proxy = 0 self.cutMaterial.ViewObject.show() @@ -153,21 +152,18 @@ class PathSimulation: # Add cut path solid for debug if self.debug: - self.cutSolid = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","CutDebug") + self.cutSolid = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutDebug") self.cutSolid.ViewObject.Proxy = 0 self.cutSolid.ViewObject.hide() - + self.SetupSimulation() self.resetSimulation = True FreeCAD.ActiveDocument.recompute() - - #self.dialog.show() def SkipStep(self): self.skipStep = True self.PerformCut() - def PerformCutBoolean(self): if self.resetSimulation: self.resetSimulation = False @@ -178,8 +174,9 @@ class PathSimulation: self.busy = True cmd = self.operation.Path.Commands[self.icmd] - #for cmd in job.Path.Commands: + # for cmd in job.Path.Commands: pathSolid = None + if cmd.Name in ['G0']: self.curpos = self.RapidMove(cmd, self.curpos) if cmd.Name in ['G1', 'G2', 'G3']: @@ -187,6 +184,17 @@ class PathSimulation: self.curpos = self.RapidMove(cmd, self.curpos) else: (pathSolid, self.curpos) = self.GetPathSolid(self.tool, cmd, self.curpos) + if cmd.Name in ['G81', 'G82', 'G83']: + if self.firstDrill: + extendcommand = Path.Command('G1', {"X": 0.0, "Y": 0.0, "Z": cmd.r}) + self.curpos = self.RapidMove(extendcommand, self.curpos) + self.firstDrill = False + extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + self.curpos = self.RapidMove(extendcommand, self.curpos) + extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z}) + self.curpos = self.RapidMove(extendcommand, self.curpos) + extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}) + self.curpos = self.RapidMove(extendcommand, self.curpos) self.skipStep = False if pathSolid is not None: if self.debug: @@ -197,14 +205,14 @@ class PathSimulation: self.stock = newStock.removeSplitter() except: if self.debug: - print "invalid cut at cmd #" + str(self.icmd) + print("invalid cut at cmd #{}".format(self.icmd)) if not self.disableAnim: self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot) self.icmd += 1 self.iprogress += 1 self.UpdateProgress() if self.icmd >= len(self.operation.Path.Commands): - #self.cutMaterial.Shape = self.stock.removeSplitter() + # self.cutMaterial.Shape = self.stock.removeSplitter() self.ioperation += 1 if self.ioperation >= len(self.activeOps): self.EndSimulation() @@ -214,7 +222,7 @@ class PathSimulation: if not self.disableAnim: self.cutMaterial.Shape = self.stock self.busy = False - + def PerformCutVoxel(self): if self.resetSimulation: self.resetSimulation = False @@ -225,17 +233,30 @@ class PathSimulation: self.busy = True cmd = self.operation.Path.Commands[self.icmd] - #for cmd in job.Path.Commands: + # for cmd in job.Path.Commands: if cmd.Name in ['G0', 'G1', 'G2', 'G3']: self.curpos = self.voxSim.ApplyCommand(self.curpos, cmd) if not self.disableAnim: - self.cutTool.Placement = self.curpos #FreeCAD.Placement(self.curpos, self.stdrot) + self.cutTool.Placement = self.curpos # FreeCAD.Placement(self.curpos, self.stdrot) (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() + if cmd.Name in ['G81', 'G82', 'G83']: + extendcommands = [] + if self.firstDrill: + extendcommands.append(Path.Command('G1', {"X": 0.0, "Y": 0.0, "Z": cmd.r})) + self.firstDrill = False + extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})) + extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z})) + extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})) + for ecmd in extendcommands: + self.curpos = self.voxSim.ApplyCommand(self.curpos, ecmd) + if not self.disableAnim: + self.cutTool.Placement = self.curpos # FreeCAD.Placement(self.curpos, self.stdrot) + (self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh() self.icmd += 1 self.iprogress += 1 self.UpdateProgress() if self.icmd >= len(self.operation.Path.Commands): - #self.cutMaterial.Shape = self.stock.removeSplitter() + # self.cutMaterial.Shape = self.stock.removeSplitter() self.ioperation += 1 if self.ioperation >= len(self.activeOps): self.EndSimulation() @@ -249,16 +270,16 @@ class PathSimulation: self.PerformCutVoxel() else: self.PerformCutBoolean() - + def RapidMove(self, cmd, curpos): - path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug + path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug if path is None: return curpos return path.valueAt(path.LastParameter) def GetPathSolidOld(self, tool, cmd, curpos): e1 = PathGeom.edgeForCmd(cmd, curpos) - #curpos = e1.valueAt(e1.LastParameter) + # curpos = e1.valueAt(e1.LastParameter) n1 = e1.tangentAt(0) n1[2] = 0.0 try: @@ -266,10 +287,10 @@ class PathSimulation: except: return (None, e1.valueAt(e1.LastParameter)) height = self.height - rad = tool.Diameter / 2.0 - 0.001 * curpos[2] # hack to overcome occ bug - if type(e1.Curve) is Part.Circle and e1.Curve.Radius <= rad: # hack to overcome occ bug + rad = tool.Diameter / 2.0 - 0.001 * curpos[2] # hack to overcome occ bug + if type(e1.Curve) is Part.Circle and e1.Curve.Radius <= rad: # hack to overcome occ bug rad = e1.Curve.Radius - 0.001 - #return (None, e1.valueAt(e1.LastParameter)) + # return (None, e1.valueAt(e1.LastParameter)) xf = n1[0] * rad yf = n1[1] * rad xp = curpos[0] @@ -279,33 +300,33 @@ class PathSimulation: v2 = Vector(yf + xp, -xf + yp, zp + height) v3 = Vector(-yf + xp, xf + yp, zp + height) v4 = Vector(-yf + xp, xf + yp, zp) - vc1 = Vector(xf + xp, yf + yp, zp) - vc2 = Vector(xf + xp, yf + yp, zp + height) + # vc1 = Vector(xf + xp, yf + yp, zp) + # vc2 = Vector(xf + xp, yf + yp, zp + height) l1 = Part.makeLine(v1, v2) l2 = Part.makeLine(v2, v3) - #l2 = Part.Edge(Part.Arc(v2, vc2, v3)) + # l2 = Part.Edge(Part.Arc(v2, vc2, v3)) l3 = Part.makeLine(v3, v4) l4 = Part.makeLine(v4, v1) - #l4 = Part.Edge(Part.Arc(v4, vc1, v1)) + # l4 = Part.Edge(Part.Arc(v4, vc1, v1)) w1 = Part.Wire([l1, l2, l3, l4]) w2 = Part.Wire(e1) try: - ex1 = w2.makePipeShell([w1],True, True) + ex1 = w2.makePipeShell([w1], True, True) except: - #Part.show(w1) - #Part.show(w2) + # Part.show(w1) + # Part.show(w2) return (None, e1.valueAt(e1.LastParameter)) cyl1 = Part.makeCylinder(rad, height, curpos) curpos = e1.valueAt(e1.LastParameter) cyl2 = Part.makeCylinder(rad, height, curpos) ex1s = Part.Solid(ex1) - f1 = ex1s.fuse([cyl1,cyl2]).removeSplitter() + f1 = ex1s.fuse([cyl1, cyl2]).removeSplitter() return (f1, curpos) - + # get a solid representation of a tool going along path def GetPathSolid(self, tool, cmd, pos): toolPath = PathGeom.edgeForCmd(cmd, pos) - #curpos = e1.valueAt(e1.LastParameter) + # curpos = e1.valueAt(e1.LastParameter) startDir = toolPath.tangentAt(0) startDir[2] = 0.0 endPos = toolPath.valueAt(toolPath.LastParameter) @@ -315,11 +336,11 @@ class PathSimulation: endDir.normalize() except: return (None, endPos) - height = self.height + # height = self.height # hack to overcome occ bugs rad = tool.Diameter / 2.0 - 0.001 * pos[2] - #rad = rad + 0.001 * self.icmd + # rad = rad + 0.001 * self.icmd if type(toolPath.Curve) is Part.Circle and toolPath.Curve.Radius <= rad: rad = toolPath.Curve.Radius - 0.01 * (pos[2] + 1) return (None, endPos) @@ -334,7 +355,7 @@ class PathSimulation: fullProf = Part.Wire([toolProf, mirroredProf]) pathWire = Part.Wire(toolPath) try: - pathShell = pathWire.makePipeShell([fullProf],False, True) + pathShell = pathWire.makePipeShell([fullProf], False, True) except: if self.debug: Part.show(pathWire) @@ -342,11 +363,11 @@ class PathSimulation: return (None, endPos) # create the start cup - startCup = toolProf.revolve(pos, Vector(0,0,1), -180) + startCup = toolProf.revolve(pos, Vector(0, 0, 1), -180) - #create the end cup + # create the end cup endProf = self.CreateToolProfile(tool, endDir, endPos, rad) - endCup = endProf.revolve(endPos, Vector(0,0,1), 180) + endCup = endProf.revolve(endPos, Vector(0, 0, 1), 180) fullShell = Part.makeShell(startCup.Faces + pathShell.Faces + endCup.Faces) return (Part.makeSolid(fullShell).removeSplitter(), endPos) @@ -354,7 +375,7 @@ class PathSimulation: # create radial profile of the tool (90 degrees to the direction of the path) def CreateToolProfile(self, tool, dir, pos, rad): type = tool.ToolType - #rad = tool.Diameter / 2.0 - 0.001 * pos[2] # hack to overcome occ bug + # rad = tool.Diameter / 2.0 - 0.001 * pos[2] # hack to overcome occ bug xf = dir[0] * rad yf = dir[1] * rad xp = pos[0] @@ -393,13 +414,13 @@ class PathSimulation: lR = Part.makeLine(vBR, vTR) res = Part.Wire([cB, lR, lT]) - else: # default: assume type == "EndMill" + else: # default: assume type == "EndMill" vBR = Vector(xp + yf, yp - xf, zp) lR = Part.makeLine(vBR, vTR) lB = Part.makeLine(vBC, vBR) res = Part.Wire([lB, lR, lT]) - return res + return res def onJobChange(self): form = self.taskForm.form @@ -407,19 +428,18 @@ class PathSimulation: form.listOperations.clear() self.operations = [] for op in j.Operations.OutList: - listItem = QtGui.QListWidgetItem(op.ViewObject.Icon, op.Label) + listItem = QtGui.QListWidgetItem(op.ViewObject.Icon, op.Label) listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable) listItem.setCheckState(QtCore.Qt.CheckState.Checked) self.operations.append(op) form.listOperations.addItem(listItem) - + def onSpeedBarChange(self): form = self.taskForm.form self.simperiod = 1000 / form.sliderSpeed.value() form.labelGPerSec.setText(str(form.sliderSpeed.value()) + " G/s") - #if (self.timer.isActive()): + # if (self.timer.isActive()): self.timer.setInterval(self.simperiod) - def onAccuracyBarChange(self): form = self.taskForm.form @@ -428,7 +448,7 @@ class PathSimulation: def GuiBusy(self, isBusy): form = self.taskForm.form - #form.toolButtonStop.setEnabled() + # form.toolButtonStop.setEnabled() form.toolButtonPlay.setEnabled(not isBusy) form.toolButtonPause.setEnabled(isBusy) form.toolButtonStep.setEnabled(not isBusy) @@ -493,18 +513,17 @@ class PathSimulation: FreeCAD.ActiveDocument.removeObject(self.cutMaterial.Name) self.cutMaterial = None self.RemoveInnerMaterial() - def accept(self): self.EndSimulation() self.RemoveInnerMaterial() self.RemoveTool() - + def cancel(self): self.EndSimulation() self.RemoveTool() self.RemoveMaterial() - + class CommandPathSimulate: @@ -527,7 +546,5 @@ class CommandPathSimulate: pathSimulation = PathSimulation() if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Simulator',CommandPathSimulate()) + FreeCADGui.addCommand('Path_Simulator', CommandPathSimulate()) FreeCAD.Console.PrintLog("Loading PathSimulator Gui... done\n") - - diff --git a/src/Mod/Path/PathScripts/post/linuxcnc_post.py b/src/Mod/Path/PathScripts/post/linuxcnc_post.py index 2cc8f48029..df052ae006 100644 --- a/src/Mod/Path/PathScripts/post/linuxcnc_post.py +++ b/src/Mod/Path/PathScripts/post/linuxcnc_post.py @@ -21,8 +21,16 @@ # * * # ***************************************************************************/ from __future__ import print_function +import FreeCAD +from FreeCAD import Units +import Path +import argparse +import datetime +import shlex +from PathScripts import PostUtils +from PathScripts import PathUtils -TOOLTIP=''' +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 linuxcnc 3 axis mill. This postprocessor, once placed @@ -33,14 +41,6 @@ import linuxcnc_post linuxcnc_post.export(object,"/path/to/file.ncc","") ''' -import FreeCAD -from FreeCAD import Units -import argparse -import datetime -import shlex -from PathScripts import PostUtils -from PathScripts import PathUtils - now = datetime.datetime.now() parser = argparse.ArgumentParser(prog='linuxcnc', add_help=False) @@ -52,12 +52,14 @@ parser.add_argument('--line-numbers', action='store_true', help='prefix with lin parser.add_argument('--no-line-numbers', action='store_true', help='don\'t prefix with line numbers (default)') parser.add_argument('--show-editor', action='store_true', help='pop up editor before writing output (default)') parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output') -parser.add_argument('--precision', default='4', help='number of digits of precision, default=4') +parser.add_argument('--precision', default='3', help='number of digits of precision, default=4') parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17\nG90"') parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"') parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') +parser.add_argument('--modal', action='store_true', help='Dont output the Same Gcommand Name USE Modal Mode') +parser.add_argument('--no-doubles', action='store_true', help='Dont output the Same Axis Value Mode') -TOOLTIP_ARGS=parser.format_help() +TOOLTIP_ARGS = parser.format_help() # These globals set common customization preferences OUTPUT_COMMENTS = True @@ -65,6 +67,7 @@ OUTPUT_HEADER = True OUTPUT_LINE_NUMBERS = False SHOW_EDITOR = True MODAL = False # if true commands are suppressed if the same as previous line. +OUTPUT_DOUBLES = True COMMAND_SPACE = " " LINENR = 100 # line number starting value @@ -76,7 +79,7 @@ UNIT_FORMAT = 'mm' MACHINE_NAME = "LinuxCNC" CORNER_MIN = {'x': 0, 'y': 0, 'z': 0} CORNER_MAX = {'x': 500, 'y': 300, 'z': 300} -PRECISION=4 +PRECISION = 3 # Preamble text will appear at the beginning of the GCODE output file. PREAMBLE = '''G17 G90 @@ -103,6 +106,7 @@ TOOL_CHANGE = '''''' if open.__module__ == '__builtin__': pythonopen = open + def processArguments(argstring): global OUTPUT_HEADER global OUTPUT_COMMENTS @@ -114,6 +118,8 @@ def processArguments(argstring): global UNITS global UNIT_SPEED_FORMAT global UNIT_FORMAT + global MODAL + global OUTPUT_DOUBLES try: args = parser.parse_args(shlex.split(argstring)) @@ -143,12 +149,18 @@ def processArguments(argstring): UNITS = 'G20' UNIT_SPEED_FORMAT = 'in/min' UNIT_FORMAT = 'in' + PRECISION = 4 + if args.modal: + MODAL = True + if args.no_doubles: + OUTPUT_DOUBLES = False except: return False return True + def export(objectslist, filename, argstring): if not processArguments(argstring): return None @@ -183,17 +195,16 @@ def export(objectslist, filename, argstring): myMachine = 'not set' - if hasattr(job,"MachineName"): + if hasattr(job, "MachineName"): myMachine = job.MachineName if hasattr(job, "MachineUnits"): if job.MachineUnits == "Metric": - UNITS = "G21" - UNIT_SPEED_FORMAT = 'mm/min' - + UNITS = "G21" + UNIT_SPEED_FORMAT = 'mm/min' else: - UNITS = "G20" - UNIT_SPEED_FORMAT = 'in/min' + UNITS = "G20" + UNIT_SPEED_FORMAT = 'in/min' # do the pre_op if OUTPUT_COMMENTS: @@ -245,16 +256,24 @@ def linenumber(): return "N" + str(LINENR) + " " return "" + def parse(pathobj): global PRECISION + global MODAL + global OUTPUT_DOUBLES + out = "" lastcommand = None - precision_string = '.' + str(PRECISION) +'f' + precision_string = '.' + str(PRECISION) + 'f' # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control # the order of parameters # linuxcnc 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'] + params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P'] + # keep track for no doubles + currLocation = {} + firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": -1}) + currLocation.update(firstmove.Parameters) if hasattr(pathobj, "Group"): # We have a compound or project. # if OUTPUT_COMMENTS: @@ -284,22 +303,31 @@ def parse(pathobj): # 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"]: #linuxcnc doesn't use rapid speeds + if param == 'F' and (currLocation[param] == c.Parameters[param] and OUTPUT_DOUBLES): + if c.Name not in ["G0", "G00"]: # linuxcnc doesn't use rapid speeds speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) outstring.append( - param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string) ) + param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) elif param == 'T': outstring.append(param + str(int(c.Parameters['T']))) + elif param == 'H': + outstring.append(param + str(int(c.Parameters['H']))) + elif param == 'D': + outstring.append(param + str(int(c.Parameters['D']))) + elif param == 'S': + outstring.append(param + str(int(c.Parameters['S']))) else: - pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) - outstring.append( - param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) ) - #param + format(c.Parameters[param], precision_string)) - + if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): + continue + else: + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outstring.append( + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)) + # param + format(c.Parameters[param], precision_string)) # store the latest command lastcommand = command + currLocation.update(c.Parameters) # Check for Tool Change: if command == 'M6':