From 46c5060140e6e82e95c01ab9dfedfe0c2c1a931f Mon Sep 17 00:00:00 2001 From: ThomasToka Date: Mon, 28 Jul 2025 16:29:53 +0200 Subject: [PATCH 1/2] DYNAMIC_MARGINS | DYNAMIC_TRAMMING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR introduces two powerful runtime features for bed leveling in Marlin: - **`DYNAMIC_MARGINS`** — Enables live configuration of the bed probing area, dynamically adapted to probe offsets and machine constraints. - **`DYNAMIC_TRAMMING`** — Automatically generates assisted tramming points based on the current margins and probe offset, removing the need for static configuration or recompilation. These features improve flexibility, safety, and usability for leveling and tramming — especially on machines with limited probe reach or off-center probes. --- - **Runtime control of probing area via `M421`:** - Example: `M421 L10 R10 F15 B20` sets probing margins directly. Keep L R the same if you want a centered mesh. - **Auto-correcting margin enforcement:** - Each value provided to `M421` is **validated and adjusted upward** if it is smaller than the minimum safe margin required by the probe offset and bed geometry. - For example: - If the minimum front margin is 25 mm due to probe offset, then both `M421 F0` and `M421 F15` will result in `F25`. - Ensures that the probe will **never be commanded outside reachable areas**. A **minimum margin of 10 mm** is always enforced, or higher if required based on the user’s input and physical limits. - `M421 L0 R0 F0 B0` triggers full auto-recalculation of all margins based on current probe offsets and usable bed area. - **Optional margin reset:** - `M421 D`resets the margins to their defaults (PROBING_MARGIN_LEFT,PROBING_MARGIN_RIGHT,PROBING_MARGIN_FRONT, PROBING_MARGIN_BACK) - **Fully integrated into:** - **Unified Bed Leveling (UBL)**: mesh probing, interpolation, and display. - **Bilinear Leveling**: mesh creation, probing boundaries, and `M420 V` output. - **Safe and dynamic mesh access:** - All probing and interpolation logic clamps access to within valid bounds. - Prevents out-of-bounds errors or unreachable probe moves. - supports positive and negative probe offsets - respects HOME_OFFSETS if enabled - Enables automatic generation of assisted tramming points (`G35`) based on the **current margins**. - Removes the need to statically define `TRAMMING_POINT_XY` when `DYNAMIC_MARGINS` is enabled. - Reacts in real time to updated margins — no rebuild required. - Works seamlessly with `ASSISTED_TRAMMING`. - Adds feature flags: - `DYNAMIC_MARGINS` - `DYNAMIC_TRAMMING` - Saves and loads dynamic margin configuration from/to EEPROM. - Integrates margin and tramming state into persistent printer settings. - Modified to retrieve margin-aware tramming points if `DYNAMIC_TRAMMING` is active. - Assisted tramming logic now generates points from current margins if `DYNAMIC_TRAMMING` is enabled. - Fallbacks to static `TRAMMING_POINT_XY` only when dynamic margins are disabled. - includes M421_report if DYNAMIC_MARGINS are enabled. - `Configuration_adv.h`: Adds feature flags `DYNAMIC_MARGINS`, `DYNAMIC_TRAMMING`. - **UBL**: - `ubl.cpp`, `ubl.h`, `ubl_g29.cpp`, `ubl_motion.cpp`: Updated to support margin-aware mesh logic and probing bounds. - **Bilinear**: - `bbl.cpp`, `bbl.h`: Respect dynamic margins during mesh generation, probing, and display. - **Tramming**: - Assisted tramming points are calculated dynamically from current margins. --- ```gcode M421 L15 R15 F10 B10 ; Manually define all probing margins (reaccounting if to low) or M421 L15 R15 ; Manually set L R conditionally. or M421 L0 R0 F0 B0 ; Auto-calculate margins from probe offset and bed size ``` If the requested margin is too small (e.g., `F0`), the system will automatically **enforce a larger minimum**, such as `F25`, depending on the probe offset and physical constraints of the printer. --- ``` start PowerUp Marlin bugfix-2.1.x echo: Last Updated: 2025-06-29 | Author: (none, default config) echo: Compiled: Jul 28 2025 echo: Free Memory: 0 PlannerBufferBytes: 1536 echo:EEPROM version mismatch (EEPROM= Marlin=V90) echo:Hardcoded Default Settings Loaded [7] echo:busy: processing X:107.00 Y:107.00 Z:5.00 E:0.00 Count X:8560 Y:8560 Z:2000 ok echo:; Linear Units: echo: G21 ; (mm) echo:; Temperature Units: echo: M149 C ; Units in Celsius echo:; Filament settings (Disabled): echo: M200 S0 D1.75 echo:; Steps per unit: echo: M92 X80.00 Y80.00 Z400.00 E500.00 echo:; Max feedrates (units/s): echo: M203 X2000.00 Y2000.00 Z5.00 E1000.00 echo:; Max Acceleration (units/s2): echo: M201 X3000.00 Y3000.00 Z100.00 E10000.00 echo:; Acceleration (units/s2) (P R T): echo: M204 P3000.00 R3000.00 T3000.00 echo:; Advanced (B S T J): echo: M205 B20000.00 S0.00 T0.00 J0.01 echo:; Home offset: echo: M206 X0.00 Y0.00 Z0.00 echo:; Auto Bed Leveling: echo: M420 S0 Z10.00 ; Leveling OFF echo:; Dynamic Margins: echo: M421 L45.00 R45.00 F45.00 B45.00 ; Margins in mm echo:; Material heatup parameters: echo: M145 S0 H180.00 B70.00 F0 echo: M145 S1 H240.00 B110.00 F0 echo:; Hotend PID: echo: M301 P22.20 I1.08 D114.00 echo:; Z-Probe Offset: echo: M851 X10.00 Y10.00 Z0.00 ; (mm) ok M421 L0 R0 F0 B0 ! L margin too small (0 < 10). L set to: 10 ! F margin too small (0 < 10). F set to: 10 ! R margin too small (0 < 10). R set to: 10 ! B margin too small (0 < 10). B set to: 10 ok ``` Changing probe offsets to X=-31.5 Y-41.8, and recalculating min margins (X_MAX_POS=242, Y_MAX_POS=235, Bedsize 235x235): ``` M851 X-31.5 Y-41.8 M421 L0 R0 F0 B0 [2] ok ! L margin too small (0 < 10). L set to: 10 ! F margin too small (0 < 10). F set to: 10 ! R margin too small (0 < 25). R set to: 25 ! B margin too small (0 < 42). B set to: 42 ok ``` - ✅ Cartesian platforms (KINEMATIC not tested) - ✅ `M421` with both manual and automatic values - ✅ Correct auto-correction behavior for undersized margins - ✅ Optional margin reset via `M421 D` - ✅ UBL and Bilinear mesh generation within validated probing zones - ✅ `M420 V` output clipped to valid mesh area - ✅ `G35` assisted tramming following the current probing area - ✅ Safe operation across all margin combinations - ✅ Safe operation with positive and negative probe offsets - ✅ respects HOME_OFFSETS if enabled --- Many printers have probes that are offset from the nozzle or have restricted reach. Without this feature, users had to manually adjust and recompile constants like `PROBING_MARGIN` or `TRAMMING_POINT_XY`. These enhancements: - Eliminate the need to recompile when changing probing margins or tramming points. - Adapt automatically to the hardware layout of the printer. - Improve safety and usability during leveling and tramming. --- This implementation is isolated behind the `DYNAMIC_MARGINS` and `DYNAMIC_TRAMMING` feature flags and maintains full backward compatibility with existing configurations. It has been tested on **Cartesian platforms**. Kinematic (e.g., CoreXY, Delta) support is untested and can be addressed in follow-up work. Feedback and review are welcome! in the PR --- Marlin/Configuration_adv.h | 32 +++- Marlin/src/feature/bedlevel/abl/bbl.cpp | 3 + Marlin/src/feature/bedlevel/abl/bbl.h | 6 + Marlin/src/feature/bedlevel/hilbert_curve.cpp | 4 +- Marlin/src/feature/bedlevel/ubl/ubl.cpp | 40 ++--- Marlin/src/feature/bedlevel/ubl/ubl.h | 74 ++++++--- Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 38 ++--- .../src/feature/bedlevel/ubl/ubl_motion.cpp | 20 +-- Marlin/src/feature/tramming.cpp | 3 + Marlin/src/feature/tramming.h | 22 ++- Marlin/src/gcode/bedlevel/G35.cpp | 16 ++ Marlin/src/gcode/bedlevel/abl/M421.cpp | 109 +++++++++++++- Marlin/src/gcode/bedlevel/ubl/M421.cpp | 132 ++++++++++++++-- Marlin/src/gcode/gcode.h | 3 + Marlin/src/module/probe.h | 142 +++++++++++++----- Marlin/src/module/settings.cpp | 21 +++ 16 files changed, 528 insertions(+), 137 deletions(-) diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 1cd89a9714..085be8eef2 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. @@ -2448,12 +2448,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 14c4bd24bc..38e1df9806 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 ca2e96593f..dc1733a50d 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 57cbdfb34d..721c210147 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 0228bd247e..7e2c01c3c8 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 84ecc3a6c7..d9ca4ffe3e 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 @@ public: static void smart_fill_wlsf(const_float_t ) __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 @@ public: FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const_float_t z) { z_values[px][py] = z; } static int8_t cell_index_x_raw(const_float_t 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_t 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_t x) { return WITHIN(cell_index_x_raw(x), 0, GRID_MAX_CELLS_X - 1); @@ -162,11 +173,11 @@ public: 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_t 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_t 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 @@ public: 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 @@ public: 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 @@ public: * 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 @@ public: 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 @@ public: }; // 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 e6f93a001b..5e66ffbd45 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -446,7 +446,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); @@ -830,8 +830,8 @@ void unified_bed_leveling::shift_mesh_height() { 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(); @@ -897,8 +897,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 @@ -1394,7 +1394,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! @@ -1537,10 +1537,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); @@ -1698,7 +1698,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); @@ -1762,14 +1762,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 053a68b77d..edb978d818 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 3721c5eb81..e9ca982cb7 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 de4c6c020c..94ead4d9af 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 b3c56b49b3..8d0dff019b 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 f66d023190..1b5dcaf88f 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 99ba3ce19b..9eef0d26b9 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 0e5d8f5681..df0aab17b7 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -1078,6 +1078,9 @@ private: 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 08a02b4d40..8c28598ff5 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 @@ public: // Note: This won't work on SCARA since the probe offset rotates with the arm. static bool can_reach(const_float_t rx, const_float_t 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_t rx, const_float_t 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 @@ public: #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 @@ public: * 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 @@ public: // 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 @@ public: { 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 @@ public: #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 @@ public: 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 8c3fae1393..60d5c4707f 100644 --- a/Marlin/src/module/settings.cpp +++ b/Marlin/src/module/settings.cpp @@ -301,6 +301,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 @@ -1103,6 +1106,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 } // @@ -2153,6 +2162,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); } @@ -3399,6 +3414,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 // @@ -3895,6 +3912,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()) { From 19f0f07d76b99b4fde3bc052e3fbd43d45613720 Mon Sep 17 00:00:00 2001 From: ThomasToka <117008525+ThomasToka@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:45:45 +0100 Subject: [PATCH 2/2] upstream merge fix this fixes the missing changes from #27866 Motion encapsulation --- Marlin/src/gcode/bedlevel/abl/M421.cpp | 4 ++-- Marlin/src/gcode/bedlevel/ubl/M421.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Marlin/src/gcode/bedlevel/abl/M421.cpp b/Marlin/src/gcode/bedlevel/abl/M421.cpp index 1b5dcaf88f..5b7a0b925b 100644 --- a/Marlin/src/gcode/bedlevel/abl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/abl/M421.cpp @@ -130,12 +130,12 @@ void GcodeSuite::M421() { } 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'); + bedlevel.margin_l = safe_margin(parser.value_int(), constrain(((TERN(HAS_HOME_OFFSET, motion.home_offset.x, X_MIN_POS) - ceilf(fabs(probe.offset_xy.x))) <= 0 ? 10 : (TERN(HAS_HOME_OFFSET, motion.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'); + bedlevel.margin_f = safe_margin(parser.value_int(), constrain(((TERN(HAS_HOME_OFFSET, motion.home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y))) <= 0 ? 10 : (TERN(HAS_HOME_OFFSET, motion.home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y)))), 10, Y_BED_SIZE), 'F'); did_something = true; } if (parser.seen('R')) { diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp index 9eef0d26b9..b546c3531b 100644 --- a/Marlin/src/gcode/bedlevel/ubl/M421.cpp +++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp @@ -74,7 +74,7 @@ void GcodeSuite::M421() { 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); + if (hasC) ij = bedlevel.find_closest_mesh_point_of_type(CLOSEST, motion.position); bool did_something = false; // Mesh point modification if (mesh_command) { @@ -135,12 +135,12 @@ void GcodeSuite::M421() { } 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'); + bedlevel.margin_l = safe_margin(parser.value_int(), constrain(_MAX(10, TERN(HAS_HOME_OFFSET, motion.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'); + bedlevel.margin_f = safe_margin(parser.value_int(), constrain(_MAX(10, TERN(HAS_HOME_OFFSET, motion.home_offset.y, Y_MIN_POS) - ceilf(fabs(probe.offset_xy.y))), 10, Y_BED_SIZE), 'F'); did_something = true; } if (parser.seen('R')) {