Path: Added 4 refactored postprocessors and postprocessor tests.

This commit is contained in:
LarryWoestman
2022-05-16 17:01:02 -07:00
parent 15360abc95
commit b2a7654c31
33 changed files with 6114 additions and 801 deletions

View File

@@ -59,51 +59,15 @@ class _TempObject:
Label = "Fixture"
def resolveFileName(job, subpartname, sequencenumber):
PathLog.track(subpartname, sequencenumber)
validPathSubstitutions = ["D", "d", "M", "j"]
validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"]
# Look for preference default
outputpath, filename = os.path.split(PathPreferences.defaultOutputFile())
filename, ext = os.path.splitext(filename)
# Override with document default if it exists
if job.PostProcessorOutputFile:
matchstring = job.PostProcessorOutputFile
candidateOutputPath, candidateFilename = os.path.split(matchstring)
if candidateOutputPath:
outputpath = candidateOutputPath
if candidateFilename:
filename, ext = os.path.splitext(candidateFilename)
# Strip any invalid substitutions from the ouputpath
for match in re.findall("%(.)", outputpath):
if match not in validPathSubstitutions:
outputpath = outputpath.replace(f"%{match}", "")
# if nothing else, use current directory
if not outputpath:
outputpath = "."
# Strip any invalid substitutions from the filename
for match in re.findall("%(.)", filename):
if match not in validFilenameSubstitutions:
filename = filename.replace(f"%{match}", "")
# if no filename, use the active document label
if not filename:
filename = FreeCAD.ActiveDocument.Label
# if no extension, use something sensible
if not ext:
ext = ".nc"
# By now we should have a sanitized path, filename and extension to work with
PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}")
def processFileNameSubstitutions(
job,
subpartname,
sequencenumber,
outputpath,
filename,
ext,
):
"""Process any substitutions in the outputpath or filename."""
# The following section allows substitution within the path part
PathLog.track(f"path before substitution: {outputpath}")
@@ -116,9 +80,7 @@ def resolveFileName(job, subpartname, sequencenumber):
if not D:
D = "."
else:
FreeCAD.Console.PrintError(
"Please save document in order to resolve output path!\n"
)
FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
return None
outputpath = outputpath.replace("%D", D)
@@ -171,9 +133,7 @@ def resolveFileName(job, subpartname, sequencenumber):
if "%O" in filename and job.OrderOutputBy == "Operation":
filename = filename.replace("%O", subpartname)
if (
"%S" in filename
): # We always add a sequence number but the user can say where
if "%S" in filename: # We always add a sequence number but the user can say where
filename = filename.replace("%S", str(sequencenumber))
else:
filename = f"{filename}-{sequencenumber}"
@@ -187,11 +147,68 @@ def resolveFileName(job, subpartname, sequencenumber):
fullPath = f"{outputpath}{os.path.sep}{filename}{ext}"
PathLog.track(f"full filepath: {fullPath}")
return fullPath
def resolveFileName(job, subpartname, sequencenumber):
PathLog.track(subpartname, sequencenumber)
validPathSubstitutions = ["D", "d", "M", "j"]
validFilenameSubstitutions = ["j", "d", "T", "t", "W", "O", "S"]
# Look for preference default
outputpath, filename = os.path.split(PathPreferences.defaultOutputFile())
filename, ext = os.path.splitext(filename)
# Override with document default if it exists
if job.PostProcessorOutputFile:
matchstring = job.PostProcessorOutputFile
candidateOutputPath, candidateFilename = os.path.split(matchstring)
if candidateOutputPath:
outputpath = candidateOutputPath
if candidateFilename:
filename, ext = os.path.splitext(candidateFilename)
# Strip any invalid substitutions from the ouputpath
for match in re.findall("%(.)", outputpath):
if match not in validPathSubstitutions:
outputpath = outputpath.replace(f"%{match}", "")
# if nothing else, use current directory
if not outputpath:
outputpath = "."
# Strip any invalid substitutions from the filename
for match in re.findall("%(.)", filename):
if match not in validFilenameSubstitutions:
filename = filename.replace(f"%{match}", "")
# if no filename, use the active document label
if not filename:
filename = FreeCAD.ActiveDocument.Label
# if no extension, use something sensible
if not ext:
ext = ".nc"
# By now we should have a sanitized path, filename and extension to work with
PathLog.track(f"path: {outputpath} name: {filename} ext: {ext}")
fullPath = processFileNameSubstitutions(
job,
subpartname,
sequencenumber,
outputpath,
filename,
ext,
)
# This section determines whether user interaction is necessary
policy = PathPreferences.defaultOutputPolicy()
openDialog = policy == "Open File Dialog"
openDialog = (policy == "Open File Dialog")
# if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
# # Either the entire filename resolves into a directory or the parent directory doesn't exist.
# # Either way I don't know what to do - ask for help
@@ -256,8 +273,7 @@ def buildPostList(job):
c2 = Path.Command(
"G0 Z"
+ str(
job.Stock.Shape.BoundBox.ZMax
+ job.SetupSheet.ClearanceHeightOffset.Value
job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value
)
)
fobj.Path.addCommands(c2)
@@ -292,10 +308,7 @@ def buildPostList(job):
c1 = Path.Command(f)
c2 = Path.Command(
"G0 Z"
+ str(
job.Stock.Shape.BoundBox.ZMax
+ job.SetupSheet.ClearanceHeightOffset.Value
)
+ str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)
)
fobj.Path = Path.Path([c1, c2])
fobj.InList.append(job)
@@ -395,9 +408,7 @@ def buildPostList(job):
return postlist
else:
PathLog.track()
finalpostlist = [
("allitems", [item for slist in postlist for item in slist[1]])
]
finalpostlist = [("allitems", [item for slist in postlist for item in slist[1]])]
return finalpostlist
@@ -407,9 +418,7 @@ class DlgSelectPostProcessor:
firstItem = None
for post in PathPreferences.allEnabledPostProcessors():
item = QtGui.QListWidgetItem(post)
item.setFlags(
QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled
)
item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
self.dialog.lwPostProcessor.addItem(item)
if not firstItem:
firstItem = item
@@ -518,9 +527,7 @@ class CommandPathPost:
selected = FreeCADGui.Selection.getSelectionEx()
if len(selected) > 1:
FreeCAD.Console.PrintError(
"Please select a single job or other path object\n"
)
FreeCAD.Console.PrintError("Please select a single job or other path object\n")
return
elif len(selected) == 1:
sel = selected[0].Object

View File

@@ -69,23 +69,6 @@ class PostProcessor:
else:
instance.units = "Inch"
if hasattr(current_post, "MACHINE_NAME"):
instance.machineName = current_post.MACHINE_NAME
if hasattr(current_post, "CORNER_MAX"):
instance.cornerMax = {
"x": current_post.CORNER_MAX["x"],
"y": current_post.CORNER_MAX["y"],
"z": current_post.CORNER_MAX["z"],
}
if hasattr(current_post, "CORNER_MIN"):
instance.cornerMin = {
"x": current_post.CORNER_MIN["x"],
"y": current_post.CORNER_MIN["y"],
"z": current_post.CORNER_MIN["z"],
}
if hasattr(current_post, "TOOLTIP"):
instance.tooltip = current_post.TOOLTIP
if hasattr(current_post, "TOOLTIP_ARGS"):
@@ -96,8 +79,6 @@ class PostProcessor:
self.script = script
self.tooltip = None
self.tooltipArgs = None
self.cornerMax = None
self.cornerMin = None
self.units = None
self.machineName = None

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
@@ -23,23 +24,24 @@
# ***************************************************************************
"""
These are a common functions and classes for creating custom post processors.
These are common functions and classes for creating custom post processors.
"""
from PySide import QtCore, QtGui
import FreeCAD
from PathMachineState import MachineState
import Path
import Part
from PathMachineState import MachineState
from PathScripts.PathGeom import CmdMoveArc, edgeForCmd, cmdsForEdge
translate = FreeCAD.Qt.translate
FreeCADGui = None
if FreeCAD.GuiUp:
import FreeCADGui
class GCodeHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent=None):
super(GCodeHighlighter, self).__init__(parent)
@@ -147,7 +149,7 @@ def stringsplit(commandline):
def fmt(num, dec, units):
"""used to format axis moves, feedrate, etc for decimal places and units"""
"""Use to format axis moves, feedrate, etc for decimal places and units."""
if units == "G21": # metric
fnum = "%.*f" % (dec, num)
else: # inch
@@ -156,8 +158,7 @@ def fmt(num, dec, units):
def editor(gcode):
"""pops up a handy little editor to look at the code output"""
"""Pops up a handy little editor to look at the code output."""
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
# default Max Highlighter Size = 512 Ko
defaultMHS = 512 * 1024
@@ -176,9 +177,7 @@ def editor(gcode):
FreeCAD.Console.PrintMessage(
translate(
"Path",
"GCode size too big ({} o), disabling syntax highlighter.".format(
gcodeSize
),
"GCode size too big ({} o), disabling syntax highlighter.".format(gcodeSize),
)
)
result = dia.exec_()
@@ -190,7 +189,7 @@ def editor(gcode):
def fcoms(string, commentsym):
"""filter and rebuild comments with user preferred comment symbol"""
"""Filter and rebuild comments with user preferred comment symbol."""
if len(commentsym) == 1:
s1 = string.replace("(", commentsym)
comment = s1.replace(")", "")
@@ -200,8 +199,10 @@ def fcoms(string, commentsym):
def splitArcs(path):
"""filters a path object and replaces at G2/G3 moves with discrete G1
returns a Path object"""
"""Filter a path object and replace all G2/G3 moves with discrete G1 moves.
Returns a Path object.
"""
prefGrp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
deflection = prefGrp.GetFloat("LibAreaCurveAccuarcy", 0.01)

View File

@@ -0,0 +1,642 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
"""
These are functions related to arguments and values for creating custom post processors.
"""
import argparse
import os
import shlex
def add_flag_type_arguments(
argument_group, default_flag, true_argument, false_argument, true_help, false_help, visible=True
):
if visible:
if default_flag:
true_help += " (default)"
else:
false_help += " (default)"
else:
true_help = false_help = argparse.SUPPRESS
argument_group.add_argument(true_argument, action="store_true", help=true_help)
argument_group.add_argument(false_argument, action="store_true", help=false_help)
def init_argument_defaults(argument_defaults):
"""Initialize which argument to show as the default in flag-type arguments"""
argument_defaults["axis-modal"] = False
argument_defaults["bcnc"] = False
argument_defaults["comments"] = True
argument_defaults["header"] = True
argument_defaults["line-numbers"] = False
argument_defaults["metric_inches"] = True
argument_defaults["modal"] = False
argument_defaults["show-editor"] = True
argument_defaults["tlo"] = True
argument_defaults["tool_change"] = True
argument_defaults["translate_drill"] = False
def init_arguments_visible(arguments_visible):
"""Initialize the flags for which arguments are visible in the arguments tooltip."""
arguments_visible["bcnc"] = False
arguments_visible["axis-modal"] = True
arguments_visible["axis-precision"] = True
arguments_visible["comments"] = True
arguments_visible["feed-precision"] = True
arguments_visible["header"] = True
arguments_visible["line-numbers"] = True
arguments_visible["metric_inches"] = True
arguments_visible["modal"] = True
arguments_visible["postamble"] = True
arguments_visible["preamble"] = True
arguments_visible["precision"] = True
arguments_visible["return-to"] = False
arguments_visible["show-editor"] = True
arguments_visible["tlo"] = True
arguments_visible["tool_change"] = False
arguments_visible["translate_drill"] = False
arguments_visible["wait-for-spindle"] = False
def init_shared_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared arguments for postprocessors."""
parser = argparse.ArgumentParser(
prog=values["MACHINE_NAME"], usage=argparse.SUPPRESS, add_help=False
)
shared = parser.add_argument_group("Arguments that are shared with all postprocessors")
add_flag_type_arguments(
shared,
argument_defaults["metric_inches"],
"--metric",
"--inches",
"Convert output for Metric mode (G21)",
"Convert output for US imperial mode (G20)",
arguments_visible["metric_inches"],
)
add_flag_type_arguments(
shared,
argument_defaults["axis-modal"],
"--axis-modal",
"--no-axis-modal",
"Don't output axis values if they are the same as the previous line",
"Output axis values even if they are the same as the previous line",
arguments_visible["axis-modal"],
)
if arguments_visible["axis-precision"]:
help_message = (
"Number of digits of precision for axis moves, default is "
+ str(values["DEFAULT_AXIS_PRECISION"])
)
else:
help_message = argparse.SUPPRESS
shared.add_argument(
"--axis-precision",
default=-1,
type=int,
help=help_message,
)
add_flag_type_arguments(
shared,
argument_defaults["bcnc"],
"--bcnc",
"--no-bcnc",
"Add Job operations as bCNC block headers. Consider suppressing comments by adding --no-comments",
"Suppress bCNC block header output",
arguments_visible["bcnc"],
)
add_flag_type_arguments(
shared,
argument_defaults["comments"],
"--comments",
"--no-comments",
"Output comments",
"Suppress comment output",
arguments_visible["comments"],
)
if arguments_visible["feed-precision"]:
help_message = (
"Number of digits of precision for feed rate, default is "
+ str(values["DEFAULT_FEED_PRECISION"])
)
else:
help_message = argparse.SUPPRESS
shared.add_argument(
"--feed-precision",
default=-1,
type=int,
help=help_message,
)
add_flag_type_arguments(
shared,
argument_defaults["header"],
"--header",
"--no-header",
"Output headers",
"Suppress header output",
arguments_visible["header"],
)
add_flag_type_arguments(
shared,
argument_defaults["line-numbers"],
"--line-numbers",
"--no-line-numbers",
"Prefix with line numbers",
"Don't prefix with line numbers",
arguments_visible["line-numbers"],
)
add_flag_type_arguments(
shared,
argument_defaults["modal"],
"--modal",
"--no-modal",
"Don't output the G-command name if it is the same as the previous line",
"Output the G-command name even if it is the same as the previous line",
arguments_visible["modal"],
)
if arguments_visible["postamble"]:
help_message = (
'Set commands to be issued after the last command, default is "'
+ values["POSTAMBLE"]
+ '"'
)
else:
help_message = argparse.SUPPRESS
shared.add_argument("--postamble", help=help_message)
if arguments_visible["preamble"]:
help_message = (
'Set commands to be issued before the first command, default is "'
+ values["PREAMBLE"]
+ '"'
)
else:
help_message = argparse.SUPPRESS
shared.add_argument("--preamble", help=help_message)
# The --precision argument is included for backwards compatibility with some postprocessors.
# If both --axis-precision and --precision are provided, the --axis-precision value "wins".
# If both --feed-precision and --precision are provided, the --feed-precision value "wins".
if arguments_visible["precision"]:
help_message = (
"Number of digits of precision for both feed rate and axis moves, default is "
+ str(values["DEFAULT_AXIS_PRECISION"])
+ " for metric or "
+ str(values["DEFAULT_INCH_AXIS_PRECISION"])
+ " for inches"
)
else:
help_message = argparse.SUPPRESS
shared.add_argument(
"--precision",
default=-1,
type=int,
help=help_message,
)
if arguments_visible["return-to"]:
help_message = "Move to the specified x,y,z coordinates at the end, e.g. --return-to=0,0,0 (default is do not move)"
else:
help_message = argparse.SUPPRESS
shared.add_argument("--return-to", default="", help=help_message)
add_flag_type_arguments(
shared,
argument_defaults["show-editor"],
"--show-editor",
"--no-show-editor",
"Pop up editor before writing output",
"Don't pop up editor before writing output",
arguments_visible["show-editor"],
)
add_flag_type_arguments(
shared,
argument_defaults["tlo"],
"--tlo",
"--no-tlo",
"Output tool length offset (G43) following tool changes",
"Suppress tool length offset (G43) following tool changes",
arguments_visible["tlo"],
)
add_flag_type_arguments(
shared,
argument_defaults["tool_change"],
"--tool_change",
"--no-tool_change",
"Insert M6 and any other tool change G-code for all tool changes",
"Convert M6 to a comment for all tool changes",
arguments_visible["tool_change"],
)
add_flag_type_arguments(
shared,
argument_defaults["translate_drill"],
"--translate_drill",
"--no-translate_drill",
"Translate drill cycles G81, G82 & G83 into G0/G1 movements",
"Don't translate drill cycles G81, G82 & G83 into G0/G1 movements",
arguments_visible["translate_drill"],
)
if arguments_visible["wait-for-spindle"]:
help_message = "Time to wait (in seconds) after M3, M4 (default = 0.0)"
else:
help_message = argparse.SUPPRESS
shared.add_argument("--wait-for-spindle", type=float, default=0.0, help=help_message)
return parser
def init_shared_values(values):
"""Initialize the default values in postprocessors."""
#
# The starting axis precision is 3 digits after the decimal point.
#
values["AXIS_PRECISION"] = 3
#
# If this is set to "", all spaces are removed from between commands and parameters.
#
values["COMMAND_SPACE"] = " "
#
# The character that indicates a comment. While "(" is the most common,
# ";" is also used.
#
values["COMMENT_SYMBOL"] = "("
#
# Variables storing the current position for the drill_translate routine.
#
values["CURRENT_X"] = 0
values["CURRENT_Y"] = 0
values["CURRENT_Z"] = 0
#
# Default axis precision for metric is 3 digits after the decimal point.
# (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices)
#
values["DEFAULT_AXIS_PRECISION"] = 3
#
# The default precision for feed is also set to 3 for metric.
#
values["DEFAULT_FEED_PRECISION"] = 3
#
# Default axis precision for inch/imperial is 4 digits after the decimal point.
#
values["DEFAULT_INCH_AXIS_PRECISION"] = 4
#
# The default precision for feed is also set to 4 for inch/imperial.
#
values["DEFAULT_INCH_FEED_PRECISION"] = 4
#
# If TRANSLATE_DRILL_CYCLES is True, these are the drill cycles
# that get translated to G0 and G1 commands.
#
values["DRILL_CYCLES_TO_TRANSLATE"] = ("G81", "G82", "G83")
#
# The default value of drill retractations (CURRENT_Z).
# The other possible value is G99.
#
values["DRILL_RETRACT_MODE"] = "G98"
#
# If this is set to True, then M7, M8, and M9 commands
# to enable/disable coolant will be output.
#
values["ENABLE_COOLANT"] = False
#
# If this is set to True, then commands that are placed in
# comments that look like (MC_RUN_COMMAND: blah) will be output.
#
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = False
#
# By default the line ending characters of the output file(s)
# are written to match the system that the postprocessor runs on.
# If you need to force the line ending characters to a specific
# value, set this variable to "\n" or "\r\n" instead.
#
values["END_OF_LINE_CHARACTERS"] = os.linesep
#
# The starting precision for feed is also set to 3 digits after the decimal point.
#
values["FEED_PRECISION"] = 3
#
# This value shows up in the post_op comment as "Finish operation:".
# At least one postprocessor changes it to "End" to produce "End operation:".
#
values["FINISH_LABEL"] = "Finish"
#
# The name of the machine the postprocessor is for
#
values["MACHINE_NAME"] = "unknown machine"
#
# The line number increment value
#
values["LINE_INCREMENT"] = 10
#
# The line number starting value
#
values["line_number"] = 100
#
# If this value is True, then a list of tool numbers
# with their labels are output just before the preamble.
#
values["LIST_TOOLS_IN_PREAMBLE"] = False
#
# If this value is true G-code commands are suppressed if they are
# the same as the previous line.
#
values["MODAL"] = False
#
# This defines the motion commands that might change the X, Y, and Z position.
#
values["MOTION_COMMANDS"] = [
"G0",
"G00",
"G1",
"G01",
"G2",
"G02",
"G3",
"G03",
]
#
# Keeps track of the motion mode currently in use.
# G90 for absolute moves, G91 for relative
#
values["MOTION_MODE"] = "G90"
#
# If True enables special processing for operations with "Adaptive" in the name
#
values["OUTPUT_ADAPTIVE"] = False
#
# If True adds bCNC operation block headers to the output G-code file.
#
values["OUTPUT_BCNC"] = False
#
# If True output comments. If False comments are suppressed.
#
values["OUTPUT_COMMENTS"] = True
#
# if False duplicate axis values are suppressed if they are the same as the previous line.
#
values["OUTPUT_DOUBLES"] = True
#
# If True output the machine name in the pre_op
#
values["OUTPUT_MACHINE_NAME"] = False
#
# If True output a header at the front of the G-code file.
# The header contains comments similar to:
# (Exported by FreeCAD)
# (Post Processor: centroid_post)
# (Cam File: box.fcstd)
# (Output Time:2020-01-01 01:02:03.123456)
#
values["OUTPUT_HEADER"] = True
#
# If True output line numbers at the front of each line.
# If False do not output line numbers.
#
values["OUTPUT_LINE_NUMBERS"] = False
#
# If True output Path labels at the beginning of each Path.
#
values["OUTPUT_PATH_LABELS"] = False
#
# If True output tool change G-code for M6 commands followed
# by any commands in the "TOOL_CHANGE" value.
# If False output the M6 command as a comment and do not output
# any commands in the "TOOL_CHANGE" value.
#
values["OUTPUT_TOOL_CHANGE"] = True
#
# This list controls the order of parameters in a line during output.
#
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"U",
"V",
"W",
"I",
"J",
"K",
"F",
"S",
"T",
"Q",
"R",
"L",
"P",
]
#
# Any commands in this value will be output as the last commands
# in the G-code file.
#
values["POSTAMBLE"] = """"""
#
# Any commands in this value will be output after the operation(s).
#
values["POST_OPERATION"] = """"""
#
# Any commands in this value will be output after the header and
# safety block at the beginning of the G-code file.
#
values["PREAMBLE"] = """"""
#
# Any commands in this value will be output before the operation(s).
#
values["PRE_OPERATION"] = """"""
#
# Defines which G-code commands are considered "rapid" moves.
#
values["RAPID_MOVES"] = ["G0", "G00"]
#
# If True suppress any messages.
#
values["REMOVE_MESSAGES"] = True
#
# Any commands in this value are output after the operation(s)
# and post_operation commands are output but before the
# TOOLRETURN, SAFETYBLOCK, and POSTAMBLE.
#
values["RETURN_TO"] = None
#
# Any commands in this value are output after the header but before the preamble,
# then again after the TOOLRETURN but before the POSTAMBLE.
#
values["SAFETYBLOCK"] = """"""
#
# If True then the G-code editor widget is shown before writing
# the G-code to the file.
#
values["SHOW_EDITOR"] = True
#
# If True then the current machine units are output just before the PRE_OPERATION.
#
values["SHOW_MACHINE_UNITS"] = True
#
# If True then the current operation label is output just before the PRE_OPERATION.
#
values["SHOW_OPERATION_LABELS"] = True
#
# The number of decimal places to use when outputting the speed (S) parameter.
#
values["SPINDLE_DECIMALS"] = 0
#
# The amount of time (in seconds) to wait after turning on the spindle
# using an M3 or M4 command (a floating point number).
#
values["SPINDLE_WAIT"] = 0.0
#
# If true then then an M5 command to stop the spindle is output
# after the M6 tool change command and before the TOOL_CHANGE commands.
#
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = True
#
# These commands are ignored by commenting them out.
# Used when replacing the drill commands by G0 and G1 commands, for example.
#
values["SUPPRESS_COMMANDS"] = []
#
# Any commands in this value are output after the M6 command
# when changing at tool (if OUTPUT_TOOL_CHANGE is True).
#
values["TOOL_CHANGE"] = """"""
#
# Any commands in this value are output after the POST_OPERATION,
# RETURN_TO, and OUTPUT_BCNC and before the SAFETYBLOCK and POSTAMBLE.
#
values["TOOLRETURN"] = """"""
#
# If true, G81, G82 & G83 drill moves are translated into G0/G1 moves.
#
values["TRANSLATE_DRILL_CYCLES"] = False
#
# These values keep track of whether we are in Metric mode (G21)
# or inches/imperial mode (G20).
#
values["UNITS"] = "G21"
values["UNIT_FORMAT"] = "mm"
values["UNIT_SPEED_FORMAT"] = "mm/min"
#
# If true a tool length command (G43) will be output following tool changes.
#
values["USE_TLO"] = True
def process_shared_arguments(values, parser, argstring):
"""Process the arguments to the postprocessor."""
try:
args = parser.parse_args(shlex.split(argstring))
if args.metric:
values["UNITS"] = "G21"
if args.inches:
values["UNITS"] = "G20"
if values["UNITS"] == "G21":
values["UNIT_FORMAT"] = "mm"
values["UNIT_SPEED_FORMAT"] = "mm/min"
if values["UNITS"] == "G20":
values["UNIT_FORMAT"] = "in"
values["UNIT_SPEED_FORMAT"] = "in/min"
# The precision-related arguments need to be processed
# after the metric/inches arguments are processed.
# If both --axis-precision and --precision are given,
# the --axis-precision argument "wins".
if args.axis_precision != -1:
values["AXIS_PRECISION"] = args.axis_precision
elif args.precision != -1:
values["AXIS_PRECISION"] = args.precision
else:
if values["UNITS"] == "G21":
values["AXIS_PRECISION"] = values["DEFAULT_AXIS_PRECISION"]
if values["UNITS"] == "G20":
values["AXIS_PRECISION"] = values["DEFAULT_INCH_AXIS_PRECISION"]
# If both --feed-precision and --precision are given,
# the --feed-precision argument "wins".
if args.feed_precision != -1:
values["FEED_PRECISION"] = args.feed_precision
elif args.precision != -1:
values["FEED_PRECISION"] = args.precision
else:
if values["UNITS"] == "G21":
values["FEED_PRECISION"] = values["DEFAULT_FEED_PRECISION"]
if values["UNITS"] == "G20":
values["FEED_PRECISION"] = values["DEFAULT_INCH_FEED_PRECISION"]
if args.axis_modal:
values["OUTPUT_DOUBLES"] = False
if args.no_axis_modal:
values["OUTPUT_DOUBLES"] = True
if args.bcnc:
values["OUTPUT_BCNC"] = True
if args.no_bcnc:
values["OUTPUT_BCNC"] = False
if args.comments:
values["OUTPUT_COMMENTS"] = True
if args.no_comments:
values["OUTPUT_COMMENTS"] = False
if args.header:
values["OUTPUT_HEADER"] = True
if args.no_header:
values["OUTPUT_HEADER"] = False
if args.line_numbers:
values["OUTPUT_LINE_NUMBERS"] = True
if args.no_line_numbers:
values["OUTPUT_LINE_NUMBERS"] = False
if args.modal:
values["MODAL"] = True
if args.no_modal:
values["MODAL"] = False
if args.postamble is not None:
values["POSTAMBLE"] = args.postamble
if args.preamble is not None:
values["PREAMBLE"] = args.preamble
if args.return_to != "":
values["RETURN_TO"] = [int(v) for v in args.return_to.split(",")]
if len(values["RETURN_TO"]) != 3:
values["RETURN_TO"] = None
print("--return-to coordinates must be specified as <x>,<y>,<z>, ignoring")
if args.show_editor:
values["SHOW_EDITOR"] = True
if args.no_show_editor:
values["SHOW_EDITOR"] = False
if args.tlo:
values["USE_TLO"] = True
if args.no_tlo:
values["USE_TLO"] = False
if args.tool_change:
values["OUTPUT_TOOL_CHANGE"] = True
if args.no_tool_change:
values["OUTPUT_TOOL_CHANGE"] = False
if args.translate_drill:
values["TRANSLATE_DRILL_CYCLES"] = True
if args.no_translate_drill:
values["TRANSLATE_DRILL_CYCLES"] = False
if args.wait_for_spindle > 0.0:
values["SPINDLE_WAIT"] = args.wait_for_spindle
except Exception:
return (False, None)
return (True, args)

View File

@@ -0,0 +1,282 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
import datetime
import os
import FreeCAD
from PathScripts import PathToolController
from PathScripts import PostUtils
from PathScripts import PostUtilsParse
# to distinguish python built-in open function from the one declared below
if open.__module__ in ["__builtin__", "io"]:
pythonopen = open
#
# This routine processes things in the following order:
#
# OUTPUT_HEADER
# SAFETYBLOCK
# LIST_TOOLS_IN_PREAMBLE
# PREAMBLE
# OUTPUT_BCNC
# SHOW_OPERATION_LABELS
# SHOW_MACHINE_UNITS
# PRE_OPERATION
# ENABLE_COOLANT (coolant on)
# operation(s)
# POST_OPERATION
# ENABLE_COOLANT (coolant off)
# RETURN_TO
# OUTPUT_BCNC
# TOOLRETURN
# SAFETYBLOCK
# POSTAMBLE
# SHOW_EDITOR
#
# The names in all caps may be enabled/disabled/modified by setting
# the corresponding value in the postprocessor.
#
def export_common(values, objectslist, filename):
"""Do the common parts of postprocessing the objects in objectslist to filename."""
#
for obj in objectslist:
if not hasattr(obj, "Path"):
print(
"The object " + obj.Name + " is not a path. Please select only path and Compounds."
)
return None
# for obj in objectslist:
# print(obj.Name)
print("PostProcessor: " + values["POSTPROCESSOR_FILE_NAME"] + " postprocessing...")
gcode = ""
# write header
if values["OUTPUT_HEADER"]:
comment = PostUtilsParse.create_comment("Exported by FreeCAD", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment(
"Post Processor: " + values["POSTPROCESSOR_FILE_NAME"],
values["COMMENT_SYMBOL"],
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if FreeCAD.ActiveDocument:
cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName)
else:
cam_file = "<None>"
comment = PostUtilsParse.create_comment("Cam File: " + cam_file, values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment(
"Output Time: " + str(datetime.datetime.now()), values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
# Check canned cycles for drilling
if values["TRANSLATE_DRILL_CYCLES"]:
if len(values["SUPPRESS_COMMANDS"]) == 0:
values["SUPPRESS_COMMANDS"] = ["G99", "G98", "G80"]
else:
values["SUPPRESS_COMMANDS"] += ["G99", "G98", "G80"]
for line in values["SAFETYBLOCK"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
# Write the preamble
if values["OUTPUT_COMMENTS"]:
if values["LIST_TOOLS_IN_PREAMBLE"]:
for item in objectslist:
if hasattr(item, "Proxy") and isinstance(
item.Proxy, PathToolController.ToolController
):
comment = PostUtilsParse.create_comment(
"T{}={}".format(item.ToolNumber, item.Name), values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment("Begin preamble", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
for line in values["PREAMBLE"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
# verify if PREAMBLE or SAFETYBLOCK have changed MOTION_MODE or UNITS
if "G90" in values["PREAMBLE"] or "G90" in values["SAFETYBLOCK"]:
values["MOTION_MODE"] = "G90"
elif "G91" in values["PREAMBLE"] or "G91" in values["SAFETYBLOCK"]:
values["MOTION_MODE"] = "G91"
else:
gcode += PostUtilsParse.linenumber(values) + values["MOTION_MODE"] + "\n"
if "G21" in values["PREAMBLE"] or "G21" in values["SAFETYBLOCK"]:
values["UNITS"] = "G21"
values["UNIT_FORMAT"] = "mm"
values["UNIT_SPEED_FORMAT"] = "mm/min"
elif "G20" in values["PREAMBLE"] or "G20" in values["SAFETYBLOCK"]:
values["UNITS"] = "G20"
values["UNIT_FORMAT"] = "in"
values["UNIT_SPEED_FORMAT"] = "in/min"
else:
gcode += PostUtilsParse.linenumber(values) + values["UNITS"] + "\n"
for obj in objectslist:
# Debug...
# print("\n" + "*"*70)
# dump(obj)
# print("*"*70 + "\n")
# Skip inactive operations
if hasattr(obj, "Active"):
if not obj.Active:
continue
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
if not obj.Base.Active:
continue
# do the pre_op
if values["OUTPUT_BCNC"]:
comment = PostUtilsParse.create_comment(
"Block-name: " + obj.Label, values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment("Block-expand: 0", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment("Block-enable: 1", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if values["OUTPUT_COMMENTS"]:
if values["SHOW_OPERATION_LABELS"]:
comment = PostUtilsParse.create_comment(
"Begin operation: %s" % obj.Label, values["COMMENT_SYMBOL"]
)
else:
comment = PostUtilsParse.create_comment("Begin operation", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if values["SHOW_MACHINE_UNITS"]:
comment = PostUtilsParse.create_comment(
"Machine units: %s" % values["UNIT_SPEED_FORMAT"], values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if values["OUTPUT_MACHINE_NAME"]:
comment = PostUtilsParse.create_comment(
"Machine: %s, %s" % (values["MACHINE_NAME"], values["UNIT_SPEED_FORMAT"]),
values["COMMENT_SYMBOL"],
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
for line in values["PRE_OPERATION"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
# get coolant mode
coolantMode = "None"
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 values["ENABLE_COOLANT"]:
if values["OUTPUT_COMMENTS"]:
if not coolantMode == "None":
comment = PostUtilsParse.create_comment(
"Coolant On:" + coolantMode, values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if coolantMode == "Flood":
gcode += PostUtilsParse.linenumber(values) + "M8" + "\n"
if coolantMode == "Mist":
gcode += PostUtilsParse.linenumber(values) + "M7" + "\n"
# process the operation gcode
gcode += PostUtilsParse.parse(values, obj)
# do the post_op
if values["OUTPUT_COMMENTS"]:
comment = PostUtilsParse.create_comment(
"%s operation: %s" % (values["FINISH_LABEL"], obj.Label),
values["COMMENT_SYMBOL"],
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
for line in values["POST_OPERATION"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
# turn coolant off if required
if values["ENABLE_COOLANT"]:
if not coolantMode == "None":
if values["OUTPUT_COMMENTS"]:
comment = PostUtilsParse.create_comment(
"Coolant Off:" + coolantMode, values["COMMENT_SYMBOL"]
)
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
gcode += PostUtilsParse.linenumber(values) + "M9" + "\n"
if values["RETURN_TO"]:
gcode += PostUtilsParse.linenumber(values) + "G0 X%s Y%s Z%s\n" % tuple(values["RETURN_TO"])
# do the post_amble
if values["OUTPUT_BCNC"]:
comment = PostUtilsParse.create_comment("Block-name: post_amble", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment("Block-expand: 0", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
comment = PostUtilsParse.create_comment("Block-enable: 1", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
if values["OUTPUT_COMMENTS"]:
comment = PostUtilsParse.create_comment("Begin postamble", values["COMMENT_SYMBOL"])
gcode += PostUtilsParse.linenumber(values) + comment + "\n"
for line in values["TOOLRETURN"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
for line in values["SAFETYBLOCK"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
for line in values["POSTAMBLE"].splitlines(False):
gcode += PostUtilsParse.linenumber(values) + line + "\n"
if FreeCAD.GuiUp and values["SHOW_EDITOR"]:
final = gcode
if len(gcode) > 100000:
print("Skipping editor since output is greater than 100kb")
else:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = gcode
print("done postprocessing.")
if not filename == "-":
gfile = pythonopen(filename, "w", newline=values["END_OF_LINE_CHARACTERS"])
gfile.write(final)
gfile.close()
return final

View File

@@ -0,0 +1,511 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
import re
import FreeCAD
from FreeCAD import Units
import Path
from PathScripts import PostUtils
def create_comment(comment_string, comment_symbol):
"""Create a comment from a string using the correct comment symbol."""
if comment_symbol == "(":
comment_string = "(" + comment_string + ")"
else:
comment_string = comment_symbol + comment_string
return comment_string
def drill_translate(values, outstring, cmd, params):
"""Translate drill cycles."""
axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f"
feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f"
trBuff = ""
if values["OUTPUT_COMMENTS"]: # Comment the original command
trBuff += (
linenumber(values)
+ create_comment(
values["COMMAND_SPACE"]
+ format_outstring(values, outstring)
+ values["COMMAND_SPACE"],
values["COMMENT_SYMBOL"],
)
+ "\n"
)
# cycle conversion
# currently only cycles in XY are provided (G17)
# other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only.
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)
RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length)
# R less than Z is error
if RETRACT_Z < drill_Z:
trBuff += (
linenumber(values)
+ create_comment("Drill cycle error: R less than Z", values["COMMENT_SYMBOL"])
+ "\n"
)
return trBuff
if values["MOTION_MODE"] == "G91": # G91 relative movements
drill_X += values["CURRENT_X"]
drill_Y += values["CURRENT_Y"]
drill_Z += values["CURRENT_Z"]
RETRACT_Z += values["CURRENT_Z"]
if values["DRILL_RETRACT_MODE"] == "G98" and values["CURRENT_Z"] >= RETRACT_Z:
RETRACT_Z = values["CURRENT_Z"]
# get the other parameters
drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity)
if cmd == "G83":
drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length)
a_bit = (
drill_Step * 0.05
) # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit."
elif cmd == "G82":
drill_DwellTime = params["P"]
# wrap this block to ensure machine's values["MOTION_MODE"] is restored in case of error
try:
if values["MOTION_MODE"] == "G91":
trBuff += linenumber(values) + "G90\n" # force absolute coordinates during cycles
strG0_RETRACT_Z = (
"G0 Z"
+ format(float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
+ "\n"
)
strF_Feedrate = (
" F"
+ format(
float(drill_feedrate.getValueAs(values["UNIT_SPEED_FORMAT"])), feed_precision_string
)
+ "\n"
)
# print(strF_Feedrate)
# preliminary movement(s)
if values["CURRENT_Z"] < RETRACT_Z:
trBuff += linenumber(values) + strG0_RETRACT_Z
trBuff += (
linenumber(values)
+ "G0 X"
+ format(float(drill_X.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
+ " Y"
+ format(float(drill_Y.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
+ "\n"
)
if values["CURRENT_Z"] > RETRACT_Z:
# NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z
# Here use G1 since retract height may be below surface !
trBuff += (
linenumber(values)
+ "G1 Z"
+ format(float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
+ strF_Feedrate
)
last_Stop_Z = RETRACT_Z
# drill moves
if cmd in ("G81", "G82"):
trBuff += (
linenumber(values)
+ "G1 Z"
+ format(float(drill_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string)
+ strF_Feedrate
)
# pause where applicable
if cmd == "G82":
trBuff += linenumber(values) + "G4 P" + str(drill_DwellTime) + "\n"
trBuff += linenumber(values) + strG0_RETRACT_Z
else: # 'G83'
if params["Q"] != 0:
while 1:
if last_Stop_Z != RETRACT_Z:
clearance_depth = (
last_Stop_Z + a_bit
) # rapid move to just short of last drilling depth
trBuff += (
linenumber(values)
+ "G0 Z"
+ format(
float(clearance_depth.getValueAs(values["UNIT_FORMAT"])),
axis_precision_string,
)
+ "\n"
)
next_Stop_Z = last_Stop_Z - drill_Step
if next_Stop_Z > drill_Z:
trBuff += (
linenumber(values)
+ "G1 Z"
+ format(
float(next_Stop_Z.getValueAs(values["UNIT_FORMAT"])),
axis_precision_string,
)
+ strF_Feedrate
)
trBuff += linenumber(values) + strG0_RETRACT_Z
last_Stop_Z = next_Stop_Z
else:
trBuff += (
linenumber(values)
+ "G1 Z"
+ format(
float(drill_Z.getValueAs(values["UNIT_FORMAT"])),
axis_precision_string,
)
+ strF_Feedrate
)
trBuff += linenumber(values) + strG0_RETRACT_Z
break
except Exception:
pass
if values["MOTION_MODE"] == "G91":
trBuff += linenumber(values) + "G91\n" # Restore if changed
return trBuff
def dump(obj):
"""For debug..."""
for attr in dir(obj):
print("obj.%s = %s" % (attr, getattr(obj, attr)))
def format_outstring(values, strTable):
"""Construct the line for the final output."""
s = ""
for w in strTable:
s += w + values["COMMAND_SPACE"]
s = s.strip()
return s
def linenumber(values, space=None):
"""Output the next line number if appropriate."""
if values["OUTPUT_LINE_NUMBERS"]:
if space is None:
space = values["COMMAND_SPACE"]
line_num = str(values["line_number"])
values["line_number"] += values["LINE_INCREMENT"]
return "N" + line_num + space
return ""
def parse(values, pathobj):
"""Parse a Path."""
out = ""
lastcommand = None
axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f"
feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f"
currLocation = {} # keep track for no doubles
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
if hasattr(pathobj, "Group"): # We have a compound or project.
if values["OUTPUT_COMMENTS"]:
comment = create_comment("Compound: " + pathobj.Label, values["COMMENT_SYMBOL"])
out += linenumber(values) + comment + "\n"
for p in pathobj.Group:
out += parse(values, p)
return out
else: # parsing simple path
# groups might contain non-path things like stock.
if not hasattr(pathobj, "Path"):
return out
if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]:
comment = create_comment("Path: " + pathobj.Label, values["COMMENT_SYMBOL"])
out += linenumber(values) + comment + "\n"
if values["OUTPUT_ADAPTIVE"]:
adaptiveOp = False
opHorizRapid = 0
opVertRapid = 0
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
)
else:
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
)
else:
FreeCAD.Console.PrintWarning(
"Tool Controller Vertical Rapid Values are unset" + "\n"
)
for c in pathobj.Path.Commands:
# List of elements in the command, code, and params.
outstring = []
# command M or G code or comment string
command = c.Name
if command[0] == "(":
if values["OUTPUT_COMMENTS"]:
if values["COMMENT_SYMBOL"] != "(":
command = PostUtils.fcoms(command, values["COMMENT_SYMBOL"])
else:
continue
if values["OUTPUT_ADAPTIVE"]:
if adaptiveOp and command in values["RAPID_MOVES"]:
if opHorizRapid and opVertRapid:
command = "G1"
else:
outstring.append("(Tool Controller Rapid Values are unset)" + "\n")
outstring.append(command)
# if modal: suppress the command if it is the same as the last one
if values["MODAL"]:
if command == lastcommand:
outstring.pop(0)
# Now add the remaining parameters in order
for param in values["PARAMETER_ORDER"]:
if param in c.Parameters:
if param == "F" and (
currLocation[param] != c.Parameters[param] or values["OUTPUT_DOUBLES"]
):
# centroid and linuxcnc don't use rapid speeds
if command not in values["RAPID_MOVES"]:
speed = Units.Quantity(c.Parameters["F"], FreeCAD.Units.Velocity)
if speed.getValueAs(values["UNIT_SPEED_FORMAT"]) > 0.0:
outstring.append(
param
+ format(
float(speed.getValueAs(values["UNIT_SPEED_FORMAT"])),
feed_precision_string,
)
)
else:
continue
elif param in ["H", "L", "T"]:
outstring.append(param + str(int(c.Parameters[param])))
elif param == "D":
if command in ["G41", "G42"]:
outstring.append(param + str(int(c.Parameters[param])))
elif command in ["G96", "G97"]:
outstring.append(
param
+ PostUtils.fmt(
c.Parameters[param], values["SPINDLE_DECIMALS"], "G21"
)
)
else: # anything else that is supported (G41.1?, G42.1?)
outstring.append(param + str(float(c.Parameters[param])))
elif param == "P":
if command in ["G2", "G02", "G3", "G03", "G5.2", "G5.3", "G10"]:
outstring.append(param + str(int(c.Parameters[param])))
elif command in ["G4", "G04", "G64", "G76", "G82", "G86", "G89"]:
outstring.append(param + str(float(c.Parameters[param])))
elif command in ["G5", "G05"]:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(
param
+ format(
float(pos.getValueAs(values["UNIT_FORMAT"])),
axis_precision_string,
)
)
else: # anything else that is supported
outstring.append(param + str(c.Parameters[param]))
elif param == "S":
outstring.append(
param
+ PostUtils.fmt(c.Parameters[param], values["SPINDLE_DECIMALS"], "G21")
)
else:
if (
(not values["OUTPUT_DOUBLES"])
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(
param
+ format(
float(pos.getValueAs(values["UNIT_FORMAT"])),
axis_precision_string,
)
)
if values["OUTPUT_ADAPTIVE"]:
if adaptiveOp and command in values["RAPID_MOVES"]:
if opHorizRapid and opVertRapid:
if "Z" not in c.Parameters:
outstring.append(
"F"
+ format(
float(opHorizRapid.getValueAs(values["UNIT_SPEED_FORMAT"])),
axis_precision_string,
)
)
else:
outstring.append(
"F"
+ format(
float(opVertRapid.getValueAs(values["UNIT_SPEED_FORMAT"])),
axis_precision_string,
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Memorizes the current position for calculating the related movements
# and the withdrawal plan
if command in values["MOTION_COMMANDS"]:
if "X" in c.Parameters:
values["CURRENT_X"] = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length)
if "Y" in c.Parameters:
values["CURRENT_Y"] = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length)
if "Z" in c.Parameters:
values["CURRENT_Z"] = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length)
if command in ("G98", "G99"):
values["DRILL_RETRACT_MODE"] = command
if command in ("G90", "G91"):
values["MOTION_MODE"] = command
if values["TRANSLATE_DRILL_CYCLES"]:
if command in values["DRILL_CYCLES_TO_TRANSLATE"]:
out += drill_translate(values, outstring, command, c.Parameters)
# Erase the line we just translated
outstring = []
if values["SPINDLE_WAIT"] > 0:
if command in ("M3", "M03", "M4", "M04"):
out += linenumber(values) + format_outstring(values, outstring) + "\n"
out += (
linenumber(values)
+ format_outstring(values, ["G4", "P%s" % values["SPINDLE_WAIT"]])
+ "\n"
)
outstring = []
# Check for Tool Change:
if command in ("M6", "M06"):
if values["OUTPUT_COMMENTS"]:
comment = create_comment("Begin toolchange", values["COMMENT_SYMBOL"])
out += linenumber(values) + comment + "\n"
if values["OUTPUT_TOOL_CHANGE"]:
if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]:
# stop the spindle
out += linenumber(values) + "M5\n"
for line in values["TOOL_CHANGE"].splitlines(False):
out += linenumber(values) + line + "\n"
else:
if values["OUTPUT_COMMENTS"]:
# convert the tool change to a comment
comment = create_comment(
values["COMMAND_SPACE"]
+ format_outstring(values, outstring)
+ values["COMMAND_SPACE"],
values["COMMENT_SYMBOL"],
)
out += linenumber(values) + comment + "\n"
outstring = []
if command == "message" and values["REMOVE_MESSAGES"]:
if values["OUTPUT_COMMENTS"] is False:
out = []
else:
outstring.pop(0) # remove the command
if command in values["SUPPRESS_COMMANDS"]:
if values["OUTPUT_COMMENTS"]:
# convert the command to a comment
comment = create_comment(
values["COMMAND_SPACE"]
+ format_outstring(values, outstring)
+ values["COMMAND_SPACE"],
values["COMMENT_SYMBOL"],
)
out += linenumber(values) + comment + "\n"
# remove the command
outstring = []
# prepend a line number and append a newline
if len(outstring) >= 1:
if values["OUTPUT_LINE_NUMBERS"]:
# In this case we don't want a space after the line number
# because the space is added in the join just below.
outstring.insert(0, (linenumber(values, "")))
# append the line to the final output
out += values["COMMAND_SPACE"].join(outstring)
# Note: Do *not* strip `out`, since that forces the allocation
# of a contiguous string & thus quadratic complexity.
out += "\n"
# add height offset
if command in ("M6", "M06") and values["USE_TLO"]:
out += linenumber(values) + "G43 H" + str(int(c.Parameters["T"])) + "\n"
# Check for comments containing machine-specific commands
# to pass literally to the controller
if values["ENABLE_MACHINE_SPECIFIC_COMMANDS"]:
m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command)
if m:
raw_command = m.group(1)
out += linenumber(values) + raw_command + "\n"
return out

View File

@@ -24,6 +24,7 @@
# ***************************************************************************
from __future__ import print_function
import os
import FreeCAD
from FreeCAD import Units
import datetime
@@ -81,7 +82,7 @@ COMMENT = ";"
# gCode header with information about CAD-software, post-processor
# and date/time
if FreeCAD.ActiveDocument:
cam_file = FreeCAD.ActiveDocument.FileName
cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName)
else:
cam_file = "<None>"

View File

@@ -407,6 +407,8 @@ def export(objectslist, filename, argstring):
gfile.write(final)
gfile.close()
return final
def linenumber():
if not OUTPUT_LINE_NUMBERS:

View File

@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
from __future__ import print_function
from PathScripts import PostUtilsArguments
from PathScripts import PostUtilsExport
#
# The following variables need to be global variables
# to keep the PathPostProcessor.load method happy:
#
# TOOLTIP
# TOOLTIP_ARGS
# UNITS
#
# The "argument_defaults", "arguments_visible", and the "values" hashes
# need to be defined before the "init_shared_arguments" routine can be
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
# TOOLTIP_ARGS can be defined, so they end up being global variables also.
#
TOOLTIP = """
This is a postprocessor file for the Path workbench. It is used to
take a pseudo-gcode fragment outputted by a Path object, and output
real GCode suitable for a centroid 3 axis mill. This postprocessor, once placed
in the appropriate PathScripts folder, can be used directly from inside
FreeCAD, via the GUI importer or via python scripts with:
import refactored_centroid_post
refactored_centroid_post.export(object,"/path/to/file.ncc","")
"""
#
# Default to metric mode
#
UNITS = "G21"
def init_values(values):
"""Initialize values that are used throughout the postprocessor."""
#
global UNITS
PostUtilsArguments.init_shared_values(values)
#
# Set any values here that need to override the default values set
# in the init_shared_values routine.
#
# Use 4 digits for axis precision by default.
#
values["AXIS_PRECISION"] = 4
values["DEFAULT_AXIS_PRECISION"] = 4
values["DEFAULT_INCH_AXIS_PRECISION"] = 4
#
# Use ";" as the comment symbol
#
values["COMMENT_SYMBOL"] = ";"
#
# Use 1 digit for feed precision by default.
#
values["FEED_PRECISION"] = 1
values["DEFAULT_FEED_PRECISION"] = 1
values["DEFAULT_INCH_FEED_PRECISION"] = 1
#
# This value usually shows up in the post_op comment as "Finish operation:".
# Change it to "End" to produce "End operation:".
#
values["FINISH_LABEL"] = "End"
#
# If this value is True, then a list of tool numbers
# with their labels are output just before the preamble.
#
values["LIST_TOOLS_IN_PREAMBLE"] = True
#
# Used in the argparser code as the "name" of the postprocessor program.
# This would normally show up in the usage message in the TOOLTIP_ARGS,
# but we are suppressing the usage message, so it doesn't show up after all.
#
values["MACHINE_NAME"] = "Centroid"
#
# This list controls the order of parameters in a line during output.
# centroid doesn't want K properties on XY plane; Arcs need work.
#
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
"A",
"B",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
]
#
# Any commands in this value will be output as the last commands
# in the G-code file.
#
values["POSTAMBLE"] = """M99"""
values["POSTPROCESSOR_FILE_NAME"] = __name__
#
# Any commands in this value will be output after the header and
# safety block at the beginning of the G-code file.
#
values["PREAMBLE"] = """G53 G00 G17"""
#
# Output any messages.
#
values["REMOVE_MESSAGES"] = False
#
# Any commands in this value are output after the header but before the preamble,
# then again after the TOOLRETURN but before the POSTAMBLE.
#
values["SAFETYBLOCK"] = """G90 G80 G40 G49"""
#
# Do not show the current machine units just before the PRE_OPERATION.
#
values["SHOW_MACHINE_UNITS"] = False
#
# Do not show the current operation label just before the PRE_OPERATION.
#
values["SHOW_OPERATION_LABELS"] = False
#
# Do not output an M5 command to stop the spindle for tool changes.
#
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = False
#
# spindle off, height offset canceled, spindle retracted
# (M25 is a centroid command to retract spindle)
#
values[
"TOOLRETURN"
] = """M5
M25
G49 H0"""
values["UNITS"] = UNITS
#
# Default to not outputting a G43 following tool changes
#
values["USE_TLO"] = False
#
# This was in the original centroid postprocessor file
# but does not appear to be used anywhere.
#
# ZAXISRETURN = """G91 G28 X0 Z0 G90"""
#
def init_argument_defaults(argument_defaults):
"""Initialize which arguments (in a pair) are shown as the default argument."""
PostUtilsArguments.init_argument_defaults(argument_defaults)
#
# Modify which argument to show as the default in flag-type arguments here.
# If the value is True, the first argument will be shown as the default.
# If the value is False, the second argument will be shown as the default.
#
# For example, if you want to show Metric mode as the default, use:
# argument_defaults["metric_inch"] = True
#
# If you want to show that "Don't pop up editor for writing output" is
# the default, use:
# argument_defaults["show-editor"] = False.
#
# Note: You also need to modify the corresponding entries in the "values" hash
# to actually make the default value(s) change to match.
#
def init_arguments_visible(arguments_visible):
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
PostUtilsArguments.init_arguments_visible(arguments_visible)
#
# Modify the visibility of any arguments from the defaults here.
#
arguments_visible["axis-modal"] = False
arguments_visible["precision"] = False
arguments_visible["tlo"] = False
def init_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared argument definitions."""
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
#
# Add any argument definitions that are not shared with all other postprocessors here.
#
return parser
#
# Creating global variables and using functions to modify them
# is useful for being able to test things later.
#
values = {}
init_values(values)
argument_defaults = {}
init_argument_defaults(argument_defaults)
arguments_visible = {}
init_arguments_visible(arguments_visible)
parser = init_arguments(values, argument_defaults, arguments_visible)
#
# The TOOLTIP_ARGS value is created from the help information about the arguments.
#
TOOLTIP_ARGS = parser.format_help()
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
#
global parser
global UNITS
global values
# print(parser.format_help())
(flag, args) = PostUtilsArguments.process_shared_arguments(values, parser, argstring)
if not flag:
return None
#
# Process any additional arguments here
#
#
# Update the global variables that might have been modified
# while processing the arguments.
#
UNITS = values["UNITS"]
return PostUtilsExport.export_common(values, objectslist, filename)

View File

@@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
from __future__ import print_function
from PathScripts import PostUtilsArguments
from PathScripts import PostUtilsExport
#
# The following variables need to be global variables
# to keep the PathPostProcessor.load method happy:
#
# TOOLTIP
# TOOLTIP_ARGS
# UNITS
#
# The "argument_defaults", "arguments_visible", and the "values" hashes
# need to be defined before the "init_shared_arguments" routine can be
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
#
TOOLTIP = """
Generate g-code from a Path that is compatible with the grbl controller:
import refactored_grbl_post
refactored_grbl_post.export(object, "/path/to/file.ncc")
"""
#
# Default to metric mode
#
UNITS = "G21"
def init_values(values):
"""Initialize values that are used throughout the postprocessor."""
#
global UNITS
PostUtilsArguments.init_shared_values(values)
#
# Set any values here that need to override the default values set
# in the init_shared_values routine.
#
#
# If this is set to True, then commands that are placed in
# comments that look like (MC_RUN_COMMAND: blah) will be output.
#
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = True
#
# Used in the argparser code as the "name" of the postprocessor program.
# This would normally show up in the usage message in the TOOLTIP_ARGS,
# but we are suppressing the usage message, so it doesn't show up after all.
#
values["MACHINE_NAME"] = "Grbl"
#
# Default to outputting Path labels at the beginning of each Path.
#
values["OUTPUT_PATH_LABELS"] = True
#
# Default to not outputting M6 tool changes (comment it) as grbl currently does not handle it
#
values["OUTPUT_TOOL_CHANGE"] = False
#
# The order of the parameters.
# Arcs may only work on the XY plane (this needs to be verified).
#
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"U",
"V",
"W",
"I",
"J",
"K",
"F",
"S",
"T",
"Q",
"R",
"L",
"P",
]
#
# Any commands in this value will be output as the last commands
# in the G-code file.
#
values[
"POSTAMBLE"
] = """M5
G17 G90
M2"""
values["POSTPROCESSOR_FILE_NAME"] = __name__
#
# Any commands in this value will be output after the header and
# safety block at the beginning of the G-code file.
#
values["PREAMBLE"] = """G17 G90"""
#
# Do not show the current machine units just before the PRE_OPERATION.
#
values["SHOW_MACHINE_UNITS"] = False
values["UNITS"] = UNITS
#
# Default to not outputting a G43 following tool changes
#
values["USE_TLO"] = False
def init_argument_defaults(argument_defaults):
"""Initialize which arguments (in a pair) are shown as the default argument."""
PostUtilsArguments.init_argument_defaults(argument_defaults)
#
# Modify which argument to show as the default in flag-type arguments here.
# If the value is True, the first argument will be shown as the default.
# If the value is False, the second argument will be shown as the default.
#
# For example, if you want to show Metric mode as the default, use:
# argument_defaults["metric_inch"] = True
#
# If you want to show that "Don't pop up editor for writing output" is
# the default, use:
# argument_defaults["show-editor"] = False.
#
# Note: You also need to modify the corresponding entries in the "values" hash
# to actually make the default value(s) change to match.
#
argument_defaults["tlo"] = False
argument_defaults["tool_change"] = False
def init_arguments_visible(arguments_visible):
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
PostUtilsArguments.init_arguments_visible(arguments_visible)
#
# Modify the visibility of any arguments from the defaults here.
#
arguments_visible["bcnc"] = True
arguments_visible["axis-modal"] = False
arguments_visible["return-to"] = True
arguments_visible["tlo"] = False
arguments_visible["tool_change"] = True
arguments_visible["translate_drill"] = True
arguments_visible["wait-for-spindle"] = True
def init_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared argument definitions."""
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
#
# Add any argument definitions that are not shared with all other postprocessors here.
#
return parser
#
# Creating global variables and using functions to modify them
# is useful for being able to test things later.
#
values = {}
init_values(values)
argument_defaults = {}
init_argument_defaults(argument_defaults)
arguments_visible = {}
init_arguments_visible(arguments_visible)
parser = init_arguments(values, argument_defaults, arguments_visible)
#
# The TOOLTIP_ARGS value is created from the help information about the arguments.
#
TOOLTIP_ARGS = parser.format_help()
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
#
global parser
global UNITS
global values
# print(parser.format_help())
(flag, args) = PostUtilsArguments.process_shared_arguments(values, parser, argstring)
if not flag:
return None
#
# Process any additional arguments here
#
#
# Update the global variables that might have been modified
# while processing the arguments.
#
UNITS = values["UNITS"]
return PostUtilsExport.export_common(values, objectslist, filename)

View File

@@ -24,278 +24,50 @@
from __future__ import print_function
import argparse
import datetime
import shlex
import FreeCAD
from FreeCAD import Units
import Path
from PathScripts import PostUtils
# to distinguish python built-in open function from the one declared below
if open.__module__ in ["__builtin__", "io"]:
pythonopen = open
from PathScripts import PostUtilsArguments
from PathScripts import PostUtilsExport
#
# Holds various values that are used throughout the postprocessor code.
# The following variables need to be global variables
# to keep the PathPostProcessor.load method happy:
#
values = {}
# TOOLTIP
# TOOLTIP_ARGS
# UNITS
#
# The "argument_defaults", "arguments_visible", and the "values" hashes
# need to be defined before the "init_shared_arguments" routine can be
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
#
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
in the appropriate PathScripts folder, can be used directly from inside
FreeCAD, via the GUI importer or via python scripts with:
def init_values():
"""Initialize many of the commonly used values."""
values["now"] = datetime.datetime.now()
values[
"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
in the appropriate PathScripts folder, can be used directly from inside
FreeCAD, via the GUI importer or via python scripts with:
import linuxcnc_post
linuxcnc_post.export(object,"/path/to/file.ncc","")
"""
#
# These values set common customization preferences
#
values["OUTPUT_COMMENTS"] = True
values["OUTPUT_HEADER"] = True
values["OUTPUT_LINE_NUMBERS"] = False
values["SHOW_EDITOR"] = True
# if true commands are suppressed if the same as previous line.
values["MODAL"] = False
# if true G43 will be output following tool changes
values["USE_TLO"] = True
# if false duplicate axis values are suppressed if the same as previous line.
values["OUTPUT_DOUBLES"] = True
values["COMMAND_SPACE"] = " "
# line number starting value
values["LINENR"] = 100
#
# These values will be reflected in the Machine configuration of the project
#
# G21 for metric, G20 for US standard
values["UNITS"] = "G21"
values["UNIT_SPEED_FORMAT"] = "mm/min"
values["UNIT_FORMAT"] = "mm"
values["MACHINE_NAME"] = "LinuxCNC"
values["CORNER_MIN"] = {"x": 0, "y": 0, "z": 0}
values["CORNER_MAX"] = {"x": 500, "y": 300, "z": 300}
values["PRECISION"] = 3
# Preamble text will appear at the beginning of the GCODE output file.
values["PREAMBLE"] = """G17 G54 G40 G49 G80 G90"""
# Postamble text will appear following the last operation.
values[
"POSTAMBLE"
] = """M05
G17 G54 G90 G80 G40
M2
import refactored_linuxcnc_post
refactored_linuxcnc_post.export(object,"/path/to/file.ncc","")
"""
# Pre operation text will be inserted before every operation
values["PRE_OPERATION"] = """"""
# Post operation text will be inserted after every operation
values["POST_OPERATION"] = """"""
# Tool Change commands will be inserted before a tool change
values["TOOL_CHANGE"] = """"""
#
# Default to metric mode
#
UNITS = "G21"
def processArguments(values, argstring):
"""Process the arguments to the postprocessor."""
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",
)
values["TOOLTIP_ARGS"] = parser.format_help()
try:
args = parser.parse_args(shlex.split(argstring))
if args.no_header:
values["OUTPUT_HEADER"] = False
if args.no_comments:
values["OUTPUT_COMMENTS"] = False
if args.line_numbers:
values["OUTPUT_LINE_NUMBERS"] = True
if args.no_show_editor:
values["SHOW_EDITOR"] = False
values["PRECISION"] = args.precision
if args.preamble is not None:
values["PREAMBLE"] = args.preamble
if args.postamble is not None:
values["POSTAMBLE"] = args.postamble
if args.inches:
values["UNITS"] = "G20"
values["UNIT_SPEED_FORMAT"] = "in/min"
values["UNIT_FORMAT"] = "in"
values["PRECISION"] = 4
if args.modal:
values["MODAL"] = True
if args.no_tlo:
values["USE_TLO"] = False
if args.axis_modal:
values["OUTPUT_DOUBLES"] = False
except Exception:
return False
return True
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
init_values()
if not processArguments(values, argstring):
return 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."
)
return None
print("postprocessing...")
gcode = ""
# write header
if values["OUTPUT_HEADER"]:
gcode += linenumber(values) + "(Exported by FreeCAD)\n"
gcode += linenumber(values) + "(Post Processor: " + __name__ + ")\n"
gcode += linenumber(values) + "(Output Time:" + str(values["now"]) + ")\n"
# Write the preamble
if values["OUTPUT_COMMENTS"]:
gcode += linenumber(values) + "(begin preamble)\n"
for line in values["PREAMBLE"].splitlines(False):
gcode += linenumber(values) + line + "\n"
gcode += linenumber(values) + values["UNITS"] + "\n"
for obj in objectslist:
# Skip inactive operations
if hasattr(obj, "Active"):
if not obj.Active:
continue
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
if not obj.Base.Active:
continue
# do the pre_op
if values["OUTPUT_COMMENTS"]:
gcode += linenumber(values) + "(begin operation: %s)\n" % obj.Label
gcode += linenumber(values) + "(machine units: %s)\n" % (values["UNIT_SPEED_FORMAT"])
for line in values["PRE_OPERATION"].splitlines(True):
gcode += linenumber(values) + line
# get coolant mode
coolantMode = "None"
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 values["OUTPUT_COMMENTS"]:
if not coolantMode == "None":
gcode += linenumber(values) + "(Coolant On:" + coolantMode + ")\n"
if coolantMode == "Flood":
gcode += linenumber(values) + "M8" + "\n"
if coolantMode == "Mist":
gcode += linenumber(values) + "M7" + "\n"
# process the operation gcode
gcode += parse(values, obj)
# do the post_op
if values["OUTPUT_COMMENTS"]:
gcode += linenumber(values) + "(finish operation: %s)\n" % obj.Label
for line in values["POST_OPERATION"].splitlines(True):
gcode += linenumber(values) + line
# turn coolant off if required
if not coolantMode == "None":
if values["OUTPUT_COMMENTS"]:
gcode += linenumber(values) + "(Coolant Off:" + coolantMode + ")\n"
gcode += linenumber(values) + "M9" + "\n"
# do the post_amble
if values["OUTPUT_COMMENTS"]:
gcode += "(begin postamble)\n"
for line in values["POSTAMBLE"].splitlines(True):
gcode += linenumber(values) + line
if FreeCAD.GuiUp and values["SHOW_EDITOR"]:
final = gcode
if len(gcode) > 100000:
print("Skipping editor since output is greater than 100kb")
else:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = gcode
print("done postprocessing.")
if not filename == "-":
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
return final
def linenumber(values):
"""Output the next line number if appropriate."""
if values["OUTPUT_LINE_NUMBERS"]:
values["LINENR"] += 10
return "N" + str(values["LINENR"]) + " "
return ""
def parse(values, pathobj):
"""Parse a Path."""
out = ""
lastcommand = None
precision_string = "." + str(values["PRECISION"]) + "f"
currLocation = {} # keep track for no doubles
def init_values(values):
"""Initialize values that are used throughout the postprocessor."""
#
global UNITS
PostUtilsArguments.init_shared_values(values)
#
# Set any values here that need to override the default values set
# in the init_shared_values routine.
#
values["ENABLE_COOLANT"] = True
# the order of parameters
# linuxcnc doesn't want K properties on XY plane Arcs need work.
params = [
# linuxcnc doesn't want K properties on XY plane; Arcs need work.
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
@@ -314,118 +86,104 @@ def parse(values, pathobj):
"D",
"P",
]
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
currLocation.update(firstmove.Parameters) # set First location Parameters
if hasattr(pathobj, "Group"): # We have a compound or project.
# if values["OUTPUT_COMMENTS"]:
# out += linenumber(values) + "(compound: " + pathobj.Label + ")\n"
for p in pathobj.Group:
out += parse(values, p)
return out
else: # parsing simple path
# groups might contain non-path things like stock.
if not hasattr(pathobj, "Path"):
return out
# if values["OUTPUT_COMMENTS"]:
# out += linenumber(values) + "(" + pathobj.Label + ")\n"
for c in pathobj.Path.Commands:
outstring = []
command = c.Name
outstring.append(command)
# if modal: suppress the command if it is the same as the last one
if values["MODAL"]:
if command == lastcommand:
outstring.pop(0)
if c.Name[0] == "(" and not values["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 values["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(values["UNIT_SPEED_FORMAT"]) > 0.0:
outstring.append(
param
+ format(
float(speed.getValueAs(values["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"])))
else:
if (
(not values["OUTPUT_DOUBLES"])
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(
param
+ format(
float(pos.getValueAs(values["UNIT_FORMAT"])), precision_string
)
)
# store the latest command
lastcommand = command
currLocation.update(c.Parameters)
# Check for Tool Change:
if command == "M6":
# stop the spindle
out += linenumber(values) + "M5\n"
for line in values["TOOL_CHANGE"].splitlines(True):
out += linenumber(values) + line
# add height offset
if values["USE_TLO"]:
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
outstring.append(tool_height)
if command == "message":
if values["OUTPUT_COMMENTS"] is False:
out = []
else:
outstring.pop(0) # remove the command
# prepend a line number and append a newline
if len(outstring) >= 1:
if values["OUTPUT_LINE_NUMBERS"]:
outstring.insert(0, (linenumber(values)))
# append the line to the final output
for w in outstring:
out += w + values["COMMAND_SPACE"]
# Note: Do *not* strip `out`, since that forces the allocation
# of a contiguous string & thus quadratic complexity.
out += "\n"
return out
#
# Used in the argparser code as the "name" of the postprocessor program.
# This would normally show up in the usage message in the TOOLTIP_ARGS,
# but we are suppressing the usage message, so it doesn't show up after all.
#
values["MACHINE_NAME"] = "LinuxCNC"
#
# Any commands in this value will be output as the last commands
# in the G-code file.
#
values[
"POSTAMBLE"
] = """M05
G17 G54 G90 G80 G40
M2"""
values["POSTPROCESSOR_FILE_NAME"] = __name__
#
# Any commands in this value will be output after the header and
# safety block at the beginning of the G-code file.
#
values["PREAMBLE"] = """G17 G54 G40 G49 G80 G90"""
values["UNITS"] = UNITS
# print(__name__ + " gcode postprocessor loaded.")
def init_argument_defaults(argument_defaults):
"""Initialize which arguments (in a pair) are shown as the default argument."""
PostUtilsArguments.init_argument_defaults(argument_defaults)
#
# Modify which argument to show as the default in flag-type arguments here.
# If the value is True, the first argument will be shown as the default.
# If the value is False, the second argument will be shown as the default.
#
# For example, if you want to show Metric mode as the default, use:
# argument_defaults["metric_inch"] = True
#
# If you want to show that "Don't pop up editor for writing output" is
# the default, use:
# argument_defaults["show-editor"] = False.
#
# Note: You also need to modify the corresponding entries in the "values" hash
# to actually make the default value(s) change to match.
#
def init_arguments_visible(arguments_visible):
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
PostUtilsArguments.init_arguments_visible(arguments_visible)
#
# Modify the visibility of any arguments from the defaults here.
#
def init_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared argument definitions."""
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
#
# Add any argument definitions that are not shared with all other postprocessors here.
#
return parser
#
# Creating global variables and using functions to modify them
# is useful for being able to test things later.
#
values = {}
init_values(values)
argument_defaults = {}
init_argument_defaults(argument_defaults)
arguments_visible = {}
init_arguments_visible(arguments_visible)
parser = init_arguments(values, argument_defaults, arguments_visible)
#
# The TOOLTIP_ARGS value is created from the help information about the arguments.
#
TOOLTIP_ARGS = parser.format_help()
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
#
global parser
global UNITS
global values
# print(parser.format_help())
(flag, args) = PostUtilsArguments.process_shared_arguments(values, parser, argstring)
if not flag:
return None
#
# Process any additional arguments here
#
#
# Update the global variables that might have been modified
# while processing the arguments.
#
UNITS = values["UNITS"]
return PostUtilsExport.export_common(values, objectslist, filename)

View File

@@ -0,0 +1,196 @@
# ***************************************************************************
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************/
from __future__ import print_function
from PathScripts import PostUtilsArguments
from PathScripts import PostUtilsExport
#
# The following variables need to be global variables
# to keep the PathPostProcessor.load method happy:
#
# TOOLTIP
# TOOLTIP_ARGS
# UNITS
#
# The "argument_defaults", "arguments_visible", and the "values" hashes
# need to be defined before the "init_shared_arguments" routine can be
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
#
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
in the appropriate PathScripts folder, can be used directly from inside
FreeCAD, via the GUI importer or via python scripts with:
import mach3_mach4_post
mach3_mach4_post.export(object,"/path/to/file.ncc","")
"""
#
# Default to metric mode
#
UNITS = "G21"
def init_values(values):
"""Initialize values that are used throughout the postprocessor."""
#
global UNITS
PostUtilsArguments.init_shared_values(values)
#
# Set any values here that need to override the default values set
# in the init_shared_values routine.
#
values["ENABLE_COOLANT"] = True
#
# Used in the argparser code as the "name" of the postprocessor program.
# This would normally show up in the usage message in the TOOLTIP_ARGS,
# but we are suppressing the usage message, so it doesn't show up after all.
#
values["MACHINE_NAME"] = "mach3_4"
# Enable special processing for operations with "Adaptive" in the name
values["OUTPUT_ADAPTIVE"] = True
# Output the machine name for mach3_mach4 instead of the machine units alone.
values["OUTPUT_MACHINE_NAME"] = True
# the order of parameters
# mach3_mach4 doesn't want K properties on XY plane; Arcs need work.
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"I",
"J",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
#
# Any commands in this value will be output as the last commands
# in the G-code file.
#
values[
"POSTAMBLE"
] = """M05
G17 G54 G90 G80 G40
M2"""
values["POSTPROCESSOR_FILE_NAME"] = __name__
#
# Any commands in this value will be output after the header and
# safety block at the beginning of the G-code file.
#
values["PREAMBLE"] = """G17 G54 G40 G49 G80 G90"""
# Output the machine name for mach3_mach4 instead of the machine units alone.
values["SHOW_MACHINE_UNITS"] = False
values["UNITS"] = UNITS
def init_argument_defaults(argument_defaults):
"""Initialize which arguments (in a pair) are shown as the default argument."""
PostUtilsArguments.init_argument_defaults(argument_defaults)
#
# Modify which argument to show as the default in flag-type arguments here.
# If the value is True, the first argument will be shown as the default.
# If the value is False, the second argument will be shown as the default.
#
# For example, if you want to show Metric mode as the default, use:
# argument_defaults["metric_inch"] = True
#
# If you want to show that "Don't pop up editor for writing output" is
# the default, use:
# argument_defaults["show-editor"] = False.
#
# Note: You also need to modify the corresponding entries in the "values" hash
# to actually make the default value(s) change to match.
#
def init_arguments_visible(arguments_visible):
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
PostUtilsArguments.init_arguments_visible(arguments_visible)
#
# Modify the visibility of any arguments from the defaults here.
#
arguments_visible["axis-modal"] = True
def init_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared argument definitions."""
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
#
# Add any argument definitions that are not shared with all other postprocessors here.
#
return parser
#
# Creating global variables and using functions to modify them
# is useful for being able to test things later.
#
values = {}
init_values(values)
argument_defaults = {}
init_argument_defaults(argument_defaults)
arguments_visible = {}
init_arguments_visible(arguments_visible)
parser = init_arguments(values, argument_defaults, arguments_visible)
#
# The TOOLTIP_ARGS value is created from the help information about the arguments.
#
TOOLTIP_ARGS = parser.format_help()
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
#
global parser
global UNITS
global values
# print(parser.format_help())
(flag, args) = PostUtilsArguments.process_shared_arguments(values, parser, argstring)
if not flag:
return None
#
# Process any additional arguments here
#
#
# Update the global variables that might have been modified
# while processing the arguments.
#
UNITS = values["UNITS"]
return PostUtilsExport.export_common(values, objectslist, filename)

View File

@@ -0,0 +1,216 @@
# ***************************************************************************
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@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 *
# * *
# ***************************************************************************
from __future__ import print_function
from PathScripts import PostUtilsArguments
from PathScripts import PostUtilsExport
#
# The following variables need to be global variables
# to keep the PathPostProcessor.load method happy:
#
# TOOLTIP
# TOOLTIP_ARGS
# UNITS
#
# The "argument_defaults", "arguments_visible", and the "values" hashes
# need to be defined before the "init_shared_arguments" routine can be
# called to create TOOLTIP_ARGS, so they also end up having to be globals.
#
TOOLTIP = """This is a postprocessor file for the Path workbench. It is used to
test the postprocessor code. It probably isn't useful for "real" gcode.
import refactored_test_post
refactored_test_post.export(object,"/path/to/file.ncc","")
"""
#
# Default to metric mode
#
UNITS = "G21"
def init_values(values):
"""Initialize values that are used throughout the postprocessor."""
#
global UNITS
PostUtilsArguments.init_shared_values(values)
#
# Set any values here that need to override the default values set
# in the init_shared_values routine.
#
# Turn off as much functionality as possible by default.
# Then the tests can turn back on the appropriate options as needed.
#
# Used in the argparser code as the "name" of the postprocessor program.
# This would normally show up in the usage message in the TOOLTIP_ARGS,
# but we are suppressing the usage message, so it doesn't show up after all.
#
values["MACHINE_NAME"] = "test"
#
# Don't output comments by default
#
values["OUTPUT_COMMENTS"] = False
#
# Don't output the header by default
#
values["OUTPUT_HEADER"] = False
#
# Convert M56 tool change commands to comments,
# which are then suppressed by default.
#
values["OUTPUT_TOOL_CHANGE"] = False
#
# Enable as many parameters as possible to be output by default
#
values["PARAMETER_ORDER"] = [
"X",
"Y",
"Z",
"A",
"B",
"C",
"U",
"V",
"W",
"I",
"J",
"K",
"F",
"S",
"T",
"Q",
"R",
"L",
"H",
"D",
"P",
]
values["POSTPROCESSOR_FILE_NAME"] = __name__
#
# Do not show the editor by default since we are testing.
#
values["SHOW_EDITOR"] = False
#
# Don't show the current machine units by default
#
values["SHOW_MACHINE_UNITS"] = False
#
# Don't show the current operation label by default.
#
values["SHOW_OPERATION_LABELS"] = False
#
# Don't output an M5 command to stop the spindle after an M6 tool change by default.
#
values["STOP_SPINDLE_FOR_TOOL_CHANGE"] = False
#
# Don't output a G43 tool length command following tool changes by default.
#
values["USE_TLO"] = False
values["UNITS"] = UNITS
def init_argument_defaults(argument_defaults):
"""Initialize which arguments (in a pair) are shown as the default argument."""
PostUtilsArguments.init_argument_defaults(argument_defaults)
#
# Modify which argument to show as the default in flag-type arguments here.
# If the value is True, the first argument will be shown as the default.
# If the value is False, the second argument will be shown as the default.
#
# For example, if you want to show Metric mode as the default, use:
# argument_defaults["metric_inch"] = True
#
# If you want to show that "Don't pop up editor for writing output" is
# the default, use:
# argument_defaults["show-editor"] = False.
#
# Note: You also need to modify the corresponding entries in the "values" hash
# to actually make the default value(s) change to match.
#
def init_arguments_visible(arguments_visible):
"""Initialize which argument pairs are visible in TOOLTIP_ARGS."""
PostUtilsArguments.init_arguments_visible(arguments_visible)
#
# Modify the visibility of any arguments from the defaults here.
#
#
# Make all arguments invisible by default.
#
for k in iter(arguments_visible):
arguments_visible[k] = False
def init_arguments(values, argument_defaults, arguments_visible):
"""Initialize the shared argument definitions."""
parser = PostUtilsArguments.init_shared_arguments(values, argument_defaults, arguments_visible)
#
# Add any argument definitions that are not shared with all other postprocessors here.
#
return parser
#
# Creating global variables and using functions to modify them
# is useful for being able to test things later.
#
values = {}
init_values(values)
argument_defaults = {}
init_argument_defaults(argument_defaults)
arguments_visible = {}
init_arguments_visible(arguments_visible)
parser = init_arguments(values, argument_defaults, arguments_visible)
#
# The TOOLTIP_ARGS value is created from the help information about the arguments.
#
TOOLTIP_ARGS = parser.format_help()
def export(objectslist, filename, argstring):
"""Postprocess the objects in objectslist to filename."""
#
global parser
global UNITS
global values
# print(parser.format_help())
(flag, args) = PostUtilsArguments.process_shared_arguments(values, parser, argstring)
if not flag:
return None
#
# Process any additional arguments here
#
#
# Update the global variables that might have been modified
# while processing the arguments.
#
UNITS = values["UNITS"]
return PostUtilsExport.export_common(values, objectslist, filename)