This commit is contained in:
ThomasToka 2026-02-24 10:45:57 +01:00 committed by GitHub
commit d7d3ffd6ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 528 additions and 137 deletions

View file

@ -1120,7 +1120,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.
@ -2530,12 +2530,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)

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -64,22 +64,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;
@ -173,8 +177,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);
}
@ -231,8 +235,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();
}

View file

@ -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 ) __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 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 @@ 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 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 @@ 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"

View file

@ -444,7 +444,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)
motion.blocking_move_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y)));
motion.blocking_move_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
motion.report_position();
SET_PROBE_DEPLOYED(true);
@ -828,8 +828,8 @@ void unified_bed_leveling::shift_mesh_height(const float zoffs) {
probe.move_z_after_probing();
motion.blocking_move_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();
@ -895,8 +895,8 @@ void set_message_with_feedback(FSTR_P const fstr) {
motion.blocking_move(
xyz_pos_t({
0.5f * ((MESH_MIN_X) + (MESH_MAX_X)),
0.5f * ((MESH_MIN_Y) + (MESH_MAX_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
@ -1392,7 +1392,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!
@ -1535,10 +1535,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);
@ -1696,7 +1696,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);
@ -1760,14 +1760,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) {

View file

@ -84,8 +84,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]);
@ -414,8 +414,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);
@ -435,15 +435,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)
@ -451,8 +451,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
@ -471,7 +471,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

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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, 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, 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')) {
// 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

View file

@ -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<xindex> J<yindex> N : Set the Mesh Point IJ to NAN (not set)
* M421 C Z<linear> : Set the closest Mesh Point to the Z value
* M421 C Q<offset> : 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, motion.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, 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, 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')) {
// 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

View file

@ -1096,6 +1096,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)

View file

@ -40,6 +40,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
@ -111,18 +122,33 @@ public:
// 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 motion.can_reach(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go?
&& motion.can_reach(rx, ry, PROBING_MARGIN); // Can the probe also go near there?
#if ENABLED(DYNAMIC_MARGINS)
return motion.can_reach(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go?
&& motion.can_reach(rx, ry, bedlevel.margin_l); // Can the probe also go near there?
#else
return motion.can_reach(rx - offset_xy.x, ry - offset_xy.y) // The nozzle can go where it needs to go?
&& motion.can_reach(rx, ry, PROBING_MARGIN); // Can the probe also go near there?
#endif
}
else {
return motion.can_reach(rx, ry)
&& motion.can_reach(rx + offset_xy.x, ry + offset_xy.y, PROBING_MARGIN);
#if ENABLED(DYNAMIC_MARGINS)
return motion.can_reach(rx, ry)
&& motion.can_reach(rx + offset_xy.x, ry + offset_xy.y, bedlevel.margin_r);
#else
return motion.can_reach(rx, ry)
&& motion.can_reach(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 motion.can_reach(rx, ry)
#if ENABLED(DYNAMIC_MARGINS)
return motion.can_reach(rx, ry)
&& motion.can_reach(rx, ry, bedlevel.margin_l);
#else
return motion.can_reach(rx, ry)
&& motion.can_reach(rx, ry, PROBING_MARGIN);
#endif
}
#endif
@ -249,9 +275,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
/**
@ -263,30 +295,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, - motion.home_offset.x)); }
static float max_x() { return _max_x() TERN_(NOZZLE_AS_PROBE, TERN_(HAS_HOME_OFFSET, - motion.home_offset.x)); }
@ -295,6 +355,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
@ -302,10 +366,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
@ -314,7 +380,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
@ -336,9 +402,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() });

View file

@ -300,6 +300,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
@ -1113,6 +1116,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
}
//
@ -2180,6 +2189,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);
}
@ -3439,6 +3454,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
//
@ -3941,6 +3958,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()) {