Optional randomization ("fuzz") of probe points for QUAD_GANTRY_LEVEL / Z_TILT to reduce build-surface wear with contact (nozzle) probes

Problem

Contact probes that touch the build surface with the nozzle (Voron TAP, load-cell probes, etc.) probe the same fixed points every time QUAD_GANTRY_LEVEL (and Z_TILT_ADJUST) runs. On a PEI sheet, repeatedly tapping the exact same spots gradually marks/wears the coating there. This typically happens 1–2× per print (e.g. a preheat-time QGL plus a print-start QGL), concentrating the wear on a handful of points.

The points of [quad_gantry_level] / [z_tilt] are static (read from config), and the QUAD_GANTRY_LEVEL command does not accept points as parameters, so there is currently no built-in way to spread this wear.

Prior art

The popular community tool KAMP (Klipper-Adaptive-Meshing-Purging) already addresses this for bed_mesh via a fuzz_amount setting that randomizes the mesh bounds by up to N mm, described as spreading out “wear from nozzle-based probes”. But KAMP only covers the mesh — the QGL / Z-tilt points stay fixed, and those are hit on every leveling, even when no mesh is done.

Proposal

Add an optional per-run jitter to the probe points of [quad_gantry_level] and [z_tilt] (it composes naturally with [bed_mesh] too), e.g.:

[quad_gantry_level]
points:
    50,50
    50,300
    300,300
    300,50
fuzz: 3.0        # max random offset (mm) added to each point's X and Y, per run (0 = off, default)

Behavior:

  • Each configured point gets an independent random offset in [-fuzz, +fuzz] on X and Y, re-rolled on every leveling run (starting from the original points, never accumulating).
  • Offsets are clamped to the axis limits (minus a small margin) so probing never goes out of range.
  • Small magnitudes (2–3 mm) are irrelevant to leveling accuracy — the points stay near the gantry corners — but meaningfully distribute surface wear over time.

Since [quad_gantry_level], [z_tilt] and [bed_mesh] all build their points through ProbePointsHelper, this could live there once and be shared by all three (opt-in, default off → no behavior change for existing configs).

Proof of concept

I implemented this as an out-of-tree extras module that overrides quad_gantry_level.probe_helper.probe_points at runtime before each level, invoked via a rename_existing wrapper on QUAD_GANTRY_LEVEL (mirroring how KAMP wraps BED_MESH_CALIBRATE). Tested on Klipper v0.13.0 with Voron TAP — it works reliably: the QGL probes the jittered points and each leveling touches a slightly different spot.

Code (proof of concept):

Would a native fuzz option in ProbePointsHelper (shared by QGL / Z-tilt / mesh) be welcome? I’m happy to submit a PR.

2 Likes