implement parsing of A B C parameters for multi axis, NOTE mutually exclusive with...
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
CommitLineData
df27a6a3 1/*
aab6cbba 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)
4cff3ded
AW
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.
df27a6a3 5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
4cff3ded
AW
6*/
7
8#include "libs/Module.h"
9#include "libs/Kernel.h"
5673fe39 10
29e809e0 11#include "Robot.h"
4cff3ded 12#include "Planner.h"
3fceb8eb 13#include "Conveyor.h"
5673fe39
MM
14#include "Pin.h"
15#include "StepperMotor.h"
16#include "Gcode.h"
5647f709 17#include "PublicDataRequest.h"
928467c0 18#include "PublicData.h"
4cff3ded
AW
19#include "arm_solutions/BaseSolution.h"
20#include "arm_solutions/CartesianSolution.h"
c41d6d95 21#include "arm_solutions/RotatableCartesianSolution.h"
2a06c415 22#include "arm_solutions/LinearDeltaSolution.h"
11a39396 23#include "arm_solutions/RotaryDeltaSolution.h"
bdaaa75d 24#include "arm_solutions/HBotSolution.h"
fff1e42d 25#include "arm_solutions/CoreXZSolution.h"
1217e470 26#include "arm_solutions/MorganSCARASolution.h"
61134a65 27#include "StepTicker.h"
7af0714f
JM
28#include "checksumm.h"
29#include "utils.h"
8d54c34c 30#include "ConfigValue.h"
5966b7d0 31#include "libs/StreamOutput.h"
dd0a7cfa 32#include "StreamOutputPool.h"
928467c0 33#include "ExtruderPublicAccess.h"
0ec2f63a 34#include "GcodeDispatch.h"
13ad7234 35#include "ActuatorCoordinates.h"
0ec2f63a 36
29e809e0
JM
37#include "mbed.h" // for us_ticker_read()
38#include "mri.h"
39
40#include <fastmath.h>
41#include <string>
42#include <algorithm>
43using std::string;
38bf9a1c 44
78d0e16a
MM
45#define default_seek_rate_checksum CHECKSUM("default_seek_rate")
46#define default_feed_rate_checksum CHECKSUM("default_feed_rate")
47#define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
48#define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
49#define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
83c6e067 50#define mm_max_arc_error_checksum CHECKSUM("mm_max_arc_error")
78d0e16a
MM
51#define arc_correction_checksum CHECKSUM("arc_correction")
52#define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
53#define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
54#define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
a3e1326a 55#define segment_z_moves_checksum CHECKSUM("segment_z_moves")
3aad33c7 56#define save_g92_checksum CHECKSUM("save_g92")
43424972
JM
57
58// arm solutions
78d0e16a
MM
59#define arm_solution_checksum CHECKSUM("arm_solution")
60#define cartesian_checksum CHECKSUM("cartesian")
61#define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
62#define rostock_checksum CHECKSUM("rostock")
2a06c415 63#define linear_delta_checksum CHECKSUM("linear_delta")
11a39396 64#define rotary_delta_checksum CHECKSUM("rotary_delta")
78d0e16a
MM
65#define delta_checksum CHECKSUM("delta")
66#define hbot_checksum CHECKSUM("hbot")
67#define corexy_checksum CHECKSUM("corexy")
fff1e42d 68#define corexz_checksum CHECKSUM("corexz")
78d0e16a 69#define kossel_checksum CHECKSUM("kossel")
1217e470 70#define morgan_checksum CHECKSUM("morgan")
78d0e16a 71
78d0e16a
MM
72// new-style actuator stuff
73#define actuator_checksum CHEKCSUM("actuator")
74
75#define step_pin_checksum CHECKSUM("step_pin")
76#define dir_pin_checksum CHEKCSUM("dir_pin")
77#define en_pin_checksum CHECKSUM("en_pin")
78
79#define steps_per_mm_checksum CHECKSUM("steps_per_mm")
df6a30f2 80#define max_rate_checksum CHECKSUM("max_rate")
29e809e0
JM
81#define acceleration_checksum CHECKSUM("acceleration")
82#define z_acceleration_checksum CHECKSUM("z_acceleration")
78d0e16a
MM
83
84#define alpha_checksum CHECKSUM("alpha")
85#define beta_checksum CHECKSUM("beta")
86#define gamma_checksum CHECKSUM("gamma")
87
29e809e0
JM
88#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7F // Float (radians)
89#define PI 3.14159265358979323846F // force to be float, do not use M_PI
5fa0c173 90
edac9072
AW
91// 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
92// It takes care of cutting arcs into segments, same thing for line that are too long
93
4710532a
JM
94Robot::Robot()
95{
a1b7e9f0 96 this->inch_mode = false;
0e8b102e 97 this->absolute_mode = true;
29e809e0 98 this->e_absolute_mode = true;
4cff3ded 99 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
29e809e0
JM
100 memset(this->last_milestone, 0, sizeof last_milestone);
101 memset(this->last_machine_position, 0, sizeof last_machine_position);
0b804a41 102 this->arm_solution = NULL;
da947c62 103 seconds_per_minute = 60.0F;
fae93525 104 this->clearToolOffset();
03b01bac 105 this->compensationTransform = nullptr;
121094a5 106 this->get_e_scale_fnc= nullptr;
03b01bac
JM
107 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
108 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
a6bbe768 109 this->next_command_is_MCS = false;
778093ce 110 this->disable_segmentation= false;
29e809e0 111 this->n_motors= 0;
4cff3ded
AW
112}
113
114//Called when the module has just been loaded
4710532a
JM
115void Robot::on_module_loaded()
116{
4cff3ded
AW
117 this->register_for_event(ON_GCODE_RECEIVED);
118
119 // Configuration
807b9b57 120 this->load_config();
da24d6ae
AW
121}
122
807b9b57
JM
123#define ACTUATOR_CHECKSUMS(X) { \
124 CHECKSUM(X "_step_pin"), \
125 CHECKSUM(X "_dir_pin"), \
126 CHECKSUM(X "_en_pin"), \
127 CHECKSUM(X "_steps_per_mm"), \
29e809e0
JM
128 CHECKSUM(X "_max_rate"), \
129 CHECKSUM(X "_acceleration") \
807b9b57 130}
5984acdf 131
807b9b57
JM
132void Robot::load_config()
133{
edac9072
AW
134 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
135 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
136 // To make adding those solution easier, they have their own, separate object.
5984acdf 137 // Here we read the config to find out which arm solution to use
0b804a41 138 if (this->arm_solution) delete this->arm_solution;
eda9facc 139 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
d149c730 140 // Note checksums are not const expressions when in debug mode, so don't use switch
98761c28 141 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
314ab8f7 142 this->arm_solution = new HBotSolution(THEKERNEL->config);
bdaaa75d 143
fff1e42d
JJ
144 } else if(solution_checksum == corexz_checksum) {
145 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
146
2a06c415
JM
147 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
148 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
73a4e3c0 149
4710532a 150 } else if(solution_checksum == rotatable_cartesian_checksum) {
314ab8f7 151 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
b73a756d 152
11a39396
JM
153 } else if(solution_checksum == rotary_delta_checksum) {
154 this->arm_solution = new RotaryDeltaSolution(THEKERNEL->config);
c52b8675 155
1217e470
QH
156 } else if(solution_checksum == morgan_checksum) {
157 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
158
4710532a 159 } else if(solution_checksum == cartesian_checksum) {
314ab8f7 160 this->arm_solution = new CartesianSolution(THEKERNEL->config);
73a4e3c0 161
4710532a 162 } else {
314ab8f7 163 this->arm_solution = new CartesianSolution(THEKERNEL->config);
d149c730 164 }
73a4e3c0 165
6b661ab3
DP
166 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
167 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
168 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
169 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
b259f517 170 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.0f)->as_number();
4d0f60a9 171 this->mm_max_arc_error = THEKERNEL->config->value(mm_max_arc_error_checksum )->by_default( 0.01f)->as_number();
6b661ab3 172 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
78d0e16a 173
29e809e0 174 // in mm/sec but specified in config as mm/min
6b661ab3
DP
175 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
176 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
177 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
feb204be 178
a3e1326a 179 this->segment_z_moves = THEKERNEL->config->value(segment_z_moves_checksum )->by_default(true)->as_bool();
3aad33c7 180 this->save_g92 = THEKERNEL->config->value(save_g92_checksum )->by_default(false)->as_bool();
a3e1326a 181
37b7b898 182 // Make our Primary XYZ StepperMotors, and potentially A B C
29e809e0
JM
183 uint16_t const checksums[][6] = {
184 ACTUATOR_CHECKSUMS("alpha"), // X
185 ACTUATOR_CHECKSUMS("beta"), // Y
186 ACTUATOR_CHECKSUMS("gamma"), // Z
37b7b898
JM
187 #if MAX_ROBOT_ACTUATORS > 3
188 ACTUATOR_CHECKSUMS("delta"), // A
189 #if MAX_ROBOT_ACTUATORS > 4
190 ACTUATOR_CHECKSUMS("epsilon"), // B
191 #if MAX_ROBOT_ACTUATORS > 5
192 ACTUATOR_CHECKSUMS("zeta") // C
193 #endif
194 #endif
195 #endif
807b9b57 196 };
807b9b57 197
29e809e0
JM
198 // default acceleration setting, can be overriden with newer per axis settings
199 this->default_acceleration= THEKERNEL->config->value(acceleration_checksum)->by_default(100.0F )->as_number(); // Acceleration is in mm/s^2
200
201 // make each motor
37b7b898 202 for (size_t a = 0; a < MAX_ROBOT_ACTUATORS; a++) {
807b9b57
JM
203 Pin pins[3]; //step, dir, enable
204 for (size_t i = 0; i < 3; i++) {
205 pins[i].from_string(THEKERNEL->config->value(checksums[a][i])->by_default("nc")->as_string())->as_output();
37b7b898
JM
206 if(!pins[i].connected()) {
207 if(i <= Z_AXIS) THEKERNEL->streams->printf("FATAL: motor %d is not defined in config\n", 'X'+a);
208 break; // if all pins are not defined then the axis is not defined (and axis need to be defined in contiguous order)
209 }
807b9b57 210 }
37b7b898 211
29e809e0
JM
212 StepperMotor *sm = new StepperMotor(pins[0], pins[1], pins[2]);
213 // register this motor (NB This must be 0,1,2) of the actuators array
214 uint8_t n= register_motor(sm);
215 if(n != a) {
216 // this is a fatal error
217 THEKERNEL->streams->printf("FATAL: motor %d does not match index %d\n", n, a);
218 __debugbreak();
219 }
78d0e16a 220
03b01bac 221 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
3702f300 222 actuators[a]->set_max_rate(THEKERNEL->config->value(checksums[a][4])->by_default(30000.0F)->as_number()/60.0F); // it is in mm/min and converted to mm/sec
29e809e0 223 actuators[a]->set_acceleration(THEKERNEL->config->value(checksums[a][5])->by_default(NAN)->as_number()); // mm/secs²
807b9b57 224 }
a84f0186 225
dd0a7cfa 226 check_max_actuator_speeds(); // check the configs are sane
df6a30f2 227
29e809e0
JM
228 // if we have not specified a z acceleration see if the legacy config was set
229 if(isnan(actuators[Z_AXIS]->get_acceleration())) {
230 float acc= THEKERNEL->config->value(z_acceleration_checksum)->by_default(NAN)->as_number(); // disabled by default
231 if(!isnan(acc)) {
232 actuators[Z_AXIS]->set_acceleration(acc);
233 }
234 }
235
975469ad
MM
236 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
237 // so the first move can be correct if homing is not performed
807b9b57 238 ActuatorCoordinates actuator_pos;
975469ad 239 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
29e809e0 240 for (size_t i = 0; i < n_motors; i++)
975469ad 241 actuators[i]->change_last_milestone(actuator_pos[i]);
5966b7d0
AT
242
243 //this->clearToolOffset();
4cff3ded
AW
244}
245
29e809e0
JM
246uint8_t Robot::register_motor(StepperMotor *motor)
247{
248 // register this motor with the step ticker
249 THEKERNEL->step_ticker->register_motor(motor);
250 if(n_motors >= k_max_actuators) {
251 // this is a fatal error
252 THEKERNEL->streams->printf("FATAL: too many motors, increase k_max_actuators\n");
253 __debugbreak();
254 }
2cd32d70
JM
255 actuators.push_back(motor);
256 return n_motors++;
29e809e0
JM
257}
258
212caccd
JM
259void Robot::push_state()
260{
03b01bac 261 bool am = this->absolute_mode;
29e809e0 262 bool em = this->e_absolute_mode;
03b01bac 263 bool im = this->inch_mode;
29e809e0 264 saved_state_t s(this->feed_rate, this->seek_rate, am, em, im, current_wcs);
212caccd
JM
265 state_stack.push(s);
266}
267
268void Robot::pop_state()
269{
03b01bac
JM
270 if(!state_stack.empty()) {
271 auto s = state_stack.top();
212caccd 272 state_stack.pop();
03b01bac
JM
273 this->feed_rate = std::get<0>(s);
274 this->seek_rate = std::get<1>(s);
275 this->absolute_mode = std::get<2>(s);
29e809e0
JM
276 this->e_absolute_mode = std::get<3>(s);
277 this->inch_mode = std::get<4>(s);
278 this->current_wcs = std::get<5>(s);
212caccd
JM
279 }
280}
281
34210908
JM
282std::vector<Robot::wcs_t> Robot::get_wcs_state() const
283{
284 std::vector<wcs_t> v;
0b8b81b6 285 v.push_back(wcs_t(current_wcs, MAX_WCS, 0));
34210908
JM
286 for(auto& i : wcs_offsets) {
287 v.push_back(i);
288 }
289 v.push_back(g92_offset);
290 v.push_back(tool_offset);
291 return v;
292}
293
e03f2747 294int Robot::print_position(uint8_t subcode, char *buf, size_t bufsize) const
2791c829
JM
295{
296 // M114.1 is a new way to do this (similar to how GRBL does it).
297 // it returns the realtime position based on the current step position of the actuators.
298 // this does require a FK to get a machine position from the actuator position
299 // and then invert all the transforms to get a workspace position from machine position
a3be54e3 300 // M114 just does it the old way uses last_milestone and does inversse transforms to get the requested position
2791c829 301 int n = 0;
e03f2747 302 if(subcode == 0) { // M114 print WCS
2791c829 303 wcs_t pos= mcs2wcs(last_milestone);
31c6c2c2 304 n = snprintf(buf, bufsize, "C: X:%1.4f Y:%1.4f Z:%1.4f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
2791c829 305
df4574e0 306 } else if(subcode == 4) { // M114.4 print last milestone (which should be the same as machine position if axis are not moving and no level compensation)
31c6c2c2 307 n = snprintf(buf, bufsize, "LMS: X:%1.4f Y:%1.4f Z:%1.4f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
2791c829 308
df4574e0 309 } else if(subcode == 5) { // M114.5 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
cf6b8fd1 310 n = snprintf(buf, bufsize, "LMP: X:%1.4f Y:%1.4f Z:%1.4f", last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
2791c829
JM
311
312 } else {
313 // get real time positions
314 // current actuator position in mm
315 ActuatorCoordinates current_position{
316 actuators[X_AXIS]->get_current_position(),
317 actuators[Y_AXIS]->get_current_position(),
318 actuators[Z_AXIS]->get_current_position()
319 };
320
321 // get machine position from the actuator position using FK
322 float mpos[3];
323 arm_solution->actuator_to_cartesian(current_position, mpos);
324
e03f2747 325 if(subcode == 1) { // M114.1 print realtime WCS
2791c829
JM
326 // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
327 wcs_t pos= mcs2wcs(mpos);
29e809e0 328 n = snprintf(buf, bufsize, "WPOS: X:%1.4f Y:%1.4f Z:%1.4f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
2791c829 329
df4574e0 330 } else if(subcode == 2) { // M114.2 print realtime Machine coordinate system
31c6c2c2 331 n = snprintf(buf, bufsize, "MPOS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
2791c829 332
df4574e0 333 } else if(subcode == 3) { // M114.3 print realtime actuator position
55783268 334 n = snprintf(buf, bufsize, "APOS: X:%1.4f Y:%1.4f Z:%1.4f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
2791c829
JM
335 }
336 }
e03f2747 337 return n;
2791c829
JM
338}
339
dc27139b 340// converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
31c6c2c2 341Robot::wcs_t Robot::mcs2wcs(const Robot::wcs_t& pos) const
dc27139b
JM
342{
343 return std::make_tuple(
31c6c2c2
JM
344 std::get<X_AXIS>(pos) - std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset) - std::get<X_AXIS>(tool_offset),
345 std::get<Y_AXIS>(pos) - std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset) - std::get<Y_AXIS>(tool_offset),
346 std::get<Z_AXIS>(pos) - std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset) - std::get<Z_AXIS>(tool_offset)
dc27139b
JM
347 );
348}
349
dd0a7cfa
JM
350// this does a sanity check that actuator speeds do not exceed steps rate capability
351// we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
352void Robot::check_max_actuator_speeds()
353{
29e809e0 354 for (size_t i = 0; i < n_motors; i++) {
807b9b57
JM
355 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
356 if (step_freq > THEKERNEL->base_stepping_frequency) {
357 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
ad6a77d1 358 THEKERNEL->streams->printf("WARNING: actuator %d rate exceeds base_stepping_frequency * ..._steps_per_mm: %f, setting to %f\n", i, step_freq, actuators[i]->get_max_rate());
807b9b57 359 }
dd0a7cfa
JM
360 }
361}
362
4cff3ded 363//A GCode has been received
edac9072 364//See if the current Gcode line has some orders for us
4710532a
JM
365void Robot::on_gcode_received(void *argument)
366{
367 Gcode *gcode = static_cast<Gcode *>(argument);
6bc4a00a 368
29e809e0 369 enum MOTION_MODE_T motion_mode= NONE;
4cff3ded 370
4710532a
JM
371 if( gcode->has_g) {
372 switch( gcode->g ) {
29e809e0
JM
373 case 0: motion_mode = SEEK; break;
374 case 1: motion_mode = LINEAR; break;
375 case 2: motion_mode = CW_ARC; break;
376 case 3: motion_mode = CCW_ARC; break;
c2f7c261 377 case 4: { // G4 pause
03b01bac 378 uint32_t delay_ms = 0;
c3df978d 379 if (gcode->has_letter('P')) {
03b01bac 380 delay_ms = gcode->get_int('P');
c3df978d
JM
381 }
382 if (gcode->has_letter('S')) {
383 delay_ms += gcode->get_int('S') * 1000;
384 }
03b01bac 385 if (delay_ms > 0) {
c3df978d 386 // drain queue
04782655 387 THEKERNEL->conveyor->wait_for_idle();
c3df978d 388 // wait for specified time
03b01bac
JM
389 uint32_t start = us_ticker_read(); // mbed call
390 while ((us_ticker_read() - start) < delay_ms * 1000) {
c3df978d 391 THEKERNEL->call_event(ON_IDLE, this);
c2f7c261 392 if(THEKERNEL->is_halted()) return;
c3df978d
JM
393 }
394 }
adba2978 395 }
6b661ab3 396 break;
807b9b57 397
a6bbe768 398 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
00e607c7 399 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
03b01bac
JM
400 size_t n = gcode->get_uint('P');
401 if(n == 0) n = current_wcs; // set current coordinate system
807b9b57 402 else --n;
0b8b81b6 403 if(n < MAX_WCS) {
807b9b57 404 float x, y, z;
03b01bac 405 std::tie(x, y, z) = wcs_offsets[n];
00e607c7 406 if(gcode->get_int('L') == 20) {
c2f7c261 407 // this makes the current machine position (less compensation transform) the offset
dc27139b
JM
408 // get current position in WCS
409 wcs_t pos= mcs2wcs(last_milestone);
410
411 if(gcode->has_letter('X')){
412 x -= to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
413 }
414
415 if(gcode->has_letter('Y')){
416 y -= to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
417 }
418 if(gcode->has_letter('Z')) {
419 z -= to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
420 }
421
a6bbe768 422 } else {
00e607c7
JM
423 // the value is the offset from machine zero
424 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
425 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
426 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
427 }
03b01bac 428 wcs_offsets[n] = wcs_t(x, y, z);
807b9b57
JM
429 }
430 }
431 break;
432
6e92ab91
JM
433 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
434 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
435 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
436 case 20: this->inch_mode = true; break;
437 case 21: this->inch_mode = false; break;
807b9b57
JM
438
439 case 54: case 55: case 56: case 57: case 58: case 59:
440 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
03b01bac 441 current_wcs = gcode->g - 54;
807b9b57
JM
442 if(gcode->g == 59 && gcode->subcode > 0) {
443 current_wcs += gcode->subcode;
0b8b81b6 444 if(current_wcs >= MAX_WCS) current_wcs = MAX_WCS - 1;
807b9b57
JM
445 }
446 break;
447
29e809e0
JM
448 case 90: this->absolute_mode = true; this->e_absolute_mode = true; break;
449 case 91: this->absolute_mode = false; this->e_absolute_mode = false; break;
807b9b57 450
0b804a41 451 case 92: {
a9e8c04b 452 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
03b01bac
JM
453 // reset G92 offsets to 0
454 g92_offset = wcs_t(0, 0, 0);
455
cee8bc1d
JM
456 } else if(gcode->subcode == 3) {
457 // initialize G92 to the specified values, only used for saving it with M500
458 float x= 0, y= 0, z= 0;
459 if(gcode->has_letter('X')) x= gcode->get_value('X');
460 if(gcode->has_letter('Y')) y= gcode->get_value('Y');
461 if(gcode->has_letter('Z')) z= gcode->get_value('Z');
462 g92_offset = wcs_t(x, y, z);
463
4710532a 464 } else {
61a3fa99 465 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
807b9b57 466 float x, y, z;
03b01bac 467 std::tie(x, y, z) = g92_offset;
61a3fa99
JM
468 // get current position in WCS
469 wcs_t pos= mcs2wcs(last_milestone);
470
471 // adjust g92 offset to make the current wpos == the value requested
472 if(gcode->has_letter('X')){
473 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
474 }
dc27139b
JM
475 if(gcode->has_letter('Y')){
476 y += to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
477 }
478 if(gcode->has_letter('Z')) {
479 z += to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
480 }
03b01bac 481 g92_offset = wcs_t(x, y, z);
6bc4a00a 482 }
a6bbe768 483
13ad7234
JM
484 #if MAX_ROBOT_ACTUATORS > 3
485 if(gcode->subcode == 0 && (gcode->has_letter('E') || gcode->get_num_args() == 0)){
486 // reset the E position, legacy for 3d Printers to be reprap compatible
487 // find the selected extruder
de2ee57c 488 // NOTE this will only work when E is 0 if volumetric and/or scaling is used as the actuator last milestone will be different if it was scaled
13ad7234
JM
489 for (int i = E_AXIS; i < n_motors; ++i) {
490 if(actuators[i]->is_selected()) {
491 float e= gcode->has_letter('E') ? gcode->get_value('E') : 0;
492 last_milestone[i]= last_machine_position[i]= e;
493 actuators[i]->change_last_milestone(e);
494 break;
495 }
496 }
497 }
498 #endif
499
78d0e16a 500 return;
4710532a
JM
501 }
502 }
67a649dd 503
4710532a
JM
504 } else if( gcode->has_m) {
505 switch( gcode->m ) {
20ed51b7
JM
506 // case 0: // M0 feed hold, (M0.1 is release feed hold, except we are in feed hold)
507 // if(THEKERNEL->is_grbl_mode()) THEKERNEL->set_feed_hold(gcode->subcode == 0);
508 // break;
01a8d21a
JM
509
510 case 30: // M30 end of program in grbl mode (otherwise it is delete sdcard file)
511 if(!THEKERNEL->is_grbl_mode()) break;
512 // fall through to M2
807b9b57 513 case 2: // M2 end of program
03b01bac
JM
514 current_wcs = 0;
515 absolute_mode = true;
807b9b57 516 break;
9e6014a6
JM
517 case 17:
518 THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
519 break;
520
521 case 18: // this used to support parameters, now it ignores them
522 case 84:
04782655 523 THEKERNEL->conveyor->wait_for_idle();
9e6014a6
JM
524 THEKERNEL->call_event(ON_ENABLE, nullptr); // turn all enable pins off
525 break;
807b9b57 526
29e809e0
JM
527 case 82: e_absolute_mode= true; break;
528 case 83: e_absolute_mode= false; break;
529
0fb5b438 530 case 92: // M92 - set steps per mm
0fb5b438 531 if (gcode->has_letter('X'))
78d0e16a 532 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
0fb5b438 533 if (gcode->has_letter('Y'))
78d0e16a 534 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
0fb5b438 535 if (gcode->has_letter('Z'))
78d0e16a 536 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
78d0e16a 537
ad6a77d1 538 gcode->stream->printf("X:%f Y:%f Z:%f ", actuators[0]->get_steps_per_mm(), actuators[1]->get_steps_per_mm(), actuators[2]->get_steps_per_mm());
0fb5b438 539 gcode->add_nl = true;
dd0a7cfa 540 check_max_actuator_speeds();
0fb5b438 541 return;
562db364 542
e03f2747
JM
543 case 114:{
544 char buf[64];
545 int n= print_position(gcode->subcode, buf, sizeof buf);
546 if(n > 0) gcode->txt_after_ok.append(buf, n);
2791c829 547 return;
e03f2747 548 }
33e4cc02 549
212caccd
JM
550 case 120: // push state
551 push_state();
552 break;
562db364
JM
553
554 case 121: // pop state
212caccd 555 pop_state();
562db364
JM
556 break;
557
125b71ce
JM
558 case 203: // M203 Set maximum feedrates in mm/sec, M203.1 set maximum actuator feedrates
559 if(gcode->get_num_args() == 0) {
560 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
55783268 561 gcode->stream->printf(" %c: %g ", 'X' + i, gcode->subcode == 0 ? this->max_speeds[i] : actuators[i]->get_max_rate());
125b71ce
JM
562 }
563 gcode->add_nl = true;
dd0a7cfa 564
125b71ce
JM
565 }else{
566 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
567 if (gcode->has_letter('X' + i)) {
55783268
JM
568 float v= gcode->get_value('X'+i);
569 if(gcode->subcode == 0) this->max_speeds[i]= v;
570 else if(gcode->subcode == 1) actuators[i]->set_max_rate(v);
125b71ce
JM
571 }
572 }
3e1ea0e2
JM
573
574 // this format is deprecated
575 if(gcode->subcode == 0 && (gcode->has_letter('A') || gcode->has_letter('B') || gcode->has_letter('C'))) {
576 gcode->stream->printf("NOTE this format is deprecated, Use M203.1 instead\n");
be7f67cd 577 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
3e1ea0e2
JM
578 if (gcode->has_letter('A' + i)) {
579 float v= gcode->get_value('A'+i);
580 actuators[i]->set_max_rate(v);
581 }
582 }
583 }
584
55783268 585 if(gcode->subcode == 1) check_max_actuator_speeds();
807b9b57 586 }
125b71ce 587 break;
83488642 588
29e809e0 589 case 204: // M204 Snnn - set default acceleration to nnn, Xnnn Ynnn Znnn sets axis specific acceleration
4710532a 590 if (gcode->has_letter('S')) {
4710532a 591 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 592 // enforce minimum
29e809e0
JM
593 if (acc < 1.0F) acc = 1.0F;
594 this->default_acceleration = acc;
d4ee6ee2 595 }
29e809e0
JM
596 for (int i = X_AXIS; i <= Z_AXIS; ++i) {
597 if (gcode->has_letter(i+'X')) {
598 float acc = gcode->get_value(i+'X'); // mm/s^2
599 // enforce positive
600 if (acc <= 0.0F) acc = NAN;
601 actuators[i]->set_acceleration(acc);
602 }
c5fe1787 603 }
d4ee6ee2
JM
604 break;
605
125b71ce 606 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed
4710532a
JM
607 if (gcode->has_letter('X')) {
608 float jd = gcode->get_value('X');
d4ee6ee2 609 // enforce minimum
8b69c90d
JM
610 if (jd < 0.0F)
611 jd = 0.0F;
4710532a 612 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 613 }
107df03f
JM
614 if (gcode->has_letter('Z')) {
615 float jd = gcode->get_value('Z');
616 // enforce minimum, -1 disables it and uses regular junction deviation
73a0eab6
JM
617 if (jd <= -1.0F)
618 jd = NAN;
107df03f
JM
619 THEKERNEL->planner->z_junction_deviation = jd;
620 }
4710532a
JM
621 if (gcode->has_letter('S')) {
622 float mps = gcode->get_value('S');
8b69c90d
JM
623 // enforce minimum
624 if (mps < 0.0F)
625 mps = 0.0F;
4710532a 626 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 627 }
d4ee6ee2 628 break;
98761c28 629
7369629d 630 case 220: // M220 - speed override percentage
4710532a 631 if (gcode->has_letter('S')) {
1ad23cd3 632 float factor = gcode->get_value('S');
98761c28 633 // enforce minimum 10% speed
da947c62
MM
634 if (factor < 10.0F)
635 factor = 10.0F;
636 // enforce maximum 10x speed
637 if (factor > 1000.0F)
638 factor = 1000.0F;
639
640 seconds_per_minute = 6000.0F / factor;
03b01bac 641 } else {
9ef9f45b 642 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 643 }
b4f56013 644 break;
ec4773e5 645
494dc541 646 case 400: // wait until all moves are done up to this point
04782655 647 THEKERNEL->conveyor->wait_for_idle();
494dc541
JM
648 break;
649
33e4cc02 650 case 500: // M500 saves some volatile settings to config override file
b7cd847e 651 case 503: { // M503 just prints the settings
ad6a77d1 652 gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", actuators[0]->get_steps_per_mm(), actuators[1]->get_steps_per_mm(), actuators[2]->get_steps_per_mm());
df56baf2
JM
653
654 // only print XYZ if not NAN
655 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f ", default_acceleration);
656 for (int i = X_AXIS; i <= Z_AXIS; ++i) {
657 if(!isnan(actuators[i]->get_acceleration())) gcode->stream->printf("%c%1.5f ", 'X'+i, actuators[i]->get_acceleration());
658 }
659 gcode->stream->printf("\n");
660
43fa8fd2 661 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, isnan(THEKERNEL->planner->z_junction_deviation)?-1:THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
125b71ce
JM
662
663 gcode->stream->printf(";Max cartesian feedrates in mm/sec:\nM203 X%1.5f Y%1.5f Z%1.5f\n", this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
664 gcode->stream->printf(";Max actuator feedrates in mm/sec:\nM203.1 X%1.5f Y%1.5f Z%1.5f\n", actuators[X_AXIS]->get_max_rate(), actuators[Y_AXIS]->get_max_rate(), actuators[Z_AXIS]->get_max_rate());
b7cd847e
JM
665
666 // get or save any arm solution specific optional values
667 BaseSolution::arm_options_t options;
668 if(arm_solution->get_optional(options) && !options.empty()) {
669 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 670 for(auto &i : options) {
b7cd847e
JM
671 gcode->stream->printf(" %c%1.4f", i.first, i.second);
672 }
673 gcode->stream->printf("\n");
674 }
6e92ab91 675
807b9b57
JM
676 // save wcs_offsets and current_wcs
677 // TODO this may need to be done whenever they change to be compliant
678 gcode->stream->printf(";WCS settings\n");
40fd5d98 679 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
03b01bac 680 int n = 1;
807b9b57 681 for(auto &i : wcs_offsets) {
2791c829 682 if(i != wcs_t(0, 0, 0)) {
807b9b57
JM
683 float x, y, z;
684 std::tie(x, y, z) = i;
40fd5d98 685 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f ; %s\n", n, x, y, z, wcs2gcode(n-1).c_str());
2791c829 686 }
807b9b57
JM
687 ++n;
688 }
3aad33c7
JM
689 if(save_g92) {
690 // linuxcnc saves G92, so we do too if configured, default is to not save to maintain backward compatibility
691 // also it needs to be used to set Z0 on rotary deltas as M206/306 can't be used, so saving it is necessary in that case
692 if(g92_offset != wcs_t(0, 0, 0)) {
693 float x, y, z;
694 std::tie(x, y, z) = g92_offset;
695 gcode->stream->printf("G92.3 X%f Y%f Z%f\n", x, y, z); // sets G92 to the specified values
696 }
67a649dd
JM
697 }
698 }
807b9b57 699 break;
33e4cc02 700
b7cd847e 701 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6 702 // the parameter args could be any letter each arm solution only accepts certain ones
03b01bac 703 BaseSolution::arm_options_t options = gcode->get_args();
ebc75fc6
JM
704 options.erase('S'); // don't include the S
705 options.erase('U'); // don't include the U
706 if(options.size() > 0) {
707 // set the specified options
708 arm_solution->set_optional(options);
709 }
710 options.clear();
b7cd847e 711 if(arm_solution->get_optional(options)) {
ebc75fc6 712 // foreach optional value
4710532a 713 for(auto &i : options) {
b7cd847e
JM
714 // print all current values of supported options
715 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 716 gcode->add_nl = true;
ec4773e5
JM
717 }
718 }
ec4773e5 719
4a839bea 720 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 721 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
722 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
723
03b01bac 724 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
4a839bea
JM
725 this->mm_per_line_segment = gcode->get_value('U');
726 this->delta_segments_per_second = 0;
727 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 728 }
4a839bea 729
ec4773e5 730 break;
b7cd847e 731 }
6989211c 732 }
494dc541
JM
733 }
734
29e809e0
JM
735 if( motion_mode != NONE) {
736 process_move(gcode, motion_mode);
00e607c7 737 }
6bc4a00a 738
c2f7c261
JM
739 next_command_is_MCS = false; // must be on same line as G0 or G1
740}
350c8a60 741
5d2319a9 742// process a G0/G1/G2/G3
29e809e0 743void Robot::process_move(Gcode *gcode, enum MOTION_MODE_T motion_mode)
c2f7c261 744{
2791c829 745 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
ad6a77d1 746 // get XYZ and one E (which goes to the selected extruder)
29e809e0
JM
747 float param[4]{NAN, NAN, NAN, NAN};
748
749 // process primary axis
350c8a60
JM
750 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
751 char letter= 'X'+i;
752 if( gcode->has_letter(letter) ) {
753 param[i] = this->to_millimeters(gcode->get_value(letter));
350c8a60
JM
754 }
755 }
6bc4a00a 756
c2f7c261 757 float offset[3]{0,0,0};
4710532a
JM
758 for(char letter = 'I'; letter <= 'K'; letter++) {
759 if( gcode->has_letter(letter) ) {
760 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
761 }
762 }
00e607c7 763
c2f7c261 764 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
29e809e0
JM
765 float target[n_motors];
766 memcpy(target, last_milestone, n_motors*sizeof(float));
767
350c8a60
JM
768 if(!next_command_is_MCS) {
769 if(this->absolute_mode) {
c2f7c261
JM
770 // apply wcs offsets and g92 offset and tool offset
771 if(!isnan(param[X_AXIS])) {
772 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);
773 }
774
775 if(!isnan(param[Y_AXIS])) {
776 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);
777 }
778
779 if(!isnan(param[Z_AXIS])) {
780 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);
781 }
350c8a60
JM
782
783 }else{
784 // they are deltas from the last_milestone if specified
785 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
c2f7c261 786 if(!isnan(param[i])) target[i] = param[i] + last_milestone[i];
a6bbe768
JM
787 }
788 }
789
350c8a60 790 }else{
c2f7c261
JM
791 // already in machine coordinates, we do not add tool offset for that
792 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
793 if(!isnan(param[i])) target[i] = param[i];
794 }
c2885de8 795 }
6bc4a00a 796
37b7b898
JM
797 float delta_e= NAN;
798
799 #if MAX_ROBOT_ACTUATORS > 3
29e809e0
JM
800 // process extruder parameters, for active extruder only (only one active extruder at a time)
801 selected_extruder= 0;
802 if(gcode->has_letter('E')) {
803 for (int i = E_AXIS; i < n_motors; ++i) {
804 // find first selected extruder
805 if(actuators[i]->is_selected()) {
806 param[E_AXIS]= gcode->get_value('E');
807 selected_extruder= i;
808 break;
809 }
810 }
811 }
812
813 // do E for the selected extruder
29e809e0
JM
814 if(selected_extruder > 0 && !isnan(param[E_AXIS])) {
815 if(this->e_absolute_mode) {
816 target[selected_extruder]= param[E_AXIS];
817 delta_e= target[selected_extruder] - last_milestone[selected_extruder];
818 }else{
819 delta_e= param[E_AXIS];
820 target[selected_extruder] = delta_e + last_milestone[selected_extruder];
821 }
822 }
823
37b7b898
JM
824 // process ABC axis, this is mutually exclusive to using E for an extruder, so if E is used and A then the results are undefined
825 for (int i = A_AXIS; i < n_motors; ++i) {
826 char letter= 'A'+i-A_AXIS;
827 if(gcode->has_letter(letter)) {
828 float p= gcode->get_value(letter);
829 if(this->absolute_mode) {
830 target[i]= p;
831 }else{
832 target[i]= p + last_milestone[i];
833 }
834 }
835 }
836 #endif
837
4710532a 838 if( gcode->has_letter('F') ) {
29e809e0 839 if( motion_mode == SEEK )
da947c62 840 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 841 else
da947c62 842 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 843 }
6bc4a00a 844
350c8a60 845 bool moved= false;
29e809e0
JM
846
847 // Perform any physical actions
848 switch(motion_mode) {
849 case NONE: break;
850
851 case SEEK:
852 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute, delta_e );
350c8a60 853 break;
29e809e0
JM
854
855 case LINEAR:
856 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute, delta_e );
350c8a60 857 break;
29e809e0
JM
858
859 case CW_ARC:
860 case CCW_ARC:
374d0777 861 // Note arcs are not currently supported by extruder based machines, as 3D slicers do not use arcs (G2/G3)
29e809e0 862 moved= this->compute_arc(gcode, offset, target, motion_mode);
350c8a60 863 break;
4cff3ded 864 }
13e4a3f9 865
c2f7c261
JM
866 if(moved) {
867 // set last_milestone to the calculated target
df56baf2 868 memcpy(last_milestone, target, n_motors*sizeof(float));
350c8a60 869 }
edac9072
AW
870}
871
a6bbe768 872// reset the machine position for all axis. Used for homing.
be7f67cd 873// During homing compensation is turned off
f6934849
JM
874// once homed and reset_axis called compensation is used for the move to origin and back off home if enabled,
875// so in those cases the final position is compensated.
cef9acea
JM
876void Robot::reset_axis_position(float x, float y, float z)
877{
f6934849 878 // these are set to the same as compensation was not used to get to the current position
c2f7c261
JM
879 last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
880 last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
881 last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
cef9acea 882
a6bbe768 883 // now set the actuator positions to match
807b9b57 884 ActuatorCoordinates actuator_pos;
c2f7c261 885 arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
29e809e0 886 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
cef9acea
JM
887 actuators[i]->change_last_milestone(actuator_pos[i]);
888}
889
de2ee57c 890// Reset the position for an axis (used in homing, and to reset extruder after suspend)
4710532a
JM
891void Robot::reset_axis_position(float position, int axis)
892{
c2f7c261 893 last_milestone[axis] = position;
de2ee57c
JM
894 if(axis <= Z_AXIS) {
895 reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
72420864 896#if MAX_ROBOT_ACTUATORS > 3
de2ee57c
JM
897 }else{
898 // extruders need to be set not calculated
899 last_machine_position[axis]= position;
72420864 900#endif
de2ee57c 901 }
4cff3ded
AW
902}
903
932a3995 904// similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
abf706e6 905// then sets the axis positions to match. currently only called from Endstops.cpp and RotaryDeltaCalibration.cpp
93f20a8c 906void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
586cc733 907{
29e809e0 908 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
93f20a8c 909 actuators[i]->change_last_milestone(ac[i]);
586cc733
JM
910
911 // now correct axis positions then recorrect actuator to account for rounding
912 reset_position_from_current_actuator_position();
913}
914
a6bbe768 915// Use FK to find out where actuator is and reset to match
728477c4
JM
916void Robot::reset_position_from_current_actuator_position()
917{
807b9b57 918 ActuatorCoordinates actuator_pos;
29e809e0 919 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
58587001 920 // NOTE actuator::current_position is curently NOT the same as actuator::last_milestone after an abrupt abort
807b9b57
JM
921 actuator_pos[i] = actuators[i]->get_current_position();
922 }
58587001
JM
923
924 // discover machine position from where actuators actually are
c2f7c261
JM
925 arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
926 // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
927 memcpy(last_milestone, last_machine_position, sizeof last_milestone);
cf91d4f3 928
58587001
JM
929 // now reset actuator::last_milestone, NOTE this may lose a little precision as FK is not always entirely accurate.
930 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
932a3995 931 // to get everything in perfect sync.
7baae81a 932 arm_solution->cartesian_to_actuator(last_machine_position, actuator_pos);
29e809e0 933 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
7baae81a 934 actuators[i]->change_last_milestone(actuator_pos[i]);
728477c4 935}
edac9072 936
ad6a77d1 937// Convert target (in machine coordinates) to machine_position, then convert to actuator position and append this to the planner
c2f7c261
JM
938// target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
939// all transforms and is what we actually convert to actuator positions
c1ebb1fe 940bool Robot::append_milestone(const float target[], float rate_mm_s)
df6a30f2 941{
29e809e0 942 float deltas[n_motors];
1a936198 943 float transformed_target[n_motors]; // adjust target for bed compensation
29e809e0 944 float unit_vec[N_PRIMARY_AXIS];
df6a30f2 945
3632a517 946 // unity transform by default
29e809e0 947 memcpy(transformed_target, target, n_motors*sizeof(float));
5e45206a 948
350c8a60 949 // check function pointer and call if set to transform the target to compensate for bed
c1ebb1fe 950 if(compensationTransform) {
350c8a60
JM
951 // some compensation strategies can transform XYZ, some just change Z
952 compensationTransform(transformed_target);
00e607c7 953 }
807b9b57 954
29e809e0 955 bool move= false;
850b4eeb 956 float sos= 0; // sun of squares for just XYZ
29e809e0 957
a6bbe768 958 // find distance moved by each axis, use transformed target from the current machine position
ec45206d 959 for (size_t i = 0; i < n_motors; i++) {
29e809e0
JM
960 deltas[i] = transformed_target[i] - last_machine_position[i];
961 if(deltas[i] == 0) continue;
962 // at least one non zero delta
963 move = true;
121094a5 964 if(i <= Z_AXIS) {
29e809e0 965 sos += powf(deltas[i], 2);
121094a5 966 }
3632a517 967 }
aab6cbba 968
29e809e0
JM
969 // nothing moved
970 if(!move) return false;
971
850b4eeb
JM
972 // see if this is a primary axis move or not
973 bool auxilliary_move= deltas[X_AXIS] == 0 && deltas[Y_AXIS] == 0 && deltas[Z_AXIS] == 0;
29e809e0 974
850b4eeb
JM
975 // total movement, use XYZ if a primary axis otherwise we calculate distance for E after scaling to mm
976 float distance= auxilliary_move ? 0 : sqrtf(sos);
df6a30f2 977
a6bbe768
JM
978 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
979 // 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
850b4eeb 980 if(!auxilliary_move && distance < 0.00001F) return false;
a6bbe768 981
a6bbe768 982
29e809e0 983 if(!auxilliary_move) {
850b4eeb 984 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
14a90ad5 985 // find distance unit vector for primary axis only
850b4eeb 986 unit_vec[i] = deltas[i] / distance;
df6a30f2 987
14a90ad5
JM
988 // Do not move faster than the configured cartesian limits for XYZ
989 if ( max_speeds[i] > 0 ) {
990 float axis_speed = fabsf(unit_vec[i] * rate_mm_s);
991
992 if (axis_speed > max_speeds[i])
993 rate_mm_s *= ( max_speeds[i] / axis_speed );
2cd32d70 994 }
7b470506
AW
995 }
996 }
4cff3ded 997
c2f7c261 998 // find actuator position given the machine position, use actual adjusted target
29e809e0 999 ActuatorCoordinates actuator_pos;
850b4eeb 1000 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
df6a30f2 1001
13ad7234 1002#if MAX_ROBOT_ACTUATORS > 3
850b4eeb 1003 sos= 0;
ad6a77d1 1004 // for the extruders just copy the position, and possibly scale it from mm³ to mm
374d0777 1005 for (size_t i = E_AXIS; i < n_motors; i++) {
850b4eeb 1006 actuator_pos[i]= transformed_target[i];
121094a5 1007 if(get_e_scale_fnc) {
29e809e0
JM
1008 // NOTE this relies on the fact only one extruder is active at a time
1009 // scale for volumetric or flow rate
1010 // TODO is this correct? scaling the absolute target? what if the scale changes?
ec45206d 1011 // for volumetric it basically converts mm³ to mm, but what about flow rate?
121094a5 1012 actuator_pos[i] *= get_e_scale_fnc();
29e809e0 1013 }
850b4eeb
JM
1014 if(auxilliary_move) {
1015 // for E only moves we need to use the scaled E to calculate the distance
1016 sos += pow(actuator_pos[i] - actuators[i]->get_last_milestone(), 2);
1017 }
1018 }
1019 if(auxilliary_move) {
850b4eeb 1020 distance= sqrtf(sos); // distance in mm of the e move
1843a68f 1021 if(distance < 0.00001F) return false;
29e809e0
JM
1022 }
1023#endif
1024
1025 // use default acceleration to start with
1026 float acceleration = default_acceleration;
1027
850b4eeb 1028 float isecs = rate_mm_s / distance;
29e809e0 1029
df6a30f2 1030 // check per-actuator speed limits
29e809e0
JM
1031 for (size_t actuator = 0; actuator < n_motors; actuator++) {
1032 float d = fabsf(actuator_pos[actuator] - actuators[actuator]->get_last_milestone());
1033 if(d == 0 || !actuators[actuator]->is_selected()) continue; // no movement for this actuator
1034
1035 float actuator_rate= d * isecs;
03b01bac 1036 if (actuator_rate > actuators[actuator]->get_max_rate()) {
3494f3d0 1037 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
850b4eeb 1038 isecs = rate_mm_s / distance;
928467c0 1039 }
29e809e0 1040
df56baf2 1041 // adjust acceleration to lowest found, for now just primary axis unless it is an auxiliary move
14a90ad5
JM
1042 // TODO we may need to do all of them, check E won't limit XYZ.. it does on long E moves, but not checking it could exceed the E acceleration.
1043 if(auxilliary_move || actuator <= Z_AXIS) {
df56baf2
JM
1044 float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
1045 if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
850b4eeb 1046 float ca = fabsf((d/distance) * acceleration);
df56baf2
JM
1047 if (ca > ma) {
1048 acceleration *= ( ma / ca );
1049 }
29e809e0 1050 }
14a90ad5 1051 }
928467c0
JM
1052 }
1053
edac9072 1054 // Append the block to the planner
850b4eeb 1055 // NOTE that distance here should be either the distance travelled by the XYZ axis, or the E mm travel if a solo E move
6f5d947f
JM
1056 if(THEKERNEL->planner->append_block( actuator_pos, n_motors, rate_mm_s, distance, auxilliary_move ? nullptr : unit_vec, acceleration )) {
1057 // this is the machine position
1058 memcpy(this->last_machine_position, transformed_target, n_motors*sizeof(float));
07879e05 1059 return true;
6f5d947f
JM
1060 }
1061
07879e05
JM
1062 // no actual move
1063 return false;
4cff3ded
AW
1064}
1065
121094a5
JM
1066// Used to plan a single move used by things like endstops when homing, zprobe, extruder firmware retracts etc.
1067bool Robot::delta_move(const float *delta, float rate_mm_s, uint8_t naxis)
c8bac202
JM
1068{
1069 if(THEKERNEL->is_halted()) return false;
1070
121094a5 1071 // catch negative or zero feed rates
c8bac202
JM
1072 if(rate_mm_s <= 0.0F) {
1073 return false;
1074 }
1075
121094a5
JM
1076 // get the absolute target position, default is current last_milestone
1077 float target[n_motors];
1078 memcpy(target, last_milestone, n_motors*sizeof(float));
c8bac202 1079
121094a5
JM
1080 // add in the deltas to get new target
1081 for (int i= 0; i < naxis; i++) {
1082 target[i] += delta[i];
c8bac202 1083 }
c8bac202 1084
121094a5 1085 // submit for planning and if moved update last_milestone
c1ebb1fe 1086 if(append_milestone(target, rate_mm_s)) {
121094a5
JM
1087 memcpy(last_milestone, target, n_motors*sizeof(float));
1088 return true;
29e809e0 1089 }
c8bac202 1090
121094a5 1091 return false;
c8bac202
JM
1092}
1093
edac9072 1094// Append a move to the queue ( cutting it into segments if needed )
29e809e0 1095bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s, float delta_e)
4710532a 1096{
121094a5
JM
1097 // catch negative or zero feed rates and return the same error as GRBL does
1098 if(rate_mm_s <= 0.0F) {
1099 gcode->is_error= true;
1100 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1101 return false;
1102 }
29e809e0
JM
1103
1104 // Find out the distance for this move in XYZ in MCS
1105 float 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 ));
1106
374d0777 1107 if(millimeters_of_travel < 0.00001F) {
121094a5
JM
1108 // we have no movement in XYZ, probably E only extrude or retract
1109 return this->append_milestone(target, rate_mm_s);
29e809e0
JM
1110 }
1111
1112 /*
374d0777
JM
1113 For extruders, we need to do some extra work...
1114 if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit.
1115 Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
1116 We ask Extruder to do all the work but we need to pass in the relevant data.
1117 NOTE we need to do this before we segment the line (for deltas)
1118 This also sets any scaling due to flow rate and volumetric if a G1
29e809e0
JM
1119 */
1120 if(!isnan(delta_e) && gcode->has_g && gcode->g == 1) {
1121 float data[2]= {delta_e, rate_mm_s / millimeters_of_travel};
d2adef5e 1122 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
29e809e0 1123 rate_mm_s *= data[1]; // adjust the feedrate
d2adef5e
JM
1124 }
1125 }
1126
c2f7c261 1127 // 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.
3b4b05b8
JM
1128 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
1129 // 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
4a0c8e14 1130 uint16_t segments;
5984acdf 1131
a3e1326a 1132 if(this->disable_segmentation || (!segment_z_moves && !gcode->has_letter('X') && !gcode->has_letter('Y'))) {
778093ce
JM
1133 segments= 1;
1134
1135 } else if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
1136 // enabled if set to something > 1, it is set to 0.0 by default
1137 // segment based on current speed and requested segments per second
1138 // the faster the travel speed the fewer segments needed
1139 // NOTE rate is mm/sec and we take into account any speed override
29e809e0 1140 float seconds = millimeters_of_travel / rate_mm_s;
9502f9d5 1141 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 1142 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 1143
4710532a
JM
1144 } else {
1145 if(this->mm_per_line_segment == 0.0F) {
1146 segments = 1; // don't split it up
1147 } else {
29e809e0 1148 segments = ceilf( millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
1149 }
1150 }
5984acdf 1151
350c8a60 1152 bool moved= false;
4710532a 1153 if (segments > 1) {
2ba859c9 1154 // A vector to keep track of the endpoint of each segment
29e809e0
JM
1155 float segment_delta[n_motors];
1156 float segment_end[n_motors];
1157 memcpy(segment_end, last_milestone, n_motors*sizeof(float));
2ba859c9
MM
1158
1159 // How far do we move each segment?
29e809e0 1160 for (int i = 0; i < n_motors; i++)
2791c829 1161 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
4cff3ded 1162
c8e0fb15
MM
1163 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
1164 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 1165 for (int i = 1; i < segments; i++) {
350c8a60 1166 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
29e809e0
JM
1167 for (int i = 0; i < n_motors; i++)
1168 segment_end[i] += segment_delta[i];
2ba859c9
MM
1169
1170 // Append the end of this segment to the queue
121094a5 1171 bool b= this->append_milestone(segment_end, rate_mm_s);
350c8a60 1172 moved= moved || b;
2ba859c9 1173 }
4cff3ded 1174 }
5984acdf
MM
1175
1176 // Append the end of this full move to the queue
121094a5 1177 if(this->append_milestone(target, rate_mm_s)) moved= true;
2134bcf2 1178
a6bbe768 1179 this->next_command_is_MCS = false; // always reset this
00e607c7 1180
350c8a60 1181 return moved;
4cff3ded
AW
1182}
1183
4cff3ded 1184
edac9072 1185// Append an arc to the queue ( cutting it into segments as needed )
350c8a60 1186bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
4710532a 1187{
121094a5
JM
1188 float rate_mm_s= this->feed_rate / seconds_per_minute;
1189 // catch negative or zero feed rates and return the same error as GRBL does
1190 if(rate_mm_s <= 0.0F) {
1191 gcode->is_error= true;
1192 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1193 return false;
1194 }
aab6cbba 1195
edac9072 1196 // Scary math
2ba859c9
MM
1197 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
1198 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
1199 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
1ad23cd3
MM
1200 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
1201 float r_axis1 = -offset[this->plane_axis_1];
1202 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
1203 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
aab6cbba 1204
51871fb8 1205 // Patch from GRBL Firmware - Christoph Baumann 04072015
aab6cbba 1206 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
fb4c9d09 1207 float angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
5fa0c173 1208 if (is_clockwise) { // Correct atan2 output per direction
29e809e0 1209 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= (2 * PI); }
5fa0c173 1210 } else {
29e809e0 1211 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += (2 * PI); }
4710532a 1212 }
aab6cbba 1213
edac9072 1214 // Find the distance for this gcode
29e809e0 1215 float millimeters_of_travel = hypotf(angular_travel * radius, fabsf(linear_travel));
436a2cd1 1216
edac9072 1217 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
29e809e0 1218 if( millimeters_of_travel < 0.00001F ) {
350c8a60 1219 return false;
4710532a 1220 }
5dcb2ff3 1221
83c6e067
RA
1222 // limit segments by maximum arc error
1223 float arc_segment = this->mm_per_arc_segment;
4d0f60a9 1224 if ((this->mm_max_arc_error > 0) && (2 * radius > this->mm_max_arc_error)) {
83c6e067
RA
1225 float min_err_segment = 2 * sqrtf((this->mm_max_arc_error * (2 * radius - this->mm_max_arc_error)));
1226 if (this->mm_per_arc_segment < min_err_segment) {
1227 arc_segment = min_err_segment;
1228 }
1229 }
5984acdf 1230 // Figure out how many segments for this gcode
29e809e0 1231 uint16_t segments = ceilf(millimeters_of_travel / arc_segment);
aab6cbba 1232
29e809e0 1233 //printf("Radius %f - Segment Length %f - Number of Segments %d\r\n",radius,arc_segment,segments); // Testing Purposes ONLY
4710532a
JM
1234 float theta_per_segment = angular_travel / segments;
1235 float linear_per_segment = linear_travel / segments;
aab6cbba
AW
1236
1237 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1238 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1239 r_T = [cos(phi) -sin(phi);
1240 sin(phi) cos(phi] * r ;
1241 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1242 defined from the circle center to the initial position. Each line segment is formed by successive
1243 vector rotations. This requires only two cos() and sin() computations to form the rotation
1244 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1ad23cd3 1245 all float numbers are single precision on the Arduino. (True float precision will not have
aab6cbba
AW
1246 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1247 tool precision in some cases. Therefore, arc path correction is implemented.
1248
1249 Small angle approximation may be used to reduce computation overhead further. This approximation
1250 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1251 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1252 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1253 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1254 issue for CNC machines with the single precision Arduino calculations.
1255 This approximation also allows mc_arc to immediately insert a line segment into the planner
1256 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1257 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1258 This is important when there are successive arc motions.
1259 */
1260 // Vector rotation matrix values
4710532a 1261 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1ad23cd3 1262 float sin_T = theta_per_segment;
aab6cbba 1263
1ad23cd3
MM
1264 float arc_target[3];
1265 float sin_Ti;
1266 float cos_Ti;
1267 float r_axisi;
aab6cbba
AW
1268 uint16_t i;
1269 int8_t count = 0;
1270
1271 // Initialize the linear axis
2ba859c9 1272 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
aab6cbba 1273
350c8a60 1274 bool moved= false;
4710532a 1275 for (i = 1; i < segments; i++) { // Increment (segments-1)
350c8a60 1276 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
aab6cbba 1277
b66fb830 1278 if (count < this->arc_correction ) {
4710532a
JM
1279 // Apply vector rotation matrix
1280 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1281 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1282 r_axis1 = r_axisi;
1283 count++;
aab6cbba 1284 } else {
4710532a
JM
1285 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1286 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1287 cos_Ti = cosf(i * theta_per_segment);
1288 sin_Ti = sinf(i * theta_per_segment);
1289 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1290 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1291 count = 0;
aab6cbba
AW
1292 }
1293
1294 // Update arc_target location
1295 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1296 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1297 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
1298
1299 // Append this segment to the queue
121094a5 1300 bool b= this->append_milestone(arc_target, rate_mm_s);
350c8a60 1301 moved= moved || b;
aab6cbba 1302 }
edac9072 1303
aab6cbba 1304 // Ensure last segment arrives at target location.
121094a5 1305 if(this->append_milestone(target, rate_mm_s)) moved= true;
350c8a60
JM
1306
1307 return moved;
aab6cbba
AW
1308}
1309
edac9072 1310// Do the math for an arc and add it to the queue
29e809e0 1311bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[], enum MOTION_MODE_T motion_mode)
4710532a 1312{
aab6cbba
AW
1313
1314 // Find the radius
13addf09 1315 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
1316
1317 // Set clockwise/counter-clockwise sign for mc_arc computations
1318 bool is_clockwise = false;
29e809e0 1319 if( motion_mode == CW_ARC ) {
4710532a
JM
1320 is_clockwise = true;
1321 }
aab6cbba
AW
1322
1323 // Append arc
350c8a60 1324 return this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
1325}
1326
1327
4710532a
JM
1328float Robot::theta(float x, float y)
1329{
1330 float t = atanf(x / fabs(y));
1331 if (y > 0) {
1332 return(t);
1333 } else {
1334 if (t > 0) {
29e809e0 1335 return(PI - t);
4710532a 1336 } else {
29e809e0 1337 return(-PI - t);
4710532a
JM
1338 }
1339 }
4cff3ded
AW
1340}
1341
4710532a
JM
1342void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1343{
4cff3ded
AW
1344 this->plane_axis_0 = axis_0;
1345 this->plane_axis_1 = axis_1;
1346 this->plane_axis_2 = axis_2;
1347}
1348
fae93525 1349void Robot::clearToolOffset()
4710532a 1350{
c2f7c261 1351 this->tool_offset= wcs_t(0,0,0);
fae93525
JM
1352}
1353
1354void Robot::setToolOffset(const float offset[3])
1355{
c2f7c261 1356 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
5966b7d0
AT
1357}
1358
0ec2f63a
JM
1359float Robot::get_feed_rate() const
1360{
1361 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1362}