Consistent black formatting of all Path python files

This commit is contained in:
Markus Lampert
2022-02-28 21:06:16 -08:00
parent 8583894284
commit 934b0aaa07
93 changed files with 5216 additions and 3118 deletions

View File

@@ -41,7 +41,7 @@ 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 High-Z S-1000T 4 Axis Mill by CNC-Step (www.cnc-step.com) using the KineticNC Control Software. This postprocessor, once placed
@@ -50,21 +50,45 @@ FreeCAD, via the GUI importer or via python scripts with:
import KineticNC_post
KineticNC_post.export(object,"/path/to/file.ncc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='linuxcnc', add_help=False)
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
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='Output the Same G-command Name USE NonModal Mode')
parser.add_argument('--axis-modal', action='store_true', help='Output the Same Axis Value Mode')
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
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="Output the Same G-command Name USE NonModal Mode",
)
parser.add_argument(
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
)
TOOLTIP_ARGS = parser.format_help()
@@ -74,44 +98,46 @@ 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 # if false duplicate axis values are suppressed if the same as previous line.
OUTPUT_DOUBLES = (
True # if false duplicate axis values are suppressed if the same as previous line.
)
COMMAND_SPACE = " "
LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "High-Z S-1000T"
CORNER_MIN = {'x': 0, 'y': 0, 'z': 0}
CORNER_MAX = {'x': 1000, 'y': 600, 'z': 300}
CORNER_MIN = {"x": 0, "y": 0, "z": 0}
CORNER_MAX = {"x": 1000, "y": 600, "z": 300}
PRECISION = 3
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''%
PREAMBLE = """%
G17 G21 G40 G49 G80 G90
M08
'''
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05 M09
POSTAMBLE = """M05 M09
G17 G90 G80 G40
M30
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
TOOL_CHANGE = '''M05
M09'''
TOOL_CHANGE = """M05
M09"""
# 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
@@ -146,14 +172,14 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
if args.modal:
MODAL = True
if args.axis_modal:
print ('here')
print("here")
OUTPUT_DOUBLES = False
except:
@@ -171,7 +197,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("postprocessing...")
@@ -195,7 +225,7 @@ def export(objectslist, filename, argstring):
# fetch machine details
job = PathUtils.findParentJob(obj)
myMachine = 'not set'
myMachine = "not set"
if hasattr(job, "MachineName"):
myMachine = job.MachineName
@@ -203,17 +233,20 @@ def export(objectslist, filename, argstring):
if hasattr(job, "MachineUnits"):
if job.MachineUnits == "Metric":
UNITS = "G21"
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = "mm"
UNIT_SPEED_FORMAT = "mm/min"
else:
UNITS = "G20"
UNIT_FORMAT = 'in'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = "in"
UNIT_SPEED_FORMAT = "in/min"
# do the pre_op
if OUTPUT_COMMENTS:
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT)
gcode += linenumber() + "(machine: %s, %s)\n" % (
myMachine,
UNIT_SPEED_FORMAT,
)
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
@@ -244,7 +277,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -269,12 +302,30 @@ def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
# the order of parameters
# linuxcnc doesn't want K properties on XY plane Arcs need work.
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -304,41 +355,64 @@ def parse(pathobj):
if command == lastcommand:
outstring.pop(0)
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
if c.Name not in ["G0", "G00"]: # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F" and (
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
):
if c.Name not in [
"G0",
"G00",
]: # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
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'])))
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:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# if OUTPUT_COMMENTS:
# out += linenumber() + "(begin toolchange)\n"
for line in TOOL_CHANGE.splitlines(True):
@@ -362,4 +436,5 @@ def parse(pathobj):
return out
print(__name__ + " gcode postprocessor loaded.")

View File

@@ -30,7 +30,7 @@ import datetime
import PathScripts
import PathScripts.PostUtils as 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
@@ -39,9 +39,9 @@ FreeCAD, via the GUI importer or via python scripts with:
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)
@@ -50,7 +50,7 @@ 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
--inches ... Convert output for US imperial mode (G20)
'''
"""
now = datetime.datetime.now()
# These globals set common customization preferences
@@ -68,56 +68,58 @@ LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = "mm"
UNIT_SPEED_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
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 = ";"
HEADER = '''
HEADER = """
;Exported by FreeCAD
;Post Processor: {}
;CAM file: {}
;Output Time: {}
'''.format(__name__, FreeCAD.ActiveDocument.FileName, str(now))
""".format(
__name__, FreeCAD.ActiveDocument.FileName, str(now)
)
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G53 G00 G17
'''
PREAMBLE = """G53 G00 G17
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M99
'''
POSTAMBLE = """M99
"""
TOOLRETURN = '''M5
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
ZAXISRETURN = """G91 G28 X0 Z0
G90
'''
"""
SAFETYBLOCK = '''G90 G80 G40 G49
'''
SAFETYBLOCK = """G90 G80 G40 G49
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -133,30 +135,30 @@ def processArguments(argstring):
global UNITS
for arg in argstring.split():
if arg == '--header':
if arg == "--header":
OUTPUT_HEADER = True
elif arg == '--no-header':
elif arg == "--no-header":
OUTPUT_HEADER = False
elif arg == '--comments':
elif arg == "--comments":
OUTPUT_COMMENTS = True
elif arg == '--no-comments':
elif arg == "--no-comments":
OUTPUT_COMMENTS = False
elif arg == '--line-numbers':
elif arg == "--line-numbers":
OUTPUT_LINE_NUMBERS = True
elif arg == '--no-line-numbers':
elif arg == "--no-line-numbers":
OUTPUT_LINE_NUMBERS = False
elif arg == '--show-editor':
elif arg == "--show-editor":
SHOW_EDITOR = True
elif arg == '--no-show-editor':
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]
elif arg == '--inches':
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
elif arg.split("=")[0] == "--axis-precision":
AXIS_PRECISION = arg.split("=")[1]
elif arg.split("=")[0] == "--feed-precision":
FEED_PRECISION = arg.split("=")[1]
elif arg == "--inches":
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
def export(objectslist, filename, argstring):
@@ -179,7 +181,9 @@ def export(objectslist, filename, argstring):
# Write the preamble
if OUTPUT_COMMENTS:
for item in objectslist:
if hasattr(item, "Proxy") and isinstance(item.Proxy, PathScripts.PathToolController.ToolController):
if hasattr(item, "Proxy") and 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):
@@ -226,7 +230,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -245,12 +249,12 @@ def linenumber():
def parse(pathobj):
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.
params = ['X', 'Y', 'Z', 'A', 'B', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H']
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L", "H"]
if hasattr(pathobj, "Group"): # We have a compound or project.
# if OUTPUT_COMMENTS:
@@ -271,7 +275,7 @@ def parse(pathobj):
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)
@@ -284,32 +288,50 @@ 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"]: # centroid doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
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_SPEED_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'])))
param
+ format(
float(speed.getValueAs(UNIT_SPEED_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:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
commandlist.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), axis_precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
axis_precision_string,
)
)
outstr = str(commandlist)
outstr = outstr.replace('[', '')
outstr = outstr.replace(']', '')
outstr = outstr.replace("'", '')
outstr = outstr.replace(",", '')
outstr = outstr.replace("[", "")
outstr = outstr.replace("]", "")
outstr = outstr.replace("'", "")
outstr = outstr.replace(",", "")
# store the latest command
lastcommand = command
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# if OUTPUT_COMMENTS:
# out += linenumber() + "(begin toolchange)\n"
for line in TOOL_CHANGE.splitlines(True):

View File

@@ -24,25 +24,26 @@ import FreeCAD
import Path
import PathScripts.PostUtils as PostUtils
TOOLTIP = '''Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output.'''
TOOLTIP = """Example Post, using Path.Commands instead of Path.toGCode strings for Path gcode output."""
SHOW_EDITOR = True
def fmt(num):
fnum = ""
fnum += '%.3f' % (num)
fnum += "%.3f" % (num)
return fnum
def ffmt(num):
fnum = ""
fnum += '%.1f' % (num)
fnum += "%.1f" % (num)
return fnum
class saveVals(object):
''' save command info for modal output'''
"""save command info for modal output"""
def __init__(self, command):
self.com = command.Name
self.params = command.Parameters
@@ -57,55 +58,55 @@ def lineout(command, oldvals, modal):
line += ""
else:
line += str(command.Name)
if command.Name == 'M6':
line += 'T' + str(int(command.Parameters['T']))
if command.Name == 'M3':
line += 'S' + str(ffmt(command.Parameters['S']))
if command.Name == 'M4':
line += 'S' + str(ffmt(command.Parameters['S']))
if 'X' in command.Parameters:
line += "X" + str(fmt(command.Parameters['X']))
if 'Y' in command.Parameters:
line += "Y" + str(fmt(command.Parameters['Y']))
if 'Z' in command.Parameters:
line += "Z" + str(fmt(command.Parameters['Z']))
if 'I' in command.Parameters:
line += "I" + str(fmt(command.Parameters['I']))
if 'J' in command.Parameters:
line += "J" + str(fmt(command.Parameters['J']))
if 'F' in command.Parameters:
line += "F" + str(ffmt(command.Parameters['F']))
if command.Name == "M6":
line += "T" + str(int(command.Parameters["T"]))
if command.Name == "M3":
line += "S" + str(ffmt(command.Parameters["S"]))
if command.Name == "M4":
line += "S" + str(ffmt(command.Parameters["S"]))
if "X" in command.Parameters:
line += "X" + str(fmt(command.Parameters["X"]))
if "Y" in command.Parameters:
line += "Y" + str(fmt(command.Parameters["Y"]))
if "Z" in command.Parameters:
line += "Z" + str(fmt(command.Parameters["Z"]))
if "I" in command.Parameters:
line += "I" + str(fmt(command.Parameters["I"]))
if "J" in command.Parameters:
line += "J" + str(fmt(command.Parameters["J"]))
if "F" in command.Parameters:
line += "F" + str(ffmt(command.Parameters["F"]))
return line
def export(obj, filename, argstring):
modal = True
gcode = ''
safetyblock1 = 'G90G40G49\n'
gcode = ""
safetyblock1 = "G90G40G49\n"
gcode += safetyblock1
units = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units")
if units.GetInt('UserSchema') == 0:
firstcommand = Path.Command('G21') # metric mode
if units.GetInt("UserSchema") == 0:
firstcommand = Path.Command("G21") # metric mode
else:
firstcommand = Path.Command('G20') # inch mode
firstcommand = Path.Command("G20") # inch mode
oldvals = saveVals(firstcommand) # save first command for modal use
fp = obj[0]
gcode += firstcommand.Name
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'
gcode += "M2\n"
gfile = open(filename, "w")
gfile.write(gcode)
gfile.close()
else:
FreeCAD.Console.PrintError('Select a path object and try again\n')
FreeCAD.Console.PrintError("Select a path object and try again\n")
if SHOW_EDITOR:
FreeCAD.Console.PrintMessage('Editor Activated\n')
FreeCAD.Console.PrintMessage("Editor Activated\n")
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
dia.exec_()

View File

@@ -23,12 +23,12 @@
from __future__ import print_function
TOOLTIP='''
TOOLTIP = """
Dumper is an extremely simple postprocessor file for the Path workbench. It is used
to dump the command list from one or more Path objects for simple inspection. This post
doesn't do any manipulation of the path and doesn't write anything to disk. It just
shows the dialog so you can see it. Useful for debugging, but not much else.
'''
"""
import datetime
from PathScripts import PostUtils
@@ -36,25 +36,29 @@ now = datetime.datetime.now()
SHOW_EDITOR = True
# 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 export(objectslist, filename,argstring):
def export(objectslist, filename, argstring):
"called when freecad exports a list of objects"
output = '''(This output produced with the dump post processor)
output = """(This output produced with the dump post processor)
(Dump is useful for inspecting the raw commands in your paths)
(but is not useful for driving machines.)
(Consider setting a default postprocessor in your project or )
(exporting your paths using a specific post that matches your machine)
'''
"""
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return
print("postprocessing...")
output += parse(obj)
@@ -84,7 +88,9 @@ def parse(pathobj):
return out
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
out += "(Path: " + pathobj.Label + ")\n"
@@ -93,4 +99,5 @@ def parse(pathobj):
out += str(c) + "\n"
return out
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -38,7 +38,7 @@ import datetime
import shlex
from PathScripts import 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 Tree Journyman 325 3 axis mill with Dynapath 20
@@ -70,26 +70,35 @@ spaces add / prior to comments
import dynapath_post
dynapath_post.export(object,"/path/to/file.ncc","")
'''
"""
parser = argparse.ArgumentParser(prog='dynapath_post',
add_help=False)
parser.add_argument('--no-header', action='store_true',
help='suppress header output')
parser.add_argument('--no-comments', action='store_true',
help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true',
help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true',
help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3',
help='number of digits of precision, default=3')
parser.add_argument('--preamble',
help='set commands to be issued before the first command, default="G17\nG90\nG80\nG40"')
parser.add_argument('--postamble',
help='set commands to be issued after the last command, default="M09\nM05\nG80\nG40\nG17\nG90\nM30"')
parser.add_argument('--inches', action='store_true',
help='Convert output for US imperial mode (G20)')
parser = argparse.ArgumentParser(prog="dynapath_post", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
parser.add_argument(
"--preamble",
help='set commands to be issued before the first command, default="G17\nG90\nG80\nG40"',
)
parser.add_argument(
"--postamble",
help='set commands to be issued after the last command, default="M09\nM05\nG80\nG40\nG17\nG90\nM30"',
)
parser.add_argument(
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
)
TOOLTIP_ARGS = parser.format_help()
@@ -105,46 +114,46 @@ MODAL = False # if true commands are suppressed if the same as previous line.
COMMAND_SPACE = " "
LINENR = 1 # line number starting value
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
MACHINE_NAME = "Tree MM"
CORNER_MIN = {'x': -340, 'y': 0, 'z': 0}
CORNER_MAX = {'x': 340, 'y': -355, 'z': -150}
CORNER_MIN = {"x": -340, "y": 0, "z": 0}
CORNER_MAX = {"x": 340, "y": -355, "z": -150}
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17
PREAMBLE = """G17
G90
;G90.1 ;needed for simulation only
G80
G40
'''
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M09
POSTAMBLE = """M09
M05
G80
G40
G17
G90
M30
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -180,9 +189,9 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
except Exception:
@@ -200,7 +209,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return
print("postprocessing...")
@@ -224,7 +237,7 @@ def export(objectslist, filename, argstring):
if OUTPUT_HEADER:
gcode += linenumber() + "(Exported by FreeCAD)\n"
gcode += linenumber() + "(Post Processor: " + __name__ + ")\n"
gcode += linenumber() + "(Output Time:"+str(now)+")\n"
gcode += linenumber() + "(Output Time:" + str(now) + ")\n"
# Write the preamble
if OUTPUT_COMMENTS:
@@ -256,7 +269,7 @@ def export(objectslist, filename, argstring):
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
print('show editor: {}'.format(SHOW_EDITOR))
print("show editor: {}".format(SHOW_EDITOR))
if FreeCAD.GuiUp and SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
@@ -291,10 +304,10 @@ def parse(pathobj):
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
params = ['X', 'Y', 'Z', 'A', 'B', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L']
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L"]
if hasattr(pathobj, "Group"): # We have a compound or project.
if OUTPUT_COMMENTS:
@@ -304,7 +317,9 @@ def parse(pathobj):
return out
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:
@@ -322,24 +337,40 @@ def parse(pathobj):
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F':
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F":
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
elif param == 'S':
outstring.append(param + format(c.Parameters[param], precision_string))
elif param == 'T':
outstring.append(param + format(c.Parameters['T'], precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
elif param == "S":
outstring.append(
param + format(c.Parameters[param], precision_string)
)
elif param == "T":
outstring.append(
param + format(c.Parameters["T"], precision_string)
)
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# store the latest command
lastcommand = command
# Check for Tool Change:
if command == 'M6':
if command == "M6":
if OUTPUT_COMMENTS:
out += linenumber() + "(begin toolchange)\n"
for line in TOOL_CHANGE.splitlines(True):

View File

@@ -23,24 +23,25 @@
from __future__ import print_function
TOOLTIP='''
TOOLTIP = """
This is an example postprocessor file for the Path workbench. It is used
to save a list of FreeCAD Path objects to a file.
Read the Path Workbench documentation to know how to convert Path objects
to GCode.
'''
"""
import datetime
now = datetime.datetime.now()
# 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 export(objectslist, filename,argstring):
def export(objectslist, filename, argstring):
"called when freecad exports a list of objects"
if len(objectslist) > 1:
print("This script is unable to write more than one Path object")
@@ -62,7 +63,7 @@ def parse(inputstring):
output = ""
# write some stuff first
output += "N10 ;time:"+str(now)+"\n"
output += "N10 ;time:" + str(now) + "\n"
output += "N20 G17 G20 G80 G40 G90\n"
output += "N30 (Exported by FreeCAD)\n"
@@ -99,4 +100,5 @@ def parse(inputstring):
print("done postprocessing.")
return output
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -22,7 +22,7 @@
# ***************************************************************************
'''
"""
This is an example preprocessor file for the Path workbench. Its aim is to
open a gcode file, parse its contents, and create the appropriate objects
in FreeCAD. This preprocessor will not add imported gcode to an existing
@@ -30,7 +30,7 @@ job. For a more useful preprocessor, look at the gcode_pre.py file
Read the Path Workbench documentation to know how to create Path objects
from GCode.
'''
"""
import os
import Path
@@ -46,7 +46,7 @@ if LEVEL == PathLog.Level.DEBUG:
# 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

View File

@@ -26,23 +26,24 @@
import datetime
from PathScripts import PostUtils
now = datetime.datetime.now()
'''
"""
Generate g-code compatible with fablin from a Path.
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)
--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)
'''
"""
# 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).
@@ -58,38 +59,38 @@ 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
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 = ["G0", "G00"]
RAPID_FEEDRATE = 10000
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G90
'''
PREAMBLE = """G90
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M5
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 = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -100,26 +101,26 @@ def processArguments(argstring):
global SHOW_EDITOR
global RAPID_FEEDRATE
for arg in argstring.split():
if arg == '--header':
if arg == "--header":
OUTPUT_HEADER = True
elif arg == '--no-header':
elif arg == "--no-header":
OUTPUT_HEADER = False
elif arg == '--comments':
elif arg == "--comments":
OUTPUT_COMMENTS = True
elif arg == '--no-comments':
elif arg == "--no-comments":
OUTPUT_COMMENTS = False
elif arg == '--line-numbers':
elif arg == "--line-numbers":
OUTPUT_LINE_NUMBERS = True
elif arg == '--no-line-numbers':
elif arg == "--no-line-numbers":
OUTPUT_LINE_NUMBERS = False
elif arg == '--show-editor':
elif arg == "--show-editor":
SHOW_EDITOR = True
elif arg == '--no-show-editor':
elif arg == "--no-show-editor":
SHOW_EDITOR = False
params = arg.split('=')
params = arg.split("=")
if params[0] == '--rapids-feedrate':
if params[0] == "--rapids-feedrate":
RAPID_FEEDRATE = params[1]
@@ -128,7 +129,11 @@ def export(objectslist, filename, 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.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return
print("postprocessing...")
@@ -216,7 +221,21 @@ def parse(pathobj):
out = ""
lastcommand = None
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:
@@ -226,7 +245,9 @@ def parse(pathobj):
return out
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:
@@ -237,7 +258,7 @@ def parse(pathobj):
command = c.Name
# fablin does not support parenthesis syntax, so removing that (pocket) in the agnostic gcode
if command[0] == '(':
if command[0] == "(":
if not OUTPUT_COMMENTS:
pass
else:
@@ -251,22 +272,22 @@ def parse(pathobj):
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F':
if param == "F":
if command not in RAPID_MOVES:
outstring.append(param + format(c.Parameters['F'], '.2f'))
elif param == 'T':
outstring.append(param + str(c.Parameters['T']))
outstring.append(param + format(c.Parameters["F"], ".2f"))
elif param == "T":
outstring.append(param + str(c.Parameters["T"]))
else:
outstring.append(param + format(c.Parameters[param], '.4f'))
outstring.append(param + format(c.Parameters[param], ".4f"))
if command in RAPID_MOVES and command != lastcommand:
outstring.append('F' + format(RAPID_FEEDRATE))
outstring.append("F" + format(RAPID_FEEDRATE))
# store the latest command
lastcommand = command
# Check for Tool Change:
if command == 'M6':
if command == "M6":
if OUTPUT_COMMENTS:
out += linenumber() + "(begin toolchange)\n"
if not OUTPUT_TOOL_CHANGE:

View File

@@ -31,7 +31,7 @@ import shlex
import os.path
from PathScripts import 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 should be suitable for most Fanuc controllers.
@@ -42,22 +42,50 @@ python scripts with:
import fanuc_post
fanuc_post.export(object,"/path/to/file.ncc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='fanuc', add_help=False)
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
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('--no-modal', action='store_true', help='Don\'t output the Same G-command Name USE NonModal Mode')
parser.add_argument('--no-axis-modal', action='store_true', help='Don\'t output the Same Axis Value Mode')
parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes')
parser = argparse.ArgumentParser(prog="fanuc", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
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(
"--no-modal",
action="store_true",
help="Don't output the Same G-command Name USE NonModal Mode",
)
parser.add_argument(
"--no-axis-modal", action="store_true", help="Don't output the Same Axis Value Mode"
)
parser.add_argument(
"--no-tlo",
action="store_true",
help="suppress tool length offset (G43) following tool changes",
)
TOOLTIP_ARGS = parser.format_help()
@@ -67,19 +95,21 @@ OUTPUT_HEADER = True
OUTPUT_LINE_NUMBERS = False
SHOW_EDITOR = True
MODAL = True # if true commands are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = False # if false duplicate axis values are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = (
False # if false duplicate axis values are suppressed if the same as previous line.
)
COMMAND_SPACE = " "
LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "fanuc"
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}
PRECISION = 3
# this global is used to pass spindle speed from the tool command into the machining command for
@@ -87,27 +117,27 @@ PRECISION = 3
tapSpeed = 0
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17 G54 G40 G49 G80 G90
'''
PREAMBLE = """G17 G54 G40 G49 G80 G90
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05
POSTAMBLE = """M05
G17 G54 G90 G80 G40
M6 T0
M2
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -143,9 +173,9 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
if args.no_modal:
MODAL = False
@@ -171,7 +201,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("postprocessing...")
@@ -181,8 +215,15 @@ def export(objectslist, filename, argstring):
if OUTPUT_HEADER:
gcode += "%\n"
gcode += ";\n"
gcode += os.path.split(filename)[-1]+" ("+"FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE"+")\n"
gcode += linenumber() + "("+filename.upper()+",EXPORTED BY FREECAD!)\n"
gcode += (
os.path.split(filename)[-1]
+ " ("
+ "FREECAD-FILENAME-GOES-HERE"
+ ", "
+ "JOB-NAME-GOES-HERE"
+ ")\n"
)
gcode += linenumber() + "(" + filename.upper() + ",EXPORTED BY FREECAD!)\n"
gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n"
gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\n"
@@ -196,23 +237,29 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
# Skip inactive operations
if hasattr(obj, 'Active'):
if hasattr(obj, "Active"):
if not obj.Active:
continue
if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'):
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
if not obj.Base.Active:
continue
# do the pre_op
if OUTPUT_COMMENTS:
gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper()
gcode += linenumber() + "(MACHINE UNITS: %s)\n" % (UNIT_SPEED_FORMAT.upper())
gcode += linenumber() + "(MACHINE UNITS: %s)\n" % (
UNIT_SPEED_FORMAT.upper()
)
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
# get coolant mode
coolantMode = 'None'
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
coolantMode = "None"
if (
hasattr(obj, "CoolantMode")
or hasattr(obj, "Base")
and hasattr(obj.Base, "CoolantMode")
):
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
else:
@@ -220,12 +267,12 @@ def export(objectslist, filename, argstring):
# turn coolant on if required
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + '(COOLANT ON:' + coolantMode.upper() + ')\n'
if coolantMode == 'Flood':
gcode += linenumber() + 'M8' + '\n'
if coolantMode == 'Mist':
gcode += linenumber() + 'M7' + '\n'
if not coolantMode == "None":
gcode += linenumber() + "(COOLANT ON:" + coolantMode.upper() + ")\n"
if coolantMode == "Flood":
gcode += linenumber() + "M8" + "\n"
if coolantMode == "Mist":
gcode += linenumber() + "M7" + "\n"
# process the operation gcode
gcode += parse(obj)
@@ -237,10 +284,10 @@ def export(objectslist, filename, argstring):
gcode += linenumber() + line
# turn coolant off if required
if not coolantMode == 'None':
if not coolantMode == "None":
if OUTPUT_COMMENTS:
gcode += linenumber() + '(COOLANT OFF:' + coolantMode.upper() + ')\n'
gcode += linenumber() +'M9' + '\n'
gcode += linenumber() + "(COOLANT OFF:" + coolantMode.upper() + ")\n"
gcode += linenumber() + "M9" + "\n"
# do the post_amble
if OUTPUT_COMMENTS:
@@ -262,7 +309,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -288,14 +335,32 @@ def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
print("Startup!")
# the order of parameters
# arcs need work. original code from mach3_4 doesn't want K properties on XY plane. Not sure
# what fanuc does here.
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -318,33 +383,47 @@ def parse(pathobj):
opHorizRapid = 0
opVertRapid = 0
if 'Adaptive' in pathobj.Name:
if "Adaptive" in pathobj.Name:
adaptiveOp = True
if hasattr(pathobj, 'ToolController'):
if hasattr(pathobj.ToolController, 'HorizRapid') and pathobj.ToolController.HorizRapid > 0:
opHorizRapid = Units.Quantity(pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity)
if hasattr(pathobj, "ToolController"):
if (
hasattr(pathobj.ToolController, "HorizRapid")
and pathobj.ToolController.HorizRapid > 0
):
opHorizRapid = Units.Quantity(
pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity
)
else:
FreeCAD.Console.PrintWarning('Tool Controller Horizontal Rapid Values are unset'+ '\n')
FreeCAD.Console.PrintWarning(
"Tool Controller Horizontal Rapid Values are unset" + "\n"
)
if hasattr(pathobj.ToolController, 'VertRapid') and pathobj.ToolController.VertRapid > 0:
opVertRapid = Units.Quantity(pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity)
if (
hasattr(pathobj.ToolController, "VertRapid")
and pathobj.ToolController.VertRapid > 0
):
opVertRapid = Units.Quantity(
pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity
)
else:
FreeCAD.Console.PrintWarning('Tool Controller Vertical Rapid Values are unset'+ '\n')
FreeCAD.Console.PrintWarning(
"Tool Controller Vertical Rapid Values are unset" + "\n"
)
for index,c in enumerate(pathobj.Path.Commands):
for index, c in enumerate(pathobj.Path.Commands):
outstring = []
command = c.Name
if index+1 == len(pathobj.Path.Commands):
nextcommand = ""
if index + 1 == len(pathobj.Path.Commands):
nextcommand = ""
else:
nextcommand = pathobj.Path.Commands[index+1].Name
nextcommand = pathobj.Path.Commands[index + 1].Name
if adaptiveOp and c.Name in ["G0", "G00"]:
if opHorizRapid and opVertRapid:
command = 'G1'
command = "G1"
else:
outstring.append('(TOOL CONTROLLER RAPID VALUES ARE UNSET)' + '\n')
outstring.append("(TOOL CONTROLLER RAPID VALUES ARE UNSET)" + "\n")
# suppress moves in fixture selection
if pathobj.Label == "Fixture":
@@ -359,45 +438,82 @@ def parse(pathobj):
# convert drill cycles to tap cycles if tool is a tap
if command == "G81" or command == "G83":
if hasattr(pathobj, 'ToolController') and pathobj.ToolController.Tool.ToolType == "Tap":
if (
hasattr(pathobj, "ToolController")
and pathobj.ToolController.Tool.ToolType == "Tap"
):
command = "G84"
out += linenumber() + "G95\n"
paramstring = ""
for param in [ "X", "Y" ]:
for param in ["X", "Y"]:
if param in c.Parameters:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
if paramstring != "":
out += linenumber() + "G00"+paramstring+"\n"
out += linenumber() + "G00" + paramstring + "\n"
if "S" in c.Parameters:
tapSpeed = int(c.Parameters['S'])
out += "M29 S"+str(tapSpeed)+"\n"
tapSpeed = int(c.Parameters["S"])
out += "M29 S" + str(tapSpeed) + "\n"
for param in [ "Z", "R" ]:
for param in ["Z", "R"]:
if param in c.Parameters:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
# in this mode, F is the distance per revolution of the thread (pitch)
# P is the dwell time in seconds at the bottom of the thread
# Q is the peck depth of the threading operation
for param in [ "F", "P", "Q" ]:
for param in ["F", "P", "Q"]:
if param in c.Parameters:
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += " " + param + format(float(value.getValueAs(UNIT_FORMAT)), precision_string)
value = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
paramstring += (
" "
+ param
+ format(
float(value.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
out += linenumber() + "G84" + paramstring + "\n"
out += linenumber() + "G80\n"
out += linenumber() + "G94\n"
continue
outstring.append(command)
# if modal: suppress the command if it is the same as the last one
@@ -409,48 +525,83 @@ def parse(pathobj):
if command == "G80" and lastcommand == nextcommand:
continue
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
if c.Name not in ["G0", "G00"]: # fanuc doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F" and (
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
):
if c.Name not in [
"G0",
"G00",
]: # fanuc doesn't use rapid speeds
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
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'])))
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:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
if adaptiveOp and c.Name in ["G0", "G00"]:
if opHorizRapid and opVertRapid:
if 'Z' not in c.Parameters:
outstring.append('F' + format(float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
if "Z" not in c.Parameters:
outstring.append(
"F"
+ format(
float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
outstring.append('F' + format(float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
"F"
+ format(
float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# stop the spindle
out += linenumber() + "M5\n"
for line in TOOL_CHANGE.splitlines(True):
@@ -458,7 +609,7 @@ def parse(pathobj):
# add height offset
if USE_TLO:
tool_height = '\nG43 H' + str(int(c.Parameters['T']))
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
outstring.append(tool_height)
if command == "message":
@@ -479,4 +630,5 @@ def parse(pathobj):
return out
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -580,7 +580,7 @@ def parse(pathobj):
out += linenumber() + format_outstring(outstring) + "\n"
# Check for comments containing machine-specific commands to pass literally to the controller
m = re.match(r'^\(MC_RUN_COMMAND: ([^)]+)\)$', command)
m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command)
if m:
raw_command = m.group(1)
out += linenumber() + raw_command + "\n"

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ import datetime
import shlex
from PathScripts import 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 jtech photonics laser. This postprocessor, once placed
@@ -39,22 +39,50 @@ FreeCAD, via the GUI importer or via python scripts with:
import jtech_post
jtech_post.export(object,"/path/to/file.ngc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='jtech', add_help=False)
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
parser.add_argument('--preamble', help='set commands to be issued before the first command, default="M05 S0\nG90"')
parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05 S0\nM2"')
parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)')
parser.add_argument('--modal', action='store_true', help='Output the Same G-command Name USE NonModal Mode')
parser.add_argument('--axis-modal', action='store_true', help='Output the Same Axis Value Mode')
parser.add_argument('--power-on-delay', default='255', help='milliseconds - Add a delay after laser on before moving to pre-heat material. Default=0')
parser = argparse.ArgumentParser(prog="jtech", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
parser.add_argument(
"--preamble",
help='set commands to be issued before the first command, default="M05 S0\nG90"',
)
parser.add_argument(
"--postamble",
help='set commands to be issued after the last command, default="M05 S0\nM2"',
)
parser.add_argument(
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
)
parser.add_argument(
"--modal",
action="store_true",
help="Output the Same G-command Name USE NonModal Mode",
)
parser.add_argument(
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
)
parser.add_argument(
"--power-on-delay",
default="255",
help="milliseconds - Add a delay after laser on before moving to pre-heat material. Default=0",
)
TOOLTIP_ARGS = parser.format_help()
@@ -65,48 +93,50 @@ 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 # if false duplicate axis values are suppressed if the same as previous line.
OUTPUT_DOUBLES = (
True # if false duplicate axis values are suppressed if the same as previous line.
)
COMMAND_SPACE = " "
LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "JTECH Photonic Laser"
PRECISION = 3
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''M05 S0
PREAMBLE = """M05 S0
G90
'''
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05 S0
POSTAMBLE = """M05 S0
M2
'''
"""
PRE_FEED = '''M03
PRE_FEED = """M03
G4 P{}
'''
"""
POST_FEED = '''M05
'''
POST_FEED = """M05
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
TOOL_CHANGE = ''''''
TOOL_CHANGE = """"""
POWER_ON_DELAY = 0
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
if open.__module__ == "__builtin__":
pythonopen = open
@@ -142,9 +172,9 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
if args.modal:
MODAL = True
@@ -164,7 +194,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("postprocessing...")
@@ -218,7 +252,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "wb")
gfile.write(final)
gfile.close()
@@ -237,12 +271,30 @@ def linenumber():
def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
RAPID_MOVES = ["G0", "G00"]
FEED_MOVES = ["G1", "G01", "G2", "G02", "G3", "G03"]
# the order of parameters
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -276,41 +328,63 @@ def parse(pathobj):
if command == lastcommand:
outstring.pop(0)
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
if c.Name not in RAPID_MOVES: # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F" and (
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
):
if (
c.Name not in RAPID_MOVES
): # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
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'])))
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:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
continue
if command == "message":

View File

@@ -30,7 +30,7 @@ import datetime
import shlex
from PathScripts import 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 linuxcnc 3 axis mill. This postprocessor, once placed
@@ -39,22 +39,50 @@ FreeCAD, via the GUI importer or via python scripts with:
import linuxcnc_post
linuxcnc_post.export(object,"/path/to/file.ncc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='linuxcnc', add_help=False)
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
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='Output the Same G-command Name USE NonModal Mode')
parser.add_argument('--axis-modal', action='store_true', help='Output the Same Axis Value Mode')
parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes')
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
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="Output the Same G-command Name USE NonModal Mode",
)
parser.add_argument(
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
)
parser.add_argument(
"--no-tlo",
action="store_true",
help="suppress tool length offset (G43) following tool changes",
)
TOOLTIP_ARGS = parser.format_help()
@@ -64,42 +92,44 @@ OUTPUT_HEADER = True
OUTPUT_LINE_NUMBERS = False
SHOW_EDITOR = True
MODAL = False # if true commands are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = True # if false duplicate axis values are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = (
True # if false duplicate axis values are suppressed if the same as previous line.
)
COMMAND_SPACE = " "
LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "LinuxCNC"
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}
PRECISION = 3
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17 G54 G40 G49 G80 G90
'''
PREAMBLE = """G17 G54 G40 G49 G80 G90
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05
POSTAMBLE = """M05
G17 G54 G90 G80 G40
M2
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -135,16 +165,16 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
if args.modal:
MODAL = True
if args.no_tlo:
USE_TLO = False
if args.axis_modal:
print ('here')
print("here")
OUTPUT_DOUBLES = False
except Exception:
@@ -152,6 +182,7 @@ def processArguments(argstring):
return True
def export(objectslist, filename, argstring):
if not processArguments(argstring):
return None
@@ -161,7 +192,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("postprocessing...")
@@ -183,10 +218,10 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
# Skip inactive operations
if hasattr(obj, 'Active'):
if hasattr(obj, "Active"):
if not obj.Active:
continue
if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'):
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
if not obj.Base.Active:
continue
@@ -198,8 +233,12 @@ def export(objectslist, filename, argstring):
gcode += linenumber() + line
# get coolant mode
coolantMode = 'None'
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
coolantMode = "None"
if (
hasattr(obj, "CoolantMode")
or hasattr(obj, "Base")
and hasattr(obj.Base, "CoolantMode")
):
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
else:
@@ -207,12 +246,12 @@ def export(objectslist, filename, argstring):
# turn coolant on if required
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n'
if coolantMode == 'Flood':
gcode += linenumber() + 'M8' + '\n'
if coolantMode == 'Mist':
gcode += linenumber() + 'M7' + '\n'
if not coolantMode == "None":
gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n"
if coolantMode == "Flood":
gcode += linenumber() + "M8" + "\n"
if coolantMode == "Mist":
gcode += linenumber() + "M7" + "\n"
# process the operation gcode
gcode += parse(obj)
@@ -224,10 +263,10 @@ def export(objectslist, filename, argstring):
gcode += linenumber() + line
# turn coolant off if required
if not coolantMode == 'None':
if not coolantMode == "None":
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n'
gcode += linenumber() +'M9' + '\n'
gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n"
gcode += linenumber() + "M9" + "\n"
# do the post_amble
if OUTPUT_COMMENTS:
@@ -250,7 +289,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -275,12 +314,30 @@ def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
# the order of parameters
# linuxcnc doesn't want K properties on XY plane Arcs need work.
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -310,41 +367,64 @@ def parse(pathobj):
if command == lastcommand:
outstring.pop(0)
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
if c.Name not in ["G0", "G00"]: # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F" and (
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
):
if c.Name not in [
"G0",
"G00",
]: # linuxcnc doesn't use rapid speeds
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
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'])))
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:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# stop the spindle
out += linenumber() + "M5\n"
for line in TOOL_CHANGE.splitlines(True):
@@ -352,7 +432,7 @@ def parse(pathobj):
# add height offset
if USE_TLO:
tool_height = '\nG43 H' + str(int(c.Parameters['T']))
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
outstring.append(tool_height)
if command == "message":
@@ -375,4 +455,5 @@ def parse(pathobj):
return out
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -29,7 +29,7 @@ import datetime
import shlex
from PathScripts import 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 mach3_4 3 axis mill. This postprocessor, once placed
@@ -38,22 +38,50 @@ FreeCAD, via the GUI importer or via python scripts with:
import mach3_4_post
mach3_4_post.export(object,"/path/to/file.ncc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='mach3_4', add_help=False)
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
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='Output the Same G-command Name USE NonModal Mode')
parser.add_argument('--axis-modal', action='store_true', help='Output the Same Axis Value Mode')
parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes')
parser = argparse.ArgumentParser(prog="mach3_4", add_help=False)
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
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="Output the Same G-command Name USE NonModal Mode",
)
parser.add_argument(
"--axis-modal", action="store_true", help="Output the Same Axis Value Mode"
)
parser.add_argument(
"--no-tlo",
action="store_true",
help="suppress tool length offset (G43) following tool changes",
)
TOOLTIP_ARGS = parser.format_help()
@@ -63,42 +91,44 @@ OUTPUT_HEADER = True
OUTPUT_LINE_NUMBERS = False
SHOW_EDITOR = True
MODAL = False # if true commands are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = True # if false duplicate axis values are suppressed if the same as previous line.
USE_TLO = True # if true G43 will be output following tool changes
OUTPUT_DOUBLES = (
True # if false duplicate axis values are suppressed if the same as previous line.
)
COMMAND_SPACE = " "
LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "mach3_4"
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}
PRECISION = 3
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17 G54 G40 G49 G80 G90
'''
PREAMBLE = """G17 G54 G40 G49 G80 G90
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05
POSTAMBLE = """M05
G17 G54 G90 G80 G40
M2
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
@@ -134,16 +164,16 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
PRECISION = 4
if args.modal:
MODAL = True
if args.no_tlo:
USE_TLO = False
if args.axis_modal:
print ('here')
print("here")
OUTPUT_DOUBLES = False
except Exception:
@@ -161,7 +191,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("postprocessing...")
@@ -183,23 +217,30 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
# Skip inactive operations
if hasattr(obj, 'Active'):
if hasattr(obj, "Active"):
if not obj.Active:
continue
if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'):
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
if not obj.Base.Active:
continue
# do the pre_op
if OUTPUT_COMMENTS:
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
gcode += linenumber() + "(machine: %s, %s)\n" % (MACHINE_NAME, UNIT_SPEED_FORMAT)
gcode += linenumber() + "(machine: %s, %s)\n" % (
MACHINE_NAME,
UNIT_SPEED_FORMAT,
)
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
# get coolant mode
coolantMode = 'None'
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
coolantMode = "None"
if (
hasattr(obj, "CoolantMode")
or hasattr(obj, "Base")
and hasattr(obj.Base, "CoolantMode")
):
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
else:
@@ -207,12 +248,12 @@ def export(objectslist, filename, argstring):
# turn coolant on if required
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n'
if coolantMode == 'Flood':
gcode += linenumber() + 'M8' + '\n'
if coolantMode == 'Mist':
gcode += linenumber() + 'M7' + '\n'
if not coolantMode == "None":
gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n"
if coolantMode == "Flood":
gcode += linenumber() + "M8" + "\n"
if coolantMode == "Mist":
gcode += linenumber() + "M7" + "\n"
# process the operation gcode
gcode += parse(obj)
@@ -224,10 +265,10 @@ def export(objectslist, filename, argstring):
gcode += linenumber() + line
# turn coolant off if required
if not coolantMode == 'None':
if not coolantMode == "None":
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n'
gcode += linenumber() +'M9' + '\n'
gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n"
gcode += linenumber() + "M9" + "\n"
# do the post_amble
if OUTPUT_COMMENTS:
@@ -248,7 +289,7 @@ def export(objectslist, filename, argstring):
print("done postprocessing.")
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -273,12 +314,30 @@ def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
# the order of parameters
# mach3_4 doesn't want K properties on XY plane Arcs need work.
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -301,18 +360,32 @@ def parse(pathobj):
opHorizRapid = 0
opVertRapid = 0
if 'Adaptive' in pathobj.Name:
if "Adaptive" in pathobj.Name:
adaptiveOp = True
if hasattr(pathobj, 'ToolController'):
if hasattr(pathobj.ToolController, 'HorizRapid') and pathobj.ToolController.HorizRapid > 0:
opHorizRapid = Units.Quantity(pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity)
if hasattr(pathobj, "ToolController"):
if (
hasattr(pathobj.ToolController, "HorizRapid")
and pathobj.ToolController.HorizRapid > 0
):
opHorizRapid = Units.Quantity(
pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity
)
else:
FreeCAD.Console.PrintWarning('Tool Controller Horizontal Rapid Values are unset'+ '\n')
FreeCAD.Console.PrintWarning(
"Tool Controller Horizontal Rapid Values are unset" + "\n"
)
if hasattr(pathobj.ToolController, 'VertRapid') and pathobj.ToolController.VertRapid > 0:
opVertRapid = Units.Quantity(pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity)
if (
hasattr(pathobj.ToolController, "VertRapid")
and pathobj.ToolController.VertRapid > 0
):
opVertRapid = Units.Quantity(
pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity
)
else:
FreeCAD.Console.PrintWarning('Tool Controller Vertical Rapid Values are unset'+ '\n')
FreeCAD.Console.PrintWarning(
"Tool Controller Vertical Rapid Values are unset" + "\n"
)
for c in pathobj.Path.Commands:
@@ -321,10 +394,9 @@ def parse(pathobj):
if adaptiveOp and c.Name in ["G0", "G00"]:
if opHorizRapid and opVertRapid:
command = 'G1'
command = "G1"
else:
outstring.append('(Tool Controller Rapid Values are unset)' + '\n')
outstring.append("(Tool Controller Rapid Values are unset)" + "\n")
outstring.append(command)
@@ -333,48 +405,83 @@ def parse(pathobj):
if command == lastcommand:
outstring.pop(0)
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
if c.Name not in ["G0", "G00"]: # mach3_4 doesn't use rapid speeds
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F" and (
currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES
):
if c.Name not in [
"G0",
"G00",
]: # mach3_4 doesn't use rapid speeds
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
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'])))
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:
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
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)
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
if adaptiveOp and c.Name in ["G0", "G00"]:
if opHorizRapid and opVertRapid:
if 'Z' not in c.Parameters:
outstring.append('F' + format(float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
if "Z" not in c.Parameters:
outstring.append(
"F"
+ format(
float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
outstring.append('F' + format(float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
outstring.append(
"F"
+ format(
float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# stop the spindle
out += linenumber() + "M5\n"
for line in TOOL_CHANGE.splitlines(True):
@@ -382,7 +489,7 @@ def parse(pathobj):
# add height offset
if USE_TLO:
tool_height = '\nG43 H' + str(int(c.Parameters['T']))
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
outstring.append(tool_height)
if command == "message":
@@ -403,4 +510,5 @@ def parse(pathobj):
return out
# print(__name__ + " gcode postprocessor loaded.")

View File

@@ -26,7 +26,7 @@ from PathScripts import PostUtils
import datetime
TOOLTIP = '''
TOOLTIP = """
This is a postprocessor file for the Path workbench. It is used to take
a pseudo-gcode fragment output by a Path object and output real GCode
suitable for the Max Computer GmbH nccad9 Computer Numeric Control.
@@ -41,33 +41,35 @@ Supported features:
import nccad_post
nccad_post.export([object], "/path/to/file.knc", "")
'''
"""
MACHINE_NAME = '''Max Computer GmbH nccad9 MCS/KOSY'''
MACHINE_NAME = """Max Computer GmbH nccad9 MCS/KOSY"""
# gCode for changing tools
# M01 <String> ; Displays <String> and waits for user interaction
TOOL_CHANGE = '''G77 ; Move to release position
TOOL_CHANGE = """G77 ; Move to release position
M10 O6.0 ; Stop spindle
M01 Insert tool TOOL
G76 ; Move to reference point to ensure correct coordinates after tool change
M10 O6.1 ; Start spindle'''
M10 O6.1 ; Start spindle"""
# gCode finishing the program
POSTAMBLE = '''G77 ; Move to release position
M10 O6.0 ; Stop spindle'''
POSTAMBLE = """G77 ; Move to release position
M10 O6.0 ; Stop spindle"""
# gCode header with information about CAD-software, post-processor
# and date/time
HEADER = ''';Exported by FreeCAD
HEADER = """;Exported by FreeCAD
;Post Processor: {}
;CAM file: {}
;Output Time: {}
'''.format(__name__, App.ActiveDocument.FileName, str(datetime.datetime.now()))
""".format(
__name__, App.ActiveDocument.FileName, str(datetime.datetime.now())
)
def export(objectslist, filename, argstring):
@@ -86,14 +88,15 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
for command in obj.Path.Commands:
# Manipulate tool change commands
if 'M6' == command.Name:
gcode += TOOL_CHANGE.replace('TOOL',
str(int(command.Parameters['T'])))
elif 'M3' == command.Name:
if "M6" == command.Name:
gcode += TOOL_CHANGE.replace("TOOL", str(int(command.Parameters["T"])))
elif "M3" == command.Name:
# Convert spindle speed (rpm) command to comment
gcode += ('M01 Set spindle speed to '
+ str(int(command.Parameters['S']))
+ ' rounds per minute')
gcode += (
"M01 Set spindle speed to "
+ str(int(command.Parameters["S"]))
+ " rounds per minute"
)
else:
# Add other commands
gcode += command.Name
@@ -102,15 +105,15 @@ def export(objectslist, filename, argstring):
for parameter, value in command.Parameters.items():
# Multiply F parameter value by 10,
# FreeCAD = mm/s, nccad = 1/10 mm/s
if 'F' == parameter:
if "F" == parameter:
value *= 10
# Add command parameters and values and round float
# as nccad9 does not support exponents
gcode += ' ' + parameter + str(round(value, 5))
gcode += " " + parameter + str(round(value, 5))
gcode += '\n'
gcode += "\n"
gcode += POSTAMBLE + '\n'
gcode += POSTAMBLE + "\n"
# Open editor window
if App.GuiUp:
@@ -121,7 +124,7 @@ def export(objectslist, filename, argstring):
gcode = dia.editor.toPlainText()
# Save to file
if filename != '-':
if filename != "-":
gfile = open(filename, "w")
gfile.write(gcode)
gfile.close()

View File

@@ -26,7 +26,7 @@ import datetime
from PathScripts import PostUtils
TOOLTIP = '''
TOOLTIP = """
This is an postprocessor file for the Path workbench. It will output path data
in a format suitable for OpenSBP controllers like shopbot. This postprocessor,
once placed in the appropriate PathScripts folder, can be used directly from
@@ -34,9 +34,9 @@ inside FreeCAD, via the GUI importer or via python scripts with:
import Path
Path.write(object,"/path/to/file.ncc","post_opensbp")
'''
"""
'''
"""
DONE:
uses native commands
handles feed and jog moves
@@ -48,15 +48,15 @@ ToDo
drilling. Haven't looked at it.
many other things
'''
"""
TOOLTIP_ARGS = '''
TOOLTIP_ARGS = """
Arguments for opensbp:
--comments ... insert comments - mostly for debugging
--inches ... convert output to inches
--no-header ... suppress header output
--no-show-editor ... don't show editor, just save result
'''
"""
now = datetime.datetime.now()
@@ -66,21 +66,21 @@ SHOW_EDITOR = True
COMMAND_SPACE = ","
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = ''''''
PREAMBLE = """"""
# Postamble text will appear following the last operation.
POSTAMBLE = ''''''
POSTAMBLE = """"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
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
CurrentState = {}
@@ -105,13 +105,13 @@ def export(objectslist, filename, argstring):
global GetValue
for arg in argstring.split():
if arg == '--comments':
if arg == "--comments":
OUTPUT_COMMENTS = True
if arg == '--inches':
if arg == "--inches":
GetValue = getImperialValue
if arg == '--no-header':
if arg == "--no-header":
OUTPUT_HEADER = False
if arg == '--no-show-editor':
if arg == "--no-show-editor":
SHOW_EDITOR = False
for obj in objectslist:
@@ -122,8 +122,15 @@ def export(objectslist, filename, argstring):
return
CurrentState = {
'X': 0, 'Y': 0, 'Z': 0, 'F': 0, 'S': 0,
'JSXY': 0, 'JSZ': 0, 'MSXY': 0, 'MSZ': 0
"X": 0,
"Y": 0,
"Z": 0,
"F": 0,
"S": 0,
"JSXY": 0,
"JSZ": 0,
"MSXY": 0,
"MSZ": 0,
}
print("postprocessing...")
gcode = ""
@@ -188,26 +195,26 @@ def move(command):
# txt += feedrate(command)
axis = ""
for p in ['X', 'Y', 'Z']:
for p in ["X", "Y", "Z"]:
if p in command.Parameters:
if command.Parameters[p] != CurrentState[p]:
axis += p
if 'F' in command.Parameters:
speed = command.Parameters['F']
if command.Name in ['G1', 'G01']: # move
if "F" in command.Parameters:
speed = command.Parameters["F"]
if command.Name in ["G1", "G01"]: # move
movetype = "MS"
else: # jog
movetype = "JS"
zspeed = ""
xyspeed = ""
if 'Z' in axis:
if "Z" in axis:
speedKey = "{}Z".format(movetype)
speedVal = GetValue(speed)
if CurrentState[speedKey] != speedVal:
CurrentState[speedKey] = speedVal
zspeed = "{:f}".format(speedVal)
if ('X' in axis) or ('Y' in axis):
if ("X" in axis) or ("Y" in axis):
speedKey = "{}XY".format(movetype)
speedVal = GetValue(speed)
if CurrentState[speedKey] != speedVal:
@@ -216,45 +223,45 @@ def move(command):
if zspeed or xyspeed:
txt += "{},{},{}\n".format(movetype, xyspeed, zspeed)
if command.Name in ['G0', 'G00']:
if command.Name in ["G0", "G00"]:
pref = "J"
else:
pref = "M"
if axis == "X":
txt += pref + "X"
txt += "," + format(GetValue(command.Parameters["X"]), '.4f')
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
txt += "\n"
elif axis == "Y":
txt += pref + "Y"
txt += "," + format(GetValue(command.Parameters["Y"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
txt += "\n"
elif axis == "Z":
txt += pref + "Z"
txt += "," + format(GetValue(command.Parameters["Z"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
txt += "\n"
elif axis == "XY":
txt += pref + "2"
txt += "," + format(GetValue(command.Parameters["X"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Y"]), '.4f')
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
txt += "\n"
elif axis == "XZ":
txt += pref + "3"
txt += "," + format(GetValue(command.Parameters["X"]), '.4f')
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
txt += ","
txt += "," + format(GetValue(command.Parameters["Z"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
txt += "\n"
elif axis == "XYZ":
txt += pref + "3"
txt += "," + format(GetValue(command.Parameters["X"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Y"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Z"]), '.4f')
txt += "," + format(GetValue(command.Parameters["X"]), ".4f")
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
txt += "\n"
elif axis == "YZ":
txt += pref + "3"
txt += ","
txt += "," + format(GetValue(command.Parameters["Y"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Z"]), '.4f')
txt += "," + format(GetValue(command.Parameters["Y"]), ".4f")
txt += "," + format(GetValue(command.Parameters["Z"]), ".4f")
txt += "\n"
elif axis == "":
print("warning: skipping duplicate move.")
@@ -267,15 +274,15 @@ def move(command):
def arc(command):
if command.Name == 'G2': # CW
if command.Name == "G2": # CW
dirstring = "1"
else: # G3 means CCW
dirstring = "-1"
txt = "CG,,"
txt += format(GetValue(command.Parameters['X']), '.4f') + ","
txt += format(GetValue(command.Parameters['Y']), '.4f') + ","
txt += format(GetValue(command.Parameters['I']), '.4f') + ","
txt += format(GetValue(command.Parameters['J']), '.4f') + ","
txt += format(GetValue(command.Parameters["X"]), ".4f") + ","
txt += format(GetValue(command.Parameters["Y"]), ".4f") + ","
txt += format(GetValue(command.Parameters["I"]), ".4f") + ","
txt += format(GetValue(command.Parameters["J"]), ".4f") + ","
txt += "T" + ","
txt += dirstring
txt += "\n"
@@ -288,9 +295,9 @@ def tool_change(command):
txt += "'a tool change happens now\n"
for line in TOOL_CHANGE.splitlines(True):
txt += line
txt += "&ToolName=" + str(int(command.Parameters['T']))
txt += "&ToolName=" + str(int(command.Parameters["T"]))
txt += "\n"
txt += "&Tool=" + str(int(command.Parameters['T']))
txt += "&Tool=" + str(int(command.Parameters["T"]))
txt += "\n"
return txt
@@ -306,7 +313,7 @@ def spindle(command):
pass
else:
pass
txt += "TR," + str(command.Parameters['S']) + "\n"
txt += "TR," + str(command.Parameters["S"]) + "\n"
txt += "C6\n"
txt += "PAUSE 2\n"
return txt
@@ -326,7 +333,7 @@ scommands = {
"G03": arc,
"M06": tool_change,
"M03": spindle,
"message": comment
"message": comment,
}
@@ -351,10 +358,10 @@ def parse(pathobj):
output += scommands[command](c)
if c.Parameters:
CurrentState.update(c.Parameters)
elif command[0] == '(':
elif command[0] == "(":
output += "' " + command + "\n"
else:
print("I don't know what the hell the command: ", end='')
print("I don't know what the hell the command: ", end="")
print(command + " means. Maybe I should support it.")
return output

View File

@@ -22,7 +22,7 @@
# * *
# ***************************************************************************
'''
"""
This is a preprocessor file for the Path workbench. Its aim is to
parse the contents of a given OpenSBP file, and transform it to make it
suitable for use in a Path object. This preprocessor, once placed in the
@@ -46,18 +46,24 @@ if operations are preceded by a comment ('New Path ...) They are split into mul
TODO
Many other OpenSBP commands not handled
'''
"""
from __future__ import print_function
import FreeCAD
import PathScripts.PathUtil as PathUtil
import os
import Path
AXIS = 'X', 'Y', 'Z', 'A', 'B' # OpenSBP always puts multiaxis move parameters in this order
SPEEDS = 'XY', 'Z', 'A', 'B'
AXIS = (
"X",
"Y",
"Z",
"A",
"B",
) # OpenSBP always puts multiaxis move parameters in this order
SPEEDS = "XY", "Z", "A", "B"
# 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
@@ -69,9 +75,9 @@ def open(filename):
def insert(filename, docname):
'''called when freecad imports a file
"""called when freecad imports a file
This insert expects parse to return a list of strings
each string will become a separate path'''
each string will become a separate path"""
gfile = pythonopen(filename)
gcode = gfile.read()
gfile.close()
@@ -90,10 +96,20 @@ def parse(inputstring):
lines = inputstring.split("\n")
return_output = []
output = ""
last = {'X': None, 'Y': None, 'Z': None, 'A': None, 'B': None}
lastrapidspeed = {'XY': "50", 'Z': "50", 'A': "50", 'B': "50"} # set default rapid speeds
lastfeedspeed = {'XY': "50", 'Z': "50", 'A': "50", 'B': "50"} # set default feed speed
movecommand = ['G1', 'G0', 'G02', 'G03']
last = {"X": None, "Y": None, "Z": None, "A": None, "B": None}
lastrapidspeed = {
"XY": "50",
"Z": "50",
"A": "50",
"B": "50",
} # set default rapid speeds
lastfeedspeed = {
"XY": "50",
"Z": "50",
"A": "50",
"B": "50",
} # set default feed speed
movecommand = ["G1", "G0", "G02", "G03"]
for line in lines:
# remove any leftover trailing and preceding spaces
@@ -105,22 +121,33 @@ def parse(inputstring):
# discard comment and other non strictly gcode lines
if line[0:9] == "'New Path":
# starting new path
if any(x in output for x in movecommand): # make sure the path has at least one move command.
if any(
x in output for x in movecommand
): # make sure the path has at least one move command.
return_output.append(output)
output = ""
continue
words = [a.strip() for a in line.split(",")]
words[0] = words[0].upper()
if words[0] in ["J2", "J3", "J4", "J5", "M2", "M3", "M4", "M5"]: # multi-axis jogs and moves
if words[0][0] == 'J': # jog move
if words[0] in [
"J2",
"J3",
"J4",
"J5",
"M2",
"M3",
"M4",
"M5",
]: # multi-axis jogs and moves
if words[0][0] == "J": # jog move
s = "G0 "
else: # feed move
else: # feed move
s = "G1 "
speed = lastfeedspeed["XY"]
for i in range(1, len(words)):
if words[i] == '':
if words[i] == "":
if last[AXIS[i - 1]] is None:
continue
else:
@@ -128,19 +155,30 @@ def parse(inputstring):
else:
s += AXIS[i - 1] + words[i]
last[AXIS[i - 1]] = words[i]
output += s + " F" + speed + '\n'
output += s + " F" + speed + "\n"
if words[0] in ["JA", "JB", "JX", "JY", "JZ", "MA", "MB", "MX", "MY", "MZ"]: # single axis jogs and moves
if words[0][0] == 'J': # jog move
if words[0] in [
"JA",
"JB",
"JX",
"JY",
"JZ",
"MA",
"MB",
"MX",
"MY",
"MZ",
]: # single axis jogs and moves
if words[0][0] == "J": # jog move
s = "G0 "
if words[0][1] in ['X', 'Y']:
if words[0][1] in ["X", "Y"]:
speed = lastrapidspeed["XY"]
else:
speed = lastrapidspeed[words[0][1]]
else: # feed move
else: # feed move
s = "G1 "
if words[0][1] in ['X', 'Y']:
if words[0][1] in ["X", "Y"]:
speed = lastfeedspeed["XY"]
else:
speed = lastfeedspeed[words[0][1]]
@@ -153,7 +191,7 @@ def parse(inputstring):
if words[0] in ["JS"]: # set jog speed
for i in range(1, len(words)):
if words[i] == '':
if words[i] == "":
continue
else:
lastrapidspeed[SPEEDS[i - 1]] = words[i]
@@ -166,7 +204,7 @@ def parse(inputstring):
continue
if words[0] in ["MS"]: # set move speed
for i in range(1, len(words)):
if words[i] == '':
if words[i] == "":
continue
else:
lastfeedspeed[SPEEDS[i - 1]] = words[i]
@@ -180,7 +218,7 @@ def parse(inputstring):
else:
s = "M3 S"
s += str(abs(float(words[1])))
output += s + '\n'
output += s + "\n"
if words[0] in ["CG"]: # Gcode circle/arc
if words[1] != "": # diameter mode
@@ -193,8 +231,19 @@ def parse(inputstring):
else: # CCW
s = "G3"
s += " X" + words[2] + " Y" + words[3] + " I" + words[4] + " J" + words[5] + " F" + str(lastfeedspeed["XY"])
output += s + '\n'
s += (
" X"
+ words[2]
+ " Y"
+ words[3]
+ " I"
+ words[4]
+ " J"
+ words[5]
+ " F"
+ str(lastfeedspeed["XY"])
)
output += s + "\n"
last["X"] = words[2]
last["Y"] = words[3]

View File

@@ -1,24 +1,24 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* Copyright (c) 2016 Christoph Blaue <blaue@fh-westkueste.de> *
#* *
#* 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) 2016 Christoph Blaue <blaue@fh-westkueste.de> *
# * *
# * 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 *
# * *
# ***************************************************************************
# 03-24-2021 Sliptonic: I've removed the PathUtils import and job lookup
# post processors shouldn't be reaching back to the job. This can cause a
@@ -30,7 +30,7 @@ import time
from PathScripts import PostUtils
import math
TOOLTIP = '''Post processor for Maho M 600E mill
TOOLTIP = """Post processor for Maho M 600E mill
Machines with Philips or Heidenhain control should be very easy to adapt.
@@ -39,18 +39,18 @@ No programming experience required. This can make a generated g-code
program more readable and since older machines have very
limited memory it seems sensible to reduce the number of commands and
parameters, like e.g. suppress the units in the header and at every hop.
'''
"""
#***************************************************************************
# ***************************************************************************
# user editable stuff here
COMMAND_SPACE = " "
MACHINE_NAME = 'Maho 600E'
CORNER_MIN = {'x': -51.877, 'y': 0, 'z': 0} # use metric for internal units
CORNER_MAX = {'x': 591.5, 'y': 391.498, 'z': 391.5} # use metric for internal units
MACHINE_NAME = "Maho 600E"
CORNER_MIN = {"x": -51.877, "y": 0, "z": 0} # use metric for internal units
CORNER_MAX = {"x": 591.5, "y": 391.498, "z": 391.5} # use metric for internal units
UNITS = 'G21' # use metric units
UNITS = "G21" # use metric units
# possible values:
# 'G20' for inches,
# 'G21' for metric units.
@@ -63,7 +63,7 @@ UNITS_INCLUDED = False # do not include the units in the GCode program
# usually the units to be used are defined in the machine constants and almost never change,
# so this can be set to False.
COMMENT = ''
COMMENT = ""
# possible values:
# ';' centroid or sinumerik comment symbol,
# '' leave blank for bracketed comments style "(comment comment comment)"
@@ -108,7 +108,7 @@ MODAL = True
# G1 X15 Y30
# suppress these parameters if they haven't changed
MODALPARAMS = ['X', 'Y', 'Z', 'S', 'F']
MODALPARAMS = ["X", "Y", "Z", "S", "F"]
# possible values:
# any list of GCode parameters
# if a parameter doesn't change from one line to the next ( or even further) it is suppressed.
@@ -126,7 +126,9 @@ SWAP_G2_G3 = True # some machines have the sign of the X-axis swapped, so they
# this might be special with some maho machines or even with mine and
# might be changed in the machine constants as well
SWAP_Y_Z = True # machines with an angle milling head do not switch axes, so we do it here
SWAP_Y_Z = (
True # machines with an angle milling head do not switch axes, so we do it here
)
# possible values:
# True if Y and Z values have to be swapped
# False do not swap
@@ -161,8 +163,12 @@ RADIUS_COMMENT = True
# and never with the comment symbol, because the radius might appear in
# the middle of a line.
GCODE_MAP = {'M1': 'M0', 'M6': 'M66', 'G20': 'G70',
'G21': 'G71'} # cb: this could be used to swap G2/G3
GCODE_MAP = {
"M1": "M0",
"M6": "M66",
"G20": "G70",
"G21": "G71",
} # cb: this could be used to swap G2/G3
# possible values:
# Comma separated list of values of the form 'sourceGCode':'targetGCode'
#
@@ -200,42 +206,56 @@ SUPPRESS_ZERO_FEED = True
# def mkHeader(selection):
# return ''
parser = argparse.ArgumentParser(prog='philips', add_help=False)
parser.add_argument('--header', action='store_true', help='create header output')
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser = argparse.ArgumentParser(prog="philips", add_help=False)
parser.add_argument("--header", action="store_true", help="create header output")
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument('--comments', action='store_true', help='create comment output')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument("--comments", action="store_true", help="create comment output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-line-numbers', action='store_true', help='omit line number prefixes')
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
"--no-line-numbers", action="store_true", help="omit line number prefixes"
)
parser.add_argument('--show-editor', action='store_true', help='pop up editor before writing output')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument(
"--show-editor", action="store_true", help="pop up editor before writing output"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
TOOLTIP_ARGS = parser.format_help()
def processArguments(argstring):
global LINENUMBERS
global SHOW_EDITOR
for arg in argstring.split():
if arg == '--line-numbers':
if arg == "--line-numbers":
LINENUMBERS = True
elif arg == '--no-line-numbers':
elif arg == "--no-line-numbers":
LINENUMBERS = False
elif arg == '--show-editor':
elif arg == "--show-editor":
SHOW_EDITOR = True
elif arg == '--no-show-editor':
elif arg == "--no-show-editor":
SHOW_EDITOR = False
def mkHeader(selection):
# job = PathUtils.findParentJob(selection[0])
# this is within a function, because otherwise filename and time don't change when changing the FreeCAD project
# this is within a function, because otherwise filename and time don't change when changing the FreeCAD project
# now = datetime.datetime.now()
now = time.strftime("%Y-%m-%d %H:%M")
originfile = FreeCAD.ActiveDocument.FileName
headerNoNumber = "%PM\n" # this line gets no linenumber
headerNoNumber = "%PM\n" # this line gets no linenumber
# if hasattr(job, "Description"):
# description = job.Description
# else:
@@ -244,18 +264,19 @@ def mkHeader(selection):
# this line gets no linenumber, it is already a specially numbered
headerNoNumber += "N9XXX (" + description + ", " + now + ")\n"
header = ""
# header += "(Output Time:" + str(now) + ")\n"
# header += "(Output Time:" + str(now) + ")\n"
header += "(" + originfile + ")\n"
# header += "(Exported by FreeCAD)\n"
# header += "(Exported by FreeCAD)\n"
header += "(Post Processor: " + __name__ + ")\n"
# header += "(Target machine: " + MACHINE_NAME + ")\n"
header += "G18\n" # Select XY plane
header += "G90\n" # Absolute coordinates
header += "G51\n" # Reset Zero
header += "G52 (ersetze G55-G59)" # set zero
# header += "(Target machine: " + MACHINE_NAME + ")\n"
header += "G18\n" # Select XY plane
header += "G90\n" # Absolute coordinates
header += "G51\n" # Reset Zero
header += "G52 (ersetze G55-G59)" # set zero
return headerNoNumber + linenumberify(header)
GCODE_HEADER = "" # do not terminate with a newline, it is inserted by linenumberify
GCODE_HEADER = "" # do not terminate with a newline, it is inserted by linenumberify
# GCODE_HEADER = "G40 G90" # do not terminate with a newline, it is inserted by linenumberify
# possible values:
# any sequence of GCode, multiple lines are welcome
@@ -270,11 +291,11 @@ GCODE_FOOTER = "M30"
# linenumbers are inserted automatically if LINENUMBERS is True
# don't edit with the stuff below the next line unless you know what you're doing :)
#***************************************************************************
# ***************************************************************************
linenr = 0 # variable has to be global because it is used by linenumberify and export
if open.__module__ in ['__builtin__','io']:
if open.__module__ in ["__builtin__", "io"]:
pythonopen = open
@@ -286,8 +307,21 @@ def angleUnder180(command, lastX, lastY, x, y, i, j):
# (or on) the connection line
middleOfLineY = (lastY + y) / 2
centerY = lastY + j
if ((command == 'G2' and ((lastX == x and ((lastY < y and i >= 0) or (lastY > y and i <= 0))) or (lastX < x and centerY <= middleOfLineY) or (lastX > x and centerY >= middleOfLineY)))
or (command == 'G3' and ((lastX == x and ((lastY < y and i <= 0) or (lastY > y and i >= 0))) or (lastX < x and centerY >= middleOfLineY) or (lastX > x and centerY <= middleOfLineY)))):
if (
command == "G2"
and (
(lastX == x and ((lastY < y and i >= 0) or (lastY > y and i <= 0)))
or (lastX < x and centerY <= middleOfLineY)
or (lastX > x and centerY >= middleOfLineY)
)
) or (
command == "G3"
and (
(lastX == x and ((lastY < y and i <= 0) or (lastY > y and i >= 0)))
or (lastX < x and centerY >= middleOfLineY)
or (lastX > x and centerY <= middleOfLineY)
)
):
return True
else:
return False
@@ -299,10 +333,10 @@ def mapGCode(command):
else:
mappedCommand = command
if SWAP_G2_G3:
if command == 'G2':
mappedCommand = 'G3'
elif command == 'G3':
mappedCommand = 'G2'
if command == "G2":
mappedCommand = "G3"
elif command == "G3":
mappedCommand = "G2"
return mappedCommand
@@ -312,7 +346,7 @@ def linenumberify(GCodeString):
if not LINENUMBERS:
result = GCodeString + "\n"
else:
result = ''
result = ""
strList = GCodeString.split("\n")
for s in strList:
if s:
@@ -325,6 +359,7 @@ def linenumberify(GCodeString):
result += s + "\n"
return result
def export(objectslist, filename, argstring):
global UNITS
global linenr
@@ -333,14 +368,32 @@ def export(objectslist, filename, argstring):
lastX = 0
lastY = 0
lastZ = 0
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
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
modalParamsDict = dict()
for mp in MODALPARAMS:
modalParamsDict[mp] = None
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return
myMachine = None
for pathobj in objectslist:
@@ -354,23 +407,23 @@ def export(objectslist, filename, argstring):
if myMachine is None:
print("philips_post: No machine found in this selection")
gcode = ''
gcode = ""
gcode += mkHeader(objectslist)
gcode += linenumberify(GCODE_HEADER)
if UNITS_INCLUDED:
gcode += linenumberify(mapGCode(UNITS))
lastcommand = None
for obj in objectslist:
if hasattr(obj, 'Comment'):
gcode += linenumberify('(' + obj.Comment + ')')
if hasattr(obj, "Comment"):
gcode += linenumberify("(" + obj.Comment + ")")
for c in obj.Path.Commands:
outstring = []
command = c.Name
if command != 'G0':
command = command.replace('G0','G') # normalize: G01 -> G1
if command != "G0":
command = command.replace("G0", "G") # normalize: G01 -> G1
if (command != UNITS or UNITS_INCLUDED):
if command[0] == '(':
if command != UNITS or UNITS_INCLUDED:
if command[0] == "(":
command = PostUtils.fcoms(command, COMMENT)
# the mapping is done for output only! For internal things we
# still use the old value.
@@ -378,63 +431,90 @@ def export(objectslist, filename, argstring):
if not MODAL or command != lastcommand:
outstring.append(mappedCommand)
# if MODAL == True:
# #\better: append iff MODAL == False
# if command == lastcommand:
# outstring.pop(0)
# if MODAL == True:
# #\better: append iff MODAL == False
# if command == lastcommand:
# outstring.pop(0)
if len(c.Parameters) >= 1:
for param in params:
# test print("param: " + param + ", command: " + command)
if param in c.Parameters:
if (param in MODALPARAMS) and (modalParamsDict[str(param)] == c.Parameters[str(param)]):
if (param in MODALPARAMS) and (
modalParamsDict[str(param)] == c.Parameters[str(param)]
):
# do nothing or append white space
outstring.append(' ')
elif param == 'F':
feed = c.Parameters['F']
outstring.append(" ")
elif param == "F":
feed = c.Parameters["F"]
if SUPPRESS_ZERO_FEED and feed == 0:
pass
else:
outstring.append(
param + PostUtils.fmt(feed, FEED_DECIMALS, UNITS))
elif param == 'H':
outstring.append(
param + str(int(c.Parameters['H'])))
elif param == 'S':
param
+ PostUtils.fmt(feed, FEED_DECIMALS, UNITS)
)
elif param == "H":
outstring.append(param + str(int(c.Parameters["H"])))
elif param == "S":
# rpm is unitless-therefore I had to 'fake it
# out' by using metric units which don't get
# converted from entered value
outstring.append(
param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS, 'G21'))
elif param == 'T':
outstring.append(
param + str(int(c.Parameters['T'])))
elif param == 'I' and (command == 'G2' or command == 'G3'):
#test print("param = 'I'")
param
+ PostUtils.fmt(
c.Parameters["S"], SPINDLE_DECIMALS, "G21"
)
)
elif param == "T":
outstring.append(param + str(int(c.Parameters["T"])))
elif param == "I" and (command == "G2" or command == "G3"):
# test print("param = 'I'")
# this is the special case for circular paths,
# where relative coordinates have to be changed
# to absolute
i = c.Parameters['I']
i = c.Parameters["I"]
# calculate the radius r
j = c.Parameters['J']
r = math.sqrt(i**2 + j**2)
if USE_RADIUS_IF_POSSIBLE and angleUnder180(command, lastX, lastY, c.Parameters['X'], c.Parameters['Y'], i, j):
j = c.Parameters["J"]
r = math.sqrt(i ** 2 + j ** 2)
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
command,
lastX,
lastY,
c.Parameters["X"],
c.Parameters["Y"],
i,
j,
):
outstring.append(
'R' + PostUtils.fmt(r, AXIS_DECIMALS, UNITS))
"R" + PostUtils.fmt(r, AXIS_DECIMALS, UNITS)
)
else:
if RADIUS_COMMENT:
outstring.append(
'(R' + PostUtils.fmt(r, AXIS_DECIMALS, UNITS) + ')')
"(R"
+ PostUtils.fmt(r, AXIS_DECIMALS, UNITS)
+ ")"
)
if ABSOLUTE_CIRCLE_CENTER:
i += lastX
outstring.append(
param + PostUtils.fmt(i, AXIS_DECIMALS, UNITS))
elif param == 'J' and (command == 'G2' or command == 'G3'):
param + PostUtils.fmt(i, AXIS_DECIMALS, UNITS)
)
elif param == "J" and (command == "G2" or command == "G3"):
# this is the special case for circular paths,
# where incremental center has to be changed to
# absolute center
i = c.Parameters['I']
j = c.Parameters['J']
if USE_RADIUS_IF_POSSIBLE and angleUnder180(command, lastX, lastY, c.Parameters['X'], c.Parameters['Y'], i, j):
i = c.Parameters["I"]
j = c.Parameters["J"]
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
command,
lastX,
lastY,
c.Parameters["X"],
c.Parameters["Y"],
i,
j,
):
# R is handled with the I parameter, here:
# do nothing at all, keep the structure as
# with I command
@@ -445,19 +525,36 @@ def export(objectslist, filename, argstring):
if SWAP_Y_Z:
# we have to swap j and k as well
outstring.append(
'K' + PostUtils.fmt(j, AXIS_DECIMALS, UNITS))
"K" + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
)
else:
outstring.append(
param + PostUtils.fmt(j, AXIS_DECIMALS, UNITS))
elif param == 'K' and (command == 'G2' or command == 'G3'):
param
+ PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
)
elif param == "K" and (command == "G2" or command == "G3"):
# this is the special case for circular paths,
# where incremental center has to be changed to
# absolute center
outstring.append(
'(' + param + PostUtils.fmt(c.Parameters[param], AXIS_DECIMALS, UNITS) + ')')
z = c.Parameters['Z']
k = c.Parameters['K']
if USE_RADIUS_IF_POSSIBLE and angleUnder180(command, lastX, lastY, c.Parameters['X'], c.Parameters['Y'], i, j):
"("
+ param
+ PostUtils.fmt(
c.Parameters[param], AXIS_DECIMALS, UNITS
)
+ ")"
)
z = c.Parameters["Z"]
k = c.Parameters["K"]
if USE_RADIUS_IF_POSSIBLE and angleUnder180(
command,
lastX,
lastY,
c.Parameters["X"],
c.Parameters["Y"],
i,
j,
):
# R is handled with the I parameter, here:
# do nothing at all, keep the structure as
# with I command
@@ -468,47 +565,60 @@ def export(objectslist, filename, argstring):
if SWAP_Y_Z:
# we have to swap j and k as well
outstring.append(
'J' + PostUtils.fmt(j, AXIS_DECIMALS, UNITS))
"J" + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
)
else:
outstring.append(
param + PostUtils.fmt(j, AXIS_DECIMALS, UNITS))
elif param == 'Y' and SWAP_Y_Z:
param + PostUtils.fmt(j, AXIS_DECIMALS, UNITS)
)
elif param == "Y" and SWAP_Y_Z:
outstring.append(
'Z' + PostUtils.fmt(c.Parameters[param], AXIS_DECIMALS, UNITS))
elif param == 'Z' and SWAP_Y_Z:
"Z"
+ PostUtils.fmt(
c.Parameters[param], AXIS_DECIMALS, UNITS
)
)
elif param == "Z" and SWAP_Y_Z:
outstring.append(
'Y' + PostUtils.fmt(c.Parameters[param], AXIS_DECIMALS, UNITS))
"Y"
+ PostUtils.fmt(
c.Parameters[param], AXIS_DECIMALS, UNITS
)
)
else:
# To Do: suppress unknown commands, if this is done here, all X parameters are suppressed
# To Do: suppress unknown commands, if this is done here, all X parameters are suppressed
# this is an unknown command, don't create GCode for it
# print("parameter " + param + " for command " + command + " ignored")
# print("parameter " + param + " for command " + command + " ignored")
outstring.append(
param + PostUtils.fmt(c.Parameters[param], AXIS_DECIMALS, UNITS))
param
+ PostUtils.fmt(
c.Parameters[param], AXIS_DECIMALS, UNITS
)
)
if param in MODALPARAMS:
modalParamsDict[str(param)] = c.Parameters[
param]
modalParamsDict[str(param)] = c.Parameters[param]
# save the last X, Y, Z values
if 'X' in c.Parameters:
lastX = c.Parameters['X']
if 'Y' in c.Parameters:
lastY = c.Parameters['Y']
if 'Z' in c.Parameters:
lastZ = c.Parameters['Z']
if "X" in c.Parameters:
lastX = c.Parameters["X"]
if "Y" in c.Parameters:
lastY = c.Parameters["Y"]
if "Z" in c.Parameters:
lastZ = c.Parameters["Z"]
outstr = ''
outstr = ""
for w in outstring:
outstr += w + COMMAND_SPACE
outstr = outstr.replace(']', '')
outstr = outstr.replace('[', '')
outstr = outstr.replace("'", '')
outstr = outstr.replace(",", '.')
outstr = outstr.replace("]", "")
outstr = outstr.replace("[", "")
outstr = outstr.replace("'", "")
outstr = outstr.replace(",", ".")
if LINENUMBERS:
gcode += "N" + str(linenr) + " "
linenr += LINENUMBER_INCREMENT
gcode += outstr + '\n'
gcode += outstr + "\n"
lastcommand = c.Name
gcode = gcode.replace("_","-")
gcode = gcode.replace("_", "-")
gcode += linenumberify(GCODE_FOOTER)
if SHOW_EDITOR:
PostUtils.editor(gcode)

View File

@@ -1,28 +1,28 @@
#***************************************************************************
#* Copyright (c) 2015 Jon Nordby <jononor@gmail.com> *
#* *
#* 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 *
#* *
#***************************************************************************
# ***************************************************************************
# * Copyright (c) 2015 Jon Nordby <jononor@gmail.com> *
# * *
# * 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 *
# * *
# ***************************************************************************
TOOLTIP='''
TOOLTIP = """
FreeCAD Path post-processor to output code for the Roland Modela MDX-## machines.
The machine speaks RML-1, specified in 'Roland RML-1 Programming Guidelines'
@@ -32,14 +32,14 @@ http://altlab.org/d/content/m/pangelo/ideas/rml_command_guide_en_v100.pdf
The format has some overlap with HPGL:
https://en.wikipedia.org/wiki/HPGL
http://paulbourke.net/dataformats/hpgl/
'''
"""
import FreeCAD
import Part
import PathScripts.PostUtils as PostUtils
# 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
@@ -51,34 +51,46 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
code += convertobject(obj)
gfile = pythonopen(filename,"w")
gfile = pythonopen(filename, "w")
gfile.write(code)
gfile.close()
def convertobject(obj):
gcode = obj.Path.toGCode()
gcode = parse(gcode)
return gcode
def motoron():
return [ "!MC1;" ]
return ["!MC1;"]
def motoroff():
return [ "!MC0;" ]
return ["!MC0;"]
def home():
return [ "H;" ]
return ["H;"]
def setjog():
# "!PZ%d,%d;",iz_down,iz_up); // set z down, jog
return ""
def addheader():
return [ "PA;PA;" ] # absolute positioning
return ["PA;PA;"] # absolute positioning
def addfooter():
return []
def mm2cord(mm):
mm = float(mm)
return int(40.0*mm)
return int(40.0 * mm)
def feed(x=None, y=None, z=None, state=None):
c = []
@@ -87,13 +99,13 @@ def feed(x=None, y=None, z=None, state=None):
if x is not None:
x = float(x)
state['X'] = x
state["X"] = x
if y is not None:
y = float(y)
state['Y'] = y
state["Y"] = y
if z is not None:
z = float(z)
state['Z'] = z
state["Z"] = z
if x is not None and y is not None and z is not None:
# 3d motion
@@ -105,6 +117,7 @@ def feed(x=None, y=None, z=None, state=None):
pass
return c
def jog(x=None, y=None, z=None, state=None):
c = []
if state is None:
@@ -112,22 +125,23 @@ def jog(x=None, y=None, z=None, state=None):
if x is not None and y is not None:
x, y = float(x), float(y)
c.append("PU%d,%d;" % (mm2cord(x), mm2cord(y)))
state['X'] = x
state['Y'] = y
state["X"] = x
state["Y"] = y
if z is not None:
z = float(z)
c.append("PU;")
state['Z'] = z
state["Z"] = z
return c
def xyarc(args, state):
# no native support in RML/Modela, convert to linear line segments
c = []
lastPoint = FreeCAD.Vector(state['X'], state['Y'])
newPoint = FreeCAD.Vector(float(args['X']), float(args['Y']))
centerOffset = FreeCAD.Vector(float(args['I']), float(args['J']))
lastPoint = FreeCAD.Vector(state["X"], state["Y"])
newPoint = FreeCAD.Vector(float(args["X"]), float(args["Y"]))
centerOffset = FreeCAD.Vector(float(args["I"]), float(args["J"]))
center = lastPoint + centerOffset
radius = (center - lastPoint).Length
xyNormal = FreeCAD.Vector(0, 0, 1)
@@ -135,14 +149,15 @@ def xyarc(args, state):
p0 = circle.parameter(lastPoint)
p1 = circle.parameter(newPoint)
arc = Part.ArcOfCircle(circle, p0, p1)
steps = 64 # specify max error instead?
steps = 64 # specify max error instead?
points = arc.discretize(steps)
# consider direction?
#print('p = Part.ArcOfCircle(Part.Circle(FreeCAD.Vector(%f, %f), FreeCAD.Vector(0, 0, 1), %f), %f, %f)' % (center.x, center.y, radius, p0, p1))
# print('p = Part.ArcOfCircle(Part.Circle(FreeCAD.Vector(%f, %f), FreeCAD.Vector(0, 0, 1), %f), %f, %f)' % (center.x, center.y, radius, p0, p1))
for p in points:
c += feed(p.x, p.y, state['Z'], state)
c += feed(p.x, p.y, state["Z"], state)
return c
def speed(xy=None, z=None, state=None):
c = []
if state is None:
@@ -150,66 +165,67 @@ def speed(xy=None, z=None, state=None):
print(xy, z, state)
if xy is not None:
xy = float(xy)
if xy > 0.0 and xy != state['XYspeed']:
if xy > 0.0 and xy != state["XYspeed"]:
c.append("VS%.1f;" % xy)
state['XYspeed'] = xy
state["XYspeed"] = xy
if z is not None:
z = float(z)
if z > 0.0 and z != state['Zspeed']:
if z > 0.0 and z != state["Zspeed"]:
c.append("!VZ%.1f;" % z)
state['Zspeed'] = z
state["Zspeed"] = z
return c
def convertgcode(cmd, args, state):
"""Convert a single gcode command to equivalent Roland code"""
if cmd == 'G0':
if cmd == "G0":
# jog
return jog(args['X'], args['Y'], args['Z'], state)
elif cmd == 'G1':
return jog(args["X"], args["Y"], args["Z"], state)
elif cmd == "G1":
# linear feed
c = []
# feedrate
c += speed(xy=args['F'], z=args['F'], state=state)
c += speed(xy=args["F"], z=args["F"], state=state)
# motion
c += feed(args['X'], args['Y'], args['Z'], state)
c += feed(args["X"], args["Y"], args["Z"], state)
return c
elif cmd == 'G2' or cmd == 'G3':
elif cmd == "G2" or cmd == "G3":
# arc feed
c = []
# feedrate
c += speed(xy=args['F'], state=state)
c += speed(xy=args["F"], state=state)
# motion
if args['X'] and args['Y'] and args['Z']:
if args["X"] and args["Y"] and args["Z"]:
# helical motion
pass
elif args['X'] and args['Y']:
elif args["X"] and args["Y"]:
# arc in plane
c += xyarc(args, state)
return c
elif cmd == 'G20':
elif cmd == "G20":
# inches mode
raise ValueError("rml_post: Inches mode not supported")
elif cmd == 'G21':
elif cmd == "G21":
# millimeter mode
return ""
elif cmd == 'G40':
elif cmd == "G40":
# tool compensation off
return ""
elif cmd == 'G80':
elif cmd == "G80":
# cancel all cycles (drill normally)
return "PU;"
elif cmd == 'G81':
elif cmd == "G81":
c = []
# feedrate
c += speed(z=args['F'], state=state)
c += speed(z=args["F"], state=state)
# motion
c += jog(args['X'], args['Y'], state=state)
c += feed(args['X'], args['Y'], args['Z'], state)
c += jog(args["X"], args["Y"], state=state)
c += feed(args["X"], args["Y"], args["Z"], state)
return c
elif cmd == 'G90':
elif cmd == "G90":
# absolute mode?
return ""
elif cmd == 'G98':
elif cmd == "G98":
# feedrate
return ""
else:
@@ -219,14 +235,14 @@ def convertgcode(cmd, args, state):
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
state = { 'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'XYspeed': -1.0, 'Zspeed': -1.0 }
state = {"X": 0.0, "Y": 0.0, "Z": 0.0, "XYspeed": -1.0, "Zspeed": -1.0}
output = []
# header
output += addheader()
output += motoron()
output += speed(2.0, 1.0, state) # defaults
output += speed(2.0, 1.0, state) # defaults
# respect clearance height?
@@ -236,13 +252,13 @@ def parse(inputstring):
if not line:
continue
parsed = PostUtils.stringsplit(line)
command = parsed['command']
print('cmd', line)
command = parsed["command"]
print("cmd", line)
try:
if command:
code = convertgcode(command, parsed, state)
if not isinstance(code, list):
code = [ code ]
code = [code]
if len(code) and code[0]:
output += code
except NotImplementedError as e:
@@ -253,7 +269,7 @@ def parse(inputstring):
output += home()
output += addfooter()
return '\n'.join(output)
return "\n".join(output)
# print (__name__ + " gcode postprocessor loaded.")

View File

@@ -34,7 +34,7 @@ from FreeCAD import Units
import PathScripts.PathUtil as PathUtil
import PathScripts.PostUtils as PostUtils
Revised = '2021-10-21' # Revision date for this file.
Revised = "2021-10-21" # Revision date for this file.
# *****************************************************************************
# * Due to the fundamentals of the FreeCAD pre-processor, *
@@ -46,154 +46,145 @@ Revised = '2021-10-21' # Revision date for this file.
# *****************************************************************************
TOOLTIP = '''
TOOLTIP = """
Generate g-code from a Path that is compatible with the Duet controller (RepRapFirmware).
import rrf_post
rrf_post.export(object, "/path/to/file.nc")
'''
"""
# *****************************************************************************
# * Initial configuration, not changeable *
# *****************************************************************************
MOTION_MODE = 'G90' # G90 only, for absolute moves
WORK_PLANE = 'G17' # G17 only, XY plane, for vertical milling
UNITS = 'G21' # G21 only, for metric
UNIT_FORMAT = 'mm'
UNIT_FEED_FORMAT = 'mm/min'
MOTION_MODE = "G90" # G90 only, for absolute moves
WORK_PLANE = "G17" # G17 only, XY plane, for vertical milling
UNITS = "G21" # G21 only, for metric
UNIT_FORMAT = "mm"
UNIT_FEED_FORMAT = "mm/min"
# *****************************************************************************
# * Initial configuration, changeable via command line arguments *
# *****************************************************************************
PRECISION = 3 # Decimal places displayed for metric
DRILL_RETRACT_MODE = 'G98' # End of drill-cycle retractation type. G99
PRECISION = 3 # Decimal places displayed for metric
DRILL_RETRACT_MODE = "G98" # End of drill-cycle retractation type. G99
# is the alternative.
TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated
TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated
# into G0/G1 moves
RETURN_TO = None # None = No movement at end of program
SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4
MODAL = False # True: Commands are suppressed if they are
RETURN_TO = None # None = No movement at end of program
SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4
MODAL = False # True: Commands are suppressed if they are
# the same as the previous line
LINENR = 100 # Line number starting value
LINEINCR = 10 # Line number increment
PRE_OPERATION = '''''' # Pre operation text will be inserted before
LINENR = 100 # Line number starting value
LINEINCR = 10 # Line number increment
PRE_OPERATION = """""" # Pre operation text will be inserted before
# every operation
POST_OPERATION = '''''' # Post operation text will be inserted after
POST_OPERATION = """""" # Post operation text will be inserted after
# every operation
TOOL_CHANGE = '''''' # Tool Change commands will be inserted
TOOL_CHANGE = """""" # Tool Change commands will be inserted
# before a tool change
# *****************************************************************************
# * Initial gcode output options, changeable via command line arguments *
# *****************************************************************************
OUTPUT_HEADER = True # Output header in output gcode file
OUTPUT_COMMENTS = True # Comments in output gcode file
OUTPUT_FINISH = False # Include an operation finished comment
OUTPUT_PATH = False # Include a Path: comment
OUTPUT_RRF_CONFIG = True # Display expected #defines for RRF config
OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file
OUTPUT_BCNC = False # Add bCNC operation block headers in output
OUTPUT_HEADER = True # Output header in output gcode file
OUTPUT_COMMENTS = True # Comments in output gcode file
OUTPUT_FINISH = False # Include an operation finished comment
OUTPUT_PATH = False # Include a Path: comment
OUTPUT_RRF_CONFIG = True # Display expected #defines for RRF config
OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file
OUTPUT_BCNC = False # Add bCNC operation block headers in output
# gcode file
SHOW_EDITOR = True # Display the resulting gcode file
SHOW_EDITOR = True # Display the resulting gcode file
OUTPUT_TOOL_CHANGE = True
# *****************************************************************************
# * Command line arguments *
# *****************************************************************************
parser = argparse.ArgumentParser(prog='rrf', add_help=False)
parser = argparse.ArgumentParser(prog="rrf", add_help=False)
parser.add_argument("--header", action="store_true", help="output headers (default)")
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument("--comments", action="store_true", help="output comment (default)")
parser.add_argument(
'--header',
action='store_true',
help='output headers (default)')
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
'--no-header',
action='store_true',
help='suppress header output')
"--finish-comments", action="store_true", help="output finish-comment"
)
parser.add_argument(
'--comments',
action='store_true',
help='output comment (default)')
"--no-finish-comments",
action="store_true",
help="suppress finish-comment output (default)",
)
parser.add_argument("--path-comments", action="store_true", help="output path-comment")
parser.add_argument(
'--no-comments',
action='store_true',
help='suppress comment output')
"--no-path-comments",
action="store_true",
help="suppress path-comment output (default)",
)
parser.add_argument("--rrf-config", action="store_true", help="output #defines for RRF")
parser.add_argument(
'--finish-comments',
action='store_true',
help='output finish-comment')
"--no-rrf-config",
action="store_true",
help="suppress output #defines for RRF (default)",
)
parser.add_argument(
'--no-finish-comments',
action='store_true',
help='suppress finish-comment output (default)')
"--line-numbers", action="store_true", help="prefix with line numbers"
)
parser.add_argument(
'--path-comments',
action='store_true',
help='output path-comment')
"--no-line-numbers",
action="store_true",
help="do not prefix with line numbers (default)",
)
parser.add_argument(
'--no-path-comments',
action='store_true',
help='suppress path-comment output (default)')
"--show-editor",
action="store_true",
help="pop up editor before writing output (default)",
)
parser.add_argument(
'--rrf-config',
action='store_true',
help='output #defines for RRF')
"--no-show-editor",
action="store_true",
help="do not pop up editor before writing output",
)
parser.add_argument(
'--no-rrf-config',
action='store_true',
help='suppress output #defines for RRF (default)')
"--precision", default="3", help="number of digits of precision, default=3"
)
parser.add_argument(
'--line-numbers',
action='store_true',
help='prefix with line numbers')
"--translate_drill",
action="store_true",
help="translate drill cycles G81, G82, G83 into G0/G1 movements (default)",
)
parser.add_argument(
'--no-line-numbers',
action='store_true',
help='do not prefix with line numbers (default)')
"--no-translate_drill",
action="store_true",
help="do not translate drill cycles G81, G82, G83 into G0/G1 movements",
)
parser.add_argument(
'--show-editor',
action='store_true',
help='pop up editor before writing output (default)')
"--preamble", help='set commands to be issued before the first command, default=""'
)
parser.add_argument(
'--no-show-editor',
action='store_true',
help='do not pop up editor before writing output')
"--postamble", help='set commands to be issued after the last command, default="M5"'
)
parser.add_argument(
'--precision',
default='3',
help='number of digits of precision, default=3')
"--tool-change", action="store_true", help="Insert M6 for all tool changes"
)
parser.add_argument(
'--translate_drill',
action='store_true',
help='translate drill cycles G81, G82, G83 into G0/G1 movements (default)')
parser.add_argument(
'--no-translate_drill',
action='store_true',
help='do not translate drill cycles G81, G82, G83 into G0/G1 movements')
parser.add_argument(
'--preamble',
help='set commands to be issued before the first command, default=""')
parser.add_argument(
'--postamble',
help='set commands to be issued after the last command, default="M5"')
parser.add_argument(
'--tool-change', action='store_true',
help='Insert M6 for all tool changes')
parser.add_argument(
'--wait-for-spindle',
"--wait-for-spindle",
type=int,
default=3,
help='Wait for spindle to reach desired speed after M3 or M4, default=0')
help="Wait for spindle to reach desired speed after M3 or M4, default=0",
)
parser.add_argument(
'--return-to',
default='',
help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"')
"--return-to",
default="",
help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"',
)
parser.add_argument(
'--bcnc',
action='store_true',
help='Add Job operations as bCNC block headers. \
Consider suppressing existing comments: Add argument --no-comments')
"--bcnc",
action="store_true",
help="Add Job operations as bCNC block headers. \
Consider suppressing existing comments: Add argument --no-comments",
)
parser.add_argument(
'--no-bcnc',
action='store_true',
help='suppress bCNC block header output (default)')
"--no-bcnc", action="store_true", help="suppress bCNC block header output (default)"
)
TOOLTIP_ARGS = parser.format_help()
# *****************************************************************************
@@ -207,19 +198,19 @@ TOOLTIP_ARGS = parser.format_help()
# *****************************************************************************
# Default preamble text will appear at the beginning of the gcode output file.
PREAMBLE = ''''''
PREAMBLE = """"""
# Default postamble text will appear following the last operation.
POSTAMBLE = '''M5
'''
POSTAMBLE = """M5
"""
# *****************************************************************************
# * Internal global variables *
# *****************************************************************************
MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
RAPID_MOVES = ['G0', 'G00'] # Rapid moves gcode commands definition
MOTION_COMMANDS = ["G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03"]
RAPID_MOVES = ["G0", "G00"] # Rapid moves gcode commands definition
SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out
COMMAND_SPACE = ' '
COMMAND_SPACE = " "
# Global variables storing current position (Use None for safety.)
CURRENT_X = None
CURRENT_Y = None
@@ -288,9 +279,9 @@ def processArguments(argstring):
OUTPUT_TOOL_CHANGE = True
if args.return_to:
RETURN_TO = args.return_to
if RETURN_TO.find(',') == -1:
if RETURN_TO.find(",") == -1:
RETURN_TO = None
print('--return-to coordinates must be specified as:')
print("--return-to coordinates must be specified as:")
print('--return-to "x.n,y.n,z.n"')
if args.bcnc:
OUTPUT_BCNC = True
@@ -309,14 +300,14 @@ def processArguments(argstring):
def dump(obj):
for attr in dir(obj):
try:
if attr.startswith('__'):
if attr.startswith("__"):
continue
print('>' + attr + '<')
print(">" + attr + "<")
attr_text = "%s = %s" % (attr, getattr(obj, attr))
if attr in ['HorizFeed', 'VertFeed']:
print('==============\n', attr_text)
if 'mm/s' in attr_text:
print('===> metric values <===')
if attr in ["HorizFeed", "VertFeed"]:
print("==============\n", attr_text)
if "mm/s" in attr_text:
print("===> metric values <===")
except Exception: # Insignificant errors
# print('==>', obj, attr)
pass
@@ -332,123 +323,129 @@ def export(objectslist, filename, argstring):
global MOTION_MODE
global SUPPRESS_COMMANDS
print('Post Processor: ' + __name__ + ' postprocessing...')
gcode = ''
print("Post Processor: " + __name__ + " postprocessing...")
gcode = ""
# Write header:
if OUTPUT_HEADER:
gcode += linenumber() + '(Exported by FreeCAD)\n'
gcode += linenumber() + '(Post Processor: ' + __name__
gcode += '.py, version: ' + Revised + ')\n'
gcode += linenumber() + '(Output Time:' + str(datetime.now()) + ')\n'
gcode += linenumber() + "(Exported by FreeCAD)\n"
gcode += linenumber() + "(Post Processor: " + __name__
gcode += ".py, version: " + Revised + ")\n"
gcode += linenumber() + "(Output Time:" + str(datetime.now()) + ")\n"
# Suppress drill-cycle commands:
if TRANSLATE_DRILL_CYCLES:
SUPPRESS_COMMANDS += ['G80', 'G98', 'G99']
SUPPRESS_COMMANDS += ["G80", "G98", "G99"]
# Write the preamble:
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Begin preamble)\n'
gcode += linenumber() + "(Begin preamble)\n"
for line in PREAMBLE.splitlines(True):
gcode += linenumber() + line
# Write these settings AFTER the preamble,
# to prevent the preamble from changing these:
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Default Configuration)\n'
gcode += linenumber() + MOTION_MODE + '\n'
gcode += linenumber() + UNITS + '\n'
gcode += linenumber() + WORK_PLANE + '\n'
gcode += linenumber() + "(Default Configuration)\n"
gcode += linenumber() + MOTION_MODE + "\n"
gcode += linenumber() + UNITS + "\n"
gcode += linenumber() + WORK_PLANE + "\n"
for obj in objectslist:
# Debug...
# print('\n' + '*'*70 + '\n')
# dump(obj)
# print('\n' + '*'*70 + '\n')
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
# Skip inactive operations:
if PathUtil.opProperty(obj, 'Active') is False:
if PathUtil.opProperty(obj, "Active") is False:
continue
# Do the pre_op:
if OUTPUT_BCNC:
gcode += linenumber() + '(Block-name: ' + obj.Label + ')\n'
gcode += linenumber() + '(Block-expand: 0)\n'
gcode += linenumber() + '(Block-enable: 1)\n'
gcode += linenumber() + "(Block-name: " + obj.Label + ")\n"
gcode += linenumber() + "(Block-expand: 0)\n"
gcode += linenumber() + "(Block-enable: 1)\n"
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Begin operation: ' + obj.Label + ')\n'
gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n"
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
# Get coolant mode:
coolantMode = 'None' # None is the word returned from the operation
if hasattr(obj, 'CoolantMode') or hasattr(obj, 'Base') and \
hasattr(obj.Base, 'CoolantMode'):
if hasattr(obj, 'CoolantMode'):
coolantMode = "None" # None is the word returned from the operation
if (
hasattr(obj, "CoolantMode")
or hasattr(obj, "Base")
and hasattr(obj.Base, "CoolantMode")
):
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
else:
coolantMode = obj.Base.CoolantMode
# Turn coolant on if required:
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n'
if coolantMode == 'Flood':
gcode += linenumber() + 'M8\n'
if coolantMode == 'Mist':
gcode += linenumber() + 'M7\n'
if not coolantMode == "None":
gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n"
if coolantMode == "Flood":
gcode += linenumber() + "M8\n"
if coolantMode == "Mist":
gcode += linenumber() + "M7\n"
# Parse the op:
gcode += parse(obj)
# Do the post_op:
if OUTPUT_COMMENTS and OUTPUT_FINISH:
gcode += linenumber() + '(Finish operation: ' + obj.Label + ')\n'
gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n"
for line in POST_OPERATION.splitlines(True):
gcode += linenumber() + line
# Turn coolant off if previously enabled:
if not coolantMode == 'None':
if not coolantMode == "None":
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n'
gcode += linenumber() + 'M9\n'
gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n"
gcode += linenumber() + "M9\n"
# Do the post_amble:
if OUTPUT_BCNC:
gcode += linenumber() + '(Block-name: post_amble)\n'
gcode += linenumber() + '(Block-expand: 0)\n'
gcode += linenumber() + '(Block-enable: 1)\n'
gcode += linenumber() + "(Block-name: post_amble)\n"
gcode += linenumber() + "(Block-expand: 0)\n"
gcode += linenumber() + "(Block-enable: 1)\n"
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Begin postamble)\n'
gcode += linenumber() + "(Begin postamble)\n"
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
# Optionally add a final XYZ position to the end of the gcode:
if RETURN_TO:
first_comma = RETURN_TO.find(',')
last_comma = RETURN_TO.rfind(',') # == first_comma if only one comma
ref_X = ' X' + RETURN_TO[0: first_comma].strip()
first_comma = RETURN_TO.find(",")
last_comma = RETURN_TO.rfind(",") # == first_comma if only one comma
ref_X = " X" + RETURN_TO[0:first_comma].strip()
# Z is optional:
if last_comma != first_comma:
ref_Z = ' Z' + RETURN_TO[last_comma + 1:].strip()
ref_Y = ' Y' + RETURN_TO[first_comma + 1:last_comma].strip()
ref_Z = " Z" + RETURN_TO[last_comma + 1 :].strip()
ref_Y = " Y" + RETURN_TO[first_comma + 1 : last_comma].strip()
else:
ref_Z = ''
ref_Y = ' Y' + RETURN_TO[first_comma + 1:].strip()
ref_Z = ""
ref_Y = " Y" + RETURN_TO[first_comma + 1 :].strip()
gcode += linenumber() + 'G0' + ref_X + ref_Y + ref_Z + '\n'
gcode += linenumber() + "G0" + ref_X + ref_Y + ref_Z + "\n"
# Optionally add recommended RRF configuration to gcode file:
if OUTPUT_RRF_CONFIG:
gcode += linenumber() + '(RRF Configuration)\n'
gcode += linenumber() + '(The following should be enabled in)\n'
gcode += linenumber() + '(the config.g)\n'
gcode += linenumber() + '(M453)\n'
gcode += linenumber() + "(RRF Configuration)\n"
gcode += linenumber() + "(The following should be enabled in)\n"
gcode += linenumber() + "(the config.g)\n"
gcode += linenumber() + "(M453)\n"
# Show the gcode result dialog:
if FreeCAD.GuiUp and SHOW_EDITOR:
@@ -462,26 +459,26 @@ def export(objectslist, filename, argstring):
else:
final = gcode
print('Done postprocessing.')
print("Done postprocessing.")
# Write the file:
with open(filename, 'w') as fp:
with open(filename, "w") as fp:
fp.write(final)
def linenumber():
if not OUTPUT_LINE_NUMBERS:
return ''
return ""
global LINENR
global LINEINCR
LINENR += LINEINCR
return 'N' + str(LINENR) + ' '
return "N" + str(LINENR) + " "
def format_outlist(strTable):
# construct the line for the final output
global COMMAND_SPACE
s = ''
s = ""
for w in strTable:
s += w + COMMAND_SPACE
return s.strip()
@@ -494,27 +491,46 @@ def parse(pathobj):
global CURRENT_Y
global CURRENT_Z
out = ''
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F',
'S', 'T', 'Q', 'R', 'L', 'P']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"U",
"V",
"W",
"I",
"J",
"K",
"F",
"S",
"T",
"Q",
"R",
"L",
"P",
]
if hasattr(pathobj, 'Group'): # We have a compound or project.
if hasattr(pathobj, "Group"): # We have a compound or project.
if OUTPUT_COMMENTS:
out += linenumber() + '(Compound: ' + pathobj.Label + ')\n'
out += linenumber() + "(Compound: " + pathobj.Label + ")\n"
for p in pathobj.Group:
out += parse(p)
return out
else: # Parsing simple path
# groups might contain non-path things like stock.
if not hasattr(pathobj, 'Path'):
if not hasattr(pathobj, "Path"):
return out
if OUTPUT_COMMENTS and OUTPUT_PATH:
out += linenumber() + '(Path: ' + pathobj.Label + ')\n'
out += linenumber() + "(Path: " + pathobj.Label + ")\n"
for c in pathobj.Path.Commands:
outlist = []
@@ -531,107 +547,112 @@ def parse(pathobj):
# Add the remaining parameters in order:
for param in params:
if param in c.Parameters:
if param == 'F':
if param == "F":
if command not in RAPID_MOVES:
feedRate = Units.Quantity(
c.Parameters['F'], FreeCAD.Units.Velocity)
c.Parameters["F"], FreeCAD.Units.Velocity
)
if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0:
outlist.append(param + format(float(
feedRate.getValueAs(UNIT_FEED_FORMAT)),
precision_string))
elif param =='T':
outlist.append(param + str(int(c.Parameters[param])))
elif param in ['H', 'D', 'S', 'P', 'L']:
outlist.append(
param
+ format(
float(feedRate.getValueAs(UNIT_FEED_FORMAT)),
precision_string,
)
)
elif param == "T":
outlist.append(param + str(int(c.Parameters[param])))
elif param in ["H", "D", "S", "P", "L"]:
outlist.append(param + str(c.Parameters[param]))
elif param in ['A', 'B', 'C']:
outlist.append(param + format(
c.Parameters[param], precision_string))
elif param in ["A", "B", "C"]:
outlist.append(
param + format(c.Parameters[param], precision_string)
)
# [X, Y, Z, U, V, W, I, J, K, R, Q]
else:
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length)
outlist.append(param + format(float(
pos.getValueAs(UNIT_FORMAT)), precision_string))
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outlist.append(
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# Store the latest command:
lastcommand = command
# Capture the current position for subsequent calculations:
if command in MOTION_COMMANDS:
if 'X' in c.Parameters:
CURRENT_X = Units.Quantity(
c.Parameters['X'], FreeCAD.Units.Length)
if 'Y' in c.Parameters:
CURRENT_Y = Units.Quantity(
c.Parameters['Y'], FreeCAD.Units.Length)
if 'Z' in c.Parameters:
CURRENT_Z = Units.Quantity(
c.Parameters['Z'], FreeCAD.Units.Length)
if "X" in c.Parameters:
CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length)
if "Y" in c.Parameters:
CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length)
if "Z" in c.Parameters:
CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length)
if command in ('G98', 'G99'):
if command in ("G98", "G99"):
DRILL_RETRACT_MODE = command
if TRANSLATE_DRILL_CYCLES:
if command in ('G81', 'G82', 'G83'):
if command in ("G81", "G82", "G83"):
out += drill_translate(outlist, command, c.Parameters)
# Erase the line just translated:
outlist = []
if SPINDLE_WAIT > 0:
if command in ('M3', 'M03', 'M4', 'M04'):
out += linenumber() + format_outlist(outlist) + '\n'
if command in ("M3", "M03", "M4", "M04"):
out += linenumber() + format_outlist(outlist) + "\n"
# RRF: P for milliseconds, S for seconds, change P to S
out += linenumber()
out += format_outlist(['G4', 'S%s' % SPINDLE_WAIT])
out += '\n'
out += format_outlist(["G4", "S%s" % SPINDLE_WAIT])
out += "\n"
outlist = []
# Check for Tool Change:
if command in ('M6', 'M06'):
if command in ("M6", "M06"):
if OUTPUT_COMMENTS:
out += linenumber() + '(Begin toolchange)\n'
out += linenumber() + "(Begin toolchange)\n"
if OUTPUT_TOOL_CHANGE:
for line in TOOL_CHANGE.splitlines(True):
out += linenumber() + line + '\n'
outlist[0] = ' '
outlist[-1] =('T' + str(int(c.Parameters['T'])))
out += linenumber() + line + "\n"
outlist[0] = " "
outlist[-1] = "T" + str(int(c.Parameters["T"]))
if not OUTPUT_TOOL_CHANGE and OUTPUT_COMMENTS:
# next 2 lines could also be replaced by a single line as "outlist = []"
outlist[0] = ' '
outlist[-1] = ' '
# next 2 lines could also be replaced by a single line as "outlist = []"
outlist[0] = " "
outlist[-1] = " "
if not OUTPUT_TOOL_CHANGE and not OUTPUT_COMMENTS:
outlist = []
if command == 'message':
if command == "message":
if OUTPUT_COMMENTS:
outlist.pop(0) # remove the command
else:
out = []
if command in SUPPRESS_COMMANDS:
outlist[0] = '(' + outlist[0]
outlist[-1] = outlist[-1] + ')'
outlist[0] = "(" + outlist[0]
outlist[-1] = outlist[-1] + ")"
# Remove embedded comments:
if not OUTPUT_COMMENTS:
tmplist = []
list_index = 0
while list_index < len(outlist):
left_index = outlist[list_index].find('(')
left_index = outlist[list_index].find("(")
if left_index == -1: # Not a comment
tmplist.append(outlist[list_index])
else: # This line contains a comment, and possibly more
right_index = outlist[list_index].find(')')
comment_area = outlist[list_index][
left_index: right_index + 1]
line_minus_comment = outlist[list_index].replace(
comment_area, '').strip()
right_index = outlist[list_index].find(")")
comment_area = outlist[list_index][left_index : right_index + 1]
line_minus_comment = (
outlist[list_index].replace(comment_area, "").strip()
)
if line_minus_comment:
# Line contained more than just a comment
tmplist.append(line_minus_comment)
@@ -641,7 +662,7 @@ def parse(pathobj):
# Prepend a line number and append a newline
if len(outlist) >= 1:
out += linenumber() + format_outlist(outlist) + '\n'
out += linenumber() + format_outlist(outlist) + "\n"
return out
@@ -662,61 +683,56 @@ def drill_translate(outlist, cmd, params):
global UNIT_FEED_FORMAT
class Drill: # Using a class is necessary for the nested functions.
gcode = ''
gcode = ""
strFormat = '.' + str(PRECISION) + 'f'
strFormat = "." + str(PRECISION) + "f"
if OUTPUT_COMMENTS: # Comment the original command
outlist[0] = '(' + outlist[0]
outlist[-1] = outlist[-1] + ')'
Drill.gcode += linenumber() + format_outlist(outlist) + '\n'
outlist[0] = "(" + outlist[0]
outlist[-1] = outlist[-1] + ")"
Drill.gcode += linenumber() + format_outlist(outlist) + "\n"
# Cycle conversion only converts the cycles in the XY plane (G17).
# --> ZX (G18) and YZ (G19) planes produce false gcode.
drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length)
drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length)
drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length)
drill_R = Units.Quantity(params['R'], FreeCAD.Units.Length)
drill_F = Units.Quantity(params['F'], FreeCAD.Units.Velocity)
if cmd == 'G82':
drill_DwellTime = params['P']
elif cmd == 'G83':
drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length)
drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length)
drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length)
drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length)
drill_R = Units.Quantity(params["R"], FreeCAD.Units.Length)
drill_F = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
if cmd == "G82":
drill_DwellTime = params["P"]
elif cmd == "G83":
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
# R less than Z is error
if drill_R < drill_Z:
Drill.gcode += linenumber() + '(drill cycle error: R less than Z )\n'
Drill.gcode += linenumber() + "(drill cycle error: R less than Z )\n"
return Drill.gcode
# Z height to retract to when drill cycle is done:
if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z > drill_R:
if DRILL_RETRACT_MODE == "G98" and CURRENT_Z > drill_R:
RETRACT_Z = CURRENT_Z
else:
RETRACT_Z = drill_R
# Z motion nested functions:
def rapid_Z_to(new_Z):
Drill.gcode += linenumber() + 'G0 Z'
Drill.gcode += format(
float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + '\n'
Drill.gcode += linenumber() + "G0 Z"
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
def feed_Z_to(new_Z):
Drill.gcode += linenumber() + 'G1 Z'
Drill.gcode += format(
float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F'
Drill.gcode += format(
float(drill_F.getValueAs(UNIT_FEED_FORMAT)), '.2f') + '\n'
Drill.gcode += linenumber() + "G1 Z"
Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + " F"
Drill.gcode += format(float(drill_F.getValueAs(UNIT_FEED_FORMAT)), ".2f") + "\n"
# Make sure that Z is not below RETRACT_Z:
if CURRENT_Z < RETRACT_Z:
rapid_Z_to(RETRACT_Z)
# Rapid to hole position XY:
Drill.gcode += linenumber() + 'G0 X'
Drill.gcode += format(
float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y'
Drill.gcode += format(
float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + '\n'
Drill.gcode += linenumber() + "G0 X"
Drill.gcode += format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + " Y"
Drill.gcode += format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
# Rapid to R:
rapid_Z_to(drill_R)
@@ -732,13 +748,13 @@ def drill_translate(outlist, cmd, params):
# * G99 After the hole has been drilled, retract to R height *
# * Select G99 only if safe to move from hole to hole at the R height *
# *************************************************************************
if cmd in ('G81', 'G82'):
if cmd in ("G81", "G82"):
feed_Z_to(drill_Z) # Drill hole in one step
if cmd == 'G82': # Dwell time delay at the bottom of the hole
Drill.gcode += linenumber() + 'G4 S' + str(drill_DwellTime) + '\n'
if cmd == "G82": # Dwell time delay at the bottom of the hole
Drill.gcode += linenumber() + "G4 S" + str(drill_DwellTime) + "\n"
# RRF uses P for milliseconds, S for seconds, change P to S
elif cmd == 'G83': # Peck drill cycle:
elif cmd == "G83": # Peck drill cycle:
chip_Space = drill_Step * 0.5
next_Stop_Z = drill_R - drill_Step
while next_Stop_Z >= drill_Z:
@@ -772,5 +788,3 @@ def drill_translate(outlist, cmd, params):
# PEP8 format passed using: http://pep8online.com/, which primarily covers
# indentation and line length. Some other aspects of PEP8 which have not
# been applied yet may be applied in future updates.

View File

@@ -21,16 +21,16 @@
# * *
# ***************************************************************************
'''
"""
This is an preprocessor to read gcode files produced from slic3r.
'''
"""
import os
import Path
import FreeCAD
# 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
@@ -93,5 +93,4 @@ def parse(inputstring):
return output
print (__name__ + " gcode preprocessor loaded.")
print(__name__ + " gcode preprocessor loaded.")

View File

@@ -30,7 +30,7 @@ import FreeCAD
from FreeCAD import Units
import shlex
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 smoothieboard. This postprocessor, once placed
@@ -39,25 +39,55 @@ FreeCAD, via the GUI importer or via python scripts with:
import smoothie_post
smoothie_post.export(object,"/path/to/file.ncc","")
'''
"""
now = datetime.datetime.now()
parser = argparse.ArgumentParser(prog='linuxcnc', add_help=False)
parser.add_argument('--header', action='store_true', help='output headers (default)')
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--comments', action='store_true', help='output comment (default)')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
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('--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('--IP_ADDR', help='IP Address for machine target machine')
parser.add_argument('--verbose', action='store_true', help='verbose output for debugging, default="False"')
parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)')
parser = argparse.ArgumentParser(prog="linuxcnc", add_help=False)
parser.add_argument("--header", action="store_true", help="output headers (default)")
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument("--comments", action="store_true", help="output comment (default)")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="prefix with line numbers"
)
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(
"--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("--IP_ADDR", help="IP Address for machine target machine")
parser.add_argument(
"--verbose",
action="store_true",
help='verbose output for debugging, default="False"',
)
parser.add_argument(
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
)
TOOLTIP_ARGS = parser.format_help()
@@ -80,38 +110,38 @@ LINENR = 100 # line number starting value
# These globals will be reflected in the Machine configuration of the project
UNITS = "G21" # G21 for metric, G20 for us standard
UNIT_SPEED_FORMAT = 'mm/min'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = "mm/min"
UNIT_FORMAT = "mm"
MACHINE_NAME = "SmoothieBoard"
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}
# Preamble text will appear at the beginning of the GCODE output file.
PREAMBLE = '''G17 G90
'''
PREAMBLE = """G17 G90
"""
# Postamble text will appear following the last operation.
POSTAMBLE = '''M05
POSTAMBLE = """M05
G17 G90
M2
'''
"""
# Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# Tool Change commands will be inserted before a tool change
TOOL_CHANGE = ''''''
TOOL_CHANGE = """"""
# Number of digits after the decimal point
PRECISION = 5
# 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
@@ -155,9 +185,9 @@ def processArguments(argstring):
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
UNITS = "G20"
UNIT_SPEED_FORMAT = "in/min"
UNIT_FORMAT = "in"
IP_ADDR = args.IP_ADDR
VERBOSE = args.verbose
@@ -173,7 +203,11 @@ def export(objectslist, filename, argstring):
global UNITS
for obj in objectslist:
if not hasattr(obj, "Path"):
FreeCAD.Console.PrintError("the object " + obj.Name + " is not a path. Please select only path and Compounds.\n")
FreeCAD.Console.PrintError(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds.\n"
)
return
FreeCAD.Console.PrintMessage("postprocessing...\n")
@@ -245,7 +279,7 @@ def export(objectslist, filename, argstring):
sendToSmoothie(IP_ADDR, final, filename)
else:
if not filename == '-':
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
@@ -260,7 +294,7 @@ def sendToSmoothie(ip, GCODE, fname):
import os
fname = os.path.basename(fname)
FreeCAD.Console.PrintMessage('sending to smoothie: {}\n'.format(fname))
FreeCAD.Console.PrintMessage("sending to smoothie: {}\n".format(fname))
f = GCODE.rstrip()
filesize = len(f)
@@ -268,9 +302,9 @@ def sendToSmoothie(ip, GCODE, fname):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(4.0)
s.connect((ip, 115))
tn = s.makefile(mode='rw')
tn = s.makefile(mode="rw")
# read startup prompt
# read startup prompt
ln = tn.readline()
if not ln.startswith("+"):
FreeCAD.Console.PrintMessage("Failed to connect with sftp: {}\n".format(ln))
@@ -279,7 +313,7 @@ def sendToSmoothie(ip, GCODE, fname):
if VERBOSE:
print("RSP: " + ln.strip())
# Issue initial store command
# Issue initial store command
tn.write("STOR OLD /sd/" + fname + "\n")
tn.flush()
@@ -291,7 +325,7 @@ def sendToSmoothie(ip, GCODE, fname):
if VERBOSE:
print("RSP: " + ln.strip())
# send size of file
# send size of file
tn.write("SIZE " + str(filesize) + "\n")
tn.flush()
@@ -304,13 +338,13 @@ def sendToSmoothie(ip, GCODE, fname):
print("RSP: " + ln.strip())
cnt = 0
# now send file
# now send file
for line in f.splitlines(1):
tn.write(line)
if VERBOSE:
cnt += len(line)
print("SND: " + line.strip())
print(str(cnt) + "/" + str(filesize) + "\r", end='')
print(str(cnt) + "/" + str(filesize) + "\r", end="")
tn.flush()
@@ -322,7 +356,7 @@ def sendToSmoothie(ip, GCODE, fname):
if VERBOSE:
print("RSP: " + ln.strip())
# exit
# exit
tn.write("DONE\n")
tn.flush()
tn.close()
@@ -343,12 +377,12 @@ def parse(pathobj):
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']
params = ["X", "Y", "Z", "A", "B", "I", "J", "F", "S", "T", "Q", "R", "L"]
if hasattr(pathobj, "Group"): # We have a compound or project.
# if OUTPUT_COMMENTS:
@@ -378,28 +412,42 @@ 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
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if param == "F":
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))
elif param == 'T':
outstring.append(param + str(c.Parameters['T']))
elif param == 'S':
outstring.append(param + str(c.Parameters['S']))
SPINDLE_SPEED = c.Parameters['S']
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
elif param == "T":
outstring.append(param + str(c.Parameters["T"]))
elif param == "S":
outstring.append(param + str(c.Parameters["S"]))
SPINDLE_SPEED = c.Parameters["S"]
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
if command in ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']:
outstring.append('S' + str(SPINDLE_SPEED))
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
if command in ["G1", "G01", "G2", "G02", "G3", "G03"]:
outstring.append("S" + str(SPINDLE_SPEED))
# store the latest command
lastcommand = command
# Check for Tool Change:
if command == 'M6':
if command == "M6":
# if OUTPUT_COMMENTS:
# out += linenumber() + "(begin toolchange)\n"
for line in TOOL_CHANGE.splitlines(True):

View File

@@ -35,12 +35,13 @@ from FreeCAD import Units
import Path
import argparse
import datetime
# import shlex
from PathScripts import PostUtils
VERSION = "0.0.4"
TOOLTIP = ''' Post processor for UC-CNC.
TOOLTIP = """ Post processor for UC-CNC.
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
@@ -55,57 +56,57 @@ This postprocessor was tested on UC-CNC v1.2111, an UC100 and a Stepcraft 420.
It was tested on FreeCAD v0.17, v0.18 and v0.19
Other (Stepcraft) machines using UC-CNC and UC* controllers should be easy to adapt.
'''
"""
# PREAMBLE_ possible values:
# Multi line text with gcode. Preamble gcode
# The preamble text will appear at the beginning of the GCODE output file.
PREAMBLE_DEFAULT = '''G17 (Default: XY-plane)
PREAMBLE_DEFAULT = """G17 (Default: XY-plane)
G54 (Default: First coordinate system)
G40 (Default: Cutter radius compensation none)
G49 (Default: Tool Length Offsets: cancel tool length)
G90 (Default: Absolute distance mode selection)
G80 (Cancel canned cycle)
'''
"""
PREAMBLE_DEFAULT_NO_COMMENT = '''G17
PREAMBLE_DEFAULT_NO_COMMENT = """G17
G54
G40
G49
G90
G80
'''
"""
# POSTAMBLE possible values:
# Multi line text with gcode. Postable gcode
# The postamble text will appear following the last operation.
POSTAMBLE_DEFAULT = '''M05 (stop spindle)
POSTAMBLE_DEFAULT = """M05 (stop spindle)
G17 (Default: XY-plane)
G54 (Default: First coordinate system)
G40 (Default: Cutter radius compensation none)
G90 (Default: Absolute distance mode selection)
G80 (Cancel canned cycle)
M30 (Stop program and rewind code)
'''
"""
POSTAMBLE_DEFAULT_NO_COMMENT = '''M05
POSTAMBLE_DEFAULT_NO_COMMENT = """M05
G17
G54
G40
G90
G80
M30
'''
"""
# PRE_OPERATION: Pre operation text will be inserted before every operation
PRE_OPERATION = ''''''
PRE_OPERATION = """"""
# POST_OPERATION: Post operation text will be inserted after every operation
POST_OPERATION = ''''''
POST_OPERATION = """"""
# TOOL_CHANGE: Tool Change commands will be inserted before a tool change
TOOL_CHANGE = ''''''
TOOL_CHANGE = """"""
################################
# Other configuration settings #
@@ -209,8 +210,8 @@ PRECISION = 3
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
# In code G20/G21 commands are silently ignored by UC-CNC
# UNITS is included in the post processor to mirror the profile settings.
UNITS_US_IMP = 'G20'
UNITS_METRIC = 'G21'
UNITS_US_IMP = "G20"
UNITS_METRIC = "G21"
UNITS = UNITS_METRIC
# UNIT_FORMAT possible values: (see UNITS)
@@ -220,8 +221,8 @@ UNITS = UNITS_METRIC
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
# In code G20/G21 commands are silently ignored by UC-CNC
# UNITS is included in the post processor to mirror the profile settings.
UNIT_FORMAT_US_IMP = 'in'
UNIT_FORMAT_METRIC = 'mm'
UNIT_FORMAT_US_IMP = "in"
UNIT_FORMAT_METRIC = "mm"
UNIT_FORMAT = UNIT_FORMAT_METRIC
# UNIT_SPEED_FORMAT possible values: (see UNITS)
@@ -231,8 +232,8 @@ UNIT_FORMAT = UNIT_FORMAT_METRIC
# note: G20/G21 are not supported by UC-CNC, units are configured in a program profile.
# In code G20/G21 commands are silently ignored by UC-CNC
# UNITS is included in the post processor to mirror the profile settings.
UNIT_SPEED_FORMAT_US_IMP = 'in/min'
UNIT_SPEED_FORMAT_METRIC = 'mm/min'
UNIT_SPEED_FORMAT_US_IMP = "in/min"
UNIT_SPEED_FORMAT_METRIC = "mm/min"
UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC
##################################################
@@ -241,40 +242,49 @@ UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC
# see: https://docs.python.org/3/library/argparse.html
parser = argparse.ArgumentParser(prog=__name__, add_help=False)
parser.add_argument('--name',
help='GCode program name')
parser.add_argument('--no-header', action='store_true',
help='suppress header output')
parser.add_argument('--no-comments', action='store_true',
help='suppress comment output')
parser.add_argument('--line-numbers', action='store_true',
help='suppress prefix with line numbers')
parser.add_argument('--no-show-editor', action='store_true',
help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3',
help='number of digits of precision, default=3')
parser.add_argument('--preamble',
help='set commands to be issued before the first command, default="G17\nG90\nG54"')
parser.add_argument('--postamble',
help='set commands to be issued after the last command, default="M05\nM30"')
parser.add_argument('--inches', action='store_true',
help='lengths in [in], G20')
parser.add_argument('--metric', action='store_true',
help='lengths in [mm], G21')
parser.add_argument('--modal', action='store_true',
help='repeat/suppress repeated command arguments')
parser.add_argument('--tool-length-offset', action='store_true',
help='suppress tool length offset G43 following tool changes')
parser.add_argument('--repeat', action='store_true',
help='repeat axis arguments')
parser.add_argument("--name", help="GCode program name")
parser.add_argument("--no-header", action="store_true", help="suppress header output")
parser.add_argument(
"--no-comments", action="store_true", help="suppress comment output"
)
parser.add_argument(
"--line-numbers", action="store_true", help="suppress prefix with line numbers"
)
parser.add_argument(
"--no-show-editor",
action="store_true",
help="don't pop up editor before writing output",
)
parser.add_argument(
"--precision", default="3", help="number of digits of precision, default=3"
)
parser.add_argument(
"--preamble",
help='set commands to be issued before the first command, default="G17\nG90\nG54"',
)
parser.add_argument(
"--postamble",
help='set commands to be issued after the last command, default="M05\nM30"',
)
parser.add_argument("--inches", action="store_true", help="lengths in [in], G20")
parser.add_argument("--metric", action="store_true", help="lengths in [mm], G21")
parser.add_argument(
"--modal", action="store_true", help="repeat/suppress repeated command arguments"
)
parser.add_argument(
"--tool-length-offset",
action="store_true",
help="suppress tool length offset G43 following tool changes",
)
parser.add_argument("--repeat", action="store_true", help="repeat axis arguments")
TOOLTIP_ARGS = parser.format_help()
# 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
# to distinguish python built-in open function from the one declared below
if open.__module__ == '__builtin__':
if open.__module__ == "__builtin__":
pythonopen = open
# debug option, trace to screen while processing to see where things break up.
@@ -291,29 +301,29 @@ UNIT_DEFAULT_CHANGED = False
warnings_count = 0
problems_count = 0
HEADER = '''(Exported by FreeCAD for {})
HEADER = """(Exported by FreeCAD for {})
(Post Processor: {}, version {})
(CAM file: {})
(Output Time: {})
'''
"""
def processArguments(argstring):
global SHOW_EDITOR # Show gcode before saving.
global PROG_NAME # Name of the G-Code program
global OUTPUT_HEADER # Use of a document header
global OUTPUT_COMMENTS # (Dont) use comments in output
global OUTPUT_LINE_NUMBERS # (Dont) use line numbers in output
global PREAMBLE # Preamble gcode
global POSTAMBLE # Postable gcode
global MODAL # Repeat/suppress repeated command arguments.
global USE_TLO # Set tool length offset
global PRECISION # Number of digits in feed and axis values
global UNITS # Code to switch to specific units
global UNIT_FORMAT # Text with specific units
global UNIT_SPEED_FORMAT # Text with specific units over time units
global UNIT_DEFAULT_CHANGED # tracing changes in UNIT settings.
global REPEAT_ARGUMENTS # Repeat or suppress axis values if the same as previous line.
global SHOW_EDITOR # Show gcode before saving.
global PROG_NAME # Name of the G-Code program
global OUTPUT_HEADER # Use of a document header
global OUTPUT_COMMENTS # (Dont) use comments in output
global OUTPUT_LINE_NUMBERS # (Dont) use line numbers in output
global PREAMBLE # Preamble gcode
global POSTAMBLE # Postable gcode
global MODAL # Repeat/suppress repeated command arguments.
global USE_TLO # Set tool length offset
global PRECISION # Number of digits in feed and axis values
global UNITS # Code to switch to specific units
global UNIT_FORMAT # Text with specific units
global UNIT_SPEED_FORMAT # Text with specific units over time units
global UNIT_DEFAULT_CHANGED # tracing changes in UNIT settings.
global REPEAT_ARGUMENTS # Repeat or suppress axis values if the same as previous line.
try:
UNIT_DEFAULT_CHANGED = False
@@ -381,14 +391,14 @@ def processArguments(argstring):
def append0(line):
result = line
if (trace_gcode):
if trace_gcode:
print("export: >>" + result)
return result
def append(line):
result = linenumber() + line
if (trace_gcode):
if trace_gcode:
print("export: >>" + result)
return result
@@ -407,7 +417,11 @@ def export(objectslist, filename, argstring):
for obj in objectslist:
if not hasattr(obj, "Path"):
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
print(
"the object "
+ obj.Name
+ " is not a path. Please select only path and Compounds."
)
return None
print("export: postprocessing...")
@@ -419,16 +433,20 @@ def export(objectslist, filename, argstring):
# write header
if OUTPUT_HEADER:
for line in HEADER.format(GCODE_PROCESSOR,
__name__, VERSION,
FreeCAD.ActiveDocument.FileName, str(now)).splitlines(False):
if (line):
for line in HEADER.format(
GCODE_PROCESSOR,
__name__,
VERSION,
FreeCAD.ActiveDocument.FileName,
str(now),
).splitlines(False):
if line:
gcode += append(line + "\n")
# Write the preamble
# G20/G21 not supported by UC-CNC, *always* report the configured units.
gcode += append("(Units: '" + UNIT_FORMAT + "' and '" + UNIT_SPEED_FORMAT + "')\n")
if (UNIT_DEFAULT_CHANGED):
if UNIT_DEFAULT_CHANGED:
gcode += append("(WARNING: Units default changed, check your UC-CNC profile)\n")
warnings_count += 1
@@ -455,12 +473,12 @@ def export(objectslist, filename, argstring):
# turn coolant on if required
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
if coolantMode == 'Mist':
if coolantMode == "Mist":
if OUTPUT_COMMENTS:
gcode += append("M7 (coolant: mist on)\n")
else:
gcode += append("M7\n")
if coolantMode == 'Flood':
if coolantMode == "Flood":
if OUTPUT_COMMENTS:
gcode += append("M8 (coolant: flood on)\n")
else:
@@ -480,7 +498,7 @@ def export(objectslist, filename, argstring):
# turn coolant off if required
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
if not coolantMode == 'None':
if not coolantMode == "None":
if OUTPUT_COMMENTS:
gcode += append("M9 (coolant: off)\n")
else:
@@ -509,12 +527,15 @@ def export(objectslist, filename, argstring):
final = gcode
if (0 < problems_count) or (0 < warnings_count):
print("export: postprocessing: done, warnings: {}, problems: {}, see GCode for details."
.format(warnings_count, problems_count))
print(
"export: postprocessing: done, warnings: {}, problems: {}, see GCode for details.".format(
warnings_count, problems_count
)
)
else:
print("export: postprocessing: done (none of the problems detected).")
if not filename == '-':
if not filename == "-":
print("export: writing to '{}'".format(filename))
gfile = pythonopen(filename, "w")
gfile.write(final)
@@ -526,7 +547,7 @@ def export(objectslist, filename, argstring):
def linenumber():
global LINENR
if (LINENR <= 0):
if LINENR <= 0:
LINENR = LINE_NUMBER_START
if OUTPUT_LINE_NUMBERS is True:
line = LINENR
@@ -538,11 +559,28 @@ def linenumber():
def parse(pathobj):
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
precision_string = "." + str(PRECISION) + "f"
currLocation = {} # keep track for no doubles
# The params list control the order of parameters
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'K', 'R', 'F', 'S', 'T', 'H', 'L', 'Q']
params = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"K",
"R",
"F",
"S",
"T",
"H",
"L",
"Q",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
@@ -574,49 +612,67 @@ def parse(pathobj):
if command == lastcommand:
commandlist.pop(0)
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment
continue
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F' and (currLocation[param] != c.Parameters[param] or REPEAT_ARGUMENTS):
if param == "F" and (
currLocation[param] != c.Parameters[param] or REPEAT_ARGUMENTS
):
if c.Name not in ["G0", "G00"]: # No F in G0
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
speed = Units.Quantity(
c.Parameters["F"], FreeCAD.Units.Velocity
)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
commandlist.append(
param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string))
param
+ format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)),
precision_string,
)
)
else:
continue
elif param == 'T':
commandlist.append(param + str(int(c.Parameters['T'])))
elif param == 'H':
commandlist.append(param + str(int(c.Parameters['H'])))
elif param == 'D':
commandlist.append(param + str(int(c.Parameters['D'])))
elif param == 'S':
commandlist.append(param + str(int(c.Parameters['S'])))
elif param == "T":
commandlist.append(param + str(int(c.Parameters["T"])))
elif param == "H":
commandlist.append(param + str(int(c.Parameters["H"])))
elif param == "D":
commandlist.append(param + str(int(c.Parameters["D"])))
elif param == "S":
commandlist.append(param + str(int(c.Parameters["S"])))
else:
if (not REPEAT_ARGUMENTS) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
if (
(not REPEAT_ARGUMENTS)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
commandlist.append(param + format(float(pos.getValueAs(UNIT_FORMAT)),
precision_string))
pos = Units.Quantity(
c.Parameters[param], FreeCAD.Units.Length
)
commandlist.append(
param
+ format(
float(pos.getValueAs(UNIT_FORMAT)), precision_string
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == 'M6':
if command == "M6":
for line in TOOL_CHANGE.splitlines(True):
out += linenumber() + line
# add height offset
if USE_TLO:
tool_height = '\nG43 H' + str(int(c.Parameters['T']))
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
commandlist.append(tool_height)
if command == "message":
@@ -633,7 +689,7 @@ def parse(pathobj):
# append the line to the final output
for w in commandlist:
out += w.strip() + COMMAND_SPACE
if (trace_gcode):
if trace_gcode:
print("parse : >>{}".format(out))
out = out.strip() + "\n"