Merge branch 'upstreamedge' into fix/version-makefile
[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 #include <string>
11 using std::string;
12 #include <math.h>
13 #include "Planner.h"
14 #include "Conveyor.h"
15 #include "Robot.h"
16 #include "libs/nuts_bolts.h"
17 #include "libs/Pin.h"
18 #include "libs/StepperMotor.h"
19 #include "../communication/utils/Gcode.h"
20 #include "PublicDataRequest.h"
21 #include "arm_solutions/BaseSolution.h"
22 #include "arm_solutions/CartesianSolution.h"
23 #include "arm_solutions/RotatableCartesianSolution.h"
24 #include "arm_solutions/RostockSolution.h"
25 #include "arm_solutions/HBotSolution.h"
26
27 // 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
28 // It takes care of cutting arcs into segments, same thing for line that are too long
29
30 Robot::Robot(){
31 this->inch_mode = false;
32 this->absolute_mode = true;
33 this->motion_mode = MOTION_MODE_SEEK;
34 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
35 clear_vector(this->current_position);
36 clear_vector(this->last_milestone);
37 this->arm_solution = NULL;
38 seconds_per_minute = 60.0;
39 }
40
41 //Called when the module has just been loaded
42 void Robot::on_module_loaded() {
43 register_for_event(ON_CONFIG_RELOAD);
44 this->register_for_event(ON_GCODE_RECEIVED);
45 this->register_for_event(ON_GET_PUBLIC_DATA);
46 this->register_for_event(ON_SET_PUBLIC_DATA);
47
48 // Configuration
49 this->on_config_reload(this);
50
51 // Make our 3 StepperMotors
52 this->alpha_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&alpha_step_pin,&alpha_dir_pin,&alpha_en_pin) );
53 this->beta_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&beta_step_pin, &beta_dir_pin, &beta_en_pin ) );
54 this->gamma_stepper_motor = this->kernel->step_ticker->add_stepper_motor( new StepperMotor(&gamma_step_pin,&gamma_dir_pin,&gamma_en_pin) );
55
56 }
57
58 void Robot::on_config_reload(void* argument){
59
60 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
61 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
62 // To make adding those solution easier, they have their own, separate object.
63 // Here we read the config to find out which arm solution to use
64 if (this->arm_solution) delete this->arm_solution;
65 int solution_checksum = get_checksum(this->kernel->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
66 // Note checksums are not const expressions when in debug mode, so don't use switch
67 if(solution_checksum == hbot_checksum) {
68 this->arm_solution = new HBotSolution(this->kernel->config);
69
70 }else if(solution_checksum == rostock_checksum) {
71 this->arm_solution = new RostockSolution(this->kernel->config);
72
73 }else if(solution_checksum == delta_checksum) {
74 // place holder for now
75 this->arm_solution = new RostockSolution(this->kernel->config);
76
77 }else if(solution_checksum == rotatable_cartesian_checksum) {
78 this->arm_solution = new RotatableCartesianSolution(this->kernel->config);
79
80 }else if(solution_checksum == cartesian_checksum) {
81 this->arm_solution = new CartesianSolution(this->kernel->config);
82
83 }else{
84 this->arm_solution = new CartesianSolution(this->kernel->config);
85 }
86
87
88 this->feed_rate = this->kernel->config->value(default_feed_rate_checksum )->by_default(100 )->as_number() / 60;
89 this->seek_rate = this->kernel->config->value(default_seek_rate_checksum )->by_default(100 )->as_number() / 60;
90 this->mm_per_line_segment = this->kernel->config->value(mm_per_line_segment_checksum )->by_default(0.0 )->as_number();
91 this->delta_segments_per_second = this->kernel->config->value(delta_segments_per_second_checksum )->by_default(0.0 )->as_number();
92 this->mm_per_arc_segment = this->kernel->config->value(mm_per_arc_segment_checksum )->by_default(0.5 )->as_number();
93 this->arc_correction = this->kernel->config->value(arc_correction_checksum )->by_default(5 )->as_number();
94 this->max_speeds[X_AXIS] = this->kernel->config->value(x_axis_max_speed_checksum )->by_default(60000 )->as_number();
95 this->max_speeds[Y_AXIS] = this->kernel->config->value(y_axis_max_speed_checksum )->by_default(60000 )->as_number();
96 this->max_speeds[Z_AXIS] = this->kernel->config->value(z_axis_max_speed_checksum )->by_default(300 )->as_number();
97 this->alpha_step_pin.from_string( this->kernel->config->value(alpha_step_pin_checksum )->by_default("2.0" )->as_string())->as_output();
98 this->alpha_dir_pin.from_string( this->kernel->config->value(alpha_dir_pin_checksum )->by_default("0.5" )->as_string())->as_output();
99 this->alpha_en_pin.from_string( this->kernel->config->value(alpha_en_pin_checksum )->by_default("0.4" )->as_string())->as_output();
100 this->beta_step_pin.from_string( this->kernel->config->value(beta_step_pin_checksum )->by_default("2.1" )->as_string())->as_output();
101 this->gamma_step_pin.from_string( this->kernel->config->value(gamma_step_pin_checksum )->by_default("2.2" )->as_string())->as_output();
102 this->gamma_dir_pin.from_string( this->kernel->config->value(gamma_dir_pin_checksum )->by_default("0.20" )->as_string())->as_output();
103 this->gamma_en_pin.from_string( this->kernel->config->value(gamma_en_pin_checksum )->by_default("0.19" )->as_string())->as_output();
104 this->beta_dir_pin.from_string( this->kernel->config->value(beta_dir_pin_checksum )->by_default("0.11" )->as_string())->as_output();
105 this->beta_en_pin.from_string( this->kernel->config->value(beta_en_pin_checksum )->by_default("0.10" )->as_string())->as_output();
106
107 }
108
109 void Robot::on_get_public_data(void* argument){
110 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
111
112 if(!pdr->starts_with(robot_checksum)) return;
113
114 if(pdr->second_element_is(speed_override_percent_checksum)) {
115 static double return_data= 100*60/seconds_per_minute;
116 pdr->set_data_ptr(&return_data);
117 pdr->set_taken();
118
119 }else if(pdr->second_element_is(current_position_checksum)) {
120 static double return_data[3];
121 return_data[0]= from_millimeters(this->current_position[0]);
122 return_data[1]= from_millimeters(this->current_position[1]);
123 return_data[2]= from_millimeters(this->current_position[2]);
124
125 pdr->set_data_ptr(&return_data);
126 pdr->set_taken();
127 }
128 }
129
130 void Robot::on_set_public_data(void* argument){
131 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
132
133 if(!pdr->starts_with(robot_checksum)) return;
134
135 if(pdr->second_element_is(speed_override_percent_checksum)) {
136 double t= *static_cast<double*>(pdr->get_data_ptr());
137 seconds_per_minute= t * 0.6;
138 pdr->set_taken();
139 }
140 }
141
142 //A GCode has been received
143 //See if the current Gcode line has some orders for us
144 void Robot::on_gcode_received(void * argument){
145 Gcode* gcode = static_cast<Gcode*>(argument);
146
147 //Temp variables, constant properties are stored in the object
148 uint8_t next_action = NEXT_ACTION_DEFAULT;
149 this->motion_mode = -1;
150
151 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
152 if( gcode->has_g){
153 switch( gcode->g ){
154 case 0: this->motion_mode = MOTION_MODE_SEEK; gcode->mark_as_taken(); break;
155 case 1: this->motion_mode = MOTION_MODE_LINEAR; gcode->mark_as_taken(); break;
156 case 2: this->motion_mode = MOTION_MODE_CW_ARC; gcode->mark_as_taken(); break;
157 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; gcode->mark_as_taken(); break;
158 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); gcode->mark_as_taken(); break;
159 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); gcode->mark_as_taken(); break;
160 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); gcode->mark_as_taken(); break;
161 case 20: this->inch_mode = true; gcode->mark_as_taken(); break;
162 case 21: this->inch_mode = false; gcode->mark_as_taken(); break;
163 case 90: this->absolute_mode = true; gcode->mark_as_taken(); break;
164 case 91: this->absolute_mode = false; gcode->mark_as_taken(); break;
165 case 92: {
166 if(gcode->get_num_args() == 0){
167 clear_vector(this->last_milestone);
168 }else{
169 for (char letter = 'X'; letter <= 'Z'; letter++){
170 if ( gcode->has_letter(letter) )
171 this->last_milestone[letter-'X'] = this->to_millimeters(gcode->get_value(letter));
172 }
173 }
174 memcpy(this->current_position, this->last_milestone, sizeof(double)*3); // current_position[] = last_milestone[];
175 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
176 gcode->mark_as_taken();
177 return; // TODO: Wait until queue empty
178 }
179 }
180 }else if( gcode->has_m){
181 switch( gcode->m ){
182 case 92: // M92 - set steps per mm
183 double steps[3];
184 this->arm_solution->get_steps_per_millimeter(steps);
185 if (gcode->has_letter('X'))
186 steps[0] = this->to_millimeters(gcode->get_value('X'));
187 if (gcode->has_letter('Y'))
188 steps[1] = this->to_millimeters(gcode->get_value('Y'));
189 if (gcode->has_letter('Z'))
190 steps[2] = this->to_millimeters(gcode->get_value('Z'));
191 if (gcode->has_letter('F'))
192 seconds_per_minute = gcode->get_value('F');
193 this->arm_solution->set_steps_per_millimeter(steps);
194 // update current position in steps
195 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
196 gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", steps[0], steps[1], steps[2], seconds_per_minute);
197 gcode->add_nl = true;
198 gcode->mark_as_taken();
199 return;
200 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f ",
201 from_millimeters(this->current_position[0]),
202 from_millimeters(this->current_position[1]),
203 from_millimeters(this->current_position[2]));
204 gcode->add_nl = true;
205 gcode->mark_as_taken();
206 return;
207 case 220: // M220 - speed override percentage
208 gcode->mark_as_taken();
209 if (gcode->has_letter('S'))
210 {
211 double factor = gcode->get_value('S');
212 // enforce minimum 1% speed
213 if (factor < 1.0)
214 factor = 1.0;
215 seconds_per_minute = factor * 0.6;
216 }
217 }
218 }
219 if( this->motion_mode < 0)
220 return;
221
222 //Get parameters
223 double target[3], offset[3];
224 clear_vector(target); clear_vector(offset);
225
226 memcpy(target, this->current_position, sizeof(target)); //default to last target
227
228 for(char letter = 'I'; letter <= 'K'; letter++){ if( gcode->has_letter(letter) ){ offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter)); } }
229 for(char letter = 'X'; letter <= 'Z'; letter++){ if( gcode->has_letter(letter) ){ target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']); } }
230
231 if( gcode->has_letter('F') )
232 {
233 if( this->motion_mode == MOTION_MODE_SEEK )
234 this->seek_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
235 else
236 this->feed_rate = this->to_millimeters( gcode->get_value('F') ) / 60.0;
237 }
238
239 //Perform any physical actions
240 switch( next_action ){
241 case NEXT_ACTION_DEFAULT:
242 switch(this->motion_mode){
243 case MOTION_MODE_CANCEL: break;
244 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate ); break;
245 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate ); break;
246 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
247 }
248 break;
249 }
250
251 // As far as the parser is concerned, the position is now == target. In reality the
252 // motion control system might still be processing the action and the real tool position
253 // in any intermediate location.
254 memcpy(this->current_position, target, sizeof(double)*3); // this->position[] = target[];
255
256 }
257
258 // We received a new gcode, and one of the functions
259 // determined the distance for that given gcode. So now we can attach this gcode to the right block
260 // and continue
261 void Robot::distance_in_gcode_is_known(Gcode* gcode){
262
263 //If the queue is empty, execute immediatly, otherwise attach to the last added block
264 if( this->kernel->conveyor->queue.size() == 0 ){
265 this->kernel->call_event(ON_GCODE_EXECUTE, gcode );
266 }else{
267 Block* block = this->kernel->conveyor->queue.get_ref( this->kernel->conveyor->queue.size() - 1 );
268 block->append_gcode(gcode);
269 }
270
271 }
272
273 // Reset the position for all axes ( used in homing and G92 stuff )
274 void Robot::reset_axis_position(double position, int axis) {
275 this->last_milestone[axis] = this->current_position[axis] = position;
276 this->arm_solution->millimeters_to_steps(this->current_position, this->kernel->planner->position);
277 }
278
279
280 // Convert target from millimeters to steps, and append this to the planner
281 void Robot::append_milestone( double target[], double rate ){
282 int steps[3]; //Holds the result of the conversion
283
284 // We use an arm solution object so exotic arm solutions can be used and neatly abstracted
285 this->arm_solution->millimeters_to_steps( target, steps );
286
287 double deltas[3];
288 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){deltas[axis]=target[axis]-this->last_milestone[axis];}
289
290 // Compute how long this move moves, so we can attach it to the block for later use
291 double millimeters_of_travel = sqrt( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
292
293 // Do not move faster than the configured limits
294 for(int axis=X_AXIS;axis<=Z_AXIS;axis++){
295 if( this->max_speeds[axis] > 0 ){
296 double axis_speed = ( fabs(deltas[axis]) / ( millimeters_of_travel / rate )) * seconds_per_minute;
297 if( axis_speed > this->max_speeds[axis] ){
298 rate = rate * ( this->max_speeds[axis] / axis_speed );
299 }
300 }
301 }
302
303 // Append the block to the planner
304 this->kernel->planner->append_block( steps, rate * seconds_per_minute, millimeters_of_travel, deltas );
305
306 // Update the last_milestone to the current target for the next time we use last_milestone
307 memcpy(this->last_milestone, target, sizeof(double)*3); // this->last_milestone[] = target[];
308
309 }
310
311 // Append a move to the queue ( cutting it into segments if needed )
312 void Robot::append_line(Gcode* gcode, double target[], double rate ){
313
314 // Find out the distance for this gcode
315 gcode->millimeters_of_travel = sqrt( pow( target[X_AXIS]-this->current_position[X_AXIS], 2 ) + pow( target[Y_AXIS]-this->current_position[Y_AXIS], 2 ) + pow( target[Z_AXIS]-this->current_position[Z_AXIS], 2 ) );
316
317 // We ignore non-moves ( for example, extruder moves are not XYZ moves )
318 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
319
320 // Mark the gcode as having a known distance
321 this->distance_in_gcode_is_known( gcode );
322
323 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
324 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
325 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second 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
326 uint16_t segments;
327
328 if(this->delta_segments_per_second > 1.0) {
329 // enabled if set to something > 1, it is set to 0.0 by default
330 // segment based on current speed and requested segments per second
331 // the faster the travel speed the fewer segments needed
332 // NOTE rate is mm/sec and we take into account any speed override
333 float seconds = 60.0/seconds_per_minute * gcode->millimeters_of_travel / rate;
334 segments= max(1, ceil(this->delta_segments_per_second * seconds));
335 // TODO if we are only moving in Z on a delta we don't really need to segment at all
336
337 }else{
338 if(this->mm_per_line_segment == 0.0){
339 segments= 1; // don't split it up
340 }else{
341 segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
342 }
343 }
344
345 // A vector to keep track of the endpoint of each segment
346 double temp_target[3];
347 //Initialize axes
348 memcpy( temp_target, this->current_position, sizeof(double)*3); // temp_target[] = this->current_position[];
349
350 //For each segment
351 for( int i=0; i<segments-1; i++ ){
352 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ ){ temp_target[axis] += ( target[axis]-this->current_position[axis] )/segments; }
353 // Append the end of this segment to the queue
354 this->append_milestone(temp_target, rate);
355 }
356
357 // Append the end of this full move to the queue
358 this->append_milestone(target, rate);
359 }
360
361
362 // Append an arc to the queue ( cutting it into segments as needed )
363 void Robot::append_arc(Gcode* gcode, double target[], double offset[], double radius, bool is_clockwise ){
364
365 // Scary math
366 double center_axis0 = this->current_position[this->plane_axis_0] + offset[this->plane_axis_0];
367 double center_axis1 = this->current_position[this->plane_axis_1] + offset[this->plane_axis_1];
368 double linear_travel = target[this->plane_axis_2] - this->current_position[this->plane_axis_2];
369 double r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
370 double r_axis1 = -offset[this->plane_axis_1];
371 double rt_axis0 = target[this->plane_axis_0] - center_axis0;
372 double rt_axis1 = target[this->plane_axis_1] - center_axis1;
373
374 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
375 double angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
376 if (angular_travel < 0) { angular_travel += 2*M_PI; }
377 if (is_clockwise) { angular_travel -= 2*M_PI; }
378
379 // Find the distance for this gcode
380 gcode->millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel));
381
382 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
383 if( gcode->millimeters_of_travel < 0.0001 ){ return; }
384
385 // Mark the gcode as having a known distance
386 this->distance_in_gcode_is_known( gcode );
387
388 // Figure out how many segments for this gcode
389 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
390
391 double theta_per_segment = angular_travel/segments;
392 double linear_per_segment = linear_travel/segments;
393
394 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
395 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
396 r_T = [cos(phi) -sin(phi);
397 sin(phi) cos(phi] * r ;
398 For arc generation, the center of the circle is the axis of rotation and the radius vector is
399 defined from the circle center to the initial position. Each line segment is formed by successive
400 vector rotations. This requires only two cos() and sin() computations to form the rotation
401 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
402 all double numbers are single precision on the Arduino. (True double precision will not have
403 round off issues for CNC applications.) Single precision error can accumulate to be greater than
404 tool precision in some cases. Therefore, arc path correction is implemented.
405
406 Small angle approximation may be used to reduce computation overhead further. This approximation
407 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
408 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
409 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
410 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
411 issue for CNC machines with the single precision Arduino calculations.
412 This approximation also allows mc_arc to immediately insert a line segment into the planner
413 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
414 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
415 This is important when there are successive arc motions.
416 */
417 // Vector rotation matrix values
418 double cos_T = 1-0.5*theta_per_segment*theta_per_segment; // Small angle approximation
419 double sin_T = theta_per_segment;
420
421 double arc_target[3];
422 double sin_Ti;
423 double cos_Ti;
424 double r_axisi;
425 uint16_t i;
426 int8_t count = 0;
427
428 // Initialize the linear axis
429 arc_target[this->plane_axis_2] = this->current_position[this->plane_axis_2];
430
431 for (i = 1; i<segments; i++) { // Increment (segments-1)
432
433 if (count < this->arc_correction ) {
434 // Apply vector rotation matrix
435 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
436 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
437 r_axis1 = r_axisi;
438 count++;
439 } else {
440 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
441 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
442 cos_Ti = cos(i*theta_per_segment);
443 sin_Ti = sin(i*theta_per_segment);
444 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
445 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
446 count = 0;
447 }
448
449 // Update arc_target location
450 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
451 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
452 arc_target[this->plane_axis_2] += linear_per_segment;
453
454 // Append this segment to the queue
455 this->append_milestone(arc_target, this->feed_rate);
456
457 }
458
459 // Ensure last segment arrives at target location.
460 this->append_milestone(target, this->feed_rate);
461 }
462
463 // Do the math for an arc and add it to the queue
464 void Robot::compute_arc(Gcode* gcode, double offset[], double target[]){
465
466 // Find the radius
467 double radius = hypot(offset[this->plane_axis_0], offset[this->plane_axis_1]);
468
469 // Set clockwise/counter-clockwise sign for mc_arc computations
470 bool is_clockwise = false;
471 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
472
473 // Append arc
474 this->append_arc(gcode, target, offset, radius, is_clockwise );
475
476 }
477
478
479 double Robot::theta(double x, double y){
480 double t = atan(x/fabs(y));
481 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
482 }
483
484 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
485 this->plane_axis_0 = axis_0;
486 this->plane_axis_1 = axis_1;
487 this->plane_axis_2 = axis_2;
488 }
489
490