refactor zprobe probe return to be a method and returns to saved last_milestone so...
[clinton/Smoothieware.git] / src / modules / tools / zprobe / ThreePointStrategy.cpp
CommitLineData
d60189a9
JM
1/*
2 Author: Jim Morris (wolfmanjm@gmail.com)
3 License: GPL3 or better see <http://www.gnu.org/licenses/>
4
5 Summary
6 -------
7 Probes three user specified points on the bed and determines the plane of the bed relative to the probe.
8 as the head moves in X and Y it will adjust Z to keep the head tram with the bed.
9
10 Configuration
11 -------------
12 The strategy must be enabled in the cofnig as well as zprobe.
13
14 leveling-strategy.three-point-leveling.enable true
15
16 Three probe points must be defined, these are best if they are the three points of an equilateral triangle, as far apart as possible.
17 They can be defined in the config file as:-
18
19 leveling-strategy.three-point-leveling.point1 100.0,0.0 # the first probe point (x,y)
20 leveling-strategy.three-point-leveling.point2 200.0,200.0 # the second probe point (x,y)
21 leveling-strategy.three-point-leveling.point3 0.0,200.0 # the third probe point (x,y)
22
23 or they may be defined (and saved with M500) using M557 P0 X30 Y40.5 where P is 0,1,2
24
25 probe offsets from the nozzle or tool head can be defined with
26
27 leveling-strategy.three-point-leveling.probe_offsets 0,0,0 # probe offsetrs x,y,z
28
29 they may also be set with M565 X0 Y0 Z0
30
31 To force homing in X and Y before G32 does the probe the following can be set in config, this is the default
32
33 leveling-strategy.three-point-leveling.home_first true # disable by setting to false
34
35 The probe tolerance can be set using the config line
36
37 leveling-strategy.three-point-leveling.tolerance 0.03 # the probe tolerance in mm, default is 0.03mm
38
39
40 Usage
41 -----
c90d92cb 42 G29 probes the three probe points and reports the Z at each point, if a plane is active it will be used to level the probe.
d60189a9
JM
43 G32 probes the three probe points and defines the bed plane, this will remain in effect until reset or M561
44 G31 reports the status
45
46 M557 defines the probe points
47 M561 clears the plane and the bed leveling is disabled until G32 is run again
48 M565 defines the probe offsets from the nozzle or tool head
49
50 M500 saves the probe points and the probe offsets
51 M503 displays the current settings
52*/
53
97832d6d
JM
54#include "ThreePointStrategy.h"
55#include "Kernel.h"
56#include "Config.h"
57#include "Robot.h"
58#include "StreamOutputPool.h"
59#include "Gcode.h"
60#include "checksumm.h"
61#include "ConfigValue.h"
62#include "PublicDataRequest.h"
63#include "PublicData.h"
64#include "Conveyor.h"
65#include "ZProbe.h"
a5542cae 66#include "Plane3D.h"
cef9acea 67#include "nuts_bolts.h"
97832d6d
JM
68
69#include <string>
70#include <algorithm>
0e44e7d7 71#include <cstdlib>
a5542cae 72#include <cmath>
97832d6d
JM
73
74#define probe_point_1_checksum CHECKSUM("point1")
75#define probe_point_2_checksum CHECKSUM("point2")
76#define probe_point_3_checksum CHECKSUM("point3")
cef9acea 77#define probe_offsets_checksum CHECKSUM("probe_offsets")
5fdf2c47
JM
78#define home_checksum CHECKSUM("home_first")
79#define tolerance_checksum CHECKSUM("tolerance")
230079d4 80#define save_plane_checksum CHECKSUM("save_plane")
97832d6d 81
a5542cae
JM
82ThreePointStrategy::ThreePointStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe)
83{
84 for (int i = 0; i < 3; ++i) {
85 probe_points[i] = std::make_tuple(NAN, NAN);
86 }
87 plane = nullptr;
88}
89
90ThreePointStrategy::~ThreePointStrategy()
91{
92 delete plane;
93}
97832d6d
JM
94
95bool ThreePointStrategy::handleConfig()
96{
0e44e7d7 97 // format is xxx,yyy for the probe points
a5542cae
JM
98 std::string p1 = THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, probe_point_1_checksum)->by_default("")->as_string();
99 std::string p2 = THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, probe_point_2_checksum)->by_default("")->as_string();
100 std::string p3 = THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, probe_point_3_checksum)->by_default("")->as_string();
101 if(!p1.empty()) probe_points[0] = parseXY(p1.c_str());
102 if(!p2.empty()) probe_points[1] = parseXY(p2.c_str());
103 if(!p3.empty()) probe_points[2] = parseXY(p3.c_str());
5fdf2c47 104
cef9acea
JM
105 // Probe offsets xxx,yyy,zzz
106 std::string po = THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string();
107 this->probe_offsets= parseXYZ(po.c_str());
108
5fdf2c47
JM
109 this->home= THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, home_checksum)->by_default(true)->as_bool();
110 this->tolerance= THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, tolerance_checksum)->by_default(0.03F)->as_number();
230079d4 111 this->save= THEKERNEL->config->value(leveling_strategy_checksum, three_point_leveling_strategy_checksum, save_plane_checksum)->by_default(false)->as_bool();
97832d6d
JM
112 return true;
113}
114
115bool ThreePointStrategy::handleGcode(Gcode *gcode)
116{
2f809c40 117 if(gcode->has_g) {
97832d6d 118 // G code processing
d5f17053
JM
119 if(gcode->g == 29) { // test probe points for level
120 if(!test_probe_points(gcode)) {
121 gcode->stream->printf("Probe failed to complete, probe not triggered or other error\n");
122 }
123 return true;
124
125 } else if( gcode->g == 31 ) { // report status
2f809c40
JM
126 if(this->plane == nullptr) {
127 gcode->stream->printf("Bed leveling plane is not set\n");
128 }else{
129 gcode->stream->printf("Bed leveling plane normal= %f, %f, %f\n", plane->getNormal()[0], plane->getNormal()[1], plane->getNormal()[2]);
130 }
131 gcode->stream->printf("Probe is %s\n", zprobe->getProbeStatus() ? "Triggered" : "Not triggered");
132 return true;
133
134 } else if( gcode->g == 32 ) { // three point probe
97832d6d 135 // first wait for an empty queue i.e. no moves left
04782655 136 THEKERNEL->conveyor->wait_for_idle();
e0be983d
JM
137
138 // clear any existing plane and compensation
139 delete this->plane;
140 this->plane= nullptr;
141 setAdjustFunction(false);
142
0e44e7d7 143 if(!doProbing(gcode->stream)) {
25dc6344 144 gcode->stream->printf("Probe failed to complete, probe not triggered or other error\n");
a5542cae 145 } else {
0e44e7d7
JM
146 gcode->stream->printf("Probe completed, bed plane defined\n");
147 }
97832d6d
JM
148 return true;
149 }
150
151 } else if(gcode->has_m) {
0e44e7d7 152 if(gcode->m == 557) { // M557 - set probe points eg M557 P0 X30 Y40.5 where P is 0,1,2
a5542cae
JM
153 int idx = 0;
154 float x = NAN, y = NAN;
155 if(gcode->has_letter('P')) idx = gcode->get_value('P');
156 if(gcode->has_letter('X')) x = gcode->get_value('X');
157 if(gcode->has_letter('Y')) y = gcode->get_value('Y');
0e44e7d7 158 if(idx >= 0 && idx <= 2) {
a5542cae 159 probe_points[idx] = std::make_tuple(x, y);
25dc6344
JM
160 }else{
161 gcode->stream->printf("only 3 probe points allowed P0-P2\n");
0e44e7d7
JM
162 }
163 return true;
164
230079d4 165 } else if(gcode->m == 561) { // M561: Set Identity Transform with no parameters, set the saved plane if A B C D are given
2f809c40 166 delete this->plane;
230079d4
JM
167 if(gcode->get_num_args() == 0) {
168 this->plane= nullptr;
3632a517 169 // delete the compensationTransform in robot
230079d4 170 setAdjustFunction(false);
6c3ab9b0 171 gcode->stream->printf("saved plane cleared\n");
230079d4 172 }else{
6c3ab9b0 173 // smoothie specific way to restore a saved plane
230079d4
JM
174 uint32_t a,b,c,d;
175 a=b=c=d= 0;
176 if(gcode->has_letter('A')) a = gcode->get_uint('A');
177 if(gcode->has_letter('B')) b = gcode->get_uint('B');
178 if(gcode->has_letter('C')) c = gcode->get_uint('C');
179 if(gcode->has_letter('D')) d = gcode->get_uint('D');
180 this->plane= new Plane3D(a, b, c, d);
a7fbf20c 181 setAdjustFunction(true);
230079d4 182 }
2f809c40
JM
183 return true;
184
cef9acea
JM
185 } else if(gcode->m == 565) { // M565: Set Z probe offsets
186 float x= 0, y= 0, z= 0;
187 if(gcode->has_letter('X')) x = gcode->get_value('X');
188 if(gcode->has_letter('Y')) y = gcode->get_value('Y');
189 if(gcode->has_letter('Z')) z = gcode->get_value('Z');
190 probe_offsets = std::make_tuple(x, y, z);
191 return true;
192
230079d4 193 } else if(gcode->m == 500 || gcode->m == 503) { // M500 save, M503 display
cef9acea 194 float x, y, z;
0e44e7d7
JM
195 gcode->stream->printf(";Probe points:\n");
196 for (int i = 0; i < 3; ++i) {
0e44e7d7
JM
197 std::tie(x, y) = probe_points[i];
198 gcode->stream->printf("M557 P%d X%1.5f Y%1.5f\n", i, x, y);
199 }
cef9acea
JM
200 gcode->stream->printf(";Probe offsets:\n");
201 std::tie(x, y, z) = probe_offsets;
202 gcode->stream->printf("M565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z);
203
230079d4 204 // encode plane and save if set and M500 and enabled
eaade38b
JM
205 if(this->save && this->plane != nullptr) {
206 if(gcode->m == 500) {
207 uint32_t a, b, c, d;
208 this->plane->encode(a, b, c, d);
209 gcode->stream->printf(";Saved bed plane:\nM561 A%lu B%lu C%lu D%lu \n", a, b, c, d);
210 }else{
211 gcode->stream->printf(";The bed plane will be saved on M500\n");
212 }
230079d4 213 }
0e44e7d7 214 return true;
5fdf2c47 215
d60189a9
JM
216 }
217 #if 0
728477c4
JM
218 else if(gcode->m == 9999) {
219 // DEBUG run a test M9999 A B C X Y set Z to A B C and test for point at X Y
5fdf2c47
JM
220 Vector3 v[3];
221 float x, y, z, a= 0, b= 0, c= 0;
222 if(gcode->has_letter('A')) a = gcode->get_value('A');
223 if(gcode->has_letter('B')) b = gcode->get_value('B');
224 if(gcode->has_letter('C')) c = gcode->get_value('C');
225 std::tie(x, y) = probe_points[0]; v[0].set(x, y, a);
226 std::tie(x, y) = probe_points[1]; v[1].set(x, y, b);
227 std::tie(x, y) = probe_points[2]; v[2].set(x, y, c);
228 delete this->plane;
229 this->plane = new Plane3D(v[0], v[1], v[2]);
25dc6344 230 gcode->stream->printf("plane normal= %f, %f, %f\n", plane->getNormal()[0], plane->getNormal()[1], plane->getNormal()[2]);
5fdf2c47
JM
231 x= 0; y=0;
232 if(gcode->has_letter('X')) x = gcode->get_value('X');
233 if(gcode->has_letter('Y')) y = gcode->get_value('Y');
234 z= getZOffset(x, y);
235 gcode->stream->printf("z= %f\n", z);
5e45206a 236 // tell robot to adjust z on each move
14568182 237 setAdjustFunction(true);
5fdf2c47 238 return true;
0e44e7d7 239 }
d60189a9 240 #endif
97832d6d
JM
241 }
242
243 return false;
244}
245
ff7e9858
JM
246void ThreePointStrategy::homeXY()
247{
248 Gcode gc("G28 X0 Y0", &(StreamOutput::NullStream));
249 THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc);
250}
251
0e44e7d7
JM
252bool ThreePointStrategy::doProbing(StreamOutput *stream)
253{
254 float x, y;
255 // check the probe points have been defined
256 for (int i = 0; i < 3; ++i) {
257 std::tie(x, y) = probe_points[i];
258 if(isnan(x) || isnan(y)) {
a5542cae
JM
259 stream->printf("Probe point P%d has not been defined, use M557 P%d Xnnn Ynnn to define it\n", i, i);
260 return false;
0e44e7d7
JM
261 }
262 }
263
5fdf2c47
JM
264 // optionally home XY axis first, but allow for manual homing
265 if(this->home)
266 homeXY();
ff7e9858
JM
267
268 // move to the first probe point
269 std::tie(x, y) = probe_points[0];
cef9acea
JM
270 // offset by the probe XY offset
271 x -= std::get<X_AXIS>(this->probe_offsets);
272 y -= std::get<Y_AXIS>(this->probe_offsets);
ff7e9858 273 zprobe->coordinated_move(x, y, NAN, zprobe->getFastFeedrate());
0e44e7d7 274
ff7e9858 275 // for now we use probe to find bed and not the Z min endstop
cef9acea
JM
276 // the first probe point becomes Z == 0 effectively so if we home Z or manually set z after this, it needs to be at the first probe point
277
ff7e9858
JM
278 // TODO this needs to be configurable to use min z or probe
279
280 // find bed via probe
6d142b73 281 float mm;
71f0df19 282 if(!zprobe->run_probe(mm, zprobe->getSlowFeedrate())) return false;
ff7e9858 283
14568182 284 // TODO if using probe then we probably need to set Z to 0 at first probe point, but take into account probe offset from head
c8bac202 285 THEROBOT->reset_axis_position(std::get<Z_AXIS>(this->probe_offsets), Z_AXIS);
14568182 286
ff7e9858 287 // move up to specified probe start position
c58d32a8 288 zprobe->coordinated_move(NAN, NAN, zprobe->getProbeHeight(), zprobe->getSlowFeedrate()); // move to probe start position
0e44e7d7
JM
289
290 // probe the three points
291 Vector3 v[3];
292 for (int i = 0; i < 3; ++i) {
293 std::tie(x, y) = probe_points[i];
cef9acea
JM
294 // offset moves by the probe XY offset
295 float z = zprobe->probeDistance(x-std::get<X_AXIS>(this->probe_offsets), y-std::get<Y_AXIS>(this->probe_offsets));
0e44e7d7 296 if(isnan(z)) return false; // probe failed
cef9acea 297 z= zprobe->getProbeHeight() - z; // relative distance between the probe points, lower is negative z
0e44e7d7 298 stream->printf("DEBUG: P%d:%1.4f\n", i, z);
bfcf42fe 299 v[i] = Vector3(x, y, z);
0e44e7d7
JM
300 }
301
cef9acea 302 // if first point is not within tolerance report it, it should ideally be 0
8b261cdc
JM
303 if(fabsf(v[0][2]) > this->tolerance) {
304 stream->printf("WARNING: probe is not within tolerance: %f > %f\n", fabsf(v[0][2]), this->tolerance);
5fdf2c47 305 }
80605954 306
0e44e7d7
JM
307 // define the plane
308 delete this->plane;
5fdf2c47 309 // check tolerance level here default 0.03mm
6d142b73
JM
310 auto mmx = std::minmax({v[0][2], v[1][2], v[2][2]});
311 if((mmx.second - mmx.first) <= this->tolerance) {
ff7e9858
JM
312 this->plane= nullptr; // plane is flat no need to do anything
313 stream->printf("DEBUG: flat plane\n");
3632a517 314 // clear the compensationTransform in robot
14568182 315 setAdjustFunction(false);
33742399 316
ff7e9858
JM
317 }else{
318 this->plane = new Plane3D(v[0], v[1], v[2]);
319 stream->printf("DEBUG: plane normal= %f, %f, %f\n", plane->getNormal()[0], plane->getNormal()[1], plane->getNormal()[2]);
14568182 320 setAdjustFunction(true);
ff7e9858 321 }
0e44e7d7
JM
322
323 return true;
324}
325
d5f17053
JM
326// Probes the 3 points and reports heights
327bool ThreePointStrategy::test_probe_points(Gcode *gcode)
328{
329 // check the probe points have been defined
330 float max_delta= 0;
331 float last_z= NAN;
332 for (int i = 0; i < 3; ++i) {
333 float x, y;
334 std::tie(x, y) = probe_points[i];
335 if(isnan(x) || isnan(y)) {
336 gcode->stream->printf("Probe point P%d has not been defined, use M557 P%d Xnnn Ynnn to define it\n", i, i);
337 return false;
338 }
339
340 float z = zprobe->probeDistance(x-std::get<X_AXIS>(this->probe_offsets), y-std::get<Y_AXIS>(this->probe_offsets));
341 if(isnan(z)) return false; // probe failed
342 gcode->stream->printf("X:%1.4f Y:%1.4f Z:%1.4f\n", x, y, z);
343
344 if(isnan(last_z)) {
345 last_z= z;
346 }else{
347 max_delta= std::max(max_delta, fabsf(z-last_z));
348 }
349 }
350
351 gcode->stream->printf("max delta: %f\n", max_delta);
352
353 return true;
354}
355
14568182
JM
356void ThreePointStrategy::setAdjustFunction(bool on)
357{
358 if(on) {
3632a517 359 // set the compensationTransform in robot
8fe38353 360 THEROBOT->compensationTransform= [this](float *target, bool inverse) { if(inverse) target[2] -= this->plane->getz(target[0], target[1]); else target[2] += this->plane->getz(target[0], target[1]); };
14568182
JM
361 }else{
362 // clear it
c8bac202 363 THEROBOT->compensationTransform= nullptr;
14568182
JM
364 }
365}
366
0e44e7d7
JM
367// find the Z offset for the point on the plane at x, y
368float ThreePointStrategy::getZOffset(float x, float y)
369{
ff7e9858
JM
370 if(this->plane == nullptr) return NAN;
371 return this->plane->getz(x, y);
0e44e7d7 372}
97832d6d 373
0e44e7d7 374// parse a "X,Y" string return x,y
a5542cae
JM
375std::tuple<float, float> ThreePointStrategy::parseXY(const char *str)
376{
377 float x = NAN, y = NAN;
0e44e7d7 378 char *p;
a5542cae
JM
379 x = strtof(str, &p);
380 if(p + 1 < str + strlen(str)) {
381 y = strtof(p + 1, nullptr);
0e44e7d7
JM
382 }
383 return std::make_tuple(x, y);
384}
cef9acea
JM
385
386// parse a "X,Y,Z" string return x,y,z tuple
387std::tuple<float, float, float> ThreePointStrategy::parseXYZ(const char *str)
388{
389 float x = 0, y = 0, z= 0;
390 char *p;
391 x = strtof(str, &p);
392 if(p + 1 < str + strlen(str)) {
393 y = strtof(p + 1, &p);
394 if(p + 1 < str + strlen(str)) {
395 z = strtof(p + 1, nullptr);
396 }
397 }
398 return std::make_tuple(x, y, z);
399}