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