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