diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index b2e29a1c9473..234fc02fd802 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -2519,6 +2519,11 @@
//
//#define INCH_MODE_SUPPORT
+//
+// G93/G94 Feedrate mode support
+//
+//#define FEEDRATE_MODE_SUPPORT
+
//
// M149 Set temperature units support
//
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 3aa430db20ec..06cfd41c7268 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -1231,7 +1231,7 @@ void setup() {
#endif
#endif
- #if ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN)
+ #if ENABLED(FREEZE_FEATURE)
SETUP_LOG("FREEZE_PIN");
#if FREEZE_STATE
SET_INPUT_PULLDOWN(FREEZE_PIN);
@@ -1334,7 +1334,10 @@ void setup() {
#endif
SERIAL_ECHO_MSG(" Compiled: " __DATE__);
SERIAL_ECHO_MSG(STR_FREE_MEMORY, hal.freeMemory(), STR_PLANNER_BUFFER_BYTES, sizeof(block_t) * (BLOCK_BUFFER_SIZE));
-
+
+ #if HAS_ROTATIONAL_AXES
+ parser.cartes_move = true;
+ #endif
// Some HAL need precise delay adjustment
calibrate_delay_loop();
diff --git a/Marlin/src/feature/bedlevel/abl/bbl.cpp b/Marlin/src/feature/bedlevel/abl/bbl.cpp
index 0e8c4a273ef9..545af97f8eab 100644
--- a/Marlin/src/feature/bedlevel/abl/bbl.cpp
+++ b/Marlin/src/feature/bedlevel/abl/bbl.cpp
@@ -78,7 +78,7 @@ void LevelingBilinear::extrapolate_one_point(const uint8_t x, const uint8_t y, c
const float a = 2 * a1 - a2, b = 2 * b1 - b2, c = 2 * c1 - c2;
// Take the average instead of the median
- z_values[x][y] = (a + b + c) / 3.0;
+ z_values[x][y] = (a + b + c) / 3.0f;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
// Median is robust (ignores outliers).
diff --git a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
index 93e50b649857..90bb162849f1 100644
--- a/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
+++ b/Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
@@ -71,14 +71,10 @@
void mesh_bed_leveling::line_to_destination(const feedRate_t scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) {
// Get current and destination cells for this line
xy_uint8_t scel = cell_indexes(motion.position), ecel = cell_indexes(motion.destination);
- NOMORE(scel.x, GRID_MAX_CELLS_X - 1);
- NOMORE(scel.y, GRID_MAX_CELLS_Y - 1);
- NOMORE(ecel.x, GRID_MAX_CELLS_X - 1);
- NOMORE(ecel.y, GRID_MAX_CELLS_Y - 1);
// Start and end in the same cell? No split needed.
if (scel == ecel) {
- motion.position = motion.destination;
+ motion.position = motion.destination;
motion.goto_current_position(scaled_fr_mm_s);
return;
}
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
index 40c308e39a7d..851ad4df99a9 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
@@ -357,23 +357,39 @@
const xyze_pos_t total = motion.destination - motion.position;
- const float cart_xy_mm_2 = HYPOT2(total.x, total.y),
- cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance
+ // If the move is only in Z/E don't split up the move
+ if (!total.x && !total.y) {
+ planner.buffer_line(motion.destination, scaled_fr_mm_s);
+ return false; // caller will update current_position
+ }
+ #if HAS_ROTATIONAL_AXES
+ bool cartes_move = true;
+ #endif
+ float cartesian_mm = motion.get_move_distance(total OPTARG(HAS_ROTATIONAL_AXES, cartes_move));
+
+ // If the move is very short, check the E move distance
+ TERN_(HAS_EXTRUDERS, if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = ABS(total.e));
+
+ // No E move either? Game over.
+ if (UNEAR_ZERO(cartesian_mm)) return true;
#if IS_KINEMATIC
- const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
+ // Minimum number of seconds to move the given distance
+ const float seconds = cartesian_mm / scaled_fr_mm_s;
+
uint16_t segments = LROUND(segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
- seglimit = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
+ seglimit = LROUND(cartesian_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
+
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
#else
- uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length
+ uint16_t segments = LROUND(cartesian_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length
#endif
NOLESS(segments, 1U); // Must have at least one segment
const float inv_segments = 1.0f / segments; // Reciprocal to save calculation
// Add hints to help optimize the move
- PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment
+ PlannerHints hints(cartesian_mm * inv_segments); // Length of each segment
#if ENABLED(FEEDRATE_SCALING)
hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
#endif
diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp
index 8383dd1a8de5..54ddd90f9212 100644
--- a/Marlin/src/gcode/bedlevel/G42.cpp
+++ b/Marlin/src/gcode/bedlevel/G42.cpp
@@ -54,6 +54,8 @@ void GcodeSuite::G42() {
return;
}
+ TERN_(FEEDRATE_MODE_SUPPORT, parser.linear_motion_gcode = true);
+
// Move to motion.position, as modified by I, J, P parameters
motion.destination = motion.position;
@@ -67,7 +69,7 @@ void GcodeSuite::G42() {
}
#endif
- const feedRate_t fval = parser.linearval('F'),
+ const feedRate_t fval = parser.feedrateval('F'),
fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f);
// SCARA kinematic has "safe" XY raw moves
@@ -76,6 +78,8 @@ void GcodeSuite::G42() {
#else
motion.prepare_internal_move_to_destination(fr_mm_s);
#endif
+
+ TERN_(FEEDRATE_MODE_SUPPORT, parser.linear_motion_gcode = false);
}
#endif // HAS_MESH
diff --git a/Marlin/src/gcode/feature/camera/M240.cpp b/Marlin/src/gcode/feature/camera/M240.cpp
index 7ce0a1934cac..bccae9268302 100644
--- a/Marlin/src/gcode/feature/camera/M240.cpp
+++ b/Marlin/src/gcode/feature/camera/M240.cpp
@@ -134,11 +134,11 @@ void GcodeSuite::M240() {
#ifdef PHOTO_RETRACT_MM
const float rval = parser.linearval('R', _PHOTO_RETRACT_MM);
- const feedRate_t sval = parser.feedrateval('S', TERN(ADVANCED_PAUSE_FEATURE, PAUSE_PARK_RETRACT_FEEDRATE, TERN(FWRETRACT, RETRACT_FEEDRATE, 45)));
+ const feedRate_t sval = MMM_TO_MMS(parser.feedrateval('S', TERN(ADVANCED_PAUSE_FEATURE, PAUSE_PARK_RETRACT_FEEDRATE, TERN(FWRETRACT, RETRACT_FEEDRATE, 45))));
e_move_m240(-rval, sval);
#endif
- feedRate_t fr_mm_s = parser.feedrateval('F');
+ feedRate_t fr_mm_s = MMM_TO_MMS(parser.feedrateval('F'));
if (fr_mm_s) NOLESS(fr_mm_s, 10.0f);
constexpr xyz_pos_t photo_position = PHOTO_POSITION;
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index e84b367e7af4..32ee47e3c0aa 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -199,6 +199,18 @@ void GcodeSuite::get_destination_from_command() {
motion.destination.e = motion.position.e;
#endif
+ #if HAS_ROTATIONAL_AXES || IS_KINEMATIC || HAS_LEVELING || ENABLED(FEEDRATE_MODE_SUPPORT)
+ const xyze_pos_t displacement = motion.destination - motion.position;
+
+ parser.cartesian_mm = motion.get_move_distance(displacement OPTARG(HAS_ROTATIONAL_AXES, parser.cartes_move));
+
+ #if HAS_EXTRUDERS
+ if (NEAR_ZERO(parser.cartesian_mm)) {
+ parser.cartesian_mm = ABS(displacement.e);
+ }
+ #endif
+ #endif
+
#if ENABLED(POWER_LOSS_RECOVERY) && !PIN_EXISTS(POWER_LOSS)
// Only update power loss recovery on moves with E
if (recovery.enabled && card.isStillPrinting() && seen.e && (seen.x || seen.y))
@@ -206,7 +218,7 @@ void GcodeSuite::get_destination_from_command() {
#endif
if (parser.floatval('F') > 0) {
- const float fr_mm_min = parser.value_linear_units();
+ const float fr_mm_min = parser.value_feedrate();
motion.feedrate_mm_s = MMM_TO_MMS(fr_mm_min);
// Update the cutter feed rate for use by M4 I set inline moves.
TERN_(LASER_FEATURE, cutter.feedrate_mm_m = fr_mm_min);
@@ -468,6 +480,11 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) {
case 92: G92(); break; // G92: Set current axis position(s)
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ case 93: G93(); break; // G93: Set feedrate mode to inverse time
+ case 94: G94(); break; // G94: Set feedrate mode to length units per minute
+ #endif
+
#if ENABLED(CALIBRATION_GCODE)
case 425: G425(); break; // G425: Perform calibration with calibration cube
#endif
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index d80305fb7556..638afe513167 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -649,6 +649,12 @@ class GcodeSuite {
static void G92();
+
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ static void G93();
+ static void G94();
+ #endif
+
#if ENABLED(CALIBRATION_GCODE)
static void G425();
#endif
diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp
index 1106ce0acc13..8b7c8e647d93 100644
--- a/Marlin/src/gcode/motion/G0_G1.cpp
+++ b/Marlin/src/gcode/motion/G0_G1.cpp
@@ -54,17 +54,32 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
#ifdef G0_FEEDRATE
feedRate_t old_feedrate;
#if ENABLED(VARIABLE_G0_FEEDRATE)
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ parser.linear_motion_gcode = true;
+ #endif
if (fast_move) {
old_feedrate = motion.feedrate_mm_s; // Back up the (old) motion mode feedrate
motion.feedrate_mm_s = fast_move_feedrate; // Get G0 feedrate from last usage
}
+ #elif ENABLED(FEEDRATE_MODE_SUPPORT)
+ if (fast_move) {
+ parser.linear_motion_gcode = false;
+ }
+ else {
+ parser.linear_motion_gcode = true;
+ }
#endif
+ #elif ENABLED(FEEDRATE_MODE_SUPPORT)
+ parser.linear_motion_gcode = true;
#endif
get_destination_from_command(); // Get X Y [Z[I[J[K]]]] [E] F (and set cutter power)
#ifdef G0_FEEDRATE
if (fast_move) {
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ parser.linear_motion_gcode = false;
+ #endif
#if ENABLED(VARIABLE_G0_FEEDRATE)
fast_move_feedrate = motion.feedrate_mm_s; // Save feedrate for the next G0
#else
@@ -104,6 +119,10 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
if (fast_move) motion.feedrate_mm_s = old_feedrate;
#endif
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ parser.linear_motion_gcode = false;
+ #endif
+
#if ENABLED(NANODLP_Z_SYNC)
#if ENABLED(NANODLP_ALL_AXIS)
#define _MOVE_SYNC parser.seenval('X') || parser.seenval('Y') || parser.seenval('Z') // For any move wait and output sync message
diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp
index 384a90fe6d09..af0812d2806e 100644
--- a/Marlin/src/gcode/motion/G2_G3.cpp
+++ b/Marlin/src/gcode/motion/G2_G3.cpp
@@ -235,7 +235,9 @@ void plan_arc(
// Add hints to help optimize the move
PlannerHints hints;
- #if ENABLED(FEEDRATE_SCALING)
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ hints.inv_duration = segments * (parser.inverse_time_enabled ? scaled_fr_mm_s : (scaled_fr_mm_s / flat_mm));
+ #elif ENABLED(FEEDRATE_SCALING)
hints.inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
#endif
@@ -426,7 +428,10 @@ void GcodeSuite::G2_G3(const bool clockwise) {
if (motion.gcode_motion_ignored()) return;
TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(M_RUNNING));
-
+ #if HAS_ROTATIONAL_AXES || IS_KINEMATIC || HAS_LEVELING || ENABLED(FEEDRATE_MODE_SUPPORT)
+ parser.linear_motion_gcode = false;
+ #endif
+
#if ENABLED(SF_ARC_FIX)
const bool relative_mode_backup = motion.relative_mode;
motion.relative_mode = true;
diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp
index b692c818aae6..760aa9324d03 100644
--- a/Marlin/src/gcode/parser.cpp
+++ b/Marlin/src/gcode/parser.cpp
@@ -37,6 +37,18 @@ bool GCodeParser::volumetric_enabled;
float GCodeParser::linear_unit_factor, GCodeParser::volumetric_unit_factor;
#endif
+#if HAS_ROTATIONAL_AXES || IS_KINEMATIC || HAS_LEVELING || ENABLED(FEEDRATE_MODE_SUPPORT)
+ bool GCodeParser::linear_motion_gcode;
+ float GCodeParser::cartesian_mm;
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ bool GCodeParser::inverse_time_enabled;
+ #endif
+ #if HAS_ROTATIONAL_AXES
+ bool GCodeParser::cartes_move;
+ #endif
+
+#endif
+
#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
TempUnit GCodeParser::input_temp_units = TEMPUNIT_C;
#endif
diff --git a/Marlin/src/gcode/parser.h b/Marlin/src/gcode/parser.h
index 71cad6857dce..440d8bf38063 100644
--- a/Marlin/src/gcode/parser.h
+++ b/Marlin/src/gcode/parser.h
@@ -76,6 +76,17 @@ class GCodeParser {
static float linear_unit_factor, volumetric_unit_factor;
#endif
+ #if HAS_ROTATIONAL_AXES || IS_KINEMATIC || HAS_LEVELING || ENABLED(FEEDRATE_MODE_SUPPORT)
+ static float cartesian_mm;
+ static bool linear_motion_gcode;
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ static bool inverse_time_enabled;
+ #endif
+ #if HAS_ROTATIONAL_AXES
+ static bool cartes_move;
+ #endif
+ #endif
+
#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
static TempUnit input_temp_units;
#endif
@@ -415,7 +426,18 @@ class GCodeParser {
#endif // !TEMPERATURE_UNITS_SUPPORT
- static feedRate_t value_feedrate() { return MMM_TO_MMS(value_linear_units()); }
+ static feedRate_t value_feedrate() {
+ #if HAS_ROTATIONAL_AXES || ENABLED(FEEDRATE_MODE_SUPPORT)
+ float fr = ((TERN0(FEEDRATE_MODE_SUPPORT, inverse_time_enabled && linear_motion_gcode)) || TERN0(HAS_ROTATIONAL_AXES, (!cartes_move))) ? value_float() : value_linear_units();
+ #if ENABLED(FEEDRATE_MODE_SUPPORT)
+ if (inverse_time_enabled && linear_motion_gcode)
+ fr *= cartesian_mm;
+ #endif
+ return fr;
+ #else
+ return value_linear_units();
+ #endif
+ }
void unknown_command_warning();
diff --git a/Marlin/src/gcode/units/G93_G94.cpp b/Marlin/src/gcode/units/G93_G94.cpp
new file mode 100644
index 000000000000..ee98be274dde
--- /dev/null
+++ b/Marlin/src/gcode/units/G93_G94.cpp
@@ -0,0 +1,47 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+/**
+ * @file G93_G94.cpp
+ * @author DerAndere
+ * @brief G93 (inverse time mode) and G94 (units per minute feedrate mode).
+ *
+ * Copyright 2025 DerAndere
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(FEEDRATE_MODE_SUPPORT)
+
+#include "../gcode.h"
+
+/**
+ * G93: Set feedrate mode to inverse time
+ */
+void GcodeSuite::G93() { parser.inverse_time_enabled = true; }
+
+/**
+ * G94: Set feedrate mode to length units per minute
+ */
+void GcodeSuite::G94() { parser.inverse_time_enabled = false; }
+
+#endif // FEEDRATE_MODE_SUPPORT
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 0e96c355f5a0..4611c3953195 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -581,10 +581,8 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
*/
#if ENABLED(SOFT_FEED_HOLD) && !defined(FREEZE_JERK)
#error "SOFT_FEED_HOLD requires FREEZE_JERK."
-#elif ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN) && !(defined(FREEZE_PIN) && defined(FREEZE_STATE))
+#elif ENABLED(FREEZE_FEATURE) && !(defined(FREEZE_PIN) && defined(FREEZE_STATE))
#error "FREEZE_FEATURE requires FREEZE_PIN and FREEZE_STATE."
-#elif ENABLED(NO_FREEZE_PIN) && !(defined(REALTIME_REPORTING_COMMANDS))
- #error "NO_FREEZE_PIN requires REALTIME_REPORTING_COMMANDS."
#endif
/**
@@ -875,6 +873,13 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
#endif
#endif
+/**
+ * Feedrate mode requirements
+ */
+#if ALL(FEEDRATE_MODE_SUPPORT, BEZIER_CURVE_SUPPORT)
+ #error "FEEDRATE_MODE_SUPPORT is currently incompatible with BEZIER_CURVE_SUPPORT."
+#endif
+
/**
* Special tool-changing options
*/
diff --git a/Marlin/src/lcd/marlinui.cpp b/Marlin/src/lcd/marlinui.cpp
index f475b52efc31..78718bce9ad9 100644
--- a/Marlin/src/lcd/marlinui.cpp
+++ b/Marlin/src/lcd/marlinui.cpp
@@ -857,13 +857,6 @@ void MarlinUI::init() {
const feedRate_t fr_mm_s = (axis < LOGICAL_AXES) ? manual_feedrate_mm_s[axis] : PLANNER_XY_FEEDRATE_MM_S;
- /**
- * For a rotational axis apply the "inch" to "mm" conversion factor. This mimics behaviour of the G-code G1
- * (see get_destination_from_command). For moves involving only rotational axes, the planner will convert
- * back to the feedrate in degrees-per-time unit.
- */
- const feedRate_t fr = parser.axis_is_rotational(axis) && parser.using_inch_units() ? IN_TO_MM(fr_mm_s) : fr_mm_s;
-
#if IS_KINEMATIC
#if HAS_MULTI_EXTRUDER
@@ -890,13 +883,13 @@ void MarlinUI::init() {
// previous invocation is being blocked. Modifications to offset shouldn't be made while
// processing is true or the planner will get out of sync.
processing = true;
- motion.prepare_internal_move_to_destination(fr); // will set motion.position from destination
+ motion.prepare_internal_move_to_destination(fr_mm_s); // will set current_position from destination
processing = false;
#else
- // For Cartesian / Core motion simply move to the motion.position
- planner.buffer_line(motion.position, fr,
+ // For Cartesian / Core motion simply move to the current_position
+ planner.buffer_line(motion.position, fr_mm_s,
TERN_(MULTI_E_MANUAL, axis == E_AXIS ? e_index :) motion.extruder
);
diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp
index b401090d8722..519d0712514b 100644
--- a/Marlin/src/module/motion.cpp
+++ b/Marlin/src/module/motion.cpp
@@ -1544,16 +1544,23 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
#endif
#if HAS_ROTATIONAL_AXES
+
if (UNEAR_ZERO(distance_sqr)) {
// Move involves no linear axes. Calculate angular distance in accordance with LinuxCNC
distance_sqr = ROTATIONAL_AXIS_GANG(sq(diff.i), + sq(diff.j), + sq(diff.k), + sq(diff.u), + sq(diff.v), + sq(diff.w));
+ if (!UNEAR_ZERO(distance_sqr)) {
+ // Move involves rotational axes, not just the extruder
+ is_cartesian_move = false;
+ }
+ else {
+ // Move involves just the extruder
+ is_cartesian_move = true;
+ }
}
- if (!UNEAR_ZERO(distance_sqr)) {
- // Move involves rotational axes, not just the extruder
- is_cartesian_move = false;
+ else {
+ is_cartesian_move = true;
}
#endif
-
#endif
return SQRT(distance_sqr);
@@ -1618,12 +1625,12 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
// Fail if attempting move outside printable radius
if (!can_reach(destination)) return true;
-
- // Get the linear distance in XYZ
#if HAS_ROTATIONAL_AXES
- bool cartes_move = true;
+ bool cartesian_move = true;
#endif
- float cartesian_mm = get_move_distance(diff OPTARG(HAS_ROTATIONAL_AXES, cartes_move));
+ float cartesian_mm = parser.cartesian_mm;
+ if (!parser.linear_motion_gcode)
+ cartesian_mm = motion.get_move_distance(diff OPTARG(HAS_ROTATIONAL_AXES, cartesian_move));
// If the move is very short, check the E move distance
TERN_(HAS_EXTRUDERS, if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = ABS(diff.e));
@@ -1632,13 +1639,7 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
if (UNEAR_ZERO(cartesian_mm)) return true;
// Minimum number of seconds to move the given distance
- const float seconds = cartesian_mm / (
- #if ALL(HAS_ROTATIONAL_AXES, INCH_MODE_SUPPORT)
- cartes_move ? scaled_fr_mm_s : LINEAR_UNIT(scaled_fr_mm_s)
- #else
- scaled_fr_mm_s
- #endif
- );
+ const float seconds = cartesian_mm / scaled_fr_mm_s;
// The number of segments-per-second times the duration
// gives the number of segments
@@ -1660,9 +1661,9 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
// Add hints to help optimize the move
PlannerHints hints(cartesian_mm * inv_segments);
- TERN_(HAS_ROTATIONAL_AXES, hints.cartesian_move = cartes_move);
- TERN_(FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
-
+ #if ENABLED(FEEDRATE_SCALING)
+ hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
+ #endif
/*
SERIAL_ECHOPGM("mm=", cartesian_mm);
SERIAL_ECHOPGM(" seconds=", seconds);
@@ -1710,11 +1711,11 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
return;
}
- // Get the linear distance in XYZ
#if HAS_ROTATIONAL_AXES
- bool cartes_move = true;
+ bool cartesian_move = true;
#endif
- float cartesian_mm = get_move_distance(diff OPTARG(HAS_ROTATIONAL_AXES, cartes_move));
+ // Get the move distance
+ float cartesian_mm = get_move_distance(diff OPTARG(HAS_ROTATIONAL_AXES, cartesian_move));
// If the move is very short, check the E move distance
TERN_(HAS_EXTRUDERS, if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = ABS(diff.e));
@@ -1733,7 +1734,6 @@ float Motion::get_move_distance(const xyze_pos_t &diff OPTARG(HAS_ROTATIONAL_AXE
// Add hints to help optimize the move
PlannerHints hints(cartesian_mm * inv_segments);
- TERN_(HAS_ROTATIONAL_AXES, hints.cartesian_move = cartes_move);
TERN_(FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
//SERIAL_ECHOPGM("mm=", cartesian_mm);
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index 8f3815a3fcbc..a7021955517e 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -2022,7 +2022,7 @@ bool Planner::_populate_block(
TERN_(FT_MOTION, block->ext_distance_mm = dist_mm); // Store the distance for all axes in mm for this block
#if HAS_ROTATIONAL_AXES
- bool cartesian_move = hints.cartesian_move;
+ bool cartesian_move = true;
#endif
// Determine linear distance for block->millimeters
@@ -2172,11 +2172,12 @@ bool Planner::_populate_block(
}
#endif // HAS_EXTRUDERS
- if (esteps)
- NOLESS(fr_mm_s, settings.min_feedrate_mm_s);
- else
- NOLESS(fr_mm_s, settings.min_travel_feedrate_mm_s);
-
+ if (esteps) {
+ NOLESS(fr_mm_s, settings.min_feedrate_mm_s);
+ }
+ else {
+ NOLESS(fr_mm_s, settings.min_travel_feedrate_mm_s);
+ }
const float inverse_millimeters = 1.0f / block->millimeters; // Inverse millimeters to remove multiple divides
/**
@@ -2184,15 +2185,12 @@ bool Planner::_populate_block(
* EXAMPLE: At 120mm/s a 60mm move involving XYZ axes takes 0.5s. So this will give 2.0.
* EXAMPLE: At 120°/s a 60° move involving only rotational axes takes 0.5s. So this will give 2.0.
*/
- float inverse_secs = inverse_millimeters * (
- #if ALL(HAS_ROTATIONAL_AXES, INCH_MODE_SUPPORT)
- /**
- * Workaround for premature feedrate conversion
- * from in/s to mm/s by get_distance_from_command.
- */
- cartesian_move ? fr_mm_s : LINEAR_UNIT(fr_mm_s)
+
+ float inverse_secs = (
+ #if ANY(FEEDRATE_SCALING, FEEDRATE_MODE_SUPPORT)
+ hints.inv_duration ? : inverse_millimeters * fr_mm_s
#else
- fr_mm_s
+ inverse_millimeters * fr_mm_s
#endif
);
@@ -2202,7 +2200,7 @@ bool Planner::_populate_block(
// Slow down when the buffer starts to empty, rather than wait at the corner for a buffer refill
#if ANY(SLOWDOWN, HAS_WIRED_LCD) || defined(XY_FREQUENCY_LIMIT)
// Segment time in microseconds
- int32_t segment_time_us = LROUND(1000000.0f / inverse_secs);
+ int32_t segment_time_us = LROUND(1000000.0f * RECIPROCAL(inverse_secs));
#endif
#if ENABLED(SLOWDOWN)
@@ -2981,10 +2979,13 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const feedRate_t fr_mm_s
// Cartesian XYZ to kinematic ABC, stored in global 'delta'
inverse_kinematics(machine);
+ #if HAS_ROTATIONAL_AXES
+ bool cartesian_move = true;
+ #endif
// Provide known Cartesian length in the hints structure
PlannerHints ph = hints;
if (!hints.millimeters)
- ph.millimeters = motion.get_move_distance(xyze_pos_t(cart_dist_mm) OPTARG(HAS_ROTATIONAL_AXES, ph.cartesian_move));
+ ph.millimeters = motion.get_move_distance(xyze_pos_t(cart_dist_mm) OPTARG(HAS_ROTATIONAL_AXES, cartesian_move));
#if DISABLED(FEEDRATE_SCALING)
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index a9d4c37afacb..0af227778090 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -469,7 +469,7 @@ typedef struct PlannerSettings {
struct PlannerHints {
float millimeters = 0.0; // Move Length, if known, else 0.
- #if ENABLED(FEEDRATE_SCALING)
+ #if ANY(FEEDRATE_SCALING, FEEDRATE_MODE_SUPPORT)
float inv_duration = 0.0; // Reciprocal of the move duration, if known
#endif
#if ENABLED(HINTS_CURVE_RADIUS)
@@ -483,11 +483,6 @@ struct PlannerHints {
// would calculate if it knew the as-yet-unbuffered path
#endif
- #if HAS_ROTATIONAL_AXES
- bool cartesian_move = true; // True if linear motion of the tool centerpoint relative to the workpiece occurs.
- // False if no movement of the tool center point relative to the work piece occurs
- // (i.e. the tool rotates around the tool centerpoint)
- #endif
PlannerHints(const float mm=0.0f) : millimeters(mm) {}
};
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index b92d8981577b..c9c2e4e71bdb 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -3921,9 +3921,7 @@ void Stepper::report_positions() {
cutter.apply_power(frozen_last_laser_power); // Restore frozen laser power
#endif
- #if ENABLED(REALTIME_REPORTING_COMMANDS)
- set_and_report_grblstate(state ? M_HOLD : M_RUNNING);
- #endif
+ TERN_(FULL_REPORT_TO_HOST_FEATURE, motion.set_and_report_grblstate(state ? M_HOLD : M_RUNNING));
}
void Stepper::check_frozen_time(uint32_t &step_rate) {
diff --git a/ini/features.ini b/ini/features.ini
index 03a9bdbba8d1..fbbeacd7bb2d 100644
--- a/ini/features.ini
+++ b/ini/features.ini
@@ -368,6 +368,7 @@ HAS_TEMP_PROBE = build_src_filter=+
MPCTEMP = build_src_filter=+
INCH_MODE_SUPPORT = build_src_filter=+
+FEEDRATE_MODE_SUPPORT = build_src_filter=+
TEMPERATURE_UNITS_SUPPORT = build_src_filter=+
NEED_HEX_PRINT = build_src_filter=+
NEED_LSF = build_src_filter=+