️ Optimize G2-G3 Arcs (#24366)

This commit is contained in:
tombrazier 2022-07-08 20:41:39 +01:00 committed by Scott Lahteine
parent af6995845c
commit 5c46ae4f00
8 changed files with 303 additions and 239 deletions

View file

@ -373,11 +373,12 @@
#endif #endif
NOLESS(segments, 1U); // Must have at least one segment NOLESS(segments, 1U); // Must have at least one segment
const float inv_segments = 1.0f / segments, // Reciprocal to save calculation const float inv_segments = 1.0f / segments; // Reciprocal to save calculation
segment_xyz_mm = SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments; // Length of each segment
// Add hints to help optimize the move
PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment
#if ENABLED(SCARA_FEEDRATE_SCALING) #if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = scaled_fr_mm_s / segment_xyz_mm; hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
#endif #endif
xyze_float_t diff = total * inv_segments; xyze_float_t diff = total * inv_segments;
@ -391,13 +392,9 @@
if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) { if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) {
while (--segments) { while (--segments) {
raw += diff; raw += diff;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
} }
planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, segment_xyz_mm planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints);
OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
);
return false; // Did not set current from destination return false; // Did not set current from destination
} }
@ -466,7 +463,7 @@
TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height
const float oldz = raw.z; raw.z += z_cxcy; const float oldz = raw.z; raw.z += z_cxcy;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration) ); planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
raw.z = oldz; raw.z = oldz;
if (segments == 0) // done with last segment if (segments == 0) // done with last segment

View file

@ -172,8 +172,9 @@ Joystick joystick;
current_position += move_dist; current_position += move_dist;
apply_motion_limits(current_position); apply_motion_limits(current_position);
const float length = sqrt(hypot2); const float length = sqrt(hypot2);
PlannerHints hints(length);
injecting_now = true; injecting_now = true;
planner.buffer_line(current_position, length / seg_time, active_extruder, length); planner.buffer_line(current_position, length / seg_time, active_extruder, hints);
injecting_now = false; injecting_now = false;
} }
} }

View file

@ -191,9 +191,12 @@ void plan_arc(
const uint16_t segments = nominal_segment_mm > (MAX_ARC_SEGMENT_MM) ? CEIL(flat_mm / (MAX_ARC_SEGMENT_MM)) : const uint16_t segments = nominal_segment_mm > (MAX_ARC_SEGMENT_MM) ? CEIL(flat_mm / (MAX_ARC_SEGMENT_MM)) :
nominal_segment_mm < (MIN_ARC_SEGMENT_MM) ? _MAX(1, FLOOR(flat_mm / (MIN_ARC_SEGMENT_MM))) : nominal_segment_mm < (MIN_ARC_SEGMENT_MM) ? _MAX(1, FLOOR(flat_mm / (MIN_ARC_SEGMENT_MM))) :
nominal_segments; nominal_segments;
const float segment_mm = flat_mm / segments;
// Add hints to help optimize the move
PlannerHints hints;
#if ENABLED(SCARA_FEEDRATE_SCALING) #if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = (scaled_fr_mm_s / flat_mm) * segments; hints.inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
#endif #endif
/** /**
@ -259,6 +262,17 @@ void plan_arc(
int8_t arc_recalc_count = N_ARC_CORRECTION; int8_t arc_recalc_count = N_ARC_CORRECTION;
#endif #endif
// An arc can always complete within limits from a speed which...
// a) is <= any configured maximum speed,
// b) does not require centripetal force greater than any configured maximum acceleration,
// c) is <= nominal speed,
// d) allows the print head to stop in the remining length of the curve within all configured maximum accelerations.
// The last has to be calculated every time through the loop.
const float limiting_accel = _MIN(planner.settings.max_acceleration_mm_per_s2[axis_p], planner.settings.max_acceleration_mm_per_s2[axis_q]),
limiting_speed = _MIN(planner.settings.max_feedrate_mm_s[axis_p], planner.settings.max_acceleration_mm_per_s2[axis_q]),
limiting_speed_sqr = _MIN(sq(limiting_speed), limiting_accel * radius, sq(scaled_fr_mm_s));
float arc_mm_remaining = flat_mm;
for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
thermalManager.task(); thermalManager.task();
@ -311,8 +325,14 @@ void plan_arc(
planner.apply_leveling(raw); planner.apply_leveling(raw);
#endif #endif
if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0 OPTARG(SCARA_FEEDRATE_SCALING, inv_duration))) // calculate safe speed for stopping by the end of the arc
arc_mm_remaining -= segment_mm;
hints.safe_exit_speed_sqr = _MIN(limiting_speed_sqr, 2 * limiting_accel * arc_mm_remaining);
if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints))
break; break;
hints.curve_radius = radius;
} }
} }
@ -328,7 +348,9 @@ void plan_arc(
planner.apply_leveling(raw); planner.apply_leveling(raw);
#endif #endif
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0 OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)); hints.curve_radius = 0;
hints.safe_exit_speed_sqr = 0.0f;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
#if ENABLED(AUTO_BED_LEVELING_UBL) #if ENABLED(AUTO_BED_LEVELING_UBL)
ARC_LIJK_CODE(raw[axis_l] = start_L, raw.i = start_I, raw.j = start_J, raw.k = start_K); ARC_LIJK_CODE(raw[axis_l] = start_L, raw.i = start_I, raw.j = start_J, raw.k = start_K);

View file

@ -1013,19 +1013,18 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
NOLESS(segments, 1U); NOLESS(segments, 1U);
// The approximate length of each segment // The approximate length of each segment
const float inv_segments = 1.0f / float(segments), const float inv_segments = 1.0f / float(segments);
cartesian_segment_mm = cartesian_mm * inv_segments;
const xyze_float_t segment_distance = diff * inv_segments; const xyze_float_t segment_distance = diff * inv_segments;
#if ENABLED(SCARA_FEEDRATE_SCALING) // Add hints to help optimize the move
const float inv_duration = scaled_fr_mm_s / cartesian_segment_mm; PlannerHints hints(cartesian_mm * inv_segments);
#endif TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
/* /*
SERIAL_ECHOPGM("mm=", cartesian_mm); SERIAL_ECHOPGM("mm=", cartesian_mm);
SERIAL_ECHOPGM(" seconds=", seconds); SERIAL_ECHOPGM(" seconds=", seconds);
SERIAL_ECHOPGM(" segments=", segments); SERIAL_ECHOPGM(" segments=", segments);
SERIAL_ECHOPGM(" segment_mm=", cartesian_segment_mm); SERIAL_ECHOPGM(" segment_mm=", hints.millimeters);
SERIAL_EOL(); SERIAL_EOL();
//*/ //*/
@ -1037,11 +1036,12 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
while (--segments) { while (--segments) {
segment_idle(next_idle_ms); segment_idle(next_idle_ms);
raw += segment_distance; raw += segment_distance;
if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration))) break; if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints))
break;
} }
// Ensure last segment arrives at target location. // Ensure last segment arrives at target location.
planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)); planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints);
return false; // caller will update current_position return false; // caller will update current_position
} }
@ -1080,17 +1080,16 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
NOLESS(segments, 1U); NOLESS(segments, 1U);
// The approximate length of each segment // The approximate length of each segment
const float inv_segments = 1.0f / float(segments), const float inv_segments = 1.0f / float(segments);
cartesian_segment_mm = cartesian_mm * inv_segments;
const xyze_float_t segment_distance = diff * inv_segments; const xyze_float_t segment_distance = diff * inv_segments;
#if ENABLED(SCARA_FEEDRATE_SCALING) // Add hints to help optimize the move
const float inv_duration = scaled_fr_mm_s / cartesian_segment_mm; PlannerHints hints(cartesian_mm * inv_segments);
#endif TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
//SERIAL_ECHOPGM("mm=", cartesian_mm); //SERIAL_ECHOPGM("mm=", cartesian_mm);
//SERIAL_ECHOLNPGM(" segments=", segments); //SERIAL_ECHOLNPGM(" segments=", segments);
//SERIAL_ECHOLNPGM(" segment_mm=", cartesian_segment_mm); //SERIAL_ECHOLNPGM(" segment_mm=", hints.millimeters);
// Get the raw current position as starting point // Get the raw current position as starting point
xyze_pos_t raw = current_position; xyze_pos_t raw = current_position;
@ -1100,12 +1099,13 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
while (--segments) { while (--segments) {
segment_idle(next_idle_ms); segment_idle(next_idle_ms);
raw += segment_distance; raw += segment_distance;
if (!planner.buffer_line(raw, fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration))) break; if (!planner.buffer_line(raw, fr_mm_s, active_extruder, hints))
break;
} }
// Since segment_distance is only approximate, // Since segment_distance is only approximate,
// the final move must be to the exact destination. // the final move must be to the exact destination.
planner.buffer_line(destination, fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)); planner.buffer_line(destination, fr_mm_s, active_extruder, hints);
} }
#endif // SEGMENT_LEVELED_MOVES && !AUTO_BED_LEVELING_UBL #endif // SEGMENT_LEVELED_MOVES && !AUTO_BED_LEVELING_UBL

View file

@ -843,20 +843,22 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
/** /**
* Laser Trapezoid Calculations * Laser Trapezoid Calculations
* *
* Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr` steps while accelerating, * Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr`
* and decrementing the power every `trap_ramp_exit_decr` while decelerating, to keep power proportional to feedrate. * steps while accelerating, and decrementing the power every `trap_ramp_exit_decr` while decelerating,
* Laser power trap will reduce the initial power to no less than the laser_power_floor value. Based on the number * to keep power proportional to feedrate. Laser power trap will reduce the initial power to no less
* of calculated accel/decel steps the power is distributed over the trapezoid entry- and exit-ramp steps. * than the laser_power_floor value. Based on the number of calculated accel/decel steps the power is
* distributed over the trapezoid entry- and exit-ramp steps.
* *
* trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial power / accel steps and * trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial
* will be additively incremented using a trap_ramp_entry_incr value for each accel step processed later in the stepper code. * power / accel steps and will be additively incremented using a trap_ramp_entry_incr value for each
* The trap_ramp_exit_decr value is calculated as power / decel steps and is also adjusted to no less than the power floor. * accel step processed later in the stepper code. The trap_ramp_exit_decr value is calculated as
* power / decel steps and is also adjusted to no less than the power floor.
* *
* If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing. The method allows * If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing.
* for simpler non-powered moves like G0 or G28. * The method allows for simpler non-powered moves like G0 or G28.
* *
* Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since the segments are * Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since
* usually too small. * the segments are usually too small.
*/ */
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) { if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) { if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
@ -937,20 +939,30 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
this block can never be less than block_buffer_tail and will always be pushed forward and maintain this block can never be less than block_buffer_tail and will always be pushed forward and maintain
this requirement when encountered by the Planner::release_current_block() routine during a cycle. this requirement when encountered by the Planner::release_current_block() routine during a cycle.
NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short NOTE: Since the planner only computes on what's in the planner buffer, some motions with many short
line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't segments (e.g., complex curves) may seem to move slowly. This is because there simply isn't
enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and then enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and
decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this happens and then decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this
becomes an annoyance, there are a few simple solutions: (1) Maximize the machine acceleration. The planner happens and becomes an annoyance, there are a few simple solutions:
will be able to compute higher velocity profiles within the same combined distance. (2) Maximize line
motion(s) distance per block to a desired tolerance. The more combined distance the planner has to use, - Maximize the machine acceleration. The planner will be able to compute higher velocity profiles
the faster it can go. (3) Maximize the planner buffer size. This also will increase the combined distance within the same combined distance.
for the planner to compute over. It also increases the number of computations the planner has to perform
to compute an optimal plan, so select carefully. - Maximize line motion(s) distance per block to a desired tolerance. The more combined distance the
planner has to use, the faster it can go.
- Maximize the planner buffer size. This also will increase the combined distance for the planner to
compute over. It also increases the number of computations the planner has to perform to compute an
optimal plan, so select carefully.
- Use G2/G3 arcs instead of many short segments. Arcs inform the planner of a safe exit speed at the
end of the last segment, which alleviates this problem.
*/ */
// The kernel called by recalculate() when scanning the plan from last to first entry. // The kernel called by recalculate() when scanning the plan from last to first entry.
void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next) { void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next
OPTARG(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)
) {
if (current) { if (current) {
// If entry speed is already at the maximum entry speed, and there was no change of speed // If entry speed is already at the maximum entry speed, and there was no change of speed
// in the next block, there is no need to recheck. Block is cruising and there is no need to // in the next block, there is no need to recheck. Block is cruising and there is no need to
@ -970,9 +982,10 @@ void Planner::reverse_pass_kernel(block_t * const current, const block_t * const
// the reverse and forward planners, the corresponding block junction speed will always be at the // the reverse and forward planners, the corresponding block junction speed will always be at the
// the maximum junction speed and may always be ignored for any speed reduction checks. // the maximum junction speed and may always be ignored for any speed reduction checks.
const float new_entry_speed_sqr = current->flag.nominal_length const float next_entry_speed_sqr = next ? next->entry_speed_sqr : _MAX(TERN0(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr), sq(float(MINIMUM_PLANNER_SPEED))),
? max_entry_speed_sqr new_entry_speed_sqr = current->flag.nominal_length
: _MIN(max_entry_speed_sqr, max_allowable_speed_sqr(-current->acceleration, next ? next->entry_speed_sqr : sq(float(MINIMUM_PLANNER_SPEED)), current->millimeters)); ? max_entry_speed_sqr
: _MIN(max_entry_speed_sqr, max_allowable_speed_sqr(-current->acceleration, next_entry_speed_sqr, current->millimeters));
if (current->entry_speed_sqr != new_entry_speed_sqr) { if (current->entry_speed_sqr != new_entry_speed_sqr) {
// Need to recalculate the block speed - Mark it now, so the stepper // Need to recalculate the block speed - Mark it now, so the stepper
@ -1001,7 +1014,7 @@ void Planner::reverse_pass_kernel(block_t * const current, const block_t * const
* recalculate() needs to go over the current plan twice. * recalculate() needs to go over the current plan twice.
* Once in reverse and once forward. This implements the reverse pass. * Once in reverse and once forward. This implements the reverse pass.
*/ */
void Planner::reverse_pass() { void Planner::reverse_pass(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
// Initialize block index to the last block in the planner buffer. // Initialize block index to the last block in the planner buffer.
uint8_t block_index = prev_block_index(block_buffer_head); uint8_t block_index = prev_block_index(block_buffer_head);
@ -1025,7 +1038,7 @@ void Planner::reverse_pass() {
// Only process movement blocks // Only process movement blocks
if (current->is_move()) { if (current->is_move()) {
reverse_pass_kernel(current, next); reverse_pass_kernel(current, next OPTARG(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
next = current; next = current;
} }
@ -1138,7 +1151,7 @@ void Planner::forward_pass() {
* according to the entry_factor for each junction. Must be called by * according to the entry_factor for each junction. Must be called by
* recalculate() after updating the blocks. * recalculate() after updating the blocks.
*/ */
void Planner::recalculate_trapezoids() { void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
// The tail may be changed by the ISR so get a local copy. // The tail may be changed by the ISR so get a local copy.
uint8_t block_index = block_buffer_tail, uint8_t block_index = block_buffer_tail,
head_block_index = block_buffer_head; head_block_index = block_buffer_head;
@ -1211,8 +1224,10 @@ void Planner::recalculate_trapezoids() {
block_index = next_block_index(block_index); block_index = next_block_index(block_index);
} }
// Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated. // Last/newest block in buffer. Always recalculated.
if (next) { if (block) {
// Exit speed is set with MINIMUM_PLANNER_SPEED unless some code higher up knows better.
next_entry_speed = _MAX(TERN0(HINTS_SAFE_EXIT_SPEED, SQRT(safe_exit_speed_sqr)), float(MINIMUM_PLANNER_SPEED));
// Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it. // Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it.
// As the last block is always recalculated here, there is a chance the block isn't // As the last block is always recalculated here, there is a chance the block isn't
@ -1225,33 +1240,33 @@ void Planner::recalculate_trapezoids() {
if (!stepper.is_block_busy(block)) { if (!stepper.is_block_busy(block)) {
// Block is not BUSY, we won the race against the Stepper ISR: // Block is not BUSY, we won the race against the Stepper ISR:
const float next_nominal_speed = SQRT(next->nominal_speed_sqr), const float current_nominal_speed = SQRT(block->nominal_speed_sqr),
nomr = 1.0f / next_nominal_speed; nomr = 1.0f / current_nominal_speed;
calculate_trapezoid_for_block(next, next_entry_speed * nomr, float(MINIMUM_PLANNER_SPEED) * nomr); calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
#if ENABLED(LIN_ADVANCE) #if ENABLED(LIN_ADVANCE)
if (next->use_advance_lead) { if (block->use_advance_lead) {
const float comp = next->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS]; const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
next->max_adv_steps = next_nominal_speed * comp; block->max_adv_steps = current_nominal_speed * comp;
next->final_adv_steps = (MINIMUM_PLANNER_SPEED) * comp; block->final_adv_steps = next_entry_speed * comp;
} }
#endif #endif
} }
// Reset next only to ensure its trapezoid is computed - The stepper is free to use // Reset block to ensure its trapezoid is computed - The stepper is free to use
// the block from now on. // the block from now on.
next->flag.recalculate = false; block->flag.recalculate = false;
} }
} }
void Planner::recalculate() { void Planner::recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
// Initialize block index to the last block in the planner buffer. // Initialize block index to the last block in the planner buffer.
const uint8_t block_index = prev_block_index(block_buffer_head); const uint8_t block_index = prev_block_index(block_buffer_head);
// If there is just one block, no planning can be done. Avoid it! // If there is just one block, no planning can be done. Avoid it!
if (block_index != block_buffer_planned) { if (block_index != block_buffer_planned) {
reverse_pass(); reverse_pass(TERN_(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
forward_pass(); forward_pass();
} }
recalculate_trapezoids(); recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
} }
/** /**
@ -1771,22 +1786,21 @@ float Planner::get_axis_position_mm(const AxisEnum axis) {
void Planner::synchronize() { while (busy()) idle(); } void Planner::synchronize() { while (busy()) idle(); }
/** /**
* Planner::_buffer_steps * @brief Add a new linear movement to the planner queue (in terms of steps).
* *
* Add a new linear movement to the planner queue (in terms of steps). * @param target Target position in steps units
* @param target_float Target position in direct (mm, degrees) units.
* @param cart_dist_mm The pre-calculated move lengths for all axes, in mm
* @param fr_mm_s (target) speed of the move
* @param extruder target extruder
* @param hints parameters to aid planner calculations
* *
* target - target position in steps units * @return true if movement was properly queued, false otherwise (if cleaning)
* target_float - target position in direct (mm, degrees) units. optional
* fr_mm_s - (target) speed of the move
* extruder - target extruder
* millimeters - the length of the movement, if known
*
* Returns true if movement was properly queued, false otherwise (if cleaning)
*/ */
bool Planner::_buffer_steps(const xyze_long_t &target bool Planner::_buffer_steps(const xyze_long_t &target
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float) OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
) { ) {
// Wait for the next available block // Wait for the next available block
@ -1802,7 +1816,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
if (!_populate_block(block, target if (!_populate_block(block, target
OPTARG(HAS_POSITION_FLOAT, target_float) OPTARG(HAS_POSITION_FLOAT, target_float)
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
, fr_mm_s, extruder, millimeters , fr_mm_s, extruder, hints
) )
) { ) {
// Movement was not queued, probably because it was too short. // Movement was not queued, probably because it was too short.
@ -1824,7 +1838,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
block_buffer_head = next_buffer_head; block_buffer_head = next_buffer_head;
// Recalculate and optimize trapezoidal speed profiles // Recalculate and optimize trapezoidal speed profiles
recalculate(); recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, hints.safe_exit_speed_sqr));
// Movement successfully queued! // Movement successfully queued!
return true; return true;
@ -1842,8 +1856,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
* @param cart_dist_mm The pre-calculated move lengths for all axes, in mm * @param cart_dist_mm The pre-calculated move lengths for all axes, in mm
* @param fr_mm_s (target) speed of the move * @param fr_mm_s (target) speed of the move
* @param extruder target extruder * @param extruder target extruder
* @param millimeters A pre-calculated linear distance for the move, in mm, * @param hints parameters to aid planner calculations
* or 0.0 to have the distance calculated here.
* *
* @return true if movement is acceptable, false otherwise * @return true if movement is acceptable, false otherwise
*/ */
@ -1852,7 +1865,7 @@ bool Planner::_populate_block(
const abce_long_t &target const abce_long_t &target
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float) OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters/*=0.0*/ , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
) { ) {
int32_t LOGICAL_AXIS_LIST( int32_t LOGICAL_AXIS_LIST(
de = target.e - position.e, de = target.e - position.e,
@ -2107,8 +2120,8 @@ bool Planner::_populate_block(
block->millimeters = TERN0(HAS_EXTRUDERS, ABS(steps_dist_mm.e)); block->millimeters = TERN0(HAS_EXTRUDERS, ABS(steps_dist_mm.e));
} }
else { else {
if (millimeters) if (hints.millimeters)
block->millimeters = millimeters; block->millimeters = hints.millimeters;
else { else {
block->millimeters = SQRT( block->millimeters = SQRT(
#if ANY(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX) #if ANY(CORE_IS_XY, MARKFORGED_XY, MARKFORGED_YX)
@ -2475,23 +2488,15 @@ bool Planner::_populate_block(
if (block->step_event_count <= acceleration_long_cutoff) { if (block->step_event_count <= acceleration_long_cutoff) {
LOGICAL_AXIS_CODE( LOGICAL_AXIS_CODE(
LIMIT_ACCEL_LONG(E_AXIS, E_INDEX_N(extruder)), LIMIT_ACCEL_LONG(E_AXIS, E_INDEX_N(extruder)),
LIMIT_ACCEL_LONG(A_AXIS, 0), LIMIT_ACCEL_LONG(A_AXIS, 0), LIMIT_ACCEL_LONG(B_AXIS, 0), LIMIT_ACCEL_LONG(C_AXIS, 0),
LIMIT_ACCEL_LONG(B_AXIS, 0), LIMIT_ACCEL_LONG(I_AXIS, 0), LIMIT_ACCEL_LONG(J_AXIS, 0), LIMIT_ACCEL_LONG(K_AXIS, 0)
LIMIT_ACCEL_LONG(C_AXIS, 0),
LIMIT_ACCEL_LONG(I_AXIS, 0),
LIMIT_ACCEL_LONG(J_AXIS, 0),
LIMIT_ACCEL_LONG(K_AXIS, 0)
); );
} }
else { else {
LOGICAL_AXIS_CODE( LOGICAL_AXIS_CODE(
LIMIT_ACCEL_FLOAT(E_AXIS, E_INDEX_N(extruder)), LIMIT_ACCEL_FLOAT(E_AXIS, E_INDEX_N(extruder)),
LIMIT_ACCEL_FLOAT(A_AXIS, 0), LIMIT_ACCEL_FLOAT(A_AXIS, 0), LIMIT_ACCEL_FLOAT(B_AXIS, 0), LIMIT_ACCEL_FLOAT(C_AXIS, 0),
LIMIT_ACCEL_FLOAT(B_AXIS, 0), LIMIT_ACCEL_FLOAT(I_AXIS, 0), LIMIT_ACCEL_FLOAT(J_AXIS, 0), LIMIT_ACCEL_FLOAT(K_AXIS, 0)
LIMIT_ACCEL_FLOAT(C_AXIS, 0),
LIMIT_ACCEL_FLOAT(I_AXIS, 0),
LIMIT_ACCEL_FLOAT(J_AXIS, 0),
LIMIT_ACCEL_FLOAT(K_AXIS, 0)
); );
} }
} }
@ -2577,7 +2582,7 @@ bool Planner::_populate_block(
// NOTE: Max junction velocity is computed without sin() or acos() by trig half angle identity. // NOTE: Max junction velocity is computed without sin() or acos() by trig half angle identity.
float junction_cos_theta = LOGICAL_AXIS_GANG( float junction_cos_theta = LOGICAL_AXIS_GANG(
+ (-prev_unit_vec.e * unit_vec.e), + (-prev_unit_vec.e * unit_vec.e),
(-prev_unit_vec.x * unit_vec.x), + (-prev_unit_vec.x * unit_vec.x),
+ (-prev_unit_vec.y * unit_vec.y), + (-prev_unit_vec.y * unit_vec.y),
+ (-prev_unit_vec.z * unit_vec.z), + (-prev_unit_vec.z * unit_vec.z),
+ (-prev_unit_vec.i * unit_vec.i), + (-prev_unit_vec.i * unit_vec.i),
@ -2591,104 +2596,110 @@ bool Planner::_populate_block(
vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED)); vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
} }
else { else {
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
// Convert delta vector to unit vector // Convert delta vector to unit vector
xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec; xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
normalize_junction_vector(junction_unit_vec); normalize_junction_vector(junction_unit_vec);
const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec), const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec);
sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2); if (TERN0(HINTS_CURVE_RADIUS, hints.curve_radius)) {
TERN_(HINTS_CURVE_RADIUS, vmax_junction_sqr = junction_acceleration * hints.curve_radius);
}
else {
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
#if ENABLED(JD_HANDLE_SMALL_SEGMENTS) const float sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
// For small moves with >135° junction (octagon) find speed for approximate arc vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
if (block->millimeters < 1 && junction_cos_theta < -0.7071067812f) {
#if ENABLED(JD_USE_MATH_ACOS) #if ENABLED(JD_HANDLE_SMALL_SEGMENTS)
#error "TODO: Inline maths with the MCU / FPU." // For small moves with >135° junction (octagon) find speed for approximate arc
if (block->millimeters < 1 && junction_cos_theta < -0.7071067812f) {
#elif ENABLED(JD_USE_LOOKUP_TABLE) #if ENABLED(JD_USE_MATH_ACOS)
// Fast acos approximation (max. error +-0.01 rads) #error "TODO: Inline maths with the MCU / FPU."
// Based on LUT table and linear interpolation
/** #elif ENABLED(JD_USE_LOOKUP_TABLE)
* // Generate the JD Lookup Table
* constexpr float c = 1.00751495f; // Correction factor to center error around 0
* for (int i = 0; i < jd_lut_count - 1; ++i) {
* const float x0 = (sq(i) - 1) / sq(i),
* y0 = acos(x0) * (i == 0 ? 1 : c),
* x1 = i < jd_lut_count - 1 ? 0.5 * x0 + 0.5 : 0.999999f,
* y1 = acos(x1) * (i < jd_lut_count - 1 ? c : 1);
* jd_lut_k[i] = (y0 - y1) / (x0 - x1);
* jd_lut_b[i] = (y1 * x0 - y0 * x1) / (x0 - x1);
* }
*
* // Compute correction factor (Set c to 1.0f first!)
* float min = INFINITY, max = -min;
* for (float t = 0; t <= 1; t += 0.0003f) {
* const float e = acos(t) / approx(t);
* if (isfinite(e)) {
* if (e < min) min = e;
* if (e > max) max = e;
* }
* }
* fprintf(stderr, "%.9gf, ", (min + max) / 2);
*/
static constexpr int16_t jd_lut_count = 16;
static constexpr uint16_t jd_lut_tll = _BV(jd_lut_count - 1);
static constexpr int16_t jd_lut_tll0 = __builtin_clz(jd_lut_tll) + 1; // i.e., 16 - jd_lut_count + 1
static constexpr float jd_lut_k[jd_lut_count] PROGMEM = {
-1.03145837f, -1.30760646f, -1.75205851f, -2.41705704f,
-3.37769222f, -4.74888992f, -6.69649887f, -9.45661736f,
-13.3640480f, -18.8928222f, -26.7136841f, -37.7754593f,
-53.4201813f, -75.5458374f, -106.836761f, -218.532821f };
static constexpr float jd_lut_b[jd_lut_count] PROGMEM = {
1.57079637f, 1.70887053f, 2.04220939f, 2.62408352f,
3.52467871f, 4.85302639f, 6.77020454f, 9.50875854f,
13.4009285f, 18.9188995f, 26.7321243f, 37.7885055f,
53.4293975f, 75.5523529f, 106.841369f, 218.534011f };
const float neg = junction_cos_theta < 0 ? -1 : 1, // Fast acos approximation (max. error +-0.01 rads)
t = neg * junction_cos_theta; // Based on LUT table and linear interpolation
const int16_t idx = (t < 0.00000003f) ? 0 : __builtin_clz(uint16_t((1.0f - t) * jd_lut_tll)) - jd_lut_tll0; /**
* // Generate the JD Lookup Table
* constexpr float c = 1.00751495f; // Correction factor to center error around 0
* for (int i = 0; i < jd_lut_count - 1; ++i) {
* const float x0 = (sq(i) - 1) / sq(i),
* y0 = acos(x0) * (i == 0 ? 1 : c),
* x1 = i < jd_lut_count - 1 ? 0.5 * x0 + 0.5 : 0.999999f,
* y1 = acos(x1) * (i < jd_lut_count - 1 ? c : 1);
* jd_lut_k[i] = (y0 - y1) / (x0 - x1);
* jd_lut_b[i] = (y1 * x0 - y0 * x1) / (x0 - x1);
* }
*
* // Compute correction factor (Set c to 1.0f first!)
* float min = INFINITY, max = -min;
* for (float t = 0; t <= 1; t += 0.0003f) {
* const float e = acos(t) / approx(t);
* if (isfinite(e)) {
* if (e < min) min = e;
* if (e > max) max = e;
* }
* }
* fprintf(stderr, "%.9gf, ", (min + max) / 2);
*/
static constexpr int16_t jd_lut_count = 16;
static constexpr uint16_t jd_lut_tll = _BV(jd_lut_count - 1);
static constexpr int16_t jd_lut_tll0 = __builtin_clz(jd_lut_tll) + 1; // i.e., 16 - jd_lut_count + 1
static constexpr float jd_lut_k[jd_lut_count] PROGMEM = {
-1.03145837f, -1.30760646f, -1.75205851f, -2.41705704f,
-3.37769222f, -4.74888992f, -6.69649887f, -9.45661736f,
-13.3640480f, -18.8928222f, -26.7136841f, -37.7754593f,
-53.4201813f, -75.5458374f, -106.836761f, -218.532821f };
static constexpr float jd_lut_b[jd_lut_count] PROGMEM = {
1.57079637f, 1.70887053f, 2.04220939f, 2.62408352f,
3.52467871f, 4.85302639f, 6.77020454f, 9.50875854f,
13.4009285f, 18.9188995f, 26.7321243f, 37.7885055f,
53.4293975f, 75.5523529f, 106.841369f, 218.534011f };
float junction_theta = t * pgm_read_float(&jd_lut_k[idx]) + pgm_read_float(&jd_lut_b[idx]); const float neg = junction_cos_theta < 0 ? -1 : 1,
if (neg > 0) junction_theta = RADIANS(180) - junction_theta; // acos(-t) t = neg * junction_cos_theta;
#else const int16_t idx = (t < 0.00000003f) ? 0 : __builtin_clz(uint16_t((1.0f - t) * jd_lut_tll)) - jd_lut_tll0;
// Fast acos(-t) approximation (max. error +-0.033rad = 1.89°) float junction_theta = t * pgm_read_float(&jd_lut_k[idx]) + pgm_read_float(&jd_lut_b[idx]);
// Based on MinMax polynomial published by W. Randolph Franklin, see if (neg > 0) junction_theta = RADIANS(180) - junction_theta; // acos(-t)
// https://wrf.ecse.rpi.edu/Research/Short_Notes/arcsin/onlyelem.html
// acos( t) = pi / 2 - asin(x)
// acos(-t) = pi - acos(t) ... pi / 2 + asin(x)
const float neg = junction_cos_theta < 0 ? -1 : 1, #else
t = neg * junction_cos_theta,
asinx = 0.032843707f
+ t * (-1.451838349f
+ t * ( 29.66153956f
+ t * (-131.1123477f
+ t * ( 262.8130562f
+ t * (-242.7199627f
+ t * ( 84.31466202f ) ))))),
junction_theta = RADIANS(90) + neg * asinx; // acos(-t)
// NOTE: junction_theta bottoms out at 0.033 which avoids divide by 0. // Fast acos(-t) approximation (max. error +-0.033rad = 1.89°)
// Based on MinMax polynomial published by W. Randolph Franklin, see
// https://wrf.ecse.rpi.edu/Research/Short_Notes/arcsin/onlyelem.html
// acos( t) = pi / 2 - asin(x)
// acos(-t) = pi - acos(t) ... pi / 2 + asin(x)
#endif const float neg = junction_cos_theta < 0 ? -1 : 1,
t = neg * junction_cos_theta,
asinx = 0.032843707f
+ t * (-1.451838349f
+ t * ( 29.66153956f
+ t * (-131.1123477f
+ t * ( 262.8130562f
+ t * (-242.7199627f
+ t * ( 84.31466202f ) ))))),
junction_theta = RADIANS(90) + neg * asinx; // acos(-t)
const float limit_sqr = (block->millimeters * junction_acceleration) / junction_theta; // NOTE: junction_theta bottoms out at 0.033 which avoids divide by 0.
NOMORE(vmax_junction_sqr, limit_sqr);
}
#endif // JD_HANDLE_SMALL_SEGMENTS #endif
const float limit_sqr = (block->millimeters * junction_acceleration) / junction_theta;
NOMORE(vmax_junction_sqr, limit_sqr);
}
#endif // JD_HANDLE_SMALL_SEGMENTS
}
} }
// Get the lowest speed // Get the lowest speed
@ -2848,12 +2859,11 @@ bool Planner::_populate_block(
} // _populate_block() } // _populate_block()
/** /**
* Planner::buffer_sync_block * @brief Add a block to the buffer that just updates the position
* Add a block to the buffer that just updates the position * Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
* @param sync_flag BLOCK_FLAG_SYNC_FANS & BLOCK_FLAG_LASER_PWR *
* Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing. * @param sync_flag The sync flag to set, determining the type of sync the block will do
*/ */
void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) { void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) {
// Wait for the next available block // Wait for the next available block
@ -2861,14 +2871,13 @@ void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_PO
block_t * const block = get_next_free_block(next_buffer_head); block_t * const block = get_next_free_block(next_buffer_head);
// Clear block // Clear block
memset(block, 0, sizeof(block_t)); block->reset();
block->flag.apply(sync_flag); block->flag.apply(sync_flag);
block->position = position; block->position = position;
#if ENABLED(BACKLASH_COMPENSATION) #if ENABLED(BACKLASH_COMPENSATION)
LOOP_LINEAR_AXES(axis) block->position[axis] += backlash.get_applied_steps((AxisEnum)axis); LOOP_LINEAR_AXES(axis) block->position[axis] += backlash.get_applied_steps((AxisEnum)axis);
#endif #endif
#if BOTH(HAS_FAN, LASER_SYNCHRONOUS_M106_M107) #if BOTH(HAS_FAN, LASER_SYNCHRONOUS_M106_M107)
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i]; FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
#endif #endif
@ -2895,22 +2904,24 @@ void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_PO
} // buffer_sync_block() } // buffer_sync_block()
/** /**
* Planner::buffer_segment * @brief Add a single linear movement
* *
* Add a new linear movement to the buffer in axis units. * @description Add a new linear movement to the buffer in axis units.
* Leveling and kinematics should be applied before calling this.
* *
* Leveling and kinematics should be applied ahead of calling this. * @param abce Target position in mm and/or degrees
* @param cart_dist_mm The pre-calculated move lengths for all axes, in mm
* @param fr_mm_s (target) speed of the move
* @param extruder optional target extruder (otherwise active_extruder)
* @param hints optional parameters to aid planner calculations
* *
* a,b,c,e - target positions in mm and/or degrees * @return false if no segment was queued due to cleaning, cold extrusion, full queue, etc.
* fr_mm_s - (target) speed of the move
* extruder - target extruder
* millimeters - the length of the movement, if known
*
* Return 'false' if no segment was queued due to cleaning, cold extrusion, full queue, etc.
*/ */
bool Planner::buffer_segment(const abce_pos_t &abce bool Planner::buffer_segment(const abce_pos_t &abce
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, const_feedRate_t fr_mm_s, const uint8_t extruder/*=active_extruder*/, const_float_t millimeters/*=0.0*/ , const_feedRate_t fr_mm_s
, const uint8_t extruder/*=active_extruder*/
, const PlannerHints &hints/*=PlannerHints()*/
) { ) {
// If we are cleaning, do not accept queuing of movements // If we are cleaning, do not accept queuing of movements
@ -2998,8 +3009,8 @@ bool Planner::buffer_segment(const abce_pos_t &abce
if (!_buffer_steps(target if (!_buffer_steps(target
OPTARG(HAS_POSITION_FLOAT, target_float) OPTARG(HAS_POSITION_FLOAT, target_float)
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
, fr_mm_s, extruder, millimeters) , fr_mm_s, extruder, hints
) return false; )) return false;
stepper.wake_up(); stepper.wake_up();
return true; return true;
@ -3012,12 +3023,12 @@ bool Planner::buffer_segment(const abce_pos_t &abce
* *
* cart - target position in mm or degrees * cart - target position in mm or degrees
* fr_mm_s - (target) speed of the move (mm/s) * fr_mm_s - (target) speed of the move (mm/s)
* extruder - target extruder * extruder - optional target extruder (otherwise active_extruder)
* millimeters - the length of the movement, if known * hints - optional parameters to aid planner calculations
* inv_duration - the reciprocal if the duration of the movement, if known (kinematic only if feeedrate scaling is enabled)
*/ */
bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, const uint8_t extruder/*=active_extruder*/, const float millimeters/*=0.0*/ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s
OPTARG(SCARA_FEEDRATE_SCALING, const_float_t inv_duration/*=0.0*/) , const uint8_t extruder/*=active_extruder*/
, const PlannerHints &hints/*=PlannerHints()*/
) { ) {
xyze_pos_t machine = cart; xyze_pos_t machine = cart;
TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine)); TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine));
@ -3037,28 +3048,30 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
); );
#endif #endif
const float mm = millimeters ?: (cart_dist_mm.x || cart_dist_mm.y) ? cart_dist_mm.magnitude() : TERN0(HAS_Z_AXIS, ABS(cart_dist_mm.z));
// Cartesian XYZ to kinematic ABC, stored in global 'delta' // Cartesian XYZ to kinematic ABC, stored in global 'delta'
inverse_kinematics(machine); inverse_kinematics(machine);
PlannerHints ph = hints;
if (!hints.millimeters)
ph.millimeters = (cart_dist_mm.x || cart_dist_mm.y) ? cart_dist_mm.magnitude() : TERN0(HAS_Z_AXIS, ABS(cart_dist_mm.z));
#if ENABLED(SCARA_FEEDRATE_SCALING) #if ENABLED(SCARA_FEEDRATE_SCALING)
// For SCARA scale the feedrate from mm/s to degrees/s // For SCARA scale the feedrate from mm/s to degrees/s
// i.e., Complete the angular vector in the given time. // i.e., Complete the angular vector in the given time.
const float duration_recip = inv_duration ?: fr_mm_s / mm; const float duration_recip = hints.inv_duration ?: fr_mm_s / ph.millimeters;
const xyz_pos_t diff = delta - position_float; const xyz_pos_t diff = delta - position_float;
const feedRate_t feedrate = diff.magnitude() * duration_recip; const feedRate_t feedrate = diff.magnitude() * duration_recip;
#else #else
const feedRate_t feedrate = fr_mm_s; const feedRate_t feedrate = fr_mm_s;
#endif #endif
TERN_(HAS_EXTRUDERS, delta.e = machine.e); TERN_(HAS_EXTRUDERS, delta.e = machine.e);
if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, mm)) { if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, ph)) {
position_cart = cart; position_cart = cart;
return true; return true;
} }
return false; return false;
#else #else
return buffer_segment(machine, fr_mm_s, extruder, millimeters); return buffer_segment(machine, fr_mm_s, extruder, hints);
#endif #endif
} // buffer_line() } // buffer_line()

View file

@ -279,6 +279,8 @@ typedef struct PlannerBlock {
block_laser_t laser; block_laser_t laser;
#endif #endif
void reset() { memset((char*)this, 0, sizeof(*this)); }
} block_t; } block_t;
#if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY) #if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
@ -348,6 +350,30 @@ typedef struct {
typedef IF<(BLOCK_BUFFER_SIZE > 64), uint16_t, uint8_t>::type last_move_t; typedef IF<(BLOCK_BUFFER_SIZE > 64), uint16_t, uint8_t>::type last_move_t;
#endif #endif
#if ENABLED(ARC_SUPPORT)
#define HINTS_CURVE_RADIUS
#define HINTS_SAFE_EXIT_SPEED
#endif
struct PlannerHints {
float millimeters = 0.0; // Move Length, if known, else 0.
#if ENABLED(SCARA_FEEDRATE_SCALING)
float inv_duration = 0.0; // Reciprocal of the move duration, if known
#endif
#if ENABLED(HINTS_CURVE_RADIUS)
float curve_radius = 0.0; // Radius of curvature of the motion path - to calculate cornering speed
#else
static constexpr float curve_radius = 0.0;
#endif
#if ENABLED(HINTS_SAFE_EXIT_SPEED)
float safe_exit_speed_sqr = 0.0; // Square of the speed considered "safe" at the end of the segment
// i.e., at or below the exit speed of the segment that the planner
// would calculate if it knew the as-yet-unbuffered path
#endif
PlannerHints(const_float_t mm=0.0f) : millimeters(mm) {}
};
class Planner { class Planner {
public: public:
@ -751,14 +777,14 @@ class Planner {
* target - target position in steps units * target - target position in steps units
* fr_mm_s - (target) speed of the move * fr_mm_s - (target) speed of the move
* extruder - target extruder * extruder - target extruder
* millimeters - the length of the movement, if known * hints - parameters to aid planner calculations
* *
* Returns true if movement was buffered, false otherwise * Returns true if movement was buffered, false otherwise
*/ */
static bool _buffer_steps(const xyze_long_t &target static bool _buffer_steps(const xyze_long_t &target
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float) OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters=0.0 , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
); );
/** /**
@ -773,15 +799,14 @@ class Planner {
* @param cart_dist_mm The pre-calculated move lengths for all axes, in mm * @param cart_dist_mm The pre-calculated move lengths for all axes, in mm
* @param fr_mm_s (target) speed of the move * @param fr_mm_s (target) speed of the move
* @param extruder target extruder * @param extruder target extruder
* @param millimeters A pre-calculated linear distance for the move, in mm, * @param hints parameters to aid planner calculations
* or 0.0 to have the distance calculated here.
* *
* @return true if movement is acceptable, false otherwise * @return true if movement is acceptable, false otherwise
*/ */
static bool _populate_block(block_t * const block, const xyze_long_t &target static bool _populate_block(block_t * const block, const xyze_long_t &target
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float) OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters=0.0 , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
); );
/** /**
@ -808,12 +833,14 @@ class Planner {
* *
* a,b,c,e - target positions in mm and/or degrees * a,b,c,e - target positions in mm and/or degrees
* fr_mm_s - (target) speed of the move * fr_mm_s - (target) speed of the move
* extruder - target extruder * extruder - optional target extruder (otherwise active_extruder)
* millimeters - the length of the movement, if known * hints - optional parameters to aid planner calculations
*/ */
static bool buffer_segment(const abce_pos_t &abce static bool buffer_segment(const abce_pos_t &abce
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm) OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
, const_feedRate_t fr_mm_s, const uint8_t extruder=active_extruder, const_float_t millimeters=0.0 , const_feedRate_t fr_mm_s
, const uint8_t extruder=active_extruder
, const PlannerHints &hints=PlannerHints()
); );
public: public:
@ -825,12 +852,12 @@ class Planner {
* *
* cart - target position in mm or degrees * cart - target position in mm or degrees
* fr_mm_s - (target) speed of the move (mm/s) * fr_mm_s - (target) speed of the move (mm/s)
* extruder - target extruder * extruder - optional target extruder (otherwise active_extruder)
* millimeters - the length of the movement, if known * hints - optional parameters to aid planner calculations
* inv_duration - the reciprocal if the duration of the movement, if known (kinematic only if feeedrate scaling is enabled)
*/ */
static bool buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, const uint8_t extruder=active_extruder, const float millimeters=0.0 static bool buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s
OPTARG(SCARA_FEEDRATE_SCALING, const_float_t inv_duration=0.0) , const uint8_t extruder=active_extruder
, const PlannerHints &hints=PlannerHints()
); );
#if ENABLED(DIRECT_STEPPING) #if ENABLED(DIRECT_STEPPING)
@ -1022,15 +1049,15 @@ class Planner {
static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor); static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor);
static void reverse_pass_kernel(block_t * const current, const block_t * const next); static void reverse_pass_kernel(block_t * const current, const block_t * const next OPTARG(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
static void forward_pass_kernel(const block_t * const previous, block_t * const current, uint8_t block_index); static void forward_pass_kernel(const block_t * const previous, block_t * const current, uint8_t block_index);
static void reverse_pass(); static void reverse_pass(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
static void forward_pass(); static void forward_pass();
static void recalculate_trapezoids(); static void recalculate_trapezoids(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
static void recalculate(); static void recalculate(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
#if HAS_JUNCTION_DEVIATION #if HAS_JUNCTION_DEVIATION

View file

@ -121,6 +121,9 @@ void cubic_b_spline(
millis_t next_idle_ms = millis() + 200UL; millis_t next_idle_ms = millis() + 200UL;
// Hints to help optimize the move
PlannerHints hints;
for (float t = 0; t < 1;) { for (float t = 0; t < 1;) {
thermalManager.task(); thermalManager.task();
@ -177,7 +180,7 @@ void cubic_b_spline(
} }
*/ */
step = new_t - t; hints.millimeters = new_t - t;
t = new_t; t = new_t;
// Compute and send new position // Compute and send new position
@ -200,7 +203,7 @@ void cubic_b_spline(
const xyze_pos_t &pos = bez_target; const xyze_pos_t &pos = bez_target;
#endif #endif
if (!planner.buffer_line(pos, scaled_fr_mm_s, active_extruder, step)) if (!planner.buffer_line(pos, scaled_fr_mm_s, active_extruder, hints))
break; break;
} }
} }

View file

@ -1958,14 +1958,15 @@ uint32_t Stepper::block_phase_isr() {
else if (LA_steps) nextAdvanceISR = 0; else if (LA_steps) nextAdvanceISR = 0;
#endif #endif
/* /**
* Adjust Laser Power - Accelerating * Adjust Laser Power - Accelerating
* isPowered - True when a move is powered.
* isEnabled - laser power is active.
* Laser power variables are calulated and stored in this block by the planner code.
* *
* trap_ramp_active_pwr - the active power in this block across accel or decel trap steps. * isPowered - True when a move is powered.
* trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step. * isEnabled - laser power is active.
*
* Laser power variables are calulated and stored in this block by the planner code.
* trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
* trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
* *
* Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive. * Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive.
*/ */
@ -1988,6 +1989,7 @@ uint32_t Stepper::block_phase_isr() {
uint32_t step_rate; uint32_t step_rate;
#if ENABLED(S_CURVE_ACCELERATION) #if ENABLED(S_CURVE_ACCELERATION)
// If this is the 1st time we process the 2nd half of the trapezoid... // If this is the 1st time we process the 2nd half of the trapezoid...
if (!bezier_2nd_half) { if (!bezier_2nd_half) {
// Initialize the Bézier speed curve // Initialize the Bézier speed curve
@ -2002,6 +2004,7 @@ uint32_t Stepper::block_phase_isr() {
? _eval_bezier_curve(deceleration_time) ? _eval_bezier_curve(deceleration_time)
: current_block->final_rate; : current_block->final_rate;
} }
#else #else
// Using the old trapezoidal control // Using the old trapezoidal control
step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate); step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
@ -2011,9 +2014,8 @@ uint32_t Stepper::block_phase_isr() {
} }
else else
step_rate = current_block->final_rate; step_rate = current_block->final_rate;
#endif
// step_rate is in steps/second #endif
// step_rate to timer interval and steps per stepper isr // step_rate to timer interval and steps per stepper isr
interval = calc_timer_interval(step_rate, &steps_per_isr); interval = calc_timer_interval(step_rate, &steps_per_isr);
@ -2065,10 +2067,10 @@ uint32_t Stepper::block_phase_isr() {
interval = ticks_nominal; interval = ticks_nominal;
} }
/* Adjust Laser Power - Cruise /**
* Adjust Laser Power - Cruise
* power - direct or floor adjusted active laser power. * power - direct or floor adjusted active laser power.
*/ */
#if ENABLED(LASER_POWER_TRAP) #if ENABLED(LASER_POWER_TRAP)
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) { if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
if (step_events_completed + 1 == accelerate_until) { if (step_events_completed + 1 == accelerate_until) {
@ -2086,7 +2088,7 @@ uint32_t Stepper::block_phase_isr() {
} }
#if ENABLED(LASER_FEATURE) #if ENABLED(LASER_FEATURE)
/* /**
* CUTTER_MODE_DYNAMIC is experimental and developing. * CUTTER_MODE_DYNAMIC is experimental and developing.
* Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute. * Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute.
* TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers. * TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers.
@ -2103,9 +2105,8 @@ uint32_t Stepper::block_phase_isr() {
} }
else { // !current_block else { // !current_block
#if ENABLED(LASER_FEATURE) #if ENABLED(LASER_FEATURE)
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) { if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC)
cutter.apply_power(0); // No movement in dynamic mode so turn Laser off cutter.apply_power(0); // No movement in dynamic mode so turn Laser off
}
#endif #endif
} }