Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
295e752
Adaptive First Layer Z offset
narno2202 Feb 25, 2026
86169d0
Change logic
narno2202 Feb 26, 2026
94045b9
Change again detection method
narno2202 Feb 27, 2026
446bd25
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Feb 28, 2026
7423447
Remove unused include
narno2202 Mar 1, 2026
dab5752
Ensure delta is not applied when leveling is in progress
narno2202 Mar 3, 2026
2277620
Finally apply Z adjust in the first layer block
narno2202 Mar 3, 2026
ab8a24f
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Mar 18, 2026
18a0527
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Mar 26, 2026
3e89db3
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Mar 29, 2026
745c7c6
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Apr 5, 2026
9eab82e
Typo
narno2202 May 3, 2026
210f073
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 May 6, 2026
fea4693
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 May 25, 2026
928a102
Merge remote-tracking branch 'upstream/HEAD' into Auto_First_Layer
narno2202 Jun 1, 2026
74a38a3
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Jun 7, 2026
d3e4f3e
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Jun 9, 2026
20c1993
Add a safety margin of 0.05mm to aflza_delta
narno2202 Jun 12, 2026
3e09c85
Fix margin
narno2202 Jun 12, 2026
4d8b843
Fix Orcaslicer
narno2202 Jun 13, 2026
3e9cb74
Initial cleanup review
thinkyhead Jun 26, 2026
1d85f2c
Merge branch 'bugfix-2.1.x' into pr/28460
thinkyhead Jun 26, 2026
6810317
adjustments
thinkyhead Jun 26, 2026
4ef873d
Remove FT_MOTION conditional for planner synchronization
narno2202 Jun 26, 2026
98782e8
Fix comments in M429
narno2202 Jun 28, 2026
e9512d6
Add documentation
narno2202 Jun 29, 2026
e5f8f1f
Fix doc
narno2202 Jun 29, 2026
df75a06
Doc should be ok
narno2202 Jun 30, 2026
7c3d980
Merge branch 'MarlinFirmware:bugfix-2.1.x' into Auto_First_Layer
narno2202 Jul 1, 2026
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
14 changes: 14 additions & 0 deletions Marlin/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,20 @@
//#define W_MIN_POS 0
//#define W_MAX_POS 50

/**
* Automatic first-layer Z-offset adjustment
*
* When enabled, Marlin will automatically apply a one-time live Z-offset correction.
* Z-offset correction is the difference between calibrated layer height and the first layer height specified in slicer.
* For example, if the first layer height is 0.2mm but the calibrated layer height is 0.3mm, calculated offset is -0.1mm,
* which means the nozzle needs to be 0.1mm closer to the bed.
*/
//#define AUTO_FIRST_LAYER_Z_ADJUST
#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
#define CALIBRATED_FIRST_LAYER_Z_HEIGHT 0.3f // (mm) Pre-calibrated assumed first layer height
//#define DEFAULT_SLICER UNKNOWN // :[ 'UNKNOWN', 'ORCA', 'PRUSA' ]
#endif

/**
* Software Endstops
*
Expand Down
1 change: 0 additions & 1 deletion Marlin/src/feature/runout.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ class TFilamentMonitor : public FilamentMonitorBase {

filament_ran_out = true;
event_filament_runout(extruder);
planner.synchronize();
}

// Reset after a filament runout or upon resuming a job
Expand Down
4 changes: 4 additions & 0 deletions Marlin/src/gcode/gcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,10 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) {
case 428: M428(); break; // M428: Apply position to home_offset
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
case 429: M429(); break; // M429: Set calibrated first layer Z height
#endif

#if HAS_POWER_MONITOR
case 430: M430(); break; // M430: Read the system current (A), voltage (V), and power (W)
#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 @@ -248,6 +248,7 @@
* M422 - Set Z Stepper automatic alignment position using probe. X<units> Y<units> A<axis> (Requires Z_STEPPER_AUTO_ALIGN)
* M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE)
* M428 - Set the home_offset based on the position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA)
* M429 - Set calibrated first layer Z height for AUTO_FIRST_LAYER_Z_ADJUST. (Requires AUTO_FIRST_LAYER_Z_ADJUST)
* M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE)
* M485 - Send RS485 packets (Requires RS485_SERIAL_PORT)
* M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS)
Expand Down Expand Up @@ -1107,6 +1108,11 @@ class GcodeSuite {
static void M428();
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
static void M429();
static void M429_report(const bool forReplay=true);
#endif

#if HAS_POWER_MONITOR
static void M430();
#endif
Expand Down
87 changes: 87 additions & 0 deletions Marlin/src/gcode/geometry/M429.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Marlin 3D Printer Firmware
* Copyright (c) 2026 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/>.
*
*/

#include "../../inc/MarlinConfig.h"

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)

#include "../gcode.h"
#include "../parser.h"

void say_aflza() {
const bool active = parser.aflza_active;
SERIAL_ECHOLN(F("Auto First Layer Z Adjust "), active ? F("A") : F("Dea"), F("ctivated"));
SERIAL_ECHOLN(F("Configured Layer Height "), parser.calibrated_first_layer_height, F(" mm"));
SERIAL_ECHOLN(parser.z_hop ? F(" Orca") : F(" Prusa"), F("Slicer"));
}

void GcodeSuite::M429_report(const bool forReplay) {
SERIAL_ECHOLN(F("Auto First Layer Z Adjust "), parser.aflza_active ? F("A") : F("Dea"), F("ctivated"));
SERIAL_ECHOLN(F("Configured Layer Height "), parser.calibrated_first_layer_height, F(" mm"));
SERIAL_ECHOLN(parser.z_hop ? F(" Orca") : F(" Prusa"), F("Slicer"));
}

/**
* M429: Auto-adjust first layer nozzle height.
*
* Parameters:
* S<bool> Activate/Deactivate the Auto-Adjust First Layer Z feature.
* H<float> Set the calibrated first layer height to <val> mm (0.0 .. 1.0)
* O<bool> Set the slicer type : true OrcaSlicer and clones, false PrusaSlicer and clones
*
* Examples:
* M429 S1 : Activate the Auto First Layer Z Adjust feature
* M429 S0 : Deactivate the Auto First Layer Z Adjust feature
* M429 H0.3 : Set the calibrated first layer height to <val> mm (0.0 .. 1.0)
*/
void GcodeSuite::M429() {
if (!parser.seen("SHO")) { say_aflza(); return; }

// S<bool> Activate/Deactivate
if (parser.seen('S')) {
const bool on = parser.value_bool();
parser.aflza_active = on;
SERIAL_ECHOLNPGM("Auto First Layer Z Adjust ", on ? F("A") : F("Dea"), "ctivated");
}

// H<float> Set Calibrated Height
if (parser.seen('H')) {
const float height = parser.value_float();
if (WITHIN(height, 0.0f, 1.0f)) {
parser.calibrated_first_layer_height = height;
SERIAL_ECHOLNPGM("Calibrated First Layer Height set to ", height, " mm");
}
else {
SERIAL_ECHOLNPGM("?Invalid Calibrated First Layer Height [H]. (0.0 .. 1.0 mm)");
}
}

// O<bool> Set Slicer Type
if (parser.seenval('O')) {
const slicer_id_t slicer_id = parser.value_ushort(); // 0 Prusa, 1 Orca, ...
parser.slicer_type = slicer_id;
parser.z_hop = (slicer_id == SlicerType::ORCA);
SERIAL_ECHOLN(parser.z_hop ? F(" Orca") : F(" Prusa"), F("Slicer"));
}
}

#endif // AUTO_FIRST_LAYER_Z_ADJUST
42 changes: 42 additions & 0 deletions Marlin/src/gcode/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ char *GCodeParser::command_ptr,
char GCodeParser::command_letter;
uint16_t GCodeParser::codenum;

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
bool GCodeParser::first_layer_detected = false;
bool GCodeParser::aflza_active = false;
bool GCodeParser::aflza_applied = false;
float GCodeParser::aflza_delta = 0.0f;
float GCodeParser::calibrated_first_layer_height;
slicer_id_t GCodeParser::slicer_type = SLICER_UNKNOWN;
bool GCodeParser::z_hop = true;
#endif

#if USE_GCODE_SUBCODES
uint8_t GCodeParser::subcode;
#endif
Expand Down Expand Up @@ -368,6 +378,38 @@ void GCodeParser::parse(char *p) {
while (*p == ' ') p++; // Skip over all spaces
}
}

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)

#define AFLZA_SAFETY_MARGIN 0.05f // Safety margin to ensure the first layer is not too squished

if (aflza_active && !first_layer_detected && !aflza_applied) {
// Filter to search the first occurrence of Z alone
if (seen("XYE")) return;

// Z alone, test for G0 or G1
if (letter == 'G' && (codenum == 0 || codenum == 1)) {
// Consider the second G-code with Z alone (with Orca Slicer)
if (z_hop) { z_hop = false; return; }

const float z = value_linear_units();

// Consider the maximum layer height of 0.80mm (1.00mm nozzle)
if (z < 0.80f) {
aflza_delta = z - calibrated_first_layer_height;

// Don't apply if delta is lower or equal to the margin
if (ABS(aflza_delta) <= AFLZA_SAFETY_MARGIN)
aflza_applied = true;
else {
aflza_delta += AFLZA_SAFETY_MARGIN;
first_layer_detected = true;
}
}
}
}

#endif // AUTO_FIRST_LAYER_Z_ADJUST
}

#if ENABLED(CNC_COORDINATE_SYSTEMS)
Expand Down
15 changes: 15 additions & 0 deletions Marlin/src/gcode/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
typedef enum : uint8_t { LINEARUNIT_MM, LINEARUNIT_INCH } LinearUnit;
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
enum SlicerType : int8_t { UNKNOWN = -1, PRUSA, ORCA };
typedef enum SlicerType slicer_id_t;
#endif

/**
* G-Code parser
*
Expand Down Expand Up @@ -89,6 +94,16 @@ class GCodeParser {
static uint8_t subcode; // .1
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
static bool first_layer_detected,
aflza_active, // If true AFLZA active for all the print until the ornter is shutdwon and EEPROM datas loaded
aflza_applied; // If the correction had been applied
static float aflza_delta; // The Z offset to apply to the first layer, calculated as the difference between the detected first layer Z and the calibrated first layer height
static float calibrated_first_layer_height; // The first layer height as determined by calibration, used to calculate the Z offset
static slicer_id_t slicer_type; // Slicer family Orca or Prusa
static bool z_hop;
#endif

#if ENABLED(GCODE_MOTION_MODES)
static int16_t motion_mode_codenum;
#if USE_GCODE_SUBCODES
Expand Down
1 change: 1 addition & 0 deletions Marlin/src/inc/Conditionals-1-axes.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@
#undef Z_SAFE_HOMING
#undef MIN_SOFTWARE_ENDSTOP_Z
#undef MAX_SOFTWARE_ENDSTOP_Z
#undef AUTO_FIRST_LAYER_Z_ADJUST
#endif

#if !HAS_I_AXIS
Expand Down
5 changes: 5 additions & 0 deletions Marlin/src/lcd/language/language_en.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ namespace LanguageNarrow_en {
LSTR MSG_LEVEL_BED_DONE = _UxGT("Leveling Done!");
LSTR MSG_SET_HOME_OFFSETS = _UxGT("Set Home Offsets");
LSTR MSG_HOME_OFFSETS_APPLIED = _UxGT("Offsets Applied");
LSTR MSG_AUTO_FIRST_LAYER_Z_ADJUST = _UxGT("Adapt. First Layer Z Offset");
LSTR MSG_AFLZA_SLICER = _UxGT("Slicer");
LSTR MSG_AFLZA_UNKNOWN = _UxGT("Unknown");
LSTR MSG_AFLZA_ORCA = _UxGT("Orca");
LSTR MSG_AFLZA_PRUSA = _UxGT("Prusa");
LSTR MSG_PREHEAT_1 = _UxGT("Preheat ") PREHEAT_1_LABEL;
LSTR MSG_PREHEAT_1_H = _UxGT("Preheat ") PREHEAT_1_LABEL " ~";
LSTR MSG_PREHEAT_1_END = _UxGT("Preheat ") PREHEAT_1_LABEL _UxGT(" End");
Expand Down
33 changes: 33 additions & 0 deletions Marlin/src/lcd/menu/menu_advanced.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,32 @@ void menu_backlash();

#endif // EDITABLE_STEPS_PER_UNIT

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)

FSTR_P get_slicer_name(const slicer_id_t slicer) {
switch (slicer) {
default:
return GET_TEXT_F(MSG_AFLZA_UNKNOWN);
case SlicerType::ORCA:
return GET_TEXT_F(MSG_AFLZA_ORCA);
case SlicerType::PRUSA:
return GET_TEXT_F(MSG_AFLZA_PRUSA);
}
}

// TODO: Select from list with active item pre-selected on entry
void menu_aflza_slicer() {
START_MENU();
BACK_ITEM(MSG_ADVANCED_SETTINGS);
if (parser.slicer_type == SlicerType::ORCA)
ACTION_ITEM(MSG_AFLZA_PRUSA, []{ parser.slicer_type = SlicerType::ORCA; parser.z_hop = true; });
else
ACTION_ITEM(MSG_AFLZA_ORCA, []{ parser.slicer_type = SlicerType::PRUSA; parser.z_hop = false; });
END_MENU();
}

#endif // AUTO_FIRST_LAYER_Z_ADJUST

void menu_advanced_settings() {
#if ANY(POLARGRAPH, SHAPING_MENU, HAS_BED_PROBE, EDITABLE_STEPS_PER_UNIT)
const bool is_busy = marlin.printer_busy();
Expand Down Expand Up @@ -713,6 +739,13 @@ void menu_advanced_settings() {
ACTION_ITEM(MSG_SET_HOME_OFFSETS, []{ queue.inject(F("M428")); ui.return_to_status(); });
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
// M429 - Set adaptive first layer Z offset
EDIT_ITEM(bool, MSG_AUTO_FIRST_LAYER_Z_ADJUST, &parser.aflza_active);
EDIT_ITEM(float32, MSG_AUTO_FIRST_LAYER_Z_ADJUST, &parser.calibrated_first_layer_height, 0.1f, 1.0f);
SUBMENU_S(get_slicer_name(parser.slicer_type), MSG_AFLZA_SLICER, menu_aflza_slicer);
#endif

// M203 / M205 - Feedrate items
SUBMENU(MSG_MAX_SPEED, menu_advanced_velocity);

Expand Down
15 changes: 14 additions & 1 deletion Marlin/src/module/planner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1987,8 +1987,21 @@ bool Planner::_populate_block(
const float dy = steps_dist.y * mm_per_step[Y_AXIS]; // Axis Y or Tower B Steps
#endif
#if HAS_Z_AXIS
const float dz = steps_dist.z * mm_per_step[Z_AXIS]; // Axis Z or Tower C Steps
float dz = steps_dist.z * mm_per_step[Z_AXIS];
#endif

#if ENABLED(AUTO_FIRST_LAYER_Z_ADJUST)
if (parser.first_layer_detected && !parser.aflza_applied) {
// For the first layer only, use the adjusted Z position to calculate the distance
// Keep aflza_active true otherwise the configuration is lost until you restart the printer
dz += parser.aflza_delta;
parser.first_layer_detected = false; // Only apply the adjustment for the first layer
parser.aflza_applied = true;
if (parser.slicer_type == SlicerType::ORCA) // Reset z_hop to true if Orca for future prints
parser.z_hop = true;
}
#endif

#if CORE_IS_XY
XYZ_CODE(
dist_mm.a = dx + dy,
Expand Down
Loading
Loading