# Full dual gantry support: IQEX and ITEX printers

**URL:** <https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101>\
**Category:** Developers\
**Created:** [October 27, 2025, 12:05am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101 "2025-10-27T00:05:55Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [October 27, 2025, 12:05am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/1 "2025-10-27T00:05:55Z")

</div>

If you happen to have a dual-gantry IQUEX (Independent Quad EXtruder) or ITEX (Independent Triple EXtruder) printer and want to test it under Klipper, I’d like to invite you to test a new experimental Klipper [branch](https://github.com/dmbutyugin/klipper/tree/generic-cartesian-gantry) that adds a support of such printers under `generic_cartesian` kinematics. A general principle for configuring such printers is to define printer gantries as normal `carriage` and `dual_carriage`, e.g.

```auto
[carriage carriage_y0]
axis: y
...

[dual_carriage carriage_y1]
primary_carriage: carriage_y0
safe_distance: 50 # safe distance between y0 and y1
...

```

and then define carriages on these gantries as, e.g.

```auto
[carriage carriage_t0]
axis: x
gantry: carriage_y0
...

[carriage carriage_t1]
axis: x
gantry: carriage_y1
...

```

And then you can define dual carriages for them as needed, e.g.

```auto
[dual_carriage carriage_t2]
primary_carriage: carriage_t0
safe_distance: 70 # safe distance between t0 and t2
...

#If present:
#[dual_carriage carriage_t3]
#primary_carriage: carriage_t1
#safe_distance: 80 # safe distance between t1 and t3
...

```

Then these defined carriages can be enabled as primary or for copy and mirror modes via SET\_DUAL\_CARRIAGE as necessary, e.g.

```auto
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=COPY
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=MIRROR

```

Also keep in mind that currently the first declared `[carriage …]` on each axis gets activated as primary by default when Klipper starts or after homing (e.g. in the example above, carriage `t0` will be activated as primary because it was declared before `t1`). You may want to explicitly activate a specific carriage though, e.g. as a part of a homing procedure.

More complete configs with some useful macros can be found [here](https://github.com/dmbutyugin/klipper/blob/generic-cartesian-gantry/test/klippy/generic_cartesian_iqex.cfg) for IQEX and [here](https://github.com/dmbutyugin/klipper/blob/generic-cartesian-gantry/test/klippy/generic_cartesian_itex.test) for ITEX printers.

Please note that I do not have such printers, so while I tested this branch to some degree by artificially adjusting my IDEX printer config, bugs are still quite possible, so exercise caution and be ready to hit emergency stop button if something goes wrong, especially in the beginning.

---

<div class="post-metadata">

**Author:** ![Dan3dp](https://avatars.discourse-cdn.com/v4/letter/d/65b543/32.png) [@Dan3dp](https://klipper.discourse.group/u/Dan3dp)\
**Post date:** [October 28, 2025, 8:16pm UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/2 "2025-10-28T20:16:18Z")

</div>

Hey, this looks very interesting! It might actually solve the blockage I’ve been stuck with on my Xplorer project.

This printer can be built as a Single Extruder, IDEX, Dual Gantry, or IQEX. For the IQEX configuration, I wasn’t able to create the necessary configs to make it work — the furthest I got was getting Dual Gantry working using standard Klipper with its `generic_cartesian` setup.

I’ll definitely give this branch a try because I have an IQEX Xplorer sitting here collecting dust.

By the way, the Xplorer project is on Discord, and the kits are sold by Formbot.

Check the links below:

> **[GitHub - FORMBOT/Xplorer](https://github.com/FORMBOT/Xplorer)**
>
> Contribute to FORMBOT/Xplorer development by creating an account on GitHub.

> **[Xplorer Multifunctional Independent Quad Extruder (IQEX) 3D Printer Kit with...](https://www.formbot3d.com/products/xplorer-multifunctional-independent-quad-extruder-iqex-3d-printer-kit-with-large-build-volume?VariantsId=11144)**
>
> Xplorer Multifunctional Independent Quad Extruder (IQEX) 3D Printer Kit with Large Build Volume

---

<div class="post-metadata">

**Author:** ![Dan3dp](https://avatars.discourse-cdn.com/v4/letter/d/65b543/32.png) [@Dan3dp](https://klipper.discourse.group/u/Dan3dp)\
**Post date:** [November 6, 2025, 5:59pm UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/3 "2025-11-06T17:59:06Z")

</div>

So, I’m back again with an update on this topic. I’ve installed this branch on my Xplorer printer, and here’s what I can say so far:

1. It works! The definition of the carriages and tools works correctly.I was able to print successfully.

If curiosity is killing you, check out this video from Discord:[Xplorer IQEX -Formbot Discord](https://discord.com/channels/1128985970486816788/1387739719181402163/1435702058698407966)

1. Copy and Mirror modes on an IDEX-style printer also work — meaning one tool can copy or mirror the movements of the other. **However, having all tools copy or mirror one tool does not work. I keep getting an “out of range” error on the X-axis, but only when I’m within the first half of the motion range (in my case, between 0 and 200). If I issue a command to move past the midpoint — for example, to 250 — the error no longer occurs.**  
I do not have defined any ‘‘_safe\_distance’_’

2. The tool calibration script no longer works. I’m using this one: [https://github.com/viesturz/klipper-toolchanger/blob/main/klipper/extras/tools\_calibrate.py](https://github.com/viesturz/klipper-toolchanger/blob/main/klipper/extras/tools_calibrate.py), but it doesn’t understand `generic_cartesian` components like “gantry” and “carriage.”

---

<div class="post-metadata">

**Author:** ![Dan3dp](https://avatars.discourse-cdn.com/v4/letter/d/65b543/32.png) [@Dan3dp](https://klipper.discourse.group/u/Dan3dp)\
**Post date:** [November 7, 2025, 9:51am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/4 "2025-11-07T09:51:10Z")

</div>

Okay ,

I have to revise something: point 2 from my previous message. Actually, it works — I just had to switch T2 and T3.

[Xplorer IQEX T1, T2, T3 Copy T0-Formbot Discord](https://discord.com/channels/1128985970486816788/1410354162834866217/1436285550033244230)

---

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [November 7, 2025, 9:20pm UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/5 "2025-11-07T21:20:34Z")

</div>

Thanks for the testing! Actually, I realized that the kinematic range calculations were all wrong for this case. I pushed a fix to the branch now. If you could test it, that would be great! Especially if you could configure `safe_distance` values and `position_min`/`position_max` correctly for all carriages, move them to various positions on the build plate, and then manually enable different COPY/MIRROR modes for them and test that they move as expected and the `safe_distance` are respected for moves of all carriages, and also do it for several different positions and different combinations of COPY/MIRROR modes, that would be great! \*You can do this of course without printing anything, just testing the carriage motion is sufficient.

And then perhaps you could also try to test Tri-DEX mode, for example by commenting out the second carriage on the second gantry out of configuration, manually move it to the side, and artificially limit `position_max` of the other carriage to prevent it colliding with the disabled carriage, and then also try various motions in various COPY/MIRROR modes, that would also be very useful! Cause I don’t have means to test neither of these myself, and it is important to validate that the new code actually works as expected.

---

<div class="post-metadata">

**Author:** ![Dan3dp](https://avatars.discourse-cdn.com/v4/letter/d/65b543/32.png) [@Dan3dp](https://klipper.discourse.group/u/Dan3dp)\
**Post date:** [November 10, 2025, 7:21am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/6 "2025-11-10T07:21:14Z")

</div>

okay, I will do that.

please explain me what ‘safe\_distance’ actually does? If i set it to 50 for example, to what refers this 50? 50mm from where to where?

---

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [November 10, 2025, 11:40am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/7 "2025-11-10T11:40:54Z")

</div>

This is configured for a `dual_carriage`, and it is a distance how close you can move it towards the `primary_carriage` via G0/G1 commands. In my example, for instance

```auto
[dual_carriage carriage_t2]
primary_carriage: carriage_t0
safe_distance: 70 # safe distance between t0 and t2 in mm
...

```

it means that you can do

```auto
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
G1 X100
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=PRIMARY
G1 X170 ; this is the closes t2 can get to t0
G1 X169 ; this is too close and will fail

```

or for example

```auto
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
G1 X0
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=PRIMARY
G1 X200
SET_DUAL_CARRIAGE CARRIAGE=carriage_t2 MODE=MIRROR
G1 X65 ; this moves t0 to 65, t2 to 135, leaving 70 mm between them, so it's OK
G1 X70 ; this triggers an error, since this commands 60 mm distance between t0 and t2

```

This also works for dual gantries, for example

```auto
[carriage carriage_y0]
axis: y
...

[dual_carriage carriage_y1]
primary_carriage: carriage_y0
safe_distance: 50 # safe distance between y0 and y1
...

```

will work in exactly the same way for `carriage_y0` and `carriage_y1`, with the minimal distance commanded on Y axis being 50 mm, e.g.

```auto
SET_DUAL_CARRIAGE CARRIAGE=carriage_y0 MODE=PRIMARY
G1 Y100
SET_DUAL_CARRIAGE CARRIAGE=carriage_y1 MODE=PRIMARY
G1 Y150 ; this is the closes y1 can get to y0

```

Note that these safety checks only work between two gantries or the dual carriages on the same gantry, which means that unrelated carriages can move however you want to, e.g.

```auto
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
G1 X10
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=PRIMARY
G1 X20 ; no collisions with t0 since they are on different gantries
SET_DUAL_CARRIAGE CARRIAGE=carriage_t0 MODE=PRIMARY
G1 X200
SET_DUAL_CARRIAGE CARRIAGE=carriage_t3 MODE=PRIMARY
G1 X100 ; no collisions with t0 since t3 they are on different gantries (t3 on the same gantry with t1)
SET_DUAL_CARRIAGE CARRIAGE=carriage_t1 MODE=PRIMARY
G1 X40 ; depending on t3's safe_distance, this may trigger an error

```

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [November 14, 2025, 2:29am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/8 "2025-11-14T02:29:43Z")

</div>

Just wanted to jump in and say that this works really well so far! Any plans to submit a PR?

---

<div class="post-metadata">

**Author:** ![Dan3dp](https://avatars.discourse-cdn.com/v4/letter/d/65b543/32.png) [@Dan3dp](https://klipper.discourse.group/u/Dan3dp)\
**Post date:** [November 14, 2025, 8:02am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/9 "2025-11-14T08:02:21Z")

</div>

So, I tested the latest version of the branch.

- _safe\_distance_ works as expected
- Tri-idex (_one gantry with 2 Tools as an IDEX and the second gantry with only 1 Tool)_ also works

I haven’t detect any issue that I can report

---

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [November 14, 2025, 11:19pm UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/10 "2025-11-14T23:19:17Z")

</div>

That’s good to hear, thanks for testing.

@koconnor could you take a look at this feature and provide your feedback? If it looks OK I could clean it up, add the documentation, and open a PR. At high level, this brings the following:

- Let a user define more than a single `[carriage …]` per axis (and corresponding `[dual_carriage …]`); proximity checks are enabled only between a `dual_carriage` and its corresponding primary `carriage`, but not between different `carriage`(s) and `dual_carriage`(s).
- Let a user set any of the carriages as a `PRIMARY` and enable `COPY` or `MIRROR` mode for any of the other carriages (previously only a `dual_carriage` could be set to `COPY` or `MIRROR` mode).

Then, a bit more of an implementation detail, a new parameter `gantry` was added for the primary carriages:

```auto
[carriage carriage_t0]
axis: x
gantry: carriage_y0
...

[carriage carriage_t1]
axis: x
gantry: carriage_y1
...

```

This parameters will play two roles: act as a sanity check to prevent a user accidentally declaring a primary and a dual carriage as primary carriages by making the intent very explicit, and also determine the primary activated carriage after the printer startup and homing (the primary carriage on primary gantry will be activated). However, this may be a bit too hardware-specific, so I’m open to other suggestions and alternatives.

One other alternative that I considered was not specify `gantry` at all, and just define

```auto
[carriage carriage_t0]
axis: x
...

[carriage carriage_t1]
axis: x
...

```

from the example below. That would work too (without added safety), and one just needs to figure out which carriage to activate at startup. This could be the first one declared (though I do not like that too much either as this becomes fragile and e.g. dependent on the includes in the config and such), or a dedicated parameter could be added (but this may look a bit weird too). So, while this is not my option of choice, I could also do that, so let me know what you think of it.

---

<div class="post-metadata">

**Author:** ![koconnor](https://avatars.discourse-cdn.com/v4/letter/k/aca169/32.png) [@koconnor](https://klipper.discourse.group/u/koconnor)\
**Post date:** [November 21, 2025, 12:14am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/11 "2025-11-21T00:14:16Z")

</div>

Interesting, thanks. I took a look through your examples and some of the test cases. I have not reviewed your code.

I’m not all that familiar with IDEX systems, and certainly not with their quad/tri brethren. So, take what I say here “with a grain of salt”.

If I understand correctly, current generic\_cartesian defines three types of carriages:

- `[carriage x]` which defines the “primary” carriage for one of x, y, and z. Today, there must be exactly three of these in the config and they must be named x, y, z.
- `[dual_carriage some_name]` defines optional carriages that may be activated in place of one of the “primary carriages” mentioned above (or in parallel in mirror/dup mode).
- `[extra_carriage my_name]` defines a carriage that homes separately from a “primary carriage”, but is otherwise tied to it.

This proposal adds to the above:

- It is on top of PR #7073. That is, it allows a `[carriage some_name]` with `axis: x`, instead of requiring the name to be one of “x”, “y”, “z”.
- It allows for more than one `[carriage my_tool]` for a given axis. That is, one could have `[carriage x]` with `axis: x` along with `[carriage my_tool_on_x]` with `axis: x`.
- A new “gantry” parameter is added to a `[carriage some_name]` config. It can be set to the name of some other carriage or dual\_carriage.

Did I get that correct? Did I miss anything?

I guess my initial feedback is that the multiple uses of `[carriage somename]` seems a little confusing to me. It no longer means the “primary carriage” for an axis.

Out of curiosity, if a carriage is not the main “primary carriage” activated on startup, why not declare it with `[dual_carriage somename]`? For example:

```kcfg
# The "primary carriage" for x activated at startup
[carriage carriage_t0]
axis: x
...

# An activatable "carriage" paired with t3
[dual_carriage carriage_t1]
axis: x
...

# An activatable carriage paired with t0
[dual_carriage carriage t2]
primary_carriage: carriage_t0
safe_distance: 70 # safe distance between t0 and t2

# Carriage paired with t1
[dual_carriage carriage_t3]
primary_carriage: carriage_t1
safe_distance: 80 # safe distance between t1 and t3

```

Otherwise, my high-level feedback is that the functionality seems useful.

Let me know if I’ve missed something,  
-Kevin

---

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [November 21, 2025, 1:14am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/12 "2025-11-21T01:14:29Z")

</div>

Thanks Kevin. For now, I wanted to get your high-level feedback on the user-facing side of a feature, so that I could make some adjustments before writing the documentation and opening a PR.

> [@koconnor](#):
>
> If I understand correctly, current generic\_cartesian defines three types of carriages:
> 
> - `[carriage x]` which defines the “primary” carriage for one of x, y, and z. Today, there must be exactly three of these in the config and they must be named x, y, z.
> - `[dual_carriage some_name]` defines optional carriages that may be activated in place of one of the “primary carriages” mentioned above (or in parallel in mirror/dup mode).
> - `[extra_carriage my_name]` defines a carriage that homes separately from a “primary carriage”, but is otherwise tied to it.
> 
> This proposal adds to the above:
> 
> - It is on top of PR #7073. That is, it allows a `[carriage some_name]` with `axis: x`, instead of requiring the name to be one of “x”, “y”, “z”.
> - It allows for more than one `[carriage my_tool]` for a given axis. That is, one could have `[carriage x]` with `axis: x` along with `[carriage my_tool_on_x]` with `axis: x`.
> - A new “gantry” parameter is added to a `[carriage some_name]` config. It can be set to the name of some other carriage or dual\_carriage.
> 
> Did I get that correct? Did I miss anything?

Yes, all of that is correct and pretty much summarizes the current primary proposal.

> [@koconnor](#):
>
> I guess my initial feedback is that the multiple uses of `[carriage somename]` seems a little confusing to me. It no longer means the “primary carriage” for an axis.

Well, FWIW, the name of a section says `[carriage …]`, but not `[primary_carriage …]`, so I guess the configuration does not explicitly call out it as ‘primary’?

> [@koconnor](#):
>
> Out of curiosity, if a carriage is not the main “primary carriage” activated on startup, why not declare it with `[dual_carriage somename]`? For example:

Yes, it is possible to go that path, and the proposed approach is fairly flexible and is not tied to ‘gantry’ concept. It would also theoretically not require PR #7073 as a prerequisite. So, it would be a third proposal, I suppose. For completeness, I see a couple of downsides with this proposal:

First, a fairly minor one, is that

```auto
[dual_carriage carriage_t1]
axis: x
...

[dual_carriage carriage_t3]
primary_carriage: carriage_t1
...

```

may look a bit odd, that a _dual_ carriage t1 is declared as a _primary_ carriage for t3. Similar to two `[carriage …]` sections for the same axis, this may be confusing to the users, as to “What exactly does that mean, `dual_carriage carriage_t1` is a `primary_carriage` for a `dual_carriage carriage_t3`? In what sense is it a primary carriage?”

The second issue, a bigger one, in my opinion, is that it may be easy for a non-informed user to write something like

```auto
[carriage carriage_t0]
axis: x
...

[dual_carriage carriage_t1]
axis: x
...

```

in the config for an actual _IDEX_ printer. This configuration is technically valid and will kind of work, but does not match the printer and the user will not get any carriage proximity checks (even if they configured position\_min/position\_max correctly), and they’ll get no errors or warnings about it. FWIW, `generic_cartesian` kinematics may appear complicated to the users, but at least today it has a lot of built-in checks that prevent users from specifying incorrect configuration, e.g. ‘forgetting’ to set some parameters in the configuration. So, this proposal, similarly to my alternative proposal, deviates from that philosophy in that it now becomes possible to create an invalid configuration by a mistake or an overlook and Klipper will silently accept it. Of course, at the end of the day it is always possible to configure something incorrectly, but in this instance the bar for an accidental mistake is rather low.

Please let me know what your thoughts are. I’m not against this proposal per se, but I am worried about the potential user confusion and mistakes.

---

<div class="post-metadata">

**Author:** ![koconnor](https://avatars.discourse-cdn.com/v4/letter/k/aca169/32.png) [@koconnor](https://klipper.discourse.group/u/koconnor)\
**Post date:** [November 21, 2025, 4:39am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/13 "2025-11-21T04:39:03Z")

</div>

Okay, thanks. Just to be clear, I’m not pushing for any particular implementation. I really don’t have enough experience with IDEX (and similar) to provide strong feedback.

> [@dmbutyugin](#):
>
> > [@koconnor](#):
> >
> > I guess my initial feedback is that the multiple uses of `[carriage somename]` seems a little confusing to me. It no longer means the “primary carriage” for an axis.
> 
> Well, FWIW, the name of a section says `[carriage …]`, but not `[primary_carriage …]`, so I guess the configuration does not explicitly call out it as ‘primary’?

Sure. I guess what I was trying to say though was that previously I could form a one or two sentence description of what `[carriage]`, `[dual_carriage]`, and `[extra_carriage]` objects are at a high-level. However, with the proposal I’m not sure I could do that. What would you say would be a few sentence description of the objects? To a regular end user what’s the fundamental distinction between a dual\_carriage and a carriage?

For what it is worth, it took me a few passes reading through the example configs to figure out what they did. In particular the connection from `[carriage t2]` to `carriage_gantry1_left` to `[dual_carriage]` wasn’t obvious to me. It seemed almost like important details of “t2” were defined in some other config object. YMMV.

> [@dmbutyugin](#):
>
> may look a bit odd, that a _dual_ carriage t1 is declared as a _primary_ carriage for t3

Yeah. I guess “primary” could be read as “group leader for this rail”. But, I agree with your observation.

> [@dmbutyugin](#):
>
> This configuration is technically valid and will kind of work, but does not match the printer and the user will not get any carriage proximity checks (even if they configured position\_min/position\_max correctly), and they’ll get no errors or warnings about it.

Well, I guess they’d get an error if they configured a `safe_distance`, but I also guess they wouldn’t get the current default for it. Otherwise is it really an “incorrect” configuration..

For what it is worth, at a somewhat philosophical level I think it’s really really hard for the software to automatically infer the “right” parameters for heavily bespoke printers like this. I guess I’d put “IDEX”, “IQEX”, and “ITEX” in the “it can work” category and not in the “it’ll automatically do what you want” category. Again, YMMV.

Cheers,  
-Kevin

---

<div class="post-metadata">

**Author:** ![koconnor](https://avatars.discourse-cdn.com/v4/letter/k/aca169/32.png) [@koconnor](https://klipper.discourse.group/u/koconnor)\
**Post date:** [November 21, 2025, 5:04am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/14 "2025-11-21T05:04:04Z")

</div>

Oh, thinking about it a little more, I guess I made a leap that going forward a “dual\_carriage” object would be: “an optional carriage that is not enabled at startup, but can be enabled by users at run-time as a replacement for the primary, a dup, a mirror, or similar. It is called “dual” because way back in the day printers only ever had 2 of these carriages on just 1 rail, but now one can have as many as they want on as many rails as they want and enable/disable them whenever they need”.

I guess if one was looking at a “dual\_carriage” object as “one of two carriages on a single rail” then my comments would indeed seem odd.

Again, I’m not pushing for any particular implementation.  
-Kevin

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [November 29, 2025, 5:48am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/15 "2025-11-29T05:48:34Z")

</div>

@dmbutyugin can I help anywhere to keep the momentum moving forward? Haven’t seen anything new to test on fork and the last commit was 3 weeks ago. Here to help! This is such an exciting feature. My iqex build is pumping electrons and extruding all of the plastic. Lemme know where or how I can support the branch! I’m here for it.

---

<div class="post-metadata">

**Author:** ![dmbutyugin](https://avatars.discourse-cdn.com/v4/letter/d/e0b2c6/32.png) [@dmbutyugin](https://klipper.discourse.group/u/dmbutyugin)\
**Post date:** [December 3, 2025, 12:01am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/16 "2025-12-03T00:01:20Z")

</div>

Well, I was busy with other things and could not dedicate time to rework the branch per suggestions. But now this was done, I modified the code in the test branch to follow this configuration:

```klipper-jinja2
# The "primary carriage" for x activated at startup
[carriage carriage_t0]
axis: x
...

# An activatable "carriage" paired with t3
[dual_carriage carriage_t1]
axis: x
...

# An activatable carriage paired with t0
[dual_carriage carriage t2]
primary_carriage: carriage_t0
safe_distance: 70 # safe distance between t0 and t2
...

# Carriage paired with t1
[dual_carriage carriage_t3]
primary_carriage: carriage_t1
safe_distance: 80 # safe distance between t1 and t3
...

```

instead of defining `carriage_t1` as

```klipper-jinja2
[carriage carriage_t1]
axis: x
gantry: carriage_y1

```

Please test the changes and let me know how it works now.

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [December 3, 2025, 4:09am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/17 "2025-12-03T04:09:46Z")

</div>

Alright, just pulled in the changes and testing now. Changed all of the bits I needed in my X configs. Looks like Y configs have no changes. Klipper starts happy. Things of notable observation, expected I’m sure but calling them out as extra data points:

- I had left over configs in t1 concerning safe distance and klipper complained, so I moved all safe distance lines to t2 and t3 definitions.
- I had left over configs for axis designation on t2 and klipper complained, so I removed it and made sure it was only declared on t0 and t1.

After reading and implementing on my end, I like this a bit more than the previous way. I like that we aren’t declaring the Y gantry configs on X definitions. For the dual\_carriage designation on t2 and t3, I wish there was different language there to signify that this is in fact another gantry. Perhaps for the extra gantry carriages could get a special object designation like aux\_carriage or opt\_carriage signifying auxiliary or optional? Just spit balling.

Regardless, I’ll run through a few test prints:

1. Primary, t0 only
2. Primary, t0 and t1 only
3. Copy, t0 and t1 only
4. Copy, all
5. Mirror, t0 and t1 only
6. Mirror, all

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [December 4, 2025, 4:18am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/18 "2025-12-04T04:18:09Z")

</div>

Alright, I was off to a rough start 😅. That whole everything but t0 being designated as a dual\_carriage is really confusing, as mentioned above. So my t0 and t1 share an x rail at the back and my t2 and t3 share a gantry in front. The designation of a carriage where there is a primary == it’s on the same rail as the primary.

I’m straightened out how. The notices I received from klipper before now make more sense:

- safe distances are defined only once per axis, not twice
- Only 1 axis can be defined on a gantry, not twice…which makes perfect sense.

All that said, I’ve managed to make it through most of the prints and they are all working with only having to make adjustments to my x motor configs as defined above in the sample configuration. Looking good from end.

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [December 12, 2025, 2:53pm UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/19 "2025-12-12T14:53:27Z")

</div>

Forgot to sound off with my testing, but I’ve validated the change pretty extensively. Nothing breaking and I’ve adopted my configs to match the latest bits. Aside from the terminology being muddy, it works as expected and certainly brings some pretty exciting functionality to klipper. From a user standpoint, it’s solid. Happy to help test or validate any thing else, but I’d like to think it’s ready for the prime time.

---

<div class="post-metadata">

**Author:** ![transmutated](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/transmutated/32/21095_2.png) [@transmutated](https://klipper.discourse.group/u/transmutated)\
**Post date:** [December 18, 2025, 3:43am UTC](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101/20 "2025-12-18T03:43:33Z")

</div>

@dmbutyugin You mind rebasing the iqex branch with mainline klipper to keep us up to date. I’ll run another round of functional tests, but this change has a pretty small footprint. Best to test though I suppose.

What is the next hurdle to clear before a PR is submitted? What can I do to help?

[Next page](https://klipper.discourse.group/t/full-dual-gantry-support-iqex-and-itex-printers/25101.md?page=2)
