Commit | Line | Data |
---|---|---|
6c972e51 JL |
1 | /* |
2 | This code is derived from (and mostly copied from) Johann Rocholls code at https://github.com/jcrocholl/Marlin/blob/deltabot/Marlin/Marlin_main.cpp | |
3 | license is the same as his code. | |
4 | ||
5 | Summary | |
6 | ------- | |
7 | Probes grid_size points in X and Y (total probes grid_size * grid_size) and stores the relative offsets from the 0,0 Z height | |
8 | When enabled every move will calculate the Z offset based on interpolating the height offset within the grids nearest 4 points. | |
9 | ||
10 | Configuration | |
11 | ------------- | |
12 | The strategy must be enabled in the config as well as zprobe. | |
13 | ||
c77455c3 | 14 | leveling-strategy.rectangular-grid.enable true |
6c972e51 JL |
15 | |
16 | The size of the grid can be set with... | |
5d7cbe0c VB |
17 | leveling-strategy.rectangular-grid.size 7 |
18 | or | |
258f176c VB |
19 | leveling-strategy.rectangular-grid.grid_x_size 7 |
20 | leveling-strategy.rectangular-grid.grid_y_size 7 | |
5d7cbe0c VB |
21 | this is the X and Y size of the grid, it must be an odd number, the default is 7 which is 49 probe points |
22 | ||
23 | If both "size" and "grid_x_size" and "grid_x_size defined "grid_x_size" and "grid_x_size" will be used. | |
24 | If "grid_x_size" and "grid_x_size" omitted then "size" will be used. | |
25 | If "size" omitted default value will be used. | |
6c972e51 | 26 | |
01cacfc9 | 27 | I and J params used for grid size. If both omitted values from config will be used. If only one provided (I or J) then it will be used for both x_size and y-size. |
6c972e51 | 28 | |
5d3233cd | 29 | The width and length of the rectangle that is probed is set with... |
c77455c3 | 30 | |
5d3233cd JM |
31 | leveling-strategy.rectangular-grid.x_size 100 |
32 | leveling-strategy.rectangular-grid.y_size 90 | |
c77455c3 | 33 | |
6c972e51 JL |
34 | Optionally probe offsets from the nozzle or tool head can be defined with... |
35 | ||
c77455c3 | 36 | leveling-strategy.rectangular-grid.probe_offsets 0,0,0 # probe offsetrs x,y,z |
6c972e51 JL |
37 | |
38 | they may also be set with M565 X0 Y0 Z0 | |
39 | ||
40 | If the saved grid is to be loaded on boot then this must be set in the config... | |
41 | ||
c77455c3 | 42 | leveling-strategy.rectangular-grid.save true |
6c972e51 JL |
43 | |
44 | Then when M500 is issued it will save M375 which will cause the grid to be loaded on boot. The default is to not autoload the grid on boot | |
45 | ||
46 | Optionally an initial_height can be set that tell the intial probe where to stop the fast decent before it probes, this should be around 5-10mm above the bed | |
c77455c3 | 47 | leveling-strategy.rectangular-grid.initial_height 10 |
6c972e51 | 48 | |
e99c6e24 | 49 | If two corners rectangular mode activated using "leveling-strategy.rectangular-grid.only_by_two_corners true" then G29/31/32 will not work without providing XYAB parameters |
825e3420 | 50 | XY - start point, AB rectangle size from starting point |
01cacfc9 | 51 | "Two corners"" not absolutely correct name for this mode, because it use only one corner and rectangle size. |
825e3420 | 52 | |
01cacfc9 | 53 | Display mode of current grid can be changed to human redable mode (table with coordinates) by using |
2f05c122 | 54 | leveling-strategy.rectangular-grid.human_readable true |
6c972e51 JL |
55 | |
56 | Usage | |
57 | ----- | |
c77455c3 | 58 | G29 test probes a rectangle which defaults to the width and height, can be overidden with Xnnn and Ynnn |
6c972e51 JL |
59 | |
60 | G31 probes the grid and turns the compensation on, this will remain in effect until reset or M561/M370 | |
c77455c3 | 61 | optional parameters {{Xn}} {{Yn}} sets the size for this rectangular probe, which gets saved with M375 |
6c972e51 JL |
62 | |
63 | M370 clears the grid and turns off compensation | |
c77455c3 JM |
64 | M374 Save grid to /sd/cartesian.grid |
65 | M374.1 delete /sd/cartesian.grid | |
66 | M375 Load the grid from /sd/cartesian.grid and enable compensation | |
6c972e51 JL |
67 | M375.1 display the current grid |
68 | M561 clears the grid and turns off compensation | |
69 | M565 defines the probe offsets from the nozzle or tool head | |
70 | ||
71 | ||
72 | M500 saves the probe points | |
73 | M503 displays the current settings | |
74 | */ | |
75 | ||
76 | #include "CartGridStrategy.h" | |
77 | ||
78 | #include "Kernel.h" | |
79 | #include "Config.h" | |
80 | #include "Robot.h" | |
81 | #include "StreamOutputPool.h" | |
82 | #include "Gcode.h" | |
83 | #include "checksumm.h" | |
84 | #include "ConfigValue.h" | |
85 | #include "PublicDataRequest.h" | |
86 | #include "PublicData.h" | |
87 | #include "Conveyor.h" | |
88 | #include "ZProbe.h" | |
89 | #include "nuts_bolts.h" | |
90 | #include "utils.h" | |
91 | #include "platform_memory.h" | |
92 | ||
93 | #include <string> | |
94 | #include <algorithm> | |
95 | #include <cstdlib> | |
96 | #include <cmath> | |
97 | #include <fastmath.h> | |
98 | ||
608a0f26 | 99 | #define grid_size_checksum CHECKSUM("size") |
258f176c VB |
100 | #define grid_x_size_checksum CHECKSUM("grid_x_size") |
101 | #define grid_y_size_checksum CHECKSUM("grid_y_size") | |
6c972e51 JL |
102 | #define tolerance_checksum CHECKSUM("tolerance") |
103 | #define save_checksum CHECKSUM("save") | |
104 | #define probe_offsets_checksum CHECKSUM("probe_offsets") | |
105 | #define initial_height_checksum CHECKSUM("initial_height") | |
5d3233cd JM |
106 | #define x_size_checksum CHECKSUM("x_size") |
107 | #define y_size_checksum CHECKSUM("y_size") | |
6c972e51 | 108 | #define do_home_checksum CHECKSUM("do_home") |
825e3420 | 109 | #define only_by_two_corners_checksum CHECKSUM("only_by_two_corners") |
2f05c122 | 110 | #define human_readable_checksum CHECKSUM("human_readable") |
92143a7e AI |
111 | #define height_limit_checksum CHECKSUM("height_limit") |
112 | #define dampening_start_checksum CHECKSUM("dampening_start") | |
6c972e51 JL |
113 | |
114 | #define GRIDFILE "/sd/cartesian.grid" | |
bf2abd76 | 115 | #define GRIDFILE_NM "/sd/cartesian_nm.grid" |
6c972e51 JL |
116 | |
117 | CartGridStrategy::CartGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe) | |
118 | { | |
119 | grid = nullptr; | |
120 | } | |
121 | ||
122 | CartGridStrategy::~CartGridStrategy() | |
123 | { | |
124 | if(grid != nullptr) AHB0.dealloc(grid); | |
125 | } | |
126 | ||
127 | bool CartGridStrategy::handleConfig() | |
128 | { | |
608a0f26 VB |
129 | |
130 | uint8_t grid_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, grid_size_checksum)->by_default(7)->as_number(); | |
0ba48b60 VB |
131 | this->current_grid_x_size = this->configured_grid_x_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, grid_x_size_checksum)->by_default(grid_size)->as_number(); |
132 | this->current_grid_y_size = this->configured_grid_y_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, grid_y_size_checksum)->by_default(grid_size)->as_number(); | |
6c972e51 JL |
133 | tolerance = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, tolerance_checksum)->by_default(0.03F)->as_number(); |
134 | save = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, save_checksum)->by_default(false)->as_bool(); | |
135 | do_home = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, do_home_checksum)->by_default(true)->as_bool(); | |
825e3420 | 136 | only_by_two_corners = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, only_by_two_corners_checksum)->by_default(false)->as_bool(); |
2f05c122 | 137 | human_readable = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, human_readable_checksum)->by_default(false)->as_bool(); |
92143a7e | 138 | |
04f8c534 IA |
139 | this->height_limit = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, height_limit_checksum)->by_default(NAN)->as_number(); |
140 | this->dampening_start = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, dampening_start_checksum)->by_default(NAN)->as_number(); | |
141 | ||
142 | if(!isnan(this->height_limit) && !isnan(this->dampening_start)) | |
143 | this->damping_interval = height_limit - dampening_start; | |
144 | else | |
145 | this->damping_interval = NAN; | |
92143a7e | 146 | |
0ba48b60 VB |
147 | this->x_start = 0.0F; |
148 | this->y_start = 0.0F; | |
149 | this->x_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, x_size_checksum)->by_default(0.0F)->as_number(); | |
150 | this->y_size = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, y_size_checksum)->by_default(0.0F)->as_number(); | |
151 | if (this->x_size == 0.0F || this->y_size == 0.0F) { | |
6c972e51 JL |
152 | THEKERNEL->streams->printf("Error: Invalid config, x_size and y_size must be defined\n"); |
153 | return false; | |
154 | } | |
155 | ||
156 | // the initial height above the bed we stop the intial move down after home to find the bed | |
157 | // this should be a height that is enough that the probe will not hit the bed and is an offset from max_z (can be set to 0 if max_z takes into account the probe offset) | |
158 | this->initial_height = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, initial_height_checksum)->by_default(10)->as_number(); | |
159 | ||
160 | // Probe offsets xxx,yyy,zzz | |
161 | { | |
162 | std::string po = THEKERNEL->config->value(leveling_strategy_checksum, cart_grid_leveling_strategy_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string(); | |
163 | std::vector<float> v = parse_number_list(po.c_str()); | |
164 | if(v.size() >= 3) { | |
165 | this->probe_offsets = std::make_tuple(v[0], v[1], v[2]); | |
166 | } | |
167 | } | |
168 | ||
169 | // allocate in AHB0 | |
258f176c | 170 | grid = (float *)AHB0.alloc(configured_grid_x_size * configured_grid_y_size * sizeof(float)); |
6c972e51 | 171 | |
fc6c9aac JM |
172 | if(grid == nullptr) { |
173 | THEKERNEL->streams->printf("Error: Not enough memory\n"); | |
174 | return false; | |
175 | } | |
176 | ||
6c972e51 JL |
177 | reset_bed_level(); |
178 | ||
179 | return true; | |
180 | } | |
181 | ||
182 | void CartGridStrategy::save_grid(StreamOutput *stream) | |
183 | { | |
825e3420 VB |
184 | if(only_by_two_corners){ |
185 | stream->printf("error:Unable to save grid in only_by_two_corners mode\n"); | |
186 | return; | |
187 | } | |
188 | ||
6c972e51 JL |
189 | if(isnan(grid[0])) { |
190 | stream->printf("error:No grid to save\n"); | |
191 | return; | |
192 | } | |
193 | ||
258f176c VB |
194 | if((current_grid_x_size != configured_grid_x_size) || (current_grid_y_size != configured_grid_y_size)) { |
195 | stream->printf("error:Unable to save grid with size different from configured\n"); | |
196 | return; | |
197 | } | |
198 | ||
bf2abd76 | 199 | FILE *fp = (configured_grid_x_size == configured_grid_y_size)?fopen(GRIDFILE, "w"):fopen(GRIDFILE_NM, "w"); |
6c972e51 JL |
200 | if(fp == NULL) { |
201 | stream->printf("error:Failed to open grid file %s\n", GRIDFILE); | |
202 | return; | |
203 | } | |
44b5bc10 VB |
204 | uint8_t tmp_configured_grid_size = configured_grid_x_size; |
205 | if(fwrite(&tmp_configured_grid_size, sizeof(uint8_t), 1, fp) != 1) { | |
258f176c VB |
206 | stream->printf("error:Failed to write grid x size\n"); |
207 | fclose(fp); | |
208 | return; | |
209 | } | |
210 | ||
44b5bc10 | 211 | tmp_configured_grid_size = configured_grid_y_size; |
bf2abd76 | 212 | if(configured_grid_y_size != configured_grid_x_size){ |
44b5bc10 | 213 | if(fwrite(&tmp_configured_grid_size, sizeof(uint8_t), 1, fp) != 1) { |
bf2abd76 VB |
214 | stream->printf("error:Failed to write grid y size\n"); |
215 | fclose(fp); | |
216 | return; | |
217 | } | |
6c972e51 JL |
218 | } |
219 | ||
220 | if(fwrite(&x_size, sizeof(float), 1, fp) != 1) { | |
221 | stream->printf("error:Failed to write x_size\n"); | |
222 | fclose(fp); | |
223 | return; | |
224 | } | |
225 | ||
226 | if(fwrite(&y_size, sizeof(float), 1, fp) != 1) { | |
227 | stream->printf("error:Failed to write y_size\n"); | |
228 | fclose(fp); | |
229 | return; | |
230 | } | |
231 | ||
258f176c VB |
232 | for (int y = 0; y < configured_grid_y_size; y++) { |
233 | for (int x = 0; x < configured_grid_x_size; x++) { | |
234 | if(fwrite(&grid[x + (configured_grid_x_size * y)], sizeof(float), 1, fp) != 1) { | |
6c972e51 JL |
235 | stream->printf("error:Failed to write grid\n"); |
236 | fclose(fp); | |
237 | return; | |
238 | } | |
239 | } | |
240 | } | |
241 | stream->printf("grid saved to %s\n", GRIDFILE); | |
242 | fclose(fp); | |
243 | } | |
244 | ||
245 | bool CartGridStrategy::load_grid(StreamOutput *stream) | |
246 | { | |
825e3420 VB |
247 | if(only_by_two_corners){ |
248 | stream->printf("error:Unable to load grid in only_by_two_corners mode\n"); | |
249 | return false; | |
250 | } | |
01cacfc9 | 251 | |
bf2abd76 | 252 | FILE *fp = (configured_grid_x_size == configured_grid_y_size)?fopen(GRIDFILE, "r"):fopen(GRIDFILE_NM, "r"); |
6c972e51 JL |
253 | if(fp == NULL) { |
254 | stream->printf("error:Failed to open grid %s\n", GRIDFILE); | |
255 | return false; | |
256 | } | |
257 | ||
258f176c | 258 | uint8_t load_grid_x_size, load_grid_y_size; |
6c972e51 JL |
259 | float x, y; |
260 | ||
258f176c VB |
261 | if(fread(&load_grid_x_size, sizeof(uint8_t), 1, fp) != 1) { |
262 | stream->printf("error:Failed to read grid size\n"); | |
263 | fclose(fp); | |
264 | return false; | |
265 | } | |
266 | ||
267 | if(load_grid_x_size != configured_grid_x_size) { | |
268 | stream->printf("error:grid size x is different read %d - config %d\n", load_grid_x_size, configured_grid_x_size); | |
269 | fclose(fp); | |
270 | return false; | |
271 | } | |
272 | ||
bf2abd76 | 273 | load_grid_y_size = load_grid_x_size; |
6c972e51 | 274 | |
bf2abd76 VB |
275 | if(configured_grid_x_size != configured_grid_y_size){ |
276 | if(fread(&load_grid_y_size, sizeof(uint8_t), 1, fp) != 1) { | |
277 | stream->printf("error:Failed to read grid size\n"); | |
278 | fclose(fp); | |
279 | return false; | |
280 | } | |
281 | ||
282 | if(load_grid_y_size != configured_grid_y_size) { | |
283 | stream->printf("error:grid size y is different read %d - config %d\n", load_grid_y_size, configured_grid_x_size); | |
284 | fclose(fp); | |
285 | return false; | |
286 | } | |
6c972e51 JL |
287 | } |
288 | ||
289 | if(fread(&x, sizeof(float), 1, fp) != 1) { | |
290 | stream->printf("error:Failed to read grid x size\n"); | |
291 | fclose(fp); | |
292 | return false; | |
293 | } | |
294 | ||
295 | if(fread(&y, sizeof(float), 1, fp) != 1) { | |
296 | stream->printf("error:Failed to read grid y size\n"); | |
297 | fclose(fp); | |
298 | return false; | |
299 | } | |
300 | ||
301 | if(x != x_size || y != y_size) { | |
302 | stream->printf("error:bed dimensions changed read (%f, %f) - config (%f,%f)\n", x, y, x_size, y_size); | |
303 | fclose(fp); | |
304 | return false; | |
305 | } | |
306 | ||
258f176c VB |
307 | for (int y = 0; y < configured_grid_y_size; y++) { |
308 | for (int x = 0; x < configured_grid_x_size; x++) { | |
309 | if(fread(&grid[x + (configured_grid_x_size * y)], sizeof(float), 1, fp) != 1) { | |
6c972e51 JL |
310 | stream->printf("error:Failed to read grid\n"); |
311 | fclose(fp); | |
312 | return false; | |
313 | } | |
314 | } | |
315 | } | |
258f176c | 316 | stream->printf("grid loaded, grid: (%f, %f), size: %d x %d\n", x_size, y_size, load_grid_x_size, load_grid_y_size); |
6c972e51 JL |
317 | fclose(fp); |
318 | return true; | |
319 | } | |
320 | ||
0ba48b60 | 321 | bool CartGridStrategy::probe_grid(int n, int m, float _x_start, float _y_start, float _x_size, float _y_size, StreamOutput *stream) |
6c972e51 | 322 | { |
608a0f26 | 323 | if((n < 5)||(m < 5)) { |
6c972e51 JL |
324 | stream->printf("Need at least a 5x5 grid to probe\n"); |
325 | return true; | |
326 | } | |
327 | ||
01cacfc9 JM |
328 | |
329 | if(!findBed()) return false; | |
6c972e51 | 330 | |
0ba48b60 VB |
331 | float x_step = _x_size / n; |
332 | float y_step = _y_size / m; | |
5dcdfcd7 | 333 | for (int c = 0; c < m; ++c) { |
0ba48b60 | 334 | float y = _y_start + y_step * c; |
6c972e51 | 335 | for (int r = 0; r < n; ++r) { |
0ba48b60 | 336 | float x = _x_start + x_step * r; |
6c972e51 | 337 | float z = 0.0F; |
c77455c3 JM |
338 | float mm; |
339 | if(!zprobe->doProbeAt(mm, x, y)) return false; | |
340 | z = zprobe->getProbeHeight() - mm; | |
953d8b14 | 341 | stream->printf("%1.4f ", z); |
6c972e51 JL |
342 | } |
343 | stream->printf("\n"); | |
344 | } | |
345 | return true; | |
346 | } | |
347 | ||
348 | bool CartGridStrategy::handleGcode(Gcode *gcode) | |
349 | { | |
350 | if(gcode->has_g) { | |
351 | if (gcode->g == 29) { // do a probe to test flatness | |
352 | // first wait for an empty queue i.e. no moves left | |
353 | THEKERNEL->conveyor->wait_for_idle(); | |
354 | ||
09080bb3 VB |
355 | int n = gcode->has_letter('I') ? gcode->get_value('I') : configured_grid_x_size; |
356 | int m = gcode->has_letter('J') ? gcode->get_value('J') : configured_grid_y_size; | |
01cacfc9 | 357 | |
0ba48b60 VB |
358 | float _x_size = this->x_size, _y_size = this->x_size; |
359 | float _x_start = this->x_start, _y_start = this->y_start; | |
825e3420 VB |
360 | |
361 | if(only_by_two_corners){ | |
27e1ee3d | 362 | if(gcode->has_letter('X') && gcode->has_letter('Y') && gcode->has_letter('A') && gcode->has_letter('B')){ |
0ba48b60 VB |
363 | _x_start = gcode->get_value('X'); // override default probe start point |
364 | _y_start = gcode->get_value('Y'); // override default probe start point | |
365 | _x_size = gcode->get_value('A'); // override default probe width | |
366 | _y_size = gcode->get_value('B'); // override default probe length | |
825e3420 VB |
367 | } else { |
368 | gcode->stream->printf("In only_by_two_corners mode all XYAB parameters needed\n"); | |
369 | return true; | |
370 | } | |
371 | } else { | |
0ba48b60 VB |
372 | if(gcode->has_letter('X')) _x_size = gcode->get_value('X'); // override default probe width |
373 | if(gcode->has_letter('Y')) _y_size = gcode->get_value('Y'); // override default probe length | |
825e3420 VB |
374 | } |
375 | ||
0ba48b60 | 376 | probe_grid(n, m, _x_start, _y_start, _x_size, _y_size, gcode->stream); |
c77455c3 | 377 | |
6c972e51 JL |
378 | return true; |
379 | ||
737e15be | 380 | } else if( gcode->g == 31 || gcode->g == 32) { // do a grid probe |
6c972e51 JL |
381 | // first wait for an empty queue i.e. no moves left |
382 | THEKERNEL->conveyor->wait_for_idle(); | |
383 | ||
384 | if(!doProbe(gcode)) { | |
385 | gcode->stream->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n"); | |
386 | } else { | |
387 | gcode->stream->printf("Probe completed\n"); | |
388 | } | |
389 | return true; | |
390 | } | |
391 | ||
392 | } else if(gcode->has_m) { | |
393 | if(gcode->m == 370 || gcode->m == 561) { // M370: Clear bed, M561: Set Identity Transform | |
394 | // delete the compensationTransform in robot | |
395 | setAdjustFunction(false); | |
396 | reset_bed_level(); | |
397 | gcode->stream->printf("grid cleared and disabled\n"); | |
398 | return true; | |
399 | ||
400 | } else if(gcode->m == 374) { // M374: Save grid, M374.1: delete saved grid | |
401 | if(gcode->subcode == 1) { | |
402 | remove(GRIDFILE); | |
403 | gcode->stream->printf("%s deleted\n", GRIDFILE); | |
404 | } else { | |
405 | save_grid(gcode->stream); | |
406 | } | |
407 | ||
408 | return true; | |
409 | ||
410 | } else if(gcode->m == 375) { // M375: load grid, M375.1 display grid | |
411 | if(gcode->subcode == 1) { | |
412 | print_bed_level(gcode->stream); | |
413 | } else { | |
414 | if(load_grid(gcode->stream)) setAdjustFunction(true); | |
415 | } | |
416 | return true; | |
417 | ||
418 | } else if(gcode->m == 565) { // M565: Set Z probe offsets | |
419 | float x = 0, y = 0, z = 0; | |
420 | if(gcode->has_letter('X')) x = gcode->get_value('X'); | |
421 | if(gcode->has_letter('Y')) y = gcode->get_value('Y'); | |
422 | if(gcode->has_letter('Z')) z = gcode->get_value('Z'); | |
423 | probe_offsets = std::make_tuple(x, y, z); | |
424 | return true; | |
425 | ||
426 | } else if(gcode->m == 500 || gcode->m == 503) { // M500 save, M503 display | |
427 | float x, y, z; | |
428 | std::tie(x, y, z) = probe_offsets; | |
429 | gcode->stream->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z); | |
430 | if(save) { | |
431 | if(!isnan(grid[0])) gcode->stream->printf(";Load saved grid\nM375\n"); | |
432 | else if(gcode->m == 503) gcode->stream->printf(";WARNING No grid to save\n"); | |
433 | } | |
434 | return true; | |
435 | } | |
436 | } | |
437 | ||
438 | return false; | |
439 | } | |
440 | ||
6c972e51 JL |
441 | #define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets) |
442 | #define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets) | |
443 | #define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets) | |
444 | ||
445 | void CartGridStrategy::setAdjustFunction(bool on) | |
446 | { | |
447 | if(on) { | |
448 | // set the compensationTransform in robot | |
449 | using std::placeholders::_1; | |
450 | using std::placeholders::_2; | |
451 | THEROBOT->compensationTransform = std::bind(&CartGridStrategy::doCompensation, this, _1, _2); // [this](float *target, bool inverse) { doCompensation(target, inverse); }; | |
452 | } else { | |
453 | // clear it | |
454 | THEROBOT->compensationTransform = nullptr; | |
455 | } | |
456 | } | |
457 | ||
01cacfc9 | 458 | bool CartGridStrategy::findBed() |
6c972e51 JL |
459 | { |
460 | if (do_home) zprobe->home(); | |
01cacfc9 JM |
461 | float z = initial_height; |
462 | zprobe->coordinated_move(NAN, NAN, z, zprobe->getFastFeedrate()); // move Z only to initial_height | |
036275de | 463 | zprobe->coordinated_move(x_start - X_PROBE_OFFSET_FROM_EXTRUDER, y_start - Y_PROBE_OFFSET_FROM_EXTRUDER, NAN, zprobe->getFastFeedrate()); // move at initial_height to x_start, y_start |
6c972e51 JL |
464 | |
465 | // find bed at 0,0 run at slow rate so as to not hit bed hard | |
466 | float mm; | |
01cacfc9 | 467 | if(!zprobe->run_probe_return(mm, zprobe->getSlowFeedrate())) return false; |
6c972e51 | 468 | |
01cacfc9 | 469 | // leave head probe_height above bed |
6c972e51 JL |
470 | float dz = zprobe->getProbeHeight() - mm; |
471 | zprobe->coordinated_move(NAN, NAN, dz, zprobe->getFastFeedrate(), true); // relative move | |
472 | ||
01cacfc9 | 473 | return true; |
6c972e51 JL |
474 | } |
475 | ||
476 | bool CartGridStrategy::doProbe(Gcode *gc) | |
477 | { | |
4138ce97 | 478 | gc->stream->printf("Rectangular Grid Probe...\n"); |
825e3420 VB |
479 | |
480 | if(only_by_two_corners){ | |
ea856391 | 481 | if(gc->has_letter('X') && gc->has_letter('Y') && gc->has_letter('A') && gc->has_letter('B')){ |
0ba48b60 VB |
482 | this->x_start = gc->get_value('X'); // override default probe start point, will get saved |
483 | this->y_start = gc->get_value('Y'); // override default probe start point, will get saved | |
484 | this->x_size = gc->get_value('A'); // override default probe width, will get saved | |
485 | this->y_size = gc->get_value('B'); // override default probe length, will get saved | |
825e3420 VB |
486 | } else { |
487 | gc->stream->printf("In only_by_two_corners mode all XYAB parameters needed\n"); | |
488 | return false; | |
489 | } | |
490 | } else { | |
0ba48b60 VB |
491 | if(gc->has_letter('X')) this->x_size = gc->get_value('X'); // override default probe width, will get saved |
492 | if(gc->has_letter('Y')) this->y_size = gc->get_value('Y'); // override default probe length, will get saved | |
825e3420 | 493 | } |
01cacfc9 | 494 | |
6c972e51 JL |
495 | setAdjustFunction(false); |
496 | reset_bed_level(); | |
497 | ||
09080bb3 VB |
498 | if(gc->has_letter('I')) current_grid_x_size = gc->get_value('I'); // override default grid x size |
499 | if(gc->has_letter('J')) current_grid_y_size = gc->get_value('J'); // override default grid y size | |
01cacfc9 | 500 | |
0ba48b60 | 501 | if((this->current_grid_x_size * this->current_grid_y_size) > (this->configured_grid_x_size * this->configured_grid_y_size)){ |
01cacfc9 | 502 | gc->stream->printf("Grid size (%d x %d = %d) bigger than configured (%d x %d = %d). Change configuration.\n", |
0ba48b60 VB |
503 | this->current_grid_x_size, this->current_grid_y_size, this->current_grid_x_size*this->current_grid_x_size, |
504 | this->configured_grid_x_size, this->configured_grid_y_size, this->configured_grid_x_size*this->configured_grid_y_size); | |
258f176c VB |
505 | return false; |
506 | } | |
507 | ||
6c972e51 | 508 | // find bed, and leave probe probe height above bed |
01cacfc9 | 509 | if(!findBed()) { |
68ed5df7 | 510 | gc->stream->printf("Finding bed failed, check the initial height setting\n"); |
6c972e51 JL |
511 | return false; |
512 | } | |
513 | ||
01cacfc9 | 514 | gc->stream->printf("Probe start ht is %f mm, rectangular bed width %fmm, height %fmm, grid size is %dx%d\n", zprobe->getProbeHeight(), x_size, y_size, current_grid_x_size, current_grid_y_size); |
6c972e51 JL |
515 | |
516 | // do first probe for 0,0 | |
517 | float mm; | |
0ba48b60 | 518 | if(!zprobe->doProbeAt(mm, this->x_start - X_PROBE_OFFSET_FROM_EXTRUDER, this->y_start - Y_PROBE_OFFSET_FROM_EXTRUDER)) return false; |
6c972e51 JL |
519 | float z_reference = zprobe->getProbeHeight() - mm; // this should be zero |
520 | gc->stream->printf("probe at 0,0 is %f mm\n", z_reference); | |
521 | ||
c77455c3 | 522 | // probe all the points of the grid |
0ba48b60 VB |
523 | for (int yCount = 0; yCount < this->current_grid_y_size; yCount++) { |
524 | float yProbe = this->y_start + (this->y_size / (this->current_grid_y_size - 1)) * yCount; | |
6c972e51 JL |
525 | int xStart, xStop, xInc; |
526 | if (yCount % 2) { | |
0ba48b60 | 527 | xStart = this->current_grid_x_size - 1; |
6c972e51 JL |
528 | xStop = -1; |
529 | xInc = -1; | |
b7b6110a VB |
530 | } else { |
531 | xStart = 0; | |
0ba48b60 | 532 | xStop = this->current_grid_x_size; |
b7b6110a | 533 | xInc = 1; |
6c972e51 JL |
534 | } |
535 | ||
536 | for (int xCount = xStart; xCount != xStop; xCount += xInc) { | |
0ba48b60 | 537 | float xProbe = this->x_start + (this->x_size / (this->current_grid_x_size - 1)) * xCount; |
6c972e51 JL |
538 | |
539 | if(!zprobe->doProbeAt(mm, xProbe - X_PROBE_OFFSET_FROM_EXTRUDER, yProbe - Y_PROBE_OFFSET_FROM_EXTRUDER)) return false; | |
540 | float measured_z = zprobe->getProbeHeight() - mm - z_reference; // this is the delta z from bed at 0,0 | |
953d8b14 | 541 | gc->stream->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe, yProbe, measured_z); |
0ba48b60 | 542 | grid[xCount + (this->current_grid_x_size * yCount)] = measured_z; |
6c972e51 JL |
543 | } |
544 | } | |
545 | ||
6c972e51 JL |
546 | print_bed_level(gc->stream); |
547 | ||
548 | setAdjustFunction(true); | |
549 | ||
550 | return true; | |
551 | } | |
552 | ||
6c972e51 | 553 | void CartGridStrategy::doCompensation(float *target, bool inverse) |
92143a7e | 554 | { |
6c972e51 | 555 | // Adjust print surface height by linear interpolation over the bed_level array. |
92143a7e | 556 | |
04f8c534 IA |
557 | // find min/maxes, and handle the case where size is negative (assuming this is possible? Legacy code supported this) |
558 | float min_x = std::min(this->x_start, this->x_start + this->x_size); | |
559 | float max_x = std::max(this->x_start, this->x_start + this->x_size); | |
560 | float min_y = std::min(this->y_start, this->y_start + this->y_size); | |
561 | float max_y = std::max(this->y_start, this->y_start + this->y_size); | |
562 | ||
563 | // clamp the input to the bounds of the compensation grid | |
564 | // if a point is beyond the bounds of the grid, it will get the offset of the closest grid point | |
565 | float x_target = std::min(std::max(target[X_AXIS], min_x), max_x); | |
566 | float y_target = std::min(std::max(target[Y_AXIS], min_y), max_y); | |
567 | ||
568 | float grid_x = std::max(0.001F, (x_target - this->x_start) / (this->x_size / (this->current_grid_x_size - 1))); | |
569 | float grid_y = std::max(0.001F, (y_target - this->y_start) / (this->y_size / (this->current_grid_y_size - 1))); | |
570 | int floor_x = floorf(grid_x); | |
571 | int floor_y = floorf(grid_y); | |
572 | float ratio_x = grid_x - floor_x; | |
573 | float ratio_y = grid_y - floor_y; | |
574 | float z1 = grid[(floor_x) + ((floor_y) * this->current_grid_x_size)]; | |
575 | float z2 = grid[(floor_x) + ((floor_y + 1) * this->current_grid_x_size)]; | |
576 | float z3 = grid[(floor_x + 1) + ((floor_y) * this->current_grid_x_size)]; | |
577 | float z4 = grid[(floor_x + 1) + ((floor_y + 1) * this->current_grid_x_size)]; | |
578 | float left = (1 - ratio_y) * z1 + ratio_y * z2; | |
579 | float right = (1 - ratio_y) * z3 + ratio_y * z4; | |
580 | float offset = (1 - ratio_x) * left + ratio_x * right; | |
581 | // offset scale: 1 for default (use offset as is) | |
582 | float scale = 1.0; | |
583 | ||
584 | if (!isnan(this->damping_interval)) { | |
585 | // first let's find out our 'world coordinate' positions for checking the limits: | |
586 | Robot::wcs_t world_coordinates = THEROBOT->mcs2wcs(THEROBOT->get_axis_position()); | |
587 | float current_z = THEROBOT->from_millimeters(std::get<Z_AXIS>(world_coordinates)); | |
588 | // THEKERNEL->streams->printf("//DEBUG: Current Z: %f\n", current_z); | |
589 | // if the height is below our compensation limit: | |
590 | if(current_z <= this->height_limit) { | |
591 | // scale the offset as necessary: | |
592 | if( current_z >= this->dampening_start) | |
593 | scale = ( 1- ( (current_z - this->dampening_start ) / this->damping_interval) ); | |
594 | // else leave scale at 1.0; | |
595 | } else | |
596 | scale = 0.0; // if Z is higher than max, no compensation | |
597 | } | |
598 | ||
599 | if(inverse) | |
600 | target[Z_AXIS] -= offset * scale; | |
601 | else | |
602 | target[Z_AXIS] += offset * scale; | |
603 | ||
604 | /*THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]); | |
605 | THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x); | |
606 | THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y); | |
607 | THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x); | |
608 | THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y); | |
609 | THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x); | |
610 | THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y); | |
611 | THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1); | |
612 | THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2); | |
613 | THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3); | |
614 | THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4); | |
615 | THEKERNEL->streams->printf("//DEBUG: left= %f\n", left); | |
616 | THEKERNEL->streams->printf("//DEBUG: right= %f\n", right); | |
617 | THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset); | |
618 | THEKERNEL->streams->printf("//DEBUG: scale= %f\n", scale); | |
619 | */ | |
92143a7e | 620 | |
6c972e51 JL |
621 | } |
622 | ||
623 | ||
624 | // Print calibration results for plotting or manual frame adjustment. | |
625 | void CartGridStrategy::print_bed_level(StreamOutput *stream) | |
626 | { | |
2f05c122 VB |
627 | if(!human_readable){ |
628 | for (int y = 0; y < current_grid_y_size; y++) { | |
629 | for (int x = 0; x < current_grid_x_size; x++) { | |
8683c07c | 630 | stream->printf("%1.4f ", grid[x + (current_grid_x_size * y)]); |
2f05c122 VB |
631 | } |
632 | stream->printf("\n"); | |
633 | } | |
634 | } else { | |
01cacfc9 | 635 | |
2f05c122 VB |
636 | int xStart = (x_size>0) ? 0 : (current_grid_x_size - 1); |
637 | int xStop = (x_size>0) ? current_grid_x_size : -1; | |
638 | int xInc = (x_size>0) ? 1: -1; | |
639 | ||
640 | int yStart = (y_size<0) ? 0 : (current_grid_y_size - 1); | |
641 | int yStop = (y_size<0) ? current_grid_y_size : -1; | |
642 | int yInc = (y_size<0) ? 1: -1; | |
643 | ||
644 | for (int y = yStart; y != yStop; y += yInc) { | |
0ba48b60 | 645 | stream->printf("%10.4f|", y * (y_size / (current_grid_y_size - 1))); |
2f05c122 VB |
646 | for (int x = xStart; x != xStop; x += xInc) { |
647 | stream->printf("%10.4f ", grid[x + (current_grid_x_size * y)]); | |
648 | } | |
649 | stream->printf("\n"); | |
650 | } | |
651 | stream->printf(" "); | |
652 | for (int x = xStart; x != xStop; x += xInc) { | |
653 | stream->printf("-----+-----"); | |
6c972e51 JL |
654 | } |
655 | stream->printf("\n"); | |
2f05c122 VB |
656 | stream->printf(" "); |
657 | for (int x = xStart; x != xStop; x += xInc) { | |
0ba48b60 | 658 | stream->printf("%1.4f ", x * (x_size / (current_grid_x_size - 1))); |
2f05c122 VB |
659 | } |
660 | stream->printf("\n"); | |
661 | ||
6c972e51 | 662 | } |
2f05c122 | 663 | |
6c972e51 JL |
664 | } |
665 | ||
666 | // Reset calibration results to zero. | |
667 | void CartGridStrategy::reset_bed_level() | |
668 | { | |
258f176c VB |
669 | for (int y = 0; y < current_grid_y_size; y++) { |
670 | for (int x = 0; x < current_grid_x_size; x++) { | |
671 | grid[x + (current_grid_x_size * y)] = NAN; | |
6c972e51 JL |
672 | } |
673 | } | |
674 | } |