commit 9f669bbbbbf21bdfa72abc3f98e627a883396ff0 Author: ManuelW Date: Fri Feb 11 20:34:13 2022 +0100 initial commit diff --git a/._README.md b/._README.md new file mode 100644 index 0000000..8809d1c Binary files /dev/null and b/._README.md differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/cura_temp_plugin.iml b/.idea/cura_temp_plugin.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/cura_temp_plugin.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d1e22ec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7268b51 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b9ba28 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +Example Extension +================= + +This is an example extension plug-in for Uranium. Uranium is the underlying framework used in Ultimaker Cura and NinjaKittens. + +The extension type plug-in is a "generic" type of plug-in that just gets some object constructed upon loading the plug-in for the first time. Using the initialisation of that class as starting point for your code, you can access all of the application. + +There are two typical use cases for extensions: +1. Modifying some behaviour in the application or modifying current functionality. This is done by listening to the desired event, such as the changing of the current machine or on start-up. When that event happens, some code can be executed that adds on the behaviour. +2. Adding a dialogue that provides additional functionality. There is a handy built-in method that allows you to add a menu item easily, and what should happen when the user clicks on it. + +This plug-in shows an example of both use cases. + +Packaging +--------- + +To package your plug-in, compress your plug-in folder in a .zip archive and rename that archive to get the `.plugin` extension. These .plugin files can be dropped into any Uranium application to be installed. diff --git a/TemperatureCalculatorPlugin.py b/TemperatureCalculatorPlugin.py new file mode 100644 index 0000000..44fe21f --- /dev/null +++ b/TemperatureCalculatorPlugin.py @@ -0,0 +1,136 @@ +# 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) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..784e1a9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) 2022 ManuelW +# The Temperature Profile Correction Plugin is released under the terms of the AGPLv3 or higher. + +from . import TemperatureCalculatorPlugin + +## Defines additional metadata for the plug-in. +# +# Some types of plug-ins require additional metadata, such as which file types +# they are able to read or the name of the tool they define. In the case of +# the "Extension" type plug-in, there is no additional metadata though. +def getMetaData(): + return {} + +## Lets Uranium know that this plug-in exists. +# +# This is called when starting the application to find out which plug-ins +# exist and what their types are. We need to return a dictionary mapping from +# strings representing plug-in types (in this case "extension") to objects +# that inherit from PluginObject. +# +# \param app The application that the plug-in needs to register with. +def register(app): + return {"extension": TemperatureCalculatorPlugin.TemperatureCalculatorPlugin()} diff --git a/plugin.json b/plugin.json new file mode 100644 index 0000000..83c4ab1 --- /dev/null +++ b/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "Profile Temperature Correction", + "author": "ManuelW", + "version": "1.0", + "api": 5, + "supported_sdk_versions": ["5.0.0", "6.0.0", "7.0.0"], + "description": "Correct the Material Temperature in Profiles by setting positive or negative Values.", + "catalog": "uranium" +} \ No newline at end of file