Faster QUAD_GANTRY_LEVEL Macro

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:

[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.

7 Likes

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

1 Like

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.

1 Like

Hi, is there also this option for Z_TILT_ADJUST ?

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

Hi, so it could look like this?

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

[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 %}
    
1 Like

That looks correct, just one thought:

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?

This is brilliant! :raised_hands:

Thank you so much @garethky and @DrumClock

1 Like

It’s better to use relative HORISONTAL_MOVE:

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

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?

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.

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 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.

1 Like

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.

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.

it returns " Option ‘probe_speed’ is not valid in section ‘quad_gantry_level’ "

In other words it does not like your idea :face_with_peeking_eye:

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:

# 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 %}

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 (11.9 KB)

[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 :face_with_diagonal_mouth:

1 Like

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 :face_with_peeking_eye:
Thanks for looking, it is much appreciated. I will try this tonight after work.

Works a treat, thank you so much1

1 Like