Saving and adjusting per-build-surface Z offsets

I have a couple of different build surfaces I use, and they require different Z offsets. On one side of my flex plate I have a textured PEI surface, and on the other a smooth PEI sheet. I found setting and storing the Z offset to be really tedious. Fortunately I was able to use the menu system to build a system for choosing a build surface from a preconfigured list, save the Z offset during live tuning, and apply the offset automatically at the start of a print. I’m using the save_variables config option; modification to these variables via the SAVE_VARIABLE command are applied immediately and accessible when referencing the printer.save_variables.variables dict.

Changing the bed surface and applying its stored Z offset is as simple as going to the Setup menu on your attached display and choosing from the Bed: list.

setup

printer.cfg

[save_variables]
filename: ~/klipper_config/saved_vars.cfg

saved_vars.cfg

Populate bed_surfaces with a list of your print surfaces. These need to be variables, not friendly names; if you’ve got multiple smooth surfaces, name them something like smooth_01 and smooth_02. Note the format: the value needs to be an eval-able Python string that returns a list of strings.

Set the selected_bed_surface key to one of the entries in the bed_surfaces list, and note the quotes around the value.

For each item in the bed_surfaces list, create a saved variable prefixed with bed_surface_offsets.: these will store the per-surface offset values.

Below is a slightly-formatted version of my current vars file:

[Variables]
bed_surfaces = ['smooth', 'textured']

selected_bed_surface = 'smooth'

bed_surface_offsets.smooth = -0.185
bed_surface_offsets.textured = -0.17

menus

;; choose a bed surface from a pre-configured list
[menu __main __setup __bed_surface]
type: input
name: Bed: { printer.save_variables.variables.bed_surfaces[menu.input | int] }
input: { printer.save_variables.variables.bed_surfaces.index(printer.save_variables.variables.selected_bed_surface) | float }
input_min: 0.0
input_max: { ((printer.save_variables.variables.bed_surfaces | length) - 1) | float }
input_step: 1.0
gcode:
    SAVE_VARIABLE VARIABLE=selected_bed_surface VALUE="'{ printer.save_variables.variables.bed_surfaces[menu.input | int] }'"

;; tunes the Z offset from the setup menu, saves the offset
;; can be performed while not actively printing
[menu __main __setup __offsetz]
type: input
name: Offset Z:{ '%05.3f' % menu.input }
input: { printer.save_variables.variables["bed_surface_offsets." + printer.save_variables.variables.selected_bed_surface] | float }
input_min: -5
input_max: 5
input_step: 0.005
realtime: True
gcode:
    RESPOND TYPE=command MSG="{ 'bed surface: %s, offset: %r' % (printer.save_variables.variables.selected_bed_surface, menu.input) }"
    SET_GCODE_OFFSET Z={ '%.3f' % menu.input } MOVE={ 1 if printer.toolhead.homed_axes == 'XYZ' else 0 }
    SAVE_VARIABLE VARIABLE=bed_surface_offsets.{ printer.save_variables.variables.selected_bed_surface } VALUE={ '%.3f' % menu.input }

;; tunes the Z offset from the tune menu, saves the offset
[menu __main __tune __offsetz]
type: input
name: Offset Z:{ '%05.3f' % menu.input }
input: { printer.gcode_move.homing_origin.z }
input_min: -5
input_max: 5
input_step: 0.005
realtime: True
gcode:
    RESPOND TYPE=command MSG="{ 'bed surface: %s, offset: %r' % (printer.save_variables.variables.selected_bed_surface, menu.input) }"
    SET_GCODE_OFFSET Z={ '%.3f' % menu.input } MOVE=1
    SAVE_VARIABLE VARIABLE=bed_surface_offsets.{ printer.save_variables.variables.selected_bed_surface } VALUE={ '%.3f' % menu.input }

PRINT_START gcode

gcode:
    {% set svv = printer.save_variables.variables %}

    …

    ;; apply Z offset for configured bed surface
    RESPOND TYPE=command MSG="{ 'bed surface: %s, offset: %r' % (svv.selected_bed_surface, svv['bed_surface_offsets.' + svv.selected_bed_surface]) }"
    SET_GCODE_OFFSET MOVE=1 Z={ '%.3f' % (svv["bed_surface_offsets." + svv.selected_bed_surface] | float) }

    …

Great to see save_variables being used in the real world. This was my first python project :smile:

1 Like

I love it! I find it super useful and it’s a much more flexible system than writing bytes to eeprom! I do kind of wish it was using json for storage, but this format is certainly consistent with the other config files. Thank you for your work!

Glad you like it. Could not have done it without Kevin’s help though.