Assigning a Filament (aka Thermal Preset) to an Extruder

I want to stop having to pedantically explain to klipper that I have ASA filament in the extruder on my printer. Like other machines, I’d like to tell klipper that information once and have it remembered across restarts. Then I’d like to make use of that information when performing common tasks, like loading/unloading filament or running a test print.

So here is a macro package that does this, for both single extruder and multi-extruder machines:

You create filaments like this:

SETUP_FILAMENT NAME=PLA EXTRUDER=215 BED=60

And set the filament like this

SET_FILAMENT NAME=PLA

The win is getting macros to heat things up that don’t take any parameters:

PREHEAT_EXTRUDER
PREHEAT_BED
PREHEAT
HEAT_EXTRUDER_AND_WAIT
HEAT_BED_AND_WAIT
HEAT_AND_WAIT

If you have a multi-tool printer these macros all take a TOOL_INDEX argument with the index of the extruder. So to set up a test print with PLA supports and PETG you might do this:

SET_FILAMENT NAME=PLA TOOL_INDEX=0
SET_FILAMENT NAME=PETG TOOL_INDEX=1
PREHEAT_EXTRUDER TOOL_INDEX=0
PREHEAT_EXTRUDER TOOL_INDEX=1
PREHEAT_BED TOOL_INDEX=1

In this case the bed is preheated with the bed temperature for the PETG in tool 1.

So, this is cool, but I hated every minute of programming it in Jinja. With enough hacking I can get Fluidd and KlipperScreen to use this instead of their own thermal preset tools but it wont be pretty. I wont get them to add buttons to call the PREHEAT* macros or to display the filament assigned to each extruder. The front ends wont evolve to take advantage of this unless I put out a klipper extension and eventually get it merged into klipper mainline.

So who would like this in Python and an installable extension? Is there value in this for you? Just like the post (or comment) if you are interested.

2 Likes

I agree it would be useful to have a mechanism to specify the filament type. I run into similar issues swapping filament on my printers. Alas, I have no suggestions on a good way to implement that.

-Kevin

1 Like

I was tumbling over a similar idea/issue to be able controlling chamber filters and fans.

Lets say you print with ABS or ASA and want to turn on carbon and HEPA filters during the print or at its end to clean and evacuate the printer/chamber.

My idea was to extend the parameter set of PRINT_START and PRINT_END macro with filament type and pass this via the slicer along with bed and hotend temps.
Then you only select for instance in Cura the filament type and the rest is handled via macros:

Then depending on the filament type one could control fans and stuff.

To get Mainsail properly showing additional information my start code in Cura looks like follows:

;Filament weight = {filament_weight}
;Nozzle diameter = {machine_nozzle_size}
;Filament type = {material_type}
;Filament name = {material_name}
PRINT_START BED_TEMP={material_bed_temperature_layer_0} HOTEND_TEMP={material_print_temperature_layer_0}

Meaning those parameters are already available and can be passed to Klipper.
Due to other topics I was not yet able to check if it is feasible…

1 Like

Klipper Screen is the only front end that I’m aware of that has this feature: Preheat Options. It stores additional fields. I think they exclusively mean “heaters” but I like the idea of arbitrarily settings stored with a preset. Some use-cases that come to mind:

  • Is the chamber fan/filter required for this filament type? What fan speed?
  • Does this filament need to hit a chamber temperature before printing? (PLA=False, ABS=True)
  • What temp should the Extruder be during the homing & probing phase (e.g. Voron Tap hit the bed so you are limited to 170C max, PETG likes to ooze so maybe that’s different)
  • Storing a retraction setting for the filament type to be used in test prints
  • Storing a generic pressure advance value to be used in test prints
  • In your multi-tool printer, do you want different tool parking and purging behavior based on filament type?

I don’t think we need to solve all of these things. We just need a way to store the data associated with the preset. I think we have some options:

  1. You could DIY this with [save_variables] by keeping all this data in some other place and looking it up based on the name of the filament preset.
  2. The Filament system could store all of this for you as fields on the preset

I’m going to look into how practical the second option is to do well.

Another feature that the front ends have is running some gcode after the preset is clicked.

I think the best way we can handle that is by invoking a macro. Pass in the preset, extruder name and extruder index to the macro, which is something none of the front ends do. That way you don’t have to write a new macro for each filament, you can just write 1 macro and vary its behavior based on the extruder and preset. This is something that I think can be set up from the klipper config. If you need a whole block of gcode you can put that in your macro.

Ok, here is my very specific suggestion-as-code: GitHub - garethky/filaments-klipper-extra: A Klipper plugin that does basic filament management for extruders in the printer

I’ll take feedback and suggestions here or on github. I haven’t had a chance to run the multi-tool code paths as I don’t have that printer running right now. Ive tried all of he single-tool code paths, I think.

If you generally like what you see I’ll reach out to the front ends and see what they think.

I set up some macros and Fluidd makes them into buttons that make this easy to live with:

Clicking a filament button does not heat anything up, it just set that as the current filament.
It would be pretty easy to do something similar in Klipper Screen. the only thing the both cant do it display a value from Moonraker in a label, so I cant see what the current filament is.

I worked out where the spare pins are on my SKR 1.4s so I could squeeze one more extruder config out of them. Here is what I got up to:

  • multi-tool testing
  • Use EXTRUDER=[name] rather than tool index in arguments, more like SET_PRESSURE_ADVANCE
  • Use the active extruder on the toolhead if no extruder argument is given. This means 99% of people wont ever need that EXTRUDER argument.
  • Switch to register_mux_command to avoid handling invalid extruder names
  • Merged LIST_FILAMENTS and SHOW_FILAMENT into a single QUERY_FILAMENTS command. QUERY_* naming convention is used on other modules to print state to the console.
  • Extruder objects (printer.extruder*) now contain the filament key that has the preset assignment. This should make integration with front ends much easier as they already iterate the extruder objects. I’m sure we can find a clean way to do this if its integrated into klipper directly, but for now its done by wrapping the get_status method on PrinterExtruder.
  • Renamed UNSET_FILAMENT to CLEAR_FILAMENT. UNSET didn’t have a naming precedent but CLEAR did.
  • SET_FILAMENT and CLEAR_FILAMENT now print a message about what just happened
  • readme updated

Some minimal edits to Fluidd’s code and it displays Filament Preset, nozzle diameter and an Active Extruder marker in the Thermals panel. Its looping over the “Heater” objects but these are really extruder objects so its an easy thing to drop in with this extension.

2 Likes

I read this thread with interest because it seems like your project might have some application for something I’m about to work on. I’ve got a 6-in-1-out setup. Right now, when I slice a model I have to make sure the slicer’s filament-to-extruder settings match what’s actually loaded in the printer. While that’s certainly not too much to ask, what I’d really like to be able to do is tell Klipper which filament is loaded in which extruder, and then make the slicer extruder-agnostic so at tool changes the gcode just identifies what filament to use, and Klipper selects whatever extruder that filament is actually loaded in. I’d even like it to be able to warn at print start if the gcode calls for a filament that’s not currently loaded.

I think I can implement this entirely in jinja via macros, but this thread has me enamored by the idea of having GUI controls. Do you think this use case is within the scope of your project?

What do you mean by “6 in 1 out? Is that like MMU/ERCF? My understanding of those setups is limited. If this is the case do you have a sample config I could look at?

Functionally yes, it’s very similar to MMU/ERCF in that there’s one hotend with one nozzle and a direct-drive extruder (pancake stepper + BMG clone), and filament is loaded into and out of it during the print as necessary. But the mechanism is (in my opinion) much simpler than the MMU/ERCF approach. I have a 6-to-1 splitter that feeds into the hotend. “Upstream” of the splitter are 6 extruders each with its own stepper.

In Klipper parlance, the direct-drive extruder on the hotend is the only “extruder,” and each of the six upstream steppers is an “extruder stepper.” The filament-loading procedure consists of syncing the appropriate extruder stepper with the extruder and then advancing the loaded filament through the splitter into the hotend, and then purging out the previous filament before proceeding with the print. Unloading the filament consists of withdrawing the loaded filament to a position just “upstream” of the splitter to clear the path for whatever filament gets loaded next.

My config file is an unholy mess at the moment, as it (like my printer) seems to be in a never-ending state of experimentation/evolution. (I’m using three different control boards in addition to the Pi). But here are some decluttered excerpts related to filament handling:

Extruder and extruder steppers config:

[extruder]
step_pin: P0.1
dir_pin: !P0.0
enable_pin: !P0.10
rotation_distance: 7.79648
microsteps: 16
nozzle_diameter: 0.400
filament_diameter: 1.750
max_extrude_only_distance: 1000.0
max_extrude_cross_section: 1000.0
max_extrude_only_velocity: 120
max_extrude_only_accel: 500
heater_pin: mini:PA8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: mini:PA0
#control: pid
#pid_Kp: 22.2
#pid_Ki: 1.08
#pid_Kd: 114
min_temp: 0
max_temp: 300
min_extrude_temp: 10

[tmc2208 extruder]
uart_pin: P1.1
interpolate: False
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

[extruder_stepper extruder_stepper_0]
extruder:
step_pin: mini:PC5
dir_pin: !mini:PB0
enable_pin: !mini:PC4
rotation_distance: 32.1792
microsteps: 16

[tmc2208 extruder_stepper extruder_stepper_0]
uart_pin: mini:PB4
interpolate: False
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

[extruder_stepper extruder_stepper_1]
extruder:
step_pin: P2.2
dir_pin: P2.6
enable_pin: !P2.1
rotation_distance: 32.1792
microsteps: 16

[tmc2208 extruder_stepper extruder_stepper_1]
uart_pin: P1.17
interpolate: False
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

[extruder_stepper extruder_stepper_2]
extruder:
step_pin: P0.19
dir_pin: P0.20
enable_pin: !P2.8
rotation_distance: 32.1792
microsteps: 16

[tmc2208 extruder_stepper extruder_stepper_2]
uart_pin: P1.15
interpolate: False
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

##This is the config for the E3 port, but the wiring needs to be fixed.
[extruder_stepper extruder_stepper_3]
extruder:
step_pin: P0.22
dir_pin: P2.11
enable_pin: !P0.21
rotation_distance: 32.1792
microsteps: 16

[tmc2208 extruder_stepper extruder_stepper_3]
uart_pin: P1.10 
interpolate: False
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

[extruder_stepper extruder_stepper_4]
extruder:
step_pin: P2.13
dir_pin: P0.11
enable_pin: !P2.12
rotation_distance: 32.1792
microsteps: 16

[tmc2208 extruder_stepper extruder_stepper_4]
uart_pin: P1.8
interpolate: False
run_current: 0.8
hold_current: 0.7
#sense_resistor: 0.110
stealthchop_threshold: 5

[extruder_stepper extruder_stepper_5]
extruder:
step_pin: e3:PB13
dir_pin: e3:PB12
enable_pin: !e3:PB14
rotation_distance: 32.1792
microsteps: 16

[tmc2209 extruder_stepper extruder_stepper_5]
uart_pin: e3:PB15
run_current: 0.8
hold_current: 0.7
stealthchop_threshold: 5

Hacky macro for storing global variables and pseudo-config:

[gcode_macro GLOBALS]
variable_bowden_length:490
variable_hotend_length:80
variable_park_x_pos:249
variable_park_y_pos:120
variable_ramming_distance:9.5
variable_ramming_speed:2000
variable_pressure_advance:0.03
variable_filament_loaded: False
variable_filament_id: None
variable_filament_temp: None
variable_filament_type: None
variable_filament_color_hex: None
variable_filament_initial_purge_volume: None
variable_retract_length:0.6
variable_tip_shape_distance:26
variable_secondary_purge_volume:0
gcode:
    M115 ; must provide something

Actual filament-handling macros:

[gcode_macro PRINT_START]
gcode:
    {% set BED_TEMP = params.BED_TEMPERATURE|default(60)|float %}
    {% set EXTRUDER_TEMP = params.FIRST_LAYER_TEMPERATURE|default(190)|float %}
    {% set MESH_MIN = params.MESH_MIN|default(None) %}
    {% set MESH_MAX = params.MESH_MAX|default(None) %}
    {% set km = printer["gcode_macro _km_globals"] %}
    {% set bed_overshoot = (BED_TEMP + (km.start_bed_heat_overshoot if BED_TEMP else 0.0),
           printer.configfile.settings.heater_bed.max_temp ) | min %}
    {% if (printer["gcode_macro SET_PSU"].value)|int == 0 %}
        SET_PSU VALUE=1
    {% endif %}
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_id VALUE='"{params.FILAMENT_ID}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_temp VALUE={EXTRUDER_TEMP}
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_type VALUE='"{params.FILAMENT_TYPE}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_color_hex VALUE='"{params.FILAMENT_COLOR_HEX}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_initial_purge_volume VALUE={params.PURGE_VOLUME}   
    CLEAR_PAUSE
    BED_MESH_PROFILE LOAD=default
    DESYNC_EXTRUDER_STEPPERS
    SMART_HOME
    SET_SKEW XY=140.71,141.77,99.91 XZ=70.14,70.65,49.70 YZ=70.54,70.30,49.70
    M140 S{bed_overshoot}
    M104 S{EXTRUDER_TEMP}
    {% if BED_TEMP > 0.0 %}
        M190 S{bed_overshoot}
        G4 P{km.start_bed_heat_delay / 2}
        M190 R{BED_TEMP} # Settle down after the overshoot.
        G4 P{km.start_bed_heat_delay / 2}
    {% endif %}
    G28 Z # Re-home only the Z axis now that the bed has stabilized.
    M109 S{EXTRUDER_TEMP}; Heat up hotend
    M400
    SYNC_EXTRUDER_MOTION EXTRUDER={params.CURRENT_EXTRUDER} MOTION_QUEUE=extruder    
    M400
    SET_PRESSURE_ADVANCE ADVANCE={(printer["gcode_macro GLOBALS"].pressure_advance)}
    SET_PRESSURE_ADVANCE EXTRUDER={params.CURRENT_EXTRUDER} ADVANCE={(printer["gcode_macro GLOBALS"].pressure_advance)}
    M400
    G91; Relative Coordinates
    G1 Z+10 F420;  Lift Z
    PARK_TOOLHEAD
    FILAMENT_LOAD FIRST_LOAD=True
    G90; Absolute Coordinates
    G1 Z0

[gcode_macro PRINT_END]
gcode:
    M106 S0 ; turn off cooling fan
    M140 S0 ; turn off bed
    FILAMENT_UNLOAD
    WIPE_NOZZLE
    M220 S100 ;Reset speed override to 100%
    M221 S100 ;Reset extrusion factor override to 100%
    M104 S0 ; turn off extruder
    G90
    G1 Y200; Remove Print Position
    M84 ; disable motors
    SET_SKEW CLEAR=1

[gcode_macro FILAMENT_CHANGE]
gcode:
    M400
    M107; Cut the fan
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_id VALUE='"{params.FILAMENT_ID}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_temp VALUE={params.NEXT_EXTRUDER_TEMP}
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_type VALUE='"{params.FILAMENT_TYPE}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_color_hex VALUE='"{params.FILAMENT_COLOR_HEX}"'
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_initial_purge_volume VALUE={params.INITIAL_PURGE_VOLUME} 
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=retract_length VALUE={params.RETRACT_LENGTH}
    FILAMENT_UNLOAD
    SYNC_EXTRUDER_MOTION EXTRUDER={params.NEXT_EXTRUDER} MOTION_QUEUE=extruder
    M400
    RESTORE_DEFAULT_PA
    FILAMENT_LOAD
    G90; Absolute Coordinates
    G1 X{(printer.toolhead.position.x)} Y{(printer.toolhead.position.y)} Z{(printer.toolhead.position.z) + 1} F18000

[gcode_macro FILAMENT_UNLOAD]
gcode:
    {% set globals = printer["gcode_macro GLOBALS"] %}
    PARK_TOOLHEAD
    G92 E0; Zero Extruder
    G91; Relative Coordinates
    ;M109 S{(printer["extruder"].target) - (printer["gcode_macro GLOBALS"].cooling_amount)}
    G1 E{globals.ramming_distance} F{globals.ramming_speed}
    G1 E-{globals.ramming_distance} F3000
    G1 E{globals.retract_length - globals.tip_shape_distance } F3000
    G1 E12 F200
    G1 E-12 F200
    G1 E12 F255
    G1 E-12 F255
    G1 E{globals.tip_shape_distance} F2000 ;move stringy tip into melt zone
    ;G4 P50 ;pause in melt zone
    G1 E-{globals.tip_shape_distance} F4000 ;extract clean tip from melt zone
    G1 E-{globals.hotend_length + globals.bowden_length - globals.tip_shape_distance} F4000
    M400
    DESYNC_EXTRUDER_STEPPERS

[gcode_macro FILAMENT_LOAD]
gcode:
    {% set last_filament = printer.save_variables.variables %}
    {% set globals = printer["gcode_macro GLOBALS"] %}
    ; Don't pass EXTRUDER_STEPPER if calling this from another macro!
    {% if params.EXTRUDER_STEPPER %}
        {% if printer["gcode_macro SYNC_EXTRUDER_MOTION"].stepper_synced %}
            action_raise_error('An EXTRUDER_STEPPER is already synced.')
        {% else %}
            SYNC_EXTRUDER_MOTION EXTRUDER=extruder_stepper_{params.EXTRUDER_STEPPER|int} MOTION_QUEUE=extruder
        {% endif %}
    {% else %}
        ; The calling macro should already have synced an extruder stepper.
        {% if not printer["gcode_macro SYNC_EXTRUDER_MOTION"].stepper_synced %}
            action_raise_error('This won't work without a synced EXTRUDER_STEPPER.')
        {% endif %}
    {% endif %}
    {% if globals.filament_loaded %}
        action_raise_error('Filament is already loaded')
    {% endif %}
    ; Don't pass PRINT_TEMP if calling this from another macro!
    {% if params.PRINT_TEMP %}
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_id VALUE="Manually Loaded Filament"
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_temp VALUE={params.PRINT_TEMP}
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_type VALUE=None
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_color_hex VALUE=None
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_initial_purge_volume VALUE=None 
    {% endif %}
    {% set print_temp = params.PRINT_TEMP|default(globals.filament_temp) %}
    {% if print_temp is none %}
        action_raise_error('New filament temperature must be specified.')
    {% endif %}
    {% set temps = ['last_filament.filament_temp', 'print_temp'] %}
    M109 S{temps|max}; Heat up hotend
    _CALCULATE_SECONDARY_PURGE
    {% if params.FIRST_LOAD %}
        {% set z_move = -10 %}
    {% else %}
        {% set z_move = 1 %}
    {% endif %}
    G91; Relative Coordinates
    G1 E25 F400; Medium insert
    G1 E25 F600
    G1 E25 F800
    G1 E{(globals.bowden_length) - 75 } F10000
    G1 E{(globals.hotend_length)} F10000
    {% if not params.SKIP_PURGE %}
        M106 S256 ;Comment out this line to make purge pellets
        G1 E{globals.filament_initial_purge_volume} F70; Initial purge
        M104 S{globals.filament_temp}
        {% if globals.secondary_purge_volume > 0 %}
            G1 E{globals.secondary_purge_volume} F70; Secondary purge
            SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=secondary_purge_volume VALUE=0
        {% endif %}
        M107 ;Cut the fan
        M109 S{globals.filament_temp}
        M106 S256
        G1 Z{z_move} F420
        G1 E10 F50; purge 10mm more
    {% endif %}
    STORE_FILAMENT_DATA
    {% if not params.FIRST_LOAD %}
        M106 S{ printer.fan.speed * 510}
    {% else %}
        M107 ;Cut the fan
    {% endif %}
    WIPE_NOZZLE
    G92 E0; Zero Extruder
    # Dummy argument block for Mainsail
    {% set dummy = None if True else "
    {% set dummy = params.PRINT_TEMP|default(None)|float %}
    {% set dummy = params.EXTRUDER_STEPPER|default(None) %}
    {% set dummy = params.SKIP_PURGE|default(True)|bool %}
    " %} # End argument block for Mainsail

Collection of supporting macros/pseudo-functions:

[gcode_macro RESTORE_DEFAULT_PA]
gcode:
    SET_PRESSURE_ADVANCE ADVANCE={(printer["gcode_macro GLOBALS"].pressure_advance)}

[gcode_macro SYNC_EXTRUDER_MOTION]
rename_existing: BASE_SYNC_EXTRUDER_MOTION
variable_stepper_synced: False
gcode:
    SET_GCODE_VARIABLE MACRO=SYNC_EXTRUDER_MOTION VARIABLE=stepper_synced VALUE={False if params.MOTION_QUEUE == '' else True}   
    BASE_SYNC_EXTRUDER_MOTION {rawparams}

[gcode_macro DESYNC_EXTRUDER_STEPPERS]
gcode:
    {% for n in range(6) %}
        SYNC_EXTRUDER_MOTION EXTRUDER=extruder_stepper_{n} MOTION_QUEUE=
    {% endfor %}

[gcode_macro WIPE_NOZZLE]
gcode:
    {% set globals = printer["gcode_macro GLOBALS"] %}
    G90; Absolute Coordinates
    G1 X{globals.park_x_pos}
    G91; Relative Coordinates
    G1 X-15 F10000
    G1 X15 F10000
    G1 X-15 F10000
    G1 X15 F10000
    {% if printer.gcode_move.absolute_coordinates %}
        G90; Absolute Coordinates
    {% else %}
        G91; Relative Coordinates
    {% endif %}

[gcode_macro _CLEAR_FILAMENT_GLOBALS]
gcode:
    { action_respond_info('Clearing filament globals...') }
    {% for param in ['id', 'temp', 'type', 'color_hex', 'initial_purge_volume'] %}
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=filament_{param} VALUE=None
    {% endfor %}

[gcode_macro STORE_FILAMENT_DATA]
gcode:
    {% set globals = printer["gcode_macro GLOBALS"] %}
    { action_respond_info('Saving filament properties to disk...') }
    {% if globals.filament_id is none %}
        SAVE_VARIABLE VARIABLE=filament_id VALUE=None
    {% else %}
        SAVE_VARIABLE VARIABLE=filament_id VALUE='"{globals.filament_id}"'
    {% endif %}
    {% if globals.filament_type is none %}
        SAVE_VARIABLE VARIABLE=filament_type VALUE=None
    {% else %}
        SAVE_VARIABLE VARIABLE=filament_type VALUE='"{globals.filament_type}"'
    {% endif %}
    {% if globals.filament_color_hex is none %}
        SAVE_VARIABLE VARIABLE=filament_color_hex VALUE=None
    {% else %}
        SAVE_VARIABLE VARIABLE=filament_color_hex VALUE='"{globals.filament_color_hex}"'
    {% endif %}
    SAVE_VARIABLE VARIABLE=filament_initial_purge_volume VALUE={globals.filament_initial_purge_volume}
    SAVE_VARIABLE VARIABLE=filament_temp VALUE={globals.filament_temp}
    _CLEAR_FILAMENT_GLOBALS

[gcode_macro PARK_TOOLHEAD]
gcode:
    {% set globals = printer["gcode_macro GLOBALS"] %}
    G91; Relative Coordinates
    G92 E0; Zero Extruder
    { action_respond_info('Parking print head...') }
    G1 E-{globals.retract_length} F10000; Fast Retract 
    G90; Absolute Coordinates
    G1 X{globals.park_x_pos} Y{globals.park_y_pos} F9000 ; Move to bucket
    G1 E{globals.retract_length} F10000; Unretract
    {% if printer.gcode_move.absolute_coordinates %}
        G90; Absolute Coordinates
    {% else %}
        G91; Relative Coordinates
    {% endif %}

[gcode_macro _CALCULATE_SECONDARY_PURGE]
gcode:
    {% set last_filament = printer.save_variables.variables %}
    {% set globals = printer["gcode_macro GLOBALS"] %}
    {% if globals.filament_id == last_filament.filament_id %}
        { action_respond_info('Same filament detected. Skipping secondary purge...') }
        {% set SECONDARY_PURGE_VOLUME = 0 %}
    {% elif last_filament.filament_color_hex is none %}
        { action_respond_info('Last filament has unknown color. Skipping secondary purge...') }
        {% set SECONDARY_PURGE_VOLUME = 0 %}
    {% else %}
        {% set color1_hex = globals.filament_color_hex %}
        {% set color2_hex = last_filament.filament_color_hex %}
        {% set c1 = [] %}
        {% set c2 = [] %}
        {% for i in range(0,6,2) %}
            { c1.append(color1_hex[i:i+2]|int(base=16)) }
            { c2.append(color2_hex[i:i+2]|int(base=16)) }
        {% endfor %}
        {% set sums = [] %}
        {% for n in range(3) %}
            {sums.append((c2[n] - c1[n])**2)}
        {% endfor %}
        {% set diff = (sums|sum())**(1/2) %}
        { action_respond_info('Color difference between new filament (%s) and last filament (%s): %.2f'|
                               format(globals.filament_id, last_filament.filament_id, diff))}
        {% if diff <= 74 %}
            {% set SECONDARY_PURGE_VOLUME = 10|int %}
        {% elif 74 < diff <= 147 %}
            {% set SECONDARY_PURGE_VOLUME = 20|int %}
        {% elif 147 < diff <= 220 %}
            {% set SECONDARY_PURGE_VOLUME = 30|int %}
        {% elif 220 < diff <= 294 %}
            {% set SECONDARY_PURGE_VOLUME = 40|int %}
        {% elif 294 < diff <= 368 %}
            {% set SECONDARY_PURGE_VOLUME = 50|int %}
        {% elif 368 < diff %}
            {% set SECONDARY_PURGE_VOLUME = 60|int %}
        {% endif %}
        {% if globals.filament_type != last_filament.filament_type %}
            { action_respond_info('Different filament type detected. Adding 30mm to secondary purge...') }
            {% set SECONDARY_PURGE_VOLUME = SECONDARY_PURGE_VOLUME + 30 %}
        {% endif %}
        { action_respond_info('Setting secondary purge to %i mm...'|format(SECONDARY_PURGE_VOLUME))}
    {% endif %}
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=secondary_purge_volume VALUE={SECONDARY_PURGE_VOLUME}

As you can see, I’m already doing some storing of filament data, both to variables and to disk, to keep track of relevant properties of whatever filament is loaded in the hotend (extruder). Eventually I want to do something similar to track what’s loaded in the upstream extruder steppers.

1 Like

Ok, in that case this should work. The filament is assigned to an extruder object. I had thought about MMU type configs but generally they don’t have different filament types (e.h. they run all PLA). And they also only have 1 extruder object form what I can see. So I’m not sure how to really support those configs. Maybe there is someone that has made a “Virtual Extruder” extra that will represent each filament as an extruder without having a stepper. Anyway, that’s not your setup!

This extra should let you assign a filament to the main extruder on tool change. You can assign a filament to each of the sub-extruders and then copy that on filament change. You can store the type, color, and purge volume in the extra data of the filament:

SETUP_FILAMENT NAME='Prusament Carmine Red' EXTRUDER=250 BED=85 COLOR='#8B0000' TYPE='PETG' PURGE_VOLUME=15.0

(Python Literal rules apply, so strings need quotes)

What are you using the color for? I think that the rest of the params make sense but color sort of implies that you have several filament of the same “Type” each with a different color. I’ve been considering this something that I’ll leave up to the slicer. Same for pressure advance.

I’d be super happy to get some feedback if you use it!

Color discussion here: Macro snippet to calculate the Euclidean distance between filament colors

In short, I’m trying to work out some math that will optimize purge volumes on material changes based on the colors of the filaments being switched.

I get it. That’s interesting.

This system only has 1 filament object per preset. So you cant create a “PETG” preset and then assign it to 2 extruders and have each with a different color. You would have to make “PETG Red” and “PETG Blue” and assign accordingly.

I don’t want this to turn into Ocroprint’s filament manager (FilamentManager). That’s kind of where color probably belongs, associated with a Spool rather than a filament type. Maybe that would be a good app built on top of Moonraker.