diff options
Diffstat (limited to 'klippy/extras/gcode_macro.py')
-rw-r--r-- | klippy/extras/gcode_macro.py | 123 |
1 files changed, 80 insertions, 43 deletions
diff --git a/klippy/extras/gcode_macro.py b/klippy/extras/gcode_macro.py index 3aa5087b..ddcdc7c9 100644 --- a/klippy/extras/gcode_macro.py +++ b/klippy/extras/gcode_macro.py @@ -11,55 +11,67 @@ import jinja2 # Template handling ###################################################################### + # Wrapper for access to printer object get_status() methods class GetStatusWrapper: def __init__(self, printer, eventtime=None): self.printer = printer self.eventtime = eventtime self.cache = {} + def __getitem__(self, val): sval = str(val).strip() if sval in self.cache: return self.cache[sval] po = self.printer.lookup_object(sval, None) - if po is None or not hasattr(po, 'get_status'): + if po is None or not hasattr(po, "get_status"): raise KeyError(val) if self.eventtime is None: self.eventtime = self.printer.get_reactor().monotonic() self.cache[sval] = res = copy.deepcopy(po.get_status(self.eventtime)) return res + def __contains__(self, val): try: self.__getitem__(val) except KeyError as e: return False return True + def __iter__(self): for name, obj in self.printer.lookup_objects(): if self.__contains__(name): yield name + # Wrapper around a Jinja2 template class TemplateWrapper: def __init__(self, printer, env, name, script): self.printer = printer self.name = name - self.gcode = self.printer.lookup_object('gcode') - gcode_macro = self.printer.lookup_object('gcode_macro') + self.gcode = self.printer.lookup_object("gcode") + gcode_macro = self.printer.lookup_object("gcode_macro") self.create_template_context = gcode_macro.create_template_context try: self.template = env.from_string(script) except jinja2.exceptions.TemplateSyntaxError as e: lines = script.splitlines() msg = "Error loading template '%s'\nline %s: %s # %s" % ( - name, e.lineno, lines[e.lineno-1], e.message) + name, + e.lineno, + lines[e.lineno - 1], + e.message, + ) logging.exception(msg) raise self.gcode.error(msg) except Exception as e: msg = "Error loading template '%s': %s" % ( - name, traceback.format_exception_only(type(e), e)[-1]) + name, + traceback.format_exception_only(type(e), e)[-1], + ) logging.exception(msg) raise printer.config_error(msg) + def render(self, context=None): if context is None: context = self.create_template_context() @@ -67,17 +79,22 @@ class TemplateWrapper: return str(self.template.render(context)) except Exception as e: msg = "Error evaluating '%s': %s" % ( - self.name, traceback.format_exception_only(type(e), e)[-1]) + self.name, + traceback.format_exception_only(type(e), e)[-1], + ) logging.exception(msg) raise self.gcode.error(msg) + def run_gcode_from_command(self, context=None): self.gcode.run_script_from_command(self.render(context)) + # Main gcode macro template tracking class PrinterGCodeMacro: def __init__(self, config): self.printer = config.get_printer() - self.env = jinja2.Environment('{%', '%}', '{', '}') + self.env = jinja2.Environment("{%", "%}", "{", "}") + def load_template(self, config, option, default=None): name = "%s:%s" % (config.get_name(), option) if default is None: @@ -85,30 +102,36 @@ class PrinterGCodeMacro: else: script = config.get(option, default) return TemplateWrapper(self.printer, self.env, name, script) + def _action_emergency_stop(self, msg="action_emergency_stop"): self.printer.invoke_shutdown("Shutdown due to %s" % (msg,)) return "" + def _action_respond_info(self, msg): - self.printer.lookup_object('gcode').respond_info(msg) + self.printer.lookup_object("gcode").respond_info(msg) return "" + def _action_raise_error(self, msg): raise self.printer.command_error(msg) + def _action_call_remote_method(self, method, **kwargs): - webhooks = self.printer.lookup_object('webhooks') + webhooks = self.printer.lookup_object("webhooks") try: webhooks.call_remote_method(method, **kwargs) except self.printer.command_error: logging.exception("Remote Call Error") return "" + def create_template_context(self, eventtime=None): return { - 'printer': GetStatusWrapper(self.printer, eventtime), - 'action_emergency_stop': self._action_emergency_stop, - 'action_respond_info': self._action_respond_info, - 'action_raise_error': self._action_raise_error, - 'action_call_remote_method': self._action_call_remote_method, + "printer": GetStatusWrapper(self.printer, eventtime), + "action_emergency_stop": self._action_emergency_stop, + "action_respond_info": self._action_respond_info, + "action_raise_error": self._action_raise_error, + "action_call_remote_method": self._action_call_remote_method, } + def load_config(config): return PrinterGCodeMacro(config) @@ -117,84 +140,98 @@ def load_config(config): # GCode macro ###################################################################### + class GCodeMacro: def __init__(self, config): if len(config.get_name().split()) > 2: raise config.error( - "Name of section '%s' contains illegal whitespace" - % (config.get_name())) + "Name of section '%s' contains illegal whitespace" % (config.get_name()) + ) name = config.get_name().split()[1] self.alias = name.upper() self.printer = printer = config.get_printer() - gcode_macro = printer.load_object(config, 'gcode_macro') - self.template = gcode_macro.load_template(config, 'gcode') - self.gcode = printer.lookup_object('gcode') + gcode_macro = printer.load_object(config, "gcode_macro") + self.template = gcode_macro.load_template(config, "gcode") + self.gcode = printer.lookup_object("gcode") self.rename_existing = config.get("rename_existing", None) self.cmd_desc = config.get("description", "G-Code macro") if self.rename_existing is not None: - if (self.gcode.is_traditional_gcode(self.alias) - != self.gcode.is_traditional_gcode(self.rename_existing)): + if self.gcode.is_traditional_gcode( + self.alias + ) != self.gcode.is_traditional_gcode(self.rename_existing): raise config.error( "G-Code macro rename of different types ('%s' vs '%s')" - % (self.alias, self.rename_existing)) - printer.register_event_handler("klippy:connect", - self.handle_connect) + % (self.alias, self.rename_existing) + ) + printer.register_event_handler("klippy:connect", self.handle_connect) else: - self.gcode.register_command(self.alias, self.cmd, - desc=self.cmd_desc) - self.gcode.register_mux_command("SET_GCODE_VARIABLE", "MACRO", - name, self.cmd_SET_GCODE_VARIABLE, - desc=self.cmd_SET_GCODE_VARIABLE_help) + self.gcode.register_command(self.alias, self.cmd, desc=self.cmd_desc) + self.gcode.register_mux_command( + "SET_GCODE_VARIABLE", + "MACRO", + name, + self.cmd_SET_GCODE_VARIABLE, + desc=self.cmd_SET_GCODE_VARIABLE_help, + ) self.in_script = False self.variables = {} - prefix = 'variable_' + prefix = "variable_" for option in config.get_prefix_options(prefix): try: literal = ast.literal_eval(config.get(option)) - json.dumps(literal, separators=(',', ':')) - self.variables[option[len(prefix):]] = literal + json.dumps(literal, separators=(",", ":")) + self.variables[option[len(prefix) :]] = literal except (SyntaxError, TypeError, ValueError) as e: raise config.error( - "Option '%s' in section '%s' is not a valid literal: %s" % ( - option, config.get_name(), e)) + "Option '%s' in section '%s' is not a valid literal: %s" + % (option, config.get_name(), e) + ) + def handle_connect(self): prev_cmd = self.gcode.register_command(self.alias, None) if prev_cmd is None: raise self.printer.config_error( - "Existing command '%s' not found in gcode_macro rename" - % (self.alias,)) + "Existing command '%s' not found in gcode_macro rename" % (self.alias,) + ) pdesc = "Renamed builtin of '%s'" % (self.alias,) self.gcode.register_command(self.rename_existing, prev_cmd, desc=pdesc) self.gcode.register_command(self.alias, self.cmd, desc=self.cmd_desc) + def get_status(self, eventtime): return self.variables + cmd_SET_GCODE_VARIABLE_help = "Set the value of a G-Code macro variable" + def cmd_SET_GCODE_VARIABLE(self, gcmd): - variable = gcmd.get('VARIABLE') - value = gcmd.get('VALUE') + variable = gcmd.get("VARIABLE") + value = gcmd.get("VALUE") if variable not in self.variables: raise gcmd.error("Unknown gcode_macro variable '%s'" % (variable,)) try: literal = ast.literal_eval(value) - json.dumps(literal, separators=(',', ':')) + json.dumps(literal, separators=(",", ":")) except (SyntaxError, TypeError, ValueError) as e: - raise gcmd.error("Unable to parse '%s' as a literal: %s in '%s'" % - (value, e, gcmd.get_commandline())) + raise gcmd.error( + "Unable to parse '%s' as a literal: %s in '%s'" + % (value, e, gcmd.get_commandline()) + ) v = dict(self.variables) v[variable] = literal self.variables = v + def cmd(self, gcmd): if self.in_script: raise gcmd.error("Macro %s called recursively" % (self.alias,)) kwparams = dict(self.variables) kwparams.update(self.template.create_template_context()) - kwparams['params'] = gcmd.get_command_parameters() - kwparams['rawparams'] = gcmd.get_raw_command_parameters() + kwparams["params"] = gcmd.get_command_parameters() + kwparams["rawparams"] = gcmd.get_raw_command_parameters() self.in_script = True try: self.template.run_gcode_from_command(kwparams) finally: self.in_script = False + def load_config_prefix(config): return GCodeMacro(config) |