Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Marlin/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,11 @@
//
//#define INCH_MODE_SUPPORT

//
// G93/G94 Feedrate mode support
//
//#define FEEDRATE_MODE_SUPPORT

//
// M149 Set temperature units support
//
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/feature/bedlevel/abl/bbl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 0 additions & 4 deletions Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@
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(current_position), ecel = cell_indexes(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) {
Expand Down
26 changes: 20 additions & 6 deletions Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,23 +357,37 @@

const xyze_pos_t total = destination - current_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(destination, scaled_fr_mm_s);
return false; // caller will update current_position
}
bool cartes_move = true;
float cartesian_mm = 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
Expand Down
6 changes: 5 additions & 1 deletion Marlin/src/gcode/bedlevel/G42.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ void GcodeSuite::G42() {
return;
}

TERN_(FEEDRATE_MODE_SUPPORT, parser.linear_motion_gcode = true);

// Move to current_position, as modified by I, J, P parameters
destination = current_position;

Expand All @@ -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
Expand All @@ -76,6 +78,8 @@ void GcodeSuite::G42() {
#else
prepare_internal_move_to_destination(fr_mm_s);
#endif

TERN_(FEEDRATE_MODE_SUPPORT, parser.linear_motion_gcode = false);
}

#endif // HAS_MESH
4 changes: 2 additions & 2 deletions Marlin/src/gcode/feature/camera/M240.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,26 @@ void GcodeSuite::get_destination_from_command() {
destination.e = current_position.e;
#endif

#if HAS_ROTATIONAL_AXES || IS_KINEMATIC || HAS_LEVELING || ENABLED(FEEDRATE_MODE_SUPPORT)
const xyze_pos_t displacement = destination - current_position;

parser.cartesian_mm = 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))
recovery.save();
#endif

if (parser.floatval('F') > 0) {
const float fr_mm_min = parser.value_linear_units();
const float fr_mm_min = parser.value_feedrate();
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);
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Marlin/src/gcode/gcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions Marlin/src/gcode/motion/G0_G1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,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 = feedrate_mm_s; // Back up the (old) motion mode feedrate
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 = feedrate_mm_s; // Save feedrate for the next G0
#else
Expand Down Expand Up @@ -107,6 +122,10 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
if (fast_move) 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
Expand Down
9 changes: 7 additions & 2 deletions Marlin/src/gcode/motion/G2_G3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -426,7 +428,10 @@ void GcodeSuite::G2_G3(const bool clockwise) {
if (!MOTION_CONDITIONS) return;

TERN_(FULL_REPORT_TO_HOST_FEATURE, 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 = relative_mode;
relative_mode = true;
Expand Down
12 changes: 12 additions & 0 deletions Marlin/src/gcode/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 23 additions & 1 deletion Marlin/src/gcode/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();

Expand Down
47 changes: 47 additions & 0 deletions Marlin/src/gcode/units/G93_G94.cpp
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
*/

/**
* @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
7 changes: 7 additions & 0 deletions Marlin/src/inc/SanityCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,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
*/
Expand Down
11 changes: 2 additions & 9 deletions Marlin/src/lcd/marlinui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -854,13 +854,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
Expand All @@ -887,13 +880,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;
prepare_internal_move_to_destination(fr); // will set current_position from destination
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 current_position
planner.buffer_line(current_position, fr,
planner.buffer_line(current_position, fr_mm_s,
TERN_(MULTI_E_MANUAL, axis == E_AXIS ? e_index :) active_extruder
);

Expand Down
Loading