# Copyright (c) 2020 Ultimaker B.V. # 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 getSettingDataString(self): return """{ "name": "Insert at Layer Change", "key": "InsertAtLayerChange", "metadata": {}, "version": 2, "settings": { "enabled": { "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": { "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": "enabled" }, "start_layer": { "label": "Starting Layer", "description": "The layer before which the first insertion will take place. 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 enabled" }, "end_layer": { "label": "Ending Layer", "description": "The layer before which the last insertion will take place. 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 enabled" }, "single_end_layer": { "label": "Layer # for Single Insertion.", "description": "The layer before which the Gcode insertion will take place. Use the layer numbers from the Cura Preview.", "type": "str", "default_value": "", "enabled": "insert_frequency == 'once_only' and enabled" }, "gcode_to_add": { "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": "", "enabled": "enabled" } } }""" def execute(self, data): # Exit if the script is not enabled if not bool(self.getSettingValueByKey("enabled")): 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 _: raise ValueError(f"Unexpected insertion frequency {when_to_insert}") #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