path: LGTM cleanup post processors

This commit is contained in:
sliptonic
2019-07-01 12:02:45 -05:00
parent 02b68f36fb
commit ecc3994337
5 changed files with 236 additions and 256 deletions

View File

@@ -19,14 +19,11 @@
# * *
# ***************************************************************************/
import FreeCAD
import FreeCADGui
import PathScripts.PathLog as PathLog
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
from PySide import QtCore, QtGui
import PathScripts.PathAdaptive as PathAdaptive
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
def initPage(self, obj):
self.setTitle("Adaptive path operation")
@@ -35,39 +32,39 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
form = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
#tool controller
# tool controller
hlayout = QtGui.QHBoxLayout()
form.ToolController = QtGui.QComboBox()
form.ToolControllerLabel=QtGui.QLabel("Tool Controller")
form.ToolControllerLabel = QtGui.QLabel("Tool Controller")
hlayout.addWidget(form.ToolControllerLabel)
hlayout.addWidget(form.ToolController)
layout.addLayout(hlayout)
#cut region
# cut region
formLayout = QtGui.QFormLayout()
form.Side = QtGui.QComboBox()
form.Side.addItem("Inside")
form.Side.addItem("Outside")
form.Side.setToolTip("Cut inside or outside of the selected shapes")
formLayout.addRow(QtGui.QLabel("Cut Region"),form.Side)
formLayout.addRow(QtGui.QLabel("Cut Region"), form.Side)
#operation type
# operation type
form.OperationType = QtGui.QComboBox()
form.OperationType.addItem("Clearing")
form.OperationType.addItem("Profiling")
form.OperationType.setToolTip("Type of adaptive operation")
formLayout.addRow(QtGui.QLabel("Operation Type"),form.OperationType)
formLayout.addRow(QtGui.QLabel("Operation Type"), form.OperationType)
#step over
# step over
form.StepOver = QtGui.QSpinBox()
form.StepOver.setMinimum(15)
form.StepOver.setMaximum(75)
form.StepOver.setSingleStep(1)
form.StepOver.setValue(25)
form.StepOver.setToolTip("Optimal value for tool stepover")
formLayout.addRow(QtGui.QLabel("Step Over Percent"),form.StepOver)
formLayout.addRow(QtGui.QLabel("Step Over Percent"), form.StepOver)
#tolerance
# tolerance
form.Tolerance = QtGui.QSlider(QtCore.Qt.Horizontal)
form.Tolerance.setMinimum(5)
form.Tolerance.setMaximum(15)
@@ -75,67 +72,62 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
form.Tolerance.setValue(10)
form.Tolerance.setTickPosition(QtGui.QSlider.TicksBelow)
form.Tolerance.setToolTip("Influences calculation performance vs stability and accuracy.")
formLayout.addRow(QtGui.QLabel("Accuracy vs Performance"),form.Tolerance)
formLayout.addRow(QtGui.QLabel("Accuracy vs Performance"), form.Tolerance)
#helix angle
# helix angle
form.HelixAngle = QtGui.QDoubleSpinBox()
form.HelixAngle.setMinimum(0.1)
form.HelixAngle.setMaximum(90)
form.HelixAngle.setSingleStep(0.1)
form.HelixAngle.setValue(5)
form.HelixAngle.setToolTip("Angle of the helix ramp entry")
formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"),form.HelixAngle)
formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle)
#helix diam. limit
# helix diam. limit
form.HelixDiameterLimit = QtGui.QDoubleSpinBox()
form.HelixDiameterLimit.setMinimum(0.0)
form.HelixDiameterLimit.setMaximum(90)
form.HelixDiameterLimit.setSingleStep(0.1)
form.HelixDiameterLimit.setValue(0)
form.HelixDiameterLimit.setToolTip("If >0 it limits the helix ramp diameter\notherwise the 75 percent of tool diameter is used as helix diameter")
formLayout.addRow(QtGui.QLabel("Helix Max Diameter"),form.HelixDiameterLimit)
formLayout.addRow(QtGui.QLabel("Helix Max Diameter"), form.HelixDiameterLimit)
#lift distance
# lift distance
form.LiftDistance = QtGui.QDoubleSpinBox()
form.LiftDistance.setMinimum(0.0)
form.LiftDistance.setMaximum(1000)
form.LiftDistance.setSingleStep(0.1)
form.LiftDistance.setValue(1.0)
form.LiftDistance.setToolTip("How much to lift the tool up during the rapid linking moves over cleared regions.\nIf linking path is not clear tool is raised to clearence height.")
formLayout.addRow(QtGui.QLabel("Lift Distance"),form.LiftDistance)
formLayout.addRow(QtGui.QLabel("Lift Distance"), form.LiftDistance)
#KeepToolDownRatio
# KeepToolDownRatio
form.KeepToolDownRatio = QtGui.QDoubleSpinBox()
form.KeepToolDownRatio.setMinimum(1.0)
form.KeepToolDownRatio.setMaximum(10)
form.KeepToolDownRatio.setSingleStep(1)
form.KeepToolDownRatio.setValue(3.0)
form.KeepToolDownRatio.setToolTip("Max length of keep-tool-down linking path compared to direct distance between points.\nIf exceeded link will be done by raising the tool to clearence height.")
formLayout.addRow(QtGui.QLabel("Keep Tool Down Ratio"),form.KeepToolDownRatio)
formLayout.addRow(QtGui.QLabel("Keep Tool Down Ratio"), form.KeepToolDownRatio)
#stock to leave
# stock to leave
form.StockToLeave = QtGui.QDoubleSpinBox()
form.StockToLeave.setMinimum(0.0)
form.StockToLeave.setMaximum(1000)
form.StockToLeave.setSingleStep(0.1)
form.StockToLeave.setValue(0)
form.StockToLeave.setToolTip("How much material to leave (i.e. for finishing operation)")
formLayout.addRow(QtGui.QLabel("Stock to Leave"),form.StockToLeave)
formLayout.addRow(QtGui.QLabel("Stock to Leave"), form.StockToLeave)
# #process holes
# form.ProcessHoles = QtGui.QCheckBox()
# form.ProcessHoles.setChecked(True)
# formLayout.addRow(QtGui.QLabel("Process Holes"),form.ProcessHoles)
#Force inside out
# Force inside out
form.ForceInsideOut = QtGui.QCheckBox()
form.ForceInsideOut.setChecked(True)
formLayout.addRow(QtGui.QLabel("Force Clearing Inside-Out"),form.ForceInsideOut)
formLayout.addRow(QtGui.QLabel("Force Clearing Inside-Out"), form.ForceInsideOut)
layout.addLayout(formLayout)
#stop button
form.StopButton=QtGui.QPushButton("Stop")
# stop button
form.StopButton = QtGui.QPushButton("Stop")
form.StopButton.setCheckable(True)
layout.addWidget(form.StopButton)
@@ -145,7 +137,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
#signals.append(self.form.button.clicked)
# signals.append(self.form.button.clicked)
signals.append(self.form.Side.currentIndexChanged)
signals.append(self.form.OperationType.currentIndexChanged)
signals.append(self.form.ToolController.currentIndexChanged)
@@ -166,7 +158,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.selectInComboBox(obj.Side, self.form.Side)
self.selectInComboBox(obj.OperationType, self.form.OperationType)
self.form.StepOver.setValue(obj.StepOver)
self.form.Tolerance.setValue(int(obj.Tolerance*100))
self.form.Tolerance.setValue(int(obj.Tolerance * 100))
self.form.HelixAngle.setValue(obj.HelixAngle)
self.form.HelixDiameterLimit.setValue(obj.HelixDiameterLimit)
self.form.LiftDistance.setValue(obj.LiftDistance)
@@ -180,8 +172,8 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.ForceInsideOut.setChecked(obj.ForceInsideOut)
self.setupToolController(obj, self.form.ToolController)
self.form.StopButton.setChecked(obj.Stopped)
obj.setEditorMode('AdaptiveInputState', 2) #hide this property
obj.setEditorMode('AdaptiveOutputState', 2) #hide this property
obj.setEditorMode('AdaptiveInputState', 2) # hide this property
obj.setEditorMode('AdaptiveOutputState', 2) # hide this property
obj.setEditorMode('StopProcessing', 2) # hide this property
obj.setEditorMode('Stopped', 2) # hide this property
@@ -193,7 +185,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
obj.OperationType = str(self.form.OperationType.currentText())
obj.StepOver = self.form.StepOver.value()
obj.Tolerance = 1.0*self.form.Tolerance.value()/100.0
obj.Tolerance = 1.0 * self.form.Tolerance.value() / 100.0
obj.HelixAngle = self.form.HelixAngle.value()
obj.HelixDiameterLimit = self.form.HelixDiameterLimit.value()
obj.LiftDistance = self.form.LiftDistance.value()
@@ -204,25 +196,19 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
if hasattr(obj, 'StockToLeave'):
obj.StockToLeave = self.form.StockToLeave.value()
# obj.ProcessHoles = self.form.ProcessHoles.isChecked()
obj.ForceInsideOut = self.form.ForceInsideOut.isChecked()
obj.Stopped = self.form.StopButton.isChecked()
if(obj.Stopped):
self.form.StopButton.setChecked(False) #reset the button
obj.StopProcessing=True
self.form.StopButton.setChecked(False) # reset the button
obj.StopProcessing = True
self.updateToolController(obj, self.form.ToolController)
obj.setEditorMode('AdaptiveInputState', 2) #hide this property
obj.setEditorMode('AdaptiveOutputState', 2) #hide this property
obj.setEditorMode('AdaptiveInputState', 2) # hide this property
obj.setEditorMode('AdaptiveOutputState', 2) # hide this property
obj.setEditorMode('StopProcessing', 2) # hide this property
obj.setEditorMode('Stopped', 2) # hide this property
Command = PathOpGui.SetupOperation('Adaptive',
PathAdaptive.Create,
TaskPanelOpPage,
'Path-Adaptive',
QtCore.QT_TRANSLATE_NOOP("PathAdaptive", "Adaptive"),
QtCore.QT_TRANSLATE_NOOP("PathPocket", "Adaptive clearing and profiling"))
Command = PathOpGui.SetupOperation('Adaptive', PathAdaptive.Create, TaskPanelOpPage,
'Path-Adaptive', QtCore.QT_TRANSLATE_NOOP("PathAdaptive", "Adaptive"),
QtCore.QT_TRANSLATE_NOOP("PathPocket", "Adaptive clearing and profiling"))

View File

@@ -21,8 +21,13 @@
# * *
# ***************************************************************************/
from __future__ import print_function
import FreeCAD
from FreeCAD import Units
import datetime
import PathScripts
PostUtils = PathScripts.PostUtils
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 centroid 3 axis mill. This postprocessor, once placed
@@ -33,7 +38,7 @@ import centroid_post
centroid_post.export(object,"/path/to/file.ncc","")
'''
TOOLTIP_ARGS='''
TOOLTIP_ARGS = '''
Arguments for centroid:
--header,--no-header ... output headers (--header)
--comments,--no-comments ... output comments (--comments)
@@ -42,13 +47,6 @@ Arguments for centroid:
--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
import PathScripts
from PathScripts import PostUtils
#from PathScripts import PathUtils
now = datetime.datetime.now()
# These globals set common customization preferences
@@ -68,10 +66,10 @@ LINENR = 100 # line number starting value
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
AXIS_PRECISION=4
FEED_PRECISION=1
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
AXIS_PRECISION = 4
FEED_PRECISION = 1
SPINDLE_DECIMALS = 0
COMMENT = ";"
@@ -93,7 +91,7 @@ POSTAMBLE = '''M99
TOOLRETURN = '''M5 M25
G49 H0
''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
''' # spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
ZAXISRETURN = '''G91 G28 X0 Z0
G90
@@ -113,9 +111,10 @@ TOOL_CHANGE = ''''''
# to distinguish python built-in open function from the one declared below
if open.__module__ in ['__builtin__','io']:
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
def processArguments(argstring):
global OUTPUT_HEADER
global OUTPUT_COMMENTS
@@ -146,22 +145,14 @@ def processArguments(argstring):
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)
print(i.Name)
global UNITS
global UNIT_FORMAT
# ISJOB = (len(objectslist) == 1) and isinstance(objectslist[0].Proxy, PathScripts.PathJob.ObjectJob)
# 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 = ""
@@ -174,7 +165,7 @@ def export(objectslist, filename, argstring):
# Write the preamble
if OUTPUT_COMMENTS:
for item in objectslist:
if isinstance (item.Proxy, PathScripts.PathToolController.ToolController):
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):
@@ -183,11 +174,6 @@ def export(objectslist, filename, argstring):
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"
@@ -241,13 +227,14 @@ def linenumber():
return "N" + str(LINENR) + " "
return ""
def parse(pathobj):
global AXIS_PRECISION
global FEED_PRECISION
out = ""
lastcommand = None
axis_precision_string = '.' + str(AXIS_PRECISION) +'f'
feed_precision_string = '.' + str(FEED_PRECISION) +'f'
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.
@@ -269,10 +256,10 @@ def parse(pathobj):
# 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
commandlist = [] # list of elements in the command, code and params.
command = c.Name # command M or G code or comment string
if command[0]=='(':
if command[0] == '(':
command = PostUtils.fcoms(command, COMMENT)
commandlist.append(command)
@@ -286,10 +273,10 @@ def parse(pathobj):
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
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) )
param + format(float(speed.getValueAs(UNIT_FORMAT)), feed_precision_string))
elif param == 'H':
commandlist.append(param + str(int(c.Parameters['H'])))
elif param == 'S':
@@ -300,11 +287,10 @@ def parse(pathobj):
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(",",'')
#out += outstr + '\n'
outstr = outstr.replace('[', '')
outstr = outstr.replace(']', '')
outstr = outstr.replace("'", '')
outstr = outstr.replace(",", '')
# store the latest command
lastcommand = command

View File

@@ -1,44 +1,48 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* *
#* 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 *
#* *
#***************************************************************************
TOOLTIP=''' Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output. '''
# ***************************************************************************
# * *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * *
# * 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 *
# * *
# ***************************************************************************
import FreeCAD
import Path, PathScripts
from PathScripts import PostUtils
import Path
import PathScripts
PostUtils = PathScripts.PostUtils
TOOLTIP = ''' Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output. '''
SHOW_EDITOR = True
SHOW_EDITOR=True
def fmt(num):
fnum = ""
fnum += '%.3f' % (num)
return fnum
def ffmt(num):
fnum = ""
fnum += '%.1f' % (num)
return fnum
class saveVals(object):
''' save command info for modal output'''
def __init__(self, command):
@@ -48,54 +52,55 @@ class saveVals(object):
def retVals(self):
return self.com, self.params
def lineout(command, oldvals, modal):
line = ""
if modal and (oldvals.com == command.Name):
line +=""
line += ""
else:
line += str(command.Name)
if command.Name == 'M6':
line+= 'T'+str(int(command.Parameters['T']))
line += 'T' + str(int(command.Parameters['T']))
if command.Name == 'M3':
line+= 'S'+str(ffmt(command.Parameters['S']))
line += 'S' + str(ffmt(command.Parameters['S']))
if command.Name == 'M4':
line+= 'S'+str(ffmt(command.Parameters['S']))
line += 'S' + str(ffmt(command.Parameters['S']))
if 'X' in command.Parameters:
line += "X"+str(fmt(command.Parameters['X']))
line += "X" + str(fmt(command.Parameters['X']))
if 'Y' in command.Parameters:
line += "Y"+str(fmt(command.Parameters['Y']))
line += "Y" + str(fmt(command.Parameters['Y']))
if 'Z' in command.Parameters:
line += "Z"+str(fmt(command.Parameters['Z']))
line += "Z" + str(fmt(command.Parameters['Z']))
if 'I' in command.Parameters:
line += "I"+str(fmt(command.Parameters['I']))
line += "I" + str(fmt(command.Parameters['I']))
if 'J' in command.Parameters:
line += "J"+str(fmt(command.Parameters['J']))
line += "J" + str(fmt(command.Parameters['J']))
if 'F' in command.Parameters:
line += "F"+str(ffmt(command.Parameters['F']))
line += "F" + str(ffmt(command.Parameters['F']))
return line
def export(obj,filename,argstring):
modal=True
commands = obj[0]
def export(obj, filename, argstring):
modal = True
gcode = ''
safetyblock1 = 'G90G40G49\n'
gcode+=safetyblock1
gcode += safetyblock1
units = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
if units.GetInt('UserSchema') == 0:
firstcommand = Path.Command('G21') #metric mode
firstcommand = Path.Command('G21') # metric mode
else:
firstcommand = Path.Command('G20') #inch mode
oldvals = saveVals(firstcommand) #save first command for modal use
firstcommand = Path.Command('G20') # inch mode
oldvals = saveVals(firstcommand) # save first command for modal use
fp = obj[0]
gcode+= firstcommand.Name
gcode += firstcommand.Name
if hasattr(fp,"Path"):
if hasattr(fp, "Path"):
for c in fp.Path.Commands:
gcode+= lineout(c, oldvals, modal)+'\n'
gcode += lineout(c, oldvals, modal) + '\n'
oldvals = saveVals(c)
gcode+='M2\n'
gfile = open(filename,"w")
gcode += 'M2\n'
gfile = open(filename, "w")
gfile.write(gcode)
gfile.close()
else:
@@ -106,5 +111,3 @@ def export(obj,filename,argstring):
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
dia.exec_()

View File

@@ -36,15 +36,16 @@ import Path
import FreeCAD
import PathScripts.PathLog as PathLog
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
# LEVEL = PathLog.Level.DEBUG
LEVEL = PathLog.Level.INFO
PathLog.setLevel(LEVEL, PathLog.thisModule())
if LEVEL == PathLog.Level.DEBUG:
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__ in ['__builtin__','io']:
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
@@ -68,6 +69,7 @@ def insert(filename, docname):
path = Path.Path(gcode)
obj.Path = path
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print("preprocessing...")
@@ -77,31 +79,30 @@ def parse(inputstring):
lines = inputstring.split("\n")
output = ""
lastcommand = None
print (lines)
print(lines)
for l in lines:
for lin in lines:
# remove any leftover trailing and preceding spaces
l = l.strip()
if not l:
lin = lin.strip()
if not lin:
# discard empty lines
continue
if l[0].upper() in ["N"]:
if lin[0].upper() in ["N"]:
# remove line numbers
l = l.split(" ",1)
if len(l)>=1:
l = l[1]
lin = lin.split(" ", 1)
if len(lin) >= 1:
lin = lin[1]
else:
continue
if l[0] in ["(","%","#",";"]:
if lin[0] in ["(", "%", "#", ";"]:
# discard comment and other non strictly gcode lines
continue
if l[0].upper() in ["G","M"]:
if lin[0].upper() in ["G", "M"]:
# found a G or M command: we store it
output += l + "\n"
last = l[0].upper()
for c in l[1:]:
output += lin + "\n"
last = lin[0].upper()
for c in lin[1:]:
if not c.isdigit():
break
else:
@@ -109,7 +110,7 @@ def parse(inputstring):
lastcommand = last
elif lastcommand:
# no G or M command: we repeat the last one
output += lastcommand + " " + l + "\n"
output += lastcommand + " " + lin + "\n"
print("done preprocessing.")
return output

View File

@@ -1,29 +1,32 @@
#***************************************************************************
#* (c) imarin 2017 *
#* *
#* heavily based on gbrl post-procesor by: *
#* (c) sliptonic (shopinthewoods@gmail.com) 2014 *
#* *
#* 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 *
#* *
#***************************************************************************/
# ***************************************************************************
# * (c) imarin 2017 *
# * *
# * heavily based on gbrl post-procesor by: *
# * (c) sliptonic (shopinthewoods@gmail.com) 2014 *
# * *
# * 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 *
# * *
# ***************************************************************************/
import datetime
from PathScripts import PostUtils
now = datetime.datetime.now()
'''
Generate g-code compatible with fablin from a Path.
@@ -32,7 +35,7 @@ import fablin_post
fablin_post.export(object,"/path/to/file.ncc")
'''
TOOLTIP_ARGS='''
TOOLTIP_ARGS = '''
Arguments for fablin:
--rapids-feedrate ... feedrate to be used for rapids (e.g. --rapids-feedrate=300)
--header,--no-header ... output headers (--header)
@@ -41,59 +44,55 @@ Arguments for fablin:
--show-editor, --no-show-editor ... pop up editor before writing output(--show-editor)
'''
import datetime
now = datetime.datetime.now()
from PathScripts import PostUtils
#These globals set common customization preferences
OUTPUT_COMMENTS = False # Fablin does not support parenthesis, it will echo the command complaining. As a side effect the spinner may turn at a very reduced speed (do not ask me why).
OUTPUT_HEADER = False # Same as above. You can enable this by passing arguments to the post-processor in case you need them for example for debugging the gcode.
# These globals set common customization preferences
OUTPUT_COMMENTS = False # Fablin does not support parenthesis, it will echo the command complaining. As a side effect the spinner may turn at a very reduced speed (do not ask me why).
OUTPUT_HEADER = False # Same as above. You can enable this by passing arguments to the post-processor in case you need them for example for debugging the gcode.
OUTPUT_LINE_NUMBERS = False
OUTPUT_TOOL_CHANGE = False
SHOW_EDITOR = True
MODAL = False #if true commands are suppressed if the same as previous line.
MODAL = False # if true commands are suppressed if the same as previous line.
COMMAND_SPACE = " "
LINENR = 100 #line number starting value
LINENR = 100 # line number starting value
#These globals will be reflected in the Machine configuration of the project
UNITS = "" # only metric, G20/G21 is ignored
# These globals will be reflected in the Machine configuration of the project
UNITS = "" # only metric, G20/G21 is ignored
MACHINE_NAME = "FABLIN"
CORNER_MIN = {'x':0, 'y':0, 'z':0 }
CORNER_MAX = {'x':500, 'y':300, 'z':300 }
CORNER_MIN = {'x': 0, 'y': 0, 'z': 0}
CORNER_MAX = {'x': 500, 'y': 300, 'z': 300}
RAPID_MOVES = ['G0', 'G00']
#RAPID_MOVES = []
RAPID_FEEDRATE = 10000
#Preamble text will appear at the beginning of the GCODE output file.
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G90
'''
#Postamble text will appear following the last operation.
# Postamble text will appear following the last operation.
POSTAMBLE = '''M5
G00 X-1.0 Y1.0
G90
'''
# These commands are ignored by commenting them out
SUPPRESS_COMMANDS = [ 'G98', 'G80', 'M6', 'G17' ]
SUPPRESS_COMMANDS = ['G98', 'G80', 'M6', 'G17']
#Pre operation text will be inserted before every operation
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
#Post operation text will be inserted after every operation
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
#Tool Change commands will be inserted before a tool change
# 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__ in ['__builtin__','io']:
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
def processArguments(argstring):
global OUTPUT_HEADER
global OUTPUT_COMMENTS
@@ -123,64 +122,67 @@ def processArguments(argstring):
if params[0] == '--rapids-feedrate':
RAPID_FEEDRATE = params[1]
def export(objectslist,filename,argstring):
def export(objectslist, filename, argstring):
processArguments(argstring)
global UNITS
for obj in objectslist:
if not hasattr(obj,"Path"):
print ("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
return
print ("postprocessing...")
print("postprocessing...")
gcode = ""
#Find the machine.
#The user my have overridden post processor defaults in the GUI.
#Make sure we're using the current values in the Machine Def.
# Find the machine.
# The user my have overridden post processor defaults in the GUI.
# Make sure we're using the current values in the Machine Def.
myMachine = None
for pathobj in objectslist:
if hasattr(pathobj,"Group"): #We have a compound or project.
if hasattr(pathobj, "Group"): # We have a compound or project.
for p in pathobj.Group:
if p.Name == "Machine":
myMachine = p
if myMachine is None:
print ("No machine found in this project")
print("No machine found in this project")
else:
if myMachine.MachineUnits == "Metric":
UNITS = "G21"
UNITS = "G21"
else:
UNITS = "G20"
UNITS = "G20"
# write header
if OUTPUT_HEADER:
gcode += linenumber() + "(Exported by FreeCAD)\n"
gcode += linenumber() + "(Post Processor: " + __name__ +")\n"
gcode += linenumber() + "(Output Time:"+str(now)+")\n"
gcode += linenumber() + "(Post Processor: " + __name__ + ")\n"
gcode += linenumber() + "(Output Time:" + str(now) + ")\n"
#Write the preamble
if OUTPUT_COMMENTS: gcode += linenumber() + "(begin preamble)\n"
# Write the preamble
if OUTPUT_COMMENTS:
gcode += linenumber() + "(begin preamble)\n"
for line in PREAMBLE.splitlines(True):
gcode += linenumber() + line
#gcode += linenumber() + UNITS + "\n"
for obj in objectslist:
#do the pre_op
if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
# do the pre_op
if OUTPUT_COMMENTS:
gcode += linenumber() + "(begin operation: " + obj.Label + ")\n"
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
gcode += parse(obj)
#do the post_op
if OUTPUT_COMMENTS: gcode += linenumber() + "(finish operation: " + obj.Label + ")\n"
# do the post_op
if OUTPUT_COMMENTS:
gcode += linenumber() + "(finish operation: " + obj.Label + ")\n"
for line in POST_OPERATION.splitlines(True):
gcode += linenumber() + line
#do the post_amble
# do the post_amble
if OUTPUT_COMMENTS: gcode += "(begin postamble)\n"
if OUTPUT_COMMENTS:
gcode += "(begin postamble)\n"
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
@@ -195,38 +197,40 @@ def export(objectslist,filename,argstring):
else:
final = gcode
print ("done postprocessing.")
print("done postprocessing.")
gfile = pythonopen(filename,"w")
gfile.write(gcode)
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
def linenumber():
global LINENR
if OUTPUT_LINE_NUMBERS == True:
if OUTPUT_LINE_NUMBERS is True:
LINENR += 10
return "N" + str(LINENR) + " "
return ""
def parse(pathobj):
out = ""
lastcommand = None
#params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters
params = ['X','Y','Z','A','B','I','J','F','S','T','Q','R','L'] #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'] # linuxcnc doesn't want K properties on XY plane Arcs need work.
if hasattr(pathobj,"Group"): #We have a compound or project.
if OUTPUT_COMMENTS: out += linenumber() + "(compound: " + pathobj.Label + ")\n"
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
else: # parsing simple path
if not hasattr(pathobj,"Path"): #groups might contain non-path things like stock.
if not hasattr(pathobj, "Path"): # groups might contain non-path things like stock.
return out
if OUTPUT_COMMENTS: out += linenumber() + "(Path: " + pathobj.Label + ")\n"
if OUTPUT_COMMENTS:
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
for c in pathobj.Path.Commands:
outstring = []
@@ -234,16 +238,16 @@ def parse(pathobj):
# fablin does not support parenthesis syntax, so removing that (pocket) in the agnostic gcode
if command[0] == '(':
if not OUTPUT_COMMENTS: pass
if not OUTPUT_COMMENTS:
pass
else:
outstring.append(command)
# if modal: only print the command if it is not the same as the last one
if MODAL == True:
if MODAL is True:
if command == lastcommand:
outstring.pop(0)
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
@@ -263,7 +267,8 @@ def parse(pathobj):
# Check for Tool Change:
if command == 'M6':
if OUTPUT_COMMENTS: out += linenumber() + "(begin toolchange)\n"
if OUTPUT_COMMENTS:
out += linenumber() + "(begin toolchange)\n"
if not OUTPUT_TOOL_CHANGE:
outstring.insert(0, ";")
else:
@@ -271,20 +276,20 @@ def parse(pathobj):
out += linenumber() + line
if command == "message":
if OUTPUT_COMMENTS == False:
if OUTPUT_COMMENTS is False:
out = []
else:
outstring.pop(0) #remove the command
outstring.pop(0) # remove the command
if command in SUPPRESS_COMMANDS:
outstring = []
#prepend a line number and append a newline
# prepend a line number and append a newline
if len(outstring) >= 1:
if OUTPUT_LINE_NUMBERS:
outstring.insert(0,(linenumber()))
outstring.insert(0, (linenumber()))
#append the line to the final output
# append the line to the final output
for w in outstring:
out += w + COMMAND_SPACE
out = out.strip() + "\n"
@@ -292,5 +297,4 @@ def parse(pathobj):
return out
print (__name__ + " gcode postprocessor loaded.")
print(__name__ + " gcode postprocessor loaded.")