Merge remote-tracking branch 'upstream/edge' into feature/soft-endstops
[clinton/Smoothieware.git] / src / modules / tools / zprobe / DeltaGridStrategy.cpp
CommitLineData
47473cfb
JM
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
5382ba62 8 When enabled every move will calculate the Z offset based on interpolating the height offset within the grids nearest 4 points.
47473cfb
JM
9
10 Configuration
11 -------------
12 The strategy must be enabled in the config as well as zprobe.
13
14 leveling-strategy.delta-grid.enable true
15
16 The radius of the bed must be specified with...
17
18 leveling-strategy.delta-grid.radius 50
19
20 this needs to be at least as big as the maximum printing radius as moves outside of this will not be compensated for correctly
21
22 The size of the grid can be set with...
23
24 leveling-strategy.delta-grid.size 7
25
26 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
27
28 Optionally probe offsets from the nozzle or tool head can be defined with...
29
30 leveling-strategy.delta-grid.probe_offsets 0,0,0 # probe offsetrs x,y,z
31
32 they may also be set with M565 X0 Y0 Z0
33
34 If the saved grid is to be loaded on boot then this must be set in the config...
35
36 leveling-strategy.delta-grid.save true
37
38 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
39
40 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
41 leveling-strategy.delta-grid.initial_height 10
42
43
44 Usage
45 -----
46 G29 test probes in a spiral pattern within the radius producing a map of offsets, this can be imported into a graphing program to visualize the bed heights
1b69657f
JM
47 optional parameters {{In}} sets the number of points to the value n, {{Jn}} sets the radius for this probe.
48
47473cfb 49 G31 probes the grid and turns the compensation on, this will remain in effect until reset or M561/M370
fce37565 50 optional parameters {{Jn}} sets the radius for this probe, which gets saved with M375
47473cfb
JM
51
52 M370 clears the grid and turns off compensation
53 M374 Save grid to /sd/delta.grid
54 M374.1 delete /sd/delta.grid
55 M375 Load the grid from /sd/delta.grid and enable compensation
56 M375.1 display the current grid
57 M561 clears the grid and turns off compensation
58 M565 defines the probe offsets from the nozzle or tool head
59
60
61 M500 saves the probe points
62 M503 displays the current settings
63*/
59d4d4ea
JM
64
65#include "DeltaGridStrategy.h"
66
67#include "Kernel.h"
68#include "Config.h"
69#include "Robot.h"
70#include "StreamOutputPool.h"
71#include "Gcode.h"
72#include "checksumm.h"
73#include "ConfigValue.h"
74#include "PublicDataRequest.h"
75#include "PublicData.h"
76#include "Conveyor.h"
77#include "ZProbe.h"
78#include "nuts_bolts.h"
79#include "utils.h"
3b4faa5e 80#include "platform_memory.h"
59d4d4ea
JM
81
82#include <string>
83#include <algorithm>
84#include <cstdlib>
85#include <cmath>
9b0a0b86 86#include <fastmath.h>
59d4d4ea 87
e9f69a35 88#define grid_radius_checksum CHECKSUM("radius")
3b4faa5e 89#define grid_size_checksum CHECKSUM("size")
e9f69a35
JM
90#define tolerance_checksum CHECKSUM("tolerance")
91#define save_checksum CHECKSUM("save")
59d4d4ea 92#define probe_offsets_checksum CHECKSUM("probe_offsets")
e9f69a35 93#define initial_height_checksum CHECKSUM("initial_height")
dbe510e8 94#define do_home_checksum CHECKSUM("do_home")
ce0ea953 95#define is_square_checksum CHECKSUM("is_square") // deprecated
59d4d4ea 96
18679bb7
JM
97#define GRIDFILE "/sd/delta.grid"
98
59d4d4ea
JM
99DeltaGridStrategy::DeltaGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe)
100{
9b0a0b86 101 grid = nullptr;
59d4d4ea
JM
102}
103
104DeltaGridStrategy::~DeltaGridStrategy()
105{
3b4faa5e 106 if(grid != nullptr) AHB0.dealloc(grid);
59d4d4ea
JM
107}
108
109bool DeltaGridStrategy::handleConfig()
110{
3b4faa5e 111 grid_size = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, grid_size_checksum)->by_default(7)->as_number();
59d4d4ea
JM
112 tolerance = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, tolerance_checksum)->by_default(0.03F)->as_number();
113 save = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, save_checksum)->by_default(false)->as_bool();
dbe510e8
JL
114 do_home = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, do_home_checksum)->by_default(true)->as_bool();
115 is_square = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, is_square_checksum)->by_default(false)->as_bool();
ce0ea953 116 grid_radius = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, grid_radius_checksum)->by_default(50.0F)->as_number();
3b4faa5e 117
59d4d4ea
JM
118 // the initial height above the bed we stop the intial move down after home to find the bed
119 // 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)
072a5bd0 120 this->initial_height = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, initial_height_checksum)->by_default(10)->as_number();
59d4d4ea
JM
121
122 // Probe offsets xxx,yyy,zzz
123 {
124 std::string po = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string();
125 std::vector<float> v = parse_number_list(po.c_str());
126 if(v.size() >= 3) {
127 this->probe_offsets = std::make_tuple(v[0], v[1], v[2]);
128 }
129 }
130
3b4faa5e 131 // allocate in AHB0
9b0a0b86 132 grid = (float *)AHB0.alloc(grid_size * grid_size * sizeof(float));
3b4faa5e 133
fc6c9aac
JM
134 if(grid == nullptr) {
135 THEKERNEL->streams->printf("Error: Not enough memory\n");
136 return false;
137 }
138
cf6b8fd1 139 reset_bed_level();
82357f91 140
59d4d4ea
JM
141 return true;
142}
143
e9f69a35
JM
144void DeltaGridStrategy::save_grid(StreamOutput *stream)
145{
fce37565
JM
146 if(isnan(grid[0])) {
147 stream->printf("error:No grid to save\n");
148 return;
149 }
150
072a5bd0 151 FILE *fp = fopen(GRIDFILE, "w");
e9f69a35 152 if(fp == NULL) {
fce37565
JM
153 stream->printf("error:Failed to open grid file %s\n", GRIDFILE);
154 return;
155 }
156
157 if(fwrite(&grid_size, sizeof(uint8_t), 1, fp) != 1) {
158 stream->printf("error:Failed to write grid size\n");
159 fclose(fp);
160 return;
161 }
162
163 if(fwrite(&grid_radius, sizeof(float), 1, fp) != 1) {
164 stream->printf("error:Failed to write grid radius\n");
165 fclose(fp);
e9f69a35
JM
166 return;
167 }
168
3b4faa5e
JM
169 for (int y = 0; y < grid_size; y++) {
170 for (int x = 0; x < grid_size; x++) {
9b0a0b86 171 if(fwrite(&grid[x + (grid_size * y)], sizeof(float), 1, fp) != 1) {
e9f69a35
JM
172 stream->printf("error:Failed to write grid\n");
173 fclose(fp);
174 return;
175 }
176 }
177 }
c5e0c263 178 stream->printf("grid saved to %s\n", GRIDFILE);
e9f69a35
JM
179 fclose(fp);
180}
181
c5e0c263 182bool DeltaGridStrategy::load_grid(StreamOutput *stream)
e9f69a35 183{
072a5bd0 184 FILE *fp = fopen(GRIDFILE, "r");
e9f69a35 185 if(fp == NULL) {
c0b50fa8 186 stream->printf("error:Failed to open grid %s\n", GRIDFILE);
fce37565
JM
187 return false;
188 }
189
190 uint8_t size;
191 float radius;
192
193 if(fread(&size, sizeof(uint8_t), 1, fp) != 1) {
c0b50fa8 194 stream->printf("error:Failed to read grid size\n");
fce37565
JM
195 fclose(fp);
196 return false;
197 }
198
199 if(size != grid_size) {
c0b50fa8 200 stream->printf("error:grid size is different read %d - config %d\n", size, grid_size);
fce37565
JM
201 fclose(fp);
202 return false;
203 }
204
e536f3eb 205 if(fread(&radius, sizeof(float), 1, fp) != 1) {
c0b50fa8 206 stream->printf("error:Failed to read grid radius\n");
fce37565 207 fclose(fp);
c5e0c263 208 return false;
e9f69a35
JM
209 }
210
fce37565 211 if(radius != grid_radius) {
ce0ea953
JM
212 stream->printf("warning:grid radius is different read %f - config %f, overriding config\n", radius, grid_radius);
213 grid_radius = radius;
fce37565
JM
214 }
215
3b4faa5e
JM
216 for (int y = 0; y < grid_size; y++) {
217 for (int x = 0; x < grid_size; x++) {
9b0a0b86 218 if(fread(&grid[x + (grid_size * y)], sizeof(float), 1, fp) != 1) {
c0b50fa8 219 stream->printf("error:Failed to read grid\n");
e9f69a35 220 fclose(fp);
c5e0c263 221 return false;
e9f69a35
JM
222 }
223 }
224 }
c0b50fa8 225 stream->printf("grid loaded, radius: %f, size: %d\n", grid_radius, grid_size);
e9f69a35 226 fclose(fp);
c5e0c263 227 return true;
e9f69a35 228}
59d4d4ea 229
95b8e3f2
JM
230bool DeltaGridStrategy::probe_grid(int n, float radius, StreamOutput *stream)
231{
232 if(n < 5) {
233 stream->printf("Need at least a 5x5 grid to probe\n");
234 return true;
235 }
236
223ae9d6
JM
237 float initial_z = findBed();
238 if(isnan(initial_z)) return false;
239
9b0a0b86 240 float d = ((radius * 2) / (n - 1));
95b8e3f2
JM
241
242 for (int c = 0; c < n; ++c) {
9b0a0b86 243 float y = -radius + d * c;
95b8e3f2 244 for (int r = 0; r < n; ++r) {
9b0a0b86 245 float x = -radius + d * r;
95b8e3f2 246 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
9b0a0b86
JM
247 float distance_from_center = sqrtf(x * x + y * y);
248 float z = 0.0F;
ce0ea953 249 if (distance_from_center <= radius) {
6d142b73
JM
250 float mm;
251 if(!zprobe->doProbeAt(mm, x, y)) return false;
252 z = zprobe->getProbeHeight() - mm;
95b8e3f2
JM
253 }
254 stream->printf("%8.4f ", z);
255 }
256 stream->printf("\n");
257 }
258 return true;
259}
260
3b4faa5e 261// taken from Oskars PR #713
95b8e3f2 262bool DeltaGridStrategy::probe_spiral(int n, float radius, StreamOutput *stream)
072a5bd0 263{
95b8e3f2
JM
264 float a = radius / (2 * sqrtf(n * M_PI));
265 float step_length = radius * radius / (2 * a * n);
072a5bd0
JM
266
267 float initial_z = findBed();
268 if(isnan(initial_z)) return false;
269
9b0a0b86 270 auto theta = [a](float length) {return sqrtf(2 * length / a); };
072a5bd0 271
9b0a0b86 272 float maxz = NAN, minz = NAN;
072a5bd0
JM
273 for (int i = 0; i < n; i++) {
274 float angle = theta(i * step_length);
275 float r = angle * a;
276 // polar to cartesian
277 float x = r * cosf(angle);
278 float y = r * sinf(angle);
279
6d142b73
JM
280 float mm;
281 if (!zprobe->doProbeAt(mm, x, y)) return false;
282 float z = zprobe->getProbeHeight() - mm;
072a5bd0 283 stream->printf("PROBE: X%1.4f, Y%1.4f, Z%1.4f\n", x, y, z);
9b0a0b86
JM
284 if(isnan(maxz) || z > maxz) maxz = z;
285 if(isnan(minz) || z < minz) minz = z;
072a5bd0
JM
286 }
287
9b0a0b86 288 stream->printf("max: %1.4f, min: %1.4f, delta: %1.4f\n", maxz, minz, maxz - minz);
072a5bd0
JM
289 return true;
290}
291
59d4d4ea
JM
292bool DeltaGridStrategy::handleGcode(Gcode *gcode)
293{
294 if(gcode->has_g) {
95b8e3f2
JM
295 if (gcode->g == 29) { // do a probe to test flatness
296 // first wait for an empty queue i.e. no moves left
04782655 297 THEKERNEL->conveyor->wait_for_idle();
95b8e3f2 298
9b0a0b86 299 int n = gcode->has_letter('I') ? gcode->get_value('I') : 0;
95b8e3f2
JM
300 float radius = grid_radius;
301 if(gcode->has_letter('J')) radius = gcode->get_value('J'); // override default probe radius
9b0a0b86
JM
302 if(gcode->subcode == 1) {
303 if(n == 0) n = 50;
95b8e3f2 304 probe_spiral(n, radius, gcode->stream);
9b0a0b86
JM
305 } else {
306 if(n == 0) n = 7;
95b8e3f2
JM
307 probe_grid(n, radius, gcode->stream);
308 }
309
072a5bd0
JM
310 return true;
311
312 } else if( gcode->g == 31 ) { // do a grid probe
ce0ea953
JM
313
314 if(is_square) {
315 // Handle deprecated is_square
316 gcode->stream->printf("Error: is_square has been removed, please use the new rectangular_grid strategy instead\n");
317 return false;
318 }
319
072a5bd0 320 // first wait for an empty queue i.e. no moves left
04782655 321 THEKERNEL->conveyor->wait_for_idle();
59d4d4ea
JM
322
323 if(!doProbe(gcode)) {
41367a87 324 gcode->stream->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n");
59d4d4ea 325 } else {
de111a90 326 gcode->stream->printf("Probe completed - Enter M374 to save this grid\n");
59d4d4ea
JM
327 }
328 return true;
329 }
330
331 } else if(gcode->has_m) {
e9f69a35 332 if(gcode->m == 370 || gcode->m == 561) { // M370: Clear bed, M561: Set Identity Transform
59d4d4ea
JM
333 // delete the compensationTransform in robot
334 setAdjustFunction(false);
335 reset_bed_level();
95b8e3f2 336 gcode->stream->printf("grid cleared and disabled\n");
59d4d4ea
JM
337 return true;
338
18679bb7 339 } else if(gcode->m == 374) { // M374: Save grid, M374.1: delete saved grid
cf6b8fd1 340 if(gcode->subcode == 1) {
18679bb7 341 remove(GRIDFILE);
c5e0c263 342 gcode->stream->printf("%s deleted\n", GRIDFILE);
072a5bd0 343 } else {
18679bb7
JM
344 save_grid(gcode->stream);
345 }
346
e9f69a35
JM
347 return true;
348
349 } else if(gcode->m == 375) { // M375: load grid, M375.1 display grid
350 if(gcode->subcode == 1) {
351 print_bed_level(gcode->stream);
072a5bd0 352 } else {
c5e0c263 353 if(load_grid(gcode->stream)) setAdjustFunction(true);
e9f69a35 354 }
c0b50fa8 355 return true;
e9f69a35 356
59d4d4ea 357 } else if(gcode->m == 565) { // M565: Set Z probe offsets
072a5bd0 358 float x = 0, y = 0, z = 0;
59d4d4ea
JM
359 if(gcode->has_letter('X')) x = gcode->get_value('X');
360 if(gcode->has_letter('Y')) y = gcode->get_value('Y');
361 if(gcode->has_letter('Z')) z = gcode->get_value('Z');
362 probe_offsets = std::make_tuple(x, y, z);
363 return true;
364
365 } else if(gcode->m == 500 || gcode->m == 503) { // M500 save, M503 display
366 float x, y, z;
59d4d4ea 367 std::tie(x, y, z) = probe_offsets;
c5e0c263 368 gcode->stream->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z);
fce37565
JM
369 if(save) {
370 if(!isnan(grid[0])) gcode->stream->printf(";Load saved grid\nM375\n");
371 else if(gcode->m == 503) gcode->stream->printf(";WARNING No grid to save\n");
372 }
59d4d4ea
JM
373 return true;
374 }
375 }
376
377 return false;
378}
379
3b4faa5e 380// These are convenience defines to keep the code as close to the original as possible it also saves memory and flash
59d4d4ea 381// set the rectangle in which to probe
3b4faa5e
JM
382#define LEFT_PROBE_BED_POSITION (-grid_radius)
383#define RIGHT_PROBE_BED_POSITION (grid_radius)
384#define BACK_PROBE_BED_POSITION (grid_radius)
385#define FRONT_PROBE_BED_POSITION (-grid_radius)
59d4d4ea
JM
386
387// probe at the points of a lattice grid
3b4faa5e
JM
388#define AUTO_BED_LEVELING_GRID_X ((RIGHT_PROBE_BED_POSITION - LEFT_PROBE_BED_POSITION) / (grid_size - 1))
389#define AUTO_BED_LEVELING_GRID_Y ((BACK_PROBE_BED_POSITION - FRONT_PROBE_BED_POSITION) / (grid_size - 1))
59d4d4ea
JM
390
391#define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets)
392#define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets)
393#define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets)
394
395void DeltaGridStrategy::setAdjustFunction(bool on)
396{
397 if(on) {
398 // set the compensationTransform in robot
8fe38353
JM
399 using std::placeholders::_1;
400 using std::placeholders::_2;
401 THEROBOT->compensationTransform = std::bind(&DeltaGridStrategy::doCompensation, this, _1, _2); // [this](float *target, bool inverse) { doCompensation(target, inverse); };
072a5bd0 402 } else {
59d4d4ea 403 // clear it
c8bac202 404 THEROBOT->compensationTransform = nullptr;
59d4d4ea
JM
405 }
406}
407
408float DeltaGridStrategy::findBed()
409{
dbe510e8 410 if (do_home) zprobe->home();
59d4d4ea 411 // move to an initial position fast so as to not take all day, we move down max_z - initial_height, which is set in config, default 10mm
e9b3ba64
JL
412 float deltaz = initial_height;
413 zprobe->coordinated_move(NAN, NAN, deltaz, zprobe->getFastFeedrate());
59d4d4ea
JM
414 zprobe->coordinated_move(0, 0, NAN, zprobe->getFastFeedrate()); // move to 0,0
415
416 // find bed at 0,0 run at slow rate so as to not hit bed hard
6d142b73 417 float mm;
71f0df19 418 if(!zprobe->run_probe_return(mm, zprobe->getSlowFeedrate())) return NAN;
a1613f8b 419
9b0a0b86 420 float dz = zprobe->getProbeHeight() - mm;
a1613f8b 421 zprobe->coordinated_move(NAN, NAN, dz, zprobe->getFastFeedrate(), true); // relative move
59d4d4ea 422
6d142b73 423 return mm + deltaz - zprobe->getProbeHeight(); // distance to move from home to 5mm above bed
59d4d4ea
JM
424}
425
426bool DeltaGridStrategy::doProbe(Gcode *gc)
427{
5382ba62 428 gc->stream->printf("Delta Grid Probe...\n");
59d4d4ea 429 setAdjustFunction(false);
95b8e3f2 430 reset_bed_level();
59d4d4ea 431
fce37565 432 if(gc->has_letter('J')) grid_radius = gc->get_value('J'); // override default probe radius, will get saved
a1613f8b 433
fce37565 434 float radius = grid_radius;
a1613f8b 435 // find bed, and leave probe probe height above bed
072a5bd0 436 float initial_z = findBed();
a1613f8b
JM
437 if(isnan(initial_z)) {
438 gc->stream->printf("Finding bed failed, check the maxz and initial height settings\n");
439 return false;
440 }
441
39156759 442 gc->stream->printf("Probe start ht is %f mm, probe radius is %f mm, grid size is %dx%d\n", initial_z, radius, grid_size, grid_size);
59d4d4ea 443
c5e0c263 444 // do first probe for 0,0
6d142b73
JM
445 float mm;
446 if(!zprobe->doProbeAt(mm, -X_PROBE_OFFSET_FROM_EXTRUDER, -Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
447 float z_reference = zprobe->getProbeHeight() - mm; // this should be zero
c5e0c263
JM
448 gc->stream->printf("probe at 0,0 is %f mm\n", z_reference);
449
95b8e3f2 450 // probe all the points in the grid within the given radius
3b4faa5e 451 for (int yCount = 0; yCount < grid_size; yCount++) {
59d4d4ea
JM
452 float yProbe = FRONT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_Y * yCount;
453 int xStart, xStop, xInc;
454 if (yCount % 2) {
455 xStart = 0;
3b4faa5e 456 xStop = grid_size;
59d4d4ea
JM
457 xInc = 1;
458 } else {
3b4faa5e 459 xStart = grid_size - 1;
59d4d4ea
JM
460 xStop = -1;
461 xInc = -1;
462 }
463
464 for (int xCount = xStart; xCount != xStop; xCount += xInc) {
465 float xProbe = LEFT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_X * xCount;
466
ce0ea953
JM
467 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
468 float distance_from_center = sqrtf(xProbe * xProbe + yProbe * yProbe);
469 if (distance_from_center > radius) continue;
59d4d4ea 470
6d142b73
JM
471 if(!zprobe->doProbeAt(mm, xProbe - X_PROBE_OFFSET_FROM_EXTRUDER, yProbe - Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
472 float measured_z = zprobe->getProbeHeight() - mm - z_reference; // this is the delta z from bed at 0,0
95b8e3f2 473 gc->stream->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe, yProbe, measured_z);
3b4faa5e 474 grid[xCount + (grid_size * yCount)] = measured_z;
59d4d4ea
JM
475 }
476 }
477
478 extrapolate_unprobed_bed_level();
479 print_bed_level(gc->stream);
480
e9f69a35
JM
481 setAdjustFunction(true);
482
59d4d4ea
JM
483 return true;
484}
485
486void DeltaGridStrategy::extrapolate_one_point(int x, int y, int xdir, int ydir)
487{
9b0a0b86 488 if (!isnan(grid[x + (grid_size * y)])) {
59d4d4ea
JM
489 return; // Don't overwrite good values.
490 }
9b0a0b86 491 float a = 2 * grid[(x + xdir) + (y * grid_size)] - grid[(x + xdir * 2) + (y * grid_size)]; // Left to right.
3b4faa5e
JM
492 float b = 2 * grid[x + ((y + ydir) * grid_size)] - grid[x + ((y + ydir * 2) * grid_size)]; // Front to back.
493 float c = 2 * grid[(x + xdir) + ((y + ydir) * grid_size)] - grid[(x + xdir * 2) + ((y + ydir * 2) * grid_size)]; // Diagonal.
59d4d4ea
JM
494 float median = c; // Median is robust (ignores outliers).
495 if (a < b) {
496 if (b < c) median = b;
497 if (c < a) median = a;
498 } else { // b <= a
499 if (c < b) median = b;
500 if (a < c) median = a;
501 }
9b0a0b86 502 grid[x + (grid_size * y)] = median;
59d4d4ea
JM
503}
504
505// Fill in the unprobed points (corners of circular print surface)
506// using linear extrapolation, away from the center.
507void DeltaGridStrategy::extrapolate_unprobed_bed_level()
508{
3b4faa5e 509 int half = (grid_size - 1) / 2;
59d4d4ea
JM
510 for (int y = 0; y <= half; y++) {
511 for (int x = 0; x <= half; x++) {
512 if (x + y < 3) continue;
513 extrapolate_one_point(half - x, half - y, x > 1 ? +1 : 0, y > 1 ? +1 : 0);
514 extrapolate_one_point(half + x, half - y, x > 1 ? -1 : 0, y > 1 ? +1 : 0);
515 extrapolate_one_point(half - x, half + y, x > 1 ? +1 : 0, y > 1 ? -1 : 0);
516 extrapolate_one_point(half + x, half + y, x > 1 ? -1 : 0, y > 1 ? -1 : 0);
517 }
518 }
519}
520
8fe38353 521void DeltaGridStrategy::doCompensation(float *target, bool inverse)
59d4d4ea
JM
522{
523 // Adjust print surface height by linear interpolation over the bed_level array.
3b4faa5e 524 int half = (grid_size - 1) / 2;
59d4d4ea
JM
525 float grid_x = std::max(0.001F - half, std::min(half - 0.001F, target[X_AXIS] / AUTO_BED_LEVELING_GRID_X));
526 float grid_y = std::max(0.001F - half, std::min(half - 0.001F, target[Y_AXIS] / AUTO_BED_LEVELING_GRID_Y));
527 int floor_x = floorf(grid_x);
528 int floor_y = floorf(grid_y);
529 float ratio_x = grid_x - floor_x;
530 float ratio_y = grid_y - floor_y;
3b4faa5e
JM
531 float z1 = grid[(floor_x + half) + ((floor_y + half) * grid_size)];
532 float z2 = grid[(floor_x + half) + ((floor_y + half + 1) * grid_size)];
533 float z3 = grid[(floor_x + half + 1) + ((floor_y + half) * grid_size)];
534 float z4 = grid[(floor_x + half + 1) + ((floor_y + half + 1) * grid_size)];
59d4d4ea
JM
535 float left = (1 - ratio_y) * z1 + ratio_y * z2;
536 float right = (1 - ratio_y) * z3 + ratio_y * z4;
537 float offset = (1 - ratio_x) * left + ratio_x * right;
538
8fe38353
JM
539 if(inverse)
540 target[Z_AXIS] -= offset;
541 else
542 target[Z_AXIS] += offset;
e9f69a35
JM
543
544
072a5bd0
JM
545 /*
546 THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]);
547 THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x);
548 THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y);
549 THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x);
550 THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y);
551 THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x);
552 THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y);
553 THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1);
554 THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2);
555 THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3);
556 THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4);
557 THEKERNEL->streams->printf("//DEBUG: left= %f\n", left);
558 THEKERNEL->streams->printf("//DEBUG: right= %f\n", right);
559 THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset);
560 */
59d4d4ea
JM
561}
562
563
564// Print calibration results for plotting or manual frame adjustment.
565void DeltaGridStrategy::print_bed_level(StreamOutput *stream)
566{
3b4faa5e
JM
567 for (int y = 0; y < grid_size; y++) {
568 for (int x = 0; x < grid_size; x++) {
9b0a0b86 569 stream->printf("%7.4f ", grid[x + (grid_size * y)]);
59d4d4ea
JM
570 }
571 stream->printf("\n");
572 }
573}
574
575// Reset calibration results to zero.
576void DeltaGridStrategy::reset_bed_level()
577{
3b4faa5e
JM
578 for (int y = 0; y < grid_size; y++) {
579 for (int x = 0; x < grid_size; x++) {
9b0a0b86 580 grid[x + (grid_size * y)] = NAN;
59d4d4ea
JM
581 }
582 }
583}