136 lines
5.7 KiB
Python
136 lines
5.7 KiB
Python
|
# 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)
|