Allow G92 A0 to change A axis, ditto for B C
[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"
7e9e31b0 36#include "EndstopsPublicAccess.h"
0ec2f63a 37
29e809e0
JM
38#include "mbed.h" // for us_ticker_read()
39#include "mri.h"
40
41#include <fastmath.h>
42#include <string>
43#include <algorithm>
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")
a01b9d3e 57#define save_g54_checksum CHECKSUM("save_g54")
39280f30 58#define set_g92_checksum CHECKSUM("set_g92")
43424972
JM
59
60// arm solutions
78d0e16a
MM
61#define arm_solution_checksum CHECKSUM("arm_solution")
62#define cartesian_checksum CHECKSUM("cartesian")
63#define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
64#define rostock_checksum CHECKSUM("rostock")
2a06c415 65#define linear_delta_checksum CHECKSUM("linear_delta")
11a39396 66#define rotary_delta_checksum CHECKSUM("rotary_delta")
78d0e16a
MM
67#define delta_checksum CHECKSUM("delta")
68#define hbot_checksum CHECKSUM("hbot")
69#define corexy_checksum CHECKSUM("corexy")
fff1e42d 70#define corexz_checksum CHECKSUM("corexz")
78d0e16a 71#define kossel_checksum CHECKSUM("kossel")
1217e470 72#define morgan_checksum CHECKSUM("morgan")
78d0e16a 73
78d0e16a
MM
74// new-style actuator stuff
75#define actuator_checksum CHEKCSUM("actuator")
76
77#define step_pin_checksum CHECKSUM("step_pin")
78#define dir_pin_checksum CHEKCSUM("dir_pin")
79#define en_pin_checksum CHECKSUM("en_pin")
80
d5262155 81#define max_speed_checksum CHECKSUM("max_speed")
29e809e0
JM
82#define acceleration_checksum CHECKSUM("acceleration")
83#define z_acceleration_checksum CHECKSUM("z_acceleration")
78d0e16a
MM
84
85#define alpha_checksum CHECKSUM("alpha")
86#define beta_checksum CHECKSUM("beta")
87#define gamma_checksum CHECKSUM("gamma")
88
507b2403
JM
89#define laser_module_default_power_checksum CHECKSUM("laser_module_default_power")
90
7e9e31b0
JM
91#define enable_checksum CHECKSUM("enable")
92#define halt_checksum CHECKSUM("halt")
93#define soft_endstop_checksum CHECKSUM("soft_endstop")
94#define xmin_checksum CHECKSUM("x_min")
95#define ymin_checksum CHECKSUM("y_min")
96#define zmin_checksum CHECKSUM("z_min")
97#define xmax_checksum CHECKSUM("x_max")
98#define ymax_checksum CHECKSUM("y_max")
99#define zmax_checksum CHECKSUM("z_max")
100
29e809e0 101#define PI 3.14159265358979323846F // force to be float, do not use M_PI
5fa0c173 102
edac9072
AW
103// The Robot converts GCodes into actual movements, and then adds them to the Planner, which passes them to the Conveyor so they can be added to the queue
104// It takes care of cutting arcs into segments, same thing for line that are too long
105
4710532a
JM
106Robot::Robot()
107{
a1b7e9f0 108 this->inch_mode = false;
0e8b102e 109 this->absolute_mode = true;
29e809e0 110 this->e_absolute_mode = true;
4cff3ded 111 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
45ca77ec
JM
112 memset(this->machine_position, 0, sizeof machine_position);
113 memset(this->compensated_machine_position, 0, sizeof compensated_machine_position);
0b804a41 114 this->arm_solution = NULL;
da947c62 115 seconds_per_minute = 60.0F;
fae93525 116 this->clearToolOffset();
03b01bac 117 this->compensationTransform = nullptr;
121094a5 118 this->get_e_scale_fnc= nullptr;
03b01bac
JM
119 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
120 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
a6bbe768 121 this->next_command_is_MCS = false;
778093ce 122 this->disable_segmentation= false;
84cf4071 123 this->disable_arm_solution= false;
29e809e0 124 this->n_motors= 0;
4cff3ded
AW
125}
126
127//Called when the module has just been loaded
4710532a
JM
128void Robot::on_module_loaded()
129{
4cff3ded
AW
130 this->register_for_event(ON_GCODE_RECEIVED);
131
132 // Configuration
807b9b57 133 this->load_config();
da24d6ae
AW
134}
135
807b9b57
JM
136#define ACTUATOR_CHECKSUMS(X) { \
137 CHECKSUM(X "_step_pin"), \
138 CHECKSUM(X "_dir_pin"), \
139 CHECKSUM(X "_en_pin"), \
140 CHECKSUM(X "_steps_per_mm"), \
29e809e0
JM
141 CHECKSUM(X "_max_rate"), \
142 CHECKSUM(X "_acceleration") \
807b9b57 143}
5984acdf 144
807b9b57
JM
145void Robot::load_config()
146{
edac9072
AW
147 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
148 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
149 // To make adding those solution easier, they have their own, separate object.
5984acdf 150 // Here we read the config to find out which arm solution to use
0b804a41 151 if (this->arm_solution) delete this->arm_solution;
eda9facc 152 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
d149c730 153 // Note checksums are not const expressions when in debug mode, so don't use switch
98761c28 154 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
314ab8f7 155 this->arm_solution = new HBotSolution(THEKERNEL->config);
bdaaa75d 156
fff1e42d
JJ
157 } else if(solution_checksum == corexz_checksum) {
158 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
159
2a06c415
JM
160 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
161 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
73a4e3c0 162
4710532a 163 } else if(solution_checksum == rotatable_cartesian_checksum) {
314ab8f7 164 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
b73a756d 165
11a39396
JM
166 } else if(solution_checksum == rotary_delta_checksum) {
167 this->arm_solution = new RotaryDeltaSolution(THEKERNEL->config);
c52b8675 168
1217e470
QH
169 } else if(solution_checksum == morgan_checksum) {
170 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
171
4710532a 172 } else if(solution_checksum == cartesian_checksum) {
314ab8f7 173 this->arm_solution = new CartesianSolution(THEKERNEL->config);
73a4e3c0 174
4710532a 175 } else {
314ab8f7 176 this->arm_solution = new CartesianSolution(THEKERNEL->config);
d149c730 177 }
73a4e3c0 178
6b661ab3
DP
179 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
180 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
181 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
182 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
b259f517 183 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.0f)->as_number();
4d0f60a9 184 this->mm_max_arc_error = THEKERNEL->config->value(mm_max_arc_error_checksum )->by_default( 0.01f)->as_number();
6b661ab3 185 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
78d0e16a 186
29e809e0 187 // in mm/sec but specified in config as mm/min
6b661ab3
DP
188 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
189 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
190 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
d5262155 191 this->max_speed = THEKERNEL->config->value(max_speed_checksum )->by_default( -60.0F)->as_number() / 60.0F;
feb204be 192
a3e1326a 193 this->segment_z_moves = THEKERNEL->config->value(segment_z_moves_checksum )->by_default(true)->as_bool();
3aad33c7 194 this->save_g92 = THEKERNEL->config->value(save_g92_checksum )->by_default(false)->as_bool();
a01b9d3e 195 this->save_g54 = THEKERNEL->config->value(save_g54_checksum )->by_default(THEKERNEL->is_grbl_mode())->as_bool();
39280f30
JM
196 string g92 = THEKERNEL->config->value(set_g92_checksum )->by_default("")->as_string();
197 if(!g92.empty()) {
198 // optional setting for a fixed G92 offset
199 std::vector<float> t= parse_number_list(g92.c_str());
200 if(t.size() == 3) {
201 g92_offset = wcs_t(t[0], t[1], t[2]);
202 }
203 }
a3e1326a 204
507b2403
JM
205 // default s value for laser
206 this->s_value = THEKERNEL->config->value(laser_module_default_power_checksum)->by_default(0.8F)->as_number();
207
319e12fc 208 // Make our Primary XYZ StepperMotors, and potentially A B C
7e9e31b0 209 uint16_t const motor_checksums[][6] = {
29e809e0
JM
210 ACTUATOR_CHECKSUMS("alpha"), // X
211 ACTUATOR_CHECKSUMS("beta"), // Y
212 ACTUATOR_CHECKSUMS("gamma"), // Z
37b7b898
JM
213 #if MAX_ROBOT_ACTUATORS > 3
214 ACTUATOR_CHECKSUMS("delta"), // A
215 #if MAX_ROBOT_ACTUATORS > 4
216 ACTUATOR_CHECKSUMS("epsilon"), // B
217 #if MAX_ROBOT_ACTUATORS > 5
218 ACTUATOR_CHECKSUMS("zeta") // C
219 #endif
220 #endif
221 #endif
807b9b57 222 };
807b9b57 223
29e809e0
JM
224 // default acceleration setting, can be overriden with newer per axis settings
225 this->default_acceleration= THEKERNEL->config->value(acceleration_checksum)->by_default(100.0F )->as_number(); // Acceleration is in mm/s^2
226
227 // make each motor
37b7b898 228 for (size_t a = 0; a < MAX_ROBOT_ACTUATORS; a++) {
807b9b57
JM
229 Pin pins[3]; //step, dir, enable
230 for (size_t i = 0; i < 3; i++) {
7e9e31b0 231 pins[i].from_string(THEKERNEL->config->value(motor_checksums[a][i])->by_default("nc")->as_string())->as_output();
96ef0809
JM
232 }
233
4d5d8050
JM
234 if(!pins[0].connected() || !pins[1].connected()) { // step and dir must be defined, but enable is optional
235 if(a <= Z_AXIS) {
236 THEKERNEL->streams->printf("FATAL: motor %c is not defined in config\n", 'X'+a);
237 n_motors= a; // we only have this number of motors
238 return;
239 }
96ef0809 240 break; // if any pin is not defined then the axis is not defined (and axis need to be defined in contiguous order)
807b9b57 241 }
37b7b898 242
29e809e0
JM
243 StepperMotor *sm = new StepperMotor(pins[0], pins[1], pins[2]);
244 // register this motor (NB This must be 0,1,2) of the actuators array
245 uint8_t n= register_motor(sm);
246 if(n != a) {
247 // this is a fatal error
248 THEKERNEL->streams->printf("FATAL: motor %d does not match index %d\n", n, a);
4d5d8050 249 return;
29e809e0 250 }
78d0e16a 251
7e9e31b0
JM
252 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(motor_checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
253 actuators[a]->set_max_rate(THEKERNEL->config->value(motor_checksums[a][4])->by_default(30000.0F)->as_number()/60.0F); // it is in mm/min and converted to mm/sec
254 actuators[a]->set_acceleration(THEKERNEL->config->value(motor_checksums[a][5])->by_default(NAN)->as_number()); // mm/secs²
807b9b57 255 }
a84f0186 256
dd0a7cfa 257 check_max_actuator_speeds(); // check the configs are sane
df6a30f2 258
29e809e0
JM
259 // if we have not specified a z acceleration see if the legacy config was set
260 if(isnan(actuators[Z_AXIS]->get_acceleration())) {
261 float acc= THEKERNEL->config->value(z_acceleration_checksum)->by_default(NAN)->as_number(); // disabled by default
262 if(!isnan(acc)) {
263 actuators[Z_AXIS]->set_acceleration(acc);
264 }
265 }
266
975469ad
MM
267 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
268 // so the first move can be correct if homing is not performed
807b9b57 269 ActuatorCoordinates actuator_pos;
45ca77ec 270 arm_solution->cartesian_to_actuator(machine_position, actuator_pos);
789e4104 271 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
975469ad 272 actuators[i]->change_last_milestone(actuator_pos[i]);
789e4104
JM
273 }
274
275 // initialize any extra axis to machine position
276 for (size_t i = A_AXIS; i < n_motors; i++) {
277 actuators[i]->change_last_milestone(machine_position[i]);
278 }
5966b7d0
AT
279
280 //this->clearToolOffset();
7e9e31b0
JM
281
282 soft_endstop_enabled= THEKERNEL->config->value(soft_endstop_checksum, enable_checksum)->by_default(false)->as_bool();
283 soft_endstop_halt= THEKERNEL->config->value(soft_endstop_checksum, halt_checksum)->by_default(true)->as_bool();
284
285 soft_endstop_min[X_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, xmin_checksum)->by_default(NAN)->as_number();
286 soft_endstop_min[Y_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, ymin_checksum)->by_default(NAN)->as_number();
287 soft_endstop_min[Z_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, zmin_checksum)->by_default(NAN)->as_number();
288 soft_endstop_max[X_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, xmax_checksum)->by_default(NAN)->as_number();
289 soft_endstop_max[Y_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, ymax_checksum)->by_default(NAN)->as_number();
290 soft_endstop_max[Z_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, zmax_checksum)->by_default(NAN)->as_number();
4cff3ded
AW
291}
292
29e809e0
JM
293uint8_t Robot::register_motor(StepperMotor *motor)
294{
295 // register this motor with the step ticker
296 THEKERNEL->step_ticker->register_motor(motor);
297 if(n_motors >= k_max_actuators) {
298 // this is a fatal error
299 THEKERNEL->streams->printf("FATAL: too many motors, increase k_max_actuators\n");
300 __debugbreak();
301 }
2cd32d70 302 actuators.push_back(motor);
8e30d648 303 motor->set_motor_id(n_motors);
2cd32d70 304 return n_motors++;
29e809e0
JM
305}
306
212caccd
JM
307void Robot::push_state()
308{
03b01bac 309 bool am = this->absolute_mode;
29e809e0 310 bool em = this->e_absolute_mode;
03b01bac 311 bool im = this->inch_mode;
29e809e0 312 saved_state_t s(this->feed_rate, this->seek_rate, am, em, im, current_wcs);
212caccd
JM
313 state_stack.push(s);
314}
315
316void Robot::pop_state()
317{
03b01bac
JM
318 if(!state_stack.empty()) {
319 auto s = state_stack.top();
212caccd 320 state_stack.pop();
03b01bac
JM
321 this->feed_rate = std::get<0>(s);
322 this->seek_rate = std::get<1>(s);
323 this->absolute_mode = std::get<2>(s);
29e809e0
JM
324 this->e_absolute_mode = std::get<3>(s);
325 this->inch_mode = std::get<4>(s);
326 this->current_wcs = std::get<5>(s);
212caccd
JM
327 }
328}
329
34210908
JM
330std::vector<Robot::wcs_t> Robot::get_wcs_state() const
331{
332 std::vector<wcs_t> v;
0b8b81b6 333 v.push_back(wcs_t(current_wcs, MAX_WCS, 0));
34210908
JM
334 for(auto& i : wcs_offsets) {
335 v.push_back(i);
336 }
337 v.push_back(g92_offset);
338 v.push_back(tool_offset);
339 return v;
340}
341
fdfa00d2
JM
342void Robot::get_current_machine_position(float *pos) const
343{
344 // get real time current actuator position in mm
345 ActuatorCoordinates current_position{
346 actuators[X_AXIS]->get_current_position(),
347 actuators[Y_AXIS]->get_current_position(),
348 actuators[Z_AXIS]->get_current_position()
349 };
350
351 // get machine position from the actuator position using FK
352 arm_solution->actuator_to_cartesian(current_position, pos);
353}
354
12ce413f 355void Robot::print_position(uint8_t subcode, std::string& res, bool ignore_extruders) const
2791c829
JM
356{
357 // M114.1 is a new way to do this (similar to how GRBL does it).
358 // it returns the realtime position based on the current step position of the actuators.
359 // this does require a FK to get a machine position from the actuator position
360 // and then invert all the transforms to get a workspace position from machine position
45ca77ec 361 // M114 just does it the old way uses machine_position and does inverse transforms to get the requested position
5fe7262c 362 uint32_t n = 0;
cb6bfefa 363 char buf[64];
e03f2747 364 if(subcode == 0) { // M114 print WCS
45ca77ec 365 wcs_t pos= mcs2wcs(machine_position);
cb6bfefa 366 n = snprintf(buf, sizeof(buf), "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 367
81c3ecbf
JM
368 } else if(subcode == 4) {
369 // M114.4 print last milestone
cb6bfefa 370 n = snprintf(buf, sizeof(buf), "MP: X:%1.4f Y:%1.4f Z:%1.4f", machine_position[X_AXIS], machine_position[Y_AXIS], machine_position[Z_AXIS]);
2791c829 371
81c3ecbf
JM
372 } else if(subcode == 5) {
373 // M114.5 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
374 // will differ from LMS by the compensation at the current position otherwise
cb6bfefa 375 n = snprintf(buf, sizeof(buf), "CMP: X:%1.4f Y:%1.4f Z:%1.4f", compensated_machine_position[X_AXIS], compensated_machine_position[Y_AXIS], compensated_machine_position[Z_AXIS]);
2791c829
JM
376
377 } else {
378 // get real time positions
2791c829 379 float mpos[3];
fdfa00d2
JM
380 get_current_machine_position(mpos);
381
8fe38353
JM
382 // current_position/mpos includes the compensation transform so we need to get the inverse to get actual position
383 if(compensationTransform) compensationTransform(mpos, true); // get inverse compensation transform
2791c829 384
e03f2747 385 if(subcode == 1) { // M114.1 print realtime WCS
2791c829 386 wcs_t pos= mcs2wcs(mpos);
cb6bfefa 387 n = snprintf(buf, sizeof(buf), "WCS: 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 388
df4574e0 389 } else if(subcode == 2) { // M114.2 print realtime Machine coordinate system
cb6bfefa 390 n = snprintf(buf, sizeof(buf), "MCS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
2791c829 391
df4574e0 392 } else if(subcode == 3) { // M114.3 print realtime actuator position
fdfa00d2
JM
393 // get real time current actuator position in mm
394 ActuatorCoordinates current_position{
395 actuators[X_AXIS]->get_current_position(),
396 actuators[Y_AXIS]->get_current_position(),
397 actuators[Z_AXIS]->get_current_position()
398 };
cb6bfefa 399 n = snprintf(buf, sizeof(buf), "APOS: X:%1.4f Y:%1.4f Z:%1.4f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
2791c829
JM
400 }
401 }
96ef0809 402
5fe7262c 403 if(n > sizeof(buf)) n= sizeof(buf);
cb6bfefa
JM
404 res.append(buf, n);
405
96ef0809
JM
406 #if MAX_ROBOT_ACTUATORS > 3
407 // deal with the ABC axis
408 for (int i = A_AXIS; i < n_motors; ++i) {
cb6bfefa 409 n= 0;
12ce413f 410 if(ignore_extruders && actuators[i]->is_extruder()) continue; // don't show an extruder as that will be E
96ef0809 411 if(subcode == 4) { // M114.4 print last milestone
cb6bfefa 412 n= snprintf(buf, sizeof(buf), " %c:%1.4f", 'A'+i-A_AXIS, machine_position[i]);
96ef0809 413
866eaaba 414 }else if(subcode == 2 || subcode == 3) { // M114.2/M114.3 print actuator position which is the same as machine position for ABC
96ef0809 415 // current actuator position
cb6bfefa 416 n= snprintf(buf, sizeof(buf), " %c:%1.4f", 'A'+i-A_AXIS, actuators[i]->get_current_position());
96ef0809 417 }
5fe7262c 418 if(n > sizeof(buf)) n= sizeof(buf);
cb6bfefa 419 if(n > 0) res.append(buf, n);
96ef0809
JM
420 }
421 #endif
2791c829
JM
422}
423
dc27139b 424// converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
31c6c2c2 425Robot::wcs_t Robot::mcs2wcs(const Robot::wcs_t& pos) const
dc27139b
JM
426{
427 return std::make_tuple(
31c6c2c2
JM
428 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),
429 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),
430 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
431 );
432}
433
dd0a7cfa
JM
434// this does a sanity check that actuator speeds do not exceed steps rate capability
435// we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
436void Robot::check_max_actuator_speeds()
437{
29e809e0 438 for (size_t i = 0; i < n_motors; i++) {
6a5a91e0
JM
439 if(actuators[i]->is_extruder()) continue; //extruders are not included in this check
440
807b9b57
JM
441 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
442 if (step_freq > THEKERNEL->base_stepping_frequency) {
443 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
ad6a77d1 444 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 445 }
dd0a7cfa
JM
446 }
447}
448
4cff3ded 449//A GCode has been received
edac9072 450//See if the current Gcode line has some orders for us
4710532a
JM
451void Robot::on_gcode_received(void *argument)
452{
453 Gcode *gcode = static_cast<Gcode *>(argument);
6bc4a00a 454
29e809e0 455 enum MOTION_MODE_T motion_mode= NONE;
4cff3ded 456
4710532a
JM
457 if( gcode->has_g) {
458 switch( gcode->g ) {
29e809e0
JM
459 case 0: motion_mode = SEEK; break;
460 case 1: motion_mode = LINEAR; break;
461 case 2: motion_mode = CW_ARC; break;
462 case 3: motion_mode = CCW_ARC; break;
df0783b7 463 case 4: { // G4 Dwell
03b01bac 464 uint32_t delay_ms = 0;
c3df978d 465 if (gcode->has_letter('P')) {
df0783b7
JM
466 if(THEKERNEL->is_grbl_mode()) {
467 // in grbl mode (and linuxcnc) P is decimal seconds
468 float f= gcode->get_value('P');
469 delay_ms= f * 1000.0F;
470
471 }else{
472 // in reprap P is milliseconds, they always have to be different!
473 delay_ms = gcode->get_int('P');
474 }
c3df978d
JM
475 }
476 if (gcode->has_letter('S')) {
477 delay_ms += gcode->get_int('S') * 1000;
478 }
03b01bac 479 if (delay_ms > 0) {
c3df978d 480 // drain queue
04782655 481 THEKERNEL->conveyor->wait_for_idle();
c3df978d 482 // wait for specified time
03b01bac
JM
483 uint32_t start = us_ticker_read(); // mbed call
484 while ((us_ticker_read() - start) < delay_ms * 1000) {
c3df978d 485 THEKERNEL->call_event(ON_IDLE, this);
c2f7c261 486 if(THEKERNEL->is_halted()) return;
c3df978d
JM
487 }
488 }
adba2978 489 }
6b661ab3 490 break;
807b9b57 491
a6bbe768 492 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
00e607c7 493 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
03b01bac
JM
494 size_t n = gcode->get_uint('P');
495 if(n == 0) n = current_wcs; // set current coordinate system
807b9b57 496 else --n;
0b8b81b6 497 if(n < MAX_WCS) {
807b9b57 498 float x, y, z;
03b01bac 499 std::tie(x, y, z) = wcs_offsets[n];
00e607c7 500 if(gcode->get_int('L') == 20) {
c2f7c261 501 // this makes the current machine position (less compensation transform) the offset
dc27139b 502 // get current position in WCS
45ca77ec 503 wcs_t pos= mcs2wcs(machine_position);
dc27139b
JM
504
505 if(gcode->has_letter('X')){
506 x -= to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
507 }
508
509 if(gcode->has_letter('Y')){
510 y -= to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
511 }
512 if(gcode->has_letter('Z')) {
513 z -= to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
514 }
515
a6bbe768 516 } else {
bb4e919a
JM
517 if(absolute_mode) {
518 // the value is the offset from machine zero
519 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
520 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
521 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
522 }else{
523 if(gcode->has_letter('X')) x += to_millimeters(gcode->get_value('X'));
524 if(gcode->has_letter('Y')) y += to_millimeters(gcode->get_value('Y'));
525 if(gcode->has_letter('Z')) z += to_millimeters(gcode->get_value('Z'));
526 }
00e607c7 527 }
03b01bac 528 wcs_offsets[n] = wcs_t(x, y, z);
807b9b57
JM
529 }
530 }
531 break;
532
6e92ab91
JM
533 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
534 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
535 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
536 case 20: this->inch_mode = true; break;
537 case 21: this->inch_mode = false; break;
807b9b57
JM
538
539 case 54: case 55: case 56: case 57: case 58: case 59:
540 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
03b01bac 541 current_wcs = gcode->g - 54;
807b9b57
JM
542 if(gcode->g == 59 && gcode->subcode > 0) {
543 current_wcs += gcode->subcode;
0b8b81b6 544 if(current_wcs >= MAX_WCS) current_wcs = MAX_WCS - 1;
807b9b57
JM
545 }
546 break;
547
29e809e0
JM
548 case 90: this->absolute_mode = true; this->e_absolute_mode = true; break;
549 case 91: this->absolute_mode = false; this->e_absolute_mode = false; break;
807b9b57 550
0b804a41 551 case 92: {
a9e8c04b 552 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
03b01bac
JM
553 // reset G92 offsets to 0
554 g92_offset = wcs_t(0, 0, 0);
555
cee8bc1d
JM
556 } else if(gcode->subcode == 3) {
557 // initialize G92 to the specified values, only used for saving it with M500
558 float x= 0, y= 0, z= 0;
559 if(gcode->has_letter('X')) x= gcode->get_value('X');
560 if(gcode->has_letter('Y')) y= gcode->get_value('Y');
561 if(gcode->has_letter('Z')) z= gcode->get_value('Z');
562 g92_offset = wcs_t(x, y, z);
563
4710532a 564 } else {
61a3fa99 565 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
807b9b57 566 float x, y, z;
03b01bac 567 std::tie(x, y, z) = g92_offset;
61a3fa99 568 // get current position in WCS
45ca77ec 569 wcs_t pos= mcs2wcs(machine_position);
61a3fa99
JM
570
571 // adjust g92 offset to make the current wpos == the value requested
572 if(gcode->has_letter('X')){
573 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
574 }
dc27139b
JM
575 if(gcode->has_letter('Y')){
576 y += to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
577 }
578 if(gcode->has_letter('Z')) {
579 z += to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
580 }
03b01bac 581 g92_offset = wcs_t(x, y, z);
6bc4a00a 582 }
a6bbe768 583
13ad7234
JM
584 #if MAX_ROBOT_ACTUATORS > 3
585 if(gcode->subcode == 0 && (gcode->has_letter('E') || gcode->get_num_args() == 0)){
586 // reset the E position, legacy for 3d Printers to be reprap compatible
587 // find the selected extruder
325ed08b
JM
588 int selected_extruder= get_active_extruder();
589 if(selected_extruder > 0) {
590 float e= gcode->has_letter('E') ? gcode->get_value('E') : 0;
591 machine_position[selected_extruder]= compensated_machine_position[selected_extruder]= e;
592 actuators[selected_extruder]->change_last_milestone(get_e_scale_fnc ? e*get_e_scale_fnc() : e);
13ad7234
JM
593 }
594 }
276e7c00
JM
595 if(gcode->subcode == 0 && gcode->get_num_args() > 0) {
596 for (int i = A_AXIS; i < n_motors; i++) {
597 // ABC just need to set machine_position and compensated_machine_position if specified
598 char axis= 'A'+i-3;
599 if(!actuators[i]->is_extruder() && gcode->has_letter(axis)) {
600 float ap= gcode->get_value(axis);
601 machine_position[i]= compensated_machine_position[i]= ap;
602 actuators[i]->change_last_milestone(ap); // this updates the last_milestone in the actuator
603 }
604 }
605 }
13ad7234
JM
606 #endif
607
78d0e16a 608 return;
4710532a
JM
609 }
610 }
67a649dd 611
4710532a
JM
612 } else if( gcode->has_m) {
613 switch( gcode->m ) {
20ed51b7
JM
614 // case 0: // M0 feed hold, (M0.1 is release feed hold, except we are in feed hold)
615 // if(THEKERNEL->is_grbl_mode()) THEKERNEL->set_feed_hold(gcode->subcode == 0);
616 // break;
01a8d21a
JM
617
618 case 30: // M30 end of program in grbl mode (otherwise it is delete sdcard file)
619 if(!THEKERNEL->is_grbl_mode()) break;
620 // fall through to M2
807b9b57 621 case 2: // M2 end of program
03b01bac
JM
622 current_wcs = 0;
623 absolute_mode = true;
807b9b57 624 break;
9e6014a6
JM
625 case 17:
626 THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
627 break;
628
d01fbc6f
JM
629 case 18: // this allows individual motors to be turned off, no parameters falls through to turn all off
630 if(gcode->get_num_args() > 0) {
631 // bitmap of motors to turn off, where bit 1:X, 2:Y, 3:Z, 4:A, 5:B, 6:C
632 uint32_t bm= 0;
633 for (int i = 0; i < n_motors; ++i) {
634 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-3));
635 if(gcode->has_letter(axis)) bm |= (0x02<<i); // set appropriate bit
636 }
559c0e6a
JM
637 // handle E parameter as currently selected extruder ABC
638 if(gcode->has_letter('E')) {
325ed08b
JM
639 // find first selected extruder
640 int i= get_active_extruder();
641 if(i > 0) {
642 bm |= (0x02<<i); // set appropriate bit
559c0e6a
JM
643 }
644 }
645
d01fbc6f
JM
646 THEKERNEL->conveyor->wait_for_idle();
647 THEKERNEL->call_event(ON_ENABLE, (void *)bm);
648 break;
649 }
650 // fall through
9e6014a6 651 case 84:
04782655 652 THEKERNEL->conveyor->wait_for_idle();
9e6014a6
JM
653 THEKERNEL->call_event(ON_ENABLE, nullptr); // turn all enable pins off
654 break;
807b9b57 655
29e809e0
JM
656 case 82: e_absolute_mode= true; break;
657 case 83: e_absolute_mode= false; break;
658
0fb5b438 659 case 92: // M92 - set steps per mm
5acc50ad 660 for (int i = 0; i < n_motors; ++i) {
13721c6c 661 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
5acc50ad
JM
662 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
663 if(gcode->has_letter(axis)) {
664 actuators[i]->change_steps_per_mm(this->to_millimeters(gcode->get_value(axis)));
665 }
666 gcode->stream->printf("%c:%f ", axis, actuators[i]->get_steps_per_mm());
667 }
0fb5b438 668 gcode->add_nl = true;
dd0a7cfa 669 check_max_actuator_speeds();
0fb5b438 670 return;
562db364 671
e03f2747 672 case 114:{
cb6bfefa 673 std::string buf;
12ce413f 674 print_position(gcode->subcode, buf, true); // ignore extruders as they will print E themselves
cb6bfefa 675 gcode->txt_after_ok.append(buf);
2791c829 676 return;
e03f2747 677 }
33e4cc02 678
212caccd
JM
679 case 120: // push state
680 push_state();
681 break;
562db364
JM
682
683 case 121: // pop state
212caccd 684 pop_state();
562db364
JM
685 break;
686
125b71ce
JM
687 case 203: // M203 Set maximum feedrates in mm/sec, M203.1 set maximum actuator feedrates
688 if(gcode->get_num_args() == 0) {
689 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
55783268 690 gcode->stream->printf(" %c: %g ", 'X' + i, gcode->subcode == 0 ? this->max_speeds[i] : actuators[i]->get_max_rate());
125b71ce 691 }
5acc50ad
JM
692 if(gcode->subcode == 1) {
693 for (size_t i = A_AXIS; i < n_motors; i++) {
13721c6c 694 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
5acc50ad
JM
695 gcode->stream->printf(" %c: %g ", 'A' + i - A_AXIS, actuators[i]->get_max_rate());
696 }
d5262155
JM
697 }else{
698 gcode->stream->printf(" S: %g ", this->max_speed);
5acc50ad
JM
699 }
700
125b71ce 701 gcode->add_nl = true;
dd0a7cfa 702
125b71ce
JM
703 }else{
704 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
705 if (gcode->has_letter('X' + i)) {
55783268
JM
706 float v= gcode->get_value('X'+i);
707 if(gcode->subcode == 0) this->max_speeds[i]= v;
708 else if(gcode->subcode == 1) actuators[i]->set_max_rate(v);
125b71ce
JM
709 }
710 }
3e1ea0e2 711
5acc50ad
JM
712 if(gcode->subcode == 1) {
713 // ABC axis only handle actuator max speeds
714 for (size_t i = A_AXIS; i < n_motors; i++) {
715 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
716 int c= 'A' + i - A_AXIS;
717 if(gcode->has_letter(c)) {
718 float v= gcode->get_value(c);
719 actuators[i]->set_max_rate(v);
720 }
721 }
d5262155
JM
722
723 }else{
724 if(gcode->has_letter('S')) max_speed= gcode->get_value('S');
5acc50ad
JM
725 }
726
727
3e1ea0e2
JM
728 // this format is deprecated
729 if(gcode->subcode == 0 && (gcode->has_letter('A') || gcode->has_letter('B') || gcode->has_letter('C'))) {
730 gcode->stream->printf("NOTE this format is deprecated, Use M203.1 instead\n");
be7f67cd 731 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
3e1ea0e2
JM
732 if (gcode->has_letter('A' + i)) {
733 float v= gcode->get_value('A'+i);
734 actuators[i]->set_max_rate(v);
735 }
736 }
737 }
738
55783268 739 if(gcode->subcode == 1) check_max_actuator_speeds();
807b9b57 740 }
125b71ce 741 break;
83488642 742
29e809e0 743 case 204: // M204 Snnn - set default acceleration to nnn, Xnnn Ynnn Znnn sets axis specific acceleration
4710532a 744 if (gcode->has_letter('S')) {
4710532a 745 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 746 // enforce minimum
29e809e0
JM
747 if (acc < 1.0F) acc = 1.0F;
748 this->default_acceleration = acc;
d4ee6ee2 749 }
5acc50ad
JM
750 for (int i = 0; i < n_motors; ++i) {
751 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
752 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
753 if(gcode->has_letter(axis)) {
754 float acc = gcode->get_value(axis); // mm/s^2
29e809e0
JM
755 // enforce positive
756 if (acc <= 0.0F) acc = NAN;
757 actuators[i]->set_acceleration(acc);
758 }
c5fe1787 759 }
d4ee6ee2
JM
760 break;
761
125b71ce 762 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed
4710532a
JM
763 if (gcode->has_letter('X')) {
764 float jd = gcode->get_value('X');
d4ee6ee2 765 // enforce minimum
8b69c90d
JM
766 if (jd < 0.0F)
767 jd = 0.0F;
4710532a 768 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 769 }
107df03f
JM
770 if (gcode->has_letter('Z')) {
771 float jd = gcode->get_value('Z');
772 // enforce minimum, -1 disables it and uses regular junction deviation
73a0eab6
JM
773 if (jd <= -1.0F)
774 jd = NAN;
107df03f
JM
775 THEKERNEL->planner->z_junction_deviation = jd;
776 }
4710532a
JM
777 if (gcode->has_letter('S')) {
778 float mps = gcode->get_value('S');
8b69c90d
JM
779 // enforce minimum
780 if (mps < 0.0F)
781 mps = 0.0F;
4710532a 782 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 783 }
d4ee6ee2 784 break;
98761c28 785
7e9e31b0 786 case 211: // M211 Sn turns soft endstops on/off
bb81b569
JM
787 if(gcode->has_letter('S')) {
788 soft_endstop_enabled= gcode->get_uint('S') == 1;
789 }else{
790 gcode->stream->printf("Soft endstops are %s", soft_endstop_enabled ? "Enabled" : "Disabled");
791 for (int i = X_AXIS; i <= Z_AXIS; ++i) {
845e6efb
JM
792 if(isnan(soft_endstop_min[i])) {
793 gcode->stream->printf(",%c min is disabled", 'X'+i);
794 }
795 if(isnan(soft_endstop_max[i])) {
796 gcode->stream->printf(",%c max is disabled", 'X'+i);
797 }
bb81b569 798 if(!is_homed(i)) {
845e6efb 799 gcode->stream->printf(",%c axis is not homed", 'X'+i);
bb81b569 800 }
845e6efb 801 }
bb81b569
JM
802 gcode->stream->printf("\n");
803 }
7e9e31b0
JM
804 break;
805
7369629d 806 case 220: // M220 - speed override percentage
4710532a 807 if (gcode->has_letter('S')) {
1ad23cd3 808 float factor = gcode->get_value('S');
98761c28 809 // enforce minimum 10% speed
da947c62
MM
810 if (factor < 10.0F)
811 factor = 10.0F;
812 // enforce maximum 10x speed
813 if (factor > 1000.0F)
814 factor = 1000.0F;
815
816 seconds_per_minute = 6000.0F / factor;
03b01bac 817 } else {
9ef9f45b 818 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 819 }
b4f56013 820 break;
ec4773e5 821
494dc541 822 case 400: // wait until all moves are done up to this point
04782655 823 THEKERNEL->conveyor->wait_for_idle();
494dc541
JM
824 break;
825
33e4cc02 826 case 500: // M500 saves some volatile settings to config override file
b7cd847e 827 case 503: { // M503 just prints the settings
5acc50ad
JM
828 gcode->stream->printf(";Steps per unit:\nM92 ");
829 for (int i = 0; i < n_motors; ++i) {
830 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
831 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
832 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_steps_per_mm());
833 }
834 gcode->stream->printf("\n");
df56baf2 835
5acc50ad 836 // only print if not NAN
df56baf2 837 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f ", default_acceleration);
5acc50ad
JM
838 for (int i = 0; i < n_motors; ++i) {
839 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
840 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
841 if(!isnan(actuators[i]->get_acceleration())) gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_acceleration());
df56baf2
JM
842 }
843 gcode->stream->printf("\n");
844
43fa8fd2 845 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 846
d5262155 847 gcode->stream->printf(";Max cartesian feedrates in mm/sec:\nM203 X%1.5f Y%1.5f Z%1.5f S%1.5f\n", this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS], this->max_speed);
5acc50ad
JM
848
849 gcode->stream->printf(";Max actuator feedrates in mm/sec:\nM203.1 ");
850 for (int i = 0; i < n_motors; ++i) {
851 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
852 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
853 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_max_rate());
854 }
855 gcode->stream->printf("\n");
b7cd847e
JM
856
857 // get or save any arm solution specific optional values
858 BaseSolution::arm_options_t options;
859 if(arm_solution->get_optional(options) && !options.empty()) {
860 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 861 for(auto &i : options) {
b7cd847e
JM
862 gcode->stream->printf(" %c%1.4f", i.first, i.second);
863 }
864 gcode->stream->printf("\n");
865 }
6e92ab91 866
807b9b57
JM
867 // save wcs_offsets and current_wcs
868 // TODO this may need to be done whenever they change to be compliant
a01b9d3e
JM
869 if(save_g54) {
870 gcode->stream->printf(";WCS settings\n");
871 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
872 int n = 1;
873 for(auto &i : wcs_offsets) {
874 if(i != wcs_t(0, 0, 0)) {
875 float x, y, z;
876 std::tie(x, y, z) = i;
877 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f ; %s\n", n, x, y, z, wcs2gcode(n-1).c_str());
878 }
879 ++n;
2791c829 880 }
807b9b57 881 }
3aad33c7
JM
882 if(save_g92) {
883 // linuxcnc saves G92, so we do too if configured, default is to not save to maintain backward compatibility
884 // 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
885 if(g92_offset != wcs_t(0, 0, 0)) {
886 float x, y, z;
887 std::tie(x, y, z) = g92_offset;
888 gcode->stream->printf("G92.3 X%f Y%f Z%f\n", x, y, z); // sets G92 to the specified values
889 }
67a649dd
JM
890 }
891 }
807b9b57 892 break;
33e4cc02 893
b7cd847e 894 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6 895 // the parameter args could be any letter each arm solution only accepts certain ones
03b01bac 896 BaseSolution::arm_options_t options = gcode->get_args();
ebc75fc6
JM
897 options.erase('S'); // don't include the S
898 options.erase('U'); // don't include the U
899 if(options.size() > 0) {
900 // set the specified options
901 arm_solution->set_optional(options);
902 }
903 options.clear();
b7cd847e 904 if(arm_solution->get_optional(options)) {
ebc75fc6 905 // foreach optional value
4710532a 906 for(auto &i : options) {
b7cd847e
JM
907 // print all current values of supported options
908 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 909 gcode->add_nl = true;
ec4773e5
JM
910 }
911 }
ec4773e5 912
4a839bea 913 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 914 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
915 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
916
03b01bac 917 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
4a839bea
JM
918 this->mm_per_line_segment = gcode->get_value('U');
919 this->delta_segments_per_second = 0;
920 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 921 }
4a839bea 922
ec4773e5 923 break;
b7cd847e 924 }
6989211c 925 }
494dc541
JM
926 }
927
29e809e0 928 if( motion_mode != NONE) {
e560f057 929 is_g123= motion_mode != SEEK;
29e809e0 930 process_move(gcode, motion_mode);
e560f057
JM
931
932 }else{
933 is_g123= false;
00e607c7 934 }
6bc4a00a 935
c2f7c261
JM
936 next_command_is_MCS = false; // must be on same line as G0 or G1
937}
350c8a60 938
325ed08b
JM
939int Robot::get_active_extruder() const
940{
941 for (int i = E_AXIS; i < n_motors; ++i) {
942 // find first selected extruder
aef2eec0 943 if(actuators[i]->is_extruder() && actuators[i]->is_selected()) return i;
325ed08b
JM
944 }
945 return 0;
946}
947
5d2319a9 948// process a G0/G1/G2/G3
29e809e0 949void Robot::process_move(Gcode *gcode, enum MOTION_MODE_T motion_mode)
c2f7c261 950{
2791c829 951 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
ad6a77d1 952 // get XYZ and one E (which goes to the selected extruder)
29e809e0
JM
953 float param[4]{NAN, NAN, NAN, NAN};
954
955 // process primary axis
350c8a60
JM
956 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
957 char letter= 'X'+i;
958 if( gcode->has_letter(letter) ) {
959 param[i] = this->to_millimeters(gcode->get_value(letter));
350c8a60
JM
960 }
961 }
6bc4a00a 962
c2f7c261 963 float offset[3]{0,0,0};
4710532a
JM
964 for(char letter = 'I'; letter <= 'K'; letter++) {
965 if( gcode->has_letter(letter) ) {
966 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
967 }
968 }
00e607c7 969
c2f7c261 970 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
29e809e0 971 float target[n_motors];
45ca77ec 972 memcpy(target, machine_position, n_motors*sizeof(float));
29e809e0 973
350c8a60
JM
974 if(!next_command_is_MCS) {
975 if(this->absolute_mode) {
c2f7c261
JM
976 // apply wcs offsets and g92 offset and tool offset
977 if(!isnan(param[X_AXIS])) {
978 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);
979 }
980
981 if(!isnan(param[Y_AXIS])) {
982 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);
983 }
984
985 if(!isnan(param[Z_AXIS])) {
986 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);
987 }
350c8a60
JM
988
989 }else{
45ca77ec 990 // they are deltas from the machine_position if specified
350c8a60 991 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
45ca77ec 992 if(!isnan(param[i])) target[i] = param[i] + machine_position[i];
a6bbe768
JM
993 }
994 }
995
350c8a60 996 }else{
48a0fdb6 997 // already in machine coordinates, we do not add wcs or tool offset for that
c2f7c261
JM
998 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
999 if(!isnan(param[i])) target[i] = param[i];
1000 }
c2885de8 1001 }
6bc4a00a 1002
37b7b898
JM
1003 float delta_e= NAN;
1004
1005 #if MAX_ROBOT_ACTUATORS > 3
29e809e0 1006 // process extruder parameters, for active extruder only (only one active extruder at a time)
325ed08b 1007 int selected_extruder= 0;
29e809e0 1008 if(gcode->has_letter('E')) {
325ed08b
JM
1009 selected_extruder= get_active_extruder();
1010 param[E_AXIS]= gcode->get_value('E');
29e809e0
JM
1011 }
1012
1013 // do E for the selected extruder
29e809e0
JM
1014 if(selected_extruder > 0 && !isnan(param[E_AXIS])) {
1015 if(this->e_absolute_mode) {
1016 target[selected_extruder]= param[E_AXIS];
45ca77ec 1017 delta_e= target[selected_extruder] - machine_position[selected_extruder];
29e809e0
JM
1018 }else{
1019 delta_e= param[E_AXIS];
45ca77ec 1020 target[selected_extruder] = delta_e + machine_position[selected_extruder];
29e809e0
JM
1021 }
1022 }
1023
37b7b898
JM
1024 // 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
1025 for (int i = A_AXIS; i < n_motors; ++i) {
1026 char letter= 'A'+i-A_AXIS;
1027 if(gcode->has_letter(letter)) {
1028 float p= gcode->get_value(letter);
1029 if(this->absolute_mode) {
1030 target[i]= p;
1031 }else{
5acc50ad 1032 target[i]= p + machine_position[i];
37b7b898
JM
1033 }
1034 }
1035 }
1036 #endif
1037
4710532a 1038 if( gcode->has_letter('F') ) {
29e809e0 1039 if( motion_mode == SEEK )
da947c62 1040 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 1041 else
da947c62 1042 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 1043 }
6bc4a00a 1044
73cc27d2 1045 // S is modal When specified on a G0/1/2/3 command
e560f057
JM
1046 if(gcode->has_letter('S')) s_value= gcode->get_value('S');
1047
350c8a60 1048 bool moved= false;
29e809e0
JM
1049
1050 // Perform any physical actions
1051 switch(motion_mode) {
1052 case NONE: break;
1053
1054 case SEEK:
1055 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute, delta_e );
350c8a60 1056 break;
29e809e0
JM
1057
1058 case LINEAR:
1059 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute, delta_e );
350c8a60 1060 break;
29e809e0
JM
1061
1062 case CW_ARC:
1063 case CCW_ARC:
374d0777 1064 // Note arcs are not currently supported by extruder based machines, as 3D slicers do not use arcs (G2/G3)
29e809e0 1065 moved= this->compute_arc(gcode, offset, target, motion_mode);
350c8a60 1066 break;
4cff3ded 1067 }
13e4a3f9 1068
f1368fc3
JM
1069 // needed to act as start of next arc command
1070 memcpy(arc_milestone, target, sizeof(arc_milestone));
1071
c2f7c261 1072 if(moved) {
45ca77ec
JM
1073 // set machine_position to the calculated target
1074 memcpy(machine_position, target, n_motors*sizeof(float));
350c8a60 1075 }
edac9072
AW
1076}
1077
a6bbe768 1078// reset the machine position for all axis. Used for homing.
fd2341bc 1079// after homing we supply the cartesian coordinates that the head is at when homed,
bccd3a3e
JM
1080// however for Z this is the compensated machine position (if enabled)
1081// So we need to apply the inverse compensation transform to the supplied coordinates to get the correct machine position
8fe38353 1082// this will make the results from M114 and ? consistent after homing.
bccd3a3e 1083// This works for cases where the Z endstop is fixed on the Z actuator and is the same regardless of where XY are.
cef9acea
JM
1084void Robot::reset_axis_position(float x, float y, float z)
1085{
fd2341bc 1086 // set both the same initially
45ca77ec
JM
1087 compensated_machine_position[X_AXIS]= machine_position[X_AXIS] = x;
1088 compensated_machine_position[Y_AXIS]= machine_position[Y_AXIS] = y;
1089 compensated_machine_position[Z_AXIS]= machine_position[Z_AXIS] = z;
cef9acea 1090
8fe38353 1091 if(compensationTransform) {
45ca77ec
JM
1092 // apply inverse transform to get machine_position
1093 compensationTransform(machine_position, true);
8fe38353
JM
1094 }
1095
fd2341bc 1096 // now set the actuator positions based on the supplied compensated position
807b9b57 1097 ActuatorCoordinates actuator_pos;
45ca77ec 1098 arm_solution->cartesian_to_actuator(this->compensated_machine_position, actuator_pos);
29e809e0 1099 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
cef9acea
JM
1100 actuators[i]->change_last_milestone(actuator_pos[i]);
1101}
1102
de2ee57c 1103// Reset the position for an axis (used in homing, and to reset extruder after suspend)
4710532a
JM
1104void Robot::reset_axis_position(float position, int axis)
1105{
45ca77ec 1106 compensated_machine_position[axis] = position;
de2ee57c 1107 if(axis <= Z_AXIS) {
45ca77ec 1108 reset_axis_position(compensated_machine_position[X_AXIS], compensated_machine_position[Y_AXIS], compensated_machine_position[Z_AXIS]);
7d9e5765 1109
72420864 1110#if MAX_ROBOT_ACTUATORS > 3
96ef0809 1111 }else if(axis < n_motors) {
7d9e5765
JM
1112 // ABC and/or extruders need to be set as there is no arm solution for them
1113 machine_position[axis]= compensated_machine_position[axis]= position;
1114 actuators[axis]->change_last_milestone(machine_position[axis]);
72420864 1115#endif
de2ee57c 1116 }
4cff3ded
AW
1117}
1118
932a3995 1119// similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
abf706e6 1120// then sets the axis positions to match. currently only called from Endstops.cpp and RotaryDeltaCalibration.cpp
93f20a8c 1121void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
586cc733 1122{
fdfa00d2
JM
1123 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1124 if(!isnan(ac[i])) actuators[i]->change_last_milestone(ac[i]);
1125 }
586cc733
JM
1126
1127 // now correct axis positions then recorrect actuator to account for rounding
1128 reset_position_from_current_actuator_position();
1129}
1130
a6bbe768 1131// Use FK to find out where actuator is and reset to match
b6187406 1132// TODO maybe we should only reset axis that are being homed unless this is due to a ON_HALT
728477c4
JM
1133void Robot::reset_position_from_current_actuator_position()
1134{
807b9b57 1135 ActuatorCoordinates actuator_pos;
9fc4679b 1136 for (size_t i = X_AXIS; i < n_motors; i++) {
45ca77ec 1137 // NOTE actuator::current_position is curently NOT the same as actuator::machine_position after an abrupt abort
807b9b57
JM
1138 actuator_pos[i] = actuators[i]->get_current_position();
1139 }
58587001
JM
1140
1141 // discover machine position from where actuators actually are
45ca77ec
JM
1142 arm_solution->actuator_to_cartesian(actuator_pos, compensated_machine_position);
1143 memcpy(machine_position, compensated_machine_position, sizeof machine_position);
cf91d4f3 1144
45ca77ec
JM
1145 // compensated_machine_position includes the compensation transform so we need to get the inverse to get actual machine_position
1146 if(compensationTransform) compensationTransform(machine_position, true); // get inverse compensation transform
cf91d4f3 1147
45ca77ec 1148 // now reset actuator::machine_position, NOTE this may lose a little precision as FK is not always entirely accurate.
58587001 1149 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
932a3995 1150 // to get everything in perfect sync.
45ca77ec 1151 arm_solution->cartesian_to_actuator(compensated_machine_position, actuator_pos);
325ed08b 1152 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
7baae81a 1153 actuators[i]->change_last_milestone(actuator_pos[i]);
325ed08b
JM
1154 }
1155
1156 // Handle extruders and/or ABC axis
1157 #if MAX_ROBOT_ACTUATORS > 3
9fc4679b 1158 for (int i = A_AXIS; i < n_motors; i++) {
325ed08b
JM
1159 // ABC and/or extruders just need to set machine_position and compensated_machine_position
1160 float ap= actuator_pos[i];
aef2eec0 1161 if(actuators[i]->is_extruder() && get_e_scale_fnc) ap /= get_e_scale_fnc(); // inverse E scale if there is one and this is an extruder
325ed08b 1162 machine_position[i]= compensated_machine_position[i]= ap;
5d34cbb3 1163 actuators[i]->change_last_milestone(actuator_pos[i]); // this updates the last_milestone in the actuator
325ed08b
JM
1164 }
1165 #endif
728477c4 1166}
edac9072 1167
ad6a77d1 1168// Convert target (in machine coordinates) to machine_position, then convert to actuator position and append this to the planner
45ca77ec 1169// target is in machine coordinates without the compensation transform, however we save a compensated_machine_position that includes
c2f7c261 1170// all transforms and is what we actually convert to actuator positions
c1ebb1fe 1171bool Robot::append_milestone(const float target[], float rate_mm_s)
df6a30f2 1172{
29e809e0 1173 float deltas[n_motors];
1a936198 1174 float transformed_target[n_motors]; // adjust target for bed compensation
29e809e0 1175 float unit_vec[N_PRIMARY_AXIS];
df6a30f2 1176
3632a517 1177 // unity transform by default
29e809e0 1178 memcpy(transformed_target, target, n_motors*sizeof(float));
5e45206a 1179
350c8a60 1180 // check function pointer and call if set to transform the target to compensate for bed
c1ebb1fe 1181 if(compensationTransform) {
350c8a60 1182 // some compensation strategies can transform XYZ, some just change Z
8fe38353 1183 compensationTransform(transformed_target, false);
00e607c7 1184 }
807b9b57 1185
7e9e31b0
JM
1186 // check soft endstops only for homed axis that are enabled
1187 if(soft_endstop_enabled) {
1188 for (int i = 0; i <= Z_AXIS; ++i) {
1189 if(!is_homed(i)) continue;
1190 if( (!isnan(soft_endstop_min[i]) && transformed_target[i] < soft_endstop_min[i]) || (!isnan(soft_endstop_max[i]) && transformed_target[i] > soft_endstop_max[i]) ) {
1191 if(soft_endstop_halt) {
e6c2b5b6 1192 if(THEKERNEL->is_grbl_mode()) {
1fc40bdc 1193 THEKERNEL->streams->printf("error:");
e6c2b5b6
JM
1194 }else{
1195 THEKERNEL->streams->printf("Error: ");
1196 }
1197
1198 THEKERNEL->streams->printf("Soft Endstop %c was exceeded - reset or $X or M999 required\n", i+'X');
7e9e31b0
JM
1199 THEKERNEL->call_event(ON_HALT, nullptr);
1200 return false;
1201
1202 //} else if(soft_endstop_truncate) {
1203 // TODO VERY hard to do need to go back and change the target, and calculate intercept with the edge
94a906ee 1204 // and store all preceding vectors that have on eor more points ourtside of bounds so we can create a propper clip against the boundaries
7e9e31b0
JM
1205
1206 } else {
1207 // ignore it
e6c2b5b6 1208 if(THEKERNEL->is_grbl_mode()) {
1fc40bdc 1209 THEKERNEL->streams->printf("error:");
e6c2b5b6
JM
1210 }else{
1211 THEKERNEL->streams->printf("Error: ");
1212 }
1213 THEKERNEL->streams->printf("Soft Endstop %c was exceeded - entire move ignored\n", i+'X');
7e9e31b0
JM
1214 return false;
1215 }
1216 }
1217 }
1218 }
1219
1220
29e809e0 1221 bool move= false;
b5bd71f8 1222 float sos= 0; // sum of squares for just primary axis (XYZ usually)
29e809e0 1223
b84bd559 1224 // find distance moved by each axis, use transformed target from the current compensated machine position
ec45206d 1225 for (size_t i = 0; i < n_motors; i++) {
45ca77ec 1226 deltas[i] = transformed_target[i] - compensated_machine_position[i];
29e809e0
JM
1227 if(deltas[i] == 0) continue;
1228 // at least one non zero delta
1229 move = true;
b5bd71f8 1230 if(i < N_PRIMARY_AXIS) {
29e809e0 1231 sos += powf(deltas[i], 2);
121094a5 1232 }
3632a517 1233 }
aab6cbba 1234
29e809e0
JM
1235 // nothing moved
1236 if(!move) return false;
1237
850b4eeb 1238 // see if this is a primary axis move or not
b5bd71f8
JM
1239 bool auxilliary_move= true;
1240 for (int i = 0; i < N_PRIMARY_AXIS; ++i) {
1241 if(deltas[i] != 0) {
1242 auxilliary_move= false;
1243 break;
1244 }
1245 }
29e809e0 1246
850b4eeb
JM
1247 // total movement, use XYZ if a primary axis otherwise we calculate distance for E after scaling to mm
1248 float distance= auxilliary_move ? 0 : sqrtf(sos);
df6a30f2 1249
a6bbe768
JM
1250 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
1251 // 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 1252 if(!auxilliary_move && distance < 0.00001F) return false;
a6bbe768 1253
29e809e0 1254 if(!auxilliary_move) {
b5bd71f8 1255 for (size_t i = X_AXIS; i < N_PRIMARY_AXIS; i++) {
14a90ad5 1256 // find distance unit vector for primary axis only
850b4eeb 1257 unit_vec[i] = deltas[i] / distance;
df6a30f2 1258
14a90ad5 1259 // Do not move faster than the configured cartesian limits for XYZ
bcc06c36 1260 if ( i <= Z_AXIS && max_speeds[i] > 0 ) {
14a90ad5
JM
1261 float axis_speed = fabsf(unit_vec[i] * rate_mm_s);
1262
1263 if (axis_speed > max_speeds[i])
1264 rate_mm_s *= ( max_speeds[i] / axis_speed );
2cd32d70 1265 }
7b470506 1266 }
d5262155
JM
1267
1268 if(this->max_speed > 0 && rate_mm_s > this->max_speed) {
1269 rate_mm_s= this->max_speed;
1270 }
7b470506 1271 }
4cff3ded 1272
c2f7c261 1273 // find actuator position given the machine position, use actual adjusted target
29e809e0 1274 ActuatorCoordinates actuator_pos;
84cf4071
JM
1275 if(!disable_arm_solution) {
1276 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
1277
1278 }else{
1279 // basically the same as cartesian, would be used for special homing situations like for scara
1280 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1281 actuator_pos[i] = transformed_target[i];
1282 }
1283 }
df6a30f2 1284
13ad7234 1285#if MAX_ROBOT_ACTUATORS > 3
850b4eeb 1286 sos= 0;
ad6a77d1 1287 // for the extruders just copy the position, and possibly scale it from mm³ to mm
374d0777 1288 for (size_t i = E_AXIS; i < n_motors; i++) {
850b4eeb 1289 actuator_pos[i]= transformed_target[i];
aef2eec0 1290 if(actuators[i]->is_extruder() && get_e_scale_fnc) {
29e809e0
JM
1291 // NOTE this relies on the fact only one extruder is active at a time
1292 // scale for volumetric or flow rate
1293 // TODO is this correct? scaling the absolute target? what if the scale changes?
ec45206d 1294 // for volumetric it basically converts mm³ to mm, but what about flow rate?
121094a5 1295 actuator_pos[i] *= get_e_scale_fnc();
29e809e0 1296 }
850b4eeb
JM
1297 if(auxilliary_move) {
1298 // for E only moves we need to use the scaled E to calculate the distance
a3b16417 1299 sos += powf(actuator_pos[i] - actuators[i]->get_last_milestone(), 2);
850b4eeb
JM
1300 }
1301 }
1302 if(auxilliary_move) {
850b4eeb 1303 distance= sqrtf(sos); // distance in mm of the e move
1843a68f 1304 if(distance < 0.00001F) return false;
29e809e0
JM
1305 }
1306#endif
1307
1308 // use default acceleration to start with
1309 float acceleration = default_acceleration;
1310
850b4eeb 1311 float isecs = rate_mm_s / distance;
29e809e0 1312
df6a30f2 1313 // check per-actuator speed limits
29e809e0
JM
1314 for (size_t actuator = 0; actuator < n_motors; actuator++) {
1315 float d = fabsf(actuator_pos[actuator] - actuators[actuator]->get_last_milestone());
1316 if(d == 0 || !actuators[actuator]->is_selected()) continue; // no movement for this actuator
1317
1318 float actuator_rate= d * isecs;
03b01bac 1319 if (actuator_rate > actuators[actuator]->get_max_rate()) {
3494f3d0 1320 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
850b4eeb 1321 isecs = rate_mm_s / distance;
928467c0 1322 }
29e809e0 1323
df56baf2 1324 // adjust acceleration to lowest found, for now just primary axis unless it is an auxiliary move
14a90ad5 1325 // 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.
b5bd71f8 1326 if(auxilliary_move || actuator < N_PRIMARY_AXIS) {
df56baf2
JM
1327 float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
1328 if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
850b4eeb 1329 float ca = fabsf((d/distance) * acceleration);
df56baf2
JM
1330 if (ca > ma) {
1331 acceleration *= ( ma / ca );
1332 }
29e809e0 1333 }
14a90ad5 1334 }
928467c0
JM
1335 }
1336
0de4d6b0
JM
1337 // if we are in feed hold wait here until it is released, this means that even segemnted lines will pause
1338 while(THEKERNEL->get_feed_hold()) {
1339 THEKERNEL->call_event(ON_IDLE, this);
1340 // if we also got a HALT then break out of this
1341 if(THEKERNEL->is_halted()) return false;
1342 }
1343
edac9072 1344 // Append the block to the planner
850b4eeb 1345 // NOTE that distance here should be either the distance travelled by the XYZ axis, or the E mm travel if a solo E move
0de4d6b0 1346 // NOTE this call will bock until there is room in the block queue, on_idle will continue to be called
e560f057 1347 if(THEKERNEL->planner->append_block( actuator_pos, n_motors, rate_mm_s, distance, auxilliary_move ? nullptr : unit_vec, acceleration, s_value, is_g123)) {
b84bd559 1348 // this is the new compensated machine position
45ca77ec 1349 memcpy(this->compensated_machine_position, transformed_target, n_motors*sizeof(float));
07879e05 1350 return true;
6f5d947f
JM
1351 }
1352
07879e05
JM
1353 // no actual move
1354 return false;
4cff3ded
AW
1355}
1356
121094a5
JM
1357// Used to plan a single move used by things like endstops when homing, zprobe, extruder firmware retracts etc.
1358bool Robot::delta_move(const float *delta, float rate_mm_s, uint8_t naxis)
c8bac202
JM
1359{
1360 if(THEKERNEL->is_halted()) return false;
1361
121094a5 1362 // catch negative or zero feed rates
c8bac202
JM
1363 if(rate_mm_s <= 0.0F) {
1364 return false;
1365 }
1366
45ca77ec 1367 // get the absolute target position, default is current machine_position
121094a5 1368 float target[n_motors];
45ca77ec 1369 memcpy(target, machine_position, n_motors*sizeof(float));
c8bac202 1370
121094a5
JM
1371 // add in the deltas to get new target
1372 for (int i= 0; i < naxis; i++) {
1373 target[i] += delta[i];
c8bac202 1374 }
c8bac202 1375
45ca77ec 1376 // submit for planning and if moved update machine_position
c1ebb1fe 1377 if(append_milestone(target, rate_mm_s)) {
45ca77ec 1378 memcpy(machine_position, target, n_motors*sizeof(float));
121094a5 1379 return true;
29e809e0 1380 }
c8bac202 1381
121094a5 1382 return false;
c8bac202
JM
1383}
1384
edac9072 1385// Append a move to the queue ( cutting it into segments if needed )
29e809e0 1386bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s, float delta_e)
4710532a 1387{
121094a5
JM
1388 // catch negative or zero feed rates and return the same error as GRBL does
1389 if(rate_mm_s <= 0.0F) {
1390 gcode->is_error= true;
1391 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1392 return false;
1393 }
29e809e0
JM
1394
1395 // Find out the distance for this move in XYZ in MCS
45ca77ec 1396 float millimeters_of_travel = sqrtf(powf( target[X_AXIS] - machine_position[X_AXIS], 2 ) + powf( target[Y_AXIS] - machine_position[Y_AXIS], 2 ) + powf( target[Z_AXIS] - machine_position[Z_AXIS], 2 ));
29e809e0 1397
374d0777 1398 if(millimeters_of_travel < 0.00001F) {
121094a5
JM
1399 // we have no movement in XYZ, probably E only extrude or retract
1400 return this->append_milestone(target, rate_mm_s);
29e809e0
JM
1401 }
1402
1403 /*
850d5f12
JM
1404 For extruders, we need to do some extra work to limit the volumetric rate if specified...
1405 If using volumetric limts we need to be using volumetric extrusion for this to work as Ennn needs to be in mm³ not mm
374d0777
JM
1406 We ask Extruder to do all the work but we need to pass in the relevant data.
1407 NOTE we need to do this before we segment the line (for deltas)
29e809e0
JM
1408 */
1409 if(!isnan(delta_e) && gcode->has_g && gcode->g == 1) {
1410 float data[2]= {delta_e, rate_mm_s / millimeters_of_travel};
d2adef5e 1411 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
29e809e0 1412 rate_mm_s *= data[1]; // adjust the feedrate
d2adef5e
JM
1413 }
1414 }
1415
c2f7c261 1416 // 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
1417 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
1418 // 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 1419 uint16_t segments;
5984acdf 1420
a3e1326a 1421 if(this->disable_segmentation || (!segment_z_moves && !gcode->has_letter('X') && !gcode->has_letter('Y'))) {
778093ce
JM
1422 segments= 1;
1423
1424 } else if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
1425 // enabled if set to something > 1, it is set to 0.0 by default
1426 // segment based on current speed and requested segments per second
1427 // the faster the travel speed the fewer segments needed
1428 // NOTE rate is mm/sec and we take into account any speed override
29e809e0 1429 float seconds = millimeters_of_travel / rate_mm_s;
9502f9d5 1430 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 1431 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 1432
4710532a
JM
1433 } else {
1434 if(this->mm_per_line_segment == 0.0F) {
1435 segments = 1; // don't split it up
1436 } else {
29e809e0 1437 segments = ceilf( millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
1438 }
1439 }
5984acdf 1440
350c8a60 1441 bool moved= false;
4710532a 1442 if (segments > 1) {
2ba859c9 1443 // A vector to keep track of the endpoint of each segment
29e809e0
JM
1444 float segment_delta[n_motors];
1445 float segment_end[n_motors];
45ca77ec 1446 memcpy(segment_end, machine_position, n_motors*sizeof(float));
2ba859c9
MM
1447
1448 // How far do we move each segment?
29e809e0 1449 for (int i = 0; i < n_motors; i++)
45ca77ec 1450 segment_delta[i] = (target[i] - machine_position[i]) / segments;
4cff3ded 1451
c8e0fb15
MM
1452 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
1453 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 1454 for (int i = 1; i < segments; i++) {
350c8a60 1455 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
bb206212
JM
1456 for (int j = 0; j < n_motors; j++)
1457 segment_end[j] += segment_delta[j];
2ba859c9
MM
1458
1459 // Append the end of this segment to the queue
0de4d6b0 1460 // this can block waiting for free block queue or if in feed hold
121094a5 1461 bool b= this->append_milestone(segment_end, rate_mm_s);
350c8a60 1462 moved= moved || b;
2ba859c9 1463 }
4cff3ded 1464 }
5984acdf
MM
1465
1466 // Append the end of this full move to the queue
121094a5 1467 if(this->append_milestone(target, rate_mm_s)) moved= true;
2134bcf2 1468
a6bbe768 1469 this->next_command_is_MCS = false; // always reset this
00e607c7 1470
350c8a60 1471 return moved;
4cff3ded
AW
1472}
1473
4cff3ded 1474
edac9072 1475// Append an arc to the queue ( cutting it into segments as needed )
850d5f12 1476// TODO does not support any E parameters so cannot be used for 3D printing.
350c8a60 1477bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
4710532a 1478{
121094a5
JM
1479 float rate_mm_s= this->feed_rate / seconds_per_minute;
1480 // catch negative or zero feed rates and return the same error as GRBL does
1481 if(rate_mm_s <= 0.0F) {
1482 gcode->is_error= true;
1483 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1484 return false;
1485 }
aab6cbba 1486
3b2168fe
JM
1487 // Scary math.
1488 // We need to use arc_milestone here to get accurate arcs as previous machine_position may have been skipped due to small movements
a8faf188
JR
1489 float center_axis0 = this->arc_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
1490 float center_axis1 = this->arc_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
1491 float linear_travel = target[this->plane_axis_2] - this->arc_milestone[this->plane_axis_2];
01b2e151 1492 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to start position
1ad23cd3 1493 float r_axis1 = -offset[this->plane_axis_1];
01b2e151 1494 float rt_axis0 = target[this->plane_axis_0] - this->arc_milestone[this->plane_axis_0] - offset[this->plane_axis_0]; // Radius vector from center to target position
a8faf188 1495 float rt_axis1 = target[this->plane_axis_1] - this->arc_milestone[this->plane_axis_1] - offset[this->plane_axis_1];
42d5c1f1 1496 float angular_travel = 0;
01b2e151 1497 //check for condition where atan2 formula will fail due to everything canceling out exactly
a8faf188 1498 if((this->arc_milestone[this->plane_axis_0]==target[this->plane_axis_0]) && (this->arc_milestone[this->plane_axis_1]==target[this->plane_axis_1])) {
01b2e151 1499 if (is_clockwise) { // set angular_travel to -2pi for a clockwise full circle
42d5c1f1 1500 angular_travel = (-2 * PI);
01b2e151 1501 } else { // set angular_travel to 2pi for a counterclockwise full circle
42d5c1f1
JR
1502 angular_travel = (2 * PI);
1503 }
5fa0c173 1504 } else {
42d5c1f1
JR
1505 // Patch from GRBL Firmware - Christoph Baumann 04072015
1506 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
01b2e151 1507 // Only run if not a full circle or angular travel will incorrectly result in 0.0f
42d5c1f1 1508 angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
42d5c1f1 1509 if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes
01b2e151 1510 if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs
42d5c1f1 1511 if (angular_travel > 0) { angular_travel -= (2 * PI); }
01b2e151 1512 } else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs
42d5c1f1
JR
1513 if (angular_travel < 0) { angular_travel += (2 * PI); }
1514 }
4710532a 1515 }
aab6cbba 1516
edac9072 1517 // Find the distance for this gcode
29e809e0 1518 float millimeters_of_travel = hypotf(angular_travel * radius, fabsf(linear_travel));
436a2cd1 1519
edac9072 1520 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
64877d0a 1521 if( millimeters_of_travel < 0.000001F ) {
350c8a60 1522 return false;
4710532a 1523 }
5dcb2ff3 1524
83c6e067
RA
1525 // limit segments by maximum arc error
1526 float arc_segment = this->mm_per_arc_segment;
4d0f60a9 1527 if ((this->mm_max_arc_error > 0) && (2 * radius > this->mm_max_arc_error)) {
83c6e067
RA
1528 float min_err_segment = 2 * sqrtf((this->mm_max_arc_error * (2 * radius - this->mm_max_arc_error)));
1529 if (this->mm_per_arc_segment < min_err_segment) {
1530 arc_segment = min_err_segment;
1531 }
1532 }
64877d0a
JM
1533
1534 // catch fall through on above
1535 if(arc_segment < 0.0001F) {
1536 arc_segment= 0.5F; /// the old default, so we avoid the divide by zero
1537 }
1538
5984acdf 1539 // Figure out how many segments for this gcode
f8935932 1540 // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the
64877d0a
JM
1541 uint16_t segments = floorf(millimeters_of_travel / arc_segment);
1542 bool moved= false;
aab6cbba 1543
64877d0a
JM
1544 if(segments > 1) {
1545 float theta_per_segment = angular_travel / segments;
1546 float linear_per_segment = linear_travel / segments;
1547
1548 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1549 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1550 r_T = [cos(phi) -sin(phi);
1551 sin(phi) cos(phi] * r ;
1552 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1553 defined from the circle center to the initial position. Each line segment is formed by successive
1554 vector rotations. This requires only two cos() and sin() computations to form the rotation
1555 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1556 all float numbers are single precision on the Arduino. (True float precision will not have
1557 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1558 tool precision in some cases. Therefore, arc path correction is implemented.
1559
1560 Small angle approximation may be used to reduce computation overhead further. This approximation
1561 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1562 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1563 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1564 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1565 issue for CNC machines with the single precision Arduino calculations.
1566 This approximation also allows mc_arc to immediately insert a line segment into the planner
1567 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1568 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1569 This is important when there are successive arc motions.
1570 */
1571 // Vector rotation matrix values
1572 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1573 float sin_T = theta_per_segment;
1574
1575 // TODO we need to handle the ABC axis here by segmenting them
1576 float arc_target[n_motors];
1577 float sin_Ti;
1578 float cos_Ti;
1579 float r_axisi;
1580 uint16_t i;
1581 int8_t count = 0;
1582
1583 // init array for all axis
1584 memcpy(arc_target, machine_position, n_motors*sizeof(float));
1585
1586 // Initialize the linear axis
1587 arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2];
1588
1589 for (i = 1; i < segments; i++) { // Increment (segments-1)
1590 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
aab6cbba 1591
64877d0a
JM
1592 if (count < this->arc_correction ) {
1593 // Apply vector rotation matrix
1594 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1595 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1596 r_axis1 = r_axisi;
1597 count++;
1598 } else {
1599 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1600 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1601 cos_Ti = cosf(i * theta_per_segment);
1602 sin_Ti = sinf(i * theta_per_segment);
1603 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1604 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1605 count = 0;
1606 }
511ba1ad 1607
64877d0a
JM
1608 // Update arc_target location
1609 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1610 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1611 arc_target[this->plane_axis_2] += linear_per_segment;
aab6cbba 1612
64877d0a
JM
1613 // Append this segment to the queue
1614 bool b= this->append_milestone(arc_target, rate_mm_s);
1615 moved= moved || b;
aab6cbba 1616 }
aab6cbba 1617 }
edac9072 1618
aab6cbba 1619 // Ensure last segment arrives at target location.
121094a5 1620 if(this->append_milestone(target, rate_mm_s)) moved= true;
350c8a60
JM
1621
1622 return moved;
aab6cbba
AW
1623}
1624
edac9072 1625// Do the math for an arc and add it to the queue
29e809e0 1626bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[], enum MOTION_MODE_T motion_mode)
4710532a 1627{
aab6cbba
AW
1628
1629 // Find the radius
13addf09 1630 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
1631
1632 // Set clockwise/counter-clockwise sign for mc_arc computations
1633 bool is_clockwise = false;
29e809e0 1634 if( motion_mode == CW_ARC ) {
4710532a
JM
1635 is_clockwise = true;
1636 }
aab6cbba
AW
1637
1638 // Append arc
350c8a60 1639 return this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
1640}
1641
1642
4710532a
JM
1643float Robot::theta(float x, float y)
1644{
1645 float t = atanf(x / fabs(y));
1646 if (y > 0) {
1647 return(t);
1648 } else {
1649 if (t > 0) {
29e809e0 1650 return(PI - t);
4710532a 1651 } else {
29e809e0 1652 return(-PI - t);
4710532a
JM
1653 }
1654 }
4cff3ded
AW
1655}
1656
4710532a
JM
1657void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1658{
4cff3ded
AW
1659 this->plane_axis_0 = axis_0;
1660 this->plane_axis_1 = axis_1;
1661 this->plane_axis_2 = axis_2;
1662}
1663
fae93525 1664void Robot::clearToolOffset()
4710532a 1665{
c2f7c261 1666 this->tool_offset= wcs_t(0,0,0);
fae93525
JM
1667}
1668
1669void Robot::setToolOffset(const float offset[3])
1670{
c2f7c261 1671 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
5966b7d0
AT
1672}
1673
0ec2f63a
JM
1674float Robot::get_feed_rate() const
1675{
1676 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1677}
7e9e31b0
JM
1678
1679bool Robot::is_homed(uint8_t i) const
1680{
1681 if(i >= 3) return false; // safety
1682
1683 // if we are homing we ignore soft endstops so return false
1684 bool homing;
1685 bool ok = PublicData::get_value(endstops_checksum, get_homing_status_checksum, 0, &homing);
1686 if(!ok || homing) return false;
1687
1688 // check individual axis homing status
1689 bool homed[3];
1690 ok = PublicData::get_value(endstops_checksum, get_homed_status_checksum, 0, homed);
1691 if(!ok) return false;
1692 return homed[i];
1693}