major refactor.
[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->last_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 last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
258 Robot::wcs_t Robot::mcs2wcs(const float *pos) const
259 {
260 // FIXME this will be incorrect if there is a compensation transform in effect and pos is machine_position instead of last_transform
261 return std::make_tuple(
262 pos[X_AXIS] - std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset) - std::get<X_AXIS>(tool_offset),
263 pos[Y_AXIS] - std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset) - std::get<Y_AXIS>(tool_offset),
264 pos[Z_AXIS] - std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset) - std::get<Z_AXIS>(tool_offset)
265 );
266 }
267
268 //A GCode has been received
269 //See if the current Gcode line has some orders for us
270 void Robot::on_gcode_received(void *argument)
271 {
272 Gcode *gcode = static_cast<Gcode *>(argument);
273
274 this->motion_mode = -1;
275
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: { // G4 pause
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 if(THEKERNEL->is_halted()) return;
298 }
299 }
300 }
301 break;
302
303 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
304 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
305 size_t n = gcode->get_uint('P');
306 if(n == 0) n = current_wcs; // set current coordinate system
307 else --n;
308 if(n < k_max_wcs) {
309 float x, y, z;
310 std::tie(x, y, z) = wcs_offsets[n];
311 if(gcode->get_int('L') == 20) {
312 // this makes the current machine position (less compensation transform) the offset
313 if(gcode->has_letter('X')) { x = to_millimeters(gcode->get_value('X')) - last_milestone[X_AXIS]; }
314 if(gcode->has_letter('Y')) { x = to_millimeters(gcode->get_value('Y')) - last_milestone[Y_AXIS]; }
315 if(gcode->has_letter('Z')) { x = to_millimeters(gcode->get_value('Z')) - last_milestone[Z_AXIS]; }
316 } else {
317 // the value is the offset from machine zero
318 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
319 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
320 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
321 }
322 wcs_offsets[n] = wcs_t(x, y, z);
323 }
324 }
325 break;
326
327 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
328 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
329 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
330 case 20: this->inch_mode = true; break;
331 case 21: this->inch_mode = false; break;
332
333 case 54: case 55: case 56: case 57: case 58: case 59:
334 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
335 current_wcs = gcode->g - 54;
336 if(gcode->g == 59 && gcode->subcode > 0) {
337 current_wcs += gcode->subcode;
338 if(current_wcs >= k_max_wcs) current_wcs = k_max_wcs - 1;
339 }
340 break;
341
342 case 90: this->absolute_mode = true; break;
343 case 91: this->absolute_mode = false; break;
344
345 case 92: {
346 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
347 // reset G92 offsets to 0
348 g92_offset = wcs_t(0, 0, 0);
349
350 } else {
351 // standard setting of the g92 offsets, making current machine position whatever the coordinate arguments are
352 float x, y, z;
353 std::tie(x, y, z) = g92_offset;
354 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X')) - last_milestone[X_AXIS];
355 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y')) - last_milestone[Y_AXIS];
356 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z')) - last_milestone[Z_AXIS];
357 g92_offset = wcs_t(x, y, z);
358 }
359
360 return;
361 }
362 }
363
364 } else if( gcode->has_m) {
365 switch( gcode->m ) {
366 case 2: // M2 end of program
367 current_wcs = 0;
368 absolute_mode = true;
369 break;
370
371 case 92: // M92 - set steps per mm
372 if (gcode->has_letter('X'))
373 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
374 if (gcode->has_letter('Y'))
375 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
376 if (gcode->has_letter('Z'))
377 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
378 if (gcode->has_letter('F'))
379 seconds_per_minute = gcode->get_value('F');
380
381 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);
382 gcode->add_nl = true;
383 check_max_actuator_speeds();
384 return;
385
386 case 114: {
387 // this is a new way to do this (similar to how GRBL does it).
388 // it returns the realtime position based on the current step position of the actuators.
389 // this does require a FK to get a machine position from the actuator position
390 // and then invert all the transforms to get a workspace position from machine position
391 char buf[64];
392 int n = 0;
393 // current actuator position in mm
394 ActuatorCoordinates current_position{
395 actuators[X_AXIS]->get_current_position(),
396 actuators[Y_AXIS]->get_current_position(),
397 actuators[Z_AXIS]->get_current_position()
398 };
399
400 // get machine position from the actuator position using FK
401 float mpos[3];
402 arm_solution->actuator_to_cartesian(current_position, mpos);
403
404 if(gcode->subcode == 0) { // M114 print WCS
405 // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
406 wcs_t pos= mcs2wcs(mpos);
407 n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
408
409 } else if(gcode->subcode == 1) { // M114.1 print Machine coordinate system
410 n = snprintf(buf, sizeof(buf), "MPOS: X:%1.3f Y:%1.3f Z:%1.3f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
411
412 } else if(gcode->subcode == 2) { // M114.2 print actuator position
413 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]);
414
415 } else if(gcode->subcode == 3) { // M114.3 print last milestone (which should be the same as machine position if axis are not moving and no level compensation)
416 n = snprintf(buf, sizeof(buf), "LMS: X:%1.3f Y:%1.3f Z:%1.3f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
417
418 } else if(gcode->subcode == 4) { // M114.4 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
419 n = snprintf(buf, sizeof(buf), "LMCS: X:%1.3f Y:%1.3f Z:%1.3f", last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
420 }
421
422 if(n > 0)
423 gcode->txt_after_ok.append(buf, n);
424 }
425 return;
426
427 case 120: // push state
428 push_state();
429 break;
430
431 case 121: // pop state
432 pop_state();
433 break;
434
435 case 203: // M203 Set maximum feedrates in mm/sec
436 if (gcode->has_letter('X'))
437 this->max_speeds[X_AXIS] = gcode->get_value('X');
438 if (gcode->has_letter('Y'))
439 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
440 if (gcode->has_letter('Z'))
441 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
442 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
443 if (gcode->has_letter('A' + i))
444 actuators[i]->set_max_rate(gcode->get_value('A' + i));
445 }
446 check_max_actuator_speeds();
447
448 if(gcode->get_num_args() == 0) {
449 gcode->stream->printf("X:%g Y:%g Z:%g",
450 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
451 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
452 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
453 }
454 gcode->add_nl = true;
455 }
456 break;
457
458 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
459 if (gcode->has_letter('S')) {
460 float acc = gcode->get_value('S'); // mm/s^2
461 // enforce minimum
462 if (acc < 1.0F)
463 acc = 1.0F;
464 THEKERNEL->planner->acceleration = acc;
465 }
466 if (gcode->has_letter('Z')) {
467 float acc = gcode->get_value('Z'); // mm/s^2
468 // enforce positive
469 if (acc < 0.0F)
470 acc = 0.0F;
471 THEKERNEL->planner->z_acceleration = acc;
472 }
473 break;
474
475 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
476 if (gcode->has_letter('X')) {
477 float jd = gcode->get_value('X');
478 // enforce minimum
479 if (jd < 0.0F)
480 jd = 0.0F;
481 THEKERNEL->planner->junction_deviation = jd;
482 }
483 if (gcode->has_letter('Z')) {
484 float jd = gcode->get_value('Z');
485 // enforce minimum, -1 disables it and uses regular junction deviation
486 if (jd < -1.0F)
487 jd = -1.0F;
488 THEKERNEL->planner->z_junction_deviation = jd;
489 }
490 if (gcode->has_letter('S')) {
491 float mps = gcode->get_value('S');
492 // enforce minimum
493 if (mps < 0.0F)
494 mps = 0.0F;
495 THEKERNEL->planner->minimum_planner_speed = mps;
496 }
497 if (gcode->has_letter('Y')) {
498 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
499 }
500 break;
501
502 case 220: // M220 - speed override percentage
503 if (gcode->has_letter('S')) {
504 float factor = gcode->get_value('S');
505 // enforce minimum 10% speed
506 if (factor < 10.0F)
507 factor = 10.0F;
508 // enforce maximum 10x speed
509 if (factor > 1000.0F)
510 factor = 1000.0F;
511
512 seconds_per_minute = 6000.0F / factor;
513 } else {
514 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
515 }
516 break;
517
518 case 400: // wait until all moves are done up to this point
519 THEKERNEL->conveyor->wait_for_empty_queue();
520 break;
521
522 case 500: // M500 saves some volatile settings to config override file
523 case 503: { // M503 just prints the settings
524 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);
525 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
526 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);
527 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
528 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
529 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
530 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
531 }
532 gcode->stream->printf("\n");
533
534 // get or save any arm solution specific optional values
535 BaseSolution::arm_options_t options;
536 if(arm_solution->get_optional(options) && !options.empty()) {
537 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
538 for(auto &i : options) {
539 gcode->stream->printf(" %c%1.4f", i.first, i.second);
540 }
541 gcode->stream->printf("\n");
542 }
543
544 // save wcs_offsets and current_wcs
545 // TODO this may need to be done whenever they change to be compliant
546 gcode->stream->printf(";WCS settings\n");
547 gcode->stream->printf("G5%c", std::min(current_wcs, (uint8_t)5) + '4');
548 if(current_wcs >= 6) {
549 gcode->stream->printf(".%c\n", '1' + (current_wcs - 6));
550 } else {
551 gcode->stream->printf("\n");
552 }
553 int n = 1;
554 for(auto &i : wcs_offsets) {
555 //if(i != wcs_t(0, 0, 0)) {
556 float x, y, z;
557 std::tie(x, y, z) = i;
558 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f\n", n, x, y, z);
559 //}
560 ++n;
561 }
562 }
563
564 if(gcode->m == 503) {
565 // just print the G92 setting as it is not saved
566 // TODO linuxcnc does seem to save G92, so maybe we should here too
567 if(g92_offset != wcs_t(0, 0, 0)) {
568 float x, y, z;
569 std::tie(x, y, z) = g92_offset;
570 gcode->stream->printf("G92 X%f Y%f Z%f ; NOT SAVED\n", x, y, z);
571 }
572 }
573 break;
574
575 case 665: { // M665 set optional arm solution variables based on arm solution.
576 // the parameter args could be any letter each arm solution only accepts certain ones
577 BaseSolution::arm_options_t options = gcode->get_args();
578 options.erase('S'); // don't include the S
579 options.erase('U'); // don't include the U
580 if(options.size() > 0) {
581 // set the specified options
582 arm_solution->set_optional(options);
583 }
584 options.clear();
585 if(arm_solution->get_optional(options)) {
586 // foreach optional value
587 for(auto &i : options) {
588 // print all current values of supported options
589 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
590 gcode->add_nl = true;
591 }
592 }
593
594 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
595 this->delta_segments_per_second = gcode->get_value('S');
596 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
597
598 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
599 this->mm_per_line_segment = gcode->get_value('U');
600 this->delta_segments_per_second = 0;
601 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
602 }
603
604 break;
605 }
606 }
607 }
608
609 if( this->motion_mode >= 0) {
610 process_move(gcode);
611 }
612
613 next_command_is_MCS = false; // must be on same line as G0 or G1
614 }
615
616 void Robot::process_move(Gcode *gcode)
617 {
618 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
619 float param[3]{NAN, NAN, NAN};
620 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
621 char letter= 'X'+i;
622 if( gcode->has_letter(letter) ) {
623 param[i] = this->to_millimeters(gcode->get_value(letter));
624 }
625 }
626
627 float offset[3]{0,0,0};
628 for(char letter = 'I'; letter <= 'K'; letter++) {
629 if( gcode->has_letter(letter) ) {
630 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
631 }
632 }
633
634 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
635 float target[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
636 if(!next_command_is_MCS) {
637 if(this->absolute_mode) {
638 // apply wcs offsets and g92 offset and tool offset
639 if(!isnan(param[X_AXIS])) {
640 target[X_AXIS]= param[X_AXIS] + std::get<X_AXIS>(wcs_offsets[current_wcs]) - std::get<X_AXIS>(g92_offset) + std::get<X_AXIS>(tool_offset);
641 }
642
643 if(!isnan(param[Y_AXIS])) {
644 target[Y_AXIS]= param[Y_AXIS] + std::get<Y_AXIS>(wcs_offsets[current_wcs]) - std::get<Y_AXIS>(g92_offset) + std::get<Y_AXIS>(tool_offset);
645 }
646
647 if(!isnan(param[Z_AXIS])) {
648 target[Z_AXIS]= param[Z_AXIS] + std::get<Z_AXIS>(wcs_offsets[current_wcs]) - std::get<Z_AXIS>(g92_offset) + std::get<Z_AXIS>(tool_offset);
649 }
650
651 }else{
652 // they are deltas from the last_milestone if specified
653 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
654 if(!isnan(param[i])) target[i] = param[i] + last_milestone[i];
655 }
656 }
657
658 }else{
659 // already in machine coordinates, we do not add tool offset for that
660 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
661 if(!isnan(param[i])) target[i] = param[i];
662 }
663 }
664
665 if( gcode->has_letter('F') ) {
666 if( this->motion_mode == MOTION_MODE_SEEK )
667 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
668 else
669 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
670 }
671
672 bool moved= false;
673 //Perform any physical actions
674 switch(this->motion_mode) {
675 case MOTION_MODE_CANCEL:
676 break;
677 case MOTION_MODE_SEEK:
678 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute );
679 break;
680 case MOTION_MODE_LINEAR:
681 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute );
682 break;
683 case MOTION_MODE_CW_ARC:
684 case MOTION_MODE_CCW_ARC:
685 moved= this->compute_arc(gcode, offset, target );
686 break;
687 }
688
689 if(moved) {
690 // set last_milestone to the calculated target
691 memcpy(this->last_milestone, target, sizeof(this->last_milestone));
692 }
693 }
694
695 // We received a new gcode, and one of the functions
696 // determined the distance for that given gcode. So now we can attach this gcode to the right block
697 // and continue
698 void Robot::distance_in_gcode_is_known(Gcode * gcode)
699 {
700 //If the queue is empty, execute immediatly, otherwise attach to the last added block
701 THEKERNEL->conveyor->append_gcode(gcode);
702 }
703
704 // reset the machine position for all axis. Used for homing.
705 // NOTE this sets the last_milestone which is machine position before compensation transform
706 void Robot::reset_axis_position(float x, float y, float z)
707 {
708 last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
709 last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
710 last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
711
712 // calculate what the last_machine_position would be by applying compensation transform if enabled
713 // otherwise it is the same as last_milestone
714 if(compensationTransform) {
715 compensationTransform(last_machine_position);
716 }
717
718 // now set the actuator positions to match
719 ActuatorCoordinates actuator_pos;
720 arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
721 for (size_t i = 0; i < actuators.size(); i++)
722 actuators[i]->change_last_milestone(actuator_pos[i]);
723 }
724
725 // Reset the position for an axis (used in homing)
726 void Robot::reset_axis_position(float position, int axis)
727 {
728 last_milestone[axis] = position;
729 reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
730 }
731
732 // Use FK to find out where actuator is and reset to match
733 void Robot::reset_position_from_current_actuator_position()
734 {
735 ActuatorCoordinates actuator_pos;
736 for (size_t i = 0; i < actuators.size(); i++) {
737 actuator_pos[i] = actuators[i]->get_current_position();
738 }
739 arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
740 // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
741 memcpy(last_milestone, last_machine_position, sizeof last_milestone);
742
743 // now reset actuator correctly, NOTE this may lose a little precision
744 // NOTE I do not recall why this was necessary, maybe to correct for small errors in FK
745 // arm_solution->cartesian_to_actuator(last_machine_position, actuator_pos);
746 // for (size_t i = 0; i < actuators.size(); i++)
747 // actuators[i]->change_last_milestone(actuator_pos[i]);
748 }
749
750 // Convert target (in machine coordinates) from millimeters to steps, and append this to the planner
751 // target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
752 // all transforms and is what we actually convert to actuator positions
753 bool Robot::append_milestone(Gcode * gcode, const float target[], float rate_mm_s)
754 {
755 float deltas[3];
756 float unit_vec[3];
757 ActuatorCoordinates actuator_pos;
758 float transformed_target[3]; // adjust target for bed compensation and WCS offsets
759 float millimeters_of_travel;
760
761 // unity transform by default
762 memcpy(transformed_target, target, sizeof(transformed_target));
763
764 // check function pointer and call if set to transform the target to compensate for bed
765 if(compensationTransform) {
766 // some compensation strategies can transform XYZ, some just change Z
767 compensationTransform(transformed_target);
768 }
769
770 // find distance moved by each axis, use transformed target from the current machine position
771 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
772 deltas[axis] = transformed_target[axis] - last_machine_position[axis];
773 }
774
775 // Compute how long this move moves, so we can attach it to the block for later use
776 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
777
778 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
779 // 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
780 if(millimeters_of_travel < 0.00001F) return false;
781
782 // this is the machine position
783 memcpy(this->last_machine_position, transformed_target, sizeof(this->last_machine_position));
784
785 // find distance unit vector
786 for (int i = 0; i < 3; i++)
787 unit_vec[i] = deltas[i] / millimeters_of_travel;
788
789 // Do not move faster than the configured cartesian limits
790 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
791 if ( max_speeds[axis] > 0 ) {
792 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
793
794 if (axis_speed > max_speeds[axis])
795 rate_mm_s *= ( max_speeds[axis] / axis_speed );
796 }
797 }
798
799 // find actuator position given the machine position, use actual adjusted target
800 arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
801
802 float isecs = rate_mm_s / millimeters_of_travel;
803 // check per-actuator speed limits
804 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
805 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
806 if (actuator_rate > actuators[actuator]->get_max_rate()) {
807 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
808 isecs = rate_mm_s / millimeters_of_travel;
809 }
810 }
811
812 // Append the block to the planner
813 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
814
815 return true;
816 }
817
818 // Append a move to the queue ( cutting it into segments if needed )
819 bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s )
820 {
821 float last_target[]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
822
823 // Find out the distance for this move in MCS
824 // 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
825 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 ));
826
827 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
828 if( gcode->millimeters_of_travel < 0.00001F ) return false;
829
830 // Mark the gcode as having a known distance
831 this->distance_in_gcode_is_known( gcode );
832
833 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
834 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
835 // We also check we are not exceeding the E max_speed for the current extruder
836 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
837 // we need to ask it now passing in the relevant data.
838 // NOTE we need to do this before we segment the line (for deltas)
839 if(gcode->has_letter('E')) {
840 float data[2];
841 data[0] = gcode->get_value('E'); // E target (maybe absolute or relative)
842 data[1] = rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
843 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
844 rate_mm_s *= data[1];
845 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
846 }
847 }
848
849 // 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 like Deltas.
850 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
851 // 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
852 uint16_t segments;
853
854 if(this->delta_segments_per_second > 1.0F) {
855 // enabled if set to something > 1, it is set to 0.0 by default
856 // segment based on current speed and requested segments per second
857 // the faster the travel speed the fewer segments needed
858 // NOTE rate is mm/sec and we take into account any speed override
859 float seconds = gcode->millimeters_of_travel / rate_mm_s;
860 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
861 // TODO if we are only moving in Z on a delta we don't really need to segment at all
862
863 } else {
864 if(this->mm_per_line_segment == 0.0F) {
865 segments = 1; // don't split it up
866 } else {
867 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
868 }
869 }
870
871 bool moved= false;
872 if (segments > 1) {
873 // A vector to keep track of the endpoint of each segment
874 float segment_delta[3];
875 float segment_end[3];
876
877 // How far do we move each segment?
878 for (int i = X_AXIS; i <= Z_AXIS; i++)
879 segment_delta[i] = (target[i] - last_target[i]) / segments;
880
881 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
882 // We always add another point after this loop so we stop at segments-1, ie i < segments
883 for (int i = 1; i < segments; i++) {
884 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
885 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
886 segment_end[axis] = last_target[axis] + segment_delta[axis];
887
888 // Append the end of this segment to the queue
889 bool b= this->append_milestone(gcode, segment_end, rate_mm_s);
890 moved= moved || b;
891 }
892 }
893
894 // Append the end of this full move to the queue
895 if(this->append_milestone(gcode, target, rate_mm_s)) moved= true;
896
897 this->next_command_is_MCS = false; // always reset this
898
899 if(moved) {
900 // if adding these blocks didn't start executing, do that now
901 THEKERNEL->conveyor->ensure_running();
902 }
903
904 return moved;
905 }
906
907
908 // Append an arc to the queue ( cutting it into segments as needed )
909 bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
910 {
911
912 // Scary math
913 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
914 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
915 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
916 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
917 float r_axis1 = -offset[this->plane_axis_1];
918 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
919 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
920
921 // Patch from GRBL Firmware - Christoph Baumann 04072015
922 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
923 float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
924 if (is_clockwise) { // Correct atan2 output per direction
925 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2 * M_PI; }
926 } else {
927 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2 * M_PI; }
928 }
929
930 // Find the distance for this gcode
931 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
932
933 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
934 if( gcode->millimeters_of_travel < 0.00001F ) {
935 return false;
936 }
937
938 // Mark the gcode as having a known distance
939 this->distance_in_gcode_is_known( gcode );
940
941 // Figure out how many segments for this gcode
942 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
943
944 float theta_per_segment = angular_travel / segments;
945 float linear_per_segment = linear_travel / segments;
946
947 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
948 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
949 r_T = [cos(phi) -sin(phi);
950 sin(phi) cos(phi] * r ;
951 For arc generation, the center of the circle is the axis of rotation and the radius vector is
952 defined from the circle center to the initial position. Each line segment is formed by successive
953 vector rotations. This requires only two cos() and sin() computations to form the rotation
954 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
955 all float numbers are single precision on the Arduino. (True float precision will not have
956 round off issues for CNC applications.) Single precision error can accumulate to be greater than
957 tool precision in some cases. Therefore, arc path correction is implemented.
958
959 Small angle approximation may be used to reduce computation overhead further. This approximation
960 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
961 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
962 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
963 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
964 issue for CNC machines with the single precision Arduino calculations.
965 This approximation also allows mc_arc to immediately insert a line segment into the planner
966 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
967 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
968 This is important when there are successive arc motions.
969 */
970 // Vector rotation matrix values
971 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
972 float sin_T = theta_per_segment;
973
974 float arc_target[3];
975 float sin_Ti;
976 float cos_Ti;
977 float r_axisi;
978 uint16_t i;
979 int8_t count = 0;
980
981 // Initialize the linear axis
982 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
983
984 bool moved= false;
985 for (i = 1; i < segments; i++) { // Increment (segments-1)
986 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
987
988 if (count < this->arc_correction ) {
989 // Apply vector rotation matrix
990 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
991 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
992 r_axis1 = r_axisi;
993 count++;
994 } else {
995 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
996 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
997 cos_Ti = cosf(i * theta_per_segment);
998 sin_Ti = sinf(i * theta_per_segment);
999 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1000 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1001 count = 0;
1002 }
1003
1004 // Update arc_target location
1005 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1006 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1007 arc_target[this->plane_axis_2] += linear_per_segment;
1008
1009 // Append this segment to the queue
1010 bool b= this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
1011 moved= moved || b;
1012 }
1013
1014 // Ensure last segment arrives at target location.
1015 if(this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute)) moved= true;
1016
1017 return moved;
1018 }
1019
1020 // Do the math for an arc and add it to the queue
1021 bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[])
1022 {
1023
1024 // Find the radius
1025 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
1026
1027 // Set clockwise/counter-clockwise sign for mc_arc computations
1028 bool is_clockwise = false;
1029 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
1030 is_clockwise = true;
1031 }
1032
1033 // Append arc
1034 return this->append_arc(gcode, target, offset, radius, is_clockwise );
1035 }
1036
1037
1038 float Robot::theta(float x, float y)
1039 {
1040 float t = atanf(x / fabs(y));
1041 if (y > 0) {
1042 return(t);
1043 } else {
1044 if (t > 0) {
1045 return(M_PI - t);
1046 } else {
1047 return(-M_PI - t);
1048 }
1049 }
1050 }
1051
1052 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1053 {
1054 this->plane_axis_0 = axis_0;
1055 this->plane_axis_1 = axis_1;
1056 this->plane_axis_2 = axis_2;
1057 }
1058
1059 void Robot::clearToolOffset()
1060 {
1061 this->tool_offset= wcs_t(0,0,0);
1062 }
1063
1064 void Robot::setToolOffset(const float offset[3])
1065 {
1066 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
1067 }
1068