Make G92 work as expected based on current WCS position
[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);
c2f7c261 113 clear_vector(this->last_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
2791c829
JM
244void Robot::print_position(Gcode *gcode) const
245{
246 // M114.1 is a new way to do this (similar to how GRBL does it).
247 // it returns the realtime position based on the current step position of the actuators.
248 // this does require a FK to get a machine position from the actuator position
249 // and then invert all the transforms to get a workspace position from machine position
250 // M114 just does it the old way uses last_milestone and does inversse tranfroms to get the requested position
251 char buf[64];
252 int n = 0;
253 if(gcode->subcode == 0) { // M114 print WCS
254 wcs_t pos= mcs2wcs(last_milestone);
255 n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
256
257 } else if(gcode->subcode == 4) { // M114.3 print last milestone (which should be the same as machine position if axis are not moving and no level compensation)
258 n = snprintf(buf, sizeof(buf), "LMS: X:%1.3f Y:%1.3f Z:%1.3f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
259
260 } else if(gcode->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)
261 n = snprintf(buf, sizeof(buf), "LMCS: X:%1.3f Y:%1.3f Z:%1.3f", last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
262
263 } else {
264 // get real time positions
265 // current actuator position in mm
266 ActuatorCoordinates current_position{
267 actuators[X_AXIS]->get_current_position(),
268 actuators[Y_AXIS]->get_current_position(),
269 actuators[Z_AXIS]->get_current_position()
270 };
271
272 // get machine position from the actuator position using FK
273 float mpos[3];
274 arm_solution->actuator_to_cartesian(current_position, mpos);
275
276 if(gcode->subcode == 1) { // M114.1 print realtime WCS
277 // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
278 wcs_t pos= mcs2wcs(mpos);
279 n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
280
281 } else if(gcode->subcode == 2) { // M114.1 print realtime Machine coordinate system
282 n = snprintf(buf, sizeof(buf), "MPOS: X:%1.3f Y:%1.3f Z:%1.3f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
283
284 } else if(gcode->subcode == 3) { // M114.2 print realtime actuator position
285 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]);
286 }
287 }
288 if(n > 0)
289 gcode->txt_after_ok.append(buf, n);
290}
291
dd0a7cfa
JM
292// this does a sanity check that actuator speeds do not exceed steps rate capability
293// we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
294void Robot::check_max_actuator_speeds()
295{
807b9b57
JM
296 for (size_t i = 0; i < actuators.size(); i++) {
297 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
298 if (step_freq > THEKERNEL->base_stepping_frequency) {
299 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
03b01bac 300 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 301 }
dd0a7cfa
JM
302 }
303}
304
c2f7c261
JM
305// converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
306Robot::wcs_t Robot::mcs2wcs(const float *pos) const
a6bbe768 307{
a6bbe768 308 return std::make_tuple(
c2f7c261
JM
309 pos[X_AXIS] - std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset) - std::get<X_AXIS>(tool_offset),
310 pos[Y_AXIS] - std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset) - std::get<Y_AXIS>(tool_offset),
311 pos[Z_AXIS] - std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset) - std::get<Z_AXIS>(tool_offset)
312 );
a6bbe768
JM
313}
314
4cff3ded 315//A GCode has been received
edac9072 316//See if the current Gcode line has some orders for us
4710532a
JM
317void Robot::on_gcode_received(void *argument)
318{
319 Gcode *gcode = static_cast<Gcode *>(argument);
6bc4a00a 320
23c90ba6 321 this->motion_mode = -1;
4cff3ded 322
4710532a
JM
323 if( gcode->has_g) {
324 switch( gcode->g ) {
c2f7c261
JM
325 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
326 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
327 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
328 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
329 case 4: { // G4 pause
03b01bac 330 uint32_t delay_ms = 0;
c3df978d 331 if (gcode->has_letter('P')) {
03b01bac 332 delay_ms = gcode->get_int('P');
c3df978d
JM
333 }
334 if (gcode->has_letter('S')) {
335 delay_ms += gcode->get_int('S') * 1000;
336 }
03b01bac 337 if (delay_ms > 0) {
c3df978d
JM
338 // drain queue
339 THEKERNEL->conveyor->wait_for_empty_queue();
340 // wait for specified time
03b01bac
JM
341 uint32_t start = us_ticker_read(); // mbed call
342 while ((us_ticker_read() - start) < delay_ms * 1000) {
c3df978d 343 THEKERNEL->call_event(ON_IDLE, this);
c2f7c261 344 if(THEKERNEL->is_halted()) return;
c3df978d
JM
345 }
346 }
adba2978 347 }
6b661ab3 348 break;
807b9b57 349
a6bbe768 350 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
00e607c7 351 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
03b01bac
JM
352 size_t n = gcode->get_uint('P');
353 if(n == 0) n = current_wcs; // set current coordinate system
807b9b57
JM
354 else --n;
355 if(n < k_max_wcs) {
356 float x, y, z;
03b01bac 357 std::tie(x, y, z) = wcs_offsets[n];
00e607c7 358 if(gcode->get_int('L') == 20) {
c2f7c261
JM
359 // this makes the current machine position (less compensation transform) the offset
360 if(gcode->has_letter('X')) { x = to_millimeters(gcode->get_value('X')) - last_milestone[X_AXIS]; }
361 if(gcode->has_letter('Y')) { x = to_millimeters(gcode->get_value('Y')) - last_milestone[Y_AXIS]; }
362 if(gcode->has_letter('Z')) { x = to_millimeters(gcode->get_value('Z')) - last_milestone[Z_AXIS]; }
a6bbe768 363 } else {
00e607c7
JM
364 // the value is the offset from machine zero
365 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
366 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
367 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
368 }
03b01bac 369 wcs_offsets[n] = wcs_t(x, y, z);
807b9b57
JM
370 }
371 }
372 break;
373
6e92ab91
JM
374 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
375 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
376 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
377 case 20: this->inch_mode = true; break;
378 case 21: this->inch_mode = false; break;
807b9b57
JM
379
380 case 54: case 55: case 56: case 57: case 58: case 59:
381 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
03b01bac 382 current_wcs = gcode->g - 54;
807b9b57
JM
383 if(gcode->g == 59 && gcode->subcode > 0) {
384 current_wcs += gcode->subcode;
03b01bac 385 if(current_wcs >= k_max_wcs) current_wcs = k_max_wcs - 1;
807b9b57
JM
386 }
387 break;
388
6e92ab91
JM
389 case 90: this->absolute_mode = true; break;
390 case 91: this->absolute_mode = false; break;
807b9b57 391
0b804a41 392 case 92: {
c2f7c261 393 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
03b01bac
JM
394 // reset G92 offsets to 0
395 g92_offset = wcs_t(0, 0, 0);
396
4710532a 397 } else {
61a3fa99 398 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
807b9b57 399 float x, y, z;
03b01bac 400 std::tie(x, y, z) = g92_offset;
61a3fa99
JM
401 // get current position in WCS
402 wcs_t pos= mcs2wcs(last_milestone);
403
404 // adjust g92 offset to make the current wpos == the value requested
405 if(gcode->has_letter('X')){
406 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
407 }
408
409 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y')) - last_milestone[Y_AXIS] - std::get<Y_AXIS>(wcs_offsets[current_wcs]) - std::get<Y_AXIS>(tool_offset);
410 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z')) - last_milestone[Z_AXIS] - std::get<Z_AXIS>(wcs_offsets[current_wcs]) - std::get<Z_AXIS>(tool_offset);
03b01bac 411 g92_offset = wcs_t(x, y, z);
6bc4a00a 412 }
a6bbe768 413
78d0e16a 414 return;
4710532a
JM
415 }
416 }
67a649dd 417
4710532a
JM
418 } else if( gcode->has_m) {
419 switch( gcode->m ) {
807b9b57 420 case 2: // M2 end of program
03b01bac
JM
421 current_wcs = 0;
422 absolute_mode = true;
807b9b57
JM
423 break;
424
0fb5b438 425 case 92: // M92 - set steps per mm
0fb5b438 426 if (gcode->has_letter('X'))
78d0e16a 427 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
0fb5b438 428 if (gcode->has_letter('Y'))
78d0e16a 429 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
0fb5b438 430 if (gcode->has_letter('Z'))
78d0e16a 431 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
7369629d
MM
432 if (gcode->has_letter('F'))
433 seconds_per_minute = gcode->get_value('F');
78d0e16a
MM
434
435 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 436 gcode->add_nl = true;
dd0a7cfa 437 check_max_actuator_speeds();
0fb5b438 438 return;
562db364 439
2791c829
JM
440 case 114:
441 print_position(gcode);
442 return;
33e4cc02 443
212caccd
JM
444 case 120: // push state
445 push_state();
446 break;
562db364
JM
447
448 case 121: // pop state
212caccd 449 pop_state();
562db364
JM
450 break;
451
83488642
JM
452 case 203: // M203 Set maximum feedrates in mm/sec
453 if (gcode->has_letter('X'))
4710532a 454 this->max_speeds[X_AXIS] = gcode->get_value('X');
83488642 455 if (gcode->has_letter('Y'))
4710532a 456 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
83488642 457 if (gcode->has_letter('Z'))
4710532a 458 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
807b9b57
JM
459 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
460 if (gcode->has_letter('A' + i))
461 actuators[i]->set_max_rate(gcode->get_value('A' + i));
462 }
dd0a7cfa
JM
463 check_max_actuator_speeds();
464
928467c0 465 if(gcode->get_num_args() == 0) {
807b9b57 466 gcode->stream->printf("X:%g Y:%g Z:%g",
03b01bac 467 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
807b9b57
JM
468 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
469 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
470 }
928467c0
JM
471 gcode->add_nl = true;
472 }
83488642
JM
473 break;
474
c5fe1787 475 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
4710532a 476 if (gcode->has_letter('S')) {
4710532a 477 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 478 // enforce minimum
da947c62
MM
479 if (acc < 1.0F)
480 acc = 1.0F;
4710532a 481 THEKERNEL->planner->acceleration = acc;
d4ee6ee2 482 }
c5fe1787 483 if (gcode->has_letter('Z')) {
c5fe1787
JM
484 float acc = gcode->get_value('Z'); // mm/s^2
485 // enforce positive
486 if (acc < 0.0F)
487 acc = 0.0F;
488 THEKERNEL->planner->z_acceleration = acc;
489 }
d4ee6ee2
JM
490 break;
491
9502f9d5 492 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
4710532a
JM
493 if (gcode->has_letter('X')) {
494 float jd = gcode->get_value('X');
d4ee6ee2 495 // enforce minimum
8b69c90d
JM
496 if (jd < 0.0F)
497 jd = 0.0F;
4710532a 498 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 499 }
107df03f
JM
500 if (gcode->has_letter('Z')) {
501 float jd = gcode->get_value('Z');
502 // enforce minimum, -1 disables it and uses regular junction deviation
503 if (jd < -1.0F)
504 jd = -1.0F;
505 THEKERNEL->planner->z_junction_deviation = jd;
506 }
4710532a
JM
507 if (gcode->has_letter('S')) {
508 float mps = gcode->get_value('S');
8b69c90d
JM
509 // enforce minimum
510 if (mps < 0.0F)
511 mps = 0.0F;
4710532a 512 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 513 }
9502f9d5 514 if (gcode->has_letter('Y')) {
807b9b57 515 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
9502f9d5 516 }
d4ee6ee2 517 break;
98761c28 518
7369629d 519 case 220: // M220 - speed override percentage
4710532a 520 if (gcode->has_letter('S')) {
1ad23cd3 521 float factor = gcode->get_value('S');
98761c28 522 // enforce minimum 10% speed
da947c62
MM
523 if (factor < 10.0F)
524 factor = 10.0F;
525 // enforce maximum 10x speed
526 if (factor > 1000.0F)
527 factor = 1000.0F;
528
529 seconds_per_minute = 6000.0F / factor;
03b01bac 530 } else {
9ef9f45b 531 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 532 }
b4f56013 533 break;
ec4773e5 534
494dc541 535 case 400: // wait until all moves are done up to this point
314ab8f7 536 THEKERNEL->conveyor->wait_for_empty_queue();
494dc541
JM
537 break;
538
33e4cc02 539 case 500: // M500 saves some volatile settings to config override file
b7cd847e 540 case 503: { // M503 just prints the settings
78d0e16a 541 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 542 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
c9cc5e06 543 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
544 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
545 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
03b01bac 546 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
807b9b57
JM
547 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
548 }
549 gcode->stream->printf("\n");
b7cd847e
JM
550
551 // get or save any arm solution specific optional values
552 BaseSolution::arm_options_t options;
553 if(arm_solution->get_optional(options) && !options.empty()) {
554 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 555 for(auto &i : options) {
b7cd847e
JM
556 gcode->stream->printf(" %c%1.4f", i.first, i.second);
557 }
558 gcode->stream->printf("\n");
559 }
6e92ab91 560
807b9b57
JM
561 // save wcs_offsets and current_wcs
562 // TODO this may need to be done whenever they change to be compliant
563 gcode->stream->printf(";WCS settings\n");
350c8a60 564 gcode->stream->printf("G5%c", std::min(current_wcs, (uint8_t)5) + '4');
807b9b57 565 if(current_wcs >= 6) {
350c8a60 566 gcode->stream->printf(".%c\n", '1' + (current_wcs - 6));
03b01bac 567 } else {
807b9b57
JM
568 gcode->stream->printf("\n");
569 }
03b01bac 570 int n = 1;
807b9b57 571 for(auto &i : wcs_offsets) {
2791c829 572 if(i != wcs_t(0, 0, 0)) {
807b9b57
JM
573 float x, y, z;
574 std::tie(x, y, z) = i;
575 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f\n", n, x, y, z);
2791c829 576 }
807b9b57
JM
577 ++n;
578 }
b7cd847e 579 }
67a649dd
JM
580
581 if(gcode->m == 503) {
582 // just print the G92 setting as it is not saved
c2f7c261 583 // TODO linuxcnc does seem to save G92, so maybe we should here too
03b01bac 584 if(g92_offset != wcs_t(0, 0, 0)) {
67a649dd
JM
585 float x, y, z;
586 std::tie(x, y, z) = g92_offset;
587 gcode->stream->printf("G92 X%f Y%f Z%f ; NOT SAVED\n", x, y, z);
588 }
589 }
807b9b57 590 break;
33e4cc02 591
b7cd847e 592 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6 593 // the parameter args could be any letter each arm solution only accepts certain ones
03b01bac 594 BaseSolution::arm_options_t options = gcode->get_args();
ebc75fc6
JM
595 options.erase('S'); // don't include the S
596 options.erase('U'); // don't include the U
597 if(options.size() > 0) {
598 // set the specified options
599 arm_solution->set_optional(options);
600 }
601 options.clear();
b7cd847e 602 if(arm_solution->get_optional(options)) {
ebc75fc6 603 // foreach optional value
4710532a 604 for(auto &i : options) {
b7cd847e
JM
605 // print all current values of supported options
606 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 607 gcode->add_nl = true;
ec4773e5
JM
608 }
609 }
ec4773e5 610
4a839bea 611 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 612 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
613 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
614
03b01bac 615 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
4a839bea
JM
616 this->mm_per_line_segment = gcode->get_value('U');
617 this->delta_segments_per_second = 0;
618 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 619 }
4a839bea 620
ec4773e5 621 break;
b7cd847e 622 }
6989211c 623 }
494dc541
JM
624 }
625
c2f7c261
JM
626 if( this->motion_mode >= 0) {
627 process_move(gcode);
00e607c7 628 }
6bc4a00a 629
c2f7c261
JM
630 next_command_is_MCS = false; // must be on same line as G0 or G1
631}
350c8a60 632
5d2319a9 633// process a G0/G1/G2/G3
c2f7c261
JM
634void Robot::process_move(Gcode *gcode)
635{
2791c829 636 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
c2f7c261 637 float param[3]{NAN, NAN, NAN};
350c8a60
JM
638 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
639 char letter= 'X'+i;
640 if( gcode->has_letter(letter) ) {
641 param[i] = this->to_millimeters(gcode->get_value(letter));
350c8a60
JM
642 }
643 }
6bc4a00a 644
c2f7c261 645 float offset[3]{0,0,0};
4710532a
JM
646 for(char letter = 'I'; letter <= 'K'; letter++) {
647 if( gcode->has_letter(letter) ) {
648 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
649 }
650 }
00e607c7 651
c2f7c261
JM
652 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
653 float target[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
350c8a60
JM
654 if(!next_command_is_MCS) {
655 if(this->absolute_mode) {
c2f7c261
JM
656 // apply wcs offsets and g92 offset and tool offset
657 if(!isnan(param[X_AXIS])) {
658 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);
659 }
660
661 if(!isnan(param[Y_AXIS])) {
662 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);
663 }
664
665 if(!isnan(param[Z_AXIS])) {
666 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);
667 }
350c8a60
JM
668
669 }else{
670 // they are deltas from the last_milestone if specified
671 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
c2f7c261 672 if(!isnan(param[i])) target[i] = param[i] + last_milestone[i];
a6bbe768
JM
673 }
674 }
675
350c8a60 676 }else{
c2f7c261
JM
677 // already in machine coordinates, we do not add tool offset for that
678 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
679 if(!isnan(param[i])) target[i] = param[i];
680 }
c2885de8 681 }
6bc4a00a 682
4710532a 683 if( gcode->has_letter('F') ) {
7369629d 684 if( this->motion_mode == MOTION_MODE_SEEK )
da947c62 685 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 686 else
da947c62 687 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 688 }
6bc4a00a 689
350c8a60
JM
690 bool moved= false;
691 //Perform any physical actions
fae93525 692 switch(this->motion_mode) {
350c8a60
JM
693 case MOTION_MODE_CANCEL:
694 break;
695 case MOTION_MODE_SEEK:
696 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute );
697 break;
698 case MOTION_MODE_LINEAR:
699 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute );
700 break;
fae93525 701 case MOTION_MODE_CW_ARC:
350c8a60
JM
702 case MOTION_MODE_CCW_ARC:
703 moved= this->compute_arc(gcode, offset, target );
704 break;
4cff3ded 705 }
13e4a3f9 706
c2f7c261
JM
707 if(moved) {
708 // set last_milestone to the calculated target
709 memcpy(this->last_milestone, target, sizeof(this->last_milestone));
350c8a60 710 }
edac9072
AW
711}
712
5984acdf 713// We received a new gcode, and one of the functions
edac9072
AW
714// determined the distance for that given gcode. So now we can attach this gcode to the right block
715// and continue
03b01bac 716void Robot::distance_in_gcode_is_known(Gcode * gcode)
4710532a 717{
edac9072 718 //If the queue is empty, execute immediatly, otherwise attach to the last added block
e0ee24ed 719 THEKERNEL->conveyor->append_gcode(gcode);
edac9072
AW
720}
721
a6bbe768 722// reset the machine position for all axis. Used for homing.
c2f7c261 723// NOTE this sets the last_milestone which is machine position before compensation transform
cef9acea
JM
724void Robot::reset_axis_position(float x, float y, float z)
725{
c2f7c261
JM
726 last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
727 last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
728 last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
cef9acea 729
c2f7c261
JM
730 // calculate what the last_machine_position would be by applying compensation transform if enabled
731 // otherwise it is the same as last_milestone
732 if(compensationTransform) {
733 compensationTransform(last_machine_position);
734 }
a6bbe768
JM
735
736 // now set the actuator positions to match
807b9b57 737 ActuatorCoordinates actuator_pos;
c2f7c261 738 arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
807b9b57 739 for (size_t i = 0; i < actuators.size(); i++)
cef9acea
JM
740 actuators[i]->change_last_milestone(actuator_pos[i]);
741}
742
807b9b57 743// Reset the position for an axis (used in homing)
4710532a
JM
744void Robot::reset_axis_position(float position, int axis)
745{
c2f7c261
JM
746 last_milestone[axis] = position;
747 reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
4cff3ded
AW
748}
749
a6bbe768 750// Use FK to find out where actuator is and reset to match
728477c4
JM
751void Robot::reset_position_from_current_actuator_position()
752{
807b9b57
JM
753 ActuatorCoordinates actuator_pos;
754 for (size_t i = 0; i < actuators.size(); i++) {
755 actuator_pos[i] = actuators[i]->get_current_position();
756 }
c2f7c261
JM
757 arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
758 // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
759 memcpy(last_milestone, last_machine_position, sizeof last_milestone);
cf91d4f3
JM
760
761 // now reset actuator correctly, NOTE this may lose a little precision
a6bbe768 762 // NOTE I do not recall why this was necessary, maybe to correct for small errors in FK
c2f7c261 763 // arm_solution->cartesian_to_actuator(last_machine_position, actuator_pos);
a6bbe768
JM
764 // for (size_t i = 0; i < actuators.size(); i++)
765 // actuators[i]->change_last_milestone(actuator_pos[i]);
728477c4 766}
edac9072 767
c2f7c261
JM
768// Convert target (in machine coordinates) from millimeters to steps, and append this to the planner
769// target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
770// all transforms and is what we actually convert to actuator positions
350c8a60 771bool Robot::append_milestone(Gcode * gcode, const float target[], float rate_mm_s)
df6a30f2 772{
1ad23cd3 773 float deltas[3];
df6a30f2 774 float unit_vec[3];
807b9b57 775 ActuatorCoordinates actuator_pos;
03b01bac 776 float transformed_target[3]; // adjust target for bed compensation and WCS offsets
df6a30f2
MM
777 float millimeters_of_travel;
778
3632a517
JM
779 // unity transform by default
780 memcpy(transformed_target, target, sizeof(transformed_target));
5e45206a 781
350c8a60
JM
782 // check function pointer and call if set to transform the target to compensate for bed
783 if(compensationTransform) {
784 // some compensation strategies can transform XYZ, some just change Z
785 compensationTransform(transformed_target);
00e607c7 786 }
807b9b57 787
a6bbe768 788 // find distance moved by each axis, use transformed target from the current machine position
03b01bac 789 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
c2f7c261 790 deltas[axis] = transformed_target[axis] - last_machine_position[axis];
3632a517 791 }
aab6cbba 792
edac9072 793 // Compute how long this move moves, so we can attach it to the block for later use
869acfb8 794 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
df6a30f2 795
a6bbe768
JM
796 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
797 // 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 798 if(millimeters_of_travel < 0.00001F) return false;
a6bbe768
JM
799
800 // this is the machine position
c2f7c261 801 memcpy(this->last_machine_position, transformed_target, sizeof(this->last_machine_position));
a6bbe768 802
df6a30f2
MM
803 // find distance unit vector
804 for (int i = 0; i < 3; i++)
805 unit_vec[i] = deltas[i] / millimeters_of_travel;
806
807 // Do not move faster than the configured cartesian limits
4710532a
JM
808 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
809 if ( max_speeds[axis] > 0 ) {
da947c62 810 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
df6a30f2
MM
811
812 if (axis_speed > max_speeds[axis])
da947c62 813 rate_mm_s *= ( max_speeds[axis] / axis_speed );
7b470506
AW
814 }
815 }
4cff3ded 816
c2f7c261
JM
817 // find actuator position given the machine position, use actual adjusted target
818 arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
df6a30f2 819
03b01bac 820 float isecs = rate_mm_s / millimeters_of_travel;
df6a30f2 821 // check per-actuator speed limits
807b9b57 822 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
928467c0 823 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
03b01bac 824 if (actuator_rate > actuators[actuator]->get_max_rate()) {
3494f3d0 825 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
03b01bac 826 isecs = rate_mm_s / millimeters_of_travel;
928467c0
JM
827 }
828 }
829
edac9072 830 // Append the block to the planner
da947c62 831 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
4cff3ded 832
350c8a60 833 return true;
4cff3ded
AW
834}
835
edac9072 836// Append a move to the queue ( cutting it into segments if needed )
c2f7c261 837bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s )
4710532a 838{
c2f7c261 839 // Find out the distance for this move in MCS
a9d299ab 840 // 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 841 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 842
3b4b05b8 843 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
350c8a60 844 if( gcode->millimeters_of_travel < 0.00001F ) return false;
436a2cd1 845
edac9072 846 // Mark the gcode as having a known distance
5dcb2ff3 847 this->distance_in_gcode_is_known( gcode );
436a2cd1 848
d2adef5e
JM
849 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
850 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
851 // We also check we are not exceeding the E max_speed for the current extruder
852 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
853 // we need to ask it now passing in the relevant data.
854 // NOTE we need to do this before we segment the line (for deltas)
855 if(gcode->has_letter('E')) {
856 float data[2];
03b01bac
JM
857 data[0] = gcode->get_value('E'); // E target (maybe absolute or relative)
858 data[1] = rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
d2adef5e
JM
859 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
860 rate_mm_s *= data[1];
861 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
862 }
863 }
864
c2f7c261 865 // 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
866 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
867 // 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 868 uint16_t segments;
5984acdf 869
c2885de8 870 if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
871 // enabled if set to something > 1, it is set to 0.0 by default
872 // segment based on current speed and requested segments per second
873 // the faster the travel speed the fewer segments needed
874 // NOTE rate is mm/sec and we take into account any speed override
da947c62 875 float seconds = gcode->millimeters_of_travel / rate_mm_s;
9502f9d5 876 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 877 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 878
4710532a
JM
879 } else {
880 if(this->mm_per_line_segment == 0.0F) {
881 segments = 1; // don't split it up
882 } else {
9502f9d5 883 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
884 }
885 }
5984acdf 886
350c8a60 887 bool moved= false;
4710532a 888 if (segments > 1) {
2ba859c9
MM
889 // A vector to keep track of the endpoint of each segment
890 float segment_delta[3];
891 float segment_end[3];
892
893 // How far do we move each segment?
9fff6045 894 for (int i = X_AXIS; i <= Z_AXIS; i++)
2791c829 895 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
4cff3ded 896
c8e0fb15
MM
897 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
898 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 899 for (int i = 1; i < segments; i++) {
350c8a60 900 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
4710532a 901 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
2791c829 902 segment_end[axis] = last_milestone[axis] + segment_delta[axis];
2ba859c9
MM
903
904 // Append the end of this segment to the queue
350c8a60
JM
905 bool b= this->append_milestone(gcode, segment_end, rate_mm_s);
906 moved= moved || b;
2ba859c9 907 }
4cff3ded 908 }
5984acdf
MM
909
910 // Append the end of this full move to the queue
350c8a60 911 if(this->append_milestone(gcode, target, rate_mm_s)) moved= true;
2134bcf2 912
a6bbe768 913 this->next_command_is_MCS = false; // always reset this
00e607c7 914
350c8a60
JM
915 if(moved) {
916 // if adding these blocks didn't start executing, do that now
917 THEKERNEL->conveyor->ensure_running();
918 }
919
920 return moved;
4cff3ded
AW
921}
922
4cff3ded 923
edac9072 924// Append an arc to the queue ( cutting it into segments as needed )
350c8a60 925bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
4710532a 926{
aab6cbba 927
edac9072 928 // Scary math
2ba859c9
MM
929 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
930 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
931 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
1ad23cd3
MM
932 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
933 float r_axis1 = -offset[this->plane_axis_1];
934 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
935 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
aab6cbba 936
51871fb8 937 // Patch from GRBL Firmware - Christoph Baumann 04072015
aab6cbba 938 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
03b01bac 939 float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
5fa0c173 940 if (is_clockwise) { // Correct atan2 output per direction
03b01bac 941 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2 * M_PI; }
5fa0c173 942 } else {
03b01bac 943 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2 * M_PI; }
4710532a 944 }
aab6cbba 945
edac9072 946 // Find the distance for this gcode
4710532a 947 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
436a2cd1 948
edac9072 949 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
3b4b05b8 950 if( gcode->millimeters_of_travel < 0.00001F ) {
350c8a60 951 return false;
4710532a 952 }
5dcb2ff3 953
edac9072 954 // Mark the gcode as having a known distance
d149c730 955 this->distance_in_gcode_is_known( gcode );
5984acdf
MM
956
957 // Figure out how many segments for this gcode
c8f4ee77 958 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
aab6cbba 959
4710532a
JM
960 float theta_per_segment = angular_travel / segments;
961 float linear_per_segment = linear_travel / segments;
aab6cbba
AW
962
963 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
964 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
965 r_T = [cos(phi) -sin(phi);
966 sin(phi) cos(phi] * r ;
967 For arc generation, the center of the circle is the axis of rotation and the radius vector is
968 defined from the circle center to the initial position. Each line segment is formed by successive
969 vector rotations. This requires only two cos() and sin() computations to form the rotation
970 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1ad23cd3 971 all float numbers are single precision on the Arduino. (True float precision will not have
aab6cbba
AW
972 round off issues for CNC applications.) Single precision error can accumulate to be greater than
973 tool precision in some cases. Therefore, arc path correction is implemented.
974
975 Small angle approximation may be used to reduce computation overhead further. This approximation
976 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
977 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
978 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
979 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
980 issue for CNC machines with the single precision Arduino calculations.
981 This approximation also allows mc_arc to immediately insert a line segment into the planner
982 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
983 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
984 This is important when there are successive arc motions.
985 */
986 // Vector rotation matrix values
4710532a 987 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1ad23cd3 988 float sin_T = theta_per_segment;
aab6cbba 989
1ad23cd3
MM
990 float arc_target[3];
991 float sin_Ti;
992 float cos_Ti;
993 float r_axisi;
aab6cbba
AW
994 uint16_t i;
995 int8_t count = 0;
996
997 // Initialize the linear axis
2ba859c9 998 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
aab6cbba 999
350c8a60 1000 bool moved= false;
4710532a 1001 for (i = 1; i < segments; i++) { // Increment (segments-1)
350c8a60 1002 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
aab6cbba 1003
b66fb830 1004 if (count < this->arc_correction ) {
4710532a
JM
1005 // Apply vector rotation matrix
1006 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1007 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1008 r_axis1 = r_axisi;
1009 count++;
aab6cbba 1010 } else {
4710532a
JM
1011 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1012 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1013 cos_Ti = cosf(i * theta_per_segment);
1014 sin_Ti = sinf(i * theta_per_segment);
1015 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1016 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1017 count = 0;
aab6cbba
AW
1018 }
1019
1020 // Update arc_target location
1021 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1022 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1023 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
1024
1025 // Append this segment to the queue
350c8a60
JM
1026 bool b= this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
1027 moved= moved || b;
aab6cbba 1028 }
edac9072 1029
aab6cbba 1030 // Ensure last segment arrives at target location.
350c8a60
JM
1031 if(this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute)) moved= true;
1032
1033 return moved;
aab6cbba
AW
1034}
1035
edac9072 1036// Do the math for an arc and add it to the queue
350c8a60 1037bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[])
4710532a 1038{
aab6cbba
AW
1039
1040 // Find the radius
13addf09 1041 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
1042
1043 // Set clockwise/counter-clockwise sign for mc_arc computations
1044 bool is_clockwise = false;
4710532a
JM
1045 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
1046 is_clockwise = true;
1047 }
aab6cbba
AW
1048
1049 // Append arc
350c8a60 1050 return this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
1051}
1052
1053
4710532a
JM
1054float Robot::theta(float x, float y)
1055{
1056 float t = atanf(x / fabs(y));
1057 if (y > 0) {
1058 return(t);
1059 } else {
1060 if (t > 0) {
1061 return(M_PI - t);
1062 } else {
1063 return(-M_PI - t);
1064 }
1065 }
4cff3ded
AW
1066}
1067
4710532a
JM
1068void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1069{
4cff3ded
AW
1070 this->plane_axis_0 = axis_0;
1071 this->plane_axis_1 = axis_1;
1072 this->plane_axis_2 = axis_2;
1073}
1074
fae93525 1075void Robot::clearToolOffset()
4710532a 1076{
c2f7c261 1077 this->tool_offset= wcs_t(0,0,0);
fae93525
JM
1078}
1079
1080void Robot::setToolOffset(const float offset[3])
1081{
c2f7c261 1082 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
5966b7d0
AT
1083}
1084