# Faster QUAD\_GANTRY\_LEVEL Macro

**URL:** <https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044>\
**Category:** Macros\
**Created:** [September 26, 2024, 5:54pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044 "2024-09-26T17:54:12Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [September 26, 2024, 5:54pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/1 "2024-09-26T17:54:12Z")

</div>

Ever wished QGL took less time? The main problem is that the initial probing height needs to be very tall (10mm-20mm) so that the machine doesn’t crash. But after that first course adjustment the machine is usually close enough to level to allow probing from a much smaller height.

The `QUAD_GANTRY_LEVEL` command isn’t documented but there are options you can pass to it to override its behavior. This is the missing manual:

#### QUAD\_GANTRY\_LEVEL

`QUAD_GANTRY_LEVEL [HORIZONTAL_MOVE_Z=<value>] [RETRY_TOLERANCE=<value>] RETRIES=<value>`

- `HORIZONTAL_MOVE_Z` - A floating point value in mm that overrides the `horizontal_move_z` option specified in the config file.
- `RETRY_TOLERANCE` - A floating point value between 0 and 1 that overrides the `retry_tolerance` value in the config.
- `RETRIES` - The maximum number of retries to perform, overrides the `retries` value in the config

And here is a pair of GCode Macros that switch from course adjustment at the default configured height to fine adjustment at a lower height:

```auto
[gcode_macro QUAD_GANTRY_LEVEL]
rename_existing: _QUAD_GANTRY_LEVEL
gcode:
    # If QGL is not applied, first run a course calibration
    {% if printer.quad_gantry_level.applied == False %}
        _QUAD_GANTRY_LEVEL RETRY_TOLERANCE=1.0
    {% endif %}
    # then perform fine QGL down to desired spec
    # this has to be a separate macro call so the results of the above call will be visible!
    _FINE_QUAD_GANTRY_LEVEL

[gcode_macro _FINE_QUAD_GANTRY_LEVEL]
gcode:
    {% if printer.quad_gantry_level.applied == True %}
        # go for full quality at reduced probing height
        _QUAD_GANTRY_LEVEL HORIZONTAL_MOVE_Z=1.0 # <- set your preferred probing height here!
    {% else %}
        # This should never happen, just perform the full calibration using the defaults
        {action_respond_info("Fine QGL called without calling course QGL first!")}
        _QUAD_GANTRY_LEVEL # default behavior, no speedup
    {% endif %}

```

If QGL is already applied, it always runs at the lower height, skipping the course adjustment.

---

<div class="post-metadata">

**Author:** ![Jedi](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/jedi/32/14765_2.png) [@Jedi](https://klipper.discourse.group/u/Jedi)\
**Post date:** [October 2, 2024, 12:41pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/2 "2024-10-02T12:41:18Z")

</div>

Cant believe this hasnt got more party time action - its fabulous - thankyou!

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [October 2, 2024, 4:39pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/3 "2024-10-02T16:39:54Z")

</div>

Note: that this could be even more clever if the QGL module exposed the achieved accuracy on the last pass in its status model. E.g. if the accuracy was better than the desired accuracy after the course pass you could skip the fine pass. But this is often a speedup and doesn’t require any changes to klipper to work.

---

<div class="post-metadata">

**Author:** ![DrumClock](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/drumclock/32/177_2.png) [@DrumClock](https://klipper.discourse.group/u/DrumClock)\
**Post date:** [October 28, 2024, 6:47pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/4 "2024-10-28T18:47:11Z")

</div>

Hi, is there also this option for Z\_TILT\_ADJUST ?

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [October 28, 2024, 8:03pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/5 "2024-10-28T20:03:42Z")

</div>

Looks like it will work. `Z_TILT_ADJUST` also reads a `RETRY_TOLERANCE` parameter that’s not documented.

---

<div class="post-metadata">

**Author:** ![DrumClock](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/drumclock/32/177_2.png) [@DrumClock](https://klipper.discourse.group/u/DrumClock)\
**Post date:** [October 29, 2024, 6:51am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/6 "2024-10-29T06:51:22Z")

</div>

Hi, so it could look like this?

```auto
 [z_tilt]
 ...  
 speed: 300  
 horizontal_move_z: 4
 retries: 6
 retry_tolerance: 0.010  

```

```auto
[gcode_macro Z_TILT_ADJUST]
rename_existing: _Z_TILT_ADJUST
gcode:
    # If Z_TILT is not applied, first run a course calibration
    {% if printer.z_tilt.applied == False %}
        _Z_TILT_ADJUST RETRY_TOLERANCE=1.0 HORIZONTAL_MOVE_Z=15 RETRIES=1
    {% endif %}
    # then perform fine Z_TILT down to desired spec
    # this has to be a separate macro call so the results of the above call will be visible!
    _FINE_Z_TILT_ADJUST
	

[gcode_macro _FINE_Z_TILT_ADJUST]
gcode:
    {% if printer.z_tilt.applied == True %}
        # go for full quality at reduced probing height
        _Z_TILT_ADJUST RETRY_TOLERANCE=0.010 HORIZONTAL_MOVE_Z=1 RETRIES=6 
    {% else %}
        # This should never happen, just perform the full calibration using the defaults
        {action_respond_info("Fine Z_TILT called without calling course Z_TILT first!")}
        _Z_TILT_ADJUST # default behavior, no speedup
    {% endif %}
    

```

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [October 29, 2024, 4:13pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/7 "2024-10-29T16:13:54Z")

</div>

That looks correct, just one thought:

> [@DrumClock](#):
>
> ```auto
> _Z_TILT_ADJUST RETRY_TOLERANCE=1.0 HORIZONTAL_MOVE_Z=15 RETRIES=1
> 
> ```

You don’t want to set `RETRIES=1`. Its going to quit when it achieves the requested tolerance. And it will be slightly better because it makes a final adjustment after the last measurement.

Not sure if the macro will stop if the course phase fails?

---

<div class="post-metadata">

**Author:** ![rveda](https://avatars.discourse-cdn.com/v4/letter/r/edb3f5/32.png) [@rveda](https://klipper.discourse.group/u/rveda)\
**Post date:** [November 5, 2024, 10:27am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/8 "2024-11-05T10:27:35Z")

</div>

This is brilliant! 🙌

Thank you so much @garethky and @DrumClock

---

<div class="post-metadata">

**Author:** ![Ogurezzz](https://avatars.discourse-cdn.com/v4/letter/o/3ab097/32.png) [@Ogurezzz](https://klipper.discourse.group/u/Ogurezzz)\
**Post date:** [March 6, 2025, 9:39am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/10 "2025-03-06T09:39:29Z")

</div>

It’s better to use relative HORISONTAL\_MOVE:

```auto
_QUAD_GANTRY_LEVEL HORIZONTAL_MOVE_Z={printer.configfile.settings.probe.z_offset|float + 1.0}

```

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 2, 2025, 5:20pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/11 "2025-06-02T17:20:22Z")

</div>

Works a treat when I set second height to 5. Problem is still the z speed when probing. I use a cartographer and this thing can probe the bed at much higher speed then the default QGL does. Do you know a way to set this speed so it uses the capability of the cartographer to the fullest?

---

<div class="post-metadata">

**Author:** ![Sineos](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sineos/32/18_2.png) [@Sineos](https://klipper.discourse.group/u/Sineos)\
**Post date:** [June 2, 2025, 5:51pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/12 "2025-06-02T17:51:09Z")

</div>

> [@3dcase](#):
>
> I use a cartographer and this thing can probe the bed at much higher speed then the default QGL does.

My experience with this type of probe is limited, but I urge caution:

- Bed probing in the sense of bed meshing is something totally different.
- QGL is more of a “homing move” than a meshing move, and if I understand the approach correctly, it is totally different from the logic used for meshing the bed.
- I would expect the “homing moves” to be actually quite descend-velocity and potentially even lift-speed dependent.

I would carefully collect some data before trying to replicate the meshing speed onto the “homing” speed.

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [June 2, 2025, 6:16pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/13 "2025-06-02T18:16:19Z")

</div>

I actually did this with the load cell recently. You can pass the `PROBE_SPEED=5` argument to `QUAD_GANTRY_LEVEL` to override the probing speed.

There is a lot to be said for the idea that faster speeds shake the printer and reduce accuracy. A better idea is to use lower speeds but for much shorter distances for the same speedup. e.g. my `sample_retract_distance` is 0.3mm and my standard probing speed is 2mm/s.

I have a [thread](https://klipper.discourse.group/t/adaptive-horizontal-move-z-for-bed-mesh/23455) about making the `horizontal_move_z` a relative move so it can adapt to taco bed shapes. IMHO, the focus on fast probing exists largely because the current `horizontal_move_z` behavior in klipper is bad for automated probes.

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 3, 2025, 3:12am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/14 "2025-06-03T03:12:06Z")

</div>

Yes I don’t mean to sweep instead of probe at a high horizontal speed. When the machine homes z, it does also do a downwards probe like move, which is faster then the probing speed in z while doing the QGL.

So the big losses are from the high distance it is doing this repeatedly, which is now sorted by the new QGL macro from @garethky , and secondly this up and down speed, which is really slow. I want to get to the same z probing speed as it uses when z homing with the cartographer.

There are also noticable delays between moves but I suppose it does need process time so we will leave them for now.

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 3, 2025, 3:15am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/15 "2025-06-03T03:15:37Z")

</div>

Thank you, will pass that speed in QGL and experiment. It already is faster now of course.  
If I can match the speed it uses for the homing probe I am happy tbh.  
Yes I agree totally that slow speeds are a remnant of mechanical switches that have a large range as accuracy, but eventually things will catch up.

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 3, 2025, 3:40pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/16 "2025-06-03T15:40:16Z")

</div>

> [@garethky](#):
>
> PROBE\_SPEED=5

it returns " Option ‘probe\_speed’ is not valid in section ‘quad\_gantry\_level’ "

In other words it does not like your idea 🫣

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [June 3, 2025, 4:02pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/17 "2025-06-03T16:02:37Z")

</div>

Should do? If you are using the macro in this thread, it renames the real QGL command, so you have to pass it to the original command:

```auto
# If QGL is not applied, first run a coarse calibration
{% if printer.quad_gantry_level.applied == False %}
    _QUAD_GANTRY_LEVEL RETRY_TOLERANCE=1.0 PROBE_SPEED=5
{% endif %}

```

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 3, 2025, 4:12pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/18 "2025-06-03T16:12:09Z")

</div>

Ahh, maybe I did it wrong even though your new code works because it does the second pass on half the height. I put the speed line in the old QGL.

Here is my cfg. Would you please check if I made a mess of it?  
[config-20250603-190955.zip](https://klipper.discourse.group/uploads/short-url/ilCuEDBPvhT7ivIdeCJnH4IN8i1.zip) (11.9 KB)

---

<div class="post-metadata">

**Author:** ![garethky](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/garethky/32/956_2.png) [@garethky](https://klipper.discourse.group/u/garethky)\
**Post date:** [June 3, 2025, 5:09pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/19 "2025-06-03T17:09:32Z")

</div>

```auto
[quad_gantry_level]
##PROBE_SPEED=5 ##throws an error!

```

`PROBE_SPEED` is the GCODE argument to the `PROBE` or `QUAD_GANTRY_LEVEL` GCcode commands. You have to put it in the GCode macro.

We generally try to be clear about gcode vs config by putting gcode stuff in `ALL_CAPS`.

But I also sympathize that the naming here is confusing.

> **Sidebar**  
> `speed` and `PROBE_SPEED` change the same thing. But they don’t have the same name, because modules like `BED_MESH_CALIBRATE` also take a `speed`/`SPEED` setting. In that case its the x/y speed to move between probe points. The GCode argument name `PROBE_SPEED` is used instead, so it can be passed to `BED_MESH_CALIBRATE` and not conflict with its `SPEED` argument. This is… a confusing foot gun 🫤

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 4, 2025, 2:50am UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/20 "2025-06-04T02:50:34Z")

</div>

When you are not a coder, it is very confusing. Its like electronics I suppose, you need to learn it early and keep doing it every day. I am a mechanical dinosaur so I live in another universe 🫣  
Thanks for looking, it is much appreciated. I will try this tonight after work.

---

<div class="post-metadata">

**Author:** ![3dcase](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/3dcase/32/7797_2.png) [@3dcase](https://klipper.discourse.group/u/3dcase)\
**Post date:** [June 4, 2025, 2:47pm UTC](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044/21 "2025-06-04T14:47:29Z")

</div>

> [@garethky](#):
>
> `PROBE_SPEED=5`

Works a treat, thank you so much1

[Next page](https://klipper.discourse.group/t/faster-quad-gantry-level-macro/19044.md?page=2)
