# Copyright (c) 2022 ManuelW # The Temperature Profile Correction Plugin is released under the terms of the AGPLv3 or higher. import re from collections import OrderedDict from UM.Extension import Extension from UM.Application import Application from UM.Settings.SettingDefinition import SettingDefinition from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Logger import Logger class TemperatureCalculatorPlugin(Extension): def __init__(self): super().__init__() self._application = Application.getInstance() self._i18n_catalog = None self._settings_dict = OrderedDict() self._settings_dict["temp_calc_number"] = { "label": "Material Temperature Correction", "description": "A correction of the print temperature from Material settings. A negative value decreases the print temperature, a positive value will increase the print temperature.", "type": "float", "unit": "°C", "default_value": 0, "maximum_value_warning": 30, "settable_per_mesh": False, "settable_per_extruder": False, "settable_per_meshgroup": False } ContainerRegistry.getInstance().containerLoadComplete.connect(self._onContainerLoadComplete) self._application.getOutputDeviceManager().writeStarted.connect(self._filterGcode) def _onContainerLoadComplete(self, container_id): if not ContainerRegistry.getInstance().isLoaded(container_id): # skip containers that could not be loaded, or subsequent findContainers() will cause an infinite loop return try: container = ContainerRegistry.getInstance().findContainers(id = container_id)[0] except IndexError: # the container no longer exists return if not isinstance(container, DefinitionContainer): # skip containers that are not definitions return if container.getMetaDataEntry("type") == "extruder": # skip extruder definitions return material = container.findDefinitions(key="material") temp_calc = container.findDefinitions(key=list(self._settings_dict.keys())[0]) if material and not temp_calc: material = material[0] for setting_key, setting_dict in self._settings_dict.items(): definition = SettingDefinition(setting_key, container, material, self._i18n_catalog) definition.deserialize(setting_dict) # add the setting to the already existing platform adhesion settingdefinition # private member access is naughty, but the alternative is to serialise, nix and deserialise the whole thing, # which breaks stuff material._children.append(definition) container._definition_cache[setting_key] = definition container._updateRelations(definition) def _filterGcode(self, output_device): scene = self._application.getController().getScene() global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: return # get setting from Cura temp_calc_value = global_container_stack.getProperty("temp_calc_number", "value") if temp_calc_value == 0: return use_temp_calc = global_container_stack.getProperty("temp_calc_number", "value") gcode_dict = getattr(scene, "gcode_dict", {}) if not gcode_dict: # this also checks for an empty dict Logger.log("w", "Scene has no gcode to process") return dict_changed = False hotend_temp_regex = re.compile(r"^(M104\sS)(\d{2,3})\Z") #grab only "M104 Sxx" Strings for plate_id in gcode_dict: gcode_list = gcode_dict[plate_id] if len(gcode_list) < 2: Logger.log("w", "Plate %s does not contain any layers", plate_id) continue if ";TEMPCALCPROCESSED\n" not in gcode_list[0]: # look for the first line that contains a G0 or G1 move on the Z axis # gcode_list[2] is the first layer, after the preamble and the start gcode if ";LAYER:0\n" in gcode_list[1]: # layer 0 somehow got appended to the start gcode chunk chunks = gcode_list[1].split(";LAYER:0\n") gcode_list[1] = chunks[0] gcode_list.insert(2, ";LAYER:0\n" + chunks[1]) # process all G0/G1 lines and adjust the Z value for n in range(2, len(gcode_list)): # all gcode lists / layers, start at layer 1 = gcode list 2 lines = gcode_list[n].split("\n") for (line_nr, line) in enumerate(lines): result = hotend_temp_regex.fullmatch(line) if result: try: adjusted_temp = round(float(result.group(2)) + temp_calc_value, 5) except ValueError: Logger.log("e", "Unable to process temp in line %s", line) continue lines[line_nr] = result.group(1) + str(adjusted_temp) + " ;adjusted by temp calc" gcode_list[n] = "\n".join(lines) gcode_list[0] += ";TEMPCALCPROCESSED\n" gcode_dict[plate_id] = gcode_list dict_changed = True else: Logger.log("d", "Plate %s has already been processed", plate_id) continue if dict_changed: setattr(scene, "gcode_dict", gcode_dict)