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