From 65d53c13962cba7e5212426d4a434b83c7e44819 Mon Sep 17 00:00:00 2001 From: VEhystrix Date: Tue, 9 Sep 2025 18:48:47 +0200 Subject: [PATCH 1/6] added G29 P to populate the mesh for ABL_BILINEAR --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 33 +++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index e0cf28156bce..88a38e615ad7 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -242,11 +242,26 @@ G29_TYPE GcodeSuite::G29() { if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false, false); #endif + // P = populate grid with specified Z value + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + bool do_init = parser.seenval('P'); + float init_val = 0.0f; + if (do_init){ + init_val = RAW_Z_POSITION(parser.value_linear_units()); + if (!WITHIN(init_val, -10, 10)) { + SERIAL_ERROR_MSG("Bad initialization value"); + G29_RETURN(false, false); + } + } + #else + constexpr bool do_init = false; + #endif + // A = Abort manual probing // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), no_action = seenA || seenQ, - faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; + faux = (ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action) || do_init; // O = Don't level if leveling is already active if (!no_action && planner.leveling_active && parser.boolval('O')) { @@ -715,7 +730,11 @@ G29_TYPE GcodeSuite::G29() { char tmp_1[32]; // move to the start point of new line - abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + if (faux) + abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); + else + abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + // Go to the end of the row/column ... and back up by one // TODO: Why not just use... PR_INNER_VAR = inStop - inInc for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc); @@ -768,7 +787,10 @@ G29_TYPE GcodeSuite::G29() { #else // !BD_SENSOR_PROBE_NO_STOP - abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + if (faux) + abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); + else + abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); #endif @@ -817,7 +839,10 @@ G29_TYPE GcodeSuite::G29() { // Retain the last probe position abl.probePos = xy_pos_t(points[i]); - abl.measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + if (faux) + abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); + else + abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); if (isnan(abl.measured_z)) { set_bed_leveling_enabled(abl.reenable); break; From bed17348dd0ed67c1dc56f404ccd8dd6148fb541 Mon Sep 17 00:00:00 2001 From: VEhystrix Date: Tue, 9 Sep 2025 19:47:36 +0200 Subject: [PATCH 2/6] Don't require homing when populating the mesh with random data (G29 C) or fixed value data (G29 P) --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index 88a38e615ad7..cc044b075443 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -274,7 +274,7 @@ G29_TYPE GcodeSuite::G29() { process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); // Don't allow auto-leveling without homing first - if (homing_needed_error()) G29_RETURN(false, false); + if (!faux || homing_needed_error()) G29_RETURN(false, false); // 3-point leveling gets points from the probe class #if ENABLED(AUTO_BED_LEVELING_3POINT) From 938140cbe8b8d98b77cd83f9b8d3e4ed9d2d08e6 Mon Sep 17 00:00:00 2001 From: VEhystrix Date: Tue, 9 Sep 2025 19:59:38 +0200 Subject: [PATCH 3/6] Added documentation --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index cc044b075443..5d1e8c5f0a17 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -206,6 +206,7 @@ class G29_State { * X For mesh point, overrides I * Y For mesh point, overrides J * Z For mesh point. If omitted, uses current position's raw Z + * P Populate the mesh with a specified Z value. * * With DEBUG_LEVELING_FEATURE: * C Make a totally fake grid with no actual probing. From 86a1889e66bd81c5c619b16b2bd14f846048e685 Mon Sep 17 00:00:00 2001 From: VEhystrix Date: Tue, 9 Sep 2025 21:01:12 +0200 Subject: [PATCH 4/6] Don't require homing on G29 W Don't run EVENT_GCODE_AFTER_G29 when running with G29 P --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index 5d1e8c5f0a17..0751485a8302 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -247,7 +247,7 @@ G29_TYPE GcodeSuite::G29() { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) bool do_init = parser.seenval('P'); float init_val = 0.0f; - if (do_init){ + if (do_init) { init_val = RAW_Z_POSITION(parser.value_linear_units()); if (!WITHIN(init_val, -10, 10)) { SERIAL_ERROR_MSG("Bad initialization value"); @@ -274,8 +274,14 @@ G29_TYPE GcodeSuite::G29() { if (parser.seen_test('N')) process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + const bool seen_w = parser.seen_test('W'); + #else + constexpr bool seen_w = false; + #endif + // Don't allow auto-leveling without homing first - if (!faux || homing_needed_error()) G29_RETURN(false, false); + if (!seen_w && !faux && homing_needed_error()) G29_RETURN(false, false); // 3-point leveling gets points from the probe class #if ENABLED(AUTO_BED_LEVELING_3POINT) @@ -315,7 +321,6 @@ G29_TYPE GcodeSuite::G29() { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - const bool seen_w = parser.seen_test('W'); if (seen_w) { if (!leveling_is_valid()) { SERIAL_ERROR_MSG("No bilinear grid"); @@ -358,10 +363,6 @@ G29_TYPE GcodeSuite::G29() { G29_RETURN(false, false); } // parser.seen_test('W') - #else - - constexpr bool seen_w = false; - #endif // Jettison bed leveling data @@ -1034,6 +1035,10 @@ G29_TYPE GcodeSuite::G29() { // Restore state after probing if (!faux) restore_feedrate_and_scaling(); + // Return here if we merely initialized for a with value (bilinear G29 P) + // No need to do the after G29 gcode or the other stuff + if (do_init) G29_RETURN(false, true); + TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); #ifdef EVENT_GCODE_AFTER_G29 From 9cd897f1e2ee1d7e49941f64bed4c786bf2ba647 Mon Sep 17 00:00:00 2001 From: VEhystrix Date: Tue, 9 Sep 2025 21:31:55 +0200 Subject: [PATCH 5/6] Fixed AUTO_BED_LEVELING_3POINT compile issue --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index 0751485a8302..08744bfb1300 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -256,13 +256,14 @@ G29_TYPE GcodeSuite::G29() { } #else constexpr bool do_init = false; + constexpr float init_val = 0.0f; #endif // A = Abort manual probing // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), no_action = seenA || seenQ, - faux = (ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action) || do_init; + faux = (ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action) || do_init; // O = Don't level if leveling is already active if (!no_action && planner.leveling_active && parser.boolval('O')) { From 0369ceb68512c0df26c5ee6e37e94807a7883cc1 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Sun, 14 Sep 2025 19:53:14 -0500 Subject: [PATCH 6/6] 'G29 P' optional, other improvements --- Marlin/Configuration.h | 3 + Marlin/src/feature/bedlevel/abl/bbl.cpp | 12 +- Marlin/src/feature/bedlevel/abl/bbl.h | 1 + Marlin/src/gcode/bedlevel/abl/G29.cpp | 184 +++++++++++------------- 4 files changed, 99 insertions(+), 101 deletions(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 40b8ba5f2234..95ac6682a325 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -2233,6 +2233,9 @@ #define BILINEAR_SUBDIVISIONS 3 #endif + // Add 'G29 P' to fill the mesh with a single value + //#define ABL_BILINEAR_G29_P_FILL_MESH + #endif #elif ENABLED(AUTO_BED_LEVELING_UBL) diff --git a/Marlin/src/feature/bedlevel/abl/bbl.cpp b/Marlin/src/feature/bedlevel/abl/bbl.cpp index 14c4bd24bcf0..deb3ad5e6343 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.cpp +++ b/Marlin/src/feature/bedlevel/abl/bbl.cpp @@ -97,15 +97,19 @@ void LevelingBilinear::extrapolate_one_point(const uint8_t x, const uint8_t y, c #endif #endif -void LevelingBilinear::reset() { - grid_start.reset(); - grid_spacing.reset(); +void LevelingBilinear::fill(const float val/*=NAN*/) { GRID_LOOP(x, y) { - z_values[x][y] = NAN; + z_values[x][y] = val; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, 0)); } } +void LevelingBilinear::reset() { + grid_start.reset(); + grid_spacing.reset(); + fill(); +} + void LevelingBilinear::set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start) { grid_spacing = _grid_spacing; grid_start = _grid_start; diff --git a/Marlin/src/feature/bedlevel/abl/bbl.h b/Marlin/src/feature/bedlevel/abl/bbl.h index ca2e96593fde..f41e5acdf3f5 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.h +++ b/Marlin/src/feature/bedlevel/abl/bbl.h @@ -50,6 +50,7 @@ class LevelingBilinear { #endif public: + static void fill(const float val=NAN); static void reset(); static void set_grid(const xy_pos_t& _grid_spacing, const xy_pos_t& _grid_start); static void extrapolate_unprobed_bed_level(); diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index 08744bfb1300..04cc8afe243b 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -200,13 +200,15 @@ class G29_State { * * With AUTO_BED_LEVELING_BILINEAR: * Z Supply additional Z offset to all probe points. - * W Write a mesh point. (If G29 is idle.) - * I Index for mesh point - * J Index for mesh point - * X For mesh point, overrides I - * Y For mesh point, overrides J - * Z For mesh point. If omitted, uses current position's raw Z - * P Populate the mesh with a specified Z value. + * W Write a mesh point. (If G29 is idle.) + * I Index for mesh point + * J Index for mesh point + * X For mesh point, overrides I + * Y For mesh point, overrides J + * Z For mesh point. If omitted, uses current position's raw Z + * + * With ABL_BILINEAR_G29_P_FILL_MESH + * P Populate the mesh with a specified Z value * * With DEBUG_LEVELING_FEATURE: * C Make a totally fake grid with no actual probing. @@ -243,30 +245,89 @@ G29_TYPE GcodeSuite::G29() { if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false, false); #endif - // P = populate grid with specified Z value - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - bool do_init = parser.seenval('P'); - float init_val = 0.0f; - if (do_init) { - init_val = RAW_Z_POSITION(parser.value_linear_units()); - if (!WITHIN(init_val, -10, 10)) { - SERIAL_ERROR_MSG("Bad initialization value"); + // W = Write a mesh point (below) + const bool seenW = TERN0(AUTO_BED_LEVELING_BILINEAR, parser.seen_test('W')); + if (seenW && g29_in_progress) { + SERIAL_WARN_MSG("(W) ignored."); + G29_RETURN(false, false); + } + + // J = Jettison bed leveling data + const bool seenJ = !seenW && parser.seen_test('J'); + if (seenJ) { + if (g29_in_progress) { + SERIAL_WARN_MSG("(J) ignored."); + G29_RETURN(false, false); + } + else + reset_bed_level(); + } + + // P = Populate the mesh with a specified value + #if ENABLED(ABL_BILINEAR_G29_P_FILL_MESH) + if (parser.seenval('P')) { + const float init_val = parser.value_linear_units(); + if (!WITHIN(init_val, -10.0f, 10.0f)) { + SERIAL_WARN_MSG("(P) value out of range (-10-10).\n"); G29_RETURN(false, false); } + bedlevel.fill(init_val); } - #else - constexpr bool do_init = false; - constexpr float init_val = 0.0f; + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (seenW) { + + const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; + if (!WITHIN(rz, -10, 10)) { + SERIAL_ERROR_MSG("(W) value out of range (-10-10)."); + G29_RETURN(false, false); + } + + const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), + ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); + int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + if (!isnan(rx) && !isnan(ry)) { + // Get nearest i / j from rx / ry + i = (rx - bedlevel.grid_start.x) / bedlevel.grid_spacing.x + 0.5f; + j = (ry - bedlevel.grid_start.y) / bedlevel.grid_spacing.y + 0.5f; + LIMIT(i, 0, (GRID_MAX_POINTS_X) - 1); + LIMIT(j, 0, (GRID_MAX_POINTS_Y) - 1); + } + + #pragma GCC diagnostic pop + + if (WITHIN(i, 0, (GRID_MAX_POINTS_X) - 1) && WITHIN(j, 0, (GRID_MAX_POINTS_Y) - 1)) { + set_bed_leveling_enabled(false); + bedlevel.z_values[i][j] = rz; + bedlevel.refresh_bed_level(); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); + if (!leveling_is_valid()) SERIAL_WARN_MSG("Bilinear grid is invalid."); + if (abl.reenable) { + set_bed_leveling_enabled(true); + report_current_position(); + } + } + + G29_RETURN(false, false); + + } // seenW + #endif // A = Abort manual probing // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), no_action = seenA || seenQ, - faux = (ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action) || do_init; + faux = (ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action); // O = Don't level if leveling is already active - if (!no_action && planner.leveling_active && parser.boolval('O')) { + if (parser.boolval('O') && !no_action && planner.leveling_active) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); G29_RETURN(false, false); } @@ -275,14 +336,8 @@ G29_TYPE GcodeSuite::G29() { if (parser.seen_test('N')) process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - const bool seen_w = parser.seen_test('W'); - #else - constexpr bool seen_w = false; - #endif - // Don't allow auto-leveling without homing first - if (!seen_w && !faux && homing_needed_error()) G29_RETURN(false, false); + if (!faux && homing_needed_error()) G29_RETURN(false, false); // 3-point leveling gets points from the probe class #if ENABLED(AUTO_BED_LEVELING_3POINT) @@ -320,58 +375,6 @@ G29_TYPE GcodeSuite::G29() { abl.reenable = planner.leveling_active; - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (seen_w) { - if (!leveling_is_valid()) { - SERIAL_ERROR_MSG("No bilinear grid"); - G29_RETURN(false, false); - } - - const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; - if (!WITHIN(rz, -10, 10)) { - SERIAL_ERROR_MSG("Bad Z value"); - G29_RETURN(false, false); - } - - const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), - ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); - int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); - - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - - if (!isnan(rx) && !isnan(ry)) { - // Get nearest i / j from rx / ry - i = (rx - bedlevel.grid_start.x) / bedlevel.grid_spacing.x + 0.5f; - j = (ry - bedlevel.grid_start.y) / bedlevel.grid_spacing.y + 0.5f; - LIMIT(i, 0, (GRID_MAX_POINTS_X) - 1); - LIMIT(j, 0, (GRID_MAX_POINTS_Y) - 1); - } - - #pragma GCC diagnostic pop - - if (WITHIN(i, 0, (GRID_MAX_POINTS_X) - 1) && WITHIN(j, 0, (GRID_MAX_POINTS_Y) - 1)) { - set_bed_leveling_enabled(false); - bedlevel.z_values[i][j] = rz; - bedlevel.refresh_bed_level(); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); - if (abl.reenable) { - set_bed_leveling_enabled(true); - report_current_position(); - } - } - G29_RETURN(false, false); - } // parser.seen_test('W') - - #endif - - // Jettison bed leveling data - if (!seen_w && parser.seen_test('J')) { - reset_bed_level(); - G29_RETURN(false, false); - } - abl.verbose_level = parser.intval('V'); if (!WITHIN(abl.verbose_level, 0, 4)) { SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-4).")); @@ -732,12 +735,9 @@ G29_TYPE GcodeSuite::G29() { if (PR_INNER_VAR == inStart) { char tmp_1[32]; - // move to the start point of new line - if (faux) - abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); - else - abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); - + // Move to the start point of new line + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + // Go to the end of the row/column ... and back up by one // TODO: Why not just use... PR_INNER_VAR = inStop - inInc for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc); @@ -790,10 +790,7 @@ G29_TYPE GcodeSuite::G29() { #else // !BD_SENSOR_PROBE_NO_STOP - if (faux) - abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); - else - abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); #endif @@ -842,10 +839,7 @@ G29_TYPE GcodeSuite::G29() { // Retain the last probe position abl.probePos = xy_pos_t(points[i]); - if (faux) - abl.measured_z = do_init ? init_val : 0.001f * random(-100, 101); - else - abl.measured_z = probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); if (isnan(abl.measured_z)) { set_bed_leveling_enabled(abl.reenable); break; @@ -1036,10 +1030,6 @@ G29_TYPE GcodeSuite::G29() { // Restore state after probing if (!faux) restore_feedrate_and_scaling(); - // Return here if we merely initialized for a with value (bilinear G29 P) - // No need to do the after G29 gcode or the other stuff - if (do_init) G29_RETURN(false, true); - TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); #ifdef EVENT_GCODE_AFTER_G29