Parcourir la source

⚡️ Optimize G2-G3 Arcs (#24366)

tombrazier il y a 2 ans
Parent
révision
920799e38d
Aucun compte lié à l'adresse e-mail de l'auteur

+ 7
- 10
Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp Voir le fichier

@@ -374,11 +374,12 @@
374 374
     #endif
375 375
 
376 376
     NOLESS(segments, 1U);                                                            // Must have at least one segment
377
-    const float inv_segments = 1.0f / segments,                                      // Reciprocal to save calculation
378
-                segment_xyz_mm = SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments;    // Length of each segment
377
+    const float inv_segments = 1.0f / segments;                                      // Reciprocal to save calculation
379 378
 
379
+    // Add hints to help optimize the move
380
+    PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments);             // Length of each segment
380 381
     #if ENABLED(SCARA_FEEDRATE_SCALING)
381
-      const float inv_duration = scaled_fr_mm_s / segment_xyz_mm;
382
+      hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
382 383
     #endif
383 384
 
384 385
     xyze_float_t diff = total * inv_segments;
@@ -392,13 +393,9 @@
392 393
     if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) {
393 394
       while (--segments) {
394 395
         raw += diff;
395
-        planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm
396
-          OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
397
-        );
396
+        planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
398 397
       }
399
-      planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, segment_xyz_mm
400
-        OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)
401
-      );
398
+      planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints);
402 399
       return false; // Did not set current from destination
403 400
     }
404 401
 
@@ -467,7 +464,7 @@
467 464
           TERN_(ENABLE_LEVELING_FADE_HEIGHT, * fade_scaling_factor); // apply fade factor to interpolated height
468 465
 
469 466
         const float oldz = raw.z; raw.z += z_cxcy;
470
-        planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration) );
467
+        planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
471 468
         raw.z = oldz;
472 469
 
473 470
         if (segments == 0)                        // done with last segment

+ 2
- 1
Marlin/src/feature/joystick.cpp Voir le fichier

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

+ 23
- 3
Marlin/src/gcode/motion/G2_G3.cpp Voir le fichier

@@ -214,9 +214,12 @@ void plan_arc(
214 214
   const uint16_t segments = nominal_segment_mm > (MAX_ARC_SEGMENT_MM) ? CEIL(flat_mm / (MAX_ARC_SEGMENT_MM)) :
215 215
                             nominal_segment_mm < (MIN_ARC_SEGMENT_MM) ? _MAX(1, FLOOR(flat_mm / (MIN_ARC_SEGMENT_MM))) :
216 216
                             nominal_segments;
217
+  const float segment_mm = flat_mm / segments;
217 218
 
219
+  // Add hints to help optimize the move
220
+  PlannerHints hints;
218 221
   #if ENABLED(SCARA_FEEDRATE_SCALING)
219
-    const float inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
222
+    hints.inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
220 223
   #endif
221 224
 
222 225
   /**
@@ -288,6 +291,16 @@ void plan_arc(
288 291
       int8_t arc_recalc_count = N_ARC_CORRECTION;
289 292
     #endif
290 293
 
294
+    // An arc can always complete within limits from a speed which...
295
+    // a) is <= any configured maximum speed,
296
+    // b) does not require centripetal force greater than any configured maximum acceleration,
297
+    // c) allows the print head to stop in the remining length of the curve within all configured maximum accelerations.
298
+    // The last has to be calculated every time through the loop.
299
+    const float limiting_accel = _MIN(planner.settings.max_acceleration_mm_per_s2[axis_p], planner.settings.max_acceleration_mm_per_s2[axis_q]),
300
+                limiting_speed = _MIN(planner.settings.max_feedrate_mm_s[axis_p], planner.settings.max_acceleration_mm_per_s2[axis_q]),
301
+                limiting_speed_sqr = _MIN(sq(limiting_speed), limiting_accel * radius);
302
+    float arc_mm_remaining = flat_mm;
303
+
291 304
     for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times
292 305
 
293 306
       thermalManager.task();
@@ -342,7 +355,13 @@ void plan_arc(
342 355
         planner.apply_leveling(raw);
343 356
       #endif
344 357
 
345
-      if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0 OPTARG(SCARA_FEEDRATE_SCALING, inv_duration)))
358
+      // calculate safe speed for stopping by the end of the arc
359
+      arc_mm_remaining -= segment_mm;
360
+
361
+      hints.curve_radius = i > 1 ? radius : 0;
362
+      hints.safe_exit_speed_sqr = _MIN(limiting_speed_sqr, 2 * limiting_accel * arc_mm_remaining);
363
+
364
+      if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints))
346 365
         break;
347 366
     }
348 367
   }
@@ -363,7 +382,8 @@ void plan_arc(
363 382
     planner.apply_leveling(raw);
364 383
   #endif
365 384
 
366
-  planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0 OPTARG(SCARA_FEEDRATE_SCALING, inv_duration));
385
+  hints.curve_radius = 0;
386
+  planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints);
367 387
 
368 388
   #if ENABLED(AUTO_BED_LEVELING_UBL)
369 389
     ARC_LIJKUVW_CODE(

+ 16
- 16
Marlin/src/module/motion.cpp Voir le fichier

@@ -1041,19 +1041,18 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
1041 1041
     NOLESS(segments, 1U);
1042 1042
 
1043 1043
     // The approximate length of each segment
1044
-    const float inv_segments = 1.0f / float(segments),
1045
-                cartesian_segment_mm = cartesian_mm * inv_segments;
1044
+    const float inv_segments = 1.0f / float(segments);
1046 1045
     const xyze_float_t segment_distance = diff * inv_segments;
1047 1046
 
1048
-    #if ENABLED(SCARA_FEEDRATE_SCALING)
1049
-      const float inv_duration = scaled_fr_mm_s / cartesian_segment_mm;
1050
-    #endif
1047
+    // Add hints to help optimize the move
1048
+    PlannerHints hints(cartesian_mm * inv_segments);
1049
+    TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
1051 1050
 
1052 1051
     /*
1053 1052
     SERIAL_ECHOPGM("mm=", cartesian_mm);
1054 1053
     SERIAL_ECHOPGM(" seconds=", seconds);
1055 1054
     SERIAL_ECHOPGM(" segments=", segments);
1056
-    SERIAL_ECHOPGM(" segment_mm=", cartesian_segment_mm);
1055
+    SERIAL_ECHOPGM(" segment_mm=", hints.millimeters);
1057 1056
     SERIAL_EOL();
1058 1057
     //*/
1059 1058
 
@@ -1065,11 +1064,12 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
1065 1064
     while (--segments) {
1066 1065
       segment_idle(next_idle_ms);
1067 1066
       raw += segment_distance;
1068
-      if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration))) break;
1067
+      if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, hints))
1068
+        break;
1069 1069
     }
1070 1070
 
1071 1071
     // Ensure last segment arrives at target location.
1072
-    planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration));
1072
+    planner.buffer_line(destination, scaled_fr_mm_s, active_extruder, hints);
1073 1073
 
1074 1074
     return false; // caller will update current_position
1075 1075
   }
@@ -1108,17 +1108,16 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
1108 1108
       NOLESS(segments, 1U);
1109 1109
 
1110 1110
       // The approximate length of each segment
1111
-      const float inv_segments = 1.0f / float(segments),
1112
-                  cartesian_segment_mm = cartesian_mm * inv_segments;
1111
+      const float inv_segments = 1.0f / float(segments);
1113 1112
       const xyze_float_t segment_distance = diff * inv_segments;
1114 1113
 
1115
-      #if ENABLED(SCARA_FEEDRATE_SCALING)
1116
-        const float inv_duration = scaled_fr_mm_s / cartesian_segment_mm;
1117
-      #endif
1114
+      // Add hints to help optimize the move
1115
+      PlannerHints hints(cartesian_mm * inv_segments);
1116
+      TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
1118 1117
 
1119 1118
       //SERIAL_ECHOPGM("mm=", cartesian_mm);
1120 1119
       //SERIAL_ECHOLNPGM(" segments=", segments);
1121
-      //SERIAL_ECHOLNPGM(" segment_mm=", cartesian_segment_mm);
1120
+      //SERIAL_ECHOLNPGM(" segment_mm=", hints.millimeters);
1122 1121
 
1123 1122
       // Get the raw current position as starting point
1124 1123
       xyze_pos_t raw = current_position;
@@ -1128,12 +1127,13 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
1128 1127
       while (--segments) {
1129 1128
         segment_idle(next_idle_ms);
1130 1129
         raw += segment_distance;
1131
-        if (!planner.buffer_line(raw, fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration))) break;
1130
+        if (!planner.buffer_line(raw, fr_mm_s, active_extruder, hints))
1131
+          break;
1132 1132
       }
1133 1133
 
1134 1134
       // Since segment_distance is only approximate,
1135 1135
       // the final move must be to the exact destination.
1136
-      planner.buffer_line(destination, fr_mm_s, active_extruder, cartesian_segment_mm OPTARG(SCARA_FEEDRATE_SCALING, inv_duration));
1136
+      planner.buffer_line(destination, fr_mm_s, active_extruder, hints);
1137 1137
     }
1138 1138
 
1139 1139
   #endif // SEGMENT_LEVELED_MOVES && !AUTO_BED_LEVELING_UBL

+ 216
- 210
Marlin/src/module/planner.cpp Voir le fichier

@@ -843,20 +843,22 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
843 843
     /**
844 844
      * Laser Trapezoid Calculations
845 845
      *
846
-     * Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr` steps while accelerating,
847
-     * and decrementing the power every `trap_ramp_exit_decr` while decelerating, to keep power proportional to feedrate.
848
-     * Laser power trap will reduce the initial power to no less than the laser_power_floor value. Based on the number
849
-     * of calculated accel/decel steps the power is distributed over the trapezoid entry- and exit-ramp steps.
846
+     * Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr`
847
+     * steps while accelerating, and decrementing the power every `trap_ramp_exit_decr` while decelerating,
848
+     * to keep power proportional to feedrate. Laser power trap will reduce the initial power to no less
849
+     * than the laser_power_floor value. Based on the number of calculated accel/decel steps the power is
850
+     * distributed over the trapezoid entry- and exit-ramp steps.
850 851
      *
851
-     * trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial power / accel steps and
852
-     * will be additively incremented using a trap_ramp_entry_incr value for each accel step processed later in the stepper code.
853
-     * The trap_ramp_exit_decr value is calculated as power / decel steps and is also adjusted to no less than the power floor.
852
+     * trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial
853
+     * power / accel steps and will be additively incremented using a trap_ramp_entry_incr value for each
854
+     * accel step processed later in the stepper code. The trap_ramp_exit_decr value is calculated as
855
+     * power / decel steps and is also adjusted to no less than the power floor.
854 856
      *
855
-     * If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing. The method allows
856
-     * for simpler non-powered moves like G0 or G28.
857
+     * If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing.
858
+     * The method allows for simpler non-powered moves like G0 or G28.
857 859
      *
858
-     * Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since the segments are
859
-     * usually too small.
860
+     * Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since
861
+     * the segments are usually too small.
860 862
      */
861 863
     if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
862 864
       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
937 939
       this block can never be less than block_buffer_tail and will always be pushed forward and maintain
938 940
       this requirement when encountered by the Planner::release_current_block() routine during a cycle.
939 941
 
940
-  NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short
941
-  line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't
942
-  enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and then
943
-  decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this happens and
944
-  becomes an annoyance, there are a few simple solutions: (1) Maximize the machine acceleration. The planner
945
-  will be able to compute higher velocity profiles within the same combined distance. (2) Maximize line
946
-  motion(s) distance per block to a desired tolerance. The more combined distance the planner has to use,
947
-  the faster it can go. (3) Maximize the planner buffer size. This also will increase the combined distance
948
-  for the planner to compute over. It also increases the number of computations the planner has to perform
949
-  to compute an optimal plan, so select carefully.
942
+  NOTE: Since the planner only computes on what's in the planner buffer, some motions with many short
943
+        segments (e.g., complex curves) may seem to move slowly. This is because there simply isn't
944
+        enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and
945
+        then decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this
946
+        happens and becomes an annoyance, there are a few simple solutions:
947
+
948
+    - Maximize the machine acceleration. The planner will be able to compute higher velocity profiles
949
+      within the same combined distance.
950
+
951
+    - Maximize line motion(s) distance per block to a desired tolerance. The more combined distance the
952
+      planner has to use, the faster it can go.
953
+
954
+    - Maximize the planner buffer size. This also will increase the combined distance for the planner to
955
+      compute over. It also increases the number of computations the planner has to perform to compute an
956
+      optimal plan, so select carefully.
957
+
958
+    - Use G2/G3 arcs instead of many short segments. Arcs inform the planner of a safe exit speed at the
959
+      end of the last segment, which alleviates this problem.
950 960
 */
951 961
 
952 962
 // The kernel called by recalculate() when scanning the plan from last to first entry.
953
-void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next) {
963
+void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next
964
+  OPTARG(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)
965
+) {
954 966
   if (current) {
955 967
     // If entry speed is already at the maximum entry speed, and there was no change of speed
956 968
     // 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
970 982
       // the reverse and forward planners, the corresponding block junction speed will always be at the
971 983
       // the maximum junction speed and may always be ignored for any speed reduction checks.
972 984
 
973
-      const float new_entry_speed_sqr = current->flag.nominal_length
974
-        ? max_entry_speed_sqr
975
-        : _MIN(max_entry_speed_sqr, max_allowable_speed_sqr(-current->acceleration, next ? next->entry_speed_sqr : sq(float(MINIMUM_PLANNER_SPEED)), current->millimeters));
985
+      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))),
986
+                  new_entry_speed_sqr = current->flag.nominal_length
987
+                    ? max_entry_speed_sqr
988
+                    : _MIN(max_entry_speed_sqr, max_allowable_speed_sqr(-current->acceleration, next_entry_speed_sqr, current->millimeters));
976 989
       if (current->entry_speed_sqr != new_entry_speed_sqr) {
977 990
 
978 991
         // 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
1001 1014
  * recalculate() needs to go over the current plan twice.
1002 1015
  * Once in reverse and once forward. This implements the reverse pass.
1003 1016
  */
1004
-void Planner::reverse_pass() {
1017
+void Planner::reverse_pass(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
1005 1018
   // Initialize block index to the last block in the planner buffer.
1006 1019
   uint8_t block_index = prev_block_index(block_buffer_head);
1007 1020
 
@@ -1025,7 +1038,7 @@ void Planner::reverse_pass() {
1025 1038
 
1026 1039
     // Only process movement blocks
1027 1040
     if (current->is_move()) {
1028
-      reverse_pass_kernel(current, next);
1041
+      reverse_pass_kernel(current, next OPTARG(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
1029 1042
       next = current;
1030 1043
     }
1031 1044
 
@@ -1138,7 +1151,7 @@ void Planner::forward_pass() {
1138 1151
  * according to the entry_factor for each junction. Must be called by
1139 1152
  * recalculate() after updating the blocks.
1140 1153
  */
1141
-void Planner::recalculate_trapezoids() {
1154
+void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
1142 1155
   // The tail may be changed by the ISR so get a local copy.
1143 1156
   uint8_t block_index = block_buffer_tail,
1144 1157
           head_block_index = block_buffer_head;
@@ -1211,8 +1224,10 @@ void Planner::recalculate_trapezoids() {
1211 1224
     block_index = next_block_index(block_index);
1212 1225
   }
1213 1226
 
1214
-  // Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
1215
-  if (next) {
1227
+  // Last/newest block in buffer. Always recalculated.
1228
+  if (block) {
1229
+    // Exit speed is set with MINIMUM_PLANNER_SPEED unless some code higher up knows better.
1230
+    next_entry_speed = _MAX(TERN0(HINTS_SAFE_EXIT_SPEED, SQRT(safe_exit_speed_sqr)), float(MINIMUM_PLANNER_SPEED));
1216 1231
 
1217 1232
     // Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it.
1218 1233
     // As the last block is always recalculated here, there is a chance the block isn't
@@ -1225,33 +1240,33 @@ void Planner::recalculate_trapezoids() {
1225 1240
     if (!stepper.is_block_busy(block)) {
1226 1241
       // Block is not BUSY, we won the race against the Stepper ISR:
1227 1242
 
1228
-      const float next_nominal_speed = SQRT(next->nominal_speed_sqr),
1229
-                  nomr = 1.0f / next_nominal_speed;
1230
-      calculate_trapezoid_for_block(next, next_entry_speed * nomr, float(MINIMUM_PLANNER_SPEED) * nomr);
1243
+      const float current_nominal_speed = SQRT(block->nominal_speed_sqr),
1244
+                  nomr = 1.0f / current_nominal_speed;
1245
+      calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
1231 1246
       #if ENABLED(LIN_ADVANCE)
1232
-        if (next->use_advance_lead) {
1233
-          const float comp = next->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
1234
-          next->max_adv_steps = next_nominal_speed * comp;
1235
-          next->final_adv_steps = (MINIMUM_PLANNER_SPEED) * comp;
1247
+        if (block->use_advance_lead) {
1248
+          const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
1249
+          block->max_adv_steps = current_nominal_speed * comp;
1250
+          block->final_adv_steps = next_entry_speed * comp;
1236 1251
         }
1237 1252
       #endif
1238 1253
     }
1239 1254
 
1240
-    // Reset next only to ensure its trapezoid is computed - The stepper is free to use
1255
+    // Reset block to ensure its trapezoid is computed - The stepper is free to use
1241 1256
     // the block from now on.
1242
-    next->flag.recalculate = false;
1257
+    block->flag.recalculate = false;
1243 1258
   }
1244 1259
 }
1245 1260
 
1246
-void Planner::recalculate() {
1261
+void Planner::recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_speed_sqr)) {
1247 1262
   // Initialize block index to the last block in the planner buffer.
1248 1263
   const uint8_t block_index = prev_block_index(block_buffer_head);
1249 1264
   // If there is just one block, no planning can be done. Avoid it!
1250 1265
   if (block_index != block_buffer_planned) {
1251
-    reverse_pass();
1266
+    reverse_pass(TERN_(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
1252 1267
     forward_pass();
1253 1268
   }
1254
-  recalculate_trapezoids();
1269
+  recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, safe_exit_speed_sqr));
1255 1270
 }
1256 1271
 
1257 1272
 /**
@@ -1777,22 +1792,21 @@ float Planner::get_axis_position_mm(const AxisEnum axis) {
1777 1792
 void Planner::synchronize() { while (busy()) idle(); }
1778 1793
 
1779 1794
 /**
1780
- * Planner::_buffer_steps
1781
- *
1782
- * Add a new linear movement to the planner queue (in terms of steps).
1795
+ * @brief Add a new linear movement to the planner queue (in terms of steps).
1783 1796
  *
1784
- *  target        - target position in steps units
1785
- *  target_float  - target position in direct (mm, degrees) units. optional
1786
- *  fr_mm_s       - (target) speed of the move
1787
- *  extruder      - target extruder
1788
- *  millimeters   - the length of the movement, if known
1797
+ * @param target        Target position in steps units
1798
+ * @param target_float  Target position in direct (mm, degrees) units.
1799
+ * @param cart_dist_mm  The pre-calculated move lengths for all axes, in mm
1800
+ * @param fr_mm_s       (target) speed of the move
1801
+ * @param extruder      target extruder
1802
+ * @param hints         parameters to aid planner calculations
1789 1803
  *
1790
- * Returns true if movement was properly queued, false otherwise (if cleaning)
1804
+ * @return  true if movement was properly queued, false otherwise (if cleaning)
1791 1805
  */
1792 1806
 bool Planner::_buffer_steps(const xyze_long_t &target
1793 1807
   OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
1794 1808
   OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
1795
-  , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters
1809
+  , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
1796 1810
 ) {
1797 1811
 
1798 1812
   // Wait for the next available block
@@ -1808,7 +1822,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
1808 1822
   if (!_populate_block(block, target
1809 1823
         OPTARG(HAS_POSITION_FLOAT, target_float)
1810 1824
         OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
1811
-        , fr_mm_s, extruder, millimeters
1825
+        , fr_mm_s, extruder, hints
1812 1826
       )
1813 1827
   ) {
1814 1828
     // Movement was not queued, probably because it was too short.
@@ -1830,7 +1844,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
1830 1844
   block_buffer_head = next_buffer_head;
1831 1845
 
1832 1846
   // Recalculate and optimize trapezoidal speed profiles
1833
-  recalculate();
1847
+  recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, hints.safe_exit_speed_sqr));
1834 1848
 
1835 1849
   // Movement successfully queued!
1836 1850
   return true;
@@ -1848,8 +1862,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target
1848 1862
  * @param cart_dist_mm  The pre-calculated move lengths for all axes, in mm
1849 1863
  * @param fr_mm_s       (target) speed of the move
1850 1864
  * @param extruder      target extruder
1851
- * @param millimeters   A pre-calculated linear distance for the move, in mm,
1852
- *                      or 0.0 to have the distance calculated here.
1865
+ * @param hints         parameters to aid planner calculations
1853 1866
  *
1854 1867
  * @return  true if movement is acceptable, false otherwise
1855 1868
  */
@@ -1858,7 +1871,7 @@ bool Planner::_populate_block(
1858 1871
   const abce_long_t &target
1859 1872
   OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
1860 1873
   OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
1861
-  , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters/*=0.0*/
1874
+  , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
1862 1875
 ) {
1863 1876
   int32_t LOGICAL_AXIS_LIST(
1864 1877
     de = target.e - position.e,
@@ -2134,8 +2147,8 @@ bool Planner::_populate_block(
2134 2147
     block->millimeters = TERN0(HAS_EXTRUDERS, ABS(steps_dist_mm.e));
2135 2148
   }
2136 2149
   else {
2137
-    if (millimeters)
2138
-      block->millimeters = millimeters;
2150
+    if (hints.millimeters)
2151
+      block->millimeters = hints.millimeters;
2139 2152
     else {
2140 2153
       /**
2141 2154
        * Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of NIST
@@ -2243,15 +2256,9 @@ bool Planner::_populate_block(
2243 2256
 
2244 2257
   #if ENABLED(AUTO_POWER_CONTROL)
2245 2258
     if (NUM_AXIS_GANG(
2246
-         block->steps.x,
2247
-      || block->steps.y,
2248
-      || block->steps.z,
2249
-      || block->steps.i,
2250
-      || block->steps.j,
2251
-      || block->steps.k,
2252
-      || block->steps.u,
2253
-      || block->steps.v,
2254
-      || block->steps.w
2259
+         block->steps.x, || block->steps.y, || block->steps.z,
2260
+      || block->steps.i, || block->steps.j, || block->steps.k,
2261
+      || block->steps.u, || block->steps.v, || block->steps.w
2255 2262
     )) powerManager.power_on();
2256 2263
   #endif
2257 2264
 
@@ -2562,29 +2569,17 @@ bool Planner::_populate_block(
2562 2569
     if (block->step_event_count <= acceleration_long_cutoff) {
2563 2570
       LOGICAL_AXIS_CODE(
2564 2571
         LIMIT_ACCEL_LONG(E_AXIS, E_INDEX_N(extruder)),
2565
-        LIMIT_ACCEL_LONG(A_AXIS, 0),
2566
-        LIMIT_ACCEL_LONG(B_AXIS, 0),
2567
-        LIMIT_ACCEL_LONG(C_AXIS, 0),
2568
-        LIMIT_ACCEL_LONG(I_AXIS, 0),
2569
-        LIMIT_ACCEL_LONG(J_AXIS, 0),
2570
-        LIMIT_ACCEL_LONG(K_AXIS, 0),
2571
-        LIMIT_ACCEL_LONG(U_AXIS, 0),
2572
-        LIMIT_ACCEL_LONG(V_AXIS, 0),
2573
-        LIMIT_ACCEL_LONG(W_AXIS, 0)
2572
+        LIMIT_ACCEL_LONG(A_AXIS, 0), LIMIT_ACCEL_LONG(B_AXIS, 0), LIMIT_ACCEL_LONG(C_AXIS, 0),
2573
+        LIMIT_ACCEL_LONG(I_AXIS, 0), LIMIT_ACCEL_LONG(J_AXIS, 0), LIMIT_ACCEL_LONG(K_AXIS, 0),
2574
+        LIMIT_ACCEL_LONG(U_AXIS, 0), LIMIT_ACCEL_LONG(V_AXIS, 0), LIMIT_ACCEL_LONG(W_AXIS, 0)
2574 2575
       );
2575 2576
     }
2576 2577
     else {
2577 2578
       LOGICAL_AXIS_CODE(
2578 2579
         LIMIT_ACCEL_FLOAT(E_AXIS, E_INDEX_N(extruder)),
2579
-        LIMIT_ACCEL_FLOAT(A_AXIS, 0),
2580
-        LIMIT_ACCEL_FLOAT(B_AXIS, 0),
2581
-        LIMIT_ACCEL_FLOAT(C_AXIS, 0),
2582
-        LIMIT_ACCEL_FLOAT(I_AXIS, 0),
2583
-        LIMIT_ACCEL_FLOAT(J_AXIS, 0),
2584
-        LIMIT_ACCEL_FLOAT(K_AXIS, 0),
2585
-        LIMIT_ACCEL_FLOAT(U_AXIS, 0),
2586
-        LIMIT_ACCEL_FLOAT(V_AXIS, 0),
2587
-        LIMIT_ACCEL_FLOAT(W_AXIS, 0)
2580
+        LIMIT_ACCEL_FLOAT(A_AXIS, 0), LIMIT_ACCEL_FLOAT(B_AXIS, 0), LIMIT_ACCEL_FLOAT(C_AXIS, 0),
2581
+        LIMIT_ACCEL_FLOAT(I_AXIS, 0), LIMIT_ACCEL_FLOAT(J_AXIS, 0), LIMIT_ACCEL_FLOAT(K_AXIS, 0),
2582
+        LIMIT_ACCEL_FLOAT(U_AXIS, 0), LIMIT_ACCEL_FLOAT(V_AXIS, 0), LIMIT_ACCEL_FLOAT(W_AXIS, 0)
2588 2583
       );
2589 2584
     }
2590 2585
   }
@@ -2649,7 +2644,10 @@ bool Planner::_populate_block(
2649 2644
       #if HAS_DIST_MM_ARG
2650 2645
         cart_dist_mm
2651 2646
       #else
2652
-        LOGICAL_AXIS_ARRAY(steps_dist_mm.e, steps_dist_mm.x, steps_dist_mm.y, steps_dist_mm.z, steps_dist_mm.i, steps_dist_mm.j, steps_dist_mm.k, steps_dist_mm.u, steps_dist_mm.v, steps_dist_mm.w)
2647
+        LOGICAL_AXIS_ARRAY(steps_dist_mm.e,
2648
+          steps_dist_mm.x, steps_dist_mm.y, steps_dist_mm.z,
2649
+          steps_dist_mm.i, steps_dist_mm.j, steps_dist_mm.k,
2650
+          steps_dist_mm.u, steps_dist_mm.v, steps_dist_mm.w)
2653 2651
       #endif
2654 2652
     ;
2655 2653
 
@@ -2670,7 +2668,7 @@ bool Planner::_populate_block(
2670 2668
       // NOTE: Max junction velocity is computed without sin() or acos() by trig half angle identity.
2671 2669
       float junction_cos_theta = LOGICAL_AXIS_GANG(
2672 2670
                                  + (-prev_unit_vec.e * unit_vec.e),
2673
-                                   (-prev_unit_vec.x * unit_vec.x),
2671
+                                 + (-prev_unit_vec.x * unit_vec.x),
2674 2672
                                  + (-prev_unit_vec.y * unit_vec.y),
2675 2673
                                  + (-prev_unit_vec.z * unit_vec.z),
2676 2674
                                  + (-prev_unit_vec.i * unit_vec.i),
@@ -2687,104 +2685,110 @@ bool Planner::_populate_block(
2687 2685
         vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
2688 2686
       }
2689 2687
       else {
2690
-        NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
2691
-
2692 2688
         // Convert delta vector to unit vector
2693 2689
         xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
2694 2690
         normalize_junction_vector(junction_unit_vec);
2695 2691
 
2696
-        const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec),
2697
-                    sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
2698
-
2699
-        vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
2700
-
2701
-        #if ENABLED(JD_HANDLE_SMALL_SEGMENTS)
2702
-
2703
-          // For small moves with >135° junction (octagon) find speed for approximate arc
2704
-          if (block->millimeters < 1 && junction_cos_theta < -0.7071067812f) {
2705
-
2706
-            #if ENABLED(JD_USE_MATH_ACOS)
2707
-
2708
-              #error "TODO: Inline maths with the MCU / FPU."
2709
-
2710
-            #elif ENABLED(JD_USE_LOOKUP_TABLE)
2711
-
2712
-              // Fast acos approximation (max. error +-0.01 rads)
2713
-              // Based on LUT table and linear interpolation
2714
-
2715
-              /**
2716
-               *  // Generate the JD Lookup Table
2717
-               *  constexpr float c = 1.00751495f; // Correction factor to center error around 0
2718
-               *  for (int i = 0; i < jd_lut_count - 1; ++i) {
2719
-               *    const float x0 = (sq(i) - 1) / sq(i),
2720
-               *                y0 = acos(x0) * (i == 0 ? 1 : c),
2721
-               *                x1 = i < jd_lut_count - 1 ?  0.5 * x0 + 0.5 : 0.999999f,
2722
-               *                y1 = acos(x1) * (i < jd_lut_count - 1 ? c : 1);
2723
-               *    jd_lut_k[i] = (y0 - y1) / (x0 - x1);
2724
-               *    jd_lut_b[i] = (y1 * x0 - y0 * x1) / (x0 - x1);
2725
-               *  }
2726
-               *
2727
-               *  // Compute correction factor (Set c to 1.0f first!)
2728
-               *  float min = INFINITY, max = -min;
2729
-               *  for (float t = 0; t <= 1; t += 0.0003f) {
2730
-               *    const float e = acos(t) / approx(t);
2731
-               *    if (isfinite(e)) {
2732
-               *      if (e < min) min = e;
2733
-               *      if (e > max) max = e;
2734
-               *    }
2735
-               *  }
2736
-               *  fprintf(stderr, "%.9gf, ", (min + max) / 2);
2737
-               */
2738
-              static constexpr int16_t  jd_lut_count = 16;
2739
-              static constexpr uint16_t jd_lut_tll   = _BV(jd_lut_count - 1);
2740
-              static constexpr int16_t  jd_lut_tll0  = __builtin_clz(jd_lut_tll) + 1; // i.e., 16 - jd_lut_count + 1
2741
-              static constexpr float jd_lut_k[jd_lut_count] PROGMEM = {
2742
-                -1.03145837f, -1.30760646f, -1.75205851f, -2.41705704f,
2743
-                -3.37769222f, -4.74888992f, -6.69649887f, -9.45661736f,
2744
-                -13.3640480f, -18.8928222f, -26.7136841f, -37.7754593f,
2745
-                -53.4201813f, -75.5458374f, -106.836761f, -218.532821f };
2746
-              static constexpr float jd_lut_b[jd_lut_count] PROGMEM = {
2747
-                 1.57079637f,  1.70887053f,  2.04220939f,  2.62408352f,
2748
-                 3.52467871f,  4.85302639f,  6.77020454f,  9.50875854f,
2749
-                 13.4009285f,  18.9188995f,  26.7321243f,  37.7885055f,
2750
-                 53.4293975f,  75.5523529f,  106.841369f,  218.534011f };
2751
-
2752
-              const float neg = junction_cos_theta < 0 ? -1 : 1,
2753
-                          t = neg * junction_cos_theta;
2754
-
2755
-              const int16_t idx = (t < 0.00000003f) ? 0 : __builtin_clz(uint16_t((1.0f - t) * jd_lut_tll)) - jd_lut_tll0;
2756
-
2757
-              float junction_theta = t * pgm_read_float(&jd_lut_k[idx]) + pgm_read_float(&jd_lut_b[idx]);
2758
-              if (neg > 0) junction_theta = RADIANS(180) - junction_theta; // acos(-t)
2759
-
2760
-            #else
2761
-
2762
-              // Fast acos(-t) approximation (max. error +-0.033rad = 1.89°)
2763
-              // Based on MinMax polynomial published by W. Randolph Franklin, see
2764
-              // https://wrf.ecse.rpi.edu/Research/Short_Notes/arcsin/onlyelem.html
2765
-              //  acos( t) = pi / 2 - asin(x)
2766
-              //  acos(-t) = pi - acos(t) ... pi / 2 + asin(x)
2767
-
2768
-              const float neg = junction_cos_theta < 0 ? -1 : 1,
2769
-                          t = neg * junction_cos_theta,
2770
-                          asinx =       0.032843707f
2771
-                                + t * (-1.451838349f
2772
-                                + t * ( 29.66153956f
2773
-                                + t * (-131.1123477f
2774
-                                + t * ( 262.8130562f
2775
-                                + t * (-242.7199627f
2776
-                                + t * ( 84.31466202f ) ))))),
2777
-                          junction_theta = RADIANS(90) + neg * asinx; // acos(-t)
2778
-
2779
-              // NOTE: junction_theta bottoms out at 0.033 which avoids divide by 0.
2780
-
2781
-            #endif
2782
-
2783
-            const float limit_sqr = (block->millimeters * junction_acceleration) / junction_theta;
2784
-            NOMORE(vmax_junction_sqr, limit_sqr);
2785
-          }
2692
+        const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec);
2786 2693
 
2787
-        #endif // JD_HANDLE_SMALL_SEGMENTS
2694
+        if (TERN0(HINTS_CURVE_RADIUS, hints.curve_radius)) {
2695
+          TERN_(HINTS_CURVE_RADIUS, vmax_junction_sqr = junction_acceleration * hints.curve_radius);
2696
+        }
2697
+        else {
2698
+          NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
2699
+
2700
+          const float sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
2701
+
2702
+          vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
2703
+
2704
+          #if ENABLED(JD_HANDLE_SMALL_SEGMENTS)
2705
+
2706
+            // For small moves with >135° junction (octagon) find speed for approximate arc
2707
+            if (block->millimeters < 1 && junction_cos_theta < -0.7071067812f) {
2708
+
2709
+              #if ENABLED(JD_USE_MATH_ACOS)
2710
+
2711
+                #error "TODO: Inline maths with the MCU / FPU."
2712
+
2713
+              #elif ENABLED(JD_USE_LOOKUP_TABLE)
2714
+
2715
+                // Fast acos approximation (max. error +-0.01 rads)
2716
+                // Based on LUT table and linear interpolation
2717
+
2718
+                /**
2719
+                 *  // Generate the JD Lookup Table
2720
+                 *  constexpr float c = 1.00751495f; // Correction factor to center error around 0
2721
+                 *  for (int i = 0; i < jd_lut_count - 1; ++i) {
2722
+                 *    const float x0 = (sq(i) - 1) / sq(i),
2723
+                 *                y0 = acos(x0) * (i == 0 ? 1 : c),
2724
+                 *                x1 = i < jd_lut_count - 1 ?  0.5 * x0 + 0.5 : 0.999999f,
2725
+                 *                y1 = acos(x1) * (i < jd_lut_count - 1 ? c : 1);
2726
+                 *    jd_lut_k[i] = (y0 - y1) / (x0 - x1);
2727
+                 *    jd_lut_b[i] = (y1 * x0 - y0 * x1) / (x0 - x1);
2728
+                 *  }
2729
+                 *
2730
+                 *  // Compute correction factor (Set c to 1.0f first!)
2731
+                 *  float min = INFINITY, max = -min;
2732
+                 *  for (float t = 0; t <= 1; t += 0.0003f) {
2733
+                 *    const float e = acos(t) / approx(t);
2734
+                 *    if (isfinite(e)) {
2735
+                 *      if (e < min) min = e;
2736
+                 *      if (e > max) max = e;
2737
+                 *    }
2738
+                 *  }
2739
+                 *  fprintf(stderr, "%.9gf, ", (min + max) / 2);
2740
+                 */
2741
+                static constexpr int16_t  jd_lut_count = 16;
2742
+                static constexpr uint16_t jd_lut_tll   = _BV(jd_lut_count - 1);
2743
+                static constexpr int16_t  jd_lut_tll0  = __builtin_clz(jd_lut_tll) + 1; // i.e., 16 - jd_lut_count + 1
2744
+                static constexpr float jd_lut_k[jd_lut_count] PROGMEM = {
2745
+                  -1.03145837f, -1.30760646f, -1.75205851f, -2.41705704f,
2746
+                  -3.37769222f, -4.74888992f, -6.69649887f, -9.45661736f,
2747
+                  -13.3640480f, -18.8928222f, -26.7136841f, -37.7754593f,
2748
+                  -53.4201813f, -75.5458374f, -106.836761f, -218.532821f };
2749
+                static constexpr float jd_lut_b[jd_lut_count] PROGMEM = {
2750
+                   1.57079637f,  1.70887053f,  2.04220939f,  2.62408352f,
2751
+                   3.52467871f,  4.85302639f,  6.77020454f,  9.50875854f,
2752
+                   13.4009285f,  18.9188995f,  26.7321243f,  37.7885055f,
2753
+                   53.4293975f,  75.5523529f,  106.841369f,  218.534011f };
2754
+
2755
+                const float neg = junction_cos_theta < 0 ? -1 : 1,
2756
+                            t = neg * junction_cos_theta;
2757
+
2758
+                const int16_t idx = (t < 0.00000003f) ? 0 : __builtin_clz(uint16_t((1.0f - t) * jd_lut_tll)) - jd_lut_tll0;
2759
+
2760
+                float junction_theta = t * pgm_read_float(&jd_lut_k[idx]) + pgm_read_float(&jd_lut_b[idx]);
2761
+                if (neg > 0) junction_theta = RADIANS(180) - junction_theta; // acos(-t)
2762
+
2763
+              #else
2764
+
2765
+                // Fast acos(-t) approximation (max. error +-0.033rad = 1.89°)
2766
+                // Based on MinMax polynomial published by W. Randolph Franklin, see
2767
+                // https://wrf.ecse.rpi.edu/Research/Short_Notes/arcsin/onlyelem.html
2768
+                //  acos( t) = pi / 2 - asin(x)
2769
+                //  acos(-t) = pi - acos(t) ... pi / 2 + asin(x)
2770
+
2771
+                const float neg = junction_cos_theta < 0 ? -1 : 1,
2772
+                            t = neg * junction_cos_theta,
2773
+                            asinx =       0.032843707f
2774
+                                  + t * (-1.451838349f
2775
+                                  + t * ( 29.66153956f
2776
+                                  + t * (-131.1123477f
2777
+                                  + t * ( 262.8130562f
2778
+                                  + t * (-242.7199627f
2779
+                                  + t * ( 84.31466202f ) ))))),
2780
+                            junction_theta = RADIANS(90) + neg * asinx; // acos(-t)
2781
+
2782
+                // NOTE: junction_theta bottoms out at 0.033 which avoids divide by 0.
2783
+
2784
+              #endif
2785
+
2786
+              const float limit_sqr = (block->millimeters * junction_acceleration) / junction_theta;
2787
+              NOMORE(vmax_junction_sqr, limit_sqr);
2788
+            }
2789
+
2790
+          #endif // JD_HANDLE_SMALL_SEGMENTS
2791
+        }
2788 2792
       }
2789 2793
 
2790 2794
       // Get the lowest speed
@@ -2944,12 +2948,11 @@ bool Planner::_populate_block(
2944 2948
 } // _populate_block()
2945 2949
 
2946 2950
 /**
2947
- * Planner::buffer_sync_block
2948
- * Add a block to the buffer that just updates the position
2949
- * @param sync_flag BLOCK_FLAG_SYNC_FANS & BLOCK_FLAG_LASER_PWR
2950
- * Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
2951
+ * @brief Add a block to the buffer that just updates the position
2952
+ *        Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
2953
+ *
2954
+ * @param sync_flag  The sync flag to set, determining the type of sync the block will do
2951 2955
  */
2952
-
2953 2956
 void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) {
2954 2957
 
2955 2958
   // Wait for the next available block
@@ -2957,14 +2960,13 @@ void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_PO
2957 2960
   block_t * const block = get_next_free_block(next_buffer_head);
2958 2961
 
2959 2962
   // Clear block
2960
-  memset(block, 0, sizeof(block_t));
2963
+  block->reset();
2961 2964
   block->flag.apply(sync_flag);
2962 2965
 
2963 2966
   block->position = position;
2964 2967
   #if ENABLED(BACKLASH_COMPENSATION)
2965 2968
     LOOP_NUM_AXES(axis) block->position[axis] += backlash.get_applied_steps((AxisEnum)axis);
2966 2969
   #endif
2967
-
2968 2970
   #if BOTH(HAS_FAN, LASER_SYNCHRONOUS_M106_M107)
2969 2971
     FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
2970 2972
   #endif
@@ -2991,22 +2993,24 @@ void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_PO
2991 2993
 } // buffer_sync_block()
2992 2994
 
2993 2995
 /**
2994
- * Planner::buffer_segment
2995
- *
2996
- * Add a new linear movement to the buffer in axis units.
2996
+ * @brief Add a single linear movement
2997 2997
  *
2998
- * Leveling and kinematics should be applied ahead of calling this.
2998
+ * @description Add a new linear movement to the buffer in axis units.
2999
+ *              Leveling and kinematics should be applied before calling this.
2999 3000
  *
3000
- *  a,b,c,e     - target positions in mm and/or degrees
3001
- *  fr_mm_s     - (target) speed of the move
3002
- *  extruder    - target extruder
3003
- *  millimeters - the length of the movement, if known
3001
+ * @param abce          Target position in mm and/or degrees
3002
+ * @param cart_dist_mm  The pre-calculated move lengths for all axes, in mm
3003
+ * @param fr_mm_s       (target) speed of the move
3004
+ * @param extruder      optional target extruder (otherwise active_extruder)
3005
+ * @param hints         optional parameters to aid planner calculations
3004 3006
  *
3005
- * Return 'false' if no segment was queued due to cleaning, cold extrusion, full queue, etc.
3007
+ * @return  false if no segment was queued due to cleaning, cold extrusion, full queue, etc.
3006 3008
  */
3007 3009
 bool Planner::buffer_segment(const abce_pos_t &abce
3008 3010
   OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
3009
-  , const_feedRate_t fr_mm_s, const uint8_t extruder/*=active_extruder*/, const_float_t millimeters/*=0.0*/
3011
+  , const_feedRate_t fr_mm_s
3012
+  , const uint8_t extruder/*=active_extruder*/
3013
+  , const PlannerHints &hints/*=PlannerHints()*/
3010 3014
 ) {
3011 3015
 
3012 3016
   // If we are cleaning, do not accept queuing of movements
@@ -3112,8 +3116,8 @@ bool Planner::buffer_segment(const abce_pos_t &abce
3112 3116
   if (!_buffer_steps(target
3113 3117
       OPTARG(HAS_POSITION_FLOAT, target_float)
3114 3118
       OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
3115
-      , fr_mm_s, extruder, millimeters)
3116
-  ) return false;
3119
+      , fr_mm_s, extruder, hints
3120
+  )) return false;
3117 3121
 
3118 3122
   stepper.wake_up();
3119 3123
   return true;
@@ -3126,12 +3130,12 @@ bool Planner::buffer_segment(const abce_pos_t &abce
3126 3130
  *
3127 3131
  *  cart            - target position in mm or degrees
3128 3132
  *  fr_mm_s         - (target) speed of the move (mm/s)
3129
- *  extruder        - target extruder
3130
- *  millimeters     - the length of the movement, if known
3131
- *  inv_duration    - the reciprocal if the duration of the movement, if known (kinematic only if feeedrate scaling is enabled)
3133
+ *  extruder        - optional target extruder (otherwise active_extruder)
3134
+ *  hints           - optional parameters to aid planner calculations
3132 3135
  */
3133
-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*/
3134
-  OPTARG(SCARA_FEEDRATE_SCALING, const_float_t inv_duration/*=0.0*/)
3136
+bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s
3137
+  , const uint8_t extruder/*=active_extruder*/
3138
+  , const PlannerHints &hints/*=PlannerHints()*/
3135 3139
 ) {
3136 3140
   xyze_pos_t machine = cart;
3137 3141
   TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine));
@@ -3153,28 +3157,30 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
3153 3157
       );
3154 3158
     #endif
3155 3159
 
3156
-    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));
3157
-
3158 3160
     // Cartesian XYZ to kinematic ABC, stored in global 'delta'
3159 3161
     inverse_kinematics(machine);
3160 3162
 
3163
+    PlannerHints ph = hints;
3164
+    if (!hints.millimeters)
3165
+      ph.millimeters = (cart_dist_mm.x || cart_dist_mm.y) ? cart_dist_mm.magnitude() : TERN0(HAS_Z_AXIS, ABS(cart_dist_mm.z));
3166
+
3161 3167
     #if ENABLED(SCARA_FEEDRATE_SCALING)
3162 3168
       // For SCARA scale the feedrate from mm/s to degrees/s
3163 3169
       // i.e., Complete the angular vector in the given time.
3164
-      const float duration_recip = inv_duration ?: fr_mm_s / mm;
3170
+      const float duration_recip = hints.inv_duration ?: fr_mm_s / ph.millimeters;
3165 3171
       const xyz_pos_t diff = delta - position_float;
3166 3172
       const feedRate_t feedrate = diff.magnitude() * duration_recip;
3167 3173
     #else
3168 3174
       const feedRate_t feedrate = fr_mm_s;
3169 3175
     #endif
3170 3176
     TERN_(HAS_EXTRUDERS, delta.e = machine.e);
3171
-    if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, mm)) {
3177
+    if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, ph)) {
3172 3178
       position_cart = cart;
3173 3179
       return true;
3174 3180
     }
3175 3181
     return false;
3176 3182
   #else
3177
-    return buffer_segment(machine, fr_mm_s, extruder, millimeters);
3183
+    return buffer_segment(machine, fr_mm_s, extruder, hints);
3178 3184
   #endif
3179 3185
 } // buffer_line()
3180 3186
 

+ 44
- 17
Marlin/src/module/planner.h Voir le fichier

@@ -280,6 +280,8 @@ typedef struct block_t {
280 280
     block_laser_t laser;
281 281
   #endif
282 282
 
283
+  void reset() { memset((char*)this, 0, sizeof(*this)); }
284
+
283 285
 } block_t;
284 286
 
285 287
 #if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
@@ -349,6 +351,30 @@ typedef struct {
349 351
   typedef IF<(BLOCK_BUFFER_SIZE > 64), uint16_t, uint8_t>::type last_move_t;
350 352
 #endif
351 353
 
354
+#if ENABLED(ARC_SUPPORT)
355
+  #define HINTS_CURVE_RADIUS
356
+  #define HINTS_SAFE_EXIT_SPEED
357
+#endif
358
+
359
+struct PlannerHints {
360
+  float millimeters = 0.0;            // Move Length, if known, else 0.
361
+  #if ENABLED(SCARA_FEEDRATE_SCALING)
362
+    float inv_duration = 0.0;         // Reciprocal of the move duration, if known
363
+  #endif
364
+  #if ENABLED(HINTS_CURVE_RADIUS)
365
+    float curve_radius = 0.0;         // Radius of curvature of the motion path - to calculate cornering speed
366
+  #else
367
+    static constexpr float curve_radius = 0.0;
368
+  #endif
369
+  #if ENABLED(HINTS_SAFE_EXIT_SPEED)
370
+    float safe_exit_speed_sqr = 0.0;  // Square of the speed considered "safe" at the end of the segment
371
+                                      // i.e., at or below the exit speed of the segment that the planner
372
+                                      // would calculate if it knew the as-yet-unbuffered path
373
+  #endif
374
+
375
+  PlannerHints(const_float_t mm=0.0f) : millimeters(mm) {}
376
+};
377
+
352 378
 class Planner {
353 379
   public:
354 380
 
@@ -752,14 +778,14 @@ class Planner {
752 778
      *  target      - target position in steps units
753 779
      *  fr_mm_s     - (target) speed of the move
754 780
      *  extruder    - target extruder
755
-     *  millimeters - the length of the movement, if known
781
+     *  hints       - parameters to aid planner calculations
756 782
      *
757 783
      * Returns true if movement was buffered, false otherwise
758 784
      */
759 785
     static bool _buffer_steps(const xyze_long_t &target
760 786
       OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
761 787
       OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
762
-      , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters=0.0
788
+      , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
763 789
     );
764 790
 
765 791
     /**
@@ -774,15 +800,14 @@ class Planner {
774 800
      * @param cart_dist_mm  The pre-calculated move lengths for all axes, in mm
775 801
      * @param fr_mm_s       (target) speed of the move
776 802
      * @param extruder      target extruder
777
-     * @param millimeters   A pre-calculated linear distance for the move, in mm,
778
-     *                      or 0.0 to have the distance calculated here.
803
+     * @param hints         parameters to aid planner calculations
779 804
      *
780 805
      * @return  true if movement is acceptable, false otherwise
781 806
      */
782 807
     static bool _populate_block(block_t * const block, const xyze_long_t &target
783 808
       OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
784 809
       OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
785
-      , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters=0.0
810
+      , feedRate_t fr_mm_s, const uint8_t extruder, const PlannerHints &hints
786 811
     );
787 812
 
788 813
     /**
@@ -809,12 +834,14 @@ class Planner {
809 834
      *
810 835
      *  a,b,c,e     - target positions in mm and/or degrees
811 836
      *  fr_mm_s     - (target) speed of the move
812
-     *  extruder    - target extruder
813
-     *  millimeters - the length of the movement, if known
837
+     *  extruder    - optional target extruder (otherwise active_extruder)
838
+     *  hints       - optional parameters to aid planner calculations
814 839
      */
815 840
     static bool buffer_segment(const abce_pos_t &abce
816 841
       OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
817
-      , const_feedRate_t fr_mm_s, const uint8_t extruder=active_extruder, const_float_t millimeters=0.0
842
+      , const_feedRate_t fr_mm_s
843
+      , const uint8_t extruder=active_extruder
844
+      , const PlannerHints &hints=PlannerHints()
818 845
     );
819 846
 
820 847
   public:
@@ -826,12 +853,12 @@ class Planner {
826 853
      *
827 854
      *  cart         - target position in mm or degrees
828 855
      *  fr_mm_s      - (target) speed of the move (mm/s)
829
-     *  extruder     - target extruder
830
-     *  millimeters  - the length of the movement, if known
831
-     *  inv_duration - the reciprocal if the duration of the movement, if known (kinematic only if feeedrate scaling is enabled)
856
+     *  extruder     - optional target extruder (otherwise active_extruder)
857
+     *  hints        - optional parameters to aid planner calculations
832 858
      */
833
-    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
834
-      OPTARG(SCARA_FEEDRATE_SCALING, const_float_t inv_duration=0.0)
859
+    static bool buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s
860
+      , const uint8_t extruder=active_extruder
861
+      , const PlannerHints &hints=PlannerHints()
835 862
     );
836 863
 
837 864
     #if ENABLED(DIRECT_STEPPING)
@@ -1024,15 +1051,15 @@ class Planner {
1024 1051
 
1025 1052
     static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor);
1026 1053
 
1027
-    static void reverse_pass_kernel(block_t * const current, const block_t * const next);
1054
+    static void reverse_pass_kernel(block_t * const current, const block_t * const next OPTARG(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
1028 1055
     static void forward_pass_kernel(const block_t * const previous, block_t * const current, uint8_t block_index);
1029 1056
 
1030
-    static void reverse_pass();
1057
+    static void reverse_pass(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
1031 1058
     static void forward_pass();
1032 1059
 
1033
-    static void recalculate_trapezoids();
1060
+    static void recalculate_trapezoids(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
1034 1061
 
1035
-    static void recalculate();
1062
+    static void recalculate(TERN_(ARC_SUPPORT, const_float_t safe_exit_speed_sqr));
1036 1063
 
1037 1064
     #if HAS_JUNCTION_DEVIATION
1038 1065
 

+ 5
- 2
Marlin/src/module/planner_bezier.cpp Voir le fichier

@@ -121,6 +121,9 @@ void cubic_b_spline(
121 121
 
122 122
   millis_t next_idle_ms = millis() + 200UL;
123 123
 
124
+  // Hints to help optimize the move
125
+  PlannerHints hints;
126
+
124 127
   for (float t = 0; t < 1;) {
125 128
 
126 129
     thermalManager.task();
@@ -177,7 +180,7 @@ void cubic_b_spline(
177 180
       }
178 181
     */
179 182
 
180
-    step = new_t - t;
183
+    hints.millimeters = new_t - t;
181 184
     t = new_t;
182 185
 
183 186
     // Compute and send new position
@@ -203,7 +206,7 @@ void cubic_b_spline(
203 206
       const xyze_pos_t &pos = bez_target;
204 207
     #endif
205 208
 
206
-    if (!planner.buffer_line(pos, scaled_fr_mm_s, active_extruder, step))
209
+    if (!planner.buffer_line(pos, scaled_fr_mm_s, active_extruder, hints))
207 210
       break;
208 211
   }
209 212
 }

+ 14
- 13
Marlin/src/module/stepper.cpp Voir le fichier

@@ -2002,14 +2002,15 @@ uint32_t Stepper::block_phase_isr() {
2002 2002
           else if (LA_steps) nextAdvanceISR = 0;
2003 2003
         #endif
2004 2004
 
2005
-        /*
2005
+        /**
2006 2006
          * Adjust Laser Power - Accelerating
2007
-         * isPowered - True when a move is powered.
2008
-         * isEnabled - laser power is active.
2009
-         * Laser power variables are calulated and stored in this block by the planner code.
2010 2007
          *
2011
-         * trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
2012
-         * trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
2008
+         *  isPowered - True when a move is powered.
2009
+         *  isEnabled - laser power is active.
2010
+         *
2011
+         * Laser power variables are calulated and stored in this block by the planner code.
2012
+         *  trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
2013
+         *  trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
2013 2014
          *
2014 2015
          * Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive.
2015 2016
          */
@@ -2032,6 +2033,7 @@ uint32_t Stepper::block_phase_isr() {
2032 2033
         uint32_t step_rate;
2033 2034
 
2034 2035
         #if ENABLED(S_CURVE_ACCELERATION)
2036
+
2035 2037
           // If this is the 1st time we process the 2nd half of the trapezoid...
2036 2038
           if (!bezier_2nd_half) {
2037 2039
             // Initialize the Bézier speed curve
@@ -2046,6 +2048,7 @@ uint32_t Stepper::block_phase_isr() {
2046 2048
               ? _eval_bezier_curve(deceleration_time)
2047 2049
               : current_block->final_rate;
2048 2050
           }
2051
+
2049 2052
         #else
2050 2053
           // Using the old trapezoidal control
2051 2054
           step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
@@ -2055,9 +2058,8 @@ uint32_t Stepper::block_phase_isr() {
2055 2058
           }
2056 2059
           else
2057 2060
             step_rate = current_block->final_rate;
2058
-        #endif
2059 2061
 
2060
-        // step_rate is in steps/second
2062
+        #endif
2061 2063
 
2062 2064
         // step_rate to timer interval and steps per stepper isr
2063 2065
         interval = calc_timer_interval(step_rate, &steps_per_isr);
@@ -2109,10 +2111,10 @@ uint32_t Stepper::block_phase_isr() {
2109 2111
         interval = ticks_nominal;
2110 2112
       }
2111 2113
 
2112
-      /* Adjust Laser Power - Cruise
2114
+      /**
2115
+       * Adjust Laser Power - Cruise
2113 2116
        * power - direct or floor adjusted active laser power.
2114 2117
        */
2115
-
2116 2118
       #if ENABLED(LASER_POWER_TRAP)
2117 2119
         if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
2118 2120
           if (step_events_completed + 1 == accelerate_until) {
@@ -2130,7 +2132,7 @@ uint32_t Stepper::block_phase_isr() {
2130 2132
     }
2131 2133
 
2132 2134
     #if ENABLED(LASER_FEATURE)
2133
-      /*
2135
+      /**
2134 2136
        * CUTTER_MODE_DYNAMIC is experimental and developing.
2135 2137
        * Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute.
2136 2138
        * TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers.
@@ -2147,9 +2149,8 @@ uint32_t Stepper::block_phase_isr() {
2147 2149
   }
2148 2150
   else { // !current_block
2149 2151
     #if ENABLED(LASER_FEATURE)
2150
-      if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
2152
+      if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC)
2151 2153
         cutter.apply_power(0);  // No movement in dynamic mode so turn Laser off
2152
-      }
2153 2154
     #endif
2154 2155
   }
2155 2156
 

Chargement…
Annuler
Enregistrer