Fix M92, M203, M204 for extruders in a n-axis world
[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 610 for (int i = 0; i < n_motors; ++i) {
13721c6c 611 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
5acc50ad
JM
612 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
613 if(gcode->has_letter(axis)) {
614 actuators[i]->change_steps_per_mm(this->to_millimeters(gcode->get_value(axis)));
615 }
616 gcode->stream->printf("%c:%f ", axis, actuators[i]->get_steps_per_mm());
617 }
0fb5b438 618 gcode->add_nl = true;
dd0a7cfa 619 check_max_actuator_speeds();
0fb5b438 620 return;
562db364 621
e03f2747
JM
622 case 114:{
623 char buf[64];
624 int n= print_position(gcode->subcode, buf, sizeof buf);
625 if(n > 0) gcode->txt_after_ok.append(buf, n);
2791c829 626 return;
e03f2747 627 }
33e4cc02 628
212caccd
JM
629 case 120: // push state
630 push_state();
631 break;
562db364
JM
632
633 case 121: // pop state
212caccd 634 pop_state();
562db364
JM
635 break;
636
125b71ce
JM
637 case 203: // M203 Set maximum feedrates in mm/sec, M203.1 set maximum actuator feedrates
638 if(gcode->get_num_args() == 0) {
639 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
55783268 640 gcode->stream->printf(" %c: %g ", 'X' + i, gcode->subcode == 0 ? this->max_speeds[i] : actuators[i]->get_max_rate());
125b71ce 641 }
5acc50ad
JM
642 if(gcode->subcode == 1) {
643 for (size_t i = A_AXIS; i < n_motors; i++) {
13721c6c 644 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
5acc50ad
JM
645 gcode->stream->printf(" %c: %g ", 'A' + i - A_AXIS, actuators[i]->get_max_rate());
646 }
647 }
648
125b71ce 649 gcode->add_nl = true;
dd0a7cfa 650
125b71ce
JM
651 }else{
652 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
653 if (gcode->has_letter('X' + i)) {
55783268
JM
654 float v= gcode->get_value('X'+i);
655 if(gcode->subcode == 0) this->max_speeds[i]= v;
656 else if(gcode->subcode == 1) actuators[i]->set_max_rate(v);
125b71ce
JM
657 }
658 }
3e1ea0e2 659
5acc50ad
JM
660 if(gcode->subcode == 1) {
661 // ABC axis only handle actuator max speeds
662 for (size_t i = A_AXIS; i < n_motors; i++) {
663 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
664 int c= 'A' + i - A_AXIS;
665 if(gcode->has_letter(c)) {
666 float v= gcode->get_value(c);
667 actuators[i]->set_max_rate(v);
668 }
669 }
670 }
671
672
3e1ea0e2
JM
673 // this format is deprecated
674 if(gcode->subcode == 0 && (gcode->has_letter('A') || gcode->has_letter('B') || gcode->has_letter('C'))) {
675 gcode->stream->printf("NOTE this format is deprecated, Use M203.1 instead\n");
be7f67cd 676 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
3e1ea0e2
JM
677 if (gcode->has_letter('A' + i)) {
678 float v= gcode->get_value('A'+i);
679 actuators[i]->set_max_rate(v);
680 }
681 }
682 }
683
55783268 684 if(gcode->subcode == 1) check_max_actuator_speeds();
807b9b57 685 }
125b71ce 686 break;
83488642 687
29e809e0 688 case 204: // M204 Snnn - set default acceleration to nnn, Xnnn Ynnn Znnn sets axis specific acceleration
4710532a 689 if (gcode->has_letter('S')) {
4710532a 690 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 691 // enforce minimum
29e809e0
JM
692 if (acc < 1.0F) acc = 1.0F;
693 this->default_acceleration = acc;
d4ee6ee2 694 }
5acc50ad
JM
695 for (int i = 0; i < n_motors; ++i) {
696 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
697 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
698 if(gcode->has_letter(axis)) {
699 float acc = gcode->get_value(axis); // mm/s^2
29e809e0
JM
700 // enforce positive
701 if (acc <= 0.0F) acc = NAN;
702 actuators[i]->set_acceleration(acc);
703 }
c5fe1787 704 }
d4ee6ee2
JM
705 break;
706
125b71ce 707 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed
4710532a
JM
708 if (gcode->has_letter('X')) {
709 float jd = gcode->get_value('X');
d4ee6ee2 710 // enforce minimum
8b69c90d
JM
711 if (jd < 0.0F)
712 jd = 0.0F;
4710532a 713 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 714 }
107df03f
JM
715 if (gcode->has_letter('Z')) {
716 float jd = gcode->get_value('Z');
717 // enforce minimum, -1 disables it and uses regular junction deviation
73a0eab6
JM
718 if (jd <= -1.0F)
719 jd = NAN;
107df03f
JM
720 THEKERNEL->planner->z_junction_deviation = jd;
721 }
4710532a
JM
722 if (gcode->has_letter('S')) {
723 float mps = gcode->get_value('S');
8b69c90d
JM
724 // enforce minimum
725 if (mps < 0.0F)
726 mps = 0.0F;
4710532a 727 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 728 }
d4ee6ee2 729 break;
98761c28 730
7369629d 731 case 220: // M220 - speed override percentage
4710532a 732 if (gcode->has_letter('S')) {
1ad23cd3 733 float factor = gcode->get_value('S');
98761c28 734 // enforce minimum 10% speed
da947c62
MM
735 if (factor < 10.0F)
736 factor = 10.0F;
737 // enforce maximum 10x speed
738 if (factor > 1000.0F)
739 factor = 1000.0F;
740
741 seconds_per_minute = 6000.0F / factor;
03b01bac 742 } else {
9ef9f45b 743 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 744 }
b4f56013 745 break;
ec4773e5 746
494dc541 747 case 400: // wait until all moves are done up to this point
04782655 748 THEKERNEL->conveyor->wait_for_idle();
494dc541
JM
749 break;
750
33e4cc02 751 case 500: // M500 saves some volatile settings to config override file
b7cd847e 752 case 503: { // M503 just prints the settings
5acc50ad
JM
753 gcode->stream->printf(";Steps per unit:\nM92 ");
754 for (int i = 0; i < n_motors; ++i) {
755 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
756 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
757 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_steps_per_mm());
758 }
759 gcode->stream->printf("\n");
df56baf2 760
5acc50ad 761 // only print if not NAN
df56baf2 762 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f ", default_acceleration);
5acc50ad
JM
763 for (int i = 0; i < n_motors; ++i) {
764 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
765 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
766 if(!isnan(actuators[i]->get_acceleration())) gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_acceleration());
df56baf2
JM
767 }
768 gcode->stream->printf("\n");
769
43fa8fd2 770 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
771
772 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
773
774 gcode->stream->printf(";Max actuator feedrates in mm/sec:\nM203.1 ");
775 for (int i = 0; i < n_motors; ++i) {
776 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
777 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
778 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_max_rate());
779 }
780 gcode->stream->printf("\n");
b7cd847e
JM
781
782 // get or save any arm solution specific optional values
783 BaseSolution::arm_options_t options;
784 if(arm_solution->get_optional(options) && !options.empty()) {
785 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 786 for(auto &i : options) {
b7cd847e
JM
787 gcode->stream->printf(" %c%1.4f", i.first, i.second);
788 }
789 gcode->stream->printf("\n");
790 }
6e92ab91 791
807b9b57
JM
792 // save wcs_offsets and current_wcs
793 // TODO this may need to be done whenever they change to be compliant
794 gcode->stream->printf(";WCS settings\n");
40fd5d98 795 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
03b01bac 796 int n = 1;
807b9b57 797 for(auto &i : wcs_offsets) {
2791c829 798 if(i != wcs_t(0, 0, 0)) {
807b9b57
JM
799 float x, y, z;
800 std::tie(x, y, z) = i;
40fd5d98 801 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 802 }
807b9b57
JM
803 ++n;
804 }
3aad33c7
JM
805 if(save_g92) {
806 // linuxcnc saves G92, so we do too if configured, default is to not save to maintain backward compatibility
807 // 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
808 if(g92_offset != wcs_t(0, 0, 0)) {
809 float x, y, z;
810 std::tie(x, y, z) = g92_offset;
811 gcode->stream->printf("G92.3 X%f Y%f Z%f\n", x, y, z); // sets G92 to the specified values
812 }
67a649dd
JM
813 }
814 }
807b9b57 815 break;
33e4cc02 816
b7cd847e 817 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6 818 // the parameter args could be any letter each arm solution only accepts certain ones
03b01bac 819 BaseSolution::arm_options_t options = gcode->get_args();
ebc75fc6
JM
820 options.erase('S'); // don't include the S
821 options.erase('U'); // don't include the U
822 if(options.size() > 0) {
823 // set the specified options
824 arm_solution->set_optional(options);
825 }
826 options.clear();
b7cd847e 827 if(arm_solution->get_optional(options)) {
ebc75fc6 828 // foreach optional value
4710532a 829 for(auto &i : options) {
b7cd847e
JM
830 // print all current values of supported options
831 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 832 gcode->add_nl = true;
ec4773e5
JM
833 }
834 }
ec4773e5 835
4a839bea 836 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 837 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
838 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
839
03b01bac 840 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
4a839bea
JM
841 this->mm_per_line_segment = gcode->get_value('U');
842 this->delta_segments_per_second = 0;
843 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 844 }
4a839bea 845
ec4773e5 846 break;
b7cd847e 847 }
6989211c 848 }
494dc541
JM
849 }
850
29e809e0 851 if( motion_mode != NONE) {
e560f057 852 is_g123= motion_mode != SEEK;
29e809e0 853 process_move(gcode, motion_mode);
e560f057
JM
854
855 }else{
856 is_g123= false;
00e607c7 857 }
6bc4a00a 858
c2f7c261
JM
859 next_command_is_MCS = false; // must be on same line as G0 or G1
860}
350c8a60 861
325ed08b
JM
862int Robot::get_active_extruder() const
863{
864 for (int i = E_AXIS; i < n_motors; ++i) {
865 // find first selected extruder
aef2eec0 866 if(actuators[i]->is_extruder() && actuators[i]->is_selected()) return i;
325ed08b
JM
867 }
868 return 0;
869}
870
5d2319a9 871// process a G0/G1/G2/G3
29e809e0 872void Robot::process_move(Gcode *gcode, enum MOTION_MODE_T motion_mode)
c2f7c261 873{
2791c829 874 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
ad6a77d1 875 // get XYZ and one E (which goes to the selected extruder)
29e809e0
JM
876 float param[4]{NAN, NAN, NAN, NAN};
877
878 // process primary axis
350c8a60
JM
879 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
880 char letter= 'X'+i;
881 if( gcode->has_letter(letter) ) {
882 param[i] = this->to_millimeters(gcode->get_value(letter));
350c8a60
JM
883 }
884 }
6bc4a00a 885
c2f7c261 886 float offset[3]{0,0,0};
4710532a
JM
887 for(char letter = 'I'; letter <= 'K'; letter++) {
888 if( gcode->has_letter(letter) ) {
889 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
890 }
891 }
00e607c7 892
c2f7c261 893 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
29e809e0 894 float target[n_motors];
45ca77ec 895 memcpy(target, machine_position, n_motors*sizeof(float));
29e809e0 896
350c8a60
JM
897 if(!next_command_is_MCS) {
898 if(this->absolute_mode) {
c2f7c261
JM
899 // apply wcs offsets and g92 offset and tool offset
900 if(!isnan(param[X_AXIS])) {
901 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);
902 }
903
904 if(!isnan(param[Y_AXIS])) {
905 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);
906 }
907
908 if(!isnan(param[Z_AXIS])) {
909 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);
910 }
350c8a60
JM
911
912 }else{
45ca77ec 913 // they are deltas from the machine_position if specified
350c8a60 914 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
45ca77ec 915 if(!isnan(param[i])) target[i] = param[i] + machine_position[i];
a6bbe768
JM
916 }
917 }
918
350c8a60 919 }else{
c2f7c261
JM
920 // already in machine coordinates, we do not add tool offset for that
921 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
922 if(!isnan(param[i])) target[i] = param[i];
923 }
c2885de8 924 }
6bc4a00a 925
37b7b898
JM
926 float delta_e= NAN;
927
928 #if MAX_ROBOT_ACTUATORS > 3
29e809e0 929 // process extruder parameters, for active extruder only (only one active extruder at a time)
325ed08b 930 int selected_extruder= 0;
29e809e0 931 if(gcode->has_letter('E')) {
325ed08b
JM
932 selected_extruder= get_active_extruder();
933 param[E_AXIS]= gcode->get_value('E');
29e809e0
JM
934 }
935
936 // do E for the selected extruder
29e809e0
JM
937 if(selected_extruder > 0 && !isnan(param[E_AXIS])) {
938 if(this->e_absolute_mode) {
939 target[selected_extruder]= param[E_AXIS];
45ca77ec 940 delta_e= target[selected_extruder] - machine_position[selected_extruder];
29e809e0
JM
941 }else{
942 delta_e= param[E_AXIS];
45ca77ec 943 target[selected_extruder] = delta_e + machine_position[selected_extruder];
29e809e0
JM
944 }
945 }
946
37b7b898
JM
947 // 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
948 for (int i = A_AXIS; i < n_motors; ++i) {
949 char letter= 'A'+i-A_AXIS;
950 if(gcode->has_letter(letter)) {
951 float p= gcode->get_value(letter);
952 if(this->absolute_mode) {
953 target[i]= p;
954 }else{
5acc50ad 955 target[i]= p + machine_position[i];
37b7b898
JM
956 }
957 }
958 }
959 #endif
960
4710532a 961 if( gcode->has_letter('F') ) {
29e809e0 962 if( motion_mode == SEEK )
da947c62 963 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 964 else
da947c62 965 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 966 }
6bc4a00a 967
73cc27d2 968 // S is modal When specified on a G0/1/2/3 command
e560f057
JM
969 if(gcode->has_letter('S')) s_value= gcode->get_value('S');
970
350c8a60 971 bool moved= false;
29e809e0
JM
972
973 // Perform any physical actions
974 switch(motion_mode) {
975 case NONE: break;
976
977 case SEEK:
978 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute, delta_e );
350c8a60 979 break;
29e809e0
JM
980
981 case LINEAR:
982 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute, delta_e );
350c8a60 983 break;
29e809e0
JM
984
985 case CW_ARC:
986 case CCW_ARC:
374d0777 987 // Note arcs are not currently supported by extruder based machines, as 3D slicers do not use arcs (G2/G3)
29e809e0 988 moved= this->compute_arc(gcode, offset, target, motion_mode);
350c8a60 989 break;
4cff3ded 990 }
13e4a3f9 991
c2f7c261 992 if(moved) {
45ca77ec
JM
993 // set machine_position to the calculated target
994 memcpy(machine_position, target, n_motors*sizeof(float));
350c8a60 995 }
edac9072
AW
996}
997
a6bbe768 998// reset the machine position for all axis. Used for homing.
fd2341bc 999// after homing we supply the cartesian coordinates that the head is at when homed,
bccd3a3e
JM
1000// however for Z this is the compensated machine position (if enabled)
1001// So we need to apply the inverse compensation transform to the supplied coordinates to get the correct machine position
8fe38353 1002// this will make the results from M114 and ? consistent after homing.
bccd3a3e 1003// 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
1004void Robot::reset_axis_position(float x, float y, float z)
1005{
fd2341bc 1006 // set both the same initially
45ca77ec
JM
1007 compensated_machine_position[X_AXIS]= machine_position[X_AXIS] = x;
1008 compensated_machine_position[Y_AXIS]= machine_position[Y_AXIS] = y;
1009 compensated_machine_position[Z_AXIS]= machine_position[Z_AXIS] = z;
cef9acea 1010
8fe38353 1011 if(compensationTransform) {
45ca77ec
JM
1012 // apply inverse transform to get machine_position
1013 compensationTransform(machine_position, true);
8fe38353
JM
1014 }
1015
fd2341bc 1016 // now set the actuator positions based on the supplied compensated position
807b9b57 1017 ActuatorCoordinates actuator_pos;
45ca77ec 1018 arm_solution->cartesian_to_actuator(this->compensated_machine_position, actuator_pos);
29e809e0 1019 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
cef9acea
JM
1020 actuators[i]->change_last_milestone(actuator_pos[i]);
1021}
1022
de2ee57c 1023// Reset the position for an axis (used in homing, and to reset extruder after suspend)
4710532a
JM
1024void Robot::reset_axis_position(float position, int axis)
1025{
45ca77ec 1026 compensated_machine_position[axis] = position;
de2ee57c 1027 if(axis <= Z_AXIS) {
45ca77ec 1028 reset_axis_position(compensated_machine_position[X_AXIS], compensated_machine_position[Y_AXIS], compensated_machine_position[Z_AXIS]);
7d9e5765 1029
72420864 1030#if MAX_ROBOT_ACTUATORS > 3
96ef0809 1031 }else if(axis < n_motors) {
7d9e5765
JM
1032 // ABC and/or extruders need to be set as there is no arm solution for them
1033 machine_position[axis]= compensated_machine_position[axis]= position;
1034 actuators[axis]->change_last_milestone(machine_position[axis]);
72420864 1035#endif
de2ee57c 1036 }
4cff3ded
AW
1037}
1038
932a3995 1039// similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
abf706e6 1040// then sets the axis positions to match. currently only called from Endstops.cpp and RotaryDeltaCalibration.cpp
93f20a8c 1041void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
586cc733 1042{
fdfa00d2
JM
1043 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1044 if(!isnan(ac[i])) actuators[i]->change_last_milestone(ac[i]);
1045 }
586cc733
JM
1046
1047 // now correct axis positions then recorrect actuator to account for rounding
1048 reset_position_from_current_actuator_position();
1049}
1050
a6bbe768 1051// Use FK to find out where actuator is and reset to match
b6187406 1052// TODO maybe we should only reset axis that are being homed unless this is due to a ON_HALT
728477c4
JM
1053void Robot::reset_position_from_current_actuator_position()
1054{
807b9b57 1055 ActuatorCoordinates actuator_pos;
9fc4679b 1056 for (size_t i = X_AXIS; i < n_motors; i++) {
45ca77ec 1057 // NOTE actuator::current_position is curently NOT the same as actuator::machine_position after an abrupt abort
807b9b57
JM
1058 actuator_pos[i] = actuators[i]->get_current_position();
1059 }
58587001
JM
1060
1061 // discover machine position from where actuators actually are
45ca77ec
JM
1062 arm_solution->actuator_to_cartesian(actuator_pos, compensated_machine_position);
1063 memcpy(machine_position, compensated_machine_position, sizeof machine_position);
cf91d4f3 1064
45ca77ec
JM
1065 // compensated_machine_position includes the compensation transform so we need to get the inverse to get actual machine_position
1066 if(compensationTransform) compensationTransform(machine_position, true); // get inverse compensation transform
cf91d4f3 1067
45ca77ec 1068 // now reset actuator::machine_position, NOTE this may lose a little precision as FK is not always entirely accurate.
58587001 1069 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
932a3995 1070 // to get everything in perfect sync.
45ca77ec 1071 arm_solution->cartesian_to_actuator(compensated_machine_position, actuator_pos);
325ed08b 1072 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
7baae81a 1073 actuators[i]->change_last_milestone(actuator_pos[i]);
325ed08b
JM
1074 }
1075
1076 // Handle extruders and/or ABC axis
1077 #if MAX_ROBOT_ACTUATORS > 3
9fc4679b 1078 for (int i = A_AXIS; i < n_motors; i++) {
325ed08b
JM
1079 // ABC and/or extruders just need to set machine_position and compensated_machine_position
1080 float ap= actuator_pos[i];
aef2eec0 1081 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 1082 machine_position[i]= compensated_machine_position[i]= ap;
5d34cbb3 1083 actuators[i]->change_last_milestone(actuator_pos[i]); // this updates the last_milestone in the actuator
325ed08b
JM
1084 }
1085 #endif
728477c4 1086}
edac9072 1087
ad6a77d1 1088// Convert target (in machine coordinates) to machine_position, then convert to actuator position and append this to the planner
45ca77ec 1089// target is in machine coordinates without the compensation transform, however we save a compensated_machine_position that includes
c2f7c261 1090// all transforms and is what we actually convert to actuator positions
c1ebb1fe 1091bool Robot::append_milestone(const float target[], float rate_mm_s)
df6a30f2 1092{
29e809e0 1093 float deltas[n_motors];
1a936198 1094 float transformed_target[n_motors]; // adjust target for bed compensation
29e809e0 1095 float unit_vec[N_PRIMARY_AXIS];
df6a30f2 1096
3632a517 1097 // unity transform by default
29e809e0 1098 memcpy(transformed_target, target, n_motors*sizeof(float));
5e45206a 1099
350c8a60 1100 // check function pointer and call if set to transform the target to compensate for bed
c1ebb1fe 1101 if(compensationTransform) {
350c8a60 1102 // some compensation strategies can transform XYZ, some just change Z
8fe38353 1103 compensationTransform(transformed_target, false);
00e607c7 1104 }
807b9b57 1105
29e809e0 1106 bool move= false;
b5bd71f8 1107 float sos= 0; // sum of squares for just primary axis (XYZ usually)
29e809e0 1108
b84bd559 1109 // find distance moved by each axis, use transformed target from the current compensated machine position
ec45206d 1110 for (size_t i = 0; i < n_motors; i++) {
45ca77ec 1111 deltas[i] = transformed_target[i] - compensated_machine_position[i];
29e809e0
JM
1112 if(deltas[i] == 0) continue;
1113 // at least one non zero delta
1114 move = true;
b5bd71f8 1115 if(i < N_PRIMARY_AXIS) {
29e809e0 1116 sos += powf(deltas[i], 2);
121094a5 1117 }
3632a517 1118 }
aab6cbba 1119
29e809e0
JM
1120 // nothing moved
1121 if(!move) return false;
1122
850b4eeb 1123 // see if this is a primary axis move or not
b5bd71f8
JM
1124 bool auxilliary_move= true;
1125 for (int i = 0; i < N_PRIMARY_AXIS; ++i) {
1126 if(deltas[i] != 0) {
1127 auxilliary_move= false;
1128 break;
1129 }
1130 }
29e809e0 1131
850b4eeb
JM
1132 // total movement, use XYZ if a primary axis otherwise we calculate distance for E after scaling to mm
1133 float distance= auxilliary_move ? 0 : sqrtf(sos);
df6a30f2 1134
a6bbe768
JM
1135 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
1136 // 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 1137 if(!auxilliary_move && distance < 0.00001F) return false;
a6bbe768 1138
a6bbe768 1139
29e809e0 1140 if(!auxilliary_move) {
b5bd71f8 1141 for (size_t i = X_AXIS; i < N_PRIMARY_AXIS; i++) {
14a90ad5 1142 // find distance unit vector for primary axis only
850b4eeb 1143 unit_vec[i] = deltas[i] / distance;
df6a30f2 1144
14a90ad5
JM
1145 // Do not move faster than the configured cartesian limits for XYZ
1146 if ( max_speeds[i] > 0 ) {
1147 float axis_speed = fabsf(unit_vec[i] * rate_mm_s);
1148
1149 if (axis_speed > max_speeds[i])
1150 rate_mm_s *= ( max_speeds[i] / axis_speed );
2cd32d70 1151 }
7b470506
AW
1152 }
1153 }
4cff3ded 1154
c2f7c261 1155 // find actuator position given the machine position, use actual adjusted target
29e809e0 1156 ActuatorCoordinates actuator_pos;
84cf4071
JM
1157 if(!disable_arm_solution) {
1158 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
1159
1160 }else{
1161 // basically the same as cartesian, would be used for special homing situations like for scara
1162 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1163 actuator_pos[i] = transformed_target[i];
1164 }
1165 }
df6a30f2 1166
13ad7234 1167#if MAX_ROBOT_ACTUATORS > 3
850b4eeb 1168 sos= 0;
ad6a77d1 1169 // for the extruders just copy the position, and possibly scale it from mm³ to mm
374d0777 1170 for (size_t i = E_AXIS; i < n_motors; i++) {
850b4eeb 1171 actuator_pos[i]= transformed_target[i];
aef2eec0 1172 if(actuators[i]->is_extruder() && get_e_scale_fnc) {
29e809e0
JM
1173 // NOTE this relies on the fact only one extruder is active at a time
1174 // scale for volumetric or flow rate
1175 // TODO is this correct? scaling the absolute target? what if the scale changes?
ec45206d 1176 // for volumetric it basically converts mm³ to mm, but what about flow rate?
121094a5 1177 actuator_pos[i] *= get_e_scale_fnc();
29e809e0 1178 }
850b4eeb
JM
1179 if(auxilliary_move) {
1180 // for E only moves we need to use the scaled E to calculate the distance
a3b16417 1181 sos += powf(actuator_pos[i] - actuators[i]->get_last_milestone(), 2);
850b4eeb
JM
1182 }
1183 }
1184 if(auxilliary_move) {
850b4eeb 1185 distance= sqrtf(sos); // distance in mm of the e move
1843a68f 1186 if(distance < 0.00001F) return false;
29e809e0
JM
1187 }
1188#endif
1189
1190 // use default acceleration to start with
1191 float acceleration = default_acceleration;
1192
850b4eeb 1193 float isecs = rate_mm_s / distance;
29e809e0 1194
df6a30f2 1195 // check per-actuator speed limits
29e809e0
JM
1196 for (size_t actuator = 0; actuator < n_motors; actuator++) {
1197 float d = fabsf(actuator_pos[actuator] - actuators[actuator]->get_last_milestone());
1198 if(d == 0 || !actuators[actuator]->is_selected()) continue; // no movement for this actuator
1199
1200 float actuator_rate= d * isecs;
03b01bac 1201 if (actuator_rate > actuators[actuator]->get_max_rate()) {
3494f3d0 1202 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
850b4eeb 1203 isecs = rate_mm_s / distance;
928467c0 1204 }
29e809e0 1205
df56baf2 1206 // adjust acceleration to lowest found, for now just primary axis unless it is an auxiliary move
14a90ad5 1207 // 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 1208 if(auxilliary_move || actuator < N_PRIMARY_AXIS) {
df56baf2
JM
1209 float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
1210 if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
850b4eeb 1211 float ca = fabsf((d/distance) * acceleration);
df56baf2
JM
1212 if (ca > ma) {
1213 acceleration *= ( ma / ca );
1214 }
29e809e0 1215 }
14a90ad5 1216 }
928467c0
JM
1217 }
1218
edac9072 1219 // Append the block to the planner
850b4eeb 1220 // 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 1221 if(THEKERNEL->planner->append_block( actuator_pos, n_motors, rate_mm_s, distance, auxilliary_move ? nullptr : unit_vec, acceleration, s_value, is_g123)) {
b84bd559 1222 // this is the new compensated machine position
45ca77ec 1223 memcpy(this->compensated_machine_position, transformed_target, n_motors*sizeof(float));
07879e05 1224 return true;
6f5d947f
JM
1225 }
1226
07879e05
JM
1227 // no actual move
1228 return false;
4cff3ded
AW
1229}
1230
121094a5
JM
1231// Used to plan a single move used by things like endstops when homing, zprobe, extruder firmware retracts etc.
1232bool Robot::delta_move(const float *delta, float rate_mm_s, uint8_t naxis)
c8bac202
JM
1233{
1234 if(THEKERNEL->is_halted()) return false;
1235
121094a5 1236 // catch negative or zero feed rates
c8bac202
JM
1237 if(rate_mm_s <= 0.0F) {
1238 return false;
1239 }
1240
45ca77ec 1241 // get the absolute target position, default is current machine_position
121094a5 1242 float target[n_motors];
45ca77ec 1243 memcpy(target, machine_position, n_motors*sizeof(float));
c8bac202 1244
121094a5
JM
1245 // add in the deltas to get new target
1246 for (int i= 0; i < naxis; i++) {
1247 target[i] += delta[i];
c8bac202 1248 }
c8bac202 1249
45ca77ec 1250 // submit for planning and if moved update machine_position
c1ebb1fe 1251 if(append_milestone(target, rate_mm_s)) {
45ca77ec 1252 memcpy(machine_position, target, n_motors*sizeof(float));
121094a5 1253 return true;
29e809e0 1254 }
c8bac202 1255
121094a5 1256 return false;
c8bac202
JM
1257}
1258
edac9072 1259// Append a move to the queue ( cutting it into segments if needed )
29e809e0 1260bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s, float delta_e)
4710532a 1261{
121094a5
JM
1262 // catch negative or zero feed rates and return the same error as GRBL does
1263 if(rate_mm_s <= 0.0F) {
1264 gcode->is_error= true;
1265 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1266 return false;
1267 }
29e809e0
JM
1268
1269 // Find out the distance for this move in XYZ in MCS
45ca77ec 1270 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 1271
374d0777 1272 if(millimeters_of_travel < 0.00001F) {
121094a5
JM
1273 // we have no movement in XYZ, probably E only extrude or retract
1274 return this->append_milestone(target, rate_mm_s);
29e809e0
JM
1275 }
1276
1277 /*
850d5f12
JM
1278 For extruders, we need to do some extra work to limit the volumetric rate if specified...
1279 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
1280 We ask Extruder to do all the work but we need to pass in the relevant data.
1281 NOTE we need to do this before we segment the line (for deltas)
29e809e0
JM
1282 */
1283 if(!isnan(delta_e) && gcode->has_g && gcode->g == 1) {
1284 float data[2]= {delta_e, rate_mm_s / millimeters_of_travel};
d2adef5e 1285 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
29e809e0 1286 rate_mm_s *= data[1]; // adjust the feedrate
d2adef5e
JM
1287 }
1288 }
1289
c2f7c261 1290 // 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
1291 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
1292 // 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 1293 uint16_t segments;
5984acdf 1294
a3e1326a 1295 if(this->disable_segmentation || (!segment_z_moves && !gcode->has_letter('X') && !gcode->has_letter('Y'))) {
778093ce
JM
1296 segments= 1;
1297
1298 } else if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
1299 // enabled if set to something > 1, it is set to 0.0 by default
1300 // segment based on current speed and requested segments per second
1301 // the faster the travel speed the fewer segments needed
1302 // NOTE rate is mm/sec and we take into account any speed override
29e809e0 1303 float seconds = millimeters_of_travel / rate_mm_s;
9502f9d5 1304 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 1305 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 1306
4710532a
JM
1307 } else {
1308 if(this->mm_per_line_segment == 0.0F) {
1309 segments = 1; // don't split it up
1310 } else {
29e809e0 1311 segments = ceilf( millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
1312 }
1313 }
5984acdf 1314
350c8a60 1315 bool moved= false;
4710532a 1316 if (segments > 1) {
2ba859c9 1317 // A vector to keep track of the endpoint of each segment
29e809e0
JM
1318 float segment_delta[n_motors];
1319 float segment_end[n_motors];
45ca77ec 1320 memcpy(segment_end, machine_position, n_motors*sizeof(float));
2ba859c9
MM
1321
1322 // How far do we move each segment?
29e809e0 1323 for (int i = 0; i < n_motors; i++)
45ca77ec 1324 segment_delta[i] = (target[i] - machine_position[i]) / segments;
4cff3ded 1325
c8e0fb15
MM
1326 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
1327 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 1328 for (int i = 1; i < segments; i++) {
350c8a60 1329 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
29e809e0
JM
1330 for (int i = 0; i < n_motors; i++)
1331 segment_end[i] += segment_delta[i];
2ba859c9
MM
1332
1333 // Append the end of this segment to the queue
121094a5 1334 bool b= this->append_milestone(segment_end, rate_mm_s);
350c8a60 1335 moved= moved || b;
2ba859c9 1336 }
4cff3ded 1337 }
5984acdf
MM
1338
1339 // Append the end of this full move to the queue
121094a5 1340 if(this->append_milestone(target, rate_mm_s)) moved= true;
2134bcf2 1341
a6bbe768 1342 this->next_command_is_MCS = false; // always reset this
00e607c7 1343
350c8a60 1344 return moved;
4cff3ded
AW
1345}
1346
4cff3ded 1347
edac9072 1348// Append an arc to the queue ( cutting it into segments as needed )
850d5f12 1349// TODO does not support any E parameters so cannot be used for 3D printing.
350c8a60 1350bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
4710532a 1351{
121094a5
JM
1352 float rate_mm_s= this->feed_rate / seconds_per_minute;
1353 // catch negative or zero feed rates and return the same error as GRBL does
1354 if(rate_mm_s <= 0.0F) {
1355 gcode->is_error= true;
1356 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1357 return false;
1358 }
aab6cbba 1359
edac9072 1360 // Scary math
45ca77ec
JM
1361 float center_axis0 = this->machine_position[this->plane_axis_0] + offset[this->plane_axis_0];
1362 float center_axis1 = this->machine_position[this->plane_axis_1] + offset[this->plane_axis_1];
1363 float linear_travel = target[this->plane_axis_2] - this->machine_position[this->plane_axis_2];
1ad23cd3
MM
1364 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
1365 float r_axis1 = -offset[this->plane_axis_1];
1366 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
1367 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
aab6cbba 1368
51871fb8 1369 // Patch from GRBL Firmware - Christoph Baumann 04072015
aab6cbba 1370 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
fb4c9d09 1371 float angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
5fa0c173 1372 if (is_clockwise) { // Correct atan2 output per direction
29e809e0 1373 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= (2 * PI); }
5fa0c173 1374 } else {
29e809e0 1375 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += (2 * PI); }
4710532a 1376 }
aab6cbba 1377
edac9072 1378 // Find the distance for this gcode
29e809e0 1379 float millimeters_of_travel = hypotf(angular_travel * radius, fabsf(linear_travel));
436a2cd1 1380
edac9072 1381 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
29e809e0 1382 if( millimeters_of_travel < 0.00001F ) {
350c8a60 1383 return false;
4710532a 1384 }
5dcb2ff3 1385
83c6e067
RA
1386 // limit segments by maximum arc error
1387 float arc_segment = this->mm_per_arc_segment;
4d0f60a9 1388 if ((this->mm_max_arc_error > 0) && (2 * radius > this->mm_max_arc_error)) {
83c6e067
RA
1389 float min_err_segment = 2 * sqrtf((this->mm_max_arc_error * (2 * radius - this->mm_max_arc_error)));
1390 if (this->mm_per_arc_segment < min_err_segment) {
1391 arc_segment = min_err_segment;
1392 }
1393 }
5984acdf 1394 // Figure out how many segments for this gcode
f8935932 1395 // 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 1396 uint16_t segments = ceilf(millimeters_of_travel / arc_segment);
aab6cbba 1397
29e809e0 1398 //printf("Radius %f - Segment Length %f - Number of Segments %d\r\n",radius,arc_segment,segments); // Testing Purposes ONLY
4710532a
JM
1399 float theta_per_segment = angular_travel / segments;
1400 float linear_per_segment = linear_travel / segments;
aab6cbba
AW
1401
1402 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1403 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1404 r_T = [cos(phi) -sin(phi);
1405 sin(phi) cos(phi] * r ;
1406 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1407 defined from the circle center to the initial position. Each line segment is formed by successive
1408 vector rotations. This requires only two cos() and sin() computations to form the rotation
1409 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1ad23cd3 1410 all float numbers are single precision on the Arduino. (True float precision will not have
aab6cbba
AW
1411 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1412 tool precision in some cases. Therefore, arc path correction is implemented.
1413
1414 Small angle approximation may be used to reduce computation overhead further. This approximation
1415 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1416 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1417 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1418 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1419 issue for CNC machines with the single precision Arduino calculations.
1420 This approximation also allows mc_arc to immediately insert a line segment into the planner
1421 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1422 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1423 This is important when there are successive arc motions.
1424 */
1425 // Vector rotation matrix values
4710532a 1426 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1ad23cd3 1427 float sin_T = theta_per_segment;
aab6cbba 1428
5acc50ad 1429 // TODO we need to handle the ABC axis here by segmenting them
1ad23cd3
MM
1430 float arc_target[3];
1431 float sin_Ti;
1432 float cos_Ti;
1433 float r_axisi;
aab6cbba
AW
1434 uint16_t i;
1435 int8_t count = 0;
1436
1437 // Initialize the linear axis
45ca77ec 1438 arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2];
aab6cbba 1439
350c8a60 1440 bool moved= false;
4710532a 1441 for (i = 1; i < segments; i++) { // Increment (segments-1)
350c8a60 1442 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
aab6cbba 1443
b66fb830 1444 if (count < this->arc_correction ) {
4710532a
JM
1445 // Apply vector rotation matrix
1446 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1447 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1448 r_axis1 = r_axisi;
1449 count++;
aab6cbba 1450 } else {
4710532a
JM
1451 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1452 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1453 cos_Ti = cosf(i * theta_per_segment);
1454 sin_Ti = sinf(i * theta_per_segment);
1455 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1456 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1457 count = 0;
aab6cbba
AW
1458 }
1459
1460 // Update arc_target location
1461 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1462 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1463 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
1464
1465 // Append this segment to the queue
121094a5 1466 bool b= this->append_milestone(arc_target, rate_mm_s);
350c8a60 1467 moved= moved || b;
aab6cbba 1468 }
edac9072 1469
aab6cbba 1470 // Ensure last segment arrives at target location.
121094a5 1471 if(this->append_milestone(target, rate_mm_s)) moved= true;
350c8a60
JM
1472
1473 return moved;
aab6cbba
AW
1474}
1475
edac9072 1476// Do the math for an arc and add it to the queue
29e809e0 1477bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[], enum MOTION_MODE_T motion_mode)
4710532a 1478{
aab6cbba
AW
1479
1480 // Find the radius
13addf09 1481 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
1482
1483 // Set clockwise/counter-clockwise sign for mc_arc computations
1484 bool is_clockwise = false;
29e809e0 1485 if( motion_mode == CW_ARC ) {
4710532a
JM
1486 is_clockwise = true;
1487 }
aab6cbba
AW
1488
1489 // Append arc
350c8a60 1490 return this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
1491}
1492
1493
4710532a
JM
1494float Robot::theta(float x, float y)
1495{
1496 float t = atanf(x / fabs(y));
1497 if (y > 0) {
1498 return(t);
1499 } else {
1500 if (t > 0) {
29e809e0 1501 return(PI - t);
4710532a 1502 } else {
29e809e0 1503 return(-PI - t);
4710532a
JM
1504 }
1505 }
4cff3ded
AW
1506}
1507
4710532a
JM
1508void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1509{
4cff3ded
AW
1510 this->plane_axis_0 = axis_0;
1511 this->plane_axis_1 = axis_1;
1512 this->plane_axis_2 = axis_2;
1513}
1514
fae93525 1515void Robot::clearToolOffset()
4710532a 1516{
c2f7c261 1517 this->tool_offset= wcs_t(0,0,0);
fae93525
JM
1518}
1519
1520void Robot::setToolOffset(const float offset[3])
1521{
c2f7c261 1522 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
5966b7d0
AT
1523}
1524
0ec2f63a
JM
1525float Robot::get_feed_rate() const
1526{
1527 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1528}