Bed mesh deviation failsafe limit msg

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.

Cheers

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.

1 Like

Actually, it occurred to me that there is an easier way that does not need Klipper’s black macro magic template expansion logic:

[gcode_macro TEST_BED_THRESHOLD]
variable_mesh_deviation_threshold: 0.02
gcode:
    { action_respond_info("Starting bed mesh threshold test") }
    {% set mesh = printer.bed_mesh.probed_matrix %}
    {% 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: " ~ mesh_deviation_threshold) }
    
    {% 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 %}
1 Like

Aah you are a magician! I will test it out.

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.

cheers

Works good :+1: Thank you very much!

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 :slight_smile: .

If you want to dig more time into it… :stuck_out_tongue:

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:

  1. Carefully level your bed
  2. Create a dense mesh, e.g. 11 x 11
  3. On this “baseline mesh” call TEST_BED_THRESHOLD to get your best-case mesh deviation
  4. 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:

  1. Call BED_MESH_CALIBRATE ADAPTIVE=1
  2. 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 %}
1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.