Commit | Line | Data |
---|---|---|
77fbe368 QH |
1 | /* |
2 | Author: Quentin Harley (quentin.harley@gmail.com) | |
3 | License: GPL3 or better see <http://www.gnu.org/licenses/> | |
4 | ||
5 | Summary | |
6 | ------- | |
7 | Probes user defined amount of calculated points on the bed and creates compensation grid data of the bed surface. | |
8 | Bilinear | |
9 | As the head moves in X and Y it will adjust Z to keep the head level with the bed. | |
10 | ||
11 | Configuration | |
12 | ------------- | |
13 | The strategy must be enabled in the config as well as zprobe. | |
14 | ||
c880f6dd | 15 | leveling-strategy.ZGrid-leveling.enable true |
77fbe368 QH |
16 | |
17 | The bed size limits must be defined, in order for the module to calculate the calibration points | |
18 | ||
c880f6dd QH |
19 | leveling-strategy.ZGrid-leveling.bed_x 200 |
20 | leveling-strategy.ZGrid-leveling.bed_y 200 | |
fc29aeae RM |
21 | |
22 | Machine height, used to determine probe attachment point (bed_z / 2) | |
23 | ||
c880f6dd | 24 | leveling-strategy.ZGrid-leveling.bed_z 20 |
77fbe368 | 25 | |
dea3c8c0 QH |
26 | Configure for Machines with bed 0:0 at center of platform |
27 | leveling-strategy.ZGrid-leveling.bed_zero false | |
28 | ||
29 | configure for Machines with circular beds | |
30 | leveling-strategy.ZGrid-leveling.bed_circular false | |
31 | ||
77fbe368 QH |
32 | |
33 | The number of divisions for X and Y should be defined | |
34 | ||
c880f6dd QH |
35 | leveling-strategy.ZGrid-leveling.rows 7 # X divisions (Default 5) |
36 | leveling-strategy.ZGrid-leveling.cols 9 # Y divisions (Default 5) | |
77fbe368 QH |
37 | |
38 | ||
39 | The probe offset should be defined, default to zero offset | |
40 | ||
c880f6dd | 41 | leveling-strategy.ZGrid-leveling.probe_offsets 0,0,16.3 |
77fbe368 | 42 | |
c880f6dd | 43 | The machine can be told to wait for probe attachment and confirmation |
77fbe368 | 44 | |
c880f6dd | 45 | leveling-strategy.ZGrid-leveling.wait_for_probe true |
77fbe368 | 46 | |
962b3c72 QH |
47 | The machine can be told to home in one of the following modes: |
48 | ||
36256be6 | 49 | leveling-strategy.ZGrid-leveling.home_before_probe homexyz; # nohome homexy homexyz (default) |
962b3c72 | 50 | |
c880f6dd | 51 | |
a4bc47fd | 52 | Slow feedrate can be defined for probe positioning speed. Note this is not Probing slow rate - it can be set to a fast speed if required. |
c880f6dd | 53 | |
a4bc47fd | 54 | leveling-strategy.ZGrid-leveling.slow_feedrate 100 # ZGrid probe positioning feedrate |
77fbe368 QH |
55 | |
56 | ||
57 | ||
58 | Usage | |
59 | ----- | |
47044040 QH |
60 | G32 : probes the probe points and defines the bed ZGrid, this will remain in effect until reset or M370 |
61 | G31 : reports the status - Display probe data points | |
77fbe368 | 62 | |
47044040 QH |
63 | M370 : clears the ZGrid and the bed levelling is disabled until G32 is run again |
64 | M370 X7 Y9 : allocates a new grid size of 7x9 and clears as above | |
77fbe368 | 65 | |
47044040 QH |
66 | M371 : moves the head to the next calibration position without saving for manual calibration |
67 | M372 : move the head to the next calibration position after saving the current probe point to memory - manual calbration | |
68 | M373 : completes calibration and enables the Z compensation grid | |
77fbe368 | 69 | |
47044040 QH |
70 | M374 : Save the grid to "Zgrid" on SD card |
71 | M374 S### : Save custom grid to "Zgrid.###" on SD card | |
77fbe368 | 72 | |
47044040 QH |
73 | M375 : Loads grid file "Zgrid" from SD |
74 | M375 S### : Load custom grid file "Zgrid.###" | |
77fbe368 | 75 | |
47044040 QH |
76 | M565 X### Y### Z### : Set new probe offsets |
77 | ||
78 | M500 : saves the probe offsets | |
79 | M503 : displays the current settings | |
77fbe368 QH |
80 | */ |
81 | ||
82 | #include "ZGridStrategy.h" | |
83 | #include "Kernel.h" | |
84 | #include "Config.h" | |
85 | #include "Robot.h" | |
86 | #include "StreamOutputPool.h" | |
87 | #include "Gcode.h" | |
88 | #include "checksumm.h" | |
89 | #include "ConfigValue.h" | |
90 | #include "PublicDataRequest.h" | |
91 | #include "PublicData.h" | |
92 | #include "EndstopsPublicAccess.h" | |
93 | #include "Conveyor.h" | |
94 | #include "ZProbe.h" | |
95 | #include "libs/FileStream.h" | |
96 | #include "nuts_bolts.h" | |
97 | #include "platform_memory.h" | |
98 | #include "MemoryPool.h" | |
99 | #include "libs/utils.h" | |
100 | ||
77fbe368 QH |
101 | #include <string> |
102 | #include <algorithm> | |
103 | #include <cstdlib> | |
104 | #include <cmath> | |
d223207b | 105 | #include <unistd.h> |
77fbe368 QH |
106 | |
107 | #define bed_x_checksum CHECKSUM("bed_x") | |
108 | #define bed_y_checksum CHECKSUM("bed_y") | |
109 | #define bed_z_checksum CHECKSUM("bed_z") | |
110 | ||
111 | #define slow_feedrate_checksum CHECKSUM("slow_feedrate") | |
112 | #define probe_offsets_checksum CHECKSUM("probe_offsets") | |
c880f6dd | 113 | #define wait_for_probe_checksum CHECKSUM("wait_for_probe") |
962b3c72 | 114 | #define home_before_probe_checksum CHECKSUM("home_before_probe") |
dea3c8c0 QH |
115 | #define center_zero_checksum CHECKSUM("center_zero") |
116 | #define circular_bed_checksum CHECKSUM("circular_bed") | |
117 | #define cal_offset_x_checksum CHECKSUM("cal_offset_x") | |
118 | #define cal_offset_y_checksum CHECKSUM("cal_offset_y") | |
119 | ||
962b3c72 QH |
120 | #define NOHOME 0 |
121 | #define HOMEXY 1 | |
122 | #define HOMEXYZ 2 | |
77fbe368 QH |
123 | |
124 | #define cols_checksum CHECKSUM("cols") | |
125 | #define rows_checksum CHECKSUM("rows") | |
126 | ||
127 | #define probe_points (this->numRows * this->numCols) | |
128 | ||
129 | ||
130 | ||
131 | ZGridStrategy::ZGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe) | |
132 | { | |
133 | this->cal[X_AXIS] = 0.0f; | |
134 | this->cal[Y_AXIS] = 0.0f; | |
135 | this->cal[Z_AXIS] = 30.0f; | |
136 | ||
137 | this->in_cal = false; | |
6100791e | 138 | this->pData = nullptr; |
77fbe368 QH |
139 | } |
140 | ||
141 | ZGridStrategy::~ZGridStrategy() | |
142 | { | |
143 | // Free program memory for the pData grid | |
144 | if(this->pData != nullptr) AHB0.dealloc(this->pData); | |
145 | } | |
146 | ||
147 | bool ZGridStrategy::handleConfig() | |
148 | { | |
149 | this->bed_x = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, bed_x_checksum)->by_default(200.0F)->as_number(); | |
150 | this->bed_y = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, bed_y_checksum)->by_default(200.0F)->as_number(); | |
fc29aeae | 151 | this->bed_z = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, bed_z_checksum)->by_default(20.0F)->as_number(); |
77fbe368 QH |
152 | |
153 | this->slow_rate = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, slow_feedrate_checksum)->by_default(20.0F)->as_number(); | |
154 | ||
155 | this->numRows = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, rows_checksum)->by_default(5)->as_number(); | |
156 | this->numCols = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, cols_checksum)->by_default(5)->as_number(); | |
157 | ||
36256be6 QH |
158 | this->wait_for_probe = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, wait_for_probe_checksum)->by_default(true)->as_bool(); // Morgan default = true |
159 | ||
160 | std::string home_mode = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, home_before_probe_checksum)->by_default("homexyz")->as_string(); | |
161 | if (home_mode.compare("nohome") == 0) { | |
162 | this->home_before_probe = NOHOME; | |
163 | } | |
164 | else if (home_mode.compare("homexy") == 0) { | |
165 | this->home_before_probe = HOMEXY; | |
166 | } | |
167 | else { // Default | |
168 | this->home_before_probe = HOMEXYZ; | |
169 | } | |
170 | ||
171 | ||
172 | //this->home_before_probe = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, home_before_probe_checksum)->by_default(HOMEXYZ)->as_number(); // Morgan default = HOMEXYZ | |
c880f6dd | 173 | |
dea3c8c0 QH |
174 | this->center_zero = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, center_zero_checksum)->by_default(false)->as_bool(); |
175 | this->circular_bed = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, circular_bed_checksum)->by_default(false)->as_bool(); | |
176 | ||
177 | // configures calbration positioning offset. Defaults to 0 for standard cartesian space machines, and to negative half of the current bed size in X and Y | |
178 | this->cal_offset_x = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, cal_offset_x_checksum)->by_default( this->center_zero ? this->bed_x / -2.0F : 0.0F )->as_number(); | |
179 | this->cal_offset_y = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, cal_offset_y_checksum)->by_default( this->center_zero ? this->bed_y / -2.0F : 0.0F )->as_number(); | |
180 | ||
c880f6dd | 181 | |
77fbe368 QH |
182 | // Probe offsets xxx,yyy,zzz |
183 | std::string po = THEKERNEL->config->value(leveling_strategy_checksum, ZGrid_leveling_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string(); | |
184 | this->probe_offsets= parseXYZ(po.c_str()); | |
185 | ||
d223207b | 186 | this->calcConfig(); // Run calculations for Grid size and allocate initial grid memory |
77fbe368 | 187 | |
d8a0c34b QH |
188 | for (int i=0; i<(probe_points); i++){ |
189 | this->pData[i] = 0.0F; // Clear the grid | |
77fbe368 QH |
190 | } |
191 | ||
192 | return true; | |
193 | } | |
194 | ||
195 | void ZGridStrategy::calcConfig() | |
196 | { | |
d223207b | 197 | this->bed_div_x = this->bed_x / float(this->numRows-1); // Find divisors to calculate the calbration points |
77fbe368 QH |
198 | this->bed_div_y = this->bed_y / float(this->numCols-1); |
199 | ||
200 | // Ensure free program memory for the pData grid | |
201 | if(this->pData != nullptr) AHB0.dealloc(this->pData); | |
202 | ||
203 | // Allocate program memory for the pData grid | |
204 | this->pData = (float *)AHB0.alloc(probe_points * sizeof(float)); | |
205 | } | |
206 | ||
207 | bool ZGridStrategy::handleGcode(Gcode *gcode) | |
208 | { | |
209 | string args = get_arguments(gcode->get_command()); | |
210 | ||
211 | // G code processing | |
212 | if(gcode->has_g) { | |
213 | if( gcode->g == 31 ) { // report status | |
214 | ||
215 | // Bed ZGrid data as gcode: | |
216 | gcode->stream->printf(";Bed Level settings:\r\n"); | |
217 | ||
218 | for (int x=0; x<this->numRows; x++){ | |
219 | gcode->stream->printf("X%i",x); | |
220 | for (int y=0; y<this->numCols; y++){ | |
221 | gcode->stream->printf(" %c%1.2f", 'A'+y, this->pData[(x*this->numCols)+y]); | |
222 | } | |
223 | gcode->stream->printf("\r\n"); | |
224 | } | |
225 | return true; | |
226 | ||
227 | } else if( gcode->g == 32 ) { //run probe | |
228 | // first wait for an empty queue i.e. no moves left | |
229 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
230 | ||
231 | this->setAdjustFunction(false); // Disable leveling code | |
232 | if(!doProbing(gcode->stream)) { | |
233 | gcode->stream->printf("Probe failed to complete, probe not triggered or other error\n"); | |
234 | } else { | |
235 | this->setAdjustFunction(true); // Enable leveling code | |
236 | gcode->stream->printf("Probe completed, bed grid defined\n"); | |
237 | } | |
238 | return true; | |
239 | } | |
240 | ||
241 | } else if(gcode->has_m) { | |
242 | switch( gcode->m ) { | |
243 | ||
244 | // manual bed ZGrid calbration: M370 - M375 | |
245 | // M370: Clear current ZGrid for calibration, and move to first position | |
246 | case 370: { | |
247 | this->setAdjustFunction(false); // Disable leveling code | |
47044040 QH |
248 | this->cal[Z_AXIS] = std::get<Z_AXIS>(this->probe_offsets) + zprobe->getProbeHeight(); |
249 | ||
77fbe368 QH |
250 | |
251 | if(gcode->has_letter('X')) // Rows (X) | |
252 | this->numRows = gcode->get_value('X'); | |
253 | if(gcode->has_letter('Y')) // Cols (Y) | |
254 | this->numCols = gcode->get_value('Y'); | |
255 | ||
256 | this->calcConfig(); // Run calculations for Grid size and allocate grid memory | |
257 | ||
258 | this->homexyz(); | |
259 | for (int i=0; i<probe_points; i++){ | |
260 | this->pData[i] = 0.0F; // Clear the ZGrid | |
261 | } | |
262 | ||
263 | this->cal[X_AXIS] = 0.0f; // Clear calibration position | |
264 | this->cal[Y_AXIS] = 0.0f; | |
265 | this->in_cal = true; // In calbration mode | |
266 | ||
267 | } | |
268 | return true; | |
269 | // M371: Move to next manual calibration position | |
270 | case 371: { | |
271 | if (in_cal){ | |
272 | this->move(this->cal, slow_rate); | |
273 | this->next_cal(); | |
274 | } | |
275 | ||
276 | } | |
277 | return true; | |
278 | // M372: save current position in ZGrid, and move to next calibration position | |
279 | case 372: { | |
280 | if (in_cal){ | |
281 | float cartesian[3]; | |
282 | int pindex = 0; | |
283 | ||
284 | THEKERNEL->robot->get_axis_position(cartesian); // get actual position from robot | |
285 | ||
286 | pindex = int(cartesian[X_AXIS]/this->bed_div_x + 0.25)*this->numCols + int(cartesian[Y_AXIS]/this->bed_div_y + 0.25); | |
287 | ||
288 | this->move(this->cal, slow_rate); // move to the next position | |
289 | this->next_cal(); // to not cause damage to machine due to Z-offset | |
290 | ||
291 | this->pData[pindex] = cartesian[Z_AXIS]; // save the offset | |
292 | ||
293 | } | |
294 | } | |
295 | return true; | |
296 | // M373: finalize calibration | |
297 | case 373: { | |
d223207b QH |
298 | // normalize the grid |
299 | this->normalize_grid(); | |
300 | ||
77fbe368 QH |
301 | this->in_cal = false; |
302 | this->setAdjustFunction(true); // Enable leveling code | |
303 | ||
304 | } | |
305 | return true; | |
306 | ||
307 | // M374: Save grid | |
308 | case 374:{ | |
d1069930 | 309 | char gridname[5]; |
be587e68 | 310 | |
d1069930 | 311 | if(gcode->has_letter('S')) // Custom grid number |
56d27ea6 | 312 | snprintf(gridname, sizeof(gridname), "S%03.0f", gcode->get_value('S')); |
d1069930 QH |
313 | else |
314 | gridname[0] = '\0'; | |
be587e68 QH |
315 | |
316 | if(this->saveGrid(gridname)) { | |
d1069930 QH |
317 | gcode->stream->printf("Grid saved: Filename: /sd/Zgrid.%s\n",gridname); |
318 | } | |
319 | else { | |
320 | gcode->stream->printf("Error: Grid not saved: Filename: /sd/Zgrid.%s\n",gridname); | |
77fbe368 QH |
321 | } |
322 | } | |
323 | return true; | |
324 | ||
be587e68 QH |
325 | case 375:{ // Load grid values |
326 | char gridname[5]; | |
327 | ||
328 | if(gcode->has_letter('S')) // Custom grid number | |
56d27ea6 | 329 | snprintf(gridname, sizeof(gridname), "S%03.0f", gcode->get_value('S')); |
d1069930 QH |
330 | else |
331 | gridname[0] = '\0'; | |
be587e68 | 332 | |
d1069930 | 333 | if(this->loadGrid(gridname)) { |
77fbe368 | 334 | this->setAdjustFunction(true); // Enable leveling code |
d1069930 QH |
335 | gcode->stream->printf("Grid loaded: /sd/Zgrid.%s\n",gridname); |
336 | } | |
337 | else { | |
338 | gcode->stream->printf("Error: Grid not loaded: /sd/Zgrid.%s\n",gridname); | |
77fbe368 | 339 | } |
be587e68 | 340 | } |
77fbe368 QH |
341 | return true; |
342 | ||
d223207b | 343 | /* case 376: { // Check grid value calculations: For debug only. |
77fbe368 QH |
344 | float target[3]; |
345 | ||
346 | for(char letter = 'X'; letter <= 'Z'; letter++) { | |
347 | if( gcode->has_letter(letter) ) { | |
348 | target[letter - 'X'] = gcode->get_value(letter); | |
349 | } | |
350 | } | |
351 | gcode->stream->printf(" Z0 %1.3f\n",getZOffset(target[0], target[1])); | |
352 | ||
353 | } | |
354 | return true; | |
355 | */ | |
356 | case 565: { // M565: Set Z probe offsets | |
357 | float x= 0, y= 0, z= 0; | |
358 | if(gcode->has_letter('X')) x = gcode->get_value('X'); | |
359 | if(gcode->has_letter('Y')) y = gcode->get_value('Y'); | |
360 | if(gcode->has_letter('Z')) z = gcode->get_value('Z'); | |
361 | probe_offsets = std::make_tuple(x, y, z); | |
362 | } | |
363 | return true; | |
364 | ||
d223207b | 365 | case 500: // M500 saves probe_offsets config override file |
d1069930 | 366 | gcode->stream->printf(";Load default grid\nM375\n"); |
e87e5be3 | 367 | |
77fbe368 QH |
368 | |
369 | case 503: { // M503 just prints the settings | |
370 | ||
371 | float x,y,z; | |
372 | gcode->stream->printf(";Probe offsets:\n"); | |
373 | std::tie(x, y, z) = probe_offsets; | |
374 | gcode->stream->printf("M565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z); | |
77fbe368 QH |
375 | break; |
376 | } | |
377 | ||
378 | return true; | |
379 | } | |
380 | } | |
381 | ||
382 | return false; | |
383 | } | |
384 | ||
d223207b | 385 | |
e87e5be3 | 386 | bool ZGridStrategy::saveGrid(std::string args) |
77fbe368 | 387 | { |
e87e5be3 QH |
388 | args = "/sd/Zgrid." + args; |
389 | StreamOutput *ZMap_file = new FileStream(args.c_str()); | |
77fbe368 | 390 | |
d223207b | 391 | ZMap_file->printf("P%i %i %i %1.3f\n", probe_points, this->numRows, this->numCols, getZhomeoffset()); // Store probe points to prevent loading undefined grid files |
77fbe368 QH |
392 | |
393 | for (int pos = 0; pos < probe_points; pos++){ | |
394 | ZMap_file->printf("%1.3f\n", this->pData[pos]); | |
395 | } | |
396 | delete ZMap_file; | |
397 | ||
398 | return true; | |
399 | ||
400 | } | |
401 | ||
e87e5be3 | 402 | bool ZGridStrategy::loadGrid(std::string args) |
77fbe368 QH |
403 | { |
404 | char flag[20]; | |
405 | ||
406 | int fpoints, GridX = 5, GridY = 5; // for 25point file | |
407 | float val, GridZ; | |
408 | ||
e87e5be3 QH |
409 | args = "/sd/Zgrid." + args; |
410 | FILE *fd = fopen(args.c_str(), "r"); | |
77fbe368 QH |
411 | if(fd != NULL) { |
412 | fscanf(fd, "%s\n", flag); | |
413 | ||
77fbe368 | 414 | if (flag[0] == 'P'){ |
77fbe368 QH |
415 | |
416 | sscanf(flag, "P%i\n", &fpoints); // read number of points, and Grid X and Y | |
d223207b | 417 | fscanf(fd, "%i %i %f\n", &GridX, &GridY, &GridZ); // read number of points, and Grid X and Y and ZHoming offset |
77fbe368 QH |
418 | fscanf(fd, "%f\n", &val); // read first value from file |
419 | ||
420 | } else { // original 25point file -- Backwards compatibility | |
421 | fpoints = 25; | |
422 | sscanf(flag, "%f\n", &val); // read first value from string | |
423 | } | |
424 | ||
425 | if (GridX != this->numRows || GridY != this->numCols){ | |
426 | this->numRows = GridX; // Change Rows and Columns to match the saved data | |
427 | this->numCols = GridY; | |
428 | this->calcConfig(); // Reallocate memory for the grid according to the grid loaded | |
429 | } | |
430 | ||
431 | this->pData[0] = val; // Place the first read value in grid | |
432 | ||
433 | for (int pos = 1; pos < probe_points; pos++){ | |
434 | fscanf(fd, "%f\n", &val); | |
435 | this->pData[pos] = val; | |
436 | } | |
437 | ||
438 | fclose(fd); | |
439 | ||
440 | this->setZoffset(GridZ); | |
441 | ||
442 | return true; | |
443 | ||
444 | } else { | |
445 | return false; | |
446 | } | |
447 | ||
448 | } | |
449 | ||
d223207b QH |
450 | float ZGridStrategy::getZhomeoffset() |
451 | { | |
452 | void* rd; | |
453 | ||
4d7c32b3 | 454 | bool ok = PublicData::get_value( endstops_checksum, home_offset_checksum, &rd ); |
d223207b | 455 | |
4d7c32b3 RM |
456 | if (ok) { |
457 | return ((float*)rd)[2]; | |
458 | } | |
459 | ||
460 | return 0; | |
d223207b QH |
461 | } |
462 | ||
77fbe368 QH |
463 | void ZGridStrategy::setZoffset(float zval) |
464 | { | |
465 | char cmd[64]; | |
466 | ||
467 | // Assemble Gcode to add onto the queue | |
468 | snprintf(cmd, sizeof(cmd), "M206 Z%1.3f", zval); // Send saved Z homing offset | |
469 | ||
470 | Gcode gc(cmd, &(StreamOutput::NullStream)); | |
471 | THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc); | |
472 | ||
473 | } | |
474 | ||
475 | bool ZGridStrategy::doProbing(StreamOutput *stream) // probed calibration | |
476 | { | |
962b3c72 | 477 | // home first using selected mode: NOHOME, HOMEXY, HOMEXYZ |
c449d578 | 478 | this->homexyz(); |
77fbe368 QH |
479 | |
480 | // deactivate correction during moves | |
481 | this->setAdjustFunction(false); | |
482 | ||
483 | for (int i=0; i<probe_points; i++){ | |
484 | this->pData[i] = 0.0F; // Clear the ZGrid | |
485 | } | |
486 | ||
c880f6dd | 487 | if (this->wait_for_probe){ |
77fbe368 | 488 | |
c880f6dd QH |
489 | this->cal[X_AXIS] = this->bed_x/2.0f; |
490 | this->cal[Y_AXIS] = this->bed_y/2.0f; | |
491 | this->cal[Z_AXIS] = this->bed_z/2.0f; // Position head for probe attachment | |
492 | this->move(this->cal, slow_rate); // Move to probe attachment point | |
77fbe368 | 493 | |
c880f6dd | 494 | stream->printf("*** Ensure probe is attached and press probe when done ***\n"); |
77fbe368 | 495 | |
c880f6dd QH |
496 | while(!zprobe->getProbeStatus()){ // Wait for button press |
497 | THEKERNEL->call_event(ON_IDLE); | |
498 | }; | |
499 | } | |
77fbe368 | 500 | |
e76710f3 | 501 | this->in_cal = true; // In calbration mode |
77fbe368 | 502 | |
e76710f3 RM |
503 | this->cal[X_AXIS] = 0.0f; // Clear calibration position |
504 | this->cal[Y_AXIS] = 0.0f; | |
505 | this->cal[Z_AXIS] = std::get<Z_AXIS>(this->probe_offsets) + zprobe->getProbeHeight(); | |
77fbe368 | 506 | |
e76710f3 | 507 | this->move(this->cal, slow_rate); // Move to probe start point |
d223207b | 508 | |
e76710f3 RM |
509 | for (int probes = 0; probes < probe_points; probes++){ |
510 | int pindex = 0; | |
77fbe368 | 511 | |
47044040 QH |
512 | // z = z home offset - probed distance |
513 | float z = getZhomeoffset() -zprobe->probeDistance((this->cal[X_AXIS] + this->cal_offset_x)-std::get<X_AXIS>(this->probe_offsets), | |
dea3c8c0 | 514 | (this->cal[Y_AXIS] + this->cal_offset_y)-std::get<Y_AXIS>(this->probe_offsets)); |
d223207b | 515 | |
e76710f3 | 516 | pindex = int(this->cal[X_AXIS]/this->bed_div_x + 0.25)*this->numCols + int(this->cal[Y_AXIS]/this->bed_div_y + 0.25); |
77fbe368 | 517 | |
dcd82318 QH |
518 | this->next_cal(); // Calculate next calibration position |
519 | ||
520 | this->pData[pindex] = z ; // save the offset | |
e76710f3 RM |
521 | } |
522 | ||
dcd82318 QH |
523 | stream->printf("\nCalibration done.\n"); |
524 | if (this->wait_for_probe) { // Only do this it the config calls for probe removal position | |
525 | this->cal[X_AXIS] = this->bed_x/2.0f; | |
526 | this->cal[Y_AXIS] = this->bed_y/2.0f; | |
527 | this->cal[Z_AXIS] = this->bed_z/2.0f; // Position head for probe removal | |
528 | this->move(this->cal, slow_rate); | |
529 | ||
530 | stream->printf("Please remove probe\n"); | |
531 | ||
532 | } | |
77fbe368 | 533 | |
e76710f3 RM |
534 | // activate correction |
535 | this->normalize_grid(); | |
536 | this->setAdjustFunction(true); | |
77fbe368 | 537 | |
e76710f3 | 538 | this->in_cal = false; |
77fbe368 | 539 | |
77fbe368 QH |
540 | return true; |
541 | } | |
542 | ||
543 | ||
d223207b QH |
544 | void ZGridStrategy::normalize_grid() |
545 | { | |
546 | float min = 100.0F, // set large start value | |
547 | norm_offset = 0; | |
548 | ||
549 | // find minimum value in offset grid | |
550 | for (int i = 0; i < probe_points; i++) | |
551 | if (this->pData[i] < min) | |
552 | min = this->pData[i]; | |
553 | ||
554 | // creates addition offset to set minimum value to zero. | |
555 | norm_offset = -min; | |
556 | ||
557 | // adds the offset to create a table of deltas, normalzed to minimum zero | |
558 | for (int i = 0; i < probe_points; i++) | |
559 | this->pData[i] += norm_offset; | |
560 | ||
d8a0c34b QH |
561 | // add the offset to the current Z homing offset to preserve full probed offset. |
562 | this->setZoffset(getZhomeoffset() + norm_offset); | |
d223207b QH |
563 | } |
564 | ||
77fbe368 QH |
565 | void ZGridStrategy::homexyz() |
566 | { | |
962b3c72 QH |
567 | |
568 | switch(this->home_before_probe) { | |
569 | case NOHOME : return; | |
570 | ||
571 | case HOMEXY : { | |
4bbbd847 | 572 | Gcode gc("G28 X0 Y0", &(StreamOutput::NullStream)); |
c449d578 QH |
573 | THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc); |
574 | break; | |
962b3c72 QH |
575 | } |
576 | ||
577 | case HOMEXYZ : { | |
c449d578 QH |
578 | Gcode gc("G28", &(StreamOutput::NullStream)); |
579 | THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc); | |
580 | break; | |
962b3c72 QH |
581 | } |
582 | } | |
77fbe368 QH |
583 | } |
584 | ||
585 | void ZGridStrategy::move(float *position, float feed) | |
586 | { | |
e0be983d JM |
587 | // translate the position for non standard cartesian spaces (cal_offset) |
588 | zprobe->coordinated_move(position[0] + this->cal_offset_x, position[1] + this->cal_offset_y, position[2], feed); // use specified feedrate (mm/sec) | |
77fbe368 | 589 | |
47044040 | 590 | //THEKERNEL->streams->printf("DEBUG: move: %s cent: %i\n", cmd, this->center_zero); |
77fbe368 QH |
591 | } |
592 | ||
593 | ||
594 | void ZGridStrategy::next_cal(void){ | |
561af4e3 | 595 | if ((((int) roundf(this->cal[X_AXIS] / this->bed_div_x)) & 1) != 0){ // Odd row |
dea3c8c0 | 596 | this->cal[Y_AXIS] -= this->bed_div_y; |
561af4e3 | 597 | if (this->cal[Y_AXIS] < (0.0F - (bed_div_y / 2.0f))){ |
dea3c8c0 | 598 | |
47044040 | 599 | //THEKERNEL->streams->printf("DEBUG: Y (%f) < cond (%f)\n",this->cal[Y_AXIS], 0.0F); |
dea3c8c0 | 600 | |
77fbe368 | 601 | this->cal[X_AXIS] += bed_div_x; |
561af4e3 | 602 | if (this->cal[X_AXIS] > (this->bed_x + (this->bed_div_x / 2.0f))){ |
dea3c8c0 QH |
603 | this->cal[X_AXIS] = 0.0F; |
604 | this->cal[Y_AXIS] = 0.0F; | |
77fbe368 QH |
605 | } |
606 | else | |
dea3c8c0 | 607 | this->cal[Y_AXIS] = 0.0F; |
77fbe368 QH |
608 | } |
609 | } | |
dea3c8c0 | 610 | else { // Even row (0 is an even row - starting point) |
77fbe368 | 611 | this->cal[Y_AXIS] += bed_div_y; |
561af4e3 | 612 | if (this->cal[Y_AXIS] > (this->bed_y + (bed_div_y / 2.0f))){ |
dea3c8c0 | 613 | |
47044040 | 614 | //THEKERNEL->streams->printf("DEBUG: Y (%f) > cond (%f)\n",this->cal[Y_AXIS], this->bed_y); |
dea3c8c0 | 615 | |
77fbe368 | 616 | this->cal[X_AXIS] += bed_div_x; |
561af4e3 | 617 | if (this->cal[X_AXIS] > (this->bed_x + (this->bed_div_x / 2.0f))){ |
dea3c8c0 QH |
618 | this->cal[X_AXIS] = 0.0F; |
619 | this->cal[Y_AXIS] = 0.0F; | |
77fbe368 QH |
620 | } |
621 | else | |
dea3c8c0 | 622 | this->cal[Y_AXIS] = this->bed_y; |
77fbe368 QH |
623 | } |
624 | } | |
625 | } | |
626 | ||
627 | ||
628 | void ZGridStrategy::setAdjustFunction(bool on) | |
629 | { | |
630 | if(on) { | |
631 | // set the compensationTransform in robot | |
632 | THEKERNEL->robot->compensationTransform= [this](float target[3]) { target[2] += this->getZOffset(target[0], target[1]); }; | |
633 | }else{ | |
634 | // clear it | |
635 | THEKERNEL->robot->compensationTransform= nullptr; | |
636 | } | |
637 | } | |
638 | ||
639 | ||
640 | // find the Z offset for the point on the plane at x, y | |
641 | float ZGridStrategy::getZOffset(float X, float Y) | |
642 | { | |
643 | int xIndex2, yIndex2; | |
644 | ||
dea3c8c0 QH |
645 | // Subtract calibration offsets as applicable |
646 | X -= this->cal_offset_x; | |
647 | Y -= this->cal_offset_y; | |
648 | ||
77fbe368 QH |
649 | float xdiff = X / this->bed_div_x; |
650 | float ydiff = Y / this->bed_div_y; | |
651 | ||
652 | float dCartX1, dCartX2; | |
653 | ||
a5f0fa18 TE |
654 | // Get floor of xdiff. Note that (int) of a negative number is its |
655 | // ceiling, not its floor. | |
77fbe368 | 656 | |
a5f0fa18 TE |
657 | int xIndex = (int)(floorf(xdiff)); // Get the current sector (X) |
658 | int yIndex = (int)(floorf(ydiff)); // Get the current sector (Y) | |
dcd82318 | 659 | |
a5f0fa18 TE |
660 | // Index bounds limited to be inside the table |
661 | if (xIndex < 0) xIndex = 0; | |
662 | else if (xIndex > (this->numRows - 2)) xIndex = this->numRows - 2; | |
663 | ||
664 | if (yIndex < 0) yIndex = 0; | |
665 | else if (yIndex > (this->numCols - 2)) yIndex = this->numCols - 2; | |
666 | ||
a5f0fa18 | 667 | xIndex2 = xIndex+1; |
a5f0fa18 | 668 | yIndex2 = yIndex+1; |
77fbe368 QH |
669 | |
670 | xdiff -= xIndex; // Find floating point | |
671 | ydiff -= yIndex; // Find floating point | |
672 | ||
673 | dCartX1 = (1-xdiff) * this->pData[(xIndex*this->numCols)+yIndex] + (xdiff) * this->pData[(xIndex2)*this->numCols+yIndex]; | |
674 | dCartX2 = (1-xdiff) * this->pData[(xIndex*this->numCols)+yIndex2] + (xdiff) * this->pData[(xIndex2)*this->numCols+yIndex2]; | |
675 | ||
676 | return ydiff * dCartX2 + (1-ydiff) * dCartX1; // Calculated Z-delta | |
677 | ||
678 | } | |
679 | ||
680 | // parse a "X,Y,Z" string return x,y,z tuple | |
681 | std::tuple<float, float, float> ZGridStrategy::parseXYZ(const char *str) | |
682 | { | |
683 | float x = 0, y = 0, z= 0; | |
684 | char *p; | |
685 | x = strtof(str, &p); | |
686 | if(p + 1 < str + strlen(str)) { | |
687 | y = strtof(p + 1, &p); | |
688 | if(p + 1 < str + strlen(str)) { | |
689 | z = strtof(p + 1, nullptr); | |
690 | } | |
691 | } | |
692 | return std::make_tuple(x, y, z); | |
693 | } | |
694 |