Commit | Line | Data |
---|---|---|
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 |
99 | DeltaGridStrategy::DeltaGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe) |
100 | { | |
9b0a0b86 | 101 | grid = nullptr; |
59d4d4ea JM |
102 | } |
103 | ||
104 | DeltaGridStrategy::~DeltaGridStrategy() | |
105 | { | |
3b4faa5e | 106 | if(grid != nullptr) AHB0.dealloc(grid); |
59d4d4ea JM |
107 | } |
108 | ||
109 | bool 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 |
144 | void 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 | 182 | bool 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 |
230 | bool 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 | 262 | bool 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 |
292 | bool 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 | ||
395 | void 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 | ||
408 | float 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 | ||
426 | bool 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 | ||
486 | void 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. | |
507 | void 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 | 521 | void 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. | |
565 | void 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. | |
576 | void 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 | } |