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