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.