diff --git a/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py b/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py index 8cadceb528..0ebeb74b22 100644 --- a/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py +++ b/plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py @@ -1,65 +1,218 @@ # Copyright (c) 2020 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. # Created by Wayne Porter +# Re-write in April of 2024 by GregValiant (Greg Foresi) +# Changes: +# Added an 'Enable' setting +# Added support for multi-line insertions (comma delimited) +# Added insertions in a range of layers or a single insertion at a layer. Numbers are consistent with the Cura Preview (base1) +# Added frequency of Insertion (once only, every layer, every 2nd, 3rd, 5th, 10th, 25th, 50th, 100th) +# Added support for 'One at a Time' print sequence +# Rafts are allowed and accounted for but no insertions are made in raft layers from ..Script import Script +import re +from UM.Application import Application class InsertAtLayerChange(Script): - def __init__(self): - super().__init__() def getSettingDataString(self): return """{ - "name": "Insert at layer change", + "name": "Insert at Layer Change", "key": "InsertAtLayerChange", "metadata": {}, "version": 2, "settings": { - "insert_location": + "enable_insatlaychange": { - "label": "When to insert", - "description": "Whether to insert code before or after layer change.", + "label": "Enable this script", + "description": "You must enable the script for it to run.", + "type": "bool", + "default_value": true, + "enabled": true + }, + "insert_frequency": + { + "label": "How often to insert", + "description": "Every so many layers starting with the Start Layer OR as single insertion at a specific layer. If the print sequence is 'one_at_a_time' then the insertions will be made for every model. Insertions are made at the beginning of a layer.", "type": "enum", - "options": {"before": "Before", "after": "After"}, - "default_value": "before" + "options": { + "once_only": "One insertion only", + "every_layer": "Every Layer", + "every_2nd": "Every 2nd", + "every_3rd": "Every 3rd", + "every_5th": "Every 5th", + "every_10th": "Every 10th", + "every_25th": "Every 25th", + "every_50th": "Every 50th", + "every_100th": "Every 100th"}, + "default_value": "every_layer", + "enabled": "enable_insatlaychange" + }, + "start_layer": + { + "label": "Starting Layer", + "description": "Layer to start the insertion at. If the Print_Sequence is 'All at Once' then use the layer numbers from the Cura Preview. Enter '1' to start at gcode LAYER:0. In 'One at a Time' mode use the layer numbers from the first model that prints AND all models will receive the same insertions. NOTE: There is never an insertion for raft layers.", + "type": "int", + "default_value": 1, + "minimum_value": 1, + "enabled": "insert_frequency != 'once_only' and enable_insatlaychange" + }, + "end_layer": + { + "label": "Ending Layer", + "description": "Layer to end the insertion at. Enter '-1' to indicate the entire file. Use layer numbers from the Cura Preview.", + "type": "int", + "default_value": -1, + "minimum_value": -1, + "enabled": "insert_frequency != 'once_only' and enable_insatlaychange" + }, + "single_end_layer": + { + "label": "Layer # for Single Insertion.", + "description": "Layer for a single insertion of the Gcode. Use the layer numbers from the Cura Preview. The insertion will be at the start of this layer.", + "type": "str", + "default_value": "", + "enabled": "insert_frequency == 'once_only' and enable_insatlaychange" }, "gcode_to_add": { - "label": "G-code to insert", - "description": "G-code to add before or after layer change.", + "label": "G-code to insert.", + "description": "G-code to add at start of the layer. Use a comma to delimit multi-line commands. EX: G28 X Y,M220 S100,M117 HELL0. NOTE: All inserted text will be converted to upper-case as some firmwares don't understand lower-case.", "type": "str", - "default_value": "" - }, - "skip_layers": - { - "label": "Skip layers", - "description": "Number of layers to skip between insertions (0 for every layer).", - "type": "int", - "default_value": 0, - "minimum_value": 0 + "default_value": "", + "enabled": "enable_insatlaychange" } } }""" def execute(self, data): - gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n" - skip_layers = self.getSettingValueByKey("skip_layers") - count = 0 - for layer in data: - # Check that a layer is being printed - lines = layer.split("\n") - for line in lines: - if ";LAYER:" in line: - index = data.index(layer) - if count == 0: - if self.getSettingValueByKey("insert_location") == "before": - layer = gcode_to_add + layer - else: - layer = layer + gcode_to_add - - data[index] = layer - - count = (count + 1) % (skip_layers + 1) - break - return data + # Exit if the script is not enabled + if not bool(self.getSettingValueByKey("enable_insatlaychange")): + return data + #Initialize variables + mycode = self.getSettingValueByKey("gcode_to_add").upper() + start_layer = int(self.getSettingValueByKey("start_layer")) + end_layer = int(self.getSettingValueByKey("end_layer")) + when_to_insert = self.getSettingValueByKey("insert_frequency") + end_list = [0] + print_sequence = Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") + # Get the topmost layer number and adjust the end_list + if end_layer == -1: + if print_sequence == "all_at_once": + for lnum in range(0, len(data) - 1): + if ";LAYER:" in data[lnum]: + the_top = int(data[lnum].split(";LAYER:")[1].split("\n")[0]) + end_list[0] = the_top + # Get the topmost layer number for each model and append it to the end_list + if print_sequence == "one_at_a_time": + for lnum in range(0, 10): + if ";LAYER:0" in data[lnum]: + start_at = lnum + 1 + break + for lnum in range(start_at, len(data)-1, 1): + if ";LAYER:" in data[lnum] and not ";LAYER:0" in data[lnum] and not ";LAYER:-" in data[lnum]: + end_list[len(end_list) - 1] = int(data[lnum].split(";LAYER:")[1].split("\n")[0]) + continue + if ";LAYER:0" in data[lnum]: + end_list.append(0) + elif end_layer != -1: + if print_sequence == "all_at_once": + # Catch an error if the entered End_Layer > the top layer in the gcode + for e_num, layer in enumerate(data): + if ";LAYER:" in layer: + top_layer = int(data[e_num].split(";LAYER:")[1].split("\n")[0]) + end_list[0] = end_layer - 1 + if top_layer < end_layer - 1: + end_list[0] = top_layer + elif print_sequence == "one_at_a_time": + # Find the index of the first Layer:0 + for lnum in range(0, 10): + if ";LAYER:0" in data[lnum]: + start_at = lnum + 1 + break + # Get the top layer number for each model + for lnum in range(start_at, len(data)-1): + if ";LAYER:" in data[lnum] and not ";LAYER:0" in data[lnum] and not ";LAYER:-" in data[lnum]: + end_list[len(end_list) - 1] = int(data[lnum].split(";LAYER:")[1].split("\n")[0]) + if ";LAYER:0" in data[lnum]: + end_list.append(0) + # Adjust the end list if an end layer was named + for index, num in enumerate(end_list): + if num > end_layer - 1: + end_list[index] = end_layer - 1 + #If the gcode_to_enter is multi-line then replace the commas with newline characters + if mycode != "": + if "," in mycode: + mycode = re.sub(",", "\n",mycode) + gcode_to_add = mycode + #Get the insertion frequency + match when_to_insert: + case "every_layer": + freq = 1 + case "every_2nd": + freq = 2 + case "every_3rd": + freq = 3 + case "every_5th": + freq = 5 + case "every_10th": + freq = 10 + case "every_25th": + freq = 25 + case "every_50th": + freq = 50 + case "every_100th": + freq = 100 + case "once_only": + the_insert_layer = int(self.getSettingValueByKey("single_end_layer"))-1 + case _: + the_insert_layer = int(self.getSettingValueByKey("single_end_layer"))-1 + raise Exception("Error. Insert changed to Once Only.") + #Single insertion + if when_to_insert == "once_only": + # For print sequence 'All at once' + if print_sequence == "all_at_once": + for index, layer in enumerate(data): + if ";LAYER:" + str(the_insert_layer) + "\n" in layer: + lines = layer.split("\n") + lines.insert(1,gcode_to_add) + data[index] = "\n".join(lines) + return data + # For print sequence 'One at a time' + else: + for index, layer in enumerate(data): + if ";LAYER:" + str(the_insert_layer) + "\n" in layer: + lines = layer.split("\n") + lines.insert(1,gcode_to_add) + data[index] = "\n".join(lines) + return data + # For multiple insertions + if when_to_insert != "once_only": + # Search from the line after the first Layer:0 so we know when a model ends if in One at a Time mode. + first_0 = True + next_layer = start_layer - 1 + end_layer = end_list.pop(0) + for index, layer in enumerate(data): + lines = layer.split("\n") + for l_index, line in enumerate(lines): + if ";LAYER:" in line: + layer_number = int(line.split(":")[1]) + if layer_number == next_layer and layer_number <= end_layer: + lines.insert(l_index + 1,gcode_to_add) + data[index] = "\n".join(lines) + next_layer += freq + # Reset the next_layer for one-at-a-time + if next_layer > int(end_layer): + next_layer = start_layer - 1 + # Index to the next end_layer when a Layer:0 is encountered + try: + if not first_0 and layer_number == 0: + end_layer = end_list.pop(0) + except: + pass + # Beyond the initial Layer:0 futher Layer:0's indicate the top layer of a model. + if layer_number == 0: + first_0 = False + break + return data \ No newline at end of file