Merge pull request #877 from wolfmanjm/upstreamedge
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
CommitLineData
df27a6a3 1/*
aab6cbba 2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
4cff3ded
AW
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
df27a6a3 5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
4cff3ded
AW
6*/
7
8#include "libs/Module.h"
9#include "libs/Kernel.h"
5673fe39 10
c3df978d
JM
11#include "mbed.h" // for us_ticker_read()
12
5673fe39 13#include <math.h>
4cff3ded
AW
14#include <string>
15using std::string;
5673fe39 16
4cff3ded 17#include "Planner.h"
3fceb8eb 18#include "Conveyor.h"
4cff3ded 19#include "Robot.h"
5673fe39
MM
20#include "nuts_bolts.h"
21#include "Pin.h"
22#include "StepperMotor.h"
23#include "Gcode.h"
5647f709 24#include "PublicDataRequest.h"
928467c0 25#include "PublicData.h"
4cff3ded
AW
26#include "arm_solutions/BaseSolution.h"
27#include "arm_solutions/CartesianSolution.h"
c41d6d95 28#include "arm_solutions/RotatableCartesianSolution.h"
2a06c415 29#include "arm_solutions/LinearDeltaSolution.h"
11a39396 30#include "arm_solutions/RotaryDeltaSolution.h"
bdaaa75d 31#include "arm_solutions/HBotSolution.h"
fff1e42d 32#include "arm_solutions/CoreXZSolution.h"
1217e470 33#include "arm_solutions/MorganSCARASolution.h"
61134a65 34#include "StepTicker.h"
7af0714f
JM
35#include "checksumm.h"
36#include "utils.h"
8d54c34c 37#include "ConfigValue.h"
5966b7d0 38#include "libs/StreamOutput.h"
dd0a7cfa 39#include "StreamOutputPool.h"
928467c0 40#include "ExtruderPublicAccess.h"
0ec2f63a
JM
41#include "GcodeDispatch.h"
42
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")
49#define arc_correction_checksum CHECKSUM("arc_correction")
50#define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
51#define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
52#define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
a3e1326a 53#define segment_z_moves_checksum CHECKSUM("segment_z_moves")
43424972
JM
54
55// arm solutions
78d0e16a
MM
56#define arm_solution_checksum CHECKSUM("arm_solution")
57#define cartesian_checksum CHECKSUM("cartesian")
58#define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
59#define rostock_checksum CHECKSUM("rostock")
2a06c415 60#define linear_delta_checksum CHECKSUM("linear_delta")
11a39396 61#define rotary_delta_checksum CHECKSUM("rotary_delta")
78d0e16a
MM
62#define delta_checksum CHECKSUM("delta")
63#define hbot_checksum CHECKSUM("hbot")
64#define corexy_checksum CHECKSUM("corexy")
fff1e42d 65#define corexz_checksum CHECKSUM("corexz")
78d0e16a 66#define kossel_checksum CHECKSUM("kossel")
1217e470 67#define morgan_checksum CHECKSUM("morgan")
78d0e16a 68
78d0e16a
MM
69// new-style actuator stuff
70#define actuator_checksum CHEKCSUM("actuator")
71
72#define step_pin_checksum CHECKSUM("step_pin")
73#define dir_pin_checksum CHEKCSUM("dir_pin")
74#define en_pin_checksum CHECKSUM("en_pin")
75
76#define steps_per_mm_checksum CHECKSUM("steps_per_mm")
df6a30f2 77#define max_rate_checksum CHECKSUM("max_rate")
78d0e16a
MM
78
79#define alpha_checksum CHECKSUM("alpha")
80#define beta_checksum CHECKSUM("beta")
81#define gamma_checksum CHECKSUM("gamma")
82
38bf9a1c
JM
83#define NEXT_ACTION_DEFAULT 0
84#define NEXT_ACTION_DWELL 1
85#define NEXT_ACTION_GO_HOME 2
86
87#define MOTION_MODE_SEEK 0 // G0
88#define MOTION_MODE_LINEAR 1 // G1
89#define MOTION_MODE_CW_ARC 2 // G2
90#define MOTION_MODE_CCW_ARC 3 // G3
91#define MOTION_MODE_CANCEL 4 // G80
92
93#define PATH_CONTROL_MODE_EXACT_PATH 0
94#define PATH_CONTROL_MODE_EXACT_STOP 1
95#define PATH_CONTROL_MODE_CONTINOUS 2
96
97#define PROGRAM_FLOW_RUNNING 0
98#define PROGRAM_FLOW_PAUSED 1
99#define PROGRAM_FLOW_COMPLETED 2
100
101#define SPINDLE_DIRECTION_CW 0
102#define SPINDLE_DIRECTION_CCW 1
103
5fa0c173
PA
104#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians)
105
edac9072
AW
106// 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
107// It takes care of cutting arcs into segments, same thing for line that are too long
108
4710532a
JM
109Robot::Robot()
110{
a1b7e9f0 111 this->inch_mode = false;
0e8b102e 112 this->absolute_mode = true;
df27a6a3 113 this->motion_mode = MOTION_MODE_SEEK;
4cff3ded 114 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
df27a6a3 115 clear_vector(this->last_milestone);
c2f7c261 116 clear_vector(this->last_machine_position);
0b804a41 117 this->arm_solution = NULL;
da947c62 118 seconds_per_minute = 60.0F;
fae93525 119 this->clearToolOffset();
03b01bac
JM
120 this->compensationTransform = nullptr;
121 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
122 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
a6bbe768 123 this->next_command_is_MCS = false;
778093ce 124 this->disable_segmentation= false;
4cff3ded
AW
125}
126
127//Called when the module has just been loaded
4710532a
JM
128void Robot::on_module_loaded()
129{
4cff3ded
AW
130 this->register_for_event(ON_GCODE_RECEIVED);
131
132 // Configuration
807b9b57 133 this->load_config();
da24d6ae
AW
134}
135
807b9b57
JM
136#define ACTUATOR_CHECKSUMS(X) { \
137 CHECKSUM(X "_step_pin"), \
138 CHECKSUM(X "_dir_pin"), \
139 CHECKSUM(X "_en_pin"), \
140 CHECKSUM(X "_steps_per_mm"), \
141 CHECKSUM(X "_max_rate") \
142}
5984acdf 143
807b9b57
JM
144void Robot::load_config()
145{
edac9072
AW
146 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
147 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
148 // To make adding those solution easier, they have their own, separate object.
5984acdf 149 // Here we read the config to find out which arm solution to use
0b804a41 150 if (this->arm_solution) delete this->arm_solution;
eda9facc 151 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
d149c730 152 // Note checksums are not const expressions when in debug mode, so don't use switch
98761c28 153 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
314ab8f7 154 this->arm_solution = new HBotSolution(THEKERNEL->config);
bdaaa75d 155
fff1e42d
JJ
156 } else if(solution_checksum == corexz_checksum) {
157 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
158
2a06c415
JM
159 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
160 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
73a4e3c0 161
4710532a 162 } else if(solution_checksum == rotatable_cartesian_checksum) {
314ab8f7 163 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
b73a756d 164
11a39396
JM
165 } else if(solution_checksum == rotary_delta_checksum) {
166 this->arm_solution = new RotaryDeltaSolution(THEKERNEL->config);
c52b8675 167
1217e470
QH
168 } else if(solution_checksum == morgan_checksum) {
169 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
170
4710532a 171 } else if(solution_checksum == cartesian_checksum) {
314ab8f7 172 this->arm_solution = new CartesianSolution(THEKERNEL->config);
73a4e3c0 173
4710532a 174 } else {
314ab8f7 175 this->arm_solution = new CartesianSolution(THEKERNEL->config);
d149c730 176 }
73a4e3c0 177
6b661ab3
DP
178 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
179 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
180 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
181 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
182 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
183 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
78d0e16a 184
6b661ab3
DP
185 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
186 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
187 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
feb204be 188
a3e1326a
JM
189 this->segment_z_moves = THEKERNEL->config->value(segment_z_moves_checksum )->by_default(true)->as_bool();
190
78d0e16a 191 // Make our 3 StepperMotors
807b9b57
JM
192 uint16_t const checksums[][5] = {
193 ACTUATOR_CHECKSUMS("alpha"),
194 ACTUATOR_CHECKSUMS("beta"),
195 ACTUATOR_CHECKSUMS("gamma"),
196#if MAX_ROBOT_ACTUATORS > 3
197 ACTUATOR_CHECKSUMS("delta"),
198 ACTUATOR_CHECKSUMS("epsilon"),
199 ACTUATOR_CHECKSUMS("zeta")
200#endif
201 };
03b01bac 202 constexpr size_t actuator_checksum_count = sizeof(checksums) / sizeof(checksums[0]);
807b9b57
JM
203 static_assert(actuator_checksum_count >= k_max_actuators, "Robot checksum array too small for k_max_actuators");
204
205 size_t motor_count = std::min(this->arm_solution->get_actuator_count(), k_max_actuators);
206 for (size_t a = 0; a < motor_count; a++) {
207 Pin pins[3]; //step, dir, enable
208 for (size_t i = 0; i < 3; i++) {
209 pins[i].from_string(THEKERNEL->config->value(checksums[a][i])->by_default("nc")->as_string())->as_output();
210 }
03b01bac 211 actuators[a] = new StepperMotor(pins[0], pins[1], pins[2]);
78d0e16a 212
03b01bac 213 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
807b9b57
JM
214 actuators[a]->set_max_rate(THEKERNEL->config->value(checksums[a][4])->by_default(30000.0F)->as_number());
215 }
a84f0186 216
dd0a7cfa 217 check_max_actuator_speeds(); // check the configs are sane
df6a30f2 218
975469ad
MM
219 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
220 // so the first move can be correct if homing is not performed
807b9b57 221 ActuatorCoordinates actuator_pos;
975469ad 222 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
807b9b57 223 for (size_t i = 0; i < actuators.size(); i++)
975469ad 224 actuators[i]->change_last_milestone(actuator_pos[i]);
5966b7d0
AT
225
226 //this->clearToolOffset();
4cff3ded
AW
227}
228
212caccd
JM
229void Robot::push_state()
230{
03b01bac
JM
231 bool am = this->absolute_mode;
232 bool im = this->inch_mode;
a6bbe768 233 saved_state_t s(this->feed_rate, this->seek_rate, am, im, current_wcs);
212caccd
JM
234 state_stack.push(s);
235}
236
237void Robot::pop_state()
238{
03b01bac
JM
239 if(!state_stack.empty()) {
240 auto s = state_stack.top();
212caccd 241 state_stack.pop();
03b01bac
JM
242 this->feed_rate = std::get<0>(s);
243 this->seek_rate = std::get<1>(s);
244 this->absolute_mode = std::get<2>(s);
245 this->inch_mode = std::get<3>(s);
a6bbe768 246 this->current_wcs = std::get<4>(s);
212caccd
JM
247 }
248}
249
34210908
JM
250std::vector<Robot::wcs_t> Robot::get_wcs_state() const
251{
252 std::vector<wcs_t> v;
0b8b81b6 253 v.push_back(wcs_t(current_wcs, MAX_WCS, 0));
34210908
JM
254 for(auto& i : wcs_offsets) {
255 v.push_back(i);
256 }
257 v.push_back(g92_offset);
258 v.push_back(tool_offset);
259 return v;
260}
261
e03f2747 262int Robot::print_position(uint8_t subcode, char *buf, size_t bufsize) const
2791c829
JM
263{
264 // M114.1 is a new way to do this (similar to how GRBL does it).
265 // it returns the realtime position based on the current step position of the actuators.
266 // this does require a FK to get a machine position from the actuator position
267 // and then invert all the transforms to get a workspace position from machine position
a3be54e3 268 // M114 just does it the old way uses last_milestone and does inversse transforms to get the requested position
2791c829 269 int n = 0;
e03f2747 270 if(subcode == 0) { // M114 print WCS
2791c829 271 wcs_t pos= mcs2wcs(last_milestone);
31c6c2c2 272 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 273
e03f2747 274 } else if(subcode == 4) { // M114.3 print last milestone (which should be the same as machine position if axis are not moving and no level compensation)
31c6c2c2 275 n = snprintf(buf, bufsize, "LMS: X:%1.4f Y:%1.4f Z:%1.4f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
2791c829 276
e03f2747 277 } else if(subcode == 5) { // M114.4 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
cf6b8fd1 278 n = snprintf(buf, bufsize, "LMP: X:%1.4f Y:%1.4f Z:%1.4f", last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
2791c829
JM
279
280 } else {
281 // get real time positions
282 // current actuator position in mm
283 ActuatorCoordinates current_position{
284 actuators[X_AXIS]->get_current_position(),
285 actuators[Y_AXIS]->get_current_position(),
286 actuators[Z_AXIS]->get_current_position()
287 };
288
289 // get machine position from the actuator position using FK
290 float mpos[3];
291 arm_solution->actuator_to_cartesian(current_position, mpos);
292
e03f2747 293 if(subcode == 1) { // M114.1 print realtime WCS
2791c829
JM
294 // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
295 wcs_t pos= mcs2wcs(mpos);
31c6c2c2 296 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 297
e03f2747 298 } else if(subcode == 2) { // M114.1 print realtime Machine coordinate system
31c6c2c2 299 n = snprintf(buf, bufsize, "MPOS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
2791c829 300
e03f2747 301 } else if(subcode == 3) { // M114.2 print realtime actuator position
31c6c2c2 302 n = snprintf(buf, bufsize, "APOS: A:%1.4f B:%1.4f C:%1.4f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
2791c829
JM
303 }
304 }
e03f2747 305 return n;
2791c829
JM
306}
307
dc27139b 308// converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
31c6c2c2 309Robot::wcs_t Robot::mcs2wcs(const Robot::wcs_t& pos) const
dc27139b
JM
310{
311 return std::make_tuple(
31c6c2c2
JM
312 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),
313 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),
314 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
315 );
316}
317
dd0a7cfa
JM
318// this does a sanity check that actuator speeds do not exceed steps rate capability
319// we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
320void Robot::check_max_actuator_speeds()
321{
807b9b57
JM
322 for (size_t i = 0; i < actuators.size(); i++) {
323 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
324 if (step_freq > THEKERNEL->base_stepping_frequency) {
325 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
03b01bac 326 THEKERNEL->streams->printf("WARNING: actuator %c rate exceeds base_stepping_frequency * alpha_steps_per_mm: %f, setting to %f\n", 'A' + i, step_freq, actuators[i]->max_rate);
807b9b57 327 }
dd0a7cfa
JM
328 }
329}
330
4cff3ded 331//A GCode has been received
edac9072 332//See if the current Gcode line has some orders for us
4710532a
JM
333void Robot::on_gcode_received(void *argument)
334{
335 Gcode *gcode = static_cast<Gcode *>(argument);
6bc4a00a 336
23c90ba6 337 this->motion_mode = -1;
4cff3ded 338
4710532a
JM
339 if( gcode->has_g) {
340 switch( gcode->g ) {
c2f7c261
JM
341 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
342 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
343 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
344 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
345 case 4: { // G4 pause
03b01bac 346 uint32_t delay_ms = 0;
c3df978d 347 if (gcode->has_letter('P')) {
03b01bac 348 delay_ms = gcode->get_int('P');
c3df978d
JM
349 }
350 if (gcode->has_letter('S')) {
351 delay_ms += gcode->get_int('S') * 1000;
352 }
03b01bac 353 if (delay_ms > 0) {
c3df978d
JM
354 // drain queue
355 THEKERNEL->conveyor->wait_for_empty_queue();
356 // wait for specified time
03b01bac
JM
357 uint32_t start = us_ticker_read(); // mbed call
358 while ((us_ticker_read() - start) < delay_ms * 1000) {
c3df978d 359 THEKERNEL->call_event(ON_IDLE, this);
c2f7c261 360 if(THEKERNEL->is_halted()) return;
c3df978d
JM
361 }
362 }
adba2978 363 }
6b661ab3 364 break;
807b9b57 365
a6bbe768 366 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
00e607c7 367 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
03b01bac
JM
368 size_t n = gcode->get_uint('P');
369 if(n == 0) n = current_wcs; // set current coordinate system
807b9b57 370 else --n;
0b8b81b6 371 if(n < MAX_WCS) {
807b9b57 372 float x, y, z;
03b01bac 373 std::tie(x, y, z) = wcs_offsets[n];
00e607c7 374 if(gcode->get_int('L') == 20) {
c2f7c261 375 // this makes the current machine position (less compensation transform) the offset
dc27139b
JM
376 // get current position in WCS
377 wcs_t pos= mcs2wcs(last_milestone);
378
379 if(gcode->has_letter('X')){
380 x -= to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
381 }
382
383 if(gcode->has_letter('Y')){
384 y -= to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
385 }
386 if(gcode->has_letter('Z')) {
387 z -= to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
388 }
389
a6bbe768 390 } else {
00e607c7
JM
391 // the value is the offset from machine zero
392 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
393 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
394 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
395 }
03b01bac 396 wcs_offsets[n] = wcs_t(x, y, z);
807b9b57
JM
397 }
398 }
399 break;
400
6e92ab91
JM
401 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
402 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
403 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
404 case 20: this->inch_mode = true; break;
405 case 21: this->inch_mode = false; break;
807b9b57
JM
406
407 case 54: case 55: case 56: case 57: case 58: case 59:
408 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
03b01bac 409 current_wcs = gcode->g - 54;
807b9b57
JM
410 if(gcode->g == 59 && gcode->subcode > 0) {
411 current_wcs += gcode->subcode;
0b8b81b6 412 if(current_wcs >= MAX_WCS) current_wcs = MAX_WCS - 1;
807b9b57
JM
413 }
414 break;
415
6e92ab91
JM
416 case 90: this->absolute_mode = true; break;
417 case 91: this->absolute_mode = false; break;
807b9b57 418
0b804a41 419 case 92: {
c2f7c261 420 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
03b01bac
JM
421 // reset G92 offsets to 0
422 g92_offset = wcs_t(0, 0, 0);
423
4710532a 424 } else {
61a3fa99 425 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
807b9b57 426 float x, y, z;
03b01bac 427 std::tie(x, y, z) = g92_offset;
61a3fa99
JM
428 // get current position in WCS
429 wcs_t pos= mcs2wcs(last_milestone);
430
431 // adjust g92 offset to make the current wpos == the value requested
432 if(gcode->has_letter('X')){
433 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
434 }
dc27139b
JM
435 if(gcode->has_letter('Y')){
436 y += to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
437 }
438 if(gcode->has_letter('Z')) {
439 z += to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
440 }
03b01bac 441 g92_offset = wcs_t(x, y, z);
6bc4a00a 442 }
a6bbe768 443
78d0e16a 444 return;
4710532a
JM
445 }
446 }
67a649dd 447
4710532a
JM
448 } else if( gcode->has_m) {
449 switch( gcode->m ) {
ea5e302b
JM
450 case 0: // M0 feed hold
451 if(THEKERNEL->is_grbl_mode()) THEKERNEL->set_feed_hold(true);
452 break;
01a8d21a
JM
453
454 case 30: // M30 end of program in grbl mode (otherwise it is delete sdcard file)
455 if(!THEKERNEL->is_grbl_mode()) break;
456 // fall through to M2
807b9b57 457 case 2: // M2 end of program
03b01bac
JM
458 current_wcs = 0;
459 absolute_mode = true;
807b9b57
JM
460 break;
461
0fb5b438 462 case 92: // M92 - set steps per mm
0fb5b438 463 if (gcode->has_letter('X'))
78d0e16a 464 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
0fb5b438 465 if (gcode->has_letter('Y'))
78d0e16a 466 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
0fb5b438 467 if (gcode->has_letter('Z'))
78d0e16a 468 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
78d0e16a 469
7f818fc8 470 gcode->stream->printf("X:%f Y:%f Z:%f ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
0fb5b438 471 gcode->add_nl = true;
dd0a7cfa 472 check_max_actuator_speeds();
0fb5b438 473 return;
562db364 474
e03f2747
JM
475 case 114:{
476 char buf[64];
477 int n= print_position(gcode->subcode, buf, sizeof buf);
478 if(n > 0) gcode->txt_after_ok.append(buf, n);
2791c829 479 return;
e03f2747 480 }
33e4cc02 481
212caccd
JM
482 case 120: // push state
483 push_state();
484 break;
562db364
JM
485
486 case 121: // pop state
212caccd 487 pop_state();
562db364
JM
488 break;
489
83488642
JM
490 case 203: // M203 Set maximum feedrates in mm/sec
491 if (gcode->has_letter('X'))
4710532a 492 this->max_speeds[X_AXIS] = gcode->get_value('X');
83488642 493 if (gcode->has_letter('Y'))
4710532a 494 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
83488642 495 if (gcode->has_letter('Z'))
4710532a 496 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
807b9b57
JM
497 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
498 if (gcode->has_letter('A' + i))
499 actuators[i]->set_max_rate(gcode->get_value('A' + i));
500 }
dd0a7cfa
JM
501 check_max_actuator_speeds();
502
928467c0 503 if(gcode->get_num_args() == 0) {
807b9b57 504 gcode->stream->printf("X:%g Y:%g Z:%g",
03b01bac 505 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
807b9b57
JM
506 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
507 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
508 }
928467c0
JM
509 gcode->add_nl = true;
510 }
83488642
JM
511 break;
512
c5fe1787 513 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
4710532a 514 if (gcode->has_letter('S')) {
4710532a 515 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 516 // enforce minimum
da947c62
MM
517 if (acc < 1.0F)
518 acc = 1.0F;
4710532a 519 THEKERNEL->planner->acceleration = acc;
d4ee6ee2 520 }
c5fe1787 521 if (gcode->has_letter('Z')) {
c5fe1787
JM
522 float acc = gcode->get_value('Z'); // mm/s^2
523 // enforce positive
524 if (acc < 0.0F)
525 acc = 0.0F;
526 THEKERNEL->planner->z_acceleration = acc;
527 }
d4ee6ee2
JM
528 break;
529
9502f9d5 530 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
4710532a
JM
531 if (gcode->has_letter('X')) {
532 float jd = gcode->get_value('X');
d4ee6ee2 533 // enforce minimum
8b69c90d
JM
534 if (jd < 0.0F)
535 jd = 0.0F;
4710532a 536 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 537 }
107df03f
JM
538 if (gcode->has_letter('Z')) {
539 float jd = gcode->get_value('Z');
540 // enforce minimum, -1 disables it and uses regular junction deviation
541 if (jd < -1.0F)
542 jd = -1.0F;
543 THEKERNEL->planner->z_junction_deviation = jd;
544 }
4710532a
JM
545 if (gcode->has_letter('S')) {
546 float mps = gcode->get_value('S');
8b69c90d
JM
547 // enforce minimum
548 if (mps < 0.0F)
549 mps = 0.0F;
4710532a 550 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 551 }
9502f9d5 552 if (gcode->has_letter('Y')) {
807b9b57 553 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
9502f9d5 554 }
d4ee6ee2 555 break;
98761c28 556
7369629d 557 case 220: // M220 - speed override percentage
4710532a 558 if (gcode->has_letter('S')) {
1ad23cd3 559 float factor = gcode->get_value('S');
98761c28 560 // enforce minimum 10% speed
da947c62
MM
561 if (factor < 10.0F)
562 factor = 10.0F;
563 // enforce maximum 10x speed
564 if (factor > 1000.0F)
565 factor = 1000.0F;
566
567 seconds_per_minute = 6000.0F / factor;
03b01bac 568 } else {
9ef9f45b 569 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 570 }
b4f56013 571 break;
ec4773e5 572
494dc541 573 case 400: // wait until all moves are done up to this point
314ab8f7 574 THEKERNEL->conveyor->wait_for_empty_queue();
494dc541
JM
575 break;
576
33e4cc02 577 case 500: // M500 saves some volatile settings to config override file
b7cd847e 578 case 503: { // M503 just prints the settings
78d0e16a 579 gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
c5fe1787 580 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
c9cc5e06 581 gcode->stream->printf(";X- Junction Deviation, Z- Z junction deviation, S - Minimum Planner speed mm/sec:\nM205 X%1.5f Z%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
807b9b57
JM
582 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
583 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
03b01bac 584 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
807b9b57
JM
585 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
586 }
587 gcode->stream->printf("\n");
b7cd847e
JM
588
589 // get or save any arm solution specific optional values
590 BaseSolution::arm_options_t options;
591 if(arm_solution->get_optional(options) && !options.empty()) {
592 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 593 for(auto &i : options) {
b7cd847e
JM
594 gcode->stream->printf(" %c%1.4f", i.first, i.second);
595 }
596 gcode->stream->printf("\n");
597 }
6e92ab91 598
807b9b57
JM
599 // save wcs_offsets and current_wcs
600 // TODO this may need to be done whenever they change to be compliant
601 gcode->stream->printf(";WCS settings\n");
40fd5d98 602 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
03b01bac 603 int n = 1;
807b9b57 604 for(auto &i : wcs_offsets) {
2791c829 605 if(i != wcs_t(0, 0, 0)) {
807b9b57
JM
606 float x, y, z;
607 std::tie(x, y, z) = i;
40fd5d98 608 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 609 }
807b9b57
JM
610 ++n;
611 }
543d4814
JM
612 // linuxcnc does seem to save G92, so we do too
613 // also it needs to be used to set Z0 on rotary deltas as M206/306 can't be used
03b01bac 614 if(g92_offset != wcs_t(0, 0, 0)) {
67a649dd
JM
615 float x, y, z;
616 std::tie(x, y, z) = g92_offset;
543d4814 617 gcode->stream->printf("G92 X%f Y%f Z%f\n", x, y, z);
67a649dd
JM
618 }
619 }
807b9b57 620 break;
33e4cc02 621
b7cd847e 622 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6 623 // the parameter args could be any letter each arm solution only accepts certain ones
03b01bac 624 BaseSolution::arm_options_t options = gcode->get_args();
ebc75fc6
JM
625 options.erase('S'); // don't include the S
626 options.erase('U'); // don't include the U
627 if(options.size() > 0) {
628 // set the specified options
629 arm_solution->set_optional(options);
630 }
631 options.clear();
b7cd847e 632 if(arm_solution->get_optional(options)) {
ebc75fc6 633 // foreach optional value
4710532a 634 for(auto &i : options) {
b7cd847e
JM
635 // print all current values of supported options
636 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 637 gcode->add_nl = true;
ec4773e5
JM
638 }
639 }
ec4773e5 640
4a839bea 641 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 642 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
643 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
644
03b01bac 645 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
4a839bea
JM
646 this->mm_per_line_segment = gcode->get_value('U');
647 this->delta_segments_per_second = 0;
648 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 649 }
4a839bea 650
ec4773e5 651 break;
b7cd847e 652 }
6989211c 653 }
494dc541
JM
654 }
655
c2f7c261
JM
656 if( this->motion_mode >= 0) {
657 process_move(gcode);
00e607c7 658 }
6bc4a00a 659
c2f7c261
JM
660 next_command_is_MCS = false; // must be on same line as G0 or G1
661}
350c8a60 662
5d2319a9 663// process a G0/G1/G2/G3
c2f7c261
JM
664void Robot::process_move(Gcode *gcode)
665{
2791c829 666 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
c2f7c261 667 float param[3]{NAN, NAN, NAN};
350c8a60
JM
668 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
669 char letter= 'X'+i;
670 if( gcode->has_letter(letter) ) {
671 param[i] = this->to_millimeters(gcode->get_value(letter));
350c8a60
JM
672 }
673 }
6bc4a00a 674
c2f7c261 675 float offset[3]{0,0,0};
4710532a
JM
676 for(char letter = 'I'; letter <= 'K'; letter++) {
677 if( gcode->has_letter(letter) ) {
678 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
679 }
680 }
00e607c7 681
c2f7c261
JM
682 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
683 float target[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
350c8a60
JM
684 if(!next_command_is_MCS) {
685 if(this->absolute_mode) {
c2f7c261
JM
686 // apply wcs offsets and g92 offset and tool offset
687 if(!isnan(param[X_AXIS])) {
688 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);
689 }
690
691 if(!isnan(param[Y_AXIS])) {
692 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);
693 }
694
695 if(!isnan(param[Z_AXIS])) {
696 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);
697 }
350c8a60
JM
698
699 }else{
700 // they are deltas from the last_milestone if specified
701 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
c2f7c261 702 if(!isnan(param[i])) target[i] = param[i] + last_milestone[i];
a6bbe768
JM
703 }
704 }
705
350c8a60 706 }else{
c2f7c261
JM
707 // already in machine coordinates, we do not add tool offset for that
708 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
709 if(!isnan(param[i])) target[i] = param[i];
710 }
c2885de8 711 }
6bc4a00a 712
4710532a 713 if( gcode->has_letter('F') ) {
7369629d 714 if( this->motion_mode == MOTION_MODE_SEEK )
da947c62 715 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 716 else
da947c62 717 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 718 }
6bc4a00a 719
350c8a60
JM
720 bool moved= false;
721 //Perform any physical actions
fae93525 722 switch(this->motion_mode) {
350c8a60
JM
723 case MOTION_MODE_CANCEL:
724 break;
725 case MOTION_MODE_SEEK:
726 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute );
727 break;
728 case MOTION_MODE_LINEAR:
729 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute );
730 break;
fae93525 731 case MOTION_MODE_CW_ARC:
350c8a60
JM
732 case MOTION_MODE_CCW_ARC:
733 moved= this->compute_arc(gcode, offset, target );
734 break;
4cff3ded 735 }
13e4a3f9 736
c2f7c261
JM
737 if(moved) {
738 // set last_milestone to the calculated target
739 memcpy(this->last_milestone, target, sizeof(this->last_milestone));
350c8a60 740 }
edac9072
AW
741}
742
5984acdf 743// We received a new gcode, and one of the functions
edac9072
AW
744// determined the distance for that given gcode. So now we can attach this gcode to the right block
745// and continue
03b01bac 746void Robot::distance_in_gcode_is_known(Gcode * gcode)
4710532a 747{
a3be54e3 748 //If the queue is empty, execute immediately, otherwise attach to the last added block
e0ee24ed 749 THEKERNEL->conveyor->append_gcode(gcode);
edac9072
AW
750}
751
a6bbe768 752// reset the machine position for all axis. Used for homing.
f6934849
JM
753// During homing compensation is turned off (actually not used as it drives steppers directly)
754// once homed and reset_axis called compensation is used for the move to origin and back off home if enabled,
755// so in those cases the final position is compensated.
cef9acea
JM
756void Robot::reset_axis_position(float x, float y, float z)
757{
f6934849 758 // these are set to the same as compensation was not used to get to the current position
c2f7c261
JM
759 last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
760 last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
761 last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
cef9acea 762
a6bbe768 763 // now set the actuator positions to match
807b9b57 764 ActuatorCoordinates actuator_pos;
c2f7c261 765 arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
807b9b57 766 for (size_t i = 0; i < actuators.size(); i++)
cef9acea
JM
767 actuators[i]->change_last_milestone(actuator_pos[i]);
768}
769
807b9b57 770// Reset the position for an axis (used in homing)
4710532a
JM
771void Robot::reset_axis_position(float position, int axis)
772{
c2f7c261
JM
773 last_milestone[axis] = position;
774 reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
4cff3ded
AW
775}
776
932a3995 777// similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
93f20a8c
JM
778// then sets the axis positions to match. currently only called from Endstops.cpp
779void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
586cc733 780{
93f20a8c
JM
781 for (size_t i = 0; i < actuators.size(); i++)
782 actuators[i]->change_last_milestone(ac[i]);
586cc733
JM
783
784 // now correct axis positions then recorrect actuator to account for rounding
785 reset_position_from_current_actuator_position();
786}
787
a6bbe768 788// Use FK to find out where actuator is and reset to match
728477c4
JM
789void Robot::reset_position_from_current_actuator_position()
790{
807b9b57
JM
791 ActuatorCoordinates actuator_pos;
792 for (size_t i = 0; i < actuators.size(); i++) {
58587001 793 // NOTE actuator::current_position is curently NOT the same as actuator::last_milestone after an abrupt abort
807b9b57
JM
794 actuator_pos[i] = actuators[i]->get_current_position();
795 }
58587001
JM
796
797 // discover machine position from where actuators actually are
c2f7c261
JM
798 arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
799 // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
800 memcpy(last_milestone, last_machine_position, sizeof last_milestone);
cf91d4f3 801
58587001
JM
802 // now reset actuator::last_milestone, NOTE this may lose a little precision as FK is not always entirely accurate.
803 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
932a3995 804 // to get everything in perfect sync.
7baae81a
JM
805 arm_solution->cartesian_to_actuator(last_machine_position, actuator_pos);
806 for (size_t i = 0; i < actuators.size(); i++)
807 actuators[i]->change_last_milestone(actuator_pos[i]);
728477c4 808}
edac9072 809
c2f7c261
JM
810// Convert target (in machine coordinates) from millimeters to steps, and append this to the planner
811// target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
812// all transforms and is what we actually convert to actuator positions
350c8a60 813bool Robot::append_milestone(Gcode * gcode, const float target[], float rate_mm_s)
df6a30f2 814{
1ad23cd3 815 float deltas[3];
df6a30f2 816 float unit_vec[3];
807b9b57 817 ActuatorCoordinates actuator_pos;
03b01bac 818 float transformed_target[3]; // adjust target for bed compensation and WCS offsets
df6a30f2
MM
819 float millimeters_of_travel;
820
3632a517
JM
821 // unity transform by default
822 memcpy(transformed_target, target, sizeof(transformed_target));
5e45206a 823
350c8a60
JM
824 // check function pointer and call if set to transform the target to compensate for bed
825 if(compensationTransform) {
826 // some compensation strategies can transform XYZ, some just change Z
827 compensationTransform(transformed_target);
00e607c7 828 }
807b9b57 829
a6bbe768 830 // find distance moved by each axis, use transformed target from the current machine position
03b01bac 831 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
c2f7c261 832 deltas[axis] = transformed_target[axis] - last_machine_position[axis];
3632a517 833 }
aab6cbba 834
edac9072 835 // Compute how long this move moves, so we can attach it to the block for later use
869acfb8 836 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
df6a30f2 837
a6bbe768
JM
838 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
839 // as the last milestone won't be updated we do not actually lose any moves as they will be accounted for in the next move
350c8a60 840 if(millimeters_of_travel < 0.00001F) return false;
a6bbe768
JM
841
842 // this is the machine position
c2f7c261 843 memcpy(this->last_machine_position, transformed_target, sizeof(this->last_machine_position));
a6bbe768 844
df6a30f2
MM
845 // find distance unit vector
846 for (int i = 0; i < 3; i++)
847 unit_vec[i] = deltas[i] / millimeters_of_travel;
848
849 // Do not move faster than the configured cartesian limits
4710532a
JM
850 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
851 if ( max_speeds[axis] > 0 ) {
da947c62 852 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
df6a30f2
MM
853
854 if (axis_speed > max_speeds[axis])
da947c62 855 rate_mm_s *= ( max_speeds[axis] / axis_speed );
7b470506
AW
856 }
857 }
4cff3ded 858
c2f7c261
JM
859 // find actuator position given the machine position, use actual adjusted target
860 arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
df6a30f2 861
03b01bac 862 float isecs = rate_mm_s / millimeters_of_travel;
df6a30f2 863 // check per-actuator speed limits
807b9b57 864 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
928467c0 865 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
03b01bac 866 if (actuator_rate > actuators[actuator]->get_max_rate()) {
3494f3d0 867 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
03b01bac 868 isecs = rate_mm_s / millimeters_of_travel;
928467c0
JM
869 }
870 }
871
edac9072 872 // Append the block to the planner
da947c62 873 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
4cff3ded 874
350c8a60 875 return true;
4cff3ded
AW
876}
877
edac9072 878// Append a move to the queue ( cutting it into segments if needed )
c2f7c261 879bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s )
4710532a 880{
c2f7c261 881 // Find out the distance for this move in MCS
a9d299ab 882 // NOTE we need to do sqrt here as this setting of millimeters_of_travel is used by extruder and other modules even if there is no XYZ move
2791c829 883 gcode->millimeters_of_travel = sqrtf(powf( target[X_AXIS] - last_milestone[X_AXIS], 2 ) + powf( target[Y_AXIS] - last_milestone[Y_AXIS], 2 ) + powf( target[Z_AXIS] - last_milestone[Z_AXIS], 2 ));
4cff3ded 884
3b4b05b8 885 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
350c8a60 886 if( gcode->millimeters_of_travel < 0.00001F ) return false;
436a2cd1 887
edac9072 888 // Mark the gcode as having a known distance
5dcb2ff3 889 this->distance_in_gcode_is_known( gcode );
436a2cd1 890
d2adef5e
JM
891 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
892 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
893 // We also check we are not exceeding the E max_speed for the current extruder
894 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
895 // we need to ask it now passing in the relevant data.
896 // NOTE we need to do this before we segment the line (for deltas)
897 if(gcode->has_letter('E')) {
898 float data[2];
a3be54e3 899 data[0] = gcode->get_value('E'); // E target (may be absolute or relative)
03b01bac 900 data[1] = rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
d2adef5e
JM
901 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
902 rate_mm_s *= data[1];
903 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
904 }
905 }
906
c2f7c261 907 // 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
908 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
909 // 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 910 uint16_t segments;
5984acdf 911
a3e1326a 912 if(this->disable_segmentation || (!segment_z_moves && !gcode->has_letter('X') && !gcode->has_letter('Y'))) {
778093ce
JM
913 segments= 1;
914
915 } else if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
916 // enabled if set to something > 1, it is set to 0.0 by default
917 // segment based on current speed and requested segments per second
918 // the faster the travel speed the fewer segments needed
919 // NOTE rate is mm/sec and we take into account any speed override
da947c62 920 float seconds = gcode->millimeters_of_travel / rate_mm_s;
9502f9d5 921 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 922 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 923
4710532a
JM
924 } else {
925 if(this->mm_per_line_segment == 0.0F) {
926 segments = 1; // don't split it up
927 } else {
9502f9d5 928 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
929 }
930 }
5984acdf 931
350c8a60 932 bool moved= false;
4710532a 933 if (segments > 1) {
2ba859c9
MM
934 // A vector to keep track of the endpoint of each segment
935 float segment_delta[3];
1b5776bf 936 float segment_end[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
2ba859c9
MM
937
938 // How far do we move each segment?
9fff6045 939 for (int i = X_AXIS; i <= Z_AXIS; i++)
2791c829 940 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
4cff3ded 941
c8e0fb15
MM
942 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
943 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 944 for (int i = 1; i < segments; i++) {
350c8a60 945 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
4710532a 946 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
1b5776bf 947 segment_end[axis] += segment_delta[axis];
2ba859c9
MM
948
949 // Append the end of this segment to the queue
350c8a60
JM
950 bool b= this->append_milestone(gcode, segment_end, rate_mm_s);
951 moved= moved || b;
2ba859c9 952 }
4cff3ded 953 }
5984acdf
MM
954
955 // Append the end of this full move to the queue
350c8a60 956 if(this->append_milestone(gcode, target, rate_mm_s)) moved= true;
2134bcf2 957
a6bbe768 958 this->next_command_is_MCS = false; // always reset this
00e607c7 959
350c8a60
JM
960 if(moved) {
961 // if adding these blocks didn't start executing, do that now
962 THEKERNEL->conveyor->ensure_running();
963 }
964
965 return moved;
4cff3ded
AW
966}
967
4cff3ded 968
edac9072 969// Append an arc to the queue ( cutting it into segments as needed )
350c8a60 970bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
4710532a 971{
aab6cbba 972
edac9072 973 // Scary math
2ba859c9
MM
974 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
975 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
976 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
1ad23cd3
MM
977 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
978 float r_axis1 = -offset[this->plane_axis_1];
979 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
980 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
aab6cbba 981
51871fb8 982 // Patch from GRBL Firmware - Christoph Baumann 04072015
aab6cbba 983 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
03b01bac 984 float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
5fa0c173 985 if (is_clockwise) { // Correct atan2 output per direction
03b01bac 986 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2 * M_PI; }
5fa0c173 987 } else {
03b01bac 988 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2 * M_PI; }
4710532a 989 }
aab6cbba 990
edac9072 991 // Find the distance for this gcode
4710532a 992 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
436a2cd1 993
edac9072 994 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
3b4b05b8 995 if( gcode->millimeters_of_travel < 0.00001F ) {
350c8a60 996 return false;
4710532a 997 }
5dcb2ff3 998
edac9072 999 // Mark the gcode as having a known distance
d149c730 1000 this->distance_in_gcode_is_known( gcode );
5984acdf
MM
1001
1002 // Figure out how many segments for this gcode
c8f4ee77 1003 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
aab6cbba 1004
4710532a
JM
1005 float theta_per_segment = angular_travel / segments;
1006 float linear_per_segment = linear_travel / segments;
aab6cbba
AW
1007
1008 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1009 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1010 r_T = [cos(phi) -sin(phi);
1011 sin(phi) cos(phi] * r ;
1012 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1013 defined from the circle center to the initial position. Each line segment is formed by successive
1014 vector rotations. This requires only two cos() and sin() computations to form the rotation
1015 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1ad23cd3 1016 all float numbers are single precision on the Arduino. (True float precision will not have
aab6cbba
AW
1017 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1018 tool precision in some cases. Therefore, arc path correction is implemented.
1019
1020 Small angle approximation may be used to reduce computation overhead further. This approximation
1021 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1022 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1023 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1024 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1025 issue for CNC machines with the single precision Arduino calculations.
1026 This approximation also allows mc_arc to immediately insert a line segment into the planner
1027 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1028 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1029 This is important when there are successive arc motions.
1030 */
1031 // Vector rotation matrix values
4710532a 1032 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1ad23cd3 1033 float sin_T = theta_per_segment;
aab6cbba 1034
1ad23cd3
MM
1035 float arc_target[3];
1036 float sin_Ti;
1037 float cos_Ti;
1038 float r_axisi;
aab6cbba
AW
1039 uint16_t i;
1040 int8_t count = 0;
1041
1042 // Initialize the linear axis
2ba859c9 1043 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
aab6cbba 1044
350c8a60 1045 bool moved= false;
4710532a 1046 for (i = 1; i < segments; i++) { // Increment (segments-1)
350c8a60 1047 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
aab6cbba 1048
b66fb830 1049 if (count < this->arc_correction ) {
4710532a
JM
1050 // Apply vector rotation matrix
1051 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1052 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1053 r_axis1 = r_axisi;
1054 count++;
aab6cbba 1055 } else {
4710532a
JM
1056 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1057 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1058 cos_Ti = cosf(i * theta_per_segment);
1059 sin_Ti = sinf(i * theta_per_segment);
1060 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1061 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1062 count = 0;
aab6cbba
AW
1063 }
1064
1065 // Update arc_target location
1066 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1067 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1068 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
1069
1070 // Append this segment to the queue
350c8a60
JM
1071 bool b= this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
1072 moved= moved || b;
aab6cbba 1073 }
edac9072 1074
aab6cbba 1075 // Ensure last segment arrives at target location.
350c8a60
JM
1076 if(this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute)) moved= true;
1077
1078 return moved;
aab6cbba
AW
1079}
1080
edac9072 1081// Do the math for an arc and add it to the queue
350c8a60 1082bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[])
4710532a 1083{
aab6cbba
AW
1084
1085 // Find the radius
13addf09 1086 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
1087
1088 // Set clockwise/counter-clockwise sign for mc_arc computations
1089 bool is_clockwise = false;
4710532a
JM
1090 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
1091 is_clockwise = true;
1092 }
aab6cbba
AW
1093
1094 // Append arc
350c8a60 1095 return this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
1096}
1097
1098
4710532a
JM
1099float Robot::theta(float x, float y)
1100{
1101 float t = atanf(x / fabs(y));
1102 if (y > 0) {
1103 return(t);
1104 } else {
1105 if (t > 0) {
1106 return(M_PI - t);
1107 } else {
1108 return(-M_PI - t);
1109 }
1110 }
4cff3ded
AW
1111}
1112
4710532a
JM
1113void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1114{
4cff3ded
AW
1115 this->plane_axis_0 = axis_0;
1116 this->plane_axis_1 = axis_1;
1117 this->plane_axis_2 = axis_2;
1118}
1119
fae93525 1120void Robot::clearToolOffset()
4710532a 1121{
c2f7c261 1122 this->tool_offset= wcs_t(0,0,0);
fae93525
JM
1123}
1124
1125void Robot::setToolOffset(const float offset[3])
1126{
c2f7c261 1127 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
5966b7d0
AT
1128}
1129
0ec2f63a
JM
1130float Robot::get_feed_rate() const
1131{
1132 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1133}