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