From c3f3a863859ea25e92ddba50ae2fed8b81778135 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 12 Oct 2023 16:53:39 +0200 Subject: [PATCH 1/3] Extend start/end gcode templates feature with support for formula's CURA-11155 --- plugins/CuraEngineBackend/StartSliceJob.py | 74 ++++++++++------------ 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 4d924ac337b..06859aef556 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2022 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. import os @@ -24,6 +24,7 @@ from UM.Scene.Scene import Scene #For typing. from UM.Settings.Validator import ValidatorState from UM.Settings.SettingRelation import RelationType +from UM.Settings.SettingFunction import SettingFunction from cura.CuraApplication import CuraApplication from cura.Scene.CuraSceneNode import CuraSceneNode @@ -46,44 +47,44 @@ class StartJobResult(IntEnum): class GcodeStartEndFormatter(Formatter): - """Formatter class that handles token expansion in start/end gcode""" + # Formatter class that handles token expansion in start/end gcode + # Example of a start/end gcode string: + # ``` + # M104 S{material_print_temperature_layer_0, 0} ;pre-heat + # M140 S{material_bed_temperature_layer_0} ;heat bed + # M204 P{acceleration_print, 0} T{acceleration_travel, 0} + # M205 X{jerk_print, 0} + # ``` + # Any expression between curly braces will be evaluated and replaced with the result, using the + # context of the provided default extruder. If no default extruder is provided, the global stack + # will be used. Alternatively, if the expression is formatted as "{[expression], [extruder_nr]}", + # then the expression will be evaluated with the extruder stack of the specified extruder_nr. + + _extruder_regex = re.compile(r"^\s*(?P.*)\s*,\s*(?P\d+)\s*$") def __init__(self, default_extruder_nr: int = -1) -> None: super().__init__() self._default_extruder_nr = default_extruder_nr - def get_value(self, key: str, args: str, kwargs: dict) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] - # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key), - # and a default_extruder_nr to use when no extruder_nr is specified - + def get_value(self, expression: str, args: str, kwargs: dict) -> str: extruder_nr = self._default_extruder_nr - key_fragments = [fragment.strip() for fragment in key.split(",")] - if len(key_fragments) == 2: - try: - extruder_nr = int(key_fragments[1]) - except ValueError: - try: - extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack #TODO: How can you ever provide the '-1' kwarg? - except (KeyError, ValueError): - # either the key does not exist, or the value is not an int - Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end g-code, using global stack", key_fragments[1], key_fragments[0]) - elif len(key_fragments) != 1: - Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end g-code", key) - return "{" + key + "}" - - key = key_fragments[0] - - default_value_str = "{" + key + "}" - value = default_value_str - # "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value. - if key in kwargs["-1"]: - value = kwargs["-1"][key] - if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]: - value = kwargs[str(extruder_nr)][key] - - if value == default_value_str: - Logger.log("w", "Unable to replace '%s' placeholder in start/end g-code", key) + # The settings may specify a specific extruder to use. This is done by + # formatting the expression as "{expression}, {extruder_nr}". If the + # expression is formatted like this, we extract the extruder_nr and use + # it to get the value from the correct extruder stack. + match = self._extruder_regex.match(expression) + if match: + expression = match.group("expression") + extruder_nr = int(match.group("extruder_nr")) + + if extruder_nr == -1: + container_stack = CuraApplication.getInstance().getGlobalContainerStack() + else: + container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr) + + setting_function = SettingFunction(expression) + value = setting_function(container_stack) return value @@ -422,17 +423,10 @@ def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: :param value: A piece of g-code to replace tokens in. :param default_extruder_nr: Stack nr to use when no stack nr is specified, defaults to the global stack """ - if not self._all_extruders_settings: - self._cacheAllExtruderSettings() - try: # any setting can be used as a token fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr) - if self._all_extruders_settings is None: - return "" - settings = self._all_extruders_settings.copy() - settings["default_extruder_nr"] = default_extruder_nr - return str(fmt.format(value, **settings)) + return str(fmt.format(value)) except: Logger.logException("w", "Unable to do token replacement on start/end g-code") return str(value) From 14afd73c19b369a97da6c39c1af2d3e58f1e3ac6 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 12 Oct 2023 21:09:10 +0200 Subject: [PATCH 2/3] Make sure default variables are available in start/end code The following properties are not settings-names, but could previously be used as template variables - material_id - time - date - day - initial_extruder_nr - material_id - material_name - material_type - material_brand - time - date - day - initial_extruder_nr These properties are _awkwardly_ propogated through the kwargs of the `get_value` method of `GcodeStartEndFormatter`. I don't quite like implementing it like this, but to avoid API breaks I couldn't change abusing the kwargs arg for this purpose. CURA-11155 --- plugins/CuraEngineBackend/StartSliceJob.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 06859aef556..4d9afd31bc0 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -84,7 +84,7 @@ def get_value(self, expression: str, args: str, kwargs: dict) -> str: container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr) setting_function = SettingFunction(expression) - value = setting_function(container_stack) + value = setting_function(container_stack, additional_variables=kwargs[str(extruder_nr)]) return value @@ -423,10 +423,17 @@ def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: :param value: A piece of g-code to replace tokens in. :param default_extruder_nr: Stack nr to use when no stack nr is specified, defaults to the global stack """ + if not self._all_extruders_settings: + self._cacheAllExtruderSettings() + try: # any setting can be used as a token fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr) - return str(fmt.format(value)) + if self._all_extruders_settings is None: + return "" + settings = self._all_extruders_settings.copy() + settings["default_extruder_nr"] = default_extruder_nr + return str(fmt.format(value, **settings)) except: Logger.logException("w", "Unable to do token replacement on start/end g-code") return str(value) From e98240fb2cdaf77d7d4bc9da84734ef29ce177b4 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Fri, 13 Oct 2023 07:36:45 +0200 Subject: [PATCH 3/3] Cleanup code in `GcodeStartEndFormatter` CURA-11155 --- plugins/CuraEngineBackend/StartSliceJob.py | 39 ++++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 4d9afd31bc0..2887743b4fe 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -62,11 +62,13 @@ class GcodeStartEndFormatter(Formatter): _extruder_regex = re.compile(r"^\s*(?P.*)\s*,\s*(?P\d+)\s*$") - def __init__(self, default_extruder_nr: int = -1) -> None: + def __init__(self, default_extruder_nr: int = -1, *, + additional_per_extruder_settings: Optional[Dict[str, Dict[str, any]]] = None) -> None: super().__init__() - self._default_extruder_nr = default_extruder_nr + self._default_extruder_nr: int = default_extruder_nr + self._additional_per_extruder_settings: Optional[Dict[str, Dict[str, any]]] = additional_per_extruder_settings - def get_value(self, expression: str, args: str, kwargs: dict) -> str: + def get_value(self, expression: str, args: [str], kwargs: dict) -> str: extruder_nr = self._default_extruder_nr # The settings may specify a specific extruder to use. This is done by @@ -78,13 +80,27 @@ def get_value(self, expression: str, args: str, kwargs: dict) -> str: expression = match.group("expression") extruder_nr = int(match.group("extruder_nr")) + if self._additional_per_extruder_settings is not None and str( + extruder_nr) in self._additional_per_extruder_settings: + additional_variables = self._additional_per_extruder_settings[str(extruder_nr)] + else: + additional_variables = dict() + + # Add the arguments and keyword arguments to the additional settings. These + # are currently _not_ used, but they are added for consistency with the + # base Formatter class. + for key, value in enumerate(args): + additional_variables[key] = value + for key, value in kwargs.items(): + additional_variables[key] = value + if extruder_nr == -1: container_stack = CuraApplication.getInstance().getGlobalContainerStack() else: container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr) setting_function = SettingFunction(expression) - value = setting_function(container_stack, additional_variables=kwargs[str(extruder_nr)]) + value = setting_function(container_stack, additional_variables=additional_variables) return value @@ -427,13 +443,14 @@ def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str: self._cacheAllExtruderSettings() try: - # any setting can be used as a token - fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr) - if self._all_extruders_settings is None: - return "" - settings = self._all_extruders_settings.copy() - settings["default_extruder_nr"] = default_extruder_nr - return str(fmt.format(value, **settings)) + # Get "replacement-keys" for the extruders. In the formatter the settings stack is used to get the + # replacement values for the setting-keys. However, the values for `material_id`, `material_type`, + # etc are not in the settings stack. + additional_per_extruder_settings = self._all_extruders_settings.copy() + additional_per_extruder_settings["default_extruder_nr"] = default_extruder_nr + fmt = GcodeStartEndFormatter(default_extruder_nr=default_extruder_nr, + additional_per_extruder_settings=additional_per_extruder_settings) + return str(fmt.format(value)) except: Logger.logException("w", "Unable to do token replacement on start/end g-code") return str(value)