Cura/plugins/PostProcessingPlugin/scripts/FilamentChange.py
Olivier B b4616c18ec
Correct filament change value assignment
Fix the value assignment for filament change option based on machine G-code flavor.
2026-01-17 18:59:10 +01:00

248 lines
11 KiB
Python

# Copyright (c) 2023 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
# Modification 06.09.2020
# add checkbox, now you can choose and use configuration from the firmware itself.
# Modification 01.13.2026 by @hobbe
# convert G-code Before and G-Code After inputs to text areas (#21298).
# Modification 01.17.2026 by @GregValiant
# Move the inserted text below the ";LAYER:??" line.
from typing import List
from ..Script import Script
from UM.Application import Application # To get the current printer's settings.
class FilamentChange(Script):
_layer_keyword = ";LAYER:"
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name": "Filament Change",
"key": "FilamentChange",
"metadata": {},
"version": 2,
"settings":
{
"enable_filament_change":
{
"label": "Enable 'Filament Change'",
"description": "Uncheck to disable this feature.",
"type": "bool",
"default_value": true
},
"layer_number":
{
"label": "Layer",
"description": "The layer(s) where the color change occurs. Enter the layer number from the Cura Preview. The change will occur at the START of the layer(s). Delimit multiple 'Layer Numbers' with commas (Ex: 12,25,134).",
"unit": "Lay#",
"type": "str",
"default_value": "1",
"enabled": "enable_filament_change"
},
"firmware_config":
{
"label": "Use Firmware Configuration",
"description": "Use the settings in your firmware, or customise the parameters of the filament change here.",
"type": "bool",
"default_value": false,
"enabled": "enable_filament_change"
},
"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,
"enabled": "enable_filament_change and not firmware_config"
},
"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,
"enabled": "enable_filament_change and not firmware_config"
},
"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,
"enabled": "enable_filament_change and not firmware_config"
},
"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,
"enabled": "enable_filament_change and not firmware_config"
},
"z_position":
{
"label": "Z Position (relative)",
"description": "Extruder relative Z position. Move the print head up for filament change.",
"unit": "mm",
"type": "float",
"default_value": 0,
"minimum_value": 0,
"enabled": "enable_filament_change and not firmware_config"
},
"retract_method":
{
"label": "Retract method",
"description": "The gcode variant to use for retract.",
"type": "enum",
"options": {"U": "Marlin (M600 U)", "L": "Reprap (M600 L)"},
"default_value": "U",
"enabled": "enable_filament_change and not firmware_config"
},
"machine_gcode_flavor":
{
"label": "G-code flavor",
"description": "The type of g-code to be generated. This setting is controlled by the script and will not be visible.",
"type": "enum",
"options":
{
"RepRap (Marlin/Sprinter)": "Marlin",
"RepRap (Volumetric)": "Marlin (Volumetric)",
"RepRap (RepRap)": "RepRap",
"UltiGCode": "Ultimaker 2",
"Griffin": "Griffin",
"Makerbot": "Makerbot",
"BFB": "Bits from Bytes",
"MACH3": "Mach3",
"Repetier": "Repetier"
},
"default_value": "RepRap (Marlin/Sprinter)",
"enabled": false
},
"enable_before_macro":
{
"label": "Enable G-code Before",
"description": "Use this to insert a custom G-code macro before the filament change happens",
"type": "bool",
"default_value": false,
"enabled": "enable_filament_change"
},
"before_macro":
{
"label": "G-code Before",
"description": "Any custom G-code to run before the filament change happens, for example, M300 S1000 P10000 for a long beep.",
"type": "str",
"comments": "multiline",
"default_value": "M300 S1000 P10000",
"enabled": "enable_filament_change and enable_before_macro"
},
"enable_after_macro":
{
"label": "Enable G-code After",
"description": "Use this to insert a custom G-code macro after the filament change",
"type": "bool",
"default_value": false,
"enabled": "enable_filament_change"
},
"after_macro":
{
"label": "G-code After",
"description": "Any custom G-code to run after the filament has been changed right before continuing the print, for example, you can add a sequence to purge filament and wipe the nozzle.",
"type": "str",
"comments": "multiline",
"default_value": "M300 S440 P500",
"enabled": "enable_filament_change and enable_after_macro"
}
}
}"""
## Copy machine name and gcode flavor from global stack so we can use their value in the script stack
def initialize(self) -> None:
super().initialize()
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None or self._instance is None:
return
for key in ["machine_gcode_flavor"]:
self._instance.setProperty(key, "value", global_container_stack.getProperty(key, "value"))
def execute(self, data: List[str]):
"""Inserts the filament change g-code at specific layer numbers.
:param data: A list of layers of g-code.
:return: A similar list, with filament change commands inserted.
"""
enabled = self.getSettingValueByKey("enable_filament_change")
# Return if the script is not enabled
if not enabled:
return data
# Initialize variables
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")
z_pos = self.getSettingValueByKey("z_position")
firmware_config = self.getSettingValueByKey("firmware_config")
enable_before_macro = self.getSettingValueByKey("enable_before_macro")
before_macro = self.getSettingValueByKey("before_macro")
enable_after_macro = self.getSettingValueByKey("enable_after_macro")
after_macro = self.getSettingValueByKey("after_macro")
# Put together the insertion string
color_change = "\n;BEGIN FilamentChange\n"
if enable_before_macro:
color_change += before_macro + "\n"
color_change += "M600"
if not firmware_config:
if initial_retract is not None and initial_retract > 0.:
color_change += (" E%.2f" % initial_retract)
if later_retract is not None and later_retract > 0.:
# Reprap uses 'L': https://reprap.org/wiki/G-code#M600:_Filament_change_pause
# Marlin uses 'U' https://marlinfw.org/docs/gcode/M600.html
retract_method = self.getSettingValueByKey("retract_method")
color_change += (" %s%.2f" % (retract_method, later_retract))
if x_pos is not None:
color_change += (" X%.2f" % x_pos)
if y_pos is not None:
color_change += (" Y%.2f" % y_pos)
if z_pos is not None and z_pos > 0.:
color_change += (" Z%.2f" % z_pos)
color_change += "\n"
if enable_after_macro:
color_change += after_macro + "\n"
color_change += ";END FilamentChange\n"
# Make the insertions
layer_targets = layer_nums.split(",")
if len(layer_targets) > 0:
for index, layer in enumerate(data):
for layer_num in layer_targets:
try:
layer_str = f"{self._layer_keyword}{int(layer_num.strip()) - 1}\n"
except ValueError: #Layer number is not an integer.
continue
if layer_str in data[index]:
layer_of_interest = data[index].split("\n")
layer_of_interest.insert(1, color_change)
data[index] = "\n".join(layer_of_interest)
return data