generic_cartesian: IQEX-related documentation

Also restored `SET_DUAL_CARRIAGE` behavior for
cartesian and other kinematics

Signed-off-by: Dmitry Butyugin <dmbutyugin@google.com>
This commit is contained in:
Dmitry Butyugin 2026-01-08 09:47:49 +01:00
parent cfb12bc657
commit caef97af6d
4 changed files with 371 additions and 6 deletions

348
config/sample-iqex.cfg Normal file
View file

@ -0,0 +1,348 @@
# This file contains a configuration snippet for a quad extruder printer
# with two independent gantries each having two independent tools.
# See docs/Config_Reference.md for a description of parameters.
# Test config for generic cartesian kinematics with quad independent extruders
[mcu]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00
[mcu auxboard]
serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_67890-if11
[carriage carriage_t0]
axis: x
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: auxboard:PG6
[dual_carriage carriage_t1]
primary_carriage: carriage_t0
safe_distance: 70
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: auxboard:PG9
[dual_carriage carriage_t2]
axis: x
position_endstop: 0
position_max: 300
homing_speed: 50
endstop_pin: auxboard:PG10
[dual_carriage carriage_t3]
primary_carriage: carriage_t2
safe_distance: 70
position_endstop: 300
position_max: 300
homing_speed: 50
endstop_pin: auxboard:PG11
[carriage carriage_gantry0]
axis: y
position_endstop: 0
position_max: 200
homing_speed: 50
endstop_pin: PG6
[dual_carriage carriage_gantry1]
primary_carriage: carriage_gantry0
safe_distance: 50
position_endstop: 200
position_max: 200
homing_speed: 50
endstop_pin: PG10
[carriage carriage_z]
axis: z
position_endstop: 0.5
position_max: 100
endstop_pin: PG12
[stepper stepper_t0_x]
carriages: carriage_t0
step_pin: auxboard:PF13
dir_pin: auxboard:PF12
enable_pin: !auxboard:PF14
microsteps: 16
rotation_distance: 40
[stepper stepper_t1_x]
carriages: carriage_t1
step_pin: auxboard:PG0
dir_pin: auxboard:PG1
enable_pin: !auxboard:PF15
microsteps: 16
rotation_distance: 40
[stepper stepper_t2_x]
carriages: carriage_t2
step_pin: auxboard:PF11
dir_pin: auxboard:PG3
enable_pin: !auxboard:PG5
microsteps: 16
rotation_distance: 40
[stepper stepper_t3_x]
carriages: carriage_t3
step_pin: auxboard:PG4
dir_pin: auxboard:PC1
enable_pin: !auxboard:PA2
microsteps: 16
rotation_distance: 40
[stepper gantry0]
carriages: carriage_gantry0
step_pin: PF13
dir_pin: PF12
enable_pin: !PF14
microsteps: 16
rotation_distance: 40
[stepper gantry1]
carriages: carriage_gantry1
step_pin: PF11
dir_pin: PG3
enable_pin: !PG5
microsteps: 16
rotation_distance: 40
[stepper z0]
carriages: carriage_z0
step_pin: PF9
dir_pin: PF10
enable_pin: !PG2
microsteps: 16
rotation_distance: 8
[extruder]
step_pin: auxboard:PF9
dir_pin: auxboard:PF10
enable_pin: !auxboard:PG2
heater_pin: auxboard:PA0
sensor_pin: auxboard:PF4
microsteps: 16
rotation_distance: 33.500
nozzle_diameter: 0.400
filament_diameter: 1.750
sensor_type: EPCOS 100K B57560G104F
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[extruder1]
step_pin: auxboard:PC13
dir_pin: auxboard:PF0
enable_pin: !auxboard:PF1
heater_pin: auxboard:PA3
sensor_pin: auxboard:PF5
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
sensor_type: EPCOS 100K B57560G104F
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[extruder2]
step_pin: auxboard:PE2
dir_pin: auxboard:PE3
enable_pin: !auxboard:PD4
heater_pin: auxboard:PB0
sensor_pin: auxboard:PF6
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
sensor_type: EPCOS 100K B57560G104F
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[extruder3]
step_pin: auxboard:PE6
dir_pin: auxboard:PA14
enable_pin: !auxboard:PE0
heater_pin: auxboard:PB11
sensor_pin: auxboard:PF7
microsteps: 16
rotation_distance: 33.5
nozzle_diameter: 0.400
filament_diameter: 1.750
sensor_type: EPCOS 100K B57560G104F
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[gcode_macro PARK_EXTRUDERS]
gcode:
G90
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
G1 Y{printer.configfile.settings["carriage carriage_gantry0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3
G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000
[gcode_macro T0]
gcode:
PARK_EXTRUDERS
ACTIVATE_EXTRUDER EXTRUDER=extruder
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
[gcode_macro T1]
gcode:
PARK_EXTRUDERS
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder1
ACTIVATE_EXTRUDER EXTRUDER=extruder1
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
[gcode_macro T2]
gcode:
PARK_EXTRUDERS
SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder2
ACTIVATE_EXTRUDER EXTRUDER=extruder2
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
[gcode_macro T3]
gcode:
PARK_EXTRUDERS
SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder3
ACTIVATE_EXTRUDER EXTRUDER=extruder3
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
[gcode_macro SET_COPY_MODE]
gcode:
G90
{% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0"].position_min) %}
{% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %}
{% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %}
{% set x_center = 0.5 * (x_max + x_min) %}
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
G1 Y{printer.configfile.settings["carriage carriage_gantry0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
G1 Y{y_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3
G1 X{x_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
G1 X{x_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY
ACTIVATE_EXTRUDER EXTRUDER=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder
[gcode_macro SET_MIRROR_MODE1]
gcode:
G90
{% set y_center = 0.5 * (printer.configfile.settings["dual_carriage carriage_gantry1"].position_max + printer.configfile.settings["carriage carriage_gantry0"].position_min) %}
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
G1 Y{printer.configfile.settings["carriage carriage_gantry0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
G1 Y{y_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3
G1 X{printer.configfile.settings["dual_carriage carriage_t3"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
G1 X{printer.configfile.settings["dual_carriage carriage_t1"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=MIRROR
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=MIRROR
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=COPY
ACTIVATE_EXTRUDER EXTRUDER=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder
[gcode_macro SET_MIRROR_MODE2]
gcode:
G90
{% set x_max = [printer.configfile.settings["dual_carriage carriage_t3"].position_max, printer.configfile.settings["dual_carriage carriage_t1"].position_max]|min %}
{% set x_min = [printer.configfile.settings["dual_carriage carriage_t2"].position_min, printer.configfile.settings["carriage carriage_t0"].position_min]|max %}
{% set x_center = 0.5 * (x_max + x_min) %}
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
G1 Y{printer.configfile.settings["carriage carriage_gantry0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
G1 Y{printer.configfile.settings["dual_carriage carriage_gantry1"].position_max} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
G1 X{printer.configfile.settings["dual_carriage carriage_t2"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
G1 X{printer.configfile.settings["carriage carriage_t0"].position_min} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3
G1 X{x_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
G1 X{x_center} F12000
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1 MODE=MIRROR
ACTIVATE_EXTRUDER EXTRUDER=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder1 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder2 MOTION_QUEUE=extruder
SYNC_EXTRUDER_MOTION EXTRUDER=extruder3 MOTION_QUEUE=extruder
[printer]
kinematics: generic_cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
## An optional input shaper support
#[input_shaper]
## The section is intentionally empty
#
#[delayed_gcode init_shaper]
#initial_duration: 0.1
#gcode:
# SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry1
# SET_DUAL_CARRIAGE CARRIAGE=carriage_t3
# SET_INPUT_SHAPER SHAPER_TYPE_X=<t3_shaper> SHAPER_FREQ_X=<t3_freq> SHAPER_TYPE_Y=<gantry1_shaper> SHAPER_FREQ_Y=<gantry1_freq>
# SET_DUAL_CARRIAGE CARRIAGE=carriage_t2
# SET_INPUT_SHAPER SHAPER_TYPE_X=<t2_shaper> SHAPER_FREQ_X=<t2_freq> SHAPER_TYPE_Y=<gantry1_shaper> SHAPER_FREQ_Y=<gantry1_freq>
# SET_DUAL_CARRIAGE CARRIAGE=carriage_gantry0
# SET_DUAL_CARRIAGE CARRIAGE=carriage_t1
# SET_INPUT_SHAPER SHAPER_TYPE_X=<t1_shaper> SHAPER_FREQ_X=<t1_freq> SHAPER_TYPE_Y=<gantry0_shaper> SHAPER_FREQ_Y=<gantry0_freq>
# SET_DUAL_CARRIAGE CARRIAGE=carriage_t0
# SET_INPUT_SHAPER SHAPER_TYPE_X=<t0_shaper> SHAPER_FREQ_X=<t0_freq> SHAPER_TYPE_Y=<gantry0_shaper> SHAPER_FREQ_Y=<gantry0_freq>

View file

@ -8,6 +8,10 @@ All dates in this document are approximate.
## Changes
20251229: `SET_DUAL_CARRIAGE CARRIAGE=<carriage_name> MODE=<mode>`
command for modes `COPY` and `MIRROR` now requires that another
carriage is activated as `PRIMARY` first for the corresponding axis.
20251122: An option `axis` has been added to `[carriage <name>]` sections
for `generic_cartesian` kinematics, allowing arbitrary names for primary
carriages. Users are encouraged to explicitly specify `axis` option now.

View file

@ -2445,6 +2445,12 @@ axis:
# See the "stepper" section for the definition of the above parameters.
```
`[dual_carriage]` is also supported with `generic_cartesian` kinematic,
in which case it can be thought of as any additional carriage of an axis
that can be moved independently from the primary `[carriage]` of that axis.
The main difference between `[carriage]` and `[dual_carriage]` is that
the latter is not activated by default after the printer startup or
homing, and must be enabled explicitly via `SET_DUAL_CARRIAGE` command.
For an example of dual carriage configuration with `generic_cartesian`
kinematic, see the following configuration
[sample](../config/example-generic-caretesian.cfg).
@ -2524,9 +2530,12 @@ Note that `SHAPER_TYPE_Y` and `SHAPER_FREQ_Y` must be the same in both
commands in this case, since the same motors drive Y axis when either
of the `carriage_x` and `carriage_u` carriages are active.
It is worth noting that `generic_cartesian` kinematic can support two
dual carriages for X and Y axes. For reference, see for instance a
[sample](../config/sample-corexyuv.cfg) of CoreXYUV configuration.
It is worth noting that `generic_cartesian` kinematic can support more
than a single `[dual_carriage]`, e.g. dual-gantry setups with one or
two independent carriages per gantry. For reference, see for instance
[CoreXYUV](../config/sample-corexyuv.cfg) or
[IQEX](../config/sample-iqex.cfg) (independent quad extruders)
printers sample configurations.
### [extruder_stepper]

View file

@ -244,6 +244,7 @@ class DualCarriages:
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
carriage_str = gcmd.get('CARRIAGE', None)
index = None
if carriage_str is None:
raise gcmd.error('CARRIAGE must be specified')
if carriage_str in self.dc_rails:
@ -265,9 +266,6 @@ class DualCarriages:
if mode not in self.VALID_MODES:
raise gcmd.error("Invalid mode=%s specified" % (mode,))
if mode in [COPY, MIRROR]:
if self.primary_mode_dcs[dc_rail.axis] in [None, dc_rail]:
raise gcmd.error(
"Must activate another carriage as PRIMARY first")
curtime = self.printer.get_reactor().monotonic()
kin = self.printer.lookup_object('toolhead').get_kinematics()
axis = 'xyz'[dc_rail.axis]
@ -275,6 +273,12 @@ class DualCarriages:
raise gcmd.error(
"Axis %s must be homed prior to enabling mode=%s" %
(axis.upper(), mode))
if index is not None:
self.toggle_active_dc_rail(
self.get_dc_rail_wrapper(dc_rail.dual_rail))
elif self.primary_mode_dcs[dc_rail.axis] in [None, dc_rail]:
raise gcmd.error(
"Must activate another carriage as PRIMARY first")
self.activate_dc_mode(dc_rail, mode)
cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
"Save dual carriages modes and positions"