gcode: Create new wrapper class for gcode command parameters

Instead of passing a dictionary to the command handlers, create a
wrapper class and pass that class to the command handlers.  This can
simplify the command handler code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2020-04-22 12:40:32 -04:00
parent 1eb2d4da90
commit ddb8311890
7 changed files with 103 additions and 60 deletions

View File

@ -54,7 +54,8 @@ class ArcSupport:
g1_params['E'] = asE / len(coords) g1_params['E'] = asE / len(coords)
if asF is not None: if asF is not None:
g1_params['F'] = asF g1_params['F'] = asF
self.gcode.cmd_G1(g1_params) g1_gcmd = self.gcode.create_gcode_command("G1", "G1", g1_params)
self.gcode.cmd_G1(g1_gcmd)
# function planArc() originates from marlin plan_arc() # function planArc() originates from marlin plan_arc()
# https://github.com/MarlinFirmware/Marlin # https://github.com/MarlinFirmware/Marlin

View File

@ -154,10 +154,10 @@ class GCodeMacro:
value,)) value,))
self.variables[variable] = literal self.variables[variable] = literal
cmd_desc = "G-Code macro" cmd_desc = "G-Code macro"
def cmd(self, params): def cmd(self, gcmd):
if self.in_script: if self.in_script:
raise self.gcode.error( raise gcmd.error("Macro %s called recursively" % (self.alias,))
"Macro %s called recursively" % (self.alias,)) params = gcmd.get_command_parameters()
kwparams = dict(self.kwparams) kwparams = dict(self.kwparams)
kwparams.update(params) kwparams.update(params)
kwparams.update(self.variables) kwparams.update(self.variables)

View File

@ -54,7 +54,7 @@ class HomingOverride:
self.gcode.reset_last_position() self.gcode.reset_last_position()
# Perform homing # Perform homing
kwparams = { 'printer': self.template.create_status_wrapper() } kwparams = { 'printer': self.template.create_status_wrapper() }
kwparams['params'] = params kwparams['params'] = params.get_command_parameters()
try: try:
self.in_script = True self.in_script = True
self.template.run_gcode_from_command(kwparams) self.template.run_gcode_from_command(kwparams)

View File

@ -57,7 +57,7 @@ class PauseResume:
self.pause_command_sent = False self.pause_command_sent = False
if self.sd_paused: if self.sd_paused:
# Printing from virtual sd, run pause command # Printing from virtual sd, run pause command
self.v_sd.cmd_M24({}) self.v_sd.cmd_M24(gcmd)
else: else:
self.gcode.respond_info("action:resumed") self.gcode.respond_info("action:resumed")
def cmd_CLEAR_PAUSE(self, params): def cmd_CLEAR_PAUSE(self, params):

View File

@ -410,7 +410,8 @@ class ProbePointsHelper:
def _manual_probe_start(self): def _manual_probe_start(self):
done = self._move_next() done = self._move_next()
if not done: if not done:
manual_probe.ManualProbeHelper(self.printer, {}, gcmd = self.gcode.create_gcode_command("", "", {})
manual_probe.ManualProbeHelper(self.printer, gcmd,
self._manual_probe_finalize) self._manual_probe_finalize)
def _manual_probe_finalize(self, kin_pos): def _manual_probe_finalize(self, kin_pos):
if kin_pos is None: if kin_pos is None:

View File

@ -63,7 +63,8 @@ class SafeZHoming:
if need_y: if need_y:
new_params['Y'] = '0' new_params['Y'] = '0'
if new_params: if new_params:
self.prev_G28(new_params) g28_gcmd = self.gcode.create_gcode_command("G28", "G28", new_params)
self.prev_G28(g28_gcmd)
# Home Z axis if necessary # Home Z axis if necessary
if need_z: if need_z:
# Move to safe XY homing position # Move to safe XY homing position
@ -75,7 +76,8 @@ class SafeZHoming:
toolhead.move(pos, self.speed) toolhead.move(pos, self.speed)
self.gcode.reset_last_position() self.gcode.reset_last_position()
# Home Z # Home Z
self.prev_G28({'Z': '0'}) g28_gcmd = self.gcode.create_gcode_command("G28", "G28", {'Z': '0'})
self.prev_G28(g28_gcmd)
# Perform Z Hop again for pressure-based probes # Perform Z Hop again for pressure-based probes
pos = toolhead.get_position() pos = toolhead.get_position()
if self.z_hop: if self.z_hop:

View File

@ -1,11 +1,63 @@
# Parse gcode commands # Parse gcode commands
# #
# Copyright (C) 2016-2019 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections, shlex import os, re, logging, collections, shlex
import homing import homing
class GCodeCommand:
error = homing.CommandError
def __init__(self, gcode, command, commandline, params):
self._command = command
self._commandline = commandline
self._params = params
# Method wrappers
self.respond_info = gcode.respond_info
self.respond_raw = gcode.respond_raw
self.__contains__ = self._params.__contains__
self.__getitem__ = self._params.__getitem__
def get_command(self):
return self._command
def get_commandline(self):
return self._commandline
def get_command_parameters(self):
return self._params
# Parameter parsing helpers
class sentinel: pass
def get(self, name, default=sentinel, parser=str, minval=None, maxval=None,
above=None, below=None):
value = self._params.get(name)
if value is None:
if default is self.sentinel:
raise self.error("Error on '%s': missing %s"
% (self._commandline, name))
return default
try:
value = parser(value)
except:
raise self.error("Error on '%s': unable to parse %s"
% (self._commandline, value))
if minval is not None and value < minval:
raise self.error("Error on '%s': %s must have minimum of %s"
% (self._commandline, name, minval))
if maxval is not None and value > maxval:
raise self.error("Error on '%s': %s must have maximum of %s"
% (self._commandline, name, maxval))
if above is not None and value <= above:
raise self.error("Error on '%s': %s must be above %s"
% (self._commandline, name, above))
if below is not None and value >= below:
raise self.error("Error on '%s': %s must be below %s"
% (self._commandline, name, below))
return value
def get_int(self, name, default=sentinel, minval=None, maxval=None):
return self.get(name, default, parser=int, minval=minval, maxval=maxval)
def get_float(self, name, default=sentinel, minval=None, maxval=None,
above=None, below=None):
return self.get(name, default, parser=float, minval=minval,
maxval=maxval, above=above, below=below)
# Parse and handle G-Code commands # Parse and handle G-Code commands
class GCodeParser: class GCodeParser:
error = homing.CommandError error = homing.CommandError
@ -214,23 +266,26 @@ class GCodeParser:
cpos = line.find(';') cpos = line.find(';')
if cpos >= 0: if cpos >= 0:
line = line[:cpos] line = line[:cpos]
# Break command into parts # Break line into parts and determine command
parts = self.args_r.split(line.upper())[1:] parts = self.args_r.split(line.upper())
params = { parts[i]: parts[i+1].strip() numparts = len(parts)
for i in range(0, len(parts), 2) } cmd = ""
params['#original'] = origline if numparts >= 3 and parts[1] != 'N':
if parts and parts[0] == 'N': cmd = parts[1] + parts[2].strip()
elif numparts >= 5 and parts[1] == 'N':
# Skip line number at start of command # Skip line number at start of command
del parts[:2] cmd = parts[3] + parts[4].strip()
if not parts: # Build gcode "params" dictionary
# Treat empty line as empty command params = { parts[i]: parts[i+1].strip()
parts = ['', ''] for i in range(1, numparts, 2) }
params['#command'] = cmd = parts[0] + parts[1].strip() params['#original'] = origline
params['#command'] = cmd
gcmd = GCodeCommand(self, cmd, origline, params)
# Invoke handler for command # Invoke handler for command
self.need_ack = need_ack self.need_ack = need_ack
handler = self.gcode_handlers.get(cmd, self.cmd_default) handler = self.gcode_handlers.get(cmd, self.cmd_default)
try: try:
handler(params) handler(gcmd)
except self.error as e: except self.error as e:
self._respond_error(str(e)) self._respond_error(str(e))
self.reset_last_position() self.reset_last_position()
@ -273,7 +328,7 @@ class GCodeParser:
# Check for M112 out-of-order # Check for M112 out-of-order
for line in lines: for line in lines:
if self.m112_r.match(line) is not None: if self.m112_r.match(line) is not None:
self.cmd_M112({}) self.cmd_M112(None)
if self.is_processing_data: if self.is_processing_data:
if len(pending_commands) >= 20: if len(pending_commands) >= 20:
# Stop reading input # Stop reading input
@ -302,6 +357,8 @@ class GCodeParser:
self._process_commands(script.split('\n'), need_ack=False) self._process_commands(script.split('\n'), need_ack=False)
def get_mutex(self): def get_mutex(self):
return self.mutex return self.mutex
def create_gcode_command(self, command, commandline, params):
return GCodeCommand(self, command, commandline, params)
# Response handling # Response handling
def ack(self, msg=None): def ack(self, msg=None):
if not self.need_ack: if not self.need_ack:
@ -340,56 +397,38 @@ class GCodeParser:
def _respond_state(self, state): def _respond_state(self, state):
self.respond_info("Klipper state: %s" % (state,), log=False) self.respond_info("Klipper state: %s" % (state,), log=False)
# Parameter parsing helpers # Parameter parsing helpers
class sentinel: pass def get_str(self, name, gcmd, default=GCodeCommand.sentinel, parser=str,
def get_str(self, name, params, default=sentinel, parser=str,
minval=None, maxval=None, above=None, below=None): minval=None, maxval=None, above=None, below=None):
if name not in params: return gcmd.get(name, default, parser, minval, maxval, above, below)
if default is self.sentinel: def get_int(self, name, gcmd, default=GCodeCommand.sentinel,
raise self.error("Error on '%s': missing %s" % ( minval=None, maxval=None):
params['#original'], name)) return gcmd.get_int(name, default, minval=minval, maxval=maxval)
return default def get_float(self, name, gcmd, default=GCodeCommand.sentinel,
try:
value = parser(params[name])
except:
raise self.error("Error on '%s': unable to parse %s" % (
params['#original'], params[name]))
if minval is not None and value < minval:
raise self.error("Error on '%s': %s must have minimum of %s" % (
params['#original'], name, minval))
if maxval is not None and value > maxval:
raise self.error("Error on '%s': %s must have maximum of %s" % (
params['#original'], name, maxval))
if above is not None and value <= above:
raise self.error("Error on '%s': %s must be above %s" % (
params['#original'], name, above))
if below is not None and value >= below:
raise self.error("Error on '%s': %s must be below %s" % (
params['#original'], name, below))
return value
def get_int(self, name, params, default=sentinel, minval=None, maxval=None):
return self.get_str(name, params, default, parser=int,
minval=minval, maxval=maxval)
def get_float(self, name, params, default=sentinel,
minval=None, maxval=None, above=None, below=None): minval=None, maxval=None, above=None, below=None):
return self.get_str(name, params, default, parser=float, minval=minval, return gcmd.get_float(name, default, minval=minval, maxval=maxval,
maxval=maxval, above=above, below=below) above=above, below=below)
extended_r = re.compile( extended_r = re.compile(
r'^\s*(?:N[0-9]+\s*)?' r'^\s*(?:N[0-9]+\s*)?'
r'(?P<cmd>[a-zA-Z_][a-zA-Z0-9_]+)(?:\s+|$)' r'(?P<cmd>[a-zA-Z_][a-zA-Z0-9_]+)(?:\s+|$)'
r'(?P<args>[^#*;]*?)' r'(?P<args>[^#*;]*?)'
r'\s*(?:[#*;].*)?$') r'\s*(?:[#*;].*)?$')
def _get_extended_params(self, params): def _get_extended_params(self, gcmd):
m = self.extended_r.match(params['#original']) m = self.extended_r.match(gcmd.get_commandline())
if m is None: if m is None:
raise self.error("Malformed command '%s'" % (params['#original'],)) raise self.error("Malformed command '%s'"
% (gcmd.get_commandline(),))
eargs = m.group('args') eargs = m.group('args')
try: try:
eparams = [earg.split('=', 1) for earg in shlex.split(eargs)] eparams = [earg.split('=', 1) for earg in shlex.split(eargs)]
eparams = { k.upper(): v for k, v in eparams } eparams = { k.upper(): v for k, v in eparams }
eparams.update({k: params[k] for k in params if k.startswith('#')}) eparams['#original'] = gcmd._params['#original']
return eparams eparams['#command'] = gcmd._params['#command']
gcmd._params.clear()
gcmd._params.update(eparams)
return gcmd
except ValueError as e: except ValueError as e:
raise self.error("Malformed command '%s'" % (params['#original'],)) raise self.error("Malformed command '%s'"
% (gcmd.get_commandline(),))
# G-Code special command handlers # G-Code special command handlers
def cmd_default(self, params): def cmd_default(self, params):
cmd = params.get('#command') cmd = params.get('#command')