Merge pull request #7007 from LarryWoestman/post_refactor
Path: (WIP) Add some refactored postprocessors along with some tests.
This commit is contained in:
@@ -142,6 +142,9 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PathWaterline.py
|
||||
PathScripts/PathWaterlineGui.py
|
||||
PathScripts/PostUtils.py
|
||||
PathScripts/PostUtilsArguments.py
|
||||
PathScripts/PostUtilsExport.py
|
||||
PathScripts/PostUtilsParse.py
|
||||
PathScripts/__init__.py
|
||||
)
|
||||
|
||||
@@ -174,6 +177,11 @@ SET(PathScripts_post_SRCS
|
||||
PathScripts/post/opensbp_post.py
|
||||
PathScripts/post/opensbp_pre.py
|
||||
PathScripts/post/philips_post.py
|
||||
PathScripts/post/refactored_centroid_post.py
|
||||
PathScripts/post/refactored_grbl_post.py
|
||||
PathScripts/post/refactored_linuxcnc_post.py
|
||||
PathScripts/post/refactored_mach3_mach4_post.py
|
||||
PathScripts/post/refactored_test_post.py
|
||||
PathScripts/post/rml_post.py
|
||||
PathScripts/post/rrf_post.py
|
||||
PathScripts/post/slic3r_pre.py
|
||||
@@ -221,14 +229,19 @@ SET(Tools_Shape_SRCS
|
||||
SET(PathTests_SRCS
|
||||
PathTests/__init__.py
|
||||
PathTests/boxtest.fcstd
|
||||
PathTests/boxtest1.fcstd
|
||||
PathTests/Drilling_1.FCStd
|
||||
PathTests/drill_test1.FCStd
|
||||
PathTests/PathTestUtils.py
|
||||
PathTests/test_adaptive.fcstd
|
||||
PathTests/test_centroid_00.ngc
|
||||
PathTests/test_filenaming.fcstd
|
||||
PathTests/test_geomop.fcstd
|
||||
PathTests/test_holes00.fcstd
|
||||
PathTests/test_linuxcnc_00.ngc
|
||||
PathTests/TestCentroidPost.py
|
||||
PathTests/TestGrblPost.py
|
||||
PathTests/TestLinuxCNCPost.py
|
||||
PathTests/TestMach3Mach4Post.py
|
||||
PathTests/TestPathAdaptive.py
|
||||
PathTests/TestPathCore.py
|
||||
PathTests/TestPathDeburr.py
|
||||
@@ -259,6 +272,11 @@ SET(PathTests_SRCS
|
||||
PathTests/TestPathUtil.py
|
||||
PathTests/TestPathVcarve.py
|
||||
PathTests/TestPathVoronoi.py
|
||||
PathTests/TestRefactoredCentroidPost.py
|
||||
PathTests/TestRefactoredGrblPost.py
|
||||
PathTests/TestRefactoredLinuxCNCPost.py
|
||||
PathTests/TestRefactoredMach3Mach4Post.py
|
||||
PathTests/TestRefactoredTestPost.py
|
||||
PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb
|
||||
PathTests/Tools/Library/test-path-tool-bit-library-00.fctl
|
||||
PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd
|
||||
@@ -413,4 +431,3 @@ INSTALL(
|
||||
DESTINATION
|
||||
Mod/Path/Data/Threads
|
||||
)
|
||||
|
||||
|
||||
@@ -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}")
|
||||
@@ -187,13 +151,71 @@ 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"
|
||||
# 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 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
|
||||
# openDialog = True
|
||||
|
||||
@@ -235,7 +257,7 @@ def resolveFileName(job, subpartname, sequencenumber):
|
||||
def buildPostList(job):
|
||||
"""Takes the job and determines the specific objects and order to
|
||||
postprocess Returns a list of objects which can be passed to
|
||||
exportObjectsWith() for final posting"""
|
||||
exportObjectsWith() for final posting."""
|
||||
wcslist = job.Fixtures
|
||||
orderby = job.OrderOutputBy
|
||||
|
||||
@@ -243,8 +265,8 @@ def buildPostList(job):
|
||||
|
||||
if orderby == "Fixture":
|
||||
PathLog.debug("Ordering by Fixture")
|
||||
# Order by fixture means all operations and tool changes will be completed in one
|
||||
# fixture before moving to the next.
|
||||
# Order by fixture means all operations and tool changes will be
|
||||
# completed in one fixture before moving to the next.
|
||||
|
||||
currTool = None
|
||||
for index, f in enumerate(wcslist):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,18 +24,20 @@
|
||||
# ***************************************************************************
|
||||
|
||||
"""
|
||||
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
|
||||
@@ -147,7 +150,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 +159,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
|
||||
@@ -190,7 +192,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 +202,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)
|
||||
|
||||
|
||||
655
src/Mod/Path/PathScripts/PostUtilsArguments.py
Normal file
655
src/Mod/Path/PathScripts/PostUtilsArguments.py
Normal file
@@ -0,0 +1,655 @@
|
||||
# -*- 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)
|
||||
313
src/Mod/Path/PathScripts/PostUtilsExport.py
Normal file
313
src/Mod/Path/PathScripts/PostUtilsExport.py
Normal file
@@ -0,0 +1,313 @@
|
||||
# -*- 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
|
||||
579
src/Mod/Path/PathScripts/PostUtilsParse.py
Normal file
579
src/Mod/Path/PathScripts/PostUtilsParse.py
Normal file
@@ -0,0 +1,579 @@
|
||||
# -*- 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
|
||||
@@ -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>"
|
||||
|
||||
|
||||
@@ -407,6 +407,8 @@ def export(objectslist, filename, argstring):
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
if not OUTPUT_LINE_NUMBERS:
|
||||
|
||||
255
src/Mod/Path/PathScripts/post/refactored_centroid_post.py
Normal file
255
src/Mod/Path/PathScripts/post/refactored_centroid_post.py
Normal 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)
|
||||
222
src/Mod/Path/PathScripts/post/refactored_grbl_post.py
Normal file
222
src/Mod/Path/PathScripts/post/refactored_grbl_post.py
Normal 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)
|
||||
189
src/Mod/Path/PathScripts/post/refactored_linuxcnc_post.py
Normal file
189
src/Mod/Path/PathScripts/post/refactored_linuxcnc_post.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# ***************************************************************************
|
||||
# * 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 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 refactored_linuxcnc_post
|
||||
refactored_linuxcnc_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
|
||||
# the order of parameters
|
||||
# linuxcnc 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",
|
||||
]
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
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)
|
||||
196
src/Mod/Path/PathScripts/post/refactored_mach3_mach4_post.py
Normal file
196
src/Mod/Path/PathScripts/post/refactored_mach3_mach4_post.py
Normal 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)
|
||||
216
src/Mod/Path/PathScripts/post/refactored_test_post.py
Normal file
216
src/Mod/Path/PathScripts/post/refactored_test_post.py
Normal 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)
|
||||
308
src/Mod/Path/PathTests/TestCentroidPost.py
Normal file
308
src/Mod/Path/PathTests/TestCentroidPost.py
Normal file
@@ -0,0 +1,308 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import centroid_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestCentroidPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does
|
||||
not have access to the class `self` reference. This method
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 16)
|
||||
|
||||
# Test without header
|
||||
expected = """G90 G80 G40 G49
|
||||
;begin preamble
|
||||
G53 G00 G17
|
||||
G21
|
||||
;begin operation
|
||||
;end operation: testpath
|
||||
;begin postamble
|
||||
M5
|
||||
M25
|
||||
G49 H0
|
||||
G90 G80 G40 G49
|
||||
M99
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G90 G80 G40 G49
|
||||
G53 G00 G17
|
||||
G21
|
||||
M5
|
||||
M25
|
||||
G49 H0
|
||||
G90 G80 G40 G49
|
||||
M99
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.0000 Y20.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --axis-precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.0000 Y20.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
#
|
||||
# The original centroid postprocessor does not have a
|
||||
# --preamble option. We end up with the default preamble.
|
||||
#
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[1]
|
||||
self.assertEqual(result, "G53 G00 G17")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
#
|
||||
# The original centroid postprocessor does not have a
|
||||
# --postamble option. We end up with the default postamble.
|
||||
#
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[-1], "M99")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[3], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --inches --axis-precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
#
|
||||
# The original centroid postprocessor does not have a
|
||||
# --modal option. We end up with the original gcode.
|
||||
#
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 X10.0000 Y30.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
#
|
||||
# The original centroid postprocessor does not have an
|
||||
# --axis-modal option. We end up with the original gcode.
|
||||
#
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 X10.0000 Y30.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[5], "M6 T2")
|
||||
self.assertEqual(gcode.splitlines()[6], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
#
|
||||
# The original centroid postprocessor does not have an
|
||||
# --no-tlo option. We end up with the original gcode.
|
||||
#
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = ";comment"
|
||||
self.assertEqual(result, expected)
|
||||
297
src/Mod/Path/PathTests/TestGrblPost.py
Normal file
297
src/Mod/Path/PathTests/TestGrblPost.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from importlib import reload
|
||||
from PathScripts.post import grbl_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestGrblPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does
|
||||
not have access to the class `self` reference. This method is able
|
||||
to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing. Only test
|
||||
# length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 13)
|
||||
|
||||
# Test without header
|
||||
expected = """(Begin preamble)
|
||||
G17 G90
|
||||
G21
|
||||
(Begin operation: testpath)
|
||||
(Path: testpath)
|
||||
(Finish operation: testpath)
|
||||
(Begin postamble)
|
||||
M5
|
||||
G17 G90
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G90
|
||||
G21
|
||||
M5
|
||||
G17 G90
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
Test imperial / inches
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55\n' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# Technical debt. The following test fails. Precision not working
|
||||
# with imperial units.
|
||||
|
||||
# args = ("--no-header --inches --precision=2")
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# result = gcode.splitlines()[5]
|
||||
# expected = "G0 X0.39 Y0.78 Z1.18 "
|
||||
# self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
#
|
||||
# The grbl postprocessor does not have a --modal option.
|
||||
#
|
||||
# args = "--no-header --modal --no-show-editor"
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# result = gcode.splitlines()[6]
|
||||
# expected = "X10.000 Y30.000 Z30.000 "
|
||||
# self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
#
|
||||
# The grbl postprocessor does not have a --axis-modal option.
|
||||
#
|
||||
# args = "--no-header --axis-modal --no-show-editor"
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# result = gcode.splitlines()[6]
|
||||
# expected = "G0 Y30.000 "
|
||||
# self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "( M6 T2 )")
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
#
|
||||
# The grbl postprocessor does not have a --no-tlo option.
|
||||
#
|
||||
# args = "--no-header --no-tlo --no-show-editor"
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# self.assertEqual(gcode.splitlines()[7], "M3 S3000 ")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment)"
|
||||
self.assertEqual(result, expected)
|
||||
290
src/Mod/Path/PathTests/TestLinuxCNCPost.py
Normal file
290
src/Mod/Path/PathTests/TestLinuxCNCPost.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from importlib import reload
|
||||
from PathScripts.post import linuxcnc_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestLinuxCNCPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does
|
||||
not have access to the class `self` reference. This method is able
|
||||
to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 13)
|
||||
|
||||
# Test without header
|
||||
expected = """(begin preamble)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(begin operation: testpath)
|
||||
(machine units: mm/min)
|
||||
(finish operation: testpath)
|
||||
(begin postamble)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
Test imperial / inches
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N160 G0 X10.000 Y20.000 Z30.000 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# Technical debt. The following test fails. Precision not working
|
||||
# with imperial units.
|
||||
|
||||
# args = ("--no-header --inches --precision=2")
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# result = gcode.splitlines()[5]
|
||||
# expected = "G0 X0.39 Y0.78 Z1.18 "
|
||||
# self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.000 Y30.000 Z30.000 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.000 "
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[5], "M5")
|
||||
self.assertEqual(gcode.splitlines()[6], "M6 T2 ")
|
||||
self.assertEqual(gcode.splitlines()[7], "G43 H2 ")
|
||||
self.assertEqual(gcode.splitlines()[8], "M3 S3000 ")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000 ")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment) "
|
||||
self.assertEqual(result, expected)
|
||||
291
src/Mod/Path/PathTests/TestMach3Mach4Post.py
Normal file
291
src/Mod/Path/PathTests/TestMach3Mach4Post.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import mach3_mach4_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestMach3Mach4Post(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does
|
||||
not have access to the class `self` reference. This method is able
|
||||
to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 13)
|
||||
|
||||
# Test without header
|
||||
expected = """(begin preamble)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(begin operation: testpath)
|
||||
(machine: mach3_4, mm/min)
|
||||
(finish operation: testpath)
|
||||
(begin postamble)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N160 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
# Technical debt. The following test fails. Precision not working
|
||||
# with imperial units.
|
||||
|
||||
# args = ("--no-header --inches --precision=2 --no-show-editor")
|
||||
# gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
# result = gcode.splitlines()[5]
|
||||
# expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
# self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.000 Y30.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[5], "M5")
|
||||
self.assertEqual(gcode.splitlines()[6], "M6 T2 ")
|
||||
self.assertEqual(gcode.splitlines()[7], "G43 H2")
|
||||
self.assertEqual(gcode.splitlines()[8], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment)"
|
||||
self.assertEqual(result, expected)
|
||||
@@ -80,7 +80,6 @@ G2 I-7.500000 J0.000000 X-2.500000 Y5.000000 Z18.000000\
|
||||
G2 I7.500000 J0.000000 X12.500000 Y5.000000 Z18.000000\
|
||||
G0 X5.000000 Y5.000000 Z18.000000G0 Z20.000000"
|
||||
|
||||
|
||||
def test00(self):
|
||||
"""Test Basic Helix Generator Return"""
|
||||
args = _resetArgs()
|
||||
@@ -118,7 +117,8 @@ G0 X5.000000 Y5.000000 Z18.000000G0 Z20.000000"
|
||||
args["tool_diameter"] = 5.0
|
||||
self.assertRaises(ValueError, generator.generate, **args)
|
||||
|
||||
# require tool fit 2: hole diameter not greater than tool diam with zero inner radius
|
||||
# require tool fit 2: hole diameter not greater than tool diam
|
||||
# with zero inner radius
|
||||
args["hole_radius"] = 2.0
|
||||
args["inner_radius"] = 0.0
|
||||
args["tool_diameter"] = 5.0
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -20,117 +21,199 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import PathScripts
|
||||
import PathScripts.post
|
||||
import PathScripts.PathProfileContour
|
||||
import PathScripts.PathJob
|
||||
import PathScripts.PathPost as PathPost
|
||||
import PathScripts.PathToolController
|
||||
import PathScripts.PathUtil
|
||||
import PathScripts.PostUtils as PostUtils
|
||||
import difflib
|
||||
import unittest
|
||||
import Path
|
||||
import os
|
||||
import PathScripts.PathPost as PathPost
|
||||
import unittest
|
||||
|
||||
WriteDebugOutput = False
|
||||
import FreeCAD
|
||||
import Path
|
||||
|
||||
from PathScripts import PathLog
|
||||
from PathScripts import PathPost
|
||||
from PathScripts import PathPreferences
|
||||
from PathScripts import PostUtils
|
||||
|
||||
from PathScripts.PathPostProcessor import PostProcessor
|
||||
|
||||
# If KEEP_DEBUG_OUTPUT is False, remove the gcode file after the test succeeds.
|
||||
# If KEEP_DEBUG_OUTPUT is True or the test fails leave the gcode file behind
|
||||
# so it can be looked at easily.
|
||||
KEEP_DEBUG_OUTPUT = False
|
||||
|
||||
PathPost.LOG_MODULE = PathLog.thisModule()
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathPost.LOG_MODULE)
|
||||
|
||||
|
||||
class PathPostTestCases(unittest.TestCase):
|
||||
class TestPathPost(unittest.TestCase):
|
||||
"""Test some of the output of the postprocessors.
|
||||
|
||||
So far there are three tests each for the linuxcnc
|
||||
and centroid postprocessors.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
testfile = FreeCAD.getHomePath() + "Mod/Path/PathTests/boxtest.fcstd"
|
||||
self.doc = FreeCAD.open(testfile)
|
||||
self.job = FreeCAD.ActiveDocument.getObject("Job")
|
||||
self.postlist = []
|
||||
currTool = None
|
||||
for obj in self.job.Group:
|
||||
if not isinstance(obj.Proxy, PathScripts.PathToolController.ToolController):
|
||||
tc = PathScripts.PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None:
|
||||
if tc.ToolNumber != currTool:
|
||||
self.postlist.append(tc)
|
||||
self.postlist.append(obj)
|
||||
"""Set up the postprocessor tests."""
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
FreeCAD.closeDocument("boxtest")
|
||||
"""Tear down after the postprocessor tests."""
|
||||
pass
|
||||
|
||||
def testLinuxCNC(self):
|
||||
from PathScripts.post import linuxcnc_post as postprocessor
|
||||
|
||||
args = (
|
||||
"--no-header --no-line-numbers --no-comments --no-show-editor --precision=2"
|
||||
#
|
||||
# You can run just this test using:
|
||||
# ./FreeCAD -c -t PathTests.TestPathPost.TestPathPost.test_postprocessors
|
||||
#
|
||||
def test_postprocessors(self):
|
||||
"""Test the postprocessors."""
|
||||
#
|
||||
# The tests are performed in the order they are listed:
|
||||
# one test performed on all of the postprocessors
|
||||
# then the next test on all of the postprocessors, etc.
|
||||
# You can comment out the tuples for tests that you don't want
|
||||
# to use.
|
||||
#
|
||||
tests_to_perform = (
|
||||
# (output_file_id, freecad_document, job_name, postprocessor_arguments,
|
||||
# postprocessor_list)
|
||||
#
|
||||
# test with all of the defaults (metric mode, etc.)
|
||||
("default", "boxtest1", "Job", "--no-show-editor", ()),
|
||||
# test in Imperial mode
|
||||
("imperial", "boxtest1", "Job", "--no-show-editor --inches", ()),
|
||||
# test in metric, G55, M4, the other way around the part
|
||||
("other_way", "boxtest1", "Job001", "--no-show-editor", ()),
|
||||
# test in metric, split by fixtures, G54, G55, G56
|
||||
("split", "boxtest1", "Job002", "--no-show-editor", ()),
|
||||
# test in metric mode without the header
|
||||
("no_header", "boxtest1", "Job", "--no-header --no-show-editor", ()),
|
||||
# test translating G81, G82, and G83 to G00 and G01 commands
|
||||
(
|
||||
"drill_translate",
|
||||
"drill_test1",
|
||||
"Job",
|
||||
"--no-show-editor --translate_drill",
|
||||
("grbl", "refactored_grbl"),
|
||||
),
|
||||
)
|
||||
gcode = postprocessor.export(self.postlist, "gcode.tmp", args)
|
||||
|
||||
referenceFile = (
|
||||
FreeCAD.getHomePath() + "Mod/Path/PathTests/test_linuxcnc_00.ngc"
|
||||
#
|
||||
# The postprocessors to test.
|
||||
# You can comment out any postprocessors that you don't want
|
||||
# to test.
|
||||
#
|
||||
postprocessors_to_test = (
|
||||
"centroid",
|
||||
# "fanuc",
|
||||
"grbl",
|
||||
"linuxcnc",
|
||||
"mach3_mach4",
|
||||
"refactored_centroid",
|
||||
# "refactored_fanuc",
|
||||
"refactored_grbl",
|
||||
"refactored_linuxcnc",
|
||||
"refactored_mach3_mach4",
|
||||
"refactored_test",
|
||||
)
|
||||
with open(referenceFile, "r") as fp:
|
||||
refGCode = fp.read()
|
||||
|
||||
# Use if this test fails in order to have a real good look at the changes
|
||||
if WriteDebugOutput:
|
||||
with open("testLinuxCNC.tmp", "w") as fp:
|
||||
fp.write(gcode)
|
||||
|
||||
if gcode != refGCode:
|
||||
msg = "".join(
|
||||
difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True))
|
||||
)
|
||||
self.fail("linuxcnc output doesn't match: " + msg)
|
||||
|
||||
def testLinuxCNCImperial(self):
|
||||
from PathScripts.post import linuxcnc_post as postprocessor
|
||||
|
||||
args = "--no-header --no-line-numbers --no-comments --no-show-editor --precision=2 --inches"
|
||||
gcode = postprocessor.export(self.postlist, "gcode.tmp", args)
|
||||
|
||||
referenceFile = (
|
||||
FreeCAD.getHomePath() + "Mod/Path/PathTests/test_linuxcnc_10.ngc"
|
||||
)
|
||||
with open(referenceFile, "r") as fp:
|
||||
refGCode = fp.read()
|
||||
|
||||
# Use if this test fails in order to have a real good look at the changes
|
||||
if WriteDebugOutput:
|
||||
with open("testLinuxCNCImplerial.tmp", "w") as fp:
|
||||
fp.write(gcode)
|
||||
|
||||
if gcode != refGCode:
|
||||
msg = "".join(
|
||||
difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True))
|
||||
)
|
||||
self.fail("linuxcnc output doesn't match: " + msg)
|
||||
|
||||
def testCentroid(self):
|
||||
from PathScripts.post import centroid_post as postprocessor
|
||||
|
||||
args = "--no-header --no-line-numbers --no-comments --no-show-editor --axis-precision=2 --feed-precision=2"
|
||||
gcode = postprocessor.export(self.postlist, "gcode.tmp", args)
|
||||
|
||||
referenceFile = (
|
||||
FreeCAD.getHomePath() + "Mod/Path/PathTests/test_centroid_00.ngc"
|
||||
)
|
||||
with open(referenceFile, "r") as fp:
|
||||
refGCode = fp.read()
|
||||
|
||||
# Use if this test fails in order to have a real good look at the changes
|
||||
if WriteDebugOutput:
|
||||
with open("testCentroid.tmp", "w") as fp:
|
||||
fp.write(gcode)
|
||||
|
||||
if gcode != refGCode:
|
||||
msg = "".join(
|
||||
difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True))
|
||||
)
|
||||
self.fail("linuxcnc output doesn't match: " + msg)
|
||||
#
|
||||
# Enough of the path to where the tests are stored so that
|
||||
# they can be found by the python interpreter.
|
||||
#
|
||||
PATHTESTS_LOCATION = "Mod/Path/PathTests"
|
||||
#
|
||||
# The following code tries to re-use an open FreeCAD document
|
||||
# as much as possible. It compares the current document with
|
||||
# the document for the next test. If the names are different
|
||||
# then the current document is closed and the new document is
|
||||
# opened. The final document is closed at the end of the code.
|
||||
#
|
||||
current_document = ""
|
||||
for (
|
||||
output_file_id,
|
||||
freecad_document,
|
||||
job_name,
|
||||
postprocessor_arguments,
|
||||
postprocessor_list,
|
||||
) in tests_to_perform:
|
||||
if current_document != freecad_document:
|
||||
if current_document != "":
|
||||
FreeCAD.closeDocument(current_document)
|
||||
current_document = freecad_document
|
||||
current_document_path = (
|
||||
FreeCAD.getHomePath()
|
||||
+ PATHTESTS_LOCATION
|
||||
+ os.path.sep
|
||||
+ current_document
|
||||
+ ".fcstd"
|
||||
)
|
||||
FreeCAD.open(current_document_path)
|
||||
job = FreeCAD.ActiveDocument.getObject(job_name)
|
||||
# Create the objects to be written by the postprocessor.
|
||||
postlist = PathPost.buildPostList(job)
|
||||
for postprocessor_id in postprocessors_to_test:
|
||||
if postprocessor_list == () or postprocessor_id in postprocessor_list:
|
||||
print(
|
||||
"\nRunning %s test on %s postprocessor:\n"
|
||||
% (output_file_id, postprocessor_id)
|
||||
)
|
||||
processor = PostProcessor.load(postprocessor_id)
|
||||
output_file_path = FreeCAD.getHomePath() + PATHTESTS_LOCATION
|
||||
output_file_pattern = "test_%s_%s" % (
|
||||
postprocessor_id,
|
||||
output_file_id,
|
||||
)
|
||||
output_file_extension = ".ngc"
|
||||
for idx, section in enumerate(postlist):
|
||||
partname = section[0]
|
||||
sublist = section[1]
|
||||
output_filename = PathPost.processFileNameSubstitutions(
|
||||
job,
|
||||
partname,
|
||||
idx,
|
||||
output_file_path,
|
||||
output_file_pattern,
|
||||
output_file_extension,
|
||||
)
|
||||
# print("output file: " + output_filename)
|
||||
file_path, extension = os.path.splitext(output_filename)
|
||||
reference_file_name = "%s%s%s" % (file_path, "_ref", extension)
|
||||
# print("reference file: " + reference_file_name)
|
||||
gcode = processor.export(
|
||||
sublist, output_filename, postprocessor_arguments
|
||||
)
|
||||
if not gcode:
|
||||
print("no gcode")
|
||||
with open(reference_file_name, "r") as fp:
|
||||
reference_gcode = fp.read()
|
||||
if not reference_gcode:
|
||||
print("no reference gcode")
|
||||
# Remove the "Output Time:" line in the header from the
|
||||
# comparison if it is present because it changes with
|
||||
# every test.
|
||||
gcode_lines = [
|
||||
i for i in gcode.splitlines(True) if "Output Time:" not in i
|
||||
]
|
||||
reference_gcode_lines = [
|
||||
i
|
||||
for i in reference_gcode.splitlines(True)
|
||||
if "Output Time:" not in i
|
||||
]
|
||||
if gcode_lines != reference_gcode_lines:
|
||||
msg = "".join(
|
||||
difflib.ndiff(gcode_lines, reference_gcode_lines)
|
||||
)
|
||||
self.fail(
|
||||
os.path.basename(output_filename)
|
||||
+ " output doesn't match:\n"
|
||||
+ msg
|
||||
)
|
||||
if not KEEP_DEBUG_OUTPUT:
|
||||
os.remove(output_filename)
|
||||
if current_document != "":
|
||||
FreeCAD.closeDocument(current_document)
|
||||
|
||||
|
||||
class TestPathPostUtils(unittest.TestCase):
|
||||
def test010(self):
|
||||
|
||||
"""Test the utility functions in the PostUtils.py file."""
|
||||
commands = [
|
||||
Path.Command("G1 X-7.5 Y5.0 Z0.0"),
|
||||
Path.Command("G2 I2.5 J0.0 K0.0 X-5.0 Y7.5 Z0.0"),
|
||||
@@ -194,7 +277,6 @@ class TestBuildPostList(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
def test000(self):
|
||||
|
||||
# check that the test file is structured correctly
|
||||
@@ -323,12 +405,14 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
job = doc.getObjectsByLabel("MainJob")[0]
|
||||
macro = FreeCAD.getUserMacroDir()
|
||||
|
||||
|
||||
def test000(self):
|
||||
# Test basic name generation with empty string
|
||||
FreeCAD.setActiveDocument(self.doc.Label)
|
||||
teststring = ""
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
self.job.SplitOutput = False
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
|
||||
@@ -342,6 +426,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
# Test basic string substitution without splitting
|
||||
teststring = "~/Desktop/%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
self.job.SplitOutput = False
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
|
||||
@@ -349,20 +436,31 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
subpart, objs = outlist[0]
|
||||
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "~/Desktop/MainJob.nc")
|
||||
self.assertEqual(
|
||||
os.path.normpath(filename), os.path.normpath("~/Desktop/MainJob.nc")
|
||||
)
|
||||
|
||||
def test010(self):
|
||||
# Substitute current file path
|
||||
teststring = "%D/testfile.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, f"{self.testfilepath}/testfile.nc")
|
||||
self.assertEqual(
|
||||
os.path.normpath(filename),
|
||||
os.path.normpath(f"{self.testfilepath}/testfile.nc"),
|
||||
)
|
||||
|
||||
def test020(self):
|
||||
teststring = "%d.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
@@ -371,6 +469,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
def test030(self):
|
||||
teststring = "%M/outfile.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
@@ -380,15 +481,24 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
# unused substitution strings should be ignored
|
||||
teststring = "%d%T%t%W%O/testdoc.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, f"{self.testfilename}/testdoc.nc")
|
||||
self.assertEqual(
|
||||
os.path.normpath(filename),
|
||||
os.path.normpath(f"{self.testfilename}/testdoc.nc"),
|
||||
)
|
||||
|
||||
def test050(self):
|
||||
# explicitly using the sequence number should include it where indicated.
|
||||
teststring = "%S-%d.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
@@ -403,6 +513,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
# substitute jobname and use default sequence numbers
|
||||
teststring = "%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "MainJob-0.nc")
|
||||
@@ -413,6 +526,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
# Use Toolnumbers and default sequence numbers
|
||||
teststring = "%T.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
@@ -424,6 +540,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
# Use Tooldescriptions and default sequence numbers
|
||||
teststring = "%t.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
outlist = PathPost.buildPostList(self.job)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
@@ -440,6 +559,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
|
||||
teststring = "%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "MainJob-0.nc")
|
||||
@@ -449,6 +571,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
|
||||
teststring = "%W-%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "G54-MainJob-0.nc")
|
||||
@@ -464,6 +589,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
|
||||
teststring = "%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "MainJob-0.nc")
|
||||
@@ -473,6 +601,9 @@ class TestOutputNameSubstitution(unittest.TestCase):
|
||||
|
||||
teststring = "%O-%j.nc"
|
||||
self.job.PostProcessorOutputFile = teststring
|
||||
PathPreferences.setOutputFileDefaults(
|
||||
teststring, "Append Unique ID on conflict"
|
||||
)
|
||||
subpart, objs = outlist[0]
|
||||
filename = PathPost.resolveFileName(self.job, subpart, 0)
|
||||
self.assertEqual(filename, "OutsideProfile-MainJob-0.nc")
|
||||
|
||||
290
src/Mod/Path/PathTests/TestRefactoredCentroidPost.py
Normal file
290
src/Mod/Path/PathTests/TestRefactoredCentroidPost.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import refactored_centroid_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestRefactoredCentroidPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does not
|
||||
have access to the class `self` reference. This method is able to
|
||||
call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 16)
|
||||
|
||||
# Test without header
|
||||
expected = """G90 G80 G40 G49
|
||||
;Begin preamble
|
||||
G53 G00 G17
|
||||
G21
|
||||
;Begin operation
|
||||
;End operation: testpath
|
||||
;Begin postamble
|
||||
M5
|
||||
M25
|
||||
G49 H0
|
||||
G90 G80 G40 G49
|
||||
M99
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G90 G80 G40 G49
|
||||
G53 G00 G17
|
||||
G21
|
||||
M5
|
||||
M25
|
||||
G49 H0
|
||||
G90 G80 G40 G49
|
||||
M99
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.0000 Y20.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --axis-precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.0000 Y20.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[1]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[3], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --inches --axis-precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.0000 Y30.0000 Z30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.0000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "M6 T2")
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = ";comment"
|
||||
self.assertEqual(result, expected)
|
||||
286
src/Mod/Path/PathTests/TestRefactoredGrblPost.py
Normal file
286
src/Mod/Path/PathTests/TestRefactoredGrblPost.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import refactored_grbl_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestRefactoredGrblPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does not
|
||||
have access to the class `self` reference. This method
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 14)
|
||||
|
||||
# Test without header
|
||||
expected = """(Begin preamble)
|
||||
G17 G90
|
||||
G21
|
||||
(Begin operation: testpath)
|
||||
(Path: testpath)
|
||||
(Finish operation: testpath)
|
||||
(Begin postamble)
|
||||
M5
|
||||
G17 G90
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G90
|
||||
G21
|
||||
M5
|
||||
G17 G90
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --inches --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.000 Y30.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "( M6 T2 )")
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[7], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment)"
|
||||
self.assertEqual(result, expected)
|
||||
288
src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py
Normal file
288
src/Mod/Path/PathTests/TestRefactoredLinuxCNCPost.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import refactored_linuxcnc_post as postprocessor
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestRefactoredLinuxCNCPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does not
|
||||
have access to the class `self` reference. This method
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 14)
|
||||
|
||||
# Test without header
|
||||
expected = """(Begin preamble)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(Begin operation: testpath)
|
||||
(Machine units: mm/min)
|
||||
(Finish operation: testpath)
|
||||
(Begin postamble)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --inches --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.000 Y30.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "M5")
|
||||
self.assertEqual(gcode.splitlines()[7], "M6 T2")
|
||||
self.assertEqual(gcode.splitlines()[8], "G43 H2")
|
||||
self.assertEqual(gcode.splitlines()[9], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[8], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment)"
|
||||
self.assertEqual(result, expected)
|
||||
287
src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py
Normal file
287
src/Mod/Path/PathTests/TestRefactoredMach3Mach4Post.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from importlib import reload
|
||||
|
||||
import FreeCAD
|
||||
|
||||
# import Part
|
||||
import Path
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
from PathScripts.post import refactored_mach3_mach4_post as postprocessor
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class TestRefactoredMach3Mach4Post(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Open existing FreeCAD document with test geometry
|
||||
FreeCAD.newDocument("Unnamed")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does not
|
||||
have access to the class `self` reference. This method is able to
|
||||
call static methods within this same class.
|
||||
"""
|
||||
# Close geometry document without saving
|
||||
FreeCAD.closeDocument(FreeCAD.ActiveDocument.Name)
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
self.doc = FreeCAD.ActiveDocument
|
||||
self.con = FreeCAD.Console
|
||||
self.docobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "testpath")
|
||||
reload(
|
||||
postprocessor
|
||||
) # technical debt. This shouldn't be necessary but here to bypass a bug
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
FreeCAD.ActiveDocument.removeObject("testpath")
|
||||
|
||||
def test000(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
args = "--no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertTrue(len(gcode.splitlines()) == 14)
|
||||
|
||||
# Test without header
|
||||
expected = """(Begin preamble)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(Begin operation: testpath)
|
||||
(Machine: mach3_4, mm/min)
|
||||
(Finish operation: testpath)
|
||||
(Begin postamble)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M2
|
||||
"""
|
||||
|
||||
args = "--no-header --no-comments --no-show-editor"
|
||||
# args = ("--no-header --no-comments --no-show-editor --precision=2")
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test010(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test020(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "N150 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test030(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[0]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test040(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.docobj.Path = Path.Path([])
|
||||
postables = [self.docobj]
|
||||
args = "--no-header --no-comments --postamble='G0 Z50\nM2' --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[-2]
|
||||
self.assertEqual(result, "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-1], "M2")
|
||||
|
||||
def test050(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --inches --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[2], "G20")
|
||||
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
args = "--no-header --inches --precision=2 --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test060(self):
|
||||
"""
|
||||
Test test modal
|
||||
Suppress the command name if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "X10.000 Y30.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test070(self):
|
||||
"""
|
||||
Test axis modal
|
||||
Suppress the axis coordinate if the same as previous
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
c1 = Path.Command("G0 X10 Y30 Z30")
|
||||
|
||||
self.docobj.Path = Path.Path([c, c1])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --axis-modal --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[6]
|
||||
expected = "G0 Y30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test080(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T2")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.docobj.Path = Path.Path([c, c2])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[6], "M5")
|
||||
self.assertEqual(gcode.splitlines()[7], "M6 T2")
|
||||
self.assertEqual(gcode.splitlines()[8], "G43 H2")
|
||||
self.assertEqual(gcode.splitlines()[9], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
args = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
self.assertEqual(gcode.splitlines()[8], "M3 S3000")
|
||||
|
||||
def test090(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.docobj.Path = Path.Path([c])
|
||||
postables = [self.docobj]
|
||||
|
||||
args = "--no-header --no-show-editor"
|
||||
gcode = postprocessor.export(postables, "gcode.tmp", args)
|
||||
result = gcode.splitlines()[5]
|
||||
expected = "(comment)"
|
||||
self.assertEqual(result, expected)
|
||||
1278
src/Mod/Path/PathTests/TestRefactoredTestPost.py
Normal file
1278
src/Mod/Path/PathTests/TestRefactoredTestPost.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/Mod/Path/PathTests/boxtest1.fcstd
Normal file
BIN
src/Mod/Path/PathTests/boxtest1.fcstd
Normal file
Binary file not shown.
BIN
src/Mod/Path/PathTests/drill_test1.FCStd
Normal file
BIN
src/Mod/Path/PathTests/drill_test1.FCStd
Normal file
Binary file not shown.
@@ -1,68 +0,0 @@
|
||||
G17 G90
|
||||
G21
|
||||
(Default_Tool)
|
||||
M6 T2
|
||||
M3 S0.00
|
||||
(Contour)
|
||||
(Uncompensated Tool Path)
|
||||
G0 Z15.00
|
||||
G90
|
||||
G17
|
||||
G0 Z15.00
|
||||
G0 X10.00 Y10.00
|
||||
G0 Z10.00
|
||||
G1 X10.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y0.00 Z9.00
|
||||
G1 X0.00 Y0.00 Z9.00
|
||||
G1 X0.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y0.00 Z8.00
|
||||
G1 X0.00 Y0.00 Z8.00
|
||||
G1 X0.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y0.00 Z7.00
|
||||
G1 X0.00 Y0.00 Z7.00
|
||||
G1 X0.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y0.00 Z6.00
|
||||
G1 X0.00 Y0.00 Z6.00
|
||||
G1 X0.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y0.00 Z5.00
|
||||
G1 X0.00 Y0.00 Z5.00
|
||||
G1 X0.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y0.00 Z4.00
|
||||
G1 X0.00 Y0.00 Z4.00
|
||||
G1 X0.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y0.00 Z3.00
|
||||
G1 X0.00 Y0.00 Z3.00
|
||||
G1 X0.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y0.00 Z2.00
|
||||
G1 X0.00 Y0.00 Z2.00
|
||||
G1 X0.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y0.00 Z1.00
|
||||
G1 X0.00 Y0.00 Z1.00
|
||||
G1 X0.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y10.00 Z0.00
|
||||
G1 X10.00 Y0.00 Z0.00
|
||||
G1 X0.00 Y0.00 Z0.00
|
||||
G1 X0.00 Y10.00 Z0.00
|
||||
G1 X10.00 Y10.00 Z0.00
|
||||
G0 Z15.00
|
||||
M05
|
||||
G00 X-1.0 Y1.0
|
||||
G17 G90
|
||||
M2
|
||||
@@ -38,6 +38,7 @@ from PathTests.TestPathHelixGenerator import TestPathHelixGenerator
|
||||
from PathTests.TestPathLog import TestPathLog
|
||||
from PathTests.TestPathOpTools import TestPathOpTools
|
||||
|
||||
# from PathTests.TestPathPost import TestPathPost
|
||||
from PathTests.TestPathPost import TestPathPostUtils
|
||||
from PathTests.TestPathPost import TestBuildPostList
|
||||
from PathTests.TestPathPost import TestOutputNameSubstitution
|
||||
@@ -58,11 +59,23 @@ from PathTests.TestPathUtil import TestPathUtil
|
||||
from PathTests.TestPathVcarve import TestPathVcarve
|
||||
from PathTests.TestPathVoronoi import TestPathVoronoi
|
||||
|
||||
from PathTests.TestCentroidPost import TestCentroidPost
|
||||
from PathTests.TestGrblPost import TestGrblPost
|
||||
from PathTests.TestLinuxCNCPost import TestLinuxCNCPost
|
||||
from PathTests.TestMach3Mach4Post import TestMach3Mach4Post
|
||||
from PathTests.TestRefactoredCentroidPost import TestRefactoredCentroidPost
|
||||
from PathTests.TestRefactoredGrblPost import TestRefactoredGrblPost
|
||||
from PathTests.TestRefactoredLinuxCNCPost import TestRefactoredLinuxCNCPost
|
||||
from PathTests.TestRefactoredMach3Mach4Post import TestRefactoredMach3Mach4Post
|
||||
from PathTests.TestRefactoredTestPost import TestRefactoredTestPost
|
||||
|
||||
# dummy usage to get flake8 and lgtm quiet
|
||||
False if depthTestCases.__name__ else True
|
||||
False if TestApp.__name__ else True
|
||||
False if TestBuildPostList.__name__ else True
|
||||
False if TestDressupDogbone.__name__ else True
|
||||
False if TestHoldingTags.__name__ else True
|
||||
False if TestOutputNameSubstitution.__name__ else True
|
||||
False if TestPathAdaptive.__name__ else True
|
||||
False if TestPathCore.__name__ else True
|
||||
False if TestPathDeburr.__name__ else True
|
||||
@@ -72,10 +85,7 @@ False if TestPathHelpers.__name__ else True
|
||||
# False if TestPathHelix.__name__ else True
|
||||
False if TestPathLog.__name__ else True
|
||||
False if TestPathOpTools.__name__ else True
|
||||
# False if TestPathPostImport.__name__ else True
|
||||
# False if TestPathPost.__name__ else True
|
||||
False if TestBuildPostList.__name__ else True
|
||||
False if TestOutputNameSubstitution.__name__ else True
|
||||
False if TestPathPostUtils.__name__ else True
|
||||
False if TestPathPreferences.__name__ else True
|
||||
False if TestPathPropertyBag.__name__ else True
|
||||
@@ -94,3 +104,13 @@ False if TestPathVcarve.__name__ else True
|
||||
False if TestPathVoronoi.__name__ else True
|
||||
False if TestPathDrillGenerator.__name__ else True
|
||||
False if TestPathHelixGenerator.__name__ else True
|
||||
|
||||
False if TestCentroidPost.__name__ else True
|
||||
False if TestGrblPost.__name__ else True
|
||||
False if TestLinuxCNCPost.__name__ else True
|
||||
False if TestMach3Mach4Post.__name__ else True
|
||||
False if TestRefactoredCentroidPost.__name__ else True
|
||||
False if TestRefactoredGrblPost.__name__ else True
|
||||
False if TestRefactoredLinuxCNCPost.__name__ else True
|
||||
False if TestRefactoredMach3Mach4Post.__name__ else True
|
||||
False if TestRefactoredTestPost.__name__ else True
|
||||
|
||||
Reference in New Issue
Block a user