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