diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index f64ee9def4a5..3fb4ad6bbb02 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -1110,7 +1110,7 @@ //#define ASSISTED_TRAMMING #if ENABLED(ASSISTED_TRAMMING) - // Define from 3 to 9 points to probe. + // Define from 3 to 9 points to probe. Overwritten by "DYNAMIC_MARGINS" if "DYNAMIC_TRAMMING" is enabled. #define TRAMMING_POINT_XY { { 20, 20 }, { 180, 20 }, { 180, 180 }, { 20, 180 } } // Define position names for probe points. @@ -2473,12 +2473,34 @@ * should the probe position be modified with M851XY then the * probe points will follow. This prevents any change from causing * the probe to be unable to reach any points. + * + * If you enable DYNAMIC_MARGINS PROBING_MARGINs are changeable without recompilation. + * They are then bedlevel.margin_l, bedlevel.margin_r, bedlevel.margin_f, bedlevel.margin_b + * and can be overwritten with "M421 L50 R50 F50 B50" for a 50mm margin around the bed + * and saved to the EEPROM on runtime. Default values underneath. + * Setting manually via gcode respects probe_offsets and the min and max positions + * and recalculates if a too low value is set so the probed area is valid. + * + * DYNAMIC_MARGINS can be also enabled to be used for the ASSISTED_TRAMMING points with + * #define DYNAMIC_TRAMMING + * */ #if PROBE_SELECTED && !IS_KINEMATIC - //#define PROBING_MARGIN_LEFT PROBING_MARGIN - //#define PROBING_MARGIN_RIGHT PROBING_MARGIN - //#define PROBING_MARGIN_FRONT PROBING_MARGIN - //#define PROBING_MARGIN_BACK PROBING_MARGIN + //#define DYNAMIC_MARGINS + #if ENABLED(DYNAMIC_MARGINS) + #if ENABLED(ASSISTED_TRAMMING) + //#define DYNAMIC_TRAMMING + #endif + #define PROBING_MARGIN_LEFT 45 + #define PROBING_MARGIN_RIGHT 45 + #define PROBING_MARGIN_FRONT 45 + #define PROBING_MARGIN_BACK 45 + #else + #define PROBING_MARGIN_LEFT PROBING_MARGIN + #define PROBING_MARGIN_RIGHT PROBING_MARGIN + #define PROBING_MARGIN_FRONT PROBING_MARGIN + #define PROBING_MARGIN_BACK PROBING_MARGIN + #endif #endif #if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL) diff --git a/Marlin/src/feature/bedlevel/abl/bbl.cpp b/Marlin/src/feature/bedlevel/abl/bbl.cpp index 918b06d2b48b..f3d78adc6152 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.cpp +++ b/Marlin/src/feature/bedlevel/abl/bbl.cpp @@ -43,6 +43,9 @@ xy_float_t LevelingBilinear::grid_factor; bed_mesh_t LevelingBilinear::z_values; xy_pos_t LevelingBilinear::cached_rel; xy_int8_t LevelingBilinear::cached_g; +#if ENABLED(DYNAMIC_MARGINS) + int16_t LevelingBilinear::margin_l, LevelingBilinear::margin_r, LevelingBilinear::margin_f, LevelingBilinear::margin_b; +#endif /** * Extrapolate a single point from its neighbors diff --git a/Marlin/src/feature/bedlevel/abl/bbl.h b/Marlin/src/feature/bedlevel/abl/bbl.h index fb890333dc76..19da764158d3 100644 --- a/Marlin/src/feature/bedlevel/abl/bbl.h +++ b/Marlin/src/feature/bedlevel/abl/bbl.h @@ -22,11 +22,17 @@ #pragma once #include "../../../inc/MarlinConfigPre.h" +#if ENABLED(DYNAMIC_MARGINS) + #include "../../../feature/bedlevel/bedlevel.h" +#endif class LevelingBilinear { public: static bed_mesh_t z_values; static xy_pos_t grid_spacing, grid_start; + #if ENABLED(DYNAMIC_MARGINS) + static int16_t margin_l, margin_r, margin_f, margin_b; + #endif private: static xy_float_t grid_factor; diff --git a/Marlin/src/feature/bedlevel/hilbert_curve.cpp b/Marlin/src/feature/bedlevel/hilbert_curve.cpp index 57cbdfb34ddd..721c21014784 100644 --- a/Marlin/src/feature/bedlevel/hilbert_curve.cpp +++ b/Marlin/src/feature/bedlevel/hilbert_curve.cpp @@ -102,8 +102,8 @@ bool hilbert_curve::search_from(uint8_t x, uint8_t y, hilbert_curve::callback_pt */ bool hilbert_curve::search_from_closest(const xy_pos_t &pos, hilbert_curve::callback_ptr func, void *data) { // Find closest grid intersection - const uint8_t grid_x = LROUND(constrain(float(pos.x - (MESH_MIN_X)) / (MESH_X_DIST), 0, (GRID_MAX_POINTS_X) - 1)); - const uint8_t grid_y = LROUND(constrain(float(pos.y - (MESH_MIN_Y)) / (MESH_Y_DIST), 0, (GRID_MAX_POINTS_Y) - 1)); + const uint8_t grid_x = LROUND(constrain(float(pos.x - (TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X))) / (TERN(DYNAMIC_MARGINS, bedlevel.get_mesh_x_dist(), MESH_X_DIST)), 0, (GRID_MAX_POINTS_X) - 1)); + const uint8_t grid_y = LROUND(constrain(float(pos.y - (TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y))) / (TERN(DYNAMIC_MARGINS, bedlevel.get_mesh_y_dist(), MESH_Y_DIST)), 0, (GRID_MAX_POINTS_Y) - 1)); return search_from(grid_x, grid_y, func, data); } diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp index e1f2ed4c166e..df3729b69c16 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp @@ -65,22 +65,26 @@ void unified_bed_leveling::report_state() { int8_t unified_bed_leveling::storage_slot; float unified_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; +#if ENABLED(DYNAMIC_MARGINS) + int16_t unified_bed_leveling::margin_l, unified_bed_leveling::margin_r, unified_bed_leveling::margin_f, unified_bed_leveling::margin_b; +#endif #define _GRIDPOS(A,N) (MESH_MIN_##A + N * (MESH_##A##_DIST)) - -const float -unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X, - _GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3), - _GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7), - _GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11), - _GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15) -), -unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y, - _GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3), - _GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7), - _GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11), - _GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15) -); +#if DISABLED(DYNAMIC_MARGINS) + const float + unified_bed_leveling::_mesh_index_to_xpos[GRID_MAX_POINTS_X] PROGMEM = ARRAY_N(GRID_MAX_POINTS_X, + _GRIDPOS(X, 0), _GRIDPOS(X, 1), _GRIDPOS(X, 2), _GRIDPOS(X, 3), + _GRIDPOS(X, 4), _GRIDPOS(X, 5), _GRIDPOS(X, 6), _GRIDPOS(X, 7), + _GRIDPOS(X, 8), _GRIDPOS(X, 9), _GRIDPOS(X, 10), _GRIDPOS(X, 11), + _GRIDPOS(X, 12), _GRIDPOS(X, 13), _GRIDPOS(X, 14), _GRIDPOS(X, 15) + ), + unified_bed_leveling::_mesh_index_to_ypos[GRID_MAX_POINTS_Y] PROGMEM = ARRAY_N(GRID_MAX_POINTS_Y, + _GRIDPOS(Y, 0), _GRIDPOS(Y, 1), _GRIDPOS(Y, 2), _GRIDPOS(Y, 3), + _GRIDPOS(Y, 4), _GRIDPOS(Y, 5), _GRIDPOS(Y, 6), _GRIDPOS(Y, 7), + _GRIDPOS(Y, 8), _GRIDPOS(Y, 9), _GRIDPOS(Y, 10), _GRIDPOS(Y, 11), + _GRIDPOS(Y, 12), _GRIDPOS(Y, 13), _GRIDPOS(Y, 14), _GRIDPOS(Y, 15) + ); +#endif volatile int16_t unified_bed_leveling::encoder_diff; @@ -174,8 +178,8 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { SERIAL_ECHOPGM("\nBed Topography Report"); if (human) { SERIAL_ECHOLNPGM(":\n"); - serial_echo_xy(4, MESH_MIN_X, MESH_MAX_Y); - serial_echo_xy(twixt, MESH_MAX_X, MESH_MAX_Y); + serial_echo_xy(4, (TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X)), (TERN(DYNAMIC_MARGINS, (Y_BED_SIZE - unified_bed_leveling::margin_f), MESH_MAX_Y))); + serial_echo_xy(twixt, (TERN(DYNAMIC_MARGINS, (X_BED_SIZE - unified_bed_leveling::margin_r), MESH_MAX_X)), (TERN(DYNAMIC_MARGINS, (Y_BED_SIZE - unified_bed_leveling::margin_f), MESH_MAX_Y))); SERIAL_EOL(); serial_echo_column_labels(eachsp - 2); } @@ -232,8 +236,8 @@ void unified_bed_leveling::display_map(const uint8_t map_type) { if (human) { serial_echo_column_labels(eachsp - 2); SERIAL_EOL(); - serial_echo_xy(4, MESH_MIN_X, MESH_MIN_Y); - serial_echo_xy(twixt, MESH_MAX_X, MESH_MIN_Y); + serial_echo_xy(4, (TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X)), (TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_Y))); + serial_echo_xy(twixt, (TERN(DYNAMIC_MARGINS, (X_BED_SIZE - unified_bed_leveling::margin_r), MESH_MAX_X)), (TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_Y))); SERIAL_EOL(); SERIAL_EOL(); } diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.h b/Marlin/src/feature/bedlevel/ubl/ubl.h index f6e9ba0cd9f3..14b15fcc55ae 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl.h +++ b/Marlin/src/feature/bedlevel/ubl/ubl.h @@ -28,6 +28,10 @@ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../../../core/debug_out.h" +#if ENABLED(DYNAMIC_MARGINS) + class unified_bed_leveling; + extern unified_bed_leveling bedlevel; +#endif #define UBL_VERSION "1.01" #define UBL_OK false #define UBL_ERR true @@ -37,9 +41,10 @@ enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP, CLOSEST }; // External references struct mesh_index_pair; - -#define MESH_X_DIST (float((MESH_MAX_X) - (MESH_MIN_X)) / (GRID_MAX_CELLS_X)) -#define MESH_Y_DIST (float((MESH_MAX_Y) - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y)) +#if DISABLED(DYNAMIC_MARGINS) + #define MESH_X_DIST (float((MESH_MAX_X) - (MESH_MIN_X)) / (GRID_MAX_CELLS_X)) + #define MESH_Y_DIST (float((MESH_MAX_Y) - (MESH_MIN_Y)) / (GRID_MAX_CELLS_Y)) +#endif #if ENABLED(OPTIMIZED_MESH_STORAGE) typedef int16_t mesh_store_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; @@ -110,14 +115,20 @@ class unified_bed_leveling { static void smart_fill_wlsf(const float ) __O2; // O2 gives smaller code than Os on A2560 static int8_t storage_slot; + #if ENABLED(DYNAMIC_MARGINS) + typedef float bed_mesh_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + static int16_t margin_l, margin_r, margin_f, margin_b; + #endif static bed_mesh_t z_values; #if ENABLED(OPTIMIZED_MESH_STORAGE) static void set_store_from_mesh(const bed_mesh_t &in_values, mesh_store_t &stored_values); static void set_mesh_from_store(const mesh_store_t &stored_values, bed_mesh_t &out_values); #endif - static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X], - _mesh_index_to_ypos[GRID_MAX_POINTS_Y]; + #if DISABLED(DYNAMIC_MARGINS) + static const float _mesh_index_to_xpos[GRID_MAX_POINTS_X], + _mesh_index_to_ypos[GRID_MAX_POINTS_Y]; + #endif #if HAS_MARLINUI_MENU static bool lcd_map_control; @@ -133,12 +144,12 @@ class unified_bed_leveling { FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const float z) { z_values[px][py] = z; } static int8_t cell_index_x_raw(const float x) { - return FLOOR((x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST)); + return FLOOR((x - (TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X))) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST))); } static int8_t cell_index_y_raw(const float y) { - return FLOOR((y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST)); - } + return FLOOR((y - (TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y))) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST))); + } static bool cell_index_x_valid(const float x) { return WITHIN(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); @@ -162,11 +173,11 @@ class unified_bed_leveling { static xy_uint8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); } static int8_t closest_x_index(const float x) { - const int8_t px = (x - (MESH_MIN_X) + (MESH_X_DIST) * 0.5) * RECIPROCAL(MESH_X_DIST); + const int8_t px = (x - (TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X)) + (TERN(DYNAMIC_MARGINS, get_mesh_x_dist(), MESH_X_DIST)) * 0.5) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST)); return WITHIN(px, 0, (GRID_MAX_POINTS_X) - 1) ? px : -1; } static int8_t closest_y_index(const float y) { - const int8_t py = (y - (MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * RECIPROCAL(MESH_Y_DIST); + const int8_t py = (y - (TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y)) + (TERN(DYNAMIC_MARGINS, get_mesh_y_dist(), MESH_Y_DIST)) * 0.5) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST)); return WITHIN(py, 0, (GRID_MAX_POINTS_Y) - 1) ? py : -1; } static xy_int8_t closest_indexes(const xy_pos_t &xy) { @@ -214,7 +225,7 @@ class unified_bed_leveling { return _UBL_OUTER_Z_RAISE; } - const float xratio = (rx0 - get_mesh_x(x1_i)) * RECIPROCAL(MESH_X_DIST), + const float xratio = (rx0 - get_mesh_x(x1_i)) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST)), z1 = z_values[x1_i][yi]; return z1 + xratio * (z_values[_MIN(x1_i, (GRID_MAX_POINTS_X) - 2) + 1][yi] - z1); // Don't allow x1_i+1 to be past the end of the array @@ -237,7 +248,7 @@ class unified_bed_leveling { return _UBL_OUTER_Z_RAISE; } - const float yratio = (ry0 - get_mesh_y(y1_i)) * RECIPROCAL(MESH_Y_DIST), + const float yratio = (ry0 - get_mesh_y(y1_i)) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST)), z1 = z_values[xi][y1_i]; return z1 + yratio * (z_values[xi][_MIN(y1_i, (GRID_MAX_POINTS_Y) - 2) + 1] - z1); // Don't allow y1_i+1 to be past the end of the array @@ -259,12 +270,12 @@ class unified_bed_leveling { * UBL_Z_RAISE_WHEN_OFF_MESH is specified, that value is returned. */ #ifdef UBL_Z_RAISE_WHEN_OFF_MESH - if (!WITHIN(rx0, MESH_MIN_X, MESH_MAX_X) || !WITHIN(ry0, MESH_MIN_Y, MESH_MAX_Y)) + if (!WITHIN(rx0, TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X), TERN(DYNAMIC_MARGINS, X_BED_SIZE - bedlevel.margin_r, MESH_MAX_X)) || !WITHIN(ry0, TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y), TERN(DYNAMIC_MARGINS, Y_BED_SIZE - bedlevel.margin_b, MESH_MAX_Y))) return UBL_Z_RAISE_WHEN_OFF_MESH; #endif - const uint8_t mx = _MIN(cx, (GRID_MAX_POINTS_X) - 2) + 1, my = _MIN(cy, (GRID_MAX_POINTS_Y) - 2) + 1; - const float x0 = get_mesh_x(cx), x1 = get_mesh_x(cx + 1), + IF_DISABLED(DYNAMIC_MARGINS, const) uint8_t mx = _MIN(cx, (GRID_MAX_POINTS_X) - 2) + 1, my = _MIN(cy, (GRID_MAX_POINTS_Y) - 2) + 1; + IF_DISABLED(DYNAMIC_MARGINS, const) float x0 = get_mesh_x(cx), x1 = get_mesh_x(cx + 1), z1 = calc_z0(rx0, x0, z_values[cx][cy], x1, z_values[mx][cy]), z2 = calc_z0(rx0, x0, z_values[cx][my], x1, z_values[mx][my]); float z0 = calc_z0(ry0, get_mesh_y(cy), z1, get_mesh_y(cy + 1), z2); @@ -286,13 +297,28 @@ class unified_bed_leveling { static float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); } static constexpr float get_z_offset() { return 0.0f; } - - static float get_mesh_x(const uint8_t i) { - return i < (GRID_MAX_POINTS_X) ? pgm_read_float(&_mesh_index_to_xpos[i]) : MESH_MIN_X + i * (MESH_X_DIST); - } - static float get_mesh_y(const uint8_t i) { - return i < (GRID_MAX_POINTS_Y) ? pgm_read_float(&_mesh_index_to_ypos[i]) : MESH_MIN_Y + i * (MESH_Y_DIST); - } + #if DISABLED(DYNAMIC_MARGINS) + static float get_mesh_x(const uint8_t i) { + return i < (GRID_MAX_POINTS_X) ? pgm_read_float(&_mesh_index_to_xpos[i]) : MESH_MIN_X + i * (MESH_X_DIST); + } + static float get_mesh_y(const uint8_t i) { + return i < (GRID_MAX_POINTS_Y) ? pgm_read_float(&_mesh_index_to_ypos[i]) : MESH_MIN_Y + i * (MESH_Y_DIST); + } + #endif + #if ENABLED(DYNAMIC_MARGINS) + static float get_mesh_x(const uint8_t i) { + return bedlevel.margin_l + i * get_mesh_x_dist(); + } + static float get_mesh_y(const uint8_t i) { + return bedlevel.margin_f + i * get_mesh_y_dist(); + } + static float get_mesh_x_dist() { + return float((X_BED_SIZE - bedlevel.margin_r) - bedlevel.margin_l) / (GRID_MAX_POINTS_X - 1); + } + static float get_mesh_y_dist() { + return float((Y_BED_SIZE - bedlevel.margin_b) - bedlevel.margin_f) / (GRID_MAX_POINTS_Y - 1); + } + #endif #if UBL_SEGMENTED static bool line_to_destination_segmented(const feedRate_t scaled_fr_mm_s); @@ -307,7 +333,9 @@ class unified_bed_leveling { }; // class unified_bed_leveling -extern unified_bed_leveling bedlevel; +#if DISABLED(DYNAMIC_MARGINS) + extern unified_bed_leveling bedlevel; +#endif // Prevent debugging propagating to other files #include "../../../core/debug_out.h" diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index 1f43508ead96..27a33048c234 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -445,7 +445,7 @@ void unified_bed_leveling::G29() { tilt_mesh_based_on_probed_grid(param.J_grid_size == 0); // Zero size does 3-Point restore_ubl_active_state(); #if ENABLED(UBL_G29_J_RECENTER) - do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y))); + do_blocking_move_to_xy(0.5f * ((TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X)) + (TERN(DYNAMIC_MARGINS, X_BED_SIZE - bedlevel.margin_r, MESH_MAX_X))), 0.5f * ((TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y)) + (TERN(DYNAMIC_MARGINS, Y_BED_SIZE - bedlevel.margin_b, MESH_MAX_Y)))); #endif report_current_position(); SET_PROBE_DEPLOYED(true); @@ -829,8 +829,8 @@ void unified_bed_leveling::shift_mesh_height(const float zoffs) { probe.move_z_after_probing(); do_blocking_move_to_xy( - constrain(nearby.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X), - constrain(nearby.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y) + constrain(TERN(DYNAMIC_MARGINS, nearby.x, nearby.x - probe.offset_xy.x), TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X), (TERN(DYNAMIC_MARGINS, (X_BED_SIZE - bedlevel.margin_r), MESH_MAX_X))), + constrain(TERN(DYNAMIC_MARGINS, nearby.y, nearby.y - probe.offset_xy.y), TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y), (TERN(DYNAMIC_MARGINS, (Y_BED_SIZE - bedlevel.margin_b), MESH_MAX_Y))) ); restore_ubl_active_state(); @@ -896,8 +896,8 @@ void set_message_with_feedback(FSTR_P const fstr) { do_blocking_move_to( xyz_pos_t({ - 0.5f * ((MESH_MAX_X) - (MESH_MIN_X)), - 0.5f * ((MESH_MAX_Y) - (MESH_MIN_Y)), + 0.5f * ((TERN(DYNAMIC_MARGINS, (X_BED_SIZE - unified_bed_leveling::margin_r), MESH_MAX_X)) - TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X)), + 0.5f * ((TERN(DYNAMIC_MARGINS, (Y_BED_SIZE - unified_bed_leveling::margin_b), MESH_MAX_Y)) - TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_f, MESH_MIN_Y)), MANUAL_PROBE_START_Z #ifdef SAFE_BED_LEVELING_START_I , SAFE_BED_LEVELING_START_I @@ -1393,7 +1393,7 @@ mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const Mesh float best_so_far = 99999.99f; GRID_LOOP(i, j) { - if ( type == CLOSEST || type == (isnan(z_values[i][j]) ? INVALID : REAL) + if (type == CLOSEST || type == (isnan(z_values[i][j]) ? INVALID : REAL) || (type == SET_IN_BITMAP && !done_flags->marked(i, j)) ) { // Found a Mesh Point of the specified type! @@ -1536,10 +1536,10 @@ void unified_bed_leveling::smart_fill_mesh() { #ifndef G29J_MESH_TILT_MARGIN #define G29J_MESH_TILT_MARGIN 0 #endif - const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_X, probe.min_x()), - x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_X, probe.max_x()), - y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_Y, probe.min_y()), - y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_Y, probe.max_y()), + const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X), probe.min_x()), + x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), TERN(DYNAMIC_MARGINS, (X_BED_SIZE - unified_bed_leveling::margin_r), MESH_MAX_X), probe.max_x()), + y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_f, MESH_MIN_Y), probe.min_y()), + y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), TERN(DYNAMIC_MARGINS, (Y_BED_SIZE - unified_bed_leveling::margin_b), MESH_MAX_Y), probe.max_y()), dx = (x_max - x_min) / (param.J_grid_size - 1), dy = (y_max - y_min) / (param.J_grid_size - 1); @@ -1697,7 +1697,7 @@ void unified_bed_leveling::smart_fill_mesh() { SERIAL_ECHOPGM("Extrapolating mesh..."); - const float weight_scaled = weight_factor * _MAX(MESH_X_DIST, MESH_Y_DIST); + const float weight_scaled = weight_factor * _MAX(TERN(DYNAMIC_MARGINS, unified_bed_leveling::get_mesh_x_dist(), MESH_X_DIST), TERN(DYNAMIC_MARGINS, unified_bed_leveling::get_mesh_y_dist(), MESH_Y_DIST)); GRID_LOOP(jx, jy) if (!isnan(z_values[jx][jy])) SBI(bitmap[jx], jy); @@ -1761,14 +1761,14 @@ void unified_bed_leveling::smart_fill_mesh() { SERIAL_ECHOLNPGM("Probe Offset M851 Z", p_float_t(probe.offset.z, 7)); #endif - SERIAL_ECHOLNPGM("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MIN_Y " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_X " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_Y " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_X_DIST ", MESH_X_DIST); - SERIAL_ECHOLNPGM("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MIN_X " STRINGIFY(TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X)) "=", TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_l, MESH_MIN_X)); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MIN_Y " STRINGIFY(TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_f, MESH_MIN_Y)) "=", TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_f, MESH_MIN_Y)); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MAX_X " STRINGIFY(TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_r, MESH_MAX_X)) "=", TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_r, MESH_MAX_X)); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MAX_Y " STRINGIFY(TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_b, MESH_MAX_Y)) "=", TERN(DYNAMIC_MARGINS, unified_bed_leveling::margin_b, MESH_MAX_Y)); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_X_DIST ", TERN(DYNAMIC_MARGINS, unified_bed_leveling::mesh_x_dist(), MESH_X_DIST)); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_Y_DIST ", TERN(DYNAMIC_MARGINS, unified_bed_leveling::mesh_y_dist(), MESH_Y_DIST)); serial_delay(50); SERIAL_ECHOPGM("X-Axis Mesh Points at: "); for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp index be9fb7b947c9..ebafb91f6fab 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp @@ -85,8 +85,8 @@ #endif // The distance is always MESH_X_DIST so multiply by the constant reciprocal. - const float xratio = (end.x - get_mesh_x(iend.x)) * RECIPROCAL(MESH_X_DIST), - yratio = (end.y - get_mesh_y(iend.y)) * RECIPROCAL(MESH_Y_DIST), + const float xratio = (end.x - get_mesh_x(iend.x)) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST)); + const float yratio = (end.y - get_mesh_y(iend.y)) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST)), z1 = z_values[iend.x][iend.y ] + xratio * (z_values[iend.x + 1][iend.y ] - z_values[iend.x][iend.y ]), z2 = z_values[iend.x][iend.y + 1] + xratio * (z_values[iend.x + 1][iend.y + 1] - z_values[iend.x][iend.y + 1]); @@ -415,8 +415,8 @@ // for mesh inset area. xy_int8_t icell = { - int8_t((raw.x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST)), - int8_t((raw.y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST)) + int8_t((raw.x - (TERN(DYNAMIC_MARGINS, bedlevel.margin_l, MESH_MIN_X))) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST))), + int8_t((raw.y - (TERN(DYNAMIC_MARGINS, bedlevel.margin_f, MESH_MIN_Y))) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST))) }; LIMIT(icell.x, 0, GRID_MAX_CELLS_X); LIMIT(icell.y, 0, GRID_MAX_CELLS_Y); @@ -436,15 +436,15 @@ const xy_pos_t pos = { get_mesh_x(icell.x), get_mesh_y(icell.y) }; xy_pos_t cell = raw - pos; - const float z_xmy0 = (z_x1y0 - z_x0y0) * RECIPROCAL(MESH_X_DIST), // z slope per x along y0 (lower left to lower right) - z_xmy1 = (z_x1y1 - z_x0y1) * RECIPROCAL(MESH_X_DIST); // z slope per x along y1 (upper left to upper right) + const float z_xmy0 = (z_x1y0 - z_x0y0) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST)); // z slope per x along y0 (lower left to lower right) + const float z_xmy1 = (z_x1y1 - z_x0y1) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_x_dist()), RECIPROCAL(MESH_X_DIST)); // z slope per x along y1 (upper left to upper right) float z_cxy0 = z_x0y0 + z_xmy0 * cell.x; // z height along y0 at cell.x (changes for each cell.x in cell) const float z_cxy1 = z_x0y1 + z_xmy1 * cell.x, // z height along y1 at cell.x z_cxyd = z_cxy1 - z_cxy0; // z height difference along cell.x from y0 to y1 - float z_cxym = z_cxyd * RECIPROCAL(MESH_Y_DIST); // z slope per y along cell.x from pos.y to y1 (changes for each cell.x in cell) + float z_cxym = z_cxyd * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST)); // z slope per y along cell.x from pos.y to y1 (changes for each cell.x in cell) // float z_cxcy = z_cxy0 + z_cxym * cell.y; // interpolated mesh z height along cell.x at cell.y (do inside the segment loop) @@ -452,8 +452,8 @@ // and the z_cxym slope will change, both as a function of cell.x within the cell, and // each change by a constant for fixed segment lengths. - const float z_sxy0 = z_xmy0 * diff.x, // per-segment adjustment to z_cxy0 - z_sxym = (z_xmy1 - z_xmy0) * RECIPROCAL(MESH_Y_DIST) * diff.x; // per-segment adjustment to z_cxym + const float z_sxy0 = z_xmy0 * diff.x; // per-segment adjustment to z_cxy0 + const float z_sxym = (z_xmy1 - z_xmy0) * TERN(DYNAMIC_MARGINS, RECIPROCAL(get_mesh_y_dist()), RECIPROCAL(MESH_Y_DIST)) * diff.x; // per-segment adjustment to z_cxym for (;;) { // for all segments within this mesh cell @@ -472,7 +472,7 @@ raw += diff; cell += diff; - if (!WITHIN(cell.x, 0, MESH_X_DIST) || !WITHIN(cell.y, 0, MESH_Y_DIST)) // done within this cell, break to next + if (!WITHIN(cell.x, 0, TERN(DYNAMIC_MARGINS, get_mesh_x_dist(), MESH_X_DIST)) || !WITHIN(cell.y, 0, TERN(DYNAMIC_MARGINS, get_mesh_y_dist(), MESH_Y_DIST))) // done within this cell, break to next break; // Next segment still within same mesh cell, adjust the per-segment diff --git a/Marlin/src/feature/tramming.cpp b/Marlin/src/feature/tramming.cpp index 3721c5eb811a..e9ca982cb714 100644 --- a/Marlin/src/feature/tramming.cpp +++ b/Marlin/src/feature/tramming.cpp @@ -29,6 +29,9 @@ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" +#if ALL(DYNAMIC_MARGINS, DYNAMIC_TRAMMING) + xy_pos_t tramming_points[] = TRAMMING_POINT_XY; +#endif #define _TRAM_NAME_DEF(N) PGMSTR(point_name_##N, TRAMMING_POINT_NAME_##N); #define _TRAM_NAME_ITEM(N) point_name_##N REPEAT_1(_NR_TRAM_NAMES, _TRAM_NAME_DEF) diff --git a/Marlin/src/feature/tramming.h b/Marlin/src/feature/tramming.h index de4c6c020c1b..94ead4d9afd5 100644 --- a/Marlin/src/feature/tramming.h +++ b/Marlin/src/feature/tramming.h @@ -35,9 +35,13 @@ static_assert( "TRAMMING_SCREW_THREAD must be M3_CW, M3_CCW, M4_CW, M4_CCW, M5_CW, or M5_CCW." ); -constexpr xy_pos_t tramming_points[] = TRAMMING_POINT_XY; - -#define G35_PROBE_COUNT COUNT(tramming_points) +#if ALL(DYNAMIC_MARGINS, DYNAMIC_TRAMMING) + #define G35_PROBE_COUNT 4 + extern xy_pos_t tramming_points[]; +#else + #define G35_PROBE_COUNT COUNT(tramming_points) + constexpr xy_pos_t tramming_points[] = TRAMMING_POINT_XY; +#endif static_assert(WITHIN(G35_PROBE_COUNT, 3, 9), "TRAMMING_POINT_XY requires between 3 and 9 XY positions."); #ifdef TRAMMING_POINT_NAME_9 @@ -63,9 +67,11 @@ static_assert(_NR_TRAM_NAMES >= G35_PROBE_COUNT, "Define enough TRAMMING_POINT_N #define _TRAM_NAME_PTR(N) point_name_##N[] extern const char REPLIST_1(_NR_TRAM_NAMES, _TRAM_NAME_PTR); -#define _CHECK_TRAM_POINT(N) static_assert(Probe::build_time::can_reach(tramming_points[N]), "TRAMMING_POINT_XY point " STRINGIFY(N) " is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN."); -REPEAT(_NR_TRAM_NAMES, _CHECK_TRAM_POINT) -#undef _CHECK_TRAM_POINT +#if DISABLED(DYNAMIC_MARGINS) + #define _CHECK_TRAM_POINT(N) static_assert(Probe::build_time::can_reach(tramming_points[N]), "TRAMMING_POINT_XY point " STRINGIFY(N) " is not reachable with the default NOZZLE_TO_PROBE offset and PROBING_MARGIN."); + REPEAT(_NR_TRAM_NAMES, _CHECK_TRAM_POINT) + #undef _CHECK_TRAM_POINT +#endif extern PGM_P const tramming_point_name[]; @@ -74,3 +80,7 @@ extern PGM_P const tramming_point_name[]; #else inline void move_to_tramming_wait_pos() {} #endif + +#if ALL(DYNAMIC_MARGINS, DYNAMIC_TRAMMING) + void updateTrammingPoints(); +#endif diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp index b3c56b49b319..8d0dff019baf 100644 --- a/Marlin/src/gcode/bedlevel/G35.cpp +++ b/Marlin/src/gcode/bedlevel/G35.cpp @@ -41,6 +41,18 @@ // #include "../../feature/tramming.h" +#if ALL(DYNAMIC_MARGINS, DYNAMIC_TRAMMING) + void updateTrammingPoints() { + tramming_points[0].x = bedlevel.margin_l; + tramming_points[0].y = bedlevel.margin_f; + tramming_points[1].x = X_BED_SIZE - bedlevel.margin_r; + tramming_points[1].y = bedlevel.margin_f; + tramming_points[2].x = bedlevel.margin_l; + tramming_points[2].y = Y_BED_SIZE - bedlevel.margin_b; + tramming_points[3].x = X_BED_SIZE - bedlevel.margin_r; + tramming_points[3].y = Y_BED_SIZE - bedlevel.margin_b; + } +#endif /** * G35: Read bed corners to help adjust bed screws @@ -60,6 +72,10 @@ void GcodeSuite::G35() { if (DEBUGGING(LEVELING)) log_machine_info(); + #if ALL(DYNAMIC_MARGINS, DYNAMIC_TRAMMING) + updateTrammingPoints(); + #endif + float z_measured[G35_PROBE_COUNT] = { 0 }; const uint8_t screw_thread = parser.byteval('S', TRAMMING_SCREW_THREAD); diff --git a/Marlin/src/gcode/bedlevel/abl/M421.cpp b/Marlin/src/gcode/bedlevel/abl/M421.cpp index f66d0231901e..1b5dcaf88f2f 100644 --- a/Marlin/src/gcode/bedlevel/abl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/abl/M421.cpp @@ -30,6 +30,9 @@ #include "../../gcode.h" #include "../../../feature/bedlevel/bedlevel.h" +#if ENABLED(DYNAMIC_MARGINS) + #include "../../../module/probe.h" +#endif #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.h" @@ -45,12 +48,28 @@ * - If I is omitted, set the entire row * - If J is omitted, set the entire column * - If both I and J are omitted, set all + * + * M421: Set one or more PROBING_MARGINS if ENABLED(DYNAMIC_MARGINS) in mm + * + * Usage: + * + * - set single margin (l): + * M421 L10 + * - set multiple margins (lf): + * M421 L10 F10 + * - set all margins to one value (lrfb): + * M421 L50 R50 F50 B50 + * - recalculate all min margins based on probe offsets and reachable area (lrfb): + * M421 L0 R0 F0 B0 + * - reset margins to the defaults + * M421 D + * */ void GcodeSuite::M421() { int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); const bool hasZ = parser.seenval('Z'), hasQ = !hasZ && parser.seenval('Q'); - + bool did_something = false; if (hasZ || hasQ) { if (WITHIN(ix, -1, GRID_MAX_POINTS_X - 1) && WITHIN(iy, -1, GRID_MAX_POINTS_Y - 1)) { const float zval = parser.value_linear_units(); @@ -63,12 +82,94 @@ void GcodeSuite::M421() { } } bedlevel.refresh_bed_level(); + did_something = true; } - else + else { SERIAL_ERROR_MSG(STR_ERR_MESH_XY); + return; + } + } + #if ENABLED(DYNAMIC_MARGINS) + auto safe_margin = [](int user_val, int min_required, const char axis_char) { + const int m = constrain(user_val, 0, 1000); + if (m < min_required) { + #if DISABLED(MARLIN_SMALL_BUILD) + SERIAL_ECHOPGM(" ! "); + SERIAL_CHAR(axis_char); + SERIAL_ECHOPGM(" margin too small ("); + SERIAL_ECHO(m); + SERIAL_ECHOPGM(" < "); + SERIAL_ECHO(min_required); + SERIAL_ECHOPGM("). "); + SERIAL_CHAR(axis_char); + SERIAL_ECHOPGM(" set to: "); + SERIAL_ECHOLN(min_required); + #endif + return min_required; + } + return m; + }; + if (parser.seen('D')) { + // 'D' with no value resets all margins + bedlevel.margin_l = PROBING_MARGIN_LEFT; + bedlevel.margin_f = PROBING_MARGIN_RIGHT; + bedlevel.margin_r = PROBING_MARGIN_FRONT; + bedlevel.margin_b = PROBING_MARGIN_BACK; + #if DISABLED(MARLIN_SMALL_BUILD) + SERIAL_ECHOPGM(" Margins reset: L"); + SERIAL_ECHO(bedlevel.margin_l); + SERIAL_ECHOPGM(" R"); + SERIAL_ECHO(bedlevel.margin_r); + SERIAL_ECHOPGM(" F"); + SERIAL_ECHO(bedlevel.margin_f); + SERIAL_ECHOPGM(" B"); + SERIAL_ECHO(bedlevel.margin_b); + SERIAL_EOL(); + #endif + did_something = true; + } + if (parser.seen('L')) { + // Left margin: X_MIN_POS + probe_offset must reach >= 0, but min 10 + bedlevel.margin_l = safe_margin(parser.value_int(), constrain(((TERN(HAS_HOME_OFFSET, home_offset.x, X_MIN_POS) - ceilf(fabs(probe.offset_xy.x))) <= 0 ? 10 : (TERN(HAS_HOME_OFFSET, home_offset.x, X_MIN_POS) - ceilf(fabs(probe.offset_xy.x)))), 10, X_BED_SIZE), 'L'); + did_something = true; + } + if (parser.seen('F')) { + // Front margin: Y_MIN_POS + probe_offset must reach >= 0, but min 10 + bedlevel.margin_f = safe_margin(parser.value_int(), constrain(((TERN(HAS_HOME_OFFSET, home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y))) <= 0 ? 10 : (TERN(HAS_HOME_OFFSET, home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y)))), 10, Y_BED_SIZE), 'F'); + did_something = true; + } + if (parser.seen('R')) { + // Right margin: The probe must not exceed X_BED_SIZE, but min 10 + bedlevel.margin_r = safe_margin(parser.value_int(), constrain(_MAX(10, X_BED_SIZE - (X_MAX_POS - ceilf(fabs(probe.offset_xy.x)))), 10, X_BED_SIZE), 'R'); + did_something = true; + } + if (parser.seen('B')) { + bedlevel.margin_b = safe_margin(parser.value_int(), constrain(_MAX(10, Y_BED_SIZE - (Y_MAX_POS - ceilf(fabs(probe.offset_xy.y)))), 10, Y_BED_SIZE), 'B'); + did_something = true; + } + #endif + if (!did_something) { + #if ENABLED(DYNAMIC_MARGINS) + M421_report(); + #else + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + #endif } - else - SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); } +#if ENABLED(DYNAMIC_MARGINS) + void GcodeSuite::M421_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + report_heading_etc(forReplay, F("Dynamic Margins")); + SERIAL_ECHOPGM(" M421 L"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_l)); + SERIAL_ECHOPGM(" R"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_r)); + SERIAL_ECHOPGM(" F"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_f)); + SERIAL_ECHOPGM(" B"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_b)); + SERIAL_ECHOLNPGM(" ; Margins in mm"); + } +#endif // DYNAMIC_MARGINS #endif // AUTO_BED_LEVELING_BILINEAR diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp index 99ba3ce19b46..9eef0d26b9eb 100644 --- a/Marlin/src/gcode/bedlevel/ubl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp @@ -30,6 +30,9 @@ #include "../../gcode.h" #include "../../../feature/bedlevel/bedlevel.h" +#if ENABLED(DYNAMIC_MARGINS) + #include "../../../module/probe.h" +#endif #if ENABLED(EXTENSIBLE_UI) #include "../../../lcd/extui/ui_api.h" @@ -44,6 +47,22 @@ * M421 I J N : Set the Mesh Point IJ to NAN (not set) * M421 C Z : Set the closest Mesh Point to the Z value * M421 C Q : Add the Q value to the closest Mesh Point + * + * M421: Set one or more PROBING_MARGINS if ENABLED(DYNAMIC_MARGINS) in mm + * + * Usage: + * + * - set single margin (l): + * M421 L10 + * - set multiple margins (lf): + * M421 L10 F10 + * - set all margins to one value (lrfb): + * M421 L50 R50 F50 B50 + * - recalculate all min margins based on probe offsets and reachable area (lrfb): + * M421 L0 R0 F0 B0 + * - reset margins to the defaults + * M421 D + * */ void GcodeSuite::M421() { xy_int8_t ij = { int8_t(parser.intval('I', -1)), int8_t(parser.intval('J', -1)) }; @@ -54,20 +73,109 @@ void GcodeSuite::M421() { hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); + const bool mesh_command = hasZ || hasQ || hasN; if (hasC) ij = bedlevel.find_closest_mesh_point_of_type(CLOSEST, current_position); - - // Test for bad parameter combinations - if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ || hasN)) - SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); - - // Test for I J out of range - else if (!WITHIN(ij.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(ij.y, 0, GRID_MAX_POINTS_Y - 1)) - SERIAL_ERROR_MSG(STR_ERR_MESH_XY); - else { - float &zval = bedlevel.z_values[ij.x][ij.y]; // Altering this Mesh Point - zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); // N=NAN, Z=NEWVAL, or Q=ADDVAL - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); // Ping ExtUI in case it's showing the mesh + bool did_something = false; + // Mesh point modification + if (mesh_command) { + // Bad parameter combination: Must have exactly one of (C or both I+J) + if (int(hasC) + int(hasI && hasJ) != 1) { + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + return; + } + // Test for I J out of range + if (!WITHIN(ij.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(ij.y, 0, GRID_MAX_POINTS_Y - 1)) { + SERIAL_ERROR_MSG(STR_ERR_MESH_XY); + return; + } + // Apply Z/Q/N value + float &zval = bedlevel.z_values[ij.x][ij.y]; + zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); + did_something = true; + } + #if ENABLED(DYNAMIC_MARGINS) + auto safe_margin = [](int user_val, int min_required, const char axis_char) { + const int m = constrain(user_val, 0, 1000); + if (m < min_required) { + #if DISABLED(MARLIN_SMALL_BUILD) + SERIAL_ECHOPGM(" ! "); + SERIAL_CHAR(axis_char); + SERIAL_ECHOPGM(" margin too small ("); + SERIAL_ECHO(m); + SERIAL_ECHOPGM(" < "); + SERIAL_ECHO(min_required); + SERIAL_ECHOPGM("). "); + SERIAL_CHAR(axis_char); + SERIAL_ECHOPGM(" set to: "); + SERIAL_ECHOLN(min_required); + #endif + return min_required; + } + return m; + }; + if (parser.seen('D')) { + // 'D' with no value resets all margins + bedlevel.margin_l = PROBING_MARGIN_LEFT; + bedlevel.margin_f = PROBING_MARGIN_RIGHT; + bedlevel.margin_r = PROBING_MARGIN_FRONT; + bedlevel.margin_b = PROBING_MARGIN_BACK; + #if DISABLED(MARLIN_SMALL_BUILD) + SERIAL_ECHOPGM(" Margins reset: L"); + SERIAL_ECHO(bedlevel.margin_l); + SERIAL_ECHOPGM(" R"); + SERIAL_ECHO(bedlevel.margin_r); + SERIAL_ECHOPGM(" F"); + SERIAL_ECHO(bedlevel.margin_f); + SERIAL_ECHOPGM(" B"); + SERIAL_ECHO(bedlevel.margin_b); + SERIAL_EOL(); + #endif + did_something = true; + } + if (parser.seen('L')) { + // Left margin: X_MIN_POS + probe_offset must reach >= 0, but min 10 + bedlevel.margin_l = safe_margin(parser.value_int(), constrain(_MAX(10, TERN(HAS_HOME_OFFSET, home_offset.x, X_MIN_POS) - ceilf(fabs(probe.offset_xy.x))), 10, X_BED_SIZE), 'L'); + did_something = true; + } + if (parser.seen('F')) { + // Front margin: Y_MIN_POS + probe_offset must reach >= 0, but min 10 + bedlevel.margin_f = safe_margin(parser.value_int(), constrain(_MAX(10, TERN(HAS_HOME_OFFSET, home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y))), 10, Y_BED_SIZE), 'F'); + did_something = true; + } + if (parser.seen('R')) { + // Right margin: The probe must not exceed X_BED_SIZE, but min 10 + bedlevel.margin_r = safe_margin(parser.value_int(), constrain(_MAX(10, X_BED_SIZE - (X_MAX_POS - ceilf(fabs(probe.offset_xy.x)))), 10, X_BED_SIZE), 'R'); + did_something = true; + } + if (parser.seen('B')) { + // Back margin: The probe must not exceed Y_BED_SIZE, but min 10 + bedlevel.margin_b = safe_margin(parser.value_int(), constrain(_MAX(10, Y_BED_SIZE - (Y_MAX_POS - ceilf(fabs(probe.offset_xy.y)))), 10, Y_BED_SIZE), 'B'); + did_something = true; + } + #endif + if (!did_something) { + #if ENABLED(DYNAMIC_MARGINS) + M421_report(); + #else + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + #endif } } +#if ENABLED(DYNAMIC_MARGINS) + void GcodeSuite::M421_report(const bool forReplay/*=true*/) { + TERN_(MARLIN_SMALL_BUILD, return); + report_heading_etc(forReplay, F("Dynamic Margins")); + SERIAL_ECHOPGM(" M421 L"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_l)); + SERIAL_ECHOPGM(" R"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_r)); + SERIAL_ECHOPGM(" F"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_f)); + SERIAL_ECHOPGM(" B"); + SERIAL_ECHO(LINEAR_UNIT(bedlevel.margin_b)); + SERIAL_ECHOLNPGM(" ; Margins in mm"); + } +#endif #endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 319262c31e2f..4b6c73a7c5aa 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -1079,6 +1079,9 @@ class GcodeSuite { static void M420(); static void M420_report(const bool forReplay=true); static void M421(); + #if ENABLED(DYNAMIC_MARGINS) + static void M421_report(const bool forReplay=true); + #endif #endif #if ENABLED(BACKLASH_GCODE) diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index 76957eb1d4ed..f889dc02a803 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -36,6 +36,17 @@ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" +#if ENABLED(DYNAMIC_MARGINS) + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + #include "../feature/bedlevel/abl/bbl.h" + extern LevelingBilinear bedlevel; + #endif + #if ENABLED(AUTO_BED_LEVELING_UBL) + #include "../feature/bedlevel/ubl/ubl.h" + extern unified_bed_leveling bedlevel; + #endif +#endif + #if HAS_BED_PROBE enum ProbePtRaise : uint8_t { PROBE_PT_NONE, // No raise or stow after run_z_probe @@ -102,18 +113,33 @@ class Probe { // Note: This won't work on SCARA since the probe offset rotates with the arm. static bool can_reach(const float rx, const float ry, const bool probe_relative=true) { if (probe_relative) { - return position_is_reachable(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go? - && position_is_reachable(rx, ry, PROBING_MARGIN); // Can the probe also go near there? + #if ENABLED(DYNAMIC_MARGINS) + return position_is_reachable(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go? + && position_is_reachable(rx, ry, bedlevel.margin_l); // Can the probe also go near there? + #else + return position_is_reachable(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go? + && position_is_reachable(rx, ry, PROBING_MARGIN); // Can the probe also go near there? + #endif } else { - return position_is_reachable(rx, ry) - && position_is_reachable(rx + offset_xy.x, ry + offset_xy.y, PROBING_MARGIN); + #if ENABLED(DYNAMIC_MARGINS) + return position_is_reachable(rx, ry) + && position_is_reachable(rx + offset_xy.x, ry + offset_xy.y, bedlevel.margin_r); + #else + return position_is_reachable(rx, ry) + && position_is_reachable(rx + offset_xy.x, ry + offset_xy.y, PROBING_MARGIN); + #endif } } #else static bool can_reach(const float rx, const float ry, const bool=true) { - return position_is_reachable(rx, ry) + #if ENABLED(DYNAMIC_MARGINS) + return position_is_reachable(rx, ry) + && position_is_reachable(rx, ry, bedlevel.margin_l); + #else + return position_is_reachable(rx, ry) && position_is_reachable(rx, ry, PROBING_MARGIN); + #endif } #endif @@ -240,9 +266,15 @@ class Probe { #if HAS_BED_PROBE || HAS_LEVELING #if IS_KINEMATIC - static constexpr float probe_radius(const xy_pos_t &probe_offset_xy=offset_xy) { - return float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y)); - } + #if ENABLED(DYNAMIC_MARGINS) + static float probe_radius(const xy_pos_t &probe_offset_xy = offset_xy) { + return float(PRINTABLE_RADIUS) - _MAX(bedlevel.margin_l, HYPOT(probe_offset_xy.x, probe_offset_xy.y)); + } + #else + static constexpr float probe_radius(const xy_pos_t &probe_offset_xy = offset_xy) { + return float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y)); + } + #endif #endif /** @@ -254,30 +286,58 @@ class Probe { * close it can get the RIGHT edge of the bed (unless the nozzle is able move * far enough past the right edge). */ - static constexpr float _min_x(const xy_pos_t &probe_offset_xy=offset_xy) { - return TERN(IS_KINEMATIC, - (X_CENTER) - probe_radius(probe_offset_xy), - _MAX((X_MIN_BED) + (PROBING_MARGIN_LEFT), (X_MIN_POS) + probe_offset_xy.x) - ); - } - static constexpr float _max_x(const xy_pos_t &probe_offset_xy=offset_xy) { - return TERN(IS_KINEMATIC, - (X_CENTER) + probe_radius(probe_offset_xy), - _MIN((X_MAX_BED) - (PROBING_MARGIN_RIGHT), (X_MAX_POS) + probe_offset_xy.x) - ); - } - static constexpr float _min_y(const xy_pos_t &probe_offset_xy=offset_xy) { - return TERN(IS_KINEMATIC, - (Y_CENTER) - probe_radius(probe_offset_xy), - _MAX((Y_MIN_BED) + (PROBING_MARGIN_FRONT), (Y_MIN_POS) + probe_offset_xy.y) - ); - } - static constexpr float _max_y(const xy_pos_t &probe_offset_xy=offset_xy) { - return TERN(IS_KINEMATIC, - (Y_CENTER) + probe_radius(probe_offset_xy), - _MIN((Y_MAX_BED) - (PROBING_MARGIN_BACK), (Y_MAX_POS) + probe_offset_xy.y) - ); - } + #if DISABLED(DYNAMIC_MARGINS) + static constexpr float _min_x(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (X_CENTER) - (float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y))), + _MAX((X_MIN_BED) + PROBING_MARGIN_LEFT, (X_MIN_POS) + probe_offset_xy.x) + ); + } + static constexpr float _max_x(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (X_CENTER) + (float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y))), + _MIN((X_MAX_BED) - PROBING_MARGIN_RIGHT, (X_MAX_POS) + probe_offset_xy.x) + ); + } + static constexpr float _min_y(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (Y_CENTER) - (float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y))), + _MAX((Y_MIN_BED) + PROBING_MARGIN_FRONT, (Y_MIN_POS) + probe_offset_xy.y) + ); + } + static constexpr float _max_y(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (Y_CENTER) + (float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y))), + _MIN((Y_MAX_BED) - PROBING_MARGIN_BACK, (Y_MAX_POS) + probe_offset_xy.y) + ); + } + #endif + #if ENABLED(DYNAMIC_MARGINS) + static float _min_x(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (X_CENTER) - probe_radius(probe_offset_xy), + _MAX((X_MIN_BED) + bedlevel.margin_l, (X_MIN_POS) + probe_offset_xy.x) + ); + } + static float _max_x(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (X_CENTER) + probe_radius(probe_offset_xy), + _MIN((X_MAX_BED) - bedlevel.margin_r, (X_MAX_POS) + probe_offset_xy.x) + ); + } + static float _min_y(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (Y_CENTER) - probe_radius(probe_offset_xy), + _MAX((Y_MIN_BED) + bedlevel.margin_f, (Y_MIN_POS) + probe_offset_xy.y) + ); + } + static float _max_y(const xy_pos_t &probe_offset_xy = offset_xy) { + return TERN(IS_KINEMATIC, + (Y_CENTER) + probe_radius(probe_offset_xy), + _MIN((Y_MAX_BED) - bedlevel.margin_b, (Y_MAX_POS) + probe_offset_xy.y) + ); + } + #endif static float min_x() { return _min_x() TERN_(NOZZLE_AS_PROBE, TERN_(HAS_HOME_OFFSET, - home_offset.x)); } static float max_x() { return _max_x() TERN_(NOZZLE_AS_PROBE, TERN_(HAS_HOME_OFFSET, - home_offset.x)); } @@ -286,6 +346,10 @@ class Probe { // constexpr helpers used in build-time static_asserts, relying on default probe offsets. class build_time { + #if ENABLED(DYNAMIC_MARGINS) + static xyz_pos_t default_probe_xyz_offset; + static xy_pos_t default_probe_xy_offset; + #else static constexpr xyz_pos_t default_probe_xyz_offset = xyz_pos_t( #if HAS_BED_PROBE NOZZLE_TO_PROBE_OFFSET @@ -293,10 +357,12 @@ class Probe { { 0 } #endif ); - static constexpr xy_pos_t default_probe_xy_offset = xy_pos_t({ default_probe_xyz_offset.x, default_probe_xyz_offset.y }); + static constexpr xy_pos_t default_probe_xy_offset = xy_pos_t({ default_probe_xyz_offset.x, default_probe_xyz_offset.y }); + #endif public: - static constexpr bool can_reach(float x, float y) { + + TERN(DYNAMIC_MARGINS, static, static constexpr) bool can_reach(float x, float y) { #if IS_KINEMATIC return HYPOT2(x, y) <= sq(probe_radius(default_probe_xy_offset)); #else @@ -305,7 +371,7 @@ class Probe { #endif } - static constexpr bool can_reach(const xy_pos_t &point) { return can_reach(point.x, point.y); } + TERN(DYNAMIC_MARGINS, static, static constexpr) bool can_reach(const xy_pos_t &point) { return can_reach(point.x, point.y); } }; #if NEEDS_THREE_PROBE_POINTS @@ -327,9 +393,9 @@ class Probe { points[1] = xy_float_t({ (X_CENTER) + probe_radius() * COS120, (Y_CENTER) + probe_radius() * SIN120 }); points[2] = xy_float_t({ (X_CENTER) + probe_radius() * COS240, (Y_CENTER) + probe_radius() * SIN240 }); #elif ENABLED(AUTO_BED_LEVELING_UBL) - points[0] = xy_float_t({ _MAX(float(MESH_MIN_X), min_x()), _MAX(float(MESH_MIN_Y), min_y()) }); - points[1] = xy_float_t({ _MIN(float(MESH_MAX_X), max_x()), _MAX(float(MESH_MIN_Y), min_y()) }); - points[2] = xy_float_t({ (_MAX(float(MESH_MIN_X), min_x()) + _MIN(float(MESH_MAX_X), max_x())) / 2, _MIN(float(MESH_MAX_Y), max_y()) }); + points[0] = xy_float_t({ _MAX(TERN(DYNAMIC_MARGINS, float(bedlevel.margin_l), float(MESH_MIN_X)), min_x()), _MAX(TERN(DYNAMIC_MARGINS, float(bedlevel.margin_r), float(MESH_MIN_Y)), min_y()) }); + points[1] = xy_float_t({ _MIN(TERN(DYNAMIC_MARGINS, float(X_BED_SIZE - bedlevel.margin_l), float(MESH_MAX_X)), max_x()), _MAX(TERN(DYNAMIC_MARGINS, float(bedlevel.margin_r), float(MESH_MIN_Y)), min_y()) }); + points[2] = xy_float_t({ (_MAX(TERN(DYNAMIC_MARGINS, float(bedlevel.margin_l), float(MESH_MIN_X)), min_x()) + _MIN(TERN(DYNAMIC_MARGINS, float(X_BED_SIZE - bedlevel.margin_r), float(MESH_MAX_X)), max_x())) / 2, _MIN(TERN(DYNAMIC_MARGINS, float(Y_BED_SIZE - bedlevel.margin_f), float(MESH_MAX_Y)), max_y()) }); #else points[0] = xy_float_t({ min_x(), min_y() }); points[1] = xy_float_t({ max_x(), min_y() }); diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp index e72a2d14ac09..7a8d42917b17 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -302,6 +302,9 @@ typedef struct SettingsDataStruct { uint8_t grid_max_x, grid_max_y; // GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y uint16_t grid_check; // Hash to check against X/Y xy_pos_t bilinear_grid_spacing, bilinear_start; // G29 L F + #if ENABLED(DYNAMIC_MARGINS) + int16_t margin_l, margin_r, margin_f, margin_b; + #endif #if ENABLED(AUTO_BED_LEVELING_BILINEAR) bed_mesh_t z_values; // G29 #else @@ -1111,6 +1114,12 @@ void MarlinSettings::postprocess() { dummyf = 0; for (uint16_t q = grid_max_x * grid_max_y; q--;) EEPROM_WRITE(dummyf); #endif + #if ANY(AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) && ENABLED(DYNAMIC_MARGINS) + EEPROM_WRITE(bedlevel.margin_l); + EEPROM_WRITE(bedlevel.margin_r); + EEPROM_WRITE(bedlevel.margin_f); + EEPROM_WRITE(bedlevel.margin_b); + #endif } // @@ -2167,6 +2176,12 @@ void MarlinSettings::postprocess() { else // EEPROM data is stale #endif // AUTO_BED_LEVELING_BILINEAR { + #if ANY(AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) && ENABLED(DYNAMIC_MARGINS) + EEPROM_READ(bedlevel.margin_l); + EEPROM_READ(bedlevel.margin_r); + EEPROM_READ(bedlevel.margin_f); + EEPROM_READ(bedlevel.margin_b); + #endif // Skip past disabled (or stale) Bilinear Grid data for (uint16_t q = grid_max_x * grid_max_y; q--;) EEPROM_READ(dummyf); } @@ -3414,6 +3429,8 @@ void MarlinSettings::reset() { TERN_(DWIN_CREALITY_LCD_JYERSUI, jyersDWIN.resetSettings()); + TERN_(DYNAMIC_MARGINS, { bedlevel.margin_l = PROBING_MARGIN_LEFT; bedlevel.margin_f = PROBING_MARGIN_FRONT; bedlevel.margin_r = PROBING_MARGIN_RIGHT; bedlevel.margin_b = PROBING_MARGIN_BACK; }); + // // Case Light Brightness // @@ -3910,6 +3927,10 @@ void MarlinSettings::reset() { gcode.M420_report(forReplay); + #if ENABLED(DYNAMIC_MARGINS) + gcode.M421_report(forReplay); + #endif + #if ENABLED(MESH_BED_LEVELING) if (leveling_is_valid()) {