#define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
#define segment_z_moves_checksum CHECKSUM("segment_z_moves")
#define save_g92_checksum CHECKSUM("save_g92")
+#define set_g92_checksum CHECKSUM("set_g92")
// arm solutions
#define arm_solution_checksum CHECKSUM("arm_solution")
#define beta_checksum CHECKSUM("beta")
#define gamma_checksum CHECKSUM("gamma")
+#define laser_module_default_power_checksum CHECKSUM("laser_module_default_power")
+
#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7F // Float (radians)
#define PI 3.14159265358979323846F // force to be float, do not use M_PI
seconds_per_minute = 60.0F;
this->clearToolOffset();
this->compensationTransform = nullptr;
+ this->get_e_scale_fnc= nullptr;
this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
this->next_command_is_MCS = false;
this->disable_segmentation= false;
+ this->disable_arm_solution= false;
this->n_motors= 0;
- this->actuators.fill(nullptr);
}
//Called when the module has just been loaded
this->segment_z_moves = THEKERNEL->config->value(segment_z_moves_checksum )->by_default(true)->as_bool();
this->save_g92 = THEKERNEL->config->value(save_g92_checksum )->by_default(false)->as_bool();
+ string g92 = THEKERNEL->config->value(set_g92_checksum )->by_default("")->as_string();
+ if(!g92.empty()) {
+ // optional setting for a fixed G92 offset
+ std::vector<float> t= parse_number_list(g92.c_str());
+ if(t.size() == 3) {
+ g92_offset = wcs_t(t[0], t[1], t[2]);
+ }
+ }
+
+ // default s value for laser
+ this->s_value = THEKERNEL->config->value(laser_module_default_power_checksum)->by_default(0.8F)->as_number();
// Make our Primary XYZ StepperMotors
uint16_t const checksums[][6] = {
THEKERNEL->streams->printf("FATAL: too many motors, increase k_max_actuators\n");
__debugbreak();
}
- actuators[n_motors++]= motor;
- return n_motors-1;
+ actuators.push_back(motor);
+ return n_motors++;
}
void Robot::push_state()
return v;
}
+void Robot::get_current_machine_position(float *pos) const
+{
+ // get real time current actuator position in mm
+ ActuatorCoordinates current_position{
+ actuators[X_AXIS]->get_current_position(),
+ actuators[Y_AXIS]->get_current_position(),
+ actuators[Z_AXIS]->get_current_position()
+ };
+
+ // get machine position from the actuator position using FK
+ arm_solution->actuator_to_cartesian(current_position, pos);
+}
+
int Robot::print_position(uint8_t subcode, char *buf, size_t bufsize) const
{
// M114.1 is a new way to do this (similar to how GRBL does it).
// it returns the realtime position based on the current step position of the actuators.
// this does require a FK to get a machine position from the actuator position
// and then invert all the transforms to get a workspace position from machine position
- // M114 just does it the old way uses last_milestone and does inversse transforms to get the requested position
+ // M114 just does it the old way uses last_milestone and does inverse transforms to get the requested position
int n = 0;
if(subcode == 0) { // M114 print WCS
wcs_t pos= mcs2wcs(last_milestone);
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)));
- } 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)
+ } else if(subcode == 4) {
+ // M114.4 print last milestone
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]);
- } 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)
+ } 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)
+ // will differ from LMS by the compensation at the current position otherwise
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]);
} else {
// get real time positions
- // current actuator position in mm
- ActuatorCoordinates current_position{
- actuators[X_AXIS]->get_current_position(),
- actuators[Y_AXIS]->get_current_position(),
- actuators[Z_AXIS]->get_current_position()
- };
-
- // get machine position from the actuator position using FK
float mpos[3];
- arm_solution->actuator_to_cartesian(current_position, mpos);
+ get_current_machine_position(mpos);
+
+ // current_position/mpos includes the compensation transform so we need to get the inverse to get actual position
+ if(compensationTransform) compensationTransform(mpos, true); // get inverse compensation transform
if(subcode == 1) { // M114.1 print realtime WCS
- // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
wcs_t pos= mcs2wcs(mpos);
- 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)));
+ n = snprintf(buf, bufsize, "WCS: 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)));
} else if(subcode == 2) { // M114.2 print realtime Machine coordinate system
- n = snprintf(buf, bufsize, "MPOS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
+ n = snprintf(buf, bufsize, "MCS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
} else if(subcode == 3) { // M114.3 print realtime actuator position
- n = snprintf(buf, bufsize, "APOS: A:%1.4f B:%1.4f C:%1.4f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
+ // get real time current actuator position in mm
+ ActuatorCoordinates current_position{
+ actuators[X_AXIS]->get_current_position(),
+ actuators[Y_AXIS]->get_current_position(),
+ actuators[Z_AXIS]->get_current_position()
+ };
+ 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]);
}
}
return n;
float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
if (step_freq > THEKERNEL->base_stepping_frequency) {
actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
- THEKERNEL->streams->printf("WARNING: actuator %d rate exceeds base_stepping_frequency * ..._steps_per_mm: %f, setting to %f\n", i, step_freq, actuators[i]->max_rate);
+ 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());
}
}
}
}
if (delay_ms > 0) {
// drain queue
- THEKERNEL->conveyor->wait_for_empty_queue();
+ THEKERNEL->conveyor->wait_for_idle();
// wait for specified time
uint32_t start = us_ticker_read(); // mbed call
while ((us_ticker_read() - start) < delay_ms * 1000) {
if(gcode->subcode == 0 && (gcode->has_letter('E') || gcode->get_num_args() == 0)){
// reset the E position, legacy for 3d Printers to be reprap compatible
// find the selected extruder
+ // 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
for (int i = E_AXIS; i < n_motors; ++i) {
if(actuators[i]->is_selected()) {
float e= gcode->has_letter('E') ? gcode->get_value('E') : 0;
case 18: // this used to support parameters, now it ignores them
case 84:
- THEKERNEL->conveyor->wait_for_empty_queue();
+ THEKERNEL->conveyor->wait_for_idle();
THEKERNEL->call_event(ON_ENABLE, nullptr); // turn all enable pins off
break;
if (gcode->has_letter('Z'))
actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
- gcode->stream->printf("X:%f Y:%f Z:%f ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
+ 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());
gcode->add_nl = true;
check_max_actuator_speeds();
return;
pop_state();
break;
- case 203: // M203 Set maximum feedrates in mm/sec
- if (gcode->has_letter('X'))
- this->max_speeds[X_AXIS] = gcode->get_value('X');
- if (gcode->has_letter('Y'))
- this->max_speeds[Y_AXIS] = gcode->get_value('Y');
- if (gcode->has_letter('Z'))
- this->max_speeds[Z_AXIS] = gcode->get_value('Z');
- for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
- if (gcode->has_letter('A' + i))
- actuators[i]->set_max_rate(gcode->get_value('A' + i));
- }
- check_max_actuator_speeds();
+ case 203: // M203 Set maximum feedrates in mm/sec, M203.1 set maximum actuator feedrates
+ if(gcode->get_num_args() == 0) {
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ gcode->stream->printf(" %c: %g ", 'X' + i, gcode->subcode == 0 ? this->max_speeds[i] : actuators[i]->get_max_rate());
+ }
+ gcode->add_nl = true;
+
+ }else{
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ if (gcode->has_letter('X' + i)) {
+ float v= gcode->get_value('X'+i);
+ if(gcode->subcode == 0) this->max_speeds[i]= v;
+ else if(gcode->subcode == 1) actuators[i]->set_max_rate(v);
+ }
+ }
+
+ // this format is deprecated
+ if(gcode->subcode == 0 && (gcode->has_letter('A') || gcode->has_letter('B') || gcode->has_letter('C'))) {
+ gcode->stream->printf("NOTE this format is deprecated, Use M203.1 instead\n");
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ if (gcode->has_letter('A' + i)) {
+ float v= gcode->get_value('A'+i);
+ actuators[i]->set_max_rate(v);
+ }
+ }
+ }
- if(gcode->get_num_args() == 0) {
- gcode->stream->printf("X:%g Y:%g Z:%g",
- this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
- for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
- gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
+ if(gcode->subcode == 1) check_max_actuator_speeds();
}
- gcode->add_nl = true;
- }
- break;
+ break;
case 204: // M204 Snnn - set default acceleration to nnn, Xnnn Ynnn Znnn sets axis specific acceleration
if (gcode->has_letter('S')) {
}
break;
- case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
+ case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed
if (gcode->has_letter('X')) {
float jd = gcode->get_value('X');
// enforce minimum
if (gcode->has_letter('Z')) {
float jd = gcode->get_value('Z');
// enforce minimum, -1 disables it and uses regular junction deviation
- if (jd < -1.0F)
- jd = -1.0F;
+ if (jd <= -1.0F)
+ jd = NAN;
THEKERNEL->planner->z_junction_deviation = jd;
}
if (gcode->has_letter('S')) {
break;
case 400: // wait until all moves are done up to this point
- THEKERNEL->conveyor->wait_for_empty_queue();
+ THEKERNEL->conveyor->wait_for_idle();
break;
case 500: // M500 saves some volatile settings to config override file
case 503: { // M503 just prints the settings
- 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);
- gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", default_acceleration, actuators[Z_AXIS]->get_acceleration()); // TODO only print XYZ if not NAN
- 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);
- gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f A%1.5f B%1.5f C%1.5f",
- this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
- actuators[X_AXIS]->get_max_rate(), actuators[Y_AXIS]->get_max_rate(), actuators[Z_AXIS]->get_max_rate());
+ 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());
+
+ // only print XYZ if not NAN
+ gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f ", default_acceleration);
+ for (int i = X_AXIS; i <= Z_AXIS; ++i) {
+ if(!isnan(actuators[i]->get_acceleration())) gcode->stream->printf("%c%1.5f ", 'X'+i, actuators[i]->get_acceleration());
+ }
gcode->stream->printf("\n");
+ 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);
+
+ 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]);
+ 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());
+
// get or save any arm solution specific optional values
BaseSolution::arm_options_t options;
if(arm_solution->get_optional(options) && !options.empty()) {
}
if( motion_mode != NONE) {
+ is_g123= motion_mode != SEEK;
process_move(gcode, motion_mode);
+
+ }else{
+ is_g123= false;
}
next_command_is_MCS = false; // must be on same line as G0 or G1
void Robot::process_move(Gcode *gcode, enum MOTION_MODE_T motion_mode)
{
// we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
+ // get XYZ and one E (which goes to the selected extruder)
float param[4]{NAN, NAN, NAN, NAN};
// process primary axis
this->feed_rate = this->to_millimeters( gcode->get_value('F') );
}
+ // S is modal When specified on a G0/1/2/3 command
+ if(gcode->has_letter('S')) s_value= gcode->get_value('S');
+
bool moved= false;
// Perform any physical actions
case CW_ARC:
case CCW_ARC:
+ // Note arcs are not currently supported by extruder based machines, as 3D slicers do not use arcs (G2/G3)
moved= this->compute_arc(gcode, offset, target, motion_mode);
break;
}
if(moved) {
// set last_milestone to the calculated target
- memcpy(this->last_milestone, target, sizeof(this->last_milestone));
+ memcpy(last_milestone, target, n_motors*sizeof(float));
}
}
// reset the machine position for all axis. Used for homing.
-// During homing compensation is turned off (actually not used as it drives steppers directly)
-// once homed and reset_axis called compensation is used for the move to origin and back off home if enabled,
-// so in those cases the final position is compensated.
+// after homing we supply the cartesian coordinates that the head is at when homed,
+// however for Z this is the compensated Z position (if enabled)
+// So we need to apply the inverse compensation transform to the supplied coordinates to get the correct last milestone
+// this will make the results from M114 and ? consistent after homing.
void Robot::reset_axis_position(float x, float y, float z)
{
- // these are set to the same as compensation was not used to get to the current position
+ // set both the same initially
last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
- // now set the actuator positions to match
+ if(compensationTransform) {
+ // apply inverse transform to get last_milestone
+ compensationTransform(last_milestone, true);
+ }
+
+ // now set the actuator positions based on the supplied compensated position
ActuatorCoordinates actuator_pos;
arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
for (size_t i = X_AXIS; i <= Z_AXIS; i++)
actuators[i]->change_last_milestone(actuator_pos[i]);
}
-// Reset the position for an axis (used in homing)
+// Reset the position for an axis (used in homing, and to reset extruder after suspend)
void Robot::reset_axis_position(float position, int axis)
{
- last_milestone[axis] = position;
- reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
+ last_machine_position[axis] = position;
+ if(axis <= Z_AXIS) {
+ reset_axis_position(last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
+#if MAX_ROBOT_ACTUATORS > 3
+ }else{
+ // extruders need to be set not calculated
+ last_machine_position[axis]= position;
+#endif
+ }
}
// similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
-// then sets the axis positions to match. currently only called from Endstops.cpp
+// then sets the axis positions to match. currently only called from Endstops.cpp and RotaryDeltaCalibration.cpp
void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
{
- for (size_t i = X_AXIS; i <= Z_AXIS; i++)
- actuators[i]->change_last_milestone(ac[i]);
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ if(!isnan(ac[i])) actuators[i]->change_last_milestone(ac[i]);
+ }
// now correct axis positions then recorrect actuator to account for rounding
reset_position_from_current_actuator_position();
// discover machine position from where actuators actually are
arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
- // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
memcpy(last_milestone, last_machine_position, sizeof last_milestone);
+ // last_machine_position includes the compensation transform so we need to get the inverse to get actual last_milestone
+ if(compensationTransform) compensationTransform(last_milestone, true); // get inverse compensation transform
+
// now reset actuator::last_milestone, NOTE this may lose a little precision as FK is not always entirely accurate.
// NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
// to get everything in perfect sync.
actuators[i]->change_last_milestone(actuator_pos[i]);
}
-// Convert target (in machine coordinates) from millimeters to steps, and append this to the planner
+// Convert target (in machine coordinates) to machine_position, then convert to actuator position and append this to the planner
// target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
// all transforms and is what we actually convert to actuator positions
-bool Robot::append_milestone(Gcode *gcode, const float target[], float rate_mm_s)
+bool Robot::append_milestone(const float target[], float rate_mm_s)
{
float deltas[n_motors];
- float transformed_target[n_motors]; // adjust target for bed compensation and WCS offsets
+ float transformed_target[n_motors]; // adjust target for bed compensation
float unit_vec[N_PRIMARY_AXIS];
- float millimeters_of_travel= 0;
-
- // catch negative or zero feed rates and return the same error as GRBL does
- if(rate_mm_s <= 0.0F) {
- gcode->is_error= true;
- gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
- return false;
- }
// unity transform by default
memcpy(transformed_target, target, n_motors*sizeof(float));
// check function pointer and call if set to transform the target to compensate for bed
if(compensationTransform) {
// some compensation strategies can transform XYZ, some just change Z
- compensationTransform(transformed_target);
+ compensationTransform(transformed_target, false);
}
bool move= false;
- float sos= 0;
+ float sos= 0; // sun of squares for just XYZ
// find distance moved by each axis, use transformed target from the current machine position
- for (size_t i = 0; i <= n_motors; i++) {
+ for (size_t i = 0; i < n_motors; i++) {
deltas[i] = transformed_target[i] - last_machine_position[i];
if(deltas[i] == 0) continue;
// at least one non zero delta
// nothing moved
if(!move) return false;
- // set if none of the primary axis is moving
- bool auxilliary_move= false;
- if(sos > 0.0F){
- millimeters_of_travel= sqrtf(sos);
+ // see if this is a primary axis move or not
+ bool auxilliary_move= deltas[X_AXIS] == 0 && deltas[Y_AXIS] == 0 && deltas[Z_AXIS] == 0;
- } else if(n_motors >= E_AXIS) { // if we have more than 3 axis/actuators (XYZE)
- // non primary axis move (like extrude)
- // select the biggest one (usually just E)
- auto mi= std::max_element(&deltas[E_AXIS], &deltas[n_motors], [](float a, float b){ return std::abs(a) < std::abs(b); } );
- millimeters_of_travel= std::abs(*mi);
- auxilliary_move= true;
-
- }else{
- // shouldn't happen but just in case
- return false;
- }
+ // total movement, use XYZ if a primary axis otherwise we calculate distance for E after scaling to mm
+ float distance= auxilliary_move ? 0 : sqrtf(sos);
// it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
// 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
- if(millimeters_of_travel < 0.00001F) return false;
+ if(!auxilliary_move && distance < 0.00001F) return false;
- // this is the machine position
- memcpy(this->last_machine_position, transformed_target, n_motors*sizeof(float));
if(!auxilliary_move) {
- // find distance unit vector for primary axis only
- for (size_t i = X_AXIS; i <= Z_AXIS; i++)
- unit_vec[i] = deltas[i] / millimeters_of_travel;
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ // find distance unit vector for primary axis only
+ unit_vec[i] = deltas[i] / distance;
- // Do not move faster than the configured cartesian limits for XYZ
- for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
- if ( max_speeds[axis] > 0 ) {
- float axis_speed = fabsf(unit_vec[axis] * rate_mm_s);
+ // Do not move faster than the configured cartesian limits for XYZ
+ if ( max_speeds[i] > 0 ) {
+ float axis_speed = fabsf(unit_vec[i] * rate_mm_s);
- if (axis_speed > max_speeds[axis])
- rate_mm_s *= ( max_speeds[axis] / axis_speed );
+ if (axis_speed > max_speeds[i])
+ rate_mm_s *= ( max_speeds[i] / axis_speed );
}
}
}
// find actuator position given the machine position, use actual adjusted target
ActuatorCoordinates actuator_pos;
- arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
+ if(!disable_arm_solution) {
+ arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
+
+ }else{
+ // basically the same as cartesian, would be used for special homing situations like for scara
+ for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
+ actuator_pos[i] = transformed_target[i];
+ }
+ }
#if MAX_ROBOT_ACTUATORS > 3
- // for the extruders just copy the position
- for (size_t i = E_AXIS; i < k_max_actuators; i++) {
- actuator_pos[i]= last_machine_position[i];
- if(!isnan(this->e_scale)) {
+ sos= 0;
+ // for the extruders just copy the position, and possibly scale it from mm³ to mm
+ for (size_t i = E_AXIS; i < n_motors; i++) {
+ actuator_pos[i]= transformed_target[i];
+ if(get_e_scale_fnc) {
// NOTE this relies on the fact only one extruder is active at a time
// scale for volumetric or flow rate
// TODO is this correct? scaling the absolute target? what if the scale changes?
- actuator_pos[i] *= this->e_scale;
+ // for volumetric it basically converts mm³ to mm, but what about flow rate?
+ actuator_pos[i] *= get_e_scale_fnc();
+ }
+ if(auxilliary_move) {
+ // for E only moves we need to use the scaled E to calculate the distance
+ sos += pow(actuator_pos[i] - actuators[i]->get_last_milestone(), 2);
}
}
+ if(auxilliary_move) {
+ distance= sqrtf(sos); // distance in mm of the e move
+ if(distance < 0.00001F) return false;
+ }
#endif
// use default acceleration to start with
float acceleration = default_acceleration;
- float isecs = rate_mm_s / millimeters_of_travel;
+ float isecs = rate_mm_s / distance;
// check per-actuator speed limits
for (size_t actuator = 0; actuator < n_motors; actuator++) {
float actuator_rate= d * isecs;
if (actuator_rate > actuators[actuator]->get_max_rate()) {
rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
- isecs = rate_mm_s / millimeters_of_travel;
+ isecs = rate_mm_s / distance;
}
- // adjust acceleration to lowest found in an active axis
- float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
- if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
- float ca = fabsf((deltas[actuator]/millimeters_of_travel) * acceleration);
- if (ca > ma) {
- acceleration *= ( ma / ca );
+ // adjust acceleration to lowest found, for now just primary axis unless it is an auxiliary move
+ // 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.
+ if(auxilliary_move || actuator <= Z_AXIS) {
+ float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
+ if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
+ float ca = fabsf((d/distance) * acceleration);
+ if (ca > ma) {
+ acceleration *= ( ma / ca );
+ }
}
}
}
// Append the block to the planner
- THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, auxilliary_move? nullptr : unit_vec, acceleration );
+ // NOTE that distance here should be either the distance travelled by the XYZ axis, or the E mm travel if a solo E move
+ if(THEKERNEL->planner->append_block( actuator_pos, n_motors, rate_mm_s, distance, auxilliary_move ? nullptr : unit_vec, acceleration, s_value, is_g123)) {
+ // this is the machine position
+ memcpy(this->last_machine_position, transformed_target, n_motors*sizeof(float));
+ return true;
+ }
- return true;
+ // no actual move
+ return false;
}
-// Used to plan a single move used by things like endstops when homing, zprobe, extruder retracts etc.
-// TODO this pretty much duplicates append_milestone, so try to refactor it away.
-bool Robot::solo_move(const float *delta, float rate_mm_s, uint8_t naxis)
+// Used to plan a single move used by things like endstops when homing, zprobe, extruder firmware retracts etc.
+bool Robot::delta_move(const float *delta, float rate_mm_s, uint8_t naxis)
{
if(THEKERNEL->is_halted()) return false;
- // catch negative or zero feed rates and return the same error as GRBL does
+ // catch negative or zero feed rates
if(rate_mm_s <= 0.0F) {
return false;
}
- bool move= false;
- float sos= 0;
-
- // find distance moved by each axis
- for (size_t i = 0; i <= naxis; i++) {
- if(delta[i] == 0) continue;
- // at least one non zero delta
- move = true;
- sos += powf(delta[i], 2);
- }
-
- // nothing moved
- if(!move) return false;
-
- // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
- // 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
- if(sos < 0.00001F) return false;
-
- float millimeters_of_travel= sqrtf(sos);
-
- // this is the new machine position
- for (int axis = 0; axis <= naxis; axis++) {
- this->last_machine_position[axis] += delta[axis];
- }
-
- // find actuator position given the machine position
- ActuatorCoordinates actuator_pos;
- arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
+ // get the absolute target position, default is current last_milestone
+ float target[n_motors];
+ memcpy(target, last_milestone, n_motors*sizeof(float));
- // for the extruders just copy the position, need to copy all possible actuators
- for (size_t i = N_PRIMARY_AXIS; i < k_max_actuators; i++) {
- actuator_pos[i]= last_machine_position[i];
- if(!isnan(this->e_scale)) {
- // NOTE this relies on the fact only one extruder is active at a time
- // scale for volumetric or flow rate
- // TODO is this correct? scaling the absolute target? what if the scale changes?
- actuator_pos[i] *= this->e_scale;
- }
+ // add in the deltas to get new target
+ for (int i= 0; i < naxis; i++) {
+ target[i] += delta[i];
}
- // use default acceleration to start with
- float acceleration = default_acceleration;
- float isecs = rate_mm_s / millimeters_of_travel;
-
- // check per-actuator speed limits
- for (size_t actuator = 0; actuator < naxis; actuator++) {
- float d = fabsf(actuator_pos[actuator] - actuators[actuator]->get_last_milestone());
- if(d == 0) continue; // no movement for this actuator
-
- float actuator_rate= d * isecs;
- if (actuator_rate > actuators[actuator]->get_max_rate()) {
- rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
- isecs = rate_mm_s / millimeters_of_travel;
- }
-
- // adjust acceleration to lowest found in an active axis
- float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
- if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
- float ca = fabsf((d/millimeters_of_travel) * acceleration);
- if (ca > ma) {
- acceleration *= ( ma / ca );
- }
- }
+ // submit for planning and if moved update last_milestone
+ if(append_milestone(target, rate_mm_s)) {
+ memcpy(last_milestone, target, n_motors*sizeof(float));
+ return true;
}
- // Append the block to the planner
- THEKERNEL->planner->append_block(actuator_pos, rate_mm_s, millimeters_of_travel, nullptr, acceleration);
- return true;
+ return false;
}
// Append a move to the queue ( cutting it into segments if needed )
bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s, float delta_e)
{
- // by default there is no e scaling required
- this->e_scale= NAN;
+ // catch negative or zero feed rates and return the same error as GRBL does
+ if(rate_mm_s <= 0.0F) {
+ gcode->is_error= true;
+ gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
+ return false;
+ }
// Find out the distance for this move in XYZ in MCS
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 ));
- if(millimeters_of_travel < 0.00001F) { // we have no movement in XYZ, probably E only
- return this->append_milestone(gcode, target, rate_mm_s);
+ if(millimeters_of_travel < 0.00001F) {
+ // we have no movement in XYZ, probably E only extrude or retract
+ return this->append_milestone(target, rate_mm_s);
}
/*
- For extruders, we need to do some extra work...
- if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit.
- Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
- We ask Extruder to do all the work but we need to pass in the relevant data.
- NOTE we need to do this before we segment the line (for deltas)
- This also sets any scaling due to flow rate and volumetric if a G1
+ For extruders, we need to do some extra work to limit the volumetric rate if specified...
+ If using volumetric limts we need to be using volumetric extrusion for this to work as Ennn needs to be in mm³ not mm
+ We ask Extruder to do all the work but we need to pass in the relevant data.
+ NOTE we need to do this before we segment the line (for deltas)
*/
if(!isnan(delta_e) && gcode->has_g && gcode->g == 1) {
float data[2]= {delta_e, rate_mm_s / millimeters_of_travel};
if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
rate_mm_s *= data[1]; // adjust the feedrate
- // we may need to scale the amount moved too
- this->e_scale= data[0];
}
}
segment_end[i] += segment_delta[i];
// Append the end of this segment to the queue
- bool b= this->append_milestone(gcode, segment_end, rate_mm_s);
+ bool b= this->append_milestone(segment_end, rate_mm_s);
moved= moved || b;
}
}
// Append the end of this full move to the queue
- if(this->append_milestone(gcode, target, rate_mm_s)) moved= true;
+ if(this->append_milestone(target, rate_mm_s)) moved= true;
this->next_command_is_MCS = false; // always reset this
// Append an arc to the queue ( cutting it into segments as needed )
+// TODO does not support any E parameters so cannot be used for 3D printing.
bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
{
+ float rate_mm_s= this->feed_rate / seconds_per_minute;
+ // catch negative or zero feed rates and return the same error as GRBL does
+ if(rate_mm_s <= 0.0F) {
+ gcode->is_error= true;
+ gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
+ return false;
+ }
// Scary math
float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
}
}
// Figure out how many segments for this gcode
+ // 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
uint16_t segments = ceilf(millimeters_of_travel / arc_segment);
//printf("Radius %f - Segment Length %f - Number of Segments %d\r\n",radius,arc_segment,segments); // Testing Purposes ONLY
arc_target[this->plane_axis_2] += linear_per_segment;
// Append this segment to the queue
- bool b= this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
+ bool b= this->append_milestone(arc_target, rate_mm_s);
moved= moved || b;
}
// Ensure last segment arrives at target location.
- if(this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute)) moved= true;
+ if(this->append_milestone(target, rate_mm_s)) moved= true;
return moved;
}