Hello, is it possible to have a failsafe function for the bed mesh if the deviation is to high?
I think many of us use Klipper with KAMP and since i installed it i usually dont have eyes on the deviation numbers like before running it more manually time to time.
Maybe this already exist but ive been trying to find it. Is it hard to create a max result/tolerance control limit after running bed mesh? So when the adaptive mesh/bed mesh is done and the tolerance value is to high it pauses or something and send a error message. Bed mesh Deviation/tolerance out of tolerance. The user sets a limitvalue of their choice.
Not all of us run the setup with premium ultra flat buildplates. Also good when running alot of printers in farms. It gives a better heads up and you know theres a problem that maybe gets worse in near future or with more heat. Saves downtime and troublefinding.
This is actually an interesting idea.
AFAIK, there is no easy way to do this. It is possible with a macro, but due to Klipper’s macro execution logic, it is rather convoluted.
[gcode_macro TEST_BED_THRESHOLD]
# Set your max allowable deviation:
variable_mesh_deviation_threshold: 0.01
# Internal macro variables:
variable_min_value: 999999
variable_max_value: -999999
variable_current_row: 0
variable_current_col: 0
variable_rows: 0
variable_cols: 0
variable_evaluation_done: False
gcode:
{% set mesh = printer.bed_mesh.probed_matrix %}
{% set rows = mesh|length %}
{% set cols = mesh[0]|length %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=rows VALUE={rows}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=cols VALUE={cols}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=evaluation_done VALUE=False
{ action_respond_info("Starting bed mesh threshold test") }
{ action_respond_info("Depending on the mesh size, this takes a few seconds") }
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_MONITOR DURATION=0.05
[gcode_macro _TEST_BED_THRESHOLD_EVALUATE]
gcode:
{% if printer['gcode_macro TEST_BED_THRESHOLD'].evaluation_done %}
{ action_respond_info("Evaluation already completed, skipping.") }
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_MONITOR DURATION=0
{% else %}
{% set current_row = printer['gcode_macro TEST_BED_THRESHOLD'].current_row %}
{% set current_col = printer['gcode_macro TEST_BED_THRESHOLD'].current_col %}
{% set mesh = printer.bed_mesh.probed_matrix %}
{% set value = mesh[current_row][current_col] %}
# Debug statement
# { action_respond_info("Evaluating point: row " ~ current_row ~ ", col " ~ current_col ~ ", value: " ~ value) }
{% set min_value = printer['gcode_macro TEST_BED_THRESHOLD'].min_value %}
{% set max_value = printer['gcode_macro TEST_BED_THRESHOLD'].max_value %}
{% if value < min_value %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=min_value VALUE={value}
{% endif %}
{% if value > max_value %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=max_value VALUE={value}
{% endif %}
{% if current_col < printer['gcode_macro TEST_BED_THRESHOLD'].cols - 1 %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=current_col VALUE={current_col + 1}
{% elif current_row < printer['gcode_macro TEST_BED_THRESHOLD'].rows - 1 %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=current_col VALUE=0
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=current_row VALUE={current_row + 1}
{% else %}
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=current_col VALUE=0
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=current_row VALUE=0
SET_GCODE_VARIABLE MACRO=TEST_BED_THRESHOLD VARIABLE=evaluation_done VALUE=True
{ action_respond_info("Finished evaluating all points") }
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_MONITOR DURATION=0
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_RESULT DURATION=0.05
{% endif %}
{% endif %}
[delayed_gcode _TEST_BED_THRESHOLD_MONITOR]
gcode:
{% if not printer['gcode_macro TEST_BED_THRESHOLD'].evaluation_done %}
_TEST_BED_THRESHOLD_EVALUATE
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_MONITOR DURATION=0.05
{% else %}
UPDATE_DELAYED_GCODE ID=_TEST_BED_THRESHOLD_MONITOR DURATION=0
{% endif %}
[delayed_gcode _TEST_BED_THRESHOLD_RESULT]
gcode:
{% set min_value = printer['gcode_macro TEST_BED_THRESHOLD'].min_value %}
{% set max_value = printer['gcode_macro TEST_BED_THRESHOLD'].max_value %}
{% set absolute_range = max_value - min_value %}
{% set mesh_deviation_threshold = printer['gcode_macro TEST_BED_THRESHOLD'].mesh_deviation_threshold %}
{ action_respond_info("Final min_value:" ~ min_value) }
{ action_respond_info("Final max_value:" ~ max_value) }
{ action_respond_info("absolute_range:" ~ absolute_range) }
{ action_respond_info("mesh_deviation_threshold:" ~ mesh_deviation_threshold) }
{% if absolute_range > mesh_deviation_threshold %}
{ action_respond_info("Mesh is exceeding mesh_deviation_threshold") }
{ action_raise_error("Mesh is exceeding mesh_deviation_threshold") }
{% endif %}
10:34:15 // Starting bed mesh threshold test
10:34:15 // Depending on the mesh size, this takes a few seconds
10:34:21 // Finished evaluating all points
10:34:21 // Final min_value:-0.096807
10:34:21 // Final max_value:0.04812
10:34:21 // absolute_range:0.144927
10:34:21 // mesh_deviation_threshold:0.01
10:34:22 // Mesh is exceeding mesh_deviation_threshold
There may be ways to do this more elegantly in some places, but this works.
Its also good if your setup is running with bed leveling screws that come loose after X hours of printing. The more we can keep checked with the already amazing klipper the better.
Just to make it more fool proof. Is it possible to put in BED_MESH_PROFILE LOAD=“default” somewhere to load the latest mesh before running the test? Its for all the ones with fast fingers and test the function right away without loading it/doing mesh/kamp mesh and get an error without knowing what it means .
I’d not recommend doing this.
IMO, this macro is only useful
if you use adaptive meshing
when your printer does not have the capability to use functions like Z_TILT_ADJUST, QUAD_GANTRY_LEVEL, etc
when you want to “be lazy” and do not manually level the bed on each print, but rely on the adaptive mesh to compensate for it
The SETUP workflow I would propose:
Carefully level your bed
Create a dense mesh, e.g. 11 x 11
On this “baseline mesh” call TEST_BED_THRESHOLD to get your best-case mesh deviation
Multiply this “best-case mesh deviation” by 1.2 to 1.5 (depending on the stability of your bed) to derive the variable_mesh_deviation_threshold and update the macro accordingly
The printing workflow then would be:
Modify either your START_PRINT macro or the slicer’s starting gcode to do:
Call BED_MESH_CALIBRATE ADAPTIVE=1
Call TEST_BED_THRESHOLD
Now, the macro will produce a warning (and/or error) if your bed moved outside the defined limits.
I have slightly updated the macro to handle the case when no mesh is active more gracefully:
[gcode_macro TEST_BED_THRESHOLD]
description:
Checks if the currently loaded mesh is within defined limits.
Especially useful for adaptive meshes if one wants to skip bed leveling on each print.
usage: Execute after running the adaptive meshing in the Klipper START_PRINT macro or the slicer's starting G-code.
# Set a proper threshold:
variable_mesh_deviation_threshold: 0.02
gcode:
# Get the currently active mesh. Exit if no active mesh
{% if printer.bed_mesh.probed_matrix is not defined or not printer.bed_mesh.probed_matrix[0] %}
{ action_respond_info("Warning: No bed mesh loaded. Exiting TEST_BED_THRESHOLD macro.") }
{% else %}
{% set mesh = printer.bed_mesh.probed_matrix %}
{ action_respond_info("Starting bed mesh threshold test") }
{% set flat_list = [] %}
# Flatten the nested array
{% for row in mesh %}
{% for value in row %}
{% set _ = flat_list.append(value) %}
{% endfor %}
{% endfor %}
# Sort the flattened array
{% set flat_list = flat_list | sort %}
# Get the min and max values
{% set min_value = flat_list[0] %}
{% set max_value = flat_list[-1] %}
{ action_respond_info("min_value: " ~ min_value) }
{ action_respond_info("max_value: " ~ max_value) }
{% set absolute_range = max_value - min_value %}
{ action_respond_info("absolute_range: " ~ absolute_range) }
{ action_respond_info("mesh_deviation_threshold: " ~ printer['gcode_macro TEST_BED_THRESHOLD'].mesh_deviation_threshold) }
# Check if threshold is exceeded
# If no error is wanted comment or delete "action_raise_error..."
{% if absolute_range > printer['gcode_macro TEST_BED_THRESHOLD'].mesh_deviation_threshold %}
{ action_respond_info("Mesh is exceeding mesh_deviation_threshold") }
{ action_raise_error("Mesh is exceeding mesh_deviation_threshold") }
{% endif %}
{% endif %}