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