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