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