# Copyright (c) 2019 Ultimaker B.V. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. from typing import Optional, Tuple from UM.Logger import Logger from ..Script import Script class FilamentChange(Script): _layer_keyword = ";LAYER:" def __init__(self): super().__init__() def getSettingDataString(self): return """{ "name":"Filament Change", "key": "FilamentChange", "metadata": {}, "version": 2, "settings": { "layer_number": { "label": "Layer", "description": "At what layer should color change occur. This will be before the layer starts printing. Specify multiple color changes with a comma.", "unit": "", "type": "str", "default_value": "1" }, "initial_retract": { "label": "Initial Retraction", "description": "Initial filament retraction distance. The filament will be retracted with this amount before moving the nozzle away from the ongoing print.", "unit": "mm", "type": "float", "default_value": 30.0 }, "later_retract": { "label": "Later Retraction Distance", "description": "Later filament retraction distance for removal. The filament will be retracted all the way out of the printer so that you can change the filament.", "unit": "mm", "type": "float", "default_value": 300.0 }, "x_position": { "label": "X Position", "description": "Extruder X position. The print head will move here for filament change.", "unit": "mm", "type": "float", "default_value": 0 }, "y_position": { "label": "Y Position", "description": "Extruder Y position. The print head will move here for filament change.", "unit": "mm", "type": "float", "default_value": 0 } } }""" def execute(self, data: list): """data is a list. Each index contains a layer""" layer_nums = self.getSettingValueByKey("layer_number") initial_retract = self.getSettingValueByKey("initial_retract") later_retract = self.getSettingValueByKey("later_retract") x_pos = self.getSettingValueByKey("x_position") y_pos = self.getSettingValueByKey("y_position") color_change = "M600" if initial_retract is not None and initial_retract > 0.: color_change = color_change + (" E%.2f" % initial_retract) if later_retract is not None and later_retract > 0.: color_change = color_change + (" L%.2f" % later_retract) if x_pos is not None: color_change = color_change + (" X%.2f" % x_pos) if y_pos is not None: color_change = color_change + (" Y%.2f" % y_pos) color_change = color_change + " ; Generated by FilamentChange plugin" layer_targets = layer_nums.split(",") if len(layer_targets) > 0: for layer_num in layer_targets: layer_num = int(layer_num.strip()) if layer_num <= len(data): index, layer_data = self._searchLayerData(data, layer_num - 1) if layer_data is None: Logger.log("e", "Could not found the layer") continue lines = layer_data.split("\n") lines.insert(2, color_change) final_line = "\n".join(lines) data[index] = final_line return data ## This method returns the data corresponding with the indicated layer number, looking in the gcode for # the occurrence of this layer number. def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]: for index, layer_data in enumerate(data): first_line = layer_data.split("\n")[0] # The first line should contain the layer number at the beginning. if first_line[:len(self._layer_keyword)] == self._layer_keyword: # If found the layer that we are looking for, then return the data if first_line[len(self._layer_keyword):] == str(layer_num): return index, layer_data return 0, None