1 #include "ThreePointStrategy.h"
5 #include "StreamOutputPool.h"
8 #include "ConfigValue.h"
9 #include "PublicDataRequest.h"
10 #include "PublicData.h"
14 #include "nuts_bolts.h"
21 #define probe_point_1_checksum CHECKSUM("point1")
22 #define probe_point_2_checksum CHECKSUM("point2")
23 #define probe_point_3_checksum CHECKSUM("point3")
24 #define probe_offsets_checksum CHECKSUM("probe_offsets")
25 #define home_checksum CHECKSUM("home_first")
26 #define tolerance_checksum CHECKSUM("tolerance")
28 ThreePointStrategy::ThreePointStrategy(ZProbe
*zprobe
) : LevelingStrategy(zprobe
)
30 for (int i
= 0; i
< 3; ++i
) {
31 probe_points
[i
] = std::make_tuple(NAN
, NAN
);
36 ThreePointStrategy::~ThreePointStrategy()
41 bool ThreePointStrategy::handleConfig()
43 // format is xxx,yyy for the probe points
44 std::string p1
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, probe_point_1_checksum
)->by_default("")->as_string();
45 std::string p2
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, probe_point_2_checksum
)->by_default("")->as_string();
46 std::string p3
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, probe_point_3_checksum
)->by_default("")->as_string();
47 if(!p1
.empty()) probe_points
[0] = parseXY(p1
.c_str());
48 if(!p2
.empty()) probe_points
[1] = parseXY(p2
.c_str());
49 if(!p3
.empty()) probe_points
[2] = parseXY(p3
.c_str());
51 // Probe offsets xxx,yyy,zzz
52 std::string po
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, probe_offsets_checksum
)->by_default("0,0,0")->as_string();
53 this->probe_offsets
= parseXYZ(po
.c_str());
55 this->home
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, home_checksum
)->by_default(true)->as_bool();
56 this->tolerance
= THEKERNEL
->config
->value(leveling_strategy_checksum
, three_point_leveling_strategy_checksum
, tolerance_checksum
)->by_default(0.03F
)->as_number();
60 bool ThreePointStrategy::handleGcode(Gcode
*gcode
)
64 if( gcode
->g
== 31 ) { // report status
65 if(this->plane
== nullptr) {
66 gcode
->stream
->printf("Bed leveling plane is not set\n");
68 gcode
->stream
->printf("Bed leveling plane normal= %f, %f, %f\n", plane
->getNormal()[0], plane
->getNormal()[1], plane
->getNormal()[2]);
70 gcode
->stream
->printf("Probe is %s\n", zprobe
->getProbeStatus() ? "Triggered" : "Not triggered");
73 } else if( gcode
->g
== 32 ) { // three point probe
74 // first wait for an empty queue i.e. no moves left
75 THEKERNEL
->conveyor
->wait_for_empty_queue();
76 if(!doProbing(gcode
->stream
)) {
77 gcode
->stream
->printf("Probe failed to complete, probe not triggered or other error\n");
79 gcode
->stream
->printf("Probe completed, bed plane defined\n");
84 } else if(gcode
->has_m
) {
85 if(gcode
->m
== 557) { // M557 - set probe points eg M557 P0 X30 Y40.5 where P is 0,1,2
87 float x
= NAN
, y
= NAN
;
88 if(gcode
->has_letter('P')) idx
= gcode
->get_value('P');
89 if(gcode
->has_letter('X')) x
= gcode
->get_value('X');
90 if(gcode
->has_letter('Y')) y
= gcode
->get_value('Y');
91 if(idx
>= 0 && idx
<= 2) {
92 probe_points
[idx
] = std::make_tuple(x
, y
);
94 gcode
->stream
->printf("only 3 probe points allowed P0-P2\n");
98 } else if(gcode
->m
== 561) { // M561: Set Identity Transform
100 this->plane
= nullptr;
101 // delete the adjustZfnc in robot
102 THEKERNEL
->robot
->adjustZfnc
= nullptr;
105 } else if(gcode
->m
== 565) { // M565: Set Z probe offsets
106 float x
= 0, y
= 0, z
= 0;
107 if(gcode
->has_letter('X')) x
= gcode
->get_value('X');
108 if(gcode
->has_letter('Y')) y
= gcode
->get_value('Y');
109 if(gcode
->has_letter('Z')) z
= gcode
->get_value('Z');
110 probe_offsets
= std::make_tuple(x
, y
, z
);
113 } else if(gcode
->m
== 503) {
115 gcode
->stream
->printf(";Probe points:\n");
116 for (int i
= 0; i
< 3; ++i
) {
117 std::tie(x
, y
) = probe_points
[i
];
118 gcode
->stream
->printf("M557 P%d X%1.5f Y%1.5f\n", i
, x
, y
);
120 gcode
->stream
->printf(";Probe offsets:\n");
121 std::tie(x
, y
, z
) = probe_offsets
;
122 gcode
->stream
->printf("M565 X%1.5f Y%1.5f Z%1.5f\n", x
, y
, z
);
124 // TODO encode plane if set and M500
127 } else if(gcode
->m
== 999) {
128 // DEBUG run a test M999 A B C X Y set Z to A B C and test for point at X Y
130 float x
, y
, z
, a
= 0, b
= 0, c
= 0;
131 if(gcode
->has_letter('A')) a
= gcode
->get_value('A');
132 if(gcode
->has_letter('B')) b
= gcode
->get_value('B');
133 if(gcode
->has_letter('C')) c
= gcode
->get_value('C');
134 std::tie(x
, y
) = probe_points
[0]; v
[0].set(x
, y
, a
);
135 std::tie(x
, y
) = probe_points
[1]; v
[1].set(x
, y
, b
);
136 std::tie(x
, y
) = probe_points
[2]; v
[2].set(x
, y
, c
);
138 this->plane
= new Plane3D(v
[0], v
[1], v
[2]);
139 gcode
->stream
->printf("plane normal= %f, %f, %f\n", plane
->getNormal()[0], plane
->getNormal()[1], plane
->getNormal()[2]);
141 if(gcode
->has_letter('X')) x
= gcode
->get_value('X');
142 if(gcode
->has_letter('Y')) y
= gcode
->get_value('Y');
144 gcode
->stream
->printf("z= %f\n", z
);
145 // tell robot to adjust z on each move
146 THEKERNEL
->robot
->adjustZfnc
= [this](float x
, float y
) { return this->plane
->getz(x
, y
); };
154 void ThreePointStrategy::homeXY()
156 Gcode
gc("G28 X0 Y0", &(StreamOutput::NullStream
));
157 THEKERNEL
->call_event(ON_GCODE_RECEIVED
, &gc
);
160 bool ThreePointStrategy::doProbing(StreamOutput
*stream
)
163 // check the probe points have been defined
164 for (int i
= 0; i
< 3; ++i
) {
165 std::tie(x
, y
) = probe_points
[i
];
166 if(isnan(x
) || isnan(y
)) {
167 stream
->printf("Probe point P%d has not been defined, use M557 P%d Xnnn Ynnn to define it\n", i
, i
);
172 // optionally home XY axis first, but allow for manual homing
176 // move to the first probe point
177 std::tie(x
, y
) = probe_points
[0];
178 // offset by the probe XY offset
179 x
-= std::get
<X_AXIS
>(this->probe_offsets
);
180 y
-= std::get
<Y_AXIS
>(this->probe_offsets
);
181 zprobe
->coordinated_move(x
, y
, NAN
, zprobe
->getFastFeedrate());
183 // for now we use probe to find bed and not the Z min endstop
184 // 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
186 // TODO this needs to be configurable to use min z or probe
187 // 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
188 THEKERNEL
->robot
->reset_axis_position(std::get
<Z_AXIS
>(this->probe_offsets
), Z_AXIS
);
190 // find bed via probe
192 if(!zprobe
->run_probe(s
, true)) return false;
194 // move up to specified probe start position
195 zprobe
->coordinated_move(NAN
, NAN
, zprobe
->getProbeHeight(), zprobe
->getFastFeedrate(), true); // do a relative move from home to the point above the bed
197 // probe the three points
199 for (int i
= 0; i
< 3; ++i
) {
200 std::tie(x
, y
) = probe_points
[i
];
201 // offset moves by the probe XY offset
202 float z
= zprobe
->probeDistance(x
-std::get
<X_AXIS
>(this->probe_offsets
), y
-std::get
<Y_AXIS
>(this->probe_offsets
));
203 if(isnan(z
)) return false; // probe failed
204 z
= zprobe
->getProbeHeight() - z
; // relative distance between the probe points, lower is negative z
205 stream
->printf("DEBUG: P%d:%1.4f\n", i
, z
);
209 // if first point is not within tolerance report it, it should ideally be 0
210 if(abs(v
[0][2]) > this->tolerance
) {
211 stream
->printf("WARNING: probe is not within tolerance\n");
216 // check tolerance level here default 0.03mm
217 auto mm
= std::minmax({v
[0][2], v
[1][2], v
[2][2]});
218 if((mm
.second
- mm
.first
) <= this->tolerance
) {
219 this->plane
= nullptr; // plane is flat no need to do anything
220 stream
->printf("DEBUG: flat plane\n");
221 // clear the adjustZfnc in robot
222 THEKERNEL
->robot
->adjustZfnc
= nullptr;
225 this->plane
= new Plane3D(v
[0], v
[1], v
[2]);
226 stream
->printf("DEBUG: plane normal= %f, %f, %f\n", plane
->getNormal()[0], plane
->getNormal()[1], plane
->getNormal()[2]);
227 // set the adjustZfnc in robot
228 THEKERNEL
->robot
->adjustZfnc
= [this](float x
, float y
) { return this->plane
->getz(x
, y
); };
234 // find the Z offset for the point on the plane at x, y
235 float ThreePointStrategy::getZOffset(float x
, float y
)
237 if(this->plane
== nullptr) return NAN
;
238 return this->plane
->getz(x
, y
);
241 // parse a "X,Y" string return x,y
242 std::tuple
<float, float> ThreePointStrategy::parseXY(const char *str
)
244 float x
= NAN
, y
= NAN
;
247 if(p
+ 1 < str
+ strlen(str
)) {
248 y
= strtof(p
+ 1, nullptr);
250 return std::make_tuple(x
, y
);
253 // parse a "X,Y,Z" string return x,y,z tuple
254 std::tuple
<float, float, float> ThreePointStrategy::parseXYZ(const char *str
)
256 float x
= 0, y
= 0, z
= 0;
259 if(p
+ 1 < str
+ strlen(str
)) {
260 y
= strtof(p
+ 1, &p
);
261 if(p
+ 1 < str
+ strlen(str
)) {
262 z
= strtof(p
+ 1, nullptr);
265 return std::make_tuple(x
, y
, z
);