Support of mapping dual_carriage(s) and extruders to custom GCode axes

A small update on the work here: I have updated my branch to make some suggested changes, for example, GCODE_AXIS parameter for the commands like SET_DUAL_CARRIAGE CARRIAGE=carriage_x MODE=DIRECT GCODE_AXIS=U. Then I have also created a GCode post-processing script for slicers alleged previously (tested on PrusaSlicer, should probably work on Orca Slicer too) that uses the features introduced by this branch. The script can manage three things in order to speed up the tool swap process:

  • manage the temperature of the inactive tool
  • purge the inactive tool prior to the tool swap (you can notice that in the video for the inactive tool close to the tool swap)
  • retract and wipe the inactive tool nozzle on the nozzle cleaner (works only if the nozzle cleaners are mounted on the gantry close to the tools’ parking positions like on my printer)

all while the other tool is still printing, minimizing the time needed for the tool swap just to the time the tools need to travel (it also moves the tool being activated to the correct position right away). This video shows the kind of tool swaps you can achieve with it. So, if you want to test it on your compatible IDEX machine, please do!

In order to use it, you need to make some minimal adjustments to the slicer and Klipper configuration. For Klipper, I’ve made the following tool changing macros:

[gcode_macro T0]
gcode:
    {% set active_tool = printer["gcode_macro ACTIVE_TOOL"].tool %}
    {% if active_tool != 0 %}
      ACTIVATE_EXTRUDER EXTRUDER=extruder
      M106 S{printer.fan.speed * 255.}  ; Call fan macro to update the servo position in CPAP splitter
      SET_GCODE_OFFSET X=0 Y=0
      {% if ('x' in printer.toolhead.homed_axes) %}
        {% set x = "" if (params.X|default(none) is none) else "X" ~ params.X %}
        {% set y = "" if (params.Y|default(none) is none) else "Y" ~ params.Y %}
        {% set z = "" if (params.Z|default(none) is none) else "Z" ~ params.Z %}
        {% set park_pos = printer.configfile.settings['dual_carriage uc'].position_endstop %}
        SET_DUAL_CARRIAGE CARRIAGE=xc
        SET_DUAL_CARRIAGE CARRIAGE=uc MODE=DIRECT GCODE_AXIS=U
        G1 {x} {y} {z} U{park_pos}
      {% endif %}
      SET_DUAL_CARRIAGE CARRIAGE=xc
      SET_GCODE_VARIABLE MACRO=ACTIVE_TOOL VARIABLE=tool VALUE=0
    {% endif %}

[gcode_macro T1]
variable_tool_x_offset: 0.05
variable_tool_y_offset: -0.8
variable_tool_z_offset: 0
gcode:
    {% set active_tool = printer["gcode_macro ACTIVE_TOOL"].tool %}
    {% set vars = printer["gcode_macro T1"] %}
    {% if active_tool != 1 %}
      ACTIVATE_EXTRUDER EXTRUDER=extruder1
      M106 S{printer.fan.speed * 255.}  ; Call fan macro to update the servo position in CPAP splitter
      SET_GCODE_OFFSET X={0.0-(vars.tool_x_offset|float)} Y={0.0-(vars.tool_y_offset|float)}
      {% if ('x' in printer.toolhead.homed_axes) %}
        {% set x = "" if (params.X|default(none) is none) else "X" ~ params.X %}
        {% set y = "" if (params.Y|default(none) is none) else "Y" ~ params.Y %}
        {% set z = "" if (params.Z|default(none) is none) else "Z" ~ params.Z %}
        {% set park_pos = printer.configfile.settings['carriage xc'].position_endstop %}
        SET_DUAL_CARRIAGE CARRIAGE=uc
        SET_DUAL_CARRIAGE CARRIAGE=xc MODE=DIRECT GCODE_AXIS=U
        G1 {x} {y} {z} U{park_pos}
      {% endif %}
      SET_DUAL_CARRIAGE CARRIAGE=uc
      SET_GCODE_VARIABLE MACRO=ACTIVE_TOOL VARIABLE=tool VALUE=1
    {% endif %}

These T0/T1 macros now accept optional coordinate parameters like T1 X200 Y150, which mean that T0 should be parked, and T1 activated and moved to X=200, Y=150. This is used by the script later.

Then on the slicer side, we need fairly minimal changes. The changes are fairly minimal really (option names come from PrusaSlicer, but should be available in OrcaSlicer too):

  • Enable ‘Wipe while retracting’ for both extruders, and disable ‘Use firmware retraction’ (as it conflicts with wipe-while-retracting feature)
  • Disable Slicer-level dynamic temperature control (e.g. ‘Idle temperature’, ‘Ooze prevention’, etc.)
  • Enable ‘Retraction when tool is disabled (advanced settings for multi-extruder setups)’: set ‘Length’ to non-zero value (around 1mm is typically OK for direct drive extruders) for both extruders
  • I’d recommend to enable Z-hops (e.g. 0.2-0.4 mm) together with ‘Use ramping lift’ (for both extruders)
  • You don’t need any complicated ‘Tool change G-Code’ (in fact, you can/should keep it at default - empty), and in the ‘Start G-Code’ you can either remove all hotend temperature-related gcode and enable ‘Emit temperature commands automatically’ option, or add this into the ‘Start G-Code’ (and don’t worry, the script will remove the second command to heat up the second toolhead, but it is still necessary if the second tool prints some first layer):
T[initial_tool]
M104 T[initial_tool] S[first_layer_temperature]
{if is_extruder_used[1-current_extruder]}
M104 T{1-current_extruder} S{first_layer_temperature[1-current_extruder]}
{endif}

Next, you need to configure the post-processing script. First, the script is in Python, so you’ll need to install Python (3), e.g. if you are on Windows or MacOS. Then, you should add the script to ‘Print Settings’ → ‘Output options’ → ‘Post-processing scripts’. You can run the script klipper/scripts/gcode/fast_tool_swaps.py with the --help argument to see all the available options and the documentation for them, I instead will just list my configuration and explain what it does:

<path_to_python_binary>/python[3][.exe] <path_to_script>/fast_tool_swaps.py --tool_shutoff_time=90 --long_preheat_time=40 --long_preheat_purge_length=15 --inactive_tool_temp_reduction=0.75 --min_preheat_time=10 --min_purge_length=3 --t0=xc:extruder:+ --t1=uc:extruder1:- --tool_wipe_z_hop=0.4 --tool_wipe_dist=25 --tool_wipe_speed=500 --tool_wipe_accel=20000 --tool_swap_speed=1000 --tool_swap_accel=10000 --tool_fast_change_gcode="T{next_tool} X{xpos} Y{ypos}"
  • If the tool is inactive for more than 90 seconds, turn it off completely. The tool will be preheated afterwards (or on the first activation) for at least 40 seconds before extruding/purging anything, and it’ll purge 15 mm of material (so, ~36 mm^3 of filament).
  • If the tool is inactive for less amount of time, it’ll reduce its temperature to 75% (so e.g. from 220 degrees to 165 degrees), and before activation, it’ll preheat for at least 10 seconds and will purge at least 3 mm of material (~7 mm^3).
  • T0 is attached to the carriage named xc and has an extruder named extruder, and it should move in the positive direction (‘+’) from its parked position.
  • T1 is attached to the carriage named uc and has an extruder named extruder1, and it should move in the negative direction (‘-’) from its parked position.
  • --tool_wipe_z_hop=0.4 enables Z-hop of 0.4 mm during tool swaps. If set to 0, this will disable Z control during the swaps (but I’d recommend to still incorporate them into your tool swap procedure).
  • --tool_wipe_dist=25 configures a wipe: the inactive toolhead will move 25 mm back and forth from its parked position (so, returning back to the parked position) before initiating the tool swapping move. The nozzle wipe happens when the active tool makes its final wipe move (hence the requirement to enable ‘Wipe while retracting’ option in the slicer). The other two settings (--tool_wipe_speed=500 --tool_wipe_accel=20000) specify the velocity and the acceleration of the wipe (note that it makes sense to set the acceleration here as high as possible, since the tool isn’t printing anything in this case, so no need to worry about ringing). Setting this to --tool_wipe_dist=0 disables the nozzle wipe.
  • --tool_swap_speed=1000 --tool_swap_accel=10000 define the speed and the acceleration of the actual tool change move (they will be set before calling the tool swap macro). Note that these two are different from their wipe counterparts: speed is higher, acceleration is lower (to prevent even a possibility of skipped steps, but still high enough for the move to be fast).
  • --tool_fast_change_gcode defines a macro that will be called for the actual tool swaps. Note that this will replace the tool activation G-Code commands T0/T1 generated by the slicer. In my case, --tool_fast_change_gcode="T{next_tool} X{xpos} Y{ypos}" means that in order to activate the new tool, the script will generate commands like T0 X200 Y100 to park T1, activate T0 and put it to X200 Y100 position, and T1 X250 Y120 to park T0, activate T1 and put it to X250 Y120 position. But you can define your own Klipper macro and put your own template there with those substitutions {next_tool}, {xpos}, {ypos}. If you don’t add {xpos}, {ypos} substitutions, the tool will not be automatically moved to the correct location (it’ll still happen, but via its own move).

The usual precautions apply: try to double-check the generated G-Code, and use some caution in the first prints. And please let me know if you encounter any issues or if everything works just fine. @DrumClock FYI.

2 Likes