243 lines
15 KiB
INI
243 lines
15 KiB
INI
#########################################
|
|
########## ADAPTIVE BED MESH ############
|
|
#########################################
|
|
# Written by Frix_x#0161 #
|
|
# @version: 4.0
|
|
|
|
# CHANGELOG:
|
|
# v4.0: - patched and re-simplified the macro to work with latest Klipper changes: zero_reference_position is much more constrained and all the fancy stuff
|
|
# that was used in the past for homing over the RRI with virtual probe or Z calibration plugin is not possible anymore. Now homing must be done at
|
|
# fixed zero_reference_poisition and can't be dynamic anymore (but it's also simpler)
|
|
# - this change also allow the mesh to have even probe point now for more flexibility
|
|
# v3.0: - added the use of [exclude_object] tags to extract the first layer bounding box (many thanks to Kyleisah for the excellent idea and inspiration)
|
|
# the macro is still fully compatible with the old way using the SIZE parameter: it will use it if specified, or else
|
|
# fallback to the [exclude_object] method and if both are not available, it will do a full and normal bed mesh as usual.
|
|
# - also added a FORCE_MESH parameter to mesh even for very small parts
|
|
# - removed the RRI that was always added put in the BED_MESH_CALIBRATE call. Now it's added only when there is one defined in the [bed_mesh] section
|
|
# v2.3: moved the install notes into a proper markdown file in: docs > features > adaptive_bed_mesh.md
|
|
# v2.2: removed the requirement to set mesh_pps in the [bed_mesh] section. It's now again optional as it should be
|
|
# v2.1: fix for the nominal mesh (when no SIZE parameter is used or SIZE=0_0_0_0)
|
|
# v2.0: split in multple macros to be able to use the center point in the z calibration bed probing position before doing the mesh
|
|
# v1.1: fix for a bug when parsing string when using uppercase letters in the [bed_mesh] section
|
|
# v1.0: first adaptive bed mesh macro
|
|
|
|
# -------------------------------------------------------------------------------------------------------------------------
|
|
# If you want to use it into your own config, please install it as a standalone macro as described in the
|
|
# installation section of this file: docs > features > adaptive_bed_mesh.md
|
|
# -------------------------------------------------------------------------------------------------------------------------
|
|
|
|
### What is it ? ###
|
|
# The adaptive bed mesh is simple: it's a normal bed mesh, but only "where" and "when" it's necessary.
|
|
# Sometime I print small parts, sometime I print full plates and I like to get a precise bed_mesh (like 9x9 or more). However, it take a
|
|
# lot of time and it's useless to probe all the plate for only a 5cm² part. So this is where the adaptive bed mesh is helping:
|
|
# 1. It get the corners coordinates of the fisrt layer surface either from the slicer or the [exclude_object] tags
|
|
# 2. It compute a new set of points to probe on this new zone to get at least the same precision as your standard bed mesh. For example, if
|
|
# a normal bed mesh is set to 9x9 for 300mm², it will then compute 3x3 for a 100mm² surface. Also if for whatever reason your parts are in
|
|
# the corner of the build plate (like for a damaged PEI in the center), it will follow them to probe this exact area.
|
|
# 3. To go further, it will not do any bed_mesh if there is less than 3x3 points to probe (very small part alone) and choose/change the
|
|
# algorithm (bicubic/lagrange) depending of the size and shape of the mesh computed (like 3x3 vs 3x9)
|
|
|
|
# Feel free to ping me on Discord (Frix_x#0161) if you need help or have any comments to improve it :)
|
|
|
|
|
|
# ===========================================================================================================
|
|
# DO NOT MODIFY THOSE VARIABLES (they are used internaly by the adaptive bed mesh macro)
|
|
[gcode_macro _ADAPTIVE_MESH_VARIABLES]
|
|
variable_ready: False
|
|
variable_do_mesh: False
|
|
variable_do_nominal: False
|
|
variable_mesh_min: 0,0
|
|
variable_mesh_max: 0,0
|
|
variable_probe_count: 0,0
|
|
variable_algo: "bicubic"
|
|
gcode:
|
|
|
|
|
|
[gcode_macro COMPUTE_MESH_PARAMETERS]
|
|
description: Compute the mesh parameters and store them for later use
|
|
gcode:
|
|
# 1 ----- GET ORIGINAL BEDMESH PARAMS FROM CONFIG ----------------------
|
|
{% set xMinConf, yMinConf = printer["configfile"].config["bed_mesh"]["mesh_min"].split(',')|map('trim')|map('int') %}
|
|
{% set xMaxConf, yMaxConf = printer["configfile"].config["bed_mesh"]["mesh_max"].split(',')|map('trim')|map('int') %}
|
|
{% set xProbeCntConf, yProbeCntConf = printer["configfile"].config["bed_mesh"]["probe_count"].split(',')|map('trim')|map('int') %}
|
|
{% set algo = printer["configfile"].config["bed_mesh"]["algorithm"]|lower %}
|
|
{% set xMeshPPS, yMeshPPS = (printer["configfile"].config["bed_mesh"]["mesh_pps"]|default('2,2')).split(',')|map('trim')|map('int') %}
|
|
|
|
{% set margin = params.MARGIN|default(5)|int %} # additional margin to mesh around the first layer
|
|
{% set force_mesh = params.FORCE_MESH|default(False) %} # force the mesh even if it's a small part (ie. computed less than 3x3)
|
|
|
|
|
|
# 2 ----- GET FIRST LAYER COORDINATES and SIZE -------------------------------------
|
|
# If the SIZE parameter is defined and not a dummy placeholder, we use it to do the adaptive bed mesh logic
|
|
{% set coordinatesFound = false %}
|
|
{% if params.SIZE is defined and params.SIZE != "0_0_0_0" %}
|
|
RESPOND MSG="Got a SIZE parameter for the adaptive bed mesh"
|
|
{% set xMinSpec, yMinSpec, xMaxSpec, yMaxSpec = params.SIZE.split('_')|map('trim')|map('int') %}
|
|
{% set coordinatesFound = true %}
|
|
|
|
{% elif printer.exclude_object is defined %}
|
|
{% if printer.exclude_object.objects %}
|
|
# Else if SIZE is not defined, we fallback to use the [exclude_object] tags
|
|
# This method is derived from Kyleisah KAMP repository: https://github.com/kyleisah/Klipper-Adaptive-Meshing-Purging)
|
|
RESPOND MSG="No SIZE parameter, using the [exclude_object] tags for the adaptive bed mesh"
|
|
{% set eo_points = printer.exclude_object.objects|map(attribute='polygon')|sum(start=[]) %}
|
|
{% set xMinSpec = eo_points|map(attribute=0)|min %}
|
|
{% set yMinSpec = eo_points|map(attribute=1)|min %}
|
|
{% set xMaxSpec = eo_points|map(attribute=0)|max %}
|
|
{% set yMaxSpec = eo_points|map(attribute=1)|max %}
|
|
{% set coordinatesFound = true %}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% if not coordinatesFound %}
|
|
# If no SIZE parameter and no [exclude_object] tags, then we want to do a nominal bed mesh
|
|
# so nothing to do here...
|
|
RESPOND MSG="No info about the first layer coordinates, doing a nominal bed mesh instead of adaptive"
|
|
{% endif %}
|
|
|
|
|
|
# If the first layer size was correctly retrieved, we can do the adaptive bed mesh logic, else we
|
|
# fallback to the original and nominal BED_MESH_CALIBRATE function (full bed probing)
|
|
{% if xMinSpec and yMinSpec and xMaxSpec and yMaxSpec %}
|
|
|
|
# 3 ----- APPLY MARGINS ----------------------------------------------
|
|
# We use min/max function as we want it to be constrained by the original
|
|
# bedmesh size. This will avoid going outside the machine limits
|
|
{% set xMin = [xMinConf, (xMinSpec - margin)]|max %}
|
|
{% set xMax = [xMaxConf, (xMaxSpec + margin)]|min %}
|
|
{% set yMin = [yMinConf, (yMinSpec - margin)]|max %}
|
|
{% set yMax = [yMaxConf, (yMaxSpec + margin)]|min %}
|
|
|
|
# 4 ----- COMPUTE A NEW PROBE COUNT ----------------------------------
|
|
# The goal is to have at least the same precision as from the config. So we compute an equivalent number
|
|
# of probe points on each X/Y dimensions (distance between two points should be the same as in the config)
|
|
{% set xProbeCnt = ((xMax - xMin) * xProbeCntConf / (xMaxConf - xMinConf))|round(0, 'ceil')|int %}
|
|
{% set yProbeCnt = ((yMax - yMin) * yProbeCntConf / (yMaxConf - yMinConf))|round(0, 'ceil')|int %}
|
|
|
|
# Then, three possibilities :
|
|
# a) Both dimensions have less than 3 probe points : the bed_mesh is not needed as it's a small print (if not forced).
|
|
# b) If one of the dimension is less than 3 and the other is greater. The print looks to be elongated and
|
|
# need the adaptive bed_mesh : we add probing points to the small direction to reach 3 and be able to do it.
|
|
# c) If both direction are greater than 3, we need the adaptive bed_mesh and it's ok.
|
|
# At the end we control (according to Klipper bed_mesh method: "_verify_algorithm") that the computed probe_count is
|
|
# valid according to the choosen algorithm or change it if needed.
|
|
{% if xProbeCnt < 3 and yProbeCnt < 3 %}
|
|
{% if force_mesh %}
|
|
RESPOND MSG="Bed mesh forced (small part detected): meshing 3x3..."
|
|
{% set xProbeCnt = 3 %}
|
|
{% set yProbeCnt = 3 %}
|
|
{% set algo = "lagrange" %}
|
|
{% set mesh_min = "%d,%d"|format(xMin, yMin) %}
|
|
{% set mesh_max = "%d,%d"|format(xMax, yMax) %}
|
|
{% set probe_count = "%d,%d"|format(xProbeCnt, yProbeCnt) %}
|
|
RESPOND MSG="Computed mesh parameters: MESH_MIN={mesh_min} MESH_MAX={mesh_max} PROBE_COUNT={probe_count} ALGORITHM={algo}"
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_mesh VALUE={True}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_nominal VALUE={False}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=mesh_min VALUE='"{mesh_min}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=mesh_max VALUE='"{mesh_max}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=probe_count VALUE='"{probe_count}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=algo VALUE='"{algo}"'
|
|
{% else %}
|
|
RESPOND MSG="Computed mesh parameters: none, bed mesh not needed for very small parts"
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_mesh VALUE={False}
|
|
{% endif %}
|
|
{% else %}
|
|
{% set xProbeCnt = [3, xProbeCnt]|max %}
|
|
{% set yProbeCnt = [3, yProbeCnt]|max %}
|
|
|
|
# Check of the probe points and interpolation algorithms according to Klipper code
|
|
{% if xMeshPPS != 0 or yMeshPPS != 0 %}
|
|
{% set probeCntMin = [xProbeCnt, yProbeCnt]|min %}
|
|
{% set probeCntMax = [xProbeCnt, yProbeCnt]|max %}
|
|
{% if algo == "lagrange" and probeCntMax > 6 %}
|
|
# Lagrange interpolation tends to oscillate when using more than 6 samples: swith to bicubic
|
|
{% set algo = "bicubic" %}
|
|
{% endif %}
|
|
{% if algo == "bicubic" and probeCntMin < 4 %}
|
|
{% if probeCntMax > 6 %}
|
|
# Impossible case: need to add probe point on the small axis to be >= 4 (we want 5 to keep it odd)
|
|
{% if xProbeCnt > yProbeCnt %}
|
|
{% set yProbeCnt = 5 %}
|
|
{% else %}
|
|
{% set xProbeCnt = 5 %}
|
|
{% endif %}
|
|
{% else %}
|
|
# In this case bicubic is not adapted (less than 4 points): switch to lagrange
|
|
{% set algo = "lagrange" %}
|
|
{% endif %}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
# 5 ----- FORMAT THE PARAMETERS AND SAVE THEM ---------------------------
|
|
{% set mesh_min = "%d,%d"|format(xMin, yMin) %}
|
|
{% set mesh_max = "%d,%d"|format(xMax, yMax) %}
|
|
{% set probe_count = "%d,%d"|format(xProbeCnt, yProbeCnt) %}
|
|
RESPOND MSG="Computed mesh parameters: MESH_MIN={mesh_min} MESH_MAX={mesh_max} PROBE_COUNT={probe_count} ALGORITHM={algo}"
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_mesh VALUE={True}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_nominal VALUE={False}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=mesh_min VALUE='"{mesh_min}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=mesh_max VALUE='"{mesh_max}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=probe_count VALUE='"{probe_count}"'
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=algo VALUE='"{algo}"'
|
|
{% endif %}
|
|
{% else %}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_mesh VALUE={True}
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=do_nominal VALUE={True}
|
|
{% endif %}
|
|
|
|
# Finaly save in the variables that we already computed the values
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=ready VALUE={True}
|
|
|
|
|
|
[gcode_macro ADAPTIVE_BED_MESH]
|
|
description: Perform a bed mesh, but only where and when it's needed
|
|
gcode:
|
|
{% set ready = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].ready %}
|
|
|
|
{% if not 'xyz' in printer.toolhead.homed_axes %}
|
|
{ action_raise_error("Must Home printer first!") }
|
|
{% endif %}
|
|
|
|
# If the parameters where computed, we can do the mesh by calling the _DO_ADAPTIVE_MESH
|
|
{% if ready %}
|
|
_DO_ADAPTIVE_MESH
|
|
|
|
# If the parameters where not computed prior to the ADAPTIVE_BED_MESH call, we call the COMPUTE_MESH_PARAMETERS
|
|
# macro first and then call the _DO_ADAPTIVE_MESH macro after it
|
|
{% else %}
|
|
RESPOND MSG="Adaptive bed mesh: parameters not already computed, automatically calling the COMPUTE_MESH_PARAMETERS macro prior to the mesh"
|
|
COMPUTE_MESH_PARAMETERS {rawparams}
|
|
M400 # mandatory to flush the gcode buffer and be sure to use the last computed parameters
|
|
_DO_ADAPTIVE_MESH
|
|
{% endif %}
|
|
|
|
|
|
[gcode_macro _DO_ADAPTIVE_MESH]
|
|
gcode:
|
|
# 1 ----- POPULATE BEDMESH PARAMS FROM SAVED VARIABLES ----------------------
|
|
{% set do_mesh = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].do_mesh %}
|
|
{% set do_nominal = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].do_nominal %}
|
|
{% set mesh_min = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].mesh_min %}
|
|
{% set mesh_max = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].mesh_max %}
|
|
{% set probe_count = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].probe_count %}
|
|
{% set algo = printer["gcode_macro _ADAPTIVE_MESH_VARIABLES"].algo %}
|
|
|
|
# 2 --------- ADAPTIVE_BED_MESH LOGIC --------------------------------------
|
|
|
|
# If it's necessary to do a mesh
|
|
{% if do_mesh %}
|
|
# If it's a standard bed_mesh to be done
|
|
{% if do_nominal %}
|
|
RESPOND MSG="Adaptive bed mesh: nominal bed mesh"
|
|
BED_MESH_CALIBRATE
|
|
{% else %}
|
|
RESPOND MSG="Adaptive bed mesh: MESH_MIN={mesh_min} MESH_MAX={mesh_max} PROBE_COUNT={probe_count} ALGORITHM={algo}"
|
|
BED_MESH_CALIBRATE MESH_MIN={mesh_min} MESH_MAX={mesh_max} PROBE_COUNT={probe_count} ALGORITHM={algo}
|
|
{% endif %}
|
|
{% else %}
|
|
RESPOND MSG="Adaptive bed mesh: no mesh to be done"
|
|
{% endif %}
|
|
|
|
# Set back the 'ready' parameter to false
|
|
SET_GCODE_VARIABLE MACRO=_ADAPTIVE_MESH_VARIABLES VARIABLE=ready VALUE={False}
|