-
Notifications
You must be signed in to change notification settings - Fork 0
/
adaptive_bed_mesh.cfg
242 lines (211 loc) · 15 KB
/
adaptive_bed_mesh.cfg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#########################################
########## 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}