# Copyright (C) 2022 Justin Schuh # # This file may be distributed under the terms of the GNU GPLv3 license. # # Credit to original inspiration: # https://klipper.discourse.group/t/saving-and-adjusting-per-build-surface-z-offsets/696 [gcode_macro _apply_bed_surface_offset] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% if surfaces.active %} SET_SURFACE_ACTIVE SURFACE={surfaces.active} {% endif %} [gcode_macro _init_surfaces] gcode: {% set km = printer["gcode_macro _km_globals"] %} {% if "bed_surfaces" in printer.save_variables.variables %} {% set old_surfaces = printer.save_variables.variables.bed_surfaces %} {% else %} {% set old_surfaces = { 'active' : '', 'available' : {} } %} {% endif %} {% set settings = printer.configfile.settings %} {% set new_probe_z = (settings.probe | default(settings.bltouch) | default(settings.smart_effector) | default({}) ).z_offset|default(0.0)|float %} {% set new_endstop_z = (settings.stepper_z | default({})).position_endstop | default(0.0)|float %} {% if 'endstop_z' not in old_surfaces %} {% set dummy = old_surfaces.__setitem__('endstop_z', new_endstop_z) %} {% endif %} {% if 'probe_z' not in old_surfaces %} {% set dummy = old_surfaces.__setitem__('probe_z', new_probe_z) %} {% endif %} {% set surfaces = { 'active' : '', 'available' : {}, 'endstop_z' : old_surfaces.endstop_z, 'probe_z' : old_surfaces.probe_z } %} {% for s in km.bed_surfaces %} {% set s = s.split()|join(' ')|lower %} {% if s|length > km.bed_surface_max_name_length or s|list|select("in", " \r\n\"\'")|list %} {action_raise_error('Invalid surface name "%s". Name must be %d or fewer ' 'characters and must not include space or quotation characters' | format(s, km.bed_surface_max_name_length))} {% endif %} {% if s in old_surfaces.available %} {% set dummy = surfaces.available.__setitem__(s, old_surfaces.available[s]) %} {% else %} {% set dummy = surfaces.available.__setitem__(s, {'offset' : 0.0}) %} {% endif %} {% endfor %} {% if old_surfaces.active in surfaces.available %} {% set dummy = surfaces.__setitem__('active', old_surfaces.active) %} {% elif km.bed_surfaces %} {% set dummy = surfaces.__setitem__('active', km.bed_surfaces[0]|lower) %} {% endif %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" _APPLY_BED_SURFACE_OFFSET {% if new_probe_z != surfaces.probe_z or new_endstop_z != surfaces.endstop_z %} { action_respond_info( 'Z probe offset or endstop position changed. Run ADJUST_SURFACE_OFFSETS ' 'to adjust the offset for all saved surfaces by the change differential, ' 'or run ADJUST_SURFACE_OFFSETS IGNORE=1 to hide this message without ' 'making changes.') } {% endif %} [gcode_macro adjust_surface_offsets] description: Adjusts surface offsets to account for changes in the Z endstop position or probe Z offset. Usage: ADJUST_SURFACE_OFFSETS [IGNORE] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% set settings = printer.configfile.settings %} {% set new_probe_z = (settings.probe | default(settings.bltouch) | default(settings.smart_effector) | default({}) ).z_offset|default(0.0)|float %} {% set new_endstop_z = (settings.stepper_z | default({})).position_endstop | default(0.0)|float %} {% set diff = (surfaces.probe_z - new_probe_z + surfaces.endstop_z - new_endstop_z)|round(6) %} {% if not params.IGNORE|default(0)|int %} {% for s in surfaces.available %} {% set offset = (surfaces.available[s].offset - diff)|round(6) %} {% set dummy = surfaces.available.__setitem__(s, {'offset' : offset}) %} {% endfor %} { action_respond_info("All bed surfaces now adjusted by %1.4f"| format(diff))} {% elif diff != 0 %} { action_respond_info("Status cleared without adjustment") } {% endif %} {% set dummy = surfaces.__setitem__('endstop_z', new_endstop_z| round(6)) %} {% set dummy = surfaces.__setitem__('probe_z', new_probe_z|round(6)) %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" [gcode_macro set_surface_offset] description: Sets the offset for a surface and moves the toolhead (if homed). Usage: SET_SURFACE_OFFSET [OFFSET=] [SURFACE=] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% set SURFACE = params.SURFACE|default(surfaces.active)|lower %} {% if SURFACE not in surfaces.available %} { action_raise_error("Bed surface %s does not exist." | format(SURFACE)) } {% endif %} {% set active = surfaces.available[SURFACE] %} # If no offset is provided just print out the current offset. {% set OFFSET = params.OFFSET|default(active.offset)|float %} {% if OFFSET != active.offset %} {% set dummy = surfaces.available[SURFACE].__setitem__("offset", OFFSET) %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" {% if SURFACE == surfaces.active %} _km_set_gcode_offset_base Z="{OFFSET}" MOVE={ 1 if printer.toolhead.homed_axes == 'xyz' else 0} {% endif %} {% endif %} { action_respond_info("Bed surface: %s Offset: %.3f" | format(SURFACE, OFFSET)) } # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.SURFACE|default(active surface) %} {% set dummy = params.OFFSET|default(none)|float %} " %} # End argument block for Mainsail [gcode_macro set_surface_active] description: Sets the active bed surface and moves the toolhead (if homed). If no SURFACE argument is present the available surfaces are listed and the active one is preceded by a "*". Usage: SET_SURFACE_ACTIVE [SURFACE=] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% if "SURFACE" in params %} {% set SURFACE = params.SURFACE|lower %} {% if SURFACE not in surfaces.available %} { action_raise_error("Bed surface %s does not exist." | format(SURFACE)) } {% endif %} {% if SURFACE != surfaces.active %} {% set dummy = surfaces.__setitem__("active", SURFACE) %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" {% endif %} {% if surfaces.available[SURFACE].offset != printer.gcode_move.homing_origin.z %} _km_set_gcode_offset_base Z="{surfaces.available[SURFACE].offset }" MOVE={1 if printer.toolhead.homed_axes == 'xyz' else 0} {% endif %} {action_respond_info("Active bed surface: %s; offset: %.3f" | format(SURFACE, surfaces.available[SURFACE].offset))} {% else %} {% set output = [] %} {% for s in surfaces.available|list|sort %} {% set dummy = output.append("%s %s - offset: %.3f" | format("*" if s == surfaces.active else " ", s, surfaces.available[s].offset)) %} {% endfor %} {action_respond_info(output|join('\n'))} {% endif %} # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.SURFACE|default(none) %} " %} # End argument block for Mainsail [gcode_macro set_gcode_offset] description: Wraps SET_GCODE_OFFSET to update the current bed sheet offset. Usage: SET_GCODE_OFFSET [X=|X_ADJUST=] [Y=|Y_ADJUST=] [Z=|Z_ADJUST=] [MOVE=1 [MOVE_SPEED=]] rename_existing: _KM_SET_GCODE_OFFSET_BASE gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% if surfaces.active and not printer["gcode_macro _km_save_state"].is_ephemeral %} {% set Z = params.Z|default(0.0)|float|round(6) %} {% set Z_ADJUST = params.Z_ADJUST|default(0.0)|float %} {% if 'Z' in params and Z != surfaces.available[surfaces.active].offset %} {% set dummy = surfaces.available[surfaces.active].__setitem__("offset", Z) %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" {% elif Z_ADJUST != 0.0 %} {% set dummy = surfaces.available[surfaces.active].__setitem__( "offset", (Z_ADJUST + printer.gcode_move.homing_origin.z)|round(6)) %} SAVE_VARIABLE VARIABLE=bed_surfaces VALUE="{surfaces}" {% endif %} {% endif %} _km_set_gcode_offset_base{% for k in params%}{' '~k~'="'~params[k]~'"' }{% endfor %} [gcode_macro make_surface_mesh] description: Generates and saves a mesh to automatically load in PRINT_START. Usage: MAKE_SURFACE_MESH [SURFACE=] [BED=] [EXTRUDER=] [MESH_MULTIPLIER=] [bed_mesh_calibrate parameters] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% set SURFACE = params.SURFACE|default(surfaces.active)|lower %} {% if SURFACE not in surfaces.available %} { action_raise_error("Bed surface %s does not exist." | format(SURFACE)) } {% endif %} {% set dummy = params.__setitem__('PROFILE', SURFACE) %} {% set BED = params.BED|default(70) | int %} {% set km = printer["gcode_macro _km_globals"] %} {% set EXTRUDER = params.EXTRUDER|default(km.start_extruder_probing_temp) | int %} {% set MESH_MULTIPLIER = (params.MESH_MULTIPLIER|default(2)|int, 1)|max %} M104 S{EXTRUDER} M140 S{BED} G28 # Adjust offset before running mesh {% if surfaces.available[SURFACE].offset != printer.gcode_move.homing_origin.z %} _km_set_gcode_offset_base Z="{surfaces.available[SURFACE].offset }" MOVE=1 {% endif %} # If no offset is provided just print out the current offset. {% if BED > 0 %} {action_respond_info("Stabilizing bed at %iC" | format(BED,))} _KM_PARK_IF_NEEDED HEATER=heater_bed RANGE=0.5 {% if BED < (printer.heater_bed.temperature - 0.2) %} M190 R{BED} G4 P{((km.start_bed_heat_delay * 10, 30000)|min, km.start_bed_heat_delay)|max} {% else %} G4 P{km.start_bed_heat_delay} {% endif %} M190 R{BED} {% endif %} {% if EXTRUDER > 0 %} _KM_PARK_IF_NEEDED HEATER={printer.toolhead.extruder} RANGE=2 M109 R{km.start_extruder_probing_temp} {% endif %} {% if km.start_home_z_at_temp and not bed_at_target %} G28 Z # Re-home only the Z axis now that the bed has stabilized. {% endif %} # Scale the mesh grid size while preserving the original points and relative # reference index. {% set probe_count = printer.configfile.settings.bed_mesh.probe_count if not params.PROBE_COUNT else params.PROBE_COUNT.split(",")|map('int')|list %} {% set dummy = params.__setitem__('PROBE_COUNT',( MESH_MULTIPLIER * (probe_count[0] - 1) + 1, MESH_MULTIPLIER * (probe_count[-1] - 1) + 1,)|join(',')) %} BED_MESH_CLEAR BED_MESH_CALIBRATE_FAST {% for k in params|reject('in',['BED','SURFACE'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} SAVE_CONFIG # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.MESH_MULTIPLIER|default(2) %} {% set dummy = params.SURFACE|default(active surface) %} " %} # End argument block for Mainsail [gcode_macro load_surface_mesh] description: Attempts to load a mesh associated with the specified surface. Usage: LOAD_SURFACE_MESH [SURFACE=] gcode: {% set surfaces = printer.save_variables.variables.bed_surfaces %} {% set SURFACE = params.SURFACE|default(surfaces.active)|lower %} {% if SURFACE != surfaces.active %} SET_SURFACE_ACTIVE SURFACE={SURFACE} {% endif %} {% if SURFACE in printer.bed_mesh.profiles %} {% set mesh = printer.bed_mesh.profiles[SURFACE].mesh_params %} {% set default = printer.configfile.settings.bed_mesh %} # Ensure the saved mesh has at least the resolution of the default. {% if mesh.min_x <= (default.mesh_min[0] + 0.5) and mesh.min_y <= (default.mesh_min[1] + 0.5) and mesh.max_x >= (default.mesh_max[0] - 0.5) and mesh.max_y >= (default.mesh_max[1] - 0.5) and mesh.x_count >= default.probe_count[0] and mesh.y_count >= default.probe_count[-1] %} # Skip the bed level if we have a good profile. BED_MESH_PROFILE LOAD={SURFACE} {action_respond_info("Loaded mesh: %s" | format(SURFACE,))} {% endif %} {% endif %}