added pygcode-norm cleaning and finalizing

This commit is contained in:
Peter Boin 2017-08-23 00:56:41 +10:00
parent bb0ab66cc9
commit 5b3c070e8c
3 changed files with 116 additions and 13 deletions

View File

@ -18,12 +18,14 @@ for pygcode_lib_type in ('installed_lib', 'relative_lib'):
from pygcode import Machine, Mode, Line from pygcode import Machine, Mode, Line
from pygcode import GCodeArcMove, GCodeArcMoveCW, GCodeArcMoveCCW from pygcode import GCodeArcMove, GCodeArcMoveCW, GCodeArcMoveCCW
from pygcode import GCodeCannedCycle from pygcode import GCodeCannedCycle
from pygcode import GCodeRapidMove, GCodeStopSpindle, GCodeAbsoluteDistanceMode
from pygcode import split_gcodes from pygcode import split_gcodes
from pygcode import Comment from pygcode import Comment
from pygcode.transform import linearize_arc, simplify_canned_cycle from pygcode.transform import linearize_arc, simplify_canned_cycle
from pygcode.transform import ArcLinearizeInside, ArcLinearizeOutside, ArcLinearizeMid from pygcode.transform import ArcLinearizeInside, ArcLinearizeOutside, ArcLinearizeMid
from pygcode.gcodes import _subclasses from pygcode.gcodes import _subclasses
from pygcode import utils from pygcode import utils
from pygcode.exceptions import MachineInvalidState
except ImportError: except ImportError:
import sys, os, inspect import sys, os, inspect
@ -65,7 +67,9 @@ def arc_lin_method_type(value):
def word_list_type(value): def word_list_type(value):
""" """
:return: [Word('G73'), Word('G89'), ... ] Convert csv string list into Word instances.
>>> word_list_type("G73,G89") == set([Word('G73'), Word('G89')])
:return: set of Word instances
""" """
canned_code_words = set() canned_code_words = set()
for word_str in re.split(r'\s*,\s*', value): for word_str in re.split(r'\s*,\s*', value):
@ -157,6 +161,34 @@ group.add_argument(
help="List of canned gcodes to expand, (default is '%s')" % DEFAULT_CANNED_CODES, help="List of canned gcodes to expand, (default is '%s')" % DEFAULT_CANNED_CODES,
) )
# Finalize Code
group = parser.add_argument_group(
"Finalise File",
"standardize what's done at the end of a gcode program."
)
group.add_argument(
'--zero_xy', '-zxy', dest="zero_xy",
action='store_const', const=True, default=False,
help="On completion, move straight up to rapid_safety_height, "
"then across to X0 Y0.",
)
group.add_argument(
'--zero_z', '-zz', dest="zero_z",
action='store_const', const=True, default=False,
help="On completion, move down to Z0 (done after zero_xy, if set)",
)
group.add_argument(
'--rapid_safety_height', '-rsh', dest="rapid_safety_height",
type=float, default=None,
help="Z value to move to before traversing workpeice (if not set, max "
"value will be attempted)",
)
group.add_argument(
'--spindle_off', '-so', dest="spindle_off",
action='store_const', const=True, default=False,
help="On completion, turn spindle off",
)
# Removing non-functional content # Removing non-functional content
group = parser.add_argument_group( group = parser.add_argument_group(
"Removing Content", "Removing Content",
@ -183,7 +215,11 @@ group.add_argument(
help="remove gcode (and it's parameters) with words in the given list " help="remove gcode (and it's parameters) with words in the given list "
"(eg: M6,G43) (note: only works for modal params with --full)", "(eg: M6,G43) (note: only works for modal params with --full)",
) )
group.add_argument(
'--rm_invalid_modal', '-rmim', dest='rm_invalid_modal',
action='store_const', const=True, default=False,
help="simply remove everything that isn't understood... not the safest strategy",
)
# --- Parse Arguments # --- Parse Arguments
args = parser.parse_args() args = parser.parse_args()
@ -195,6 +231,7 @@ class MyMode(Mode):
class MyMachine(Machine): class MyMachine(Machine):
MODE_CLASS = MyMode MODE_CLASS = MyMode
ignore_invalid_modal = args.rm_invalid_modal
machine = MyMachine() machine = MyMachine()
@ -276,6 +313,9 @@ def split_and_process(gcode_list, gcode_class, comment):
for line_str in args.infile.readlines(): for line_str in args.infile.readlines():
line = Line(line_str) line = Line(line_str)
if args.rm_invalid_modal:
machine.clean_block(line.block)
# Effective G-Codes: # Effective G-Codes:
# fills in missing motion modal gcodes (using machine's current motion mode). # fills in missing motion modal gcodes (using machine's current motion mode).
effective_gcodes = machine.block_modal_gcodes(line.block) effective_gcodes = machine.block_modal_gcodes(line.block)
@ -315,3 +355,23 @@ for line_str in args.infile.readlines():
else: else:
write(line.block.gcodes, modal_params=line.block.modal_params, comment=line.comment, macro=line.macro) write(line.block.gcodes, modal_params=line.block.modal_params, comment=line.comment, macro=line.macro)
machine.process_block(line.block) machine.process_block(line.block)
# Finalizing Motion & Spindle
if any([args.spindle_off, args.zero_xy, args.zero_z]):
write([], comment=Comment("pygcode-norm: finalizing"))
if any([args.zero_xy, args.zero_z]) and not(isinstance(machine.mode.distance, GCodeAbsoluteDistanceMode)):
write([GCodeAbsoluteDistanceMode()])
if args.spindle_off:
write([GCodeStopSpindle()], comment=Comment("spindle off"))
if args.zero_xy:
rapid_safety_height = args.rapid_safety_height
if rapid_safety_height is None:
rapid_safety_height = machine.abs2work(machine.abs_range_max).Z
if args.zero_xy:
write([GCodeRapidMove(Z=rapid_safety_height)], comment=Comment("move to safe height"))
write([GCodeRapidMove(X=0, Y=0)], comment=Comment("move to planar origin"))
if args.zero_z:
write([GCodeRapidMove(Z=0)], comment=Comment("move to zero height"))

View File

@ -6,7 +6,7 @@
# 1.x - Development Status :: 5 - Production/Stable # 1.x - Development Status :: 5 - Production/Stable
# <any above>.y - developments on that version (pre-release) # <any above>.y - developments on that version (pre-release)
# <any above>*.dev* - development release (intended purely to test deployment) # <any above>*.dev* - development release (intended purely to test deployment)
__version__ = "0.2.0" __version__ = "0.2.1"
__title__ = "pygcode" __title__ = "pygcode"
__description__ = "Basic g-code parser, interpreter, and encoder library." __description__ = "Basic g-code parser, interpreter, and encoder library."
@ -69,6 +69,7 @@ __all__ = [
'GCodeCutterCompRight', 'GCodeCutterCompRight',
'GCodeCutterRadiusComp', 'GCodeCutterRadiusComp',
'GCodeCutterRadiusCompOff', 'GCodeCutterRadiusCompOff',
'GCodeDefinition',
'GCodeDigitalOutput', 'GCodeDigitalOutput',
'GCodeDigitalOutputOff', 'GCodeDigitalOutputOff',
'GCodeDigitalOutputOffSyncd', 'GCodeDigitalOutputOffSyncd',
@ -98,6 +99,7 @@ __all__ = [
'GCodeInverseTimeMode', 'GCodeInverseTimeMode',
'GCodeLatheDiameterMode', 'GCodeLatheDiameterMode',
'GCodeLatheRadiusMode', 'GCodeLatheRadiusMode',
'GCodeLineNumber',
'GCodeLinearMove', 'GCodeLinearMove',
'GCodeMotion', 'GCodeMotion',
'GCodeMoveInMachineCoords', 'GCodeMoveInMachineCoords',
@ -113,6 +115,7 @@ __all__ = [
'GCodePauseProgramOptional', 'GCodePauseProgramOptional',
'GCodePlaneSelect', 'GCodePlaneSelect',
'GCodeProgramControl', 'GCodeProgramControl',
'GCodeProgramName',
'GCodeQuadraticSpline', 'GCodeQuadraticSpline',
'GCodeRapidMove', 'GCodeRapidMove',
'GCodeResetCoordSystemOffset', 'GCodeResetCoordSystemOffset',
@ -190,7 +193,6 @@ from .words import (
# GCode # GCode
from .gcodes import ( from .gcodes import (
words2gcodes, text2gcodes, split_gcodes, words2gcodes, text2gcodes, split_gcodes,
# $ python -c "from pygcode.gcodes import _gcode_class_infostr as x; print(x(prefix=' # '))" # $ python -c "from pygcode.gcodes import _gcode_class_infostr as x; print(x(prefix=' # '))"
# - GCode: # - GCode:
# - GCodeCannedCycle: # - GCodeCannedCycle:
@ -214,6 +216,9 @@ from .gcodes import (
# G40 - GCodeCutterRadiusCompOff: G40: Cutter Radius Compensation Off # G40 - GCodeCutterRadiusCompOff: G40: Cutter Radius Compensation Off
# G41.1 - GCodeDynamicCutterCompLeft: G41.1: Dynamic Cutter Radius Compensation (left) # G41.1 - GCodeDynamicCutterCompLeft: G41.1: Dynamic Cutter Radius Compensation (left)
# G42.1 - GCodeDynamicCutterCompRight: G42.1: Dynamic Cutter Radius Compensation (right) # G42.1 - GCodeDynamicCutterCompRight: G42.1: Dynamic Cutter Radius Compensation (right)
# - GCodeDefinition:
# - GCodeLineNumber: N: Line Number
# - GCodeProgramName: O: Program Name
# - GCodeDistanceMode: # - GCodeDistanceMode:
# G90.1 - GCodeAbsoluteArcDistanceMode: G90.1: Absolute Distance Mode for Arc IJK Parameters # G90.1 - GCodeAbsoluteArcDistanceMode: G90.1: Absolute Distance Mode for Arc IJK Parameters
# G90 - GCodeAbsoluteDistanceMode: G90: Absolute Distance Mode # G90 - GCodeAbsoluteDistanceMode: G90: Absolute Distance Mode
@ -346,6 +351,7 @@ from .gcodes import (
GCodeCutterCompRight, GCodeCutterCompRight,
GCodeCutterRadiusComp, GCodeCutterRadiusComp,
GCodeCutterRadiusCompOff, GCodeCutterRadiusCompOff,
GCodeDefinition,
GCodeDigitalOutput, GCodeDigitalOutput,
GCodeDigitalOutputOff, GCodeDigitalOutputOff,
GCodeDigitalOutputOffSyncd, GCodeDigitalOutputOffSyncd,
@ -375,6 +381,7 @@ from .gcodes import (
GCodeInverseTimeMode, GCodeInverseTimeMode,
GCodeLatheDiameterMode, GCodeLatheDiameterMode,
GCodeLatheRadiusMode, GCodeLatheRadiusMode,
GCodeLineNumber,
GCodeLinearMove, GCodeLinearMove,
GCodeMotion, GCodeMotion,
GCodeMoveInMachineCoords, GCodeMoveInMachineCoords,
@ -390,6 +397,7 @@ from .gcodes import (
GCodePauseProgramOptional, GCodePauseProgramOptional,
GCodePlaneSelect, GCodePlaneSelect,
GCodeProgramControl, GCodeProgramControl,
GCodeProgramName,
GCodeQuadraticSpline, GCodeQuadraticSpline,
GCodeRapidMove, GCodeRapidMove,
GCodeResetCoordSystemOffset, GCodeResetCoordSystemOffset,

View File

@ -5,6 +5,7 @@ from collections import defaultdict
from .gcodes import ( from .gcodes import (
MODAL_GROUP_MAP, GCode, MODAL_GROUP_MAP, GCode,
# Modal GCodes # Modal GCodes
GCodeMotion,
GCodeIncrementalDistanceMode, GCodeIncrementalDistanceMode,
GCodeUseInches, GCodeUseMillimeters, GCodeUseInches, GCodeUseMillimeters,
# Utilities # Utilities
@ -369,6 +370,7 @@ class Machine(object):
STATE_CLASS = State STATE_CLASS = State
axes = set('XYZ') axes = set('XYZ')
ignore_invalid_modal = False
def __init__(self): def __init__(self):
self.mode = self.MODE_CLASS() self.mode = self.MODE_CLASS()
@ -407,12 +409,15 @@ class Machine(object):
# TODO: convert coord systems between inches/mm, G20/G21 respectively # TODO: convert coord systems between inches/mm, G20/G21 respectively
def modal_gcode(self, modal_params): def modal_gcode(self, modal_params):
"""
:param ignore_unassigned: if truthy, unassigned parameters will be ignored
"""
if not modal_params: if not modal_params:
return None return None
if self.mode.motion is None: if self.mode.motion is None:
unasigned_words = modal_params (modal_gcodes, unasigned_words) = ([], modal_params)
# forces exception to be raised in next step # forces exception to be raised in next step
else: else:
params = copy(self.mode.motion.params) # dict params = copy(self.mode.motion.params) # dict
@ -421,9 +426,11 @@ class Machine(object):
[self.mode.motion.word] + list(params.values()) [self.mode.motion.word] + list(params.values())
) )
if unasigned_words: if unasigned_words and (not self.ignore_invalid_modal):
# Can't process with unknown words on the same line... # Can't process with unknown words on the same line...
# raising: MachineInvalidState # 2 choices:
# - raise MachineInvalidState
# - or remove unassigned parameters from line
plausable_codes = [w for w in unasigned_words if w.letter in set('GM')] plausable_codes = [w for w in unasigned_words if w.letter in set('GM')]
if plausable_codes: if plausable_codes:
# words in list are probably valid, but unsupported, G-Codes # words in list are probably valid, but unsupported, G-Codes
@ -456,6 +463,25 @@ class Machine(object):
gcodes.append(modal_gcode) gcodes.append(modal_gcode)
return sorted(gcodes) return sorted(gcodes)
def clean_block(self, block):
"""
Remove invalid modal parameters from given block
:param block: Block instance to clean
"""
assert isinstance(block, Block), "invalid parameter"
if self.mode.motion is None:
# no modal motion, modal parameters are all invalid
block.modal_params = []
elif any(True for g in block.gcodes if g.modal_group == GCodeMotion.modal_group):
# block defines new motion, modal motion is irrelevant
block.modal_params = []
else:
(modal_gcodes, unasigned_words) = words2gcodes(
[self.mode.motion.word] + list(block.modal_params)
)
for w in unasigned_words:
block.modal_params.remove(w)
def process_gcodes(self, *gcode_list, **kwargs): def process_gcodes(self, *gcode_list, **kwargs):
""" """
Process gcodes Process gcodes
@ -491,19 +517,28 @@ class Machine(object):
line = Line(block_str) line = Line(block_str)
self.process_block(line.block) self.process_block(line.block)
# Position conversions (considering offsets)
def abs2work(self, abs_pos):
assert isinstance(abs_pos, Position), "bad abs_pos type"
coord_sys_offset = getattr(self.state.coord_sys, 'offset', Position(axes=self.axes))
temp_offset = self.state.offset
return (abs_pos - coord_sys_offset) - temp_offset
def work2abs(self, work_pos):
assert isinstance(work_pos, Position), "bad work_pos type"
coord_sys_offset = getattr(self.state.coord_sys, 'offset', Position(axes=self.axes))
temp_offset = self.state.offset
return (work_pos + temp_offset + coord_sys_offset)
@property @property
def pos(self): def pos(self):
"""Return current position in current coordinate system""" """Return current position in current coordinate system"""
coord_sys_offset = getattr(self.state.coord_sys, 'offset', Position(axes=self.axes)) return self.abs2work(self.abs_pos)
temp_offset = self.state.offset
return (self.abs_pos - coord_sys_offset) - temp_offset
@pos.setter @pos.setter
def pos(self, value): def pos(self, value):
"""Set absolute position given current position and coordinate system""" """Set absolute position given current position and coordinate system"""
coord_sys_offset = getattr(self.state.coord_sys, 'offset', Position(axes=self.axes)) self.abs_pos = self.work2abs(value)
temp_offset = self.state.offset
self.abs_pos = (value + temp_offset) + coord_sys_offset
self._update_abs_range(self.abs_pos) self._update_abs_range(self.abs_pos)
def _update_abs_range(self, pos): def _update_abs_range(self, pos):