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