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.

1 Like