Step 13.52 — Add the scrubber macros¶
Step 52 of 54 · Chapter 13 — Initial startup
(no image — see text)
Parts: none.
Do: Paste the block below under [gcode_macro PRINT_END]. Replace the four placeholder values with the numbers you wrote down, then SAVE & RESTART.
#####################################################################
# Nozzle scrubber — ADDED at Ch 13 (add-on). Geometry and macro logic
# adapted from jinetix, "Voron 2.4 Silicone Nozzle Scrubber w/ Sheet
# Stops", Printables 796563, macro revision 2025-12-22, CC BY-NC-SA 4.0.
# Changes: one variable block instead of two copies; bucket on the LEFT
# (mirrored fit); the circle radius and centre are clamped to this
# machine's Y overtravel; XY moves happen at park height and Z drops only
# over the bucket (the author's Z-then-XY order drags the nozzle at scrub
# height); refuses to run until the values are measured.
#####################################################################
[gcode_arcs]
resolution: 0.1
[gcode_macro _SCRUB]
description: Scrubber geometry. Values only; read by NOZZLE_PARK_BUCKET and NOZZLE_CLEAN.
## Every value marked (verify on bench) is measured on YOUR machine.
## The placeholders below make both macros refuse to run.
variable_brush_x_min: -1.0 # (verify on bench) X, nozzle over the OUTBOARD (left) bristle edge
variable_brush_x_max: -1.0 # (verify on bench) X, nozzle over the INBOARD (right) bristle edge
variable_edge_y: -1.0 # (verify on bench) Y, nozzle tip exactly over the plate's rear edge
variable_z_scrub: 99.0 # (verify on bench) Z of the bristle tops minus 0.5
variable_bucket_offset: 10.0 # park this far outboard of brush_x_min, over the bucket
variable_park_z: 5.0
variable_circle_r: 2.0 # largest scrub circle; shrunk to fit the Y overtravel
variable_passes: 5
variable_circles: 2
variable_flicks: 5
variable_flick_depth: 3.0
variable_travel_speed: 9000
variable_flick_speed: 9000
variable_scrub_speed: 3000
variable_z_speed: 1500
gcode:
{action_respond_info("_SCRUB only holds the scrubber's measured values")}
[gcode_macro NOZZLE_PARK_BUCKET]
description: Lift, then park the nozzle over the purge bucket.
gcode:
{% set s = printer["gcode_macro _SCRUB"] %}
{% if s.brush_x_min < 0 or s.brush_x_max <= s.brush_x_min or s.edge_y < 0 or s.z_scrub > 10 %}
{action_raise_error("Scrubber not measured yet: fill in [gcode_macro _SCRUB]")}
{% endif %}
{% if printer.toolhead.homed_axes != "xyz" %}
{action_raise_error("Scrubber: home all three axes first")}
{% endif %}
{% set ymax = printer.toolhead.axis_maximum.y - 0.5 %}
{% set ylo = s.edge_y + 1.5 %}
{% set r = [s.circle_r, (ymax - ylo) / 2.0]|min %}
{% set cy = ylo + r %}
G90
G1 Z{[printer.toolhead.position.z, s.park_z]|max} F{s.z_speed}
G1 X{s.brush_x_min - s.bucket_offset} Y{cy} F{s.travel_speed}
G1 Z{s.park_z} F{s.z_speed}
[gcode_macro NOZZLE_CLEAN]
description: Flick ooze into the bucket, then scrub the nozzle on the silicone brush.
gcode:
{% set s = printer["gcode_macro _SCRUB"] %}
{% set ymax = printer.toolhead.axis_maximum.y - 0.5 %}
{% set ylo = s.edge_y + 1.5 %}
{% set r = [s.circle_r, (ymax - ylo) / 2.0]|min %}
{% if r < 0.75 %}
{action_raise_error("Scrubber: Y overtravel past the plate is too short for the brush")}
{% endif %}
{% set cy = ylo + r %}
{% set bucket_x = s.brush_x_min - s.bucket_offset %}
{% set flick_x = s.brush_x_min + s.flick_depth %}
{% set x0 = s.brush_x_min + 0.5 %}
{% set x1 = s.brush_x_max - 2 * r - 0.5 %}
{% set dx = (x1 - x0) / (s.passes - 1) if s.passes > 1 else 0 %}
NOZZLE_PARK_BUCKET
G90
G1 Z{s.z_scrub} F{s.z_speed} ; down over the bucket, never over the brush
{% for i in range(s.flicks) %}
G1 X{flick_x} F{s.flick_speed} ; into the outboard bristles
G4 P20
G1 X{bucket_x} F{s.flick_speed * 1.5} ; snap back: the string drops in the bucket
{% endfor %}
{% for i in range(s.passes) %}
{% set x = x0 + i * dx %}
G1 X{x} Y{cy} F{s.travel_speed}
{% for j in range(s.circles) %}
{% if (i + j) % 2 == 1 %}
G3 X{x} Y{cy} I{r} J0 F{s.scrub_speed}
{% else %}
G2 X{x} Y{cy} I{r} J0 F{s.scrub_speed}
{% endif %}
{% endfor %}
{% endfor %}
G1 Z{s.park_z} F{s.z_speed} ; lift straight up off the brush
Check
Klipper restarts without an error, and NOZZLE_PARK_BUCKET parks the nozzle over the bucket at Z5.
What you're looking at
[gcode_arcs] lets Klipper run the small G2/G3 circles of the scrub. _SCRUB holds your numbers; NOZZLE_PARK_BUCKET and NOZZLE_CLEAN read them and refuse to run while any is still a placeholder.
Tip
The macro sizes its scrub circles to fit your reach past the plate, from 2 mm radius down to 0.75 mm.
Source: Printables 796563 — macros, revision 2025-12-22 · Klipper docs § gcode_arcs · Klipper docs § Command templates