Bed Tramming from the LCD

I wanted to be able to use my Z probe to tram the bed and tell me how to adjust the leveling screws all from the LCD without having to use the computer. I finally got it working, and here’s a video of it in action.

This solution depends on this pull request to work.

With that patch applied, I then created a new screws_display.cfg containing the following:

#############################################
### DISPLAY GROUP FOR "MEASURING" MESSAGE ###
#############################################

[display_data screws_running line1]
position: 1, 0
text: { " Measuring Bed." }

[display_data screws_running line2]
position: 2, 0
text: { " Please wait..." }

#########################################
### DISPLAY GROUP FOR "ERROR" MESSAGE ###
#########################################

[display_data screws_error line1]
position: 0, 0
text: Tilt exceeds

[display_data screws_error line2]
position: 1, 0
text: max deviation.

[display_data screws_error line3]
position: 2, 0
text: Adjust screws

[display_data screws_error line4]
position: 3, 0
text: and try again.

########################################
### DISPLAY GROUP FOR RESULTS SCREEN ###
########################################

[display_template _results_template_text]
param_adjust: None
text:
    {% if not param_adjust %}
        Base
    {% else %}
        { '{} '.format(param_adjust) }
    {% endif %}

[display_template _results_template_sign]
param_sign: None
text:
    {% if param_sign == 'CW' %}
        ~clockwise_1~~clockwise_2~
    {% elif param_sign == 'CCW' %}
        ~counter_clockwise_1~~counter_clockwise_2~
    {% endif %}


# Back left screw
[display_data screws_results back_left_text]
position: 1, 0
text:
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_third * 2 %}
        {% set x_left = result['x'] <= bed_width_third %}
        {% if y_back and x_left %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

[display_data screws_results back_left_sign]
position: 0, 1
text: 
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_third * 2 %}
        {% set x_left = result['x'] <= bed_width_third %}
        {% if y_back and x_left %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

# Front left screw
[display_data screws_results front_left_text]
position: 3, 0
text:
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_third %}
        {% set x_left = result['x'] <= bed_width_third %}
        {% if y_front and x_left %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

[display_data screws_results front_left_sign]
position: 2, 1
text:
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_third %}
        {% set x_left = result['x'] <= bed_width_third %}
        {% if y_front and x_left %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

# Middle right screw
[display_data screws_results middle_right_text]
position: 1, 11
text:
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_middle = bed_depth_third < result['y'] < bed_depth_third * 2 %}
        {% set x_right = result['x'] >= bed_width_third %}
        {% if y_middle and x_right %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

[display_data screws_results middle_right_sign]
position: 2, 12
text:
    {% set bed_width_third = printer.configfile.settings.stepper_x.position_max / 3 %}
    {% set bed_depth_third = printer.configfile.settings.stepper_y.position_max / 3 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_middle = bed_depth_third < result['y'] < bed_depth_third * 2 %}
        {% set x_right = result['x'] >= bed_width_third %}
        {% if y_middle and x_right %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

#########################################
### EXTRA GLYPHS FOR SCREW DIRECTIONS ###
#########################################

[display_glyph clockwise_1]
data:
  ................
  .........******.
  ........***..***
  .......**......*
  ......**........
  ......*........*
  ......*.......**
  ................
  ................
  ....******......
  ....*****.......
  ....****........
  ....*****......*
  ....**..***..***
  ....*....******.
  ................

[display_glyph clockwise_2]
data:
  ................
  ...*............
  ..**............
  ****............
  ****............
  ****............
  ****............
  ................
  ................
  .*..............
  .*..............
  **..............
  *...............
  ................
  ................
  ................

[display_glyph counter_clockwise_1]
data:
  ................
  ....*....******.
  ....**..***..***
  ....*****......*
  ....****........
  ....*****.......
  ....******......
  ................
  ................
  ......*.......**
  ......*........*
  ......**........
  .......**......*
  ........***..***
  .........******.
  ................

[display_glyph counter_clockwise_2]
data:
  ................
  ................
  ................
  *...............
  **..............
  .*..............
  .*..............
  ................
  ................
  ****............
  ****............
  ****............
  ****............
  ..**............
  ...*............
  ................

This also requires a custom menu.cfg to override defaults. I was already using a custom menu from this thread. Here’s my new menu.cfg with the bed-tramming additions:

[menu __main]
type: list
name: Main

### TOP-LEVEL MENUS ###
[menu __main __tune]
type: list
enable: 
    {% set printing = printer.idle_timeout.state == "Printing" %}
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {printing and mainmenu}
name: Tune
index: 1

[menu __main __octoprint]
type: list
name: OctoPrint
enable: false

[menu __main __mainsail]
type: list
name: Mainsail
enable:
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {mainmenu}
index: 2

[menu __main __calibrate]
type: list
enable:
    {% set printing = printer.idle_timeout.state == "Printing" %}
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {not printing and mainmenu}
name: Calibrate
index: 3

[menu __main __control]
type: list
enable:
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {mainmenu}
name: Control

[menu __main __temp]
type: list
enable:
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {mainmenu}
name: Temperature

[menu __main __filament]
type: list
enable:
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {mainmenu}
name: Filament

[menu __main __sdcard]
type: vsdlist
enable: false
name: SD Card

[menu __main __setup]
type: list
enable:
    {% set printing = printer.idle_timeout.state == "Printing" %}
    {% set mainmenu = printer['gcode_macro GLOBALS'].display_group == printer.configfile.settings.display.display_group %}
    {not printing and mainmenu}
name: Setup

### MAINSAIL SUB-MENU ###
[menu __main __mainsail __pause]
type: command
enable: {printer.idle_timeout.state == "Printing"}
name: Pause printing
gcode: PAUSE

[menu __main __mainsail __resume]
type: command
enable: {not printer.idle_timeout.state == "Printing"}
name: Resume printing
gcode: RESUME

[menu __main __mainsail __abort]
type: command
enable: {printer.idle_timeout.state == "Printing"}
name: Abort printing
gcode: CANCEL_PRINT

### CONTROL SUB-MENU ###
[menu __main __control __home]
type: command
enable: false
name: Home All

[menu __main __control __smart_home]
type: command
name: Smart Home All
index: 1
gcode: SMART_HOME

### CALIBRATE SUB-MENU ###
[menu __main __calibrate __calibration_mesh_calibrate]
type: command
name: Measure Bed Mesh
gcode: MEASURE_BED_MESH

[menu __main __calibrate __calibration_bed_tram]
type: command
name: Tram Bed
gcode:
    {(menu.exit(true))}
    TRAM_BED

[menu __main __calibrate __calibration_probe_calibrate]
type: list
name: Probe Calibrate

[menu __main __calibrate __calibration_probe_calibrate __calibrate]
type: command
name: Probe Calib.
gcode:
    SMART_HOME
    PROBE_CALIBRATE

[menu __main __calibrate __calibration_probe_calibrate __adjust_Z+1]
type: command
name: Z+1: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=+1

[menu __main __calibrate __calibration_probe_calibrate __adjust_Z-1]
type: command
name: Z-1: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=-1

[menu __main __calibrate __calibration_probe_calibrate __adjust_Z+.1]
type: command
name: Z+.1: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=+.1

[menu __main __calibrate __calibration_probe_calibrate __adjust_Z-.1]
type: command
name: Z-.1: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=-.1

[menu __main __calibrate __calibration_probe_calibrate __adjust_Zpp]
type: command
name: Z+: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=+

[menu __main __calibrate __calibration_probe_calibrate __adjust_Zmm]
type: command
name: Z-: {'%05.1f' % printer.gcode_move.position.z}
gcode: TESTZ Z=-

[menu __main __calibrate __calibration_probe_calibrate __calibration_accept]
type: command
name: Accept Adj.
gcode:
    ACCEPT
    SAVE_CONFIG
    {(menu.back.True)}

[menu __main __calibrate __calibration_probe_calibrate __calibration_abort]
type: command
name: Abort
gcode:
    ABORT
    {(menu.back.True)}

### SECONDARY MENU FOR BED TRAMMING RESULTS SCREEN ###
[menu __main __redo_tramming]
type: command
enable:
    {printer['gcode_macro GLOBALS'].display_group == 'screws_results'}
name: Measure Again
gcode:
    {(menu.exit(true))}
    TRAM_BED    
index: 0

[menu __main __finished]
type: command
enable:
    {printer['gcode_macro GLOBALS'].display_group == 'screws_results'}
name: Done
gcode:
    SET_DISPLAY_GROUP GROUP={printer.configfile.settings.display.display_group}
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=display_group VALUE="'_default_16x4'"
    {(menu.exit(true))}
index: 1

Finally, some macros need to be added to printer.cfg:

This one is a hack to store a variable that is used to keep track of what menu structure to display.

[gcode_macro GLOBALS]
variable_display_group:"_default_16x4"
gcode:
    M115 ; must provide something

This one will home all axes that aren’t already homed. This isn’t strictly necessary but it saves time when repeating the measurements.

[gcode_macro SMART_HOME]
gcode:
    {% set toHome = [] %}
    {% if 'x' not in printer.toolhead.homed_axes %}
        { toHome.append('x') or "" }
    {% endif %}
    {% if 'y' not in printer.toolhead.homed_axes %}
        { toHome.append('y') or "" }
    {% endif %}
    {% if 'z' not in printer.toolhead.homed_axes %}
        { toHome.append('z') or "" }
    {% endif %}
    {% if toHome|length == 0 %}
        { action_respond_info('All axes homed!') }
    {% else %}
        G28 { toHome|join(' ') }
    {% endif %}

This one is the magic.

[gcode_macro TRAM_BED]
gcode:
    SET_DISPLAY_GROUP GROUP=screws_running
    SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=display_group VALUE="'screws_running'"
    SMART_HOME
    screws_tilt_calculate
    {% if printer.screws_tilt_adjust.error %}
        SET_DISPLAY_GROUP GROUP=screws_error
    {% else %}
        SET_DISPLAY_GROUP GROUP=screws_results
        SET_GCODE_VARIABLE MACRO=GLOBALS VARIABLE=display_group VALUE="'screws_results'"
    {% endif %}

I converted my bed from four leveling screws to three, so adjustments would be needed for a different configuration.

3 Likes

I know this is an old post but I love this! How do I get this to properly work on a 4 screw bed? I tried adding some screws in the screw_display.cfg but it seems to only want me to adjust 1 screw. Thanks in advance

Got it working for the most part. Only thing left to figure out is why Base is not showing.

#############################################
### DISPLAY GROUP FOR "MEASURING" MESSAGE ###
#############################################

[display_data screws_running line1]
position: 0, 0
text: { " Measuring Bed." }

[display_data screws_running line2]
position: 1, 0
text: { " Please wait..." }

#########################################
### DISPLAY GROUP FOR "ERROR" MESSAGE ###
#########################################

[display_data screws_error line1]
position: 0, 0
text: Tilt exceeds

[display_data screws_error line2]
position: 1, 0
text: max deviation.

[display_data screws_error line3]
position: 2, 0
text: Adjust screws

[display_data screws_error line4]
position: 3, 0
text: and try again.

########################################
### DISPLAY GROUP FOR RESULTS SCREEN ###
########################################

[display_template _results_template_text]
param_adjust: None
text:
    {% if not param_adjust %}
        Base
    {% else %}
        { '{} '.format(param_adjust) }
    {% endif %}

[display_template _results_template_sign]
param_sign: None
text:
    {% if param_sign == 'CW' %}
        ~clockwise_1~~clockwise_2~
    {% elif param_sign == 'CCW' %}
        ~counter_clockwise_1~~counter_clockwise_2~
    {% endif %}

########################################
###            BACK SCREWS           ###
########################################

############# BACK LEFT ################
# Back Left Screw Text
[display_data screws_results back_left_text]
position: 1, 0
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_half %}
        {% set x_left = result['x'] <= bed_width_half %}
        {% if y_back and x_left %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

#[display_data screws_results back_left_sign]
position: 0, 1
text: 
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_half %}
        {% set x_left = result['x'] <= bed_width_half %}
        {% if y_back and x_left %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

############# BACK RIGHT ################
# Back Right Screw Text
[display_data screws_results back_right_text]
position: 0, 11
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_half %}
        {% set x_right = result['x'] >= bed_width_half %}
        {% if y_back and x_right %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

# Back Right Screw Sign
[display_data screws_results back_right_sign]
position: 1, 12
text: 
   {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_back = result['y'] >= bed_depth_half %}
        {% set x_right = result['x'] >= bed_width_half %}
        {% if y_back and x_right %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}


########################################
###           FRONT SCREWS           ###
########################################

############# Front Left ################
# Front Left Screw Text
[display_data screws_results front_left_text]
position: 3, 0
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_half %}
        {% set x_left = result['x'] <= bed_width_half %}
        {% if y_front and x_left %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

[display_data screws_results front_left_sign]
position: 2, 1
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_half %}
        {% set x_left = result['x'] <= bed_width_half %}
        {% if y_front and x_left %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

############# Front RIGHT ################
# Front Right Screw Text
[display_data screws_results front_right_text]
position: 3, 11
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_half %}
        {% set x_right = result['x'] >= bed_width_half %}
        {% if y_front and x_right %}
            { render("_results_template_text", param_adjust=result.get('adjust')) }
        {% endif %}
    {% endfor %}

# Front Right Screw Sign
[display_data screws_results front_right_sign]
position: 2, 12
text:
    {% set bed_width_half = printer.configfile.settings.stepper_x.position_max / 2 %}
    {% set bed_depth_half = printer.configfile.settings.stepper_y.position_max / 2 %}
    {% for result in printer.screws_tilt_adjust.results %}
        {% set y_front = result['y'] <= bed_depth_half %}
        {% set x_right = result['x'] >= bed_width_half %}
        {% if y_front and x_right %}
            { render("_results_template_sign", param_sign=result.get('sign')) }
        {% endif %}
    {% endfor %}

#########################################
### EXTRA GLYPHS FOR SCREW DIRECTIONS ###
#########################################

[display_glyph clockwise_1]
data:
  ................
  .........******.
  ........***..***
  .......**......*
  ......**........
  ......*........*
  ......*.......**
  ................
  ................
  ....******......
  ....*****.......
  ....****........
  ....*****......*
  ....**..***..***
  ....*....******.
  ................

[display_glyph clockwise_2]
data:
  ................
  ...*............
  ..**............
  ****............
  ****............
  ****............
  ****............
  ................
  ................
  .*..............
  .*..............
  **..............
  *...............
  ................
  ................
  ................

[display_glyph counter_clockwise_1]
data:
  ................
  ....*....******.
  ....**..***..***
  ....*****......*
  ....****........
  ....*****.......
  ....******......
  ................
  ................
  ......*.......**
  ......*........*
  ......**........
  .......**......*
  ........***..***
  .........******.
  ................

[display_glyph counter_clockwise_2]
data:
  ................
  ................
  ................
  *...............
  **..............
  .*..............
  .*..............
  ................
  ................
  ****............
  ****............
  ****............
  ****............
  ..**............
  ...*............
  ................

Which screw is your base screw and what is the display showing instead?

Base is back left. Its showing nothing right now.

Thanks for the help

In your “BACK LEFT” block, this line shouldn’t be commented out:

#[display_data screws_results back_left_sign]

So it was! Thank you! An extra set of eyes is great to have! Works perfectly now.