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