tighten up the concept of machine position and workspace coordinates.
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "libs/Module.h"
9 #include "libs/Kernel.h"
10
11 #include "mbed.h" // for us_ticker_read()
12
13 #include <math.h>
14 #include <string>
15 using std::string;
16
17 #include "Planner.h"
18 #include "Conveyor.h"
19 #include "Robot.h"
20 #include "nuts_bolts.h"
21 #include "Pin.h"
22 #include "StepperMotor.h"
23 #include "Gcode.h"
24 #include "PublicDataRequest.h"
25 #include "PublicData.h"
26 #include "arm_solutions/BaseSolution.h"
27 #include "arm_solutions/CartesianSolution.h"
28 #include "arm_solutions/RotatableCartesianSolution.h"
29 #include "arm_solutions/LinearDeltaSolution.h"
30 #include "arm_solutions/RotatableDeltaSolution.h"
31 #include "arm_solutions/HBotSolution.h"
32 #include "arm_solutions/CoreXZSolution.h"
33 #include "arm_solutions/MorganSCARASolution.h"
34 #include "StepTicker.h"
35 #include "checksumm.h"
36 #include "utils.h"
37 #include "ConfigValue.h"
38 #include "libs/StreamOutput.h"
39 #include "StreamOutputPool.h"
40 #include "ExtruderPublicAccess.h"
41
42 #define default_seek_rate_checksum CHECKSUM("default_seek_rate")
43 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
44 #define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
45 #define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
46 #define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
47 #define arc_correction_checksum CHECKSUM("arc_correction")
48 #define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
49 #define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
50 #define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
51
52 // arm solutions
53 #define arm_solution_checksum CHECKSUM("arm_solution")
54 #define cartesian_checksum CHECKSUM("cartesian")
55 #define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
56 #define rostock_checksum CHECKSUM("rostock")
57 #define linear_delta_checksum CHECKSUM("linear_delta")
58 #define rotatable_delta_checksum CHECKSUM("rotatable_delta")
59 #define delta_checksum CHECKSUM("delta")
60 #define hbot_checksum CHECKSUM("hbot")
61 #define corexy_checksum CHECKSUM("corexy")
62 #define corexz_checksum CHECKSUM("corexz")
63 #define kossel_checksum CHECKSUM("kossel")
64 #define morgan_checksum CHECKSUM("morgan")
65
66 // new-style actuator stuff
67 #define actuator_checksum CHEKCSUM("actuator")
68
69 #define step_pin_checksum CHECKSUM("step_pin")
70 #define dir_pin_checksum CHEKCSUM("dir_pin")
71 #define en_pin_checksum CHECKSUM("en_pin")
72
73 #define steps_per_mm_checksum CHECKSUM("steps_per_mm")
74 #define max_rate_checksum CHECKSUM("max_rate")
75
76 #define alpha_checksum CHECKSUM("alpha")
77 #define beta_checksum CHECKSUM("beta")
78 #define gamma_checksum CHECKSUM("gamma")
79
80 #define NEXT_ACTION_DEFAULT 0
81 #define NEXT_ACTION_DWELL 1
82 #define NEXT_ACTION_GO_HOME 2
83
84 #define MOTION_MODE_SEEK 0 // G0
85 #define MOTION_MODE_LINEAR 1 // G1
86 #define MOTION_MODE_CW_ARC 2 // G2
87 #define MOTION_MODE_CCW_ARC 3 // G3
88 #define MOTION_MODE_CANCEL 4 // G80
89
90 #define PATH_CONTROL_MODE_EXACT_PATH 0
91 #define PATH_CONTROL_MODE_EXACT_STOP 1
92 #define PATH_CONTROL_MODE_CONTINOUS 2
93
94 #define PROGRAM_FLOW_RUNNING 0
95 #define PROGRAM_FLOW_PAUSED 1
96 #define PROGRAM_FLOW_COMPLETED 2
97
98 #define SPINDLE_DIRECTION_CW 0
99 #define SPINDLE_DIRECTION_CCW 1
100
101 #define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians)
102
103 // The Robot converts GCodes into actual movements, and then adds them to the Planner, which passes them to the Conveyor so they can be added to the queue
104 // It takes care of cutting arcs into segments, same thing for line that are too long
105
106 Robot::Robot()
107 {
108 this->inch_mode = false;
109 this->absolute_mode = true;
110 this->motion_mode = MOTION_MODE_SEEK;
111 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
112 clear_vector(this->last_milestone);
113 clear_vector(this->machine_position);
114 this->arm_solution = NULL;
115 seconds_per_minute = 60.0F;
116 this->clearToolOffset();
117 this->compensationTransform = nullptr;
118 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
119 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
120 this->next_command_is_MCS = false;
121 }
122
123 //Called when the module has just been loaded
124 void Robot::on_module_loaded()
125 {
126 this->register_for_event(ON_GCODE_RECEIVED);
127
128 // Configuration
129 this->load_config();
130 }
131
132 #define ACTUATOR_CHECKSUMS(X) { \
133 CHECKSUM(X "_step_pin"), \
134 CHECKSUM(X "_dir_pin"), \
135 CHECKSUM(X "_en_pin"), \
136 CHECKSUM(X "_steps_per_mm"), \
137 CHECKSUM(X "_max_rate") \
138 }
139
140 void Robot::load_config()
141 {
142 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
143 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
144 // To make adding those solution easier, they have their own, separate object.
145 // Here we read the config to find out which arm solution to use
146 if (this->arm_solution) delete this->arm_solution;
147 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
148 // Note checksums are not const expressions when in debug mode, so don't use switch
149 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
150 this->arm_solution = new HBotSolution(THEKERNEL->config);
151
152 } else if(solution_checksum == corexz_checksum) {
153 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
154
155 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
156 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
157
158 } else if(solution_checksum == rotatable_cartesian_checksum) {
159 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
160
161 } else if(solution_checksum == rotatable_delta_checksum) {
162 this->arm_solution = new RotatableDeltaSolution(THEKERNEL->config);
163
164 } else if(solution_checksum == morgan_checksum) {
165 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
166
167 } else if(solution_checksum == cartesian_checksum) {
168 this->arm_solution = new CartesianSolution(THEKERNEL->config);
169
170 } else {
171 this->arm_solution = new CartesianSolution(THEKERNEL->config);
172 }
173
174 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
175 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
176 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
177 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
178 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
179 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
180
181 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
182 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
183 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
184
185 // Make our 3 StepperMotors
186 uint16_t const checksums[][5] = {
187 ACTUATOR_CHECKSUMS("alpha"),
188 ACTUATOR_CHECKSUMS("beta"),
189 ACTUATOR_CHECKSUMS("gamma"),
190 #if MAX_ROBOT_ACTUATORS > 3
191 ACTUATOR_CHECKSUMS("delta"),
192 ACTUATOR_CHECKSUMS("epsilon"),
193 ACTUATOR_CHECKSUMS("zeta")
194 #endif
195 };
196 constexpr size_t actuator_checksum_count = sizeof(checksums) / sizeof(checksums[0]);
197 static_assert(actuator_checksum_count >= k_max_actuators, "Robot checksum array too small for k_max_actuators");
198
199 size_t motor_count = std::min(this->arm_solution->get_actuator_count(), k_max_actuators);
200 for (size_t a = 0; a < motor_count; a++) {
201 Pin pins[3]; //step, dir, enable
202 for (size_t i = 0; i < 3; i++) {
203 pins[i].from_string(THEKERNEL->config->value(checksums[a][i])->by_default("nc")->as_string())->as_output();
204 }
205 actuators[a] = new StepperMotor(pins[0], pins[1], pins[2]);
206
207 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
208 actuators[a]->set_max_rate(THEKERNEL->config->value(checksums[a][4])->by_default(30000.0F)->as_number());
209 }
210
211 check_max_actuator_speeds(); // check the configs are sane
212
213 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
214 // so the first move can be correct if homing is not performed
215 ActuatorCoordinates actuator_pos;
216 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
217 for (size_t i = 0; i < actuators.size(); i++)
218 actuators[i]->change_last_milestone(actuator_pos[i]);
219
220 //this->clearToolOffset();
221 }
222
223 void Robot::push_state()
224 {
225 bool am = this->absolute_mode;
226 bool im = this->inch_mode;
227 saved_state_t s(this->feed_rate, this->seek_rate, am, im, current_wcs);
228 state_stack.push(s);
229 }
230
231 void Robot::pop_state()
232 {
233 if(!state_stack.empty()) {
234 auto s = state_stack.top();
235 state_stack.pop();
236 this->feed_rate = std::get<0>(s);
237 this->seek_rate = std::get<1>(s);
238 this->absolute_mode = std::get<2>(s);
239 this->inch_mode = std::get<3>(s);
240 this->current_wcs = std::get<4>(s);
241 }
242 }
243
244 // this does a sanity check that actuator speeds do not exceed steps rate capability
245 // we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
246 void Robot::check_max_actuator_speeds()
247 {
248 for (size_t i = 0; i < actuators.size(); i++) {
249 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
250 if (step_freq > THEKERNEL->base_stepping_frequency) {
251 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
252 THEKERNEL->streams->printf("WARNING: actuator %c rate exceeds base_stepping_frequency * alpha_steps_per_mm: %f, setting to %f\n", 'A' + i, step_freq, actuators[i]->max_rate);
253 }
254 }
255 }
256
257 // converts current machine position to work coordinate system
258 Robot::wcs_t Robot::mcs2wcs()
259 {
260 // FIXME this will be incorrect if there is a compensation transform in effect
261 return std::make_tuple(
262 machine_position[X_AXIS] - (std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset)),
263 machine_position[Y_AXIS] - (std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset)),
264 machine_position[Z_AXIS] - (std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset)));
265 }
266
267 //A GCode has been received
268 //See if the current Gcode line has some orders for us
269 void Robot::on_gcode_received(void *argument)
270 {
271 Gcode *gcode = static_cast<Gcode *>(argument);
272
273 this->motion_mode = -1;
274
275 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
276 if( gcode->has_g) {
277 switch( gcode->g ) {
278 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
279 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
280 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
281 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
282 case 4: {
283 uint32_t delay_ms = 0;
284 if (gcode->has_letter('P')) {
285 delay_ms = gcode->get_int('P');
286 }
287 if (gcode->has_letter('S')) {
288 delay_ms += gcode->get_int('S') * 1000;
289 }
290 if (delay_ms > 0) {
291 // drain queue
292 THEKERNEL->conveyor->wait_for_empty_queue();
293 // wait for specified time
294 uint32_t start = us_ticker_read(); // mbed call
295 while ((us_ticker_read() - start) < delay_ms * 1000) {
296 THEKERNEL->call_event(ON_IDLE, this);
297 }
298 }
299 }
300 break;
301
302 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
303 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
304 size_t n = gcode->get_uint('P');
305 if(n == 0) n = current_wcs; // set current coordinate system
306 else --n;
307 if(n < k_max_wcs) {
308 float x, y, z;
309 std::tie(x, y, z) = wcs_offsets[n];
310 if(gcode->get_int('L') == 20) {
311 // this makes the current machine position the offset
312 if(gcode->has_letter('X')) { x = to_millimeters(gcode->get_value('X')) - machine_position[X_AXIS]; }
313 if(gcode->has_letter('Y')) { x = to_millimeters(gcode->get_value('Y')) - machine_position[Y_AXIS]; }
314 if(gcode->has_letter('Z')) { x = to_millimeters(gcode->get_value('Z')) - machine_position[Z_AXIS]; }
315 } else {
316 // the value is the offset from machine zero
317 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
318 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
319 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
320 }
321 wcs_offsets[n] = wcs_t(x, y, z);
322 }
323 }
324 break;
325
326 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
327 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
328 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
329 case 20: this->inch_mode = true; break;
330 case 21: this->inch_mode = false; break;
331
332 case 54: case 55: case 56: case 57: case 58: case 59:
333 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
334 current_wcs = gcode->g - 54;
335 if(gcode->g == 59 && gcode->subcode > 0) {
336 current_wcs += gcode->subcode;
337 if(current_wcs >= k_max_wcs) current_wcs = k_max_wcs - 1;
338 }
339 break;
340
341 case 90: this->absolute_mode = true; break;
342 case 91: this->absolute_mode = false; break;
343
344 case 92: {
345 wcs_t old = g92_offset;
346
347 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
348 // reset G92 offsets to 0
349 g92_offset = wcs_t(0, 0, 0);
350
351 } else {
352 // standard setting of the g92 offsets, making current machine position whatever the coordinate arguments are
353 float x, y, z;
354 std::tie(x, y, z) = g92_offset;
355 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X')) - machine_position[X_AXIS];
356 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y')) - machine_position[Y_AXIS];
357 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z')) - machine_position[Z_AXIS];
358 g92_offset = wcs_t(x, y, z);
359 }
360
361 if(old != g92_offset) {
362 // as it changed we need to update the last_milestone to reflect the new coordinate system
363 std::tie(this->last_milestone[X_AXIS], this->last_milestone[Y_AXIS], this->last_milestone[Z_AXIS]) = mcs2wcs();
364 }
365 return;
366 }
367 }
368
369 } else if( gcode->has_m) {
370 switch( gcode->m ) {
371 case 2: // M2 end of program
372 current_wcs = 0;
373 absolute_mode = true;
374 break;
375
376 case 92: // M92 - set steps per mm
377 if (gcode->has_letter('X'))
378 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
379 if (gcode->has_letter('Y'))
380 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
381 if (gcode->has_letter('Z'))
382 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
383 if (gcode->has_letter('F'))
384 seconds_per_minute = gcode->get_value('F');
385
386 gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm, seconds_per_minute);
387 gcode->add_nl = true;
388 check_max_actuator_speeds();
389 return;
390
391 case 114: {
392 // this is a new way to do this (similar to how GRBL does it).
393 // it returns the realtime position based on the current step position of the actuators.
394 // this does require a FK to get a machine position from the actuator position
395 // and then invert all the transforms to get a workspace position from machine position
396 char buf[64];
397 int n = 0;
398 // current actuator position in mm
399 ActuatorCoordinates current_position{
400 actuators[X_AXIS]->get_current_position(),
401 actuators[Y_AXIS]->get_current_position(),
402 actuators[Z_AXIS]->get_current_position()
403 };
404
405 // get machine position from the actuator position using FK
406 float mpos[3];
407 arm_solution->actuator_to_cartesian(current_position, mpos);
408
409 if(gcode->subcode == 0) { // M114 print WCS
410 // Note this is workspace coordinates after any bed level compensation has been applied, currently there is no way to get
411 // the position we were asked for (although it should be last_milestone) without doing an inverse compensation, which is probably not a big deal.
412 n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f",
413 from_millimeters(mpos[X_AXIS] - (std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset))),
414 from_millimeters(mpos[Y_AXIS] - (std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset))),
415 from_millimeters(mpos[Z_AXIS] - (std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset))) );
416
417
418 } else if(gcode->subcode == 1) { // M114.1 print Machine coordinate system
419 n = snprintf(buf, sizeof(buf), "MPOS: X:%1.3f Y:%1.3f Z:%1.3f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
420
421 } else if(gcode->subcode == 2) { // M114.2 print actuator position
422 n = snprintf(buf, sizeof(buf), "APOS: A:%1.3f B:%1.3f C:%1.3f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
423
424 } else if(gcode->subcode == 3) { // M114.3 print last milestone (which should be the same as M114 if axis are not moving and no level compensation)
425 n = snprintf(buf, sizeof(buf), "LM: X:%1.3f Y:%1.3f Z:%1.3f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
426
427 } else if(gcode->subcode == 4) { // M114.4 print last machins position (which should be the same as M114.1 if axis are not moving)
428 n = snprintf(buf, sizeof(buf), "LMPOS: X:%1.3f Y:%1.3f Z:%1.3f", machine_position[X_AXIS], machine_position[Y_AXIS], machine_position[Z_AXIS]);
429 }
430
431 if(n > 0)
432 gcode->txt_after_ok.append(buf, n);
433 }
434 return;
435
436 case 120: // push state
437 push_state();
438 break;
439
440 case 121: // pop state
441 pop_state();
442 break;
443
444 case 203: // M203 Set maximum feedrates in mm/sec
445 if (gcode->has_letter('X'))
446 this->max_speeds[X_AXIS] = gcode->get_value('X');
447 if (gcode->has_letter('Y'))
448 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
449 if (gcode->has_letter('Z'))
450 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
451 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
452 if (gcode->has_letter('A' + i))
453 actuators[i]->set_max_rate(gcode->get_value('A' + i));
454 }
455 check_max_actuator_speeds();
456
457 if(gcode->get_num_args() == 0) {
458 gcode->stream->printf("X:%g Y:%g Z:%g",
459 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
460 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
461 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
462 }
463 gcode->add_nl = true;
464 }
465 break;
466
467 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
468 if (gcode->has_letter('S')) {
469 float acc = gcode->get_value('S'); // mm/s^2
470 // enforce minimum
471 if (acc < 1.0F)
472 acc = 1.0F;
473 THEKERNEL->planner->acceleration = acc;
474 }
475 if (gcode->has_letter('Z')) {
476 float acc = gcode->get_value('Z'); // mm/s^2
477 // enforce positive
478 if (acc < 0.0F)
479 acc = 0.0F;
480 THEKERNEL->planner->z_acceleration = acc;
481 }
482 break;
483
484 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
485 if (gcode->has_letter('X')) {
486 float jd = gcode->get_value('X');
487 // enforce minimum
488 if (jd < 0.0F)
489 jd = 0.0F;
490 THEKERNEL->planner->junction_deviation = jd;
491 }
492 if (gcode->has_letter('Z')) {
493 float jd = gcode->get_value('Z');
494 // enforce minimum, -1 disables it and uses regular junction deviation
495 if (jd < -1.0F)
496 jd = -1.0F;
497 THEKERNEL->planner->z_junction_deviation = jd;
498 }
499 if (gcode->has_letter('S')) {
500 float mps = gcode->get_value('S');
501 // enforce minimum
502 if (mps < 0.0F)
503 mps = 0.0F;
504 THEKERNEL->planner->minimum_planner_speed = mps;
505 }
506 if (gcode->has_letter('Y')) {
507 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
508 }
509 break;
510
511 case 220: // M220 - speed override percentage
512 if (gcode->has_letter('S')) {
513 float factor = gcode->get_value('S');
514 // enforce minimum 10% speed
515 if (factor < 10.0F)
516 factor = 10.0F;
517 // enforce maximum 10x speed
518 if (factor > 1000.0F)
519 factor = 1000.0F;
520
521 seconds_per_minute = 6000.0F / factor;
522 } else {
523 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
524 }
525 break;
526
527 case 400: // wait until all moves are done up to this point
528 THEKERNEL->conveyor->wait_for_empty_queue();
529 break;
530
531 case 500: // M500 saves some volatile settings to config override file
532 case 503: { // M503 just prints the settings
533 gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
534 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
535 gcode->stream->printf(";X- Junction Deviation, Z- Z junction deviation, S - Minimum Planner speed mm/sec:\nM205 X%1.5f Z%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
536 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
537 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
538 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
539 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
540 }
541 gcode->stream->printf("\n");
542
543 // get or save any arm solution specific optional values
544 BaseSolution::arm_options_t options;
545 if(arm_solution->get_optional(options) && !options.empty()) {
546 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
547 for(auto &i : options) {
548 gcode->stream->printf(" %c%1.4f", i.first, i.second);
549 }
550 gcode->stream->printf("\n");
551 }
552
553 // save wcs_offsets and current_wcs
554 // TODO this may need to be done whenever they change to be compliant
555 gcode->stream->printf(";WCS settings\n");
556 gcode->stream->printf("G5%c", std::min(current_wcs, (uint8_t)(5 + '4')));
557 if(current_wcs >= 6) {
558 gcode->stream->printf(".%c\n", '1' + (current_wcs - 5));
559 } else {
560 gcode->stream->printf("\n");
561 }
562 int n = 1;
563 for(auto &i : wcs_offsets) {
564 if(i != wcs_t(0, 0, 0)) {
565 float x, y, z;
566 std::tie(x, y, z) = i;
567 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f\n", n, x, y, z);
568 }
569 ++n;
570 }
571 }
572
573 if(gcode->m == 503) {
574 // just print the G92 setting as it is not saved
575 if(g92_offset != wcs_t(0, 0, 0)) {
576 float x, y, z;
577 std::tie(x, y, z) = g92_offset;
578 gcode->stream->printf("G92 X%f Y%f Z%f ; NOT SAVED\n", x, y, z);
579 }
580 }
581 break;
582
583 case 665: { // M665 set optional arm solution variables based on arm solution.
584 // the parameter args could be any letter each arm solution only accepts certain ones
585 BaseSolution::arm_options_t options = gcode->get_args();
586 options.erase('S'); // don't include the S
587 options.erase('U'); // don't include the U
588 if(options.size() > 0) {
589 // set the specified options
590 arm_solution->set_optional(options);
591 }
592 options.clear();
593 if(arm_solution->get_optional(options)) {
594 // foreach optional value
595 for(auto &i : options) {
596 // print all current values of supported options
597 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
598 gcode->add_nl = true;
599 }
600 }
601
602 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
603 this->delta_segments_per_second = gcode->get_value('S');
604 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
605
606 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
607 this->mm_per_line_segment = gcode->get_value('U');
608 this->delta_segments_per_second = 0;
609 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
610 }
611
612 break;
613 }
614 }
615 }
616
617 if( this->motion_mode < 0) {
618 next_command_is_MCS = false; // must be on same line as G0 or G1
619 return;
620 }
621
622 // Get parameters
623 float target[3], offset[3];
624
625 clear_vector(offset);
626 for(char letter = 'I'; letter <= 'K'; letter++) {
627 if( gcode->has_letter(letter) ) {
628 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
629 }
630 }
631
632 if(next_command_is_MCS) {
633 // we are getting different coordinates here MCS instead of WCS
634 memcpy(target, this->machine_position, sizeof(target)); //default to last machine position instead of last milestone
635 for(char letter = 'X'; letter <= 'Z'; letter++) {
636 if( gcode->has_letter(letter) ) {
637 target[letter - 'X'] = this->to_millimeters(gcode->get_value(letter));
638 }
639 }
640
641 } else {
642 memcpy(target, this->last_milestone, sizeof(target)); //default to last target
643 for(char letter = 'X'; letter <= 'Z'; letter++) {
644 if( gcode->has_letter(letter) ) {
645 target[letter - 'X'] = this->to_millimeters(gcode->get_value(letter)) + (this->absolute_mode ? this->toolOffset[letter - 'X'] : last_milestone[letter - 'X']);
646 }
647 }
648 }
649
650 if( gcode->has_letter('F') ) {
651 if( this->motion_mode == MOTION_MODE_SEEK )
652 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
653 else
654 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
655 }
656
657 //Perform any physical actions
658 switch(this->motion_mode) {
659 case MOTION_MODE_CANCEL: break;
660 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate / seconds_per_minute ); break;
661 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate / seconds_per_minute ); break;
662 case MOTION_MODE_CW_ARC:
663 case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
664 }
665
666 // last_milestone was set to target in append_milestone, no need to do it again
667
668 }
669
670 // We received a new gcode, and one of the functions
671 // determined the distance for that given gcode. So now we can attach this gcode to the right block
672 // and continue
673 void Robot::distance_in_gcode_is_known(Gcode * gcode)
674 {
675 //If the queue is empty, execute immediatly, otherwise attach to the last added block
676 THEKERNEL->conveyor->append_gcode(gcode);
677 }
678
679 // reset the machine position for all axis. Used for homing.
680 // we need to also set the last_milestone by applying the inverse offsets
681 void Robot::reset_axis_position(float x, float y, float z)
682 {
683 this->machine_position[X_AXIS] = x;
684 this->machine_position[Y_AXIS] = y;
685 this->machine_position[Z_AXIS] = z;
686
687 // calculate what the last milestone would be
688 std::tie(this->last_milestone[X_AXIS], this->last_milestone[Y_AXIS], this->last_milestone[Z_AXIS]) = mcs2wcs();
689
690 // now set the actuator positions to match
691 ActuatorCoordinates actuator_pos;
692 arm_solution->cartesian_to_actuator(this->machine_position, actuator_pos);
693 for (size_t i = 0; i < actuators.size(); i++)
694 actuators[i]->change_last_milestone(actuator_pos[i]);
695 }
696
697 // Reset the position for an axis (used in homing)
698 void Robot::reset_axis_position(float position, int axis)
699 {
700 machine_position[axis] = position;
701 reset_axis_position(machine_position[X_AXIS], machine_position[Y_AXIS], machine_position[Z_AXIS]);
702 }
703
704 // Use FK to find out where actuator is and reset to match
705 void Robot::reset_position_from_current_actuator_position()
706 {
707 ActuatorCoordinates actuator_pos;
708 for (size_t i = 0; i < actuators.size(); i++) {
709 actuator_pos[i] = actuators[i]->get_current_position();
710 }
711 arm_solution->actuator_to_cartesian(actuator_pos, machine_position);
712
713 std::tie(this->last_milestone[X_AXIS], this->last_milestone[Y_AXIS], this->last_milestone[Z_AXIS]) = mcs2wcs();
714
715 // now reset actuator correctly, NOTE this may lose a little precision
716 // NOTE I do not recall why this was necessary, maybe to correct for small errors in FK
717 // arm_solution->cartesian_to_actuator(machine_position, actuator_pos);
718 // for (size_t i = 0; i < actuators.size(); i++)
719 // actuators[i]->change_last_milestone(actuator_pos[i]);
720 }
721
722 // Convert target from millimeters to steps, and append this to the planner
723 void Robot::append_milestone(Gcode * gcode, const float target[], float rate_mm_s)
724 {
725 float deltas[3];
726 float unit_vec[3];
727 ActuatorCoordinates actuator_pos;
728 float transformed_target[3]; // adjust target for bed compensation and WCS offsets
729 float millimeters_of_travel;
730
731 // unity transform by default
732 memcpy(transformed_target, target, sizeof(transformed_target));
733
734 // if the target is in machine coordinates we do not apply any translations (G53)
735 if(!next_command_is_MCS) {
736 // check function pointer and call if set to transform the target to compensate for bed
737 if(compensationTransform) {
738 // some compensation strategies can transform XYZ, some just change Z
739 compensationTransform(transformed_target);
740 }
741
742 // apply wcs offsets and g92 offset
743 transformed_target[X_AXIS] += (std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset));
744 transformed_target[Y_AXIS] += (std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset));
745 transformed_target[Z_AXIS] += (std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset));
746 }
747
748 // find distance moved by each axis, use transformed target from the current machine position
749 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
750 deltas[axis] = transformed_target[axis] - machine_position[axis];
751 }
752
753 // Compute how long this move moves, so we can attach it to the block for later use
754 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
755
756 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
757 // as the last milestone won't be updated we do not actually lose any moves as they will be accounted for in the next move
758 if(millimeters_of_travel < 0.00001F) return;
759
760 // this is the machine position
761 memcpy(this->machine_position, transformed_target, sizeof(this->machine_position));
762
763
764 // find distance unit vector
765 for (int i = 0; i < 3; i++)
766 unit_vec[i] = deltas[i] / millimeters_of_travel;
767
768 // Do not move faster than the configured cartesian limits
769 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
770 if ( max_speeds[axis] > 0 ) {
771 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
772
773 if (axis_speed > max_speeds[axis])
774 rate_mm_s *= ( max_speeds[axis] / axis_speed );
775 }
776 }
777
778 // find actuator position given cartesian position, use actual adjusted target
779 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
780
781 float isecs = rate_mm_s / millimeters_of_travel;
782 // check per-actuator speed limits
783 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
784 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
785 if (actuator_rate > actuators[actuator]->get_max_rate()) {
786 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
787 isecs = rate_mm_s / millimeters_of_travel;
788 }
789 }
790
791 // Append the block to the planner
792 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
793
794 // Update the last_milestone to the current target for the next time we use last_milestone, use the requested target not the adjusted one
795 if(next_command_is_MCS) {
796 // as the target was in machine coordinates we need to add inverse wcs offsets and g92 offset to get a reasonable equivalent last_milestone
797 std::tie(this->last_milestone[X_AXIS], this->last_milestone[Y_AXIS], this->last_milestone[Z_AXIS]) = mcs2wcs();
798
799 } else {
800 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
801 }
802
803 }
804
805 // Append a move to the queue ( cutting it into segments if needed )
806 void Robot::append_line(Gcode * gcode, const float target[], float rate_mm_s )
807 {
808 float last_target[]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
809
810 if(next_command_is_MCS) {
811 // we are in machine coordinates
812 memcpy(last_target, machine_position, sizeof(last_target));
813 }
814
815 // Find out the distance for this move in WCS
816 // NOTE we need to do sqrt here as this setting of millimeters_of_travel is used by extruder and other modules even if there is no XYZ move
817 gcode->millimeters_of_travel = sqrtf(powf( target[X_AXIS] - last_target[X_AXIS], 2 ) + powf( target[Y_AXIS] - last_target[Y_AXIS], 2 ) + powf( target[Z_AXIS] - last_target[Z_AXIS], 2 ));
818
819 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
820 if( gcode->millimeters_of_travel < 0.00001F ) return;
821
822 // Mark the gcode as having a known distance
823 this->distance_in_gcode_is_known( gcode );
824
825 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
826 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
827 // We also check we are not exceeding the E max_speed for the current extruder
828 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
829 // we need to ask it now passing in the relevant data.
830 // NOTE we need to do this before we segment the line (for deltas)
831 if(gcode->has_letter('E')) {
832 float data[2];
833 data[0] = gcode->get_value('E'); // E target (maybe absolute or relative)
834 data[1] = rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
835 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
836 rate_mm_s *= data[1];
837 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
838 }
839 }
840
841 // We cut the line into smaller segments. This is only needed on a cartesian robot for zgrid, but always necessary for robots with rotational axes.
842 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
843 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
844 // The latter is more efficient and avoids splitting fast long lines into very small segments, like initial z move to 0, it is what Johanns Marlin delta port does
845 uint16_t segments;
846
847 if(this->delta_segments_per_second > 1.0F) {
848 // enabled if set to something > 1, it is set to 0.0 by default
849 // segment based on current speed and requested segments per second
850 // the faster the travel speed the fewer segments needed
851 // NOTE rate is mm/sec and we take into account any speed override
852 float seconds = gcode->millimeters_of_travel / rate_mm_s;
853 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
854 // TODO if we are only moving in Z on a delta we don't really need to segment at all
855
856 } else {
857 if(this->mm_per_line_segment == 0.0F) {
858 segments = 1; // don't split it up
859 } else {
860 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
861 }
862 }
863
864 if (segments > 1) {
865 // A vector to keep track of the endpoint of each segment
866 float segment_delta[3];
867 float segment_end[3];
868
869 // How far do we move each segment?
870 for (int i = X_AXIS; i <= Z_AXIS; i++)
871 segment_delta[i] = (target[i] - last_target[i]) / segments;
872
873 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
874 // We always add another point after this loop so we stop at segments-1, ie i < segments
875 for (int i = 1; i < segments; i++) {
876 if(THEKERNEL->is_halted()) return; // don't queue any more segments
877 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
878 segment_end[axis] = last_target[axis] + segment_delta[axis];
879
880 // Append the end of this segment to the queue
881 this->append_milestone(gcode, segment_end, rate_mm_s);
882 }
883 }
884
885 // Append the end of this full move to the queue
886 this->append_milestone(gcode, target, rate_mm_s);
887
888 this->next_command_is_MCS = false; // always reset this
889
890 // if adding these blocks didn't start executing, do that now
891 THEKERNEL->conveyor->ensure_running();
892 }
893
894
895 // Append an arc to the queue ( cutting it into segments as needed )
896 void Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
897 {
898
899 // Scary math
900 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
901 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
902 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
903 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
904 float r_axis1 = -offset[this->plane_axis_1];
905 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
906 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
907
908 // Patch from GRBL Firmware - Christoph Baumann 04072015
909 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
910 float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
911 if (is_clockwise) { // Correct atan2 output per direction
912 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2 * M_PI; }
913 } else {
914 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2 * M_PI; }
915 }
916
917 // Find the distance for this gcode
918 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
919
920 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
921 if( gcode->millimeters_of_travel < 0.00001F ) {
922 return;
923 }
924
925 // Mark the gcode as having a known distance
926 this->distance_in_gcode_is_known( gcode );
927
928 // Figure out how many segments for this gcode
929 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
930
931 float theta_per_segment = angular_travel / segments;
932 float linear_per_segment = linear_travel / segments;
933
934 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
935 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
936 r_T = [cos(phi) -sin(phi);
937 sin(phi) cos(phi] * r ;
938 For arc generation, the center of the circle is the axis of rotation and the radius vector is
939 defined from the circle center to the initial position. Each line segment is formed by successive
940 vector rotations. This requires only two cos() and sin() computations to form the rotation
941 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
942 all float numbers are single precision on the Arduino. (True float precision will not have
943 round off issues for CNC applications.) Single precision error can accumulate to be greater than
944 tool precision in some cases. Therefore, arc path correction is implemented.
945
946 Small angle approximation may be used to reduce computation overhead further. This approximation
947 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
948 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
949 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
950 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
951 issue for CNC machines with the single precision Arduino calculations.
952 This approximation also allows mc_arc to immediately insert a line segment into the planner
953 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
954 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
955 This is important when there are successive arc motions.
956 */
957 // Vector rotation matrix values
958 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
959 float sin_T = theta_per_segment;
960
961 float arc_target[3];
962 float sin_Ti;
963 float cos_Ti;
964 float r_axisi;
965 uint16_t i;
966 int8_t count = 0;
967
968 // Initialize the linear axis
969 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
970
971 for (i = 1; i < segments; i++) { // Increment (segments-1)
972 if(THEKERNEL->is_halted()) return; // don't queue any more segments
973
974 if (count < this->arc_correction ) {
975 // Apply vector rotation matrix
976 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
977 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
978 r_axis1 = r_axisi;
979 count++;
980 } else {
981 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
982 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
983 cos_Ti = cosf(i * theta_per_segment);
984 sin_Ti = sinf(i * theta_per_segment);
985 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
986 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
987 count = 0;
988 }
989
990 // Update arc_target location
991 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
992 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
993 arc_target[this->plane_axis_2] += linear_per_segment;
994
995 // Append this segment to the queue
996 this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
997
998 }
999
1000 // Ensure last segment arrives at target location.
1001 this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute);
1002 }
1003
1004 // Do the math for an arc and add it to the queue
1005 void Robot::compute_arc(Gcode * gcode, const float offset[], const float target[])
1006 {
1007
1008 // Find the radius
1009 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
1010
1011 // Set clockwise/counter-clockwise sign for mc_arc computations
1012 bool is_clockwise = false;
1013 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
1014 is_clockwise = true;
1015 }
1016
1017 // Append arc
1018 this->append_arc(gcode, target, offset, radius, is_clockwise );
1019 }
1020
1021
1022 float Robot::theta(float x, float y)
1023 {
1024 float t = atanf(x / fabs(y));
1025 if (y > 0) {
1026 return(t);
1027 } else {
1028 if (t > 0) {
1029 return(M_PI - t);
1030 } else {
1031 return(-M_PI - t);
1032 }
1033 }
1034 }
1035
1036 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1037 {
1038 this->plane_axis_0 = axis_0;
1039 this->plane_axis_1 = axis_1;
1040 this->plane_axis_2 = axis_2;
1041 }
1042
1043 void Robot::clearToolOffset()
1044 {
1045 memset(this->toolOffset, 0, sizeof(this->toolOffset));
1046 }
1047
1048 void Robot::setToolOffset(const float offset[3])
1049 {
1050 memcpy(this->toolOffset, offset, sizeof(this->toolOffset));
1051 }
1052