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