Add custom types for position (#15204)

This commit is contained in:
Scott Lahteine
2019-09-29 04:25:39 -05:00
committed by GitHub
parent 43d6e9fa43
commit 50e4545255
227 changed files with 3147 additions and 3264 deletions

View File

@ -35,9 +35,9 @@
#include "../../../lcd/extensible_ui/ui_api.h"
#endif
int bilinear_grid_spacing[2], bilinear_start[2];
float bilinear_grid_factor[2],
z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
xy_int_t bilinear_grid_spacing, bilinear_start;
xy_float_t bilinear_grid_factor;
bed_mesh_t z_values;
/**
* Extrapolate a single point from its neighbors
@ -153,8 +153,8 @@ void print_bilinear_leveling_grid() {
#define ABL_TEMP_POINTS_X (GRID_MAX_POINTS_X + 2)
#define ABL_TEMP_POINTS_Y (GRID_MAX_POINTS_Y + 2)
float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y];
int bilinear_grid_spacing_virt[2] = { 0 };
float bilinear_grid_factor_virt[2] = { 0 };
xy_int_t bilinear_grid_spacing_virt;
xy_float_t bilinear_grid_factor_virt;
void print_bilinear_leveling_grid_virt() {
SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:");
@ -207,7 +207,7 @@ void print_bilinear_leveling_grid() {
+ p[i] * (2 - 5 * sq(t) + 3 * t * sq(t))
+ p[i+1] * t * (1 + 4 * t - 3 * sq(t))
- p[i+2] * sq(t) * (1 - t)
) * 0.5;
) * 0.5f;
}
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const float &tx, const float &ty) {
@ -222,10 +222,8 @@ void print_bilinear_leveling_grid() {
}
void bed_level_virt_interpolate() {
bilinear_grid_spacing_virt[X_AXIS] = bilinear_grid_spacing[X_AXIS] / (BILINEAR_SUBDIVISIONS);
bilinear_grid_spacing_virt[Y_AXIS] = bilinear_grid_spacing[Y_AXIS] / (BILINEAR_SUBDIVISIONS);
bilinear_grid_factor_virt[X_AXIS] = RECIPROCAL(bilinear_grid_spacing_virt[X_AXIS]);
bilinear_grid_factor_virt[Y_AXIS] = RECIPROCAL(bilinear_grid_spacing_virt[Y_AXIS]);
bilinear_grid_spacing_virt = bilinear_grid_spacing / (BILINEAR_SUBDIVISIONS);
bilinear_grid_factor_virt = bilinear_grid_spacing_virt.reciprocal();
for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++)
for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++)
for (uint8_t ty = 0; ty < BILINEAR_SUBDIVISIONS; ty++)
@ -245,40 +243,38 @@ void print_bilinear_leveling_grid() {
// Refresh after other values have been updated
void refresh_bed_level() {
bilinear_grid_factor[X_AXIS] = RECIPROCAL(bilinear_grid_spacing[X_AXIS]);
bilinear_grid_factor[Y_AXIS] = RECIPROCAL(bilinear_grid_spacing[Y_AXIS]);
bilinear_grid_factor = bilinear_grid_spacing.reciprocal();
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
bed_level_virt_interpolate();
#endif
}
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
#define ABL_BG_SPACING(A) bilinear_grid_spacing_virt[A]
#define ABL_BG_FACTOR(A) bilinear_grid_factor_virt[A]
#define ABL_BG_SPACING(A) bilinear_grid_spacing_virt.A
#define ABL_BG_FACTOR(A) bilinear_grid_factor_virt.A
#define ABL_BG_POINTS_X ABL_GRID_POINTS_VIRT_X
#define ABL_BG_POINTS_Y ABL_GRID_POINTS_VIRT_Y
#define ABL_BG_GRID(X,Y) z_values_virt[X][Y]
#else
#define ABL_BG_SPACING(A) bilinear_grid_spacing[A]
#define ABL_BG_FACTOR(A) bilinear_grid_factor[A]
#define ABL_BG_SPACING(A) bilinear_grid_spacing.A
#define ABL_BG_FACTOR(A) bilinear_grid_factor.A
#define ABL_BG_POINTS_X GRID_MAX_POINTS_X
#define ABL_BG_POINTS_Y GRID_MAX_POINTS_Y
#define ABL_BG_GRID(X,Y) z_values[X][Y]
#endif
// Get the Z adjustment for non-linear bed leveling
float bilinear_z_offset(const float raw[XYZ]) {
float bilinear_z_offset(const xy_pos_t &raw) {
static float z1, d2, z3, d4, L, D, ratio_x, ratio_y,
last_x = -999.999, last_y = -999.999;
static float z1, d2, z3, d4, L, D;
static xy_pos_t prev { -999.999, -999.999 }, ratio;
// Whole units for the grid line indices. Constrained within bounds.
static int8_t gridx, gridy, nextx, nexty,
last_gridx = -99, last_gridy = -99;
static xy_int8_t thisg, nextg, lastg { -99, -99 };
// XY relative to the probed area
const float rx = raw[X_AXIS] - bilinear_start[X_AXIS],
ry = raw[Y_AXIS] - bilinear_start[Y_AXIS];
xy_pos_t rel = raw - bilinear_start.asFloat();
#if ENABLED(EXTRAPOLATE_BEYOND_GRID)
#define FAR_EDGE_OR_BOX 2 // Keep using the last grid box
@ -286,63 +282,62 @@ float bilinear_z_offset(const float raw[XYZ]) {
#define FAR_EDGE_OR_BOX 1 // Just use the grid far edge
#endif
if (last_x != rx) {
last_x = rx;
ratio_x = rx * ABL_BG_FACTOR(X_AXIS);
const float gx = constrain(FLOOR(ratio_x), 0, ABL_BG_POINTS_X - (FAR_EDGE_OR_BOX));
ratio_x -= gx; // Subtract whole to get the ratio within the grid box
if (prev.x != rel.x) {
prev.x = rel.x;
ratio.x = rel.x * ABL_BG_FACTOR(x);
const float gx = constrain(FLOOR(ratio.x), 0, ABL_BG_POINTS_X - (FAR_EDGE_OR_BOX));
ratio.x -= gx; // Subtract whole to get the ratio within the grid box
#if DISABLED(EXTRAPOLATE_BEYOND_GRID)
// Beyond the grid maintain height at grid edges
NOLESS(ratio_x, 0); // Never < 0.0. (> 1.0 is ok when nextx==gridx.)
NOLESS(ratio.x, 0); // Never <0 (>1 is ok when nextg.x==thisg.x)
#endif
gridx = gx;
nextx = _MIN(gridx + 1, ABL_BG_POINTS_X - 1);
thisg.x = gx;
nextg.x = _MIN(thisg.x + 1, ABL_BG_POINTS_X - 1);
}
if (last_y != ry || last_gridx != gridx) {
if (prev.y != rel.y || lastg.x != thisg.x) {
if (last_y != ry) {
last_y = ry;
ratio_y = ry * ABL_BG_FACTOR(Y_AXIS);
const float gy = constrain(FLOOR(ratio_y), 0, ABL_BG_POINTS_Y - (FAR_EDGE_OR_BOX));
ratio_y -= gy;
if (prev.y != rel.y) {
prev.y = rel.y;
ratio.y = rel.y * ABL_BG_FACTOR(y);
const float gy = constrain(FLOOR(ratio.y), 0, ABL_BG_POINTS_Y - (FAR_EDGE_OR_BOX));
ratio.y -= gy;
#if DISABLED(EXTRAPOLATE_BEYOND_GRID)
// Beyond the grid maintain height at grid edges
NOLESS(ratio_y, 0); // Never < 0.0. (> 1.0 is ok when nexty==gridy.)
NOLESS(ratio.y, 0); // Never < 0.0. (> 1.0 is ok when nextg.y==thisg.y.)
#endif
gridy = gy;
nexty = _MIN(gridy + 1, ABL_BG_POINTS_Y - 1);
thisg.y = gy;
nextg.y = _MIN(thisg.y + 1, ABL_BG_POINTS_Y - 1);
}
if (last_gridx != gridx || last_gridy != gridy) {
last_gridx = gridx;
last_gridy = gridy;
if (lastg != thisg) {
lastg = thisg;
// Z at the box corners
z1 = ABL_BG_GRID(gridx, gridy); // left-front
d2 = ABL_BG_GRID(gridx, nexty) - z1; // left-back (delta)
z3 = ABL_BG_GRID(nextx, gridy); // right-front
d4 = ABL_BG_GRID(nextx, nexty) - z3; // right-back (delta)
z1 = ABL_BG_GRID(thisg.x, thisg.y); // left-front
d2 = ABL_BG_GRID(thisg.x, nextg.y) - z1; // left-back (delta)
z3 = ABL_BG_GRID(nextg.x, thisg.y); // right-front
d4 = ABL_BG_GRID(nextg.x, nextg.y) - z3; // right-back (delta)
}
// Bilinear interpolate. Needed since ry or gridx has changed.
L = z1 + d2 * ratio_y; // Linear interp. LF -> LB
const float R = z3 + d4 * ratio_y; // Linear interp. RF -> RB
// Bilinear interpolate. Needed since rel.y or thisg.x has changed.
L = z1 + d2 * ratio.y; // Linear interp. LF -> LB
const float R = z3 + d4 * ratio.y; // Linear interp. RF -> RB
D = R - L;
}
const float offset = L + ratio_x * D; // the offset almost always changes
const float offset = L + ratio.x * D; // the offset almost always changes
/*
static float last_offset = 0;
if (ABS(last_offset - offset) > 0.2) {
SERIAL_ECHOLNPAIR("Sudden Shift at x=", rx, " / ", bilinear_grid_spacing[X_AXIS], " -> gridx=", gridx);
SERIAL_ECHOLNPAIR(" y=", ry, " / ", bilinear_grid_spacing[Y_AXIS], " -> gridy=", gridy);
SERIAL_ECHOLNPAIR(" ratio_x=", ratio_x, " ratio_y=", ratio_y);
SERIAL_ECHOLNPAIR("Sudden Shift at x=", rel.x, " / ", bilinear_grid_spacing.x, " -> thisg.x=", thisg.x);
SERIAL_ECHOLNPAIR(" y=", rel.y, " / ", bilinear_grid_spacing.y, " -> thisg.y=", thisg.y);
SERIAL_ECHOLNPAIR(" ratio.x=", ratio.x, " ratio.y=", ratio.y);
SERIAL_ECHOLNPAIR(" z1=", z1, " z2=", z2, " z3=", z3, " z4=", z4);
SERIAL_ECHOLNPAIR(" L=", L, " R=", R, " offset=", offset);
}
@ -354,7 +349,7 @@ float bilinear_z_offset(const float raw[XYZ]) {
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)
#define CELL_INDEX(A,V) ((V - bilinear_start[_AXIS(A)]) * ABL_BG_FACTOR(_AXIS(A)))
#define CELL_INDEX(A,V) ((V - bilinear_start.A) * ABL_BG_FACTOR(A))
/**
* Prepare a bilinear-leveled linear move on Cartesian,
@ -362,62 +357,61 @@ float bilinear_z_offset(const float raw[XYZ]) {
*/
void bilinear_line_to_destination(const feedRate_t scaled_fr_mm_s, uint16_t x_splits, uint16_t y_splits) {
// Get current and destination cells for this line
int cx1 = CELL_INDEX(X, current_position[X_AXIS]),
cy1 = CELL_INDEX(Y, current_position[Y_AXIS]),
cx2 = CELL_INDEX(X, destination[X_AXIS]),
cy2 = CELL_INDEX(Y, destination[Y_AXIS]);
LIMIT(cx1, 0, ABL_BG_POINTS_X - 2);
LIMIT(cy1, 0, ABL_BG_POINTS_Y - 2);
LIMIT(cx2, 0, ABL_BG_POINTS_X - 2);
LIMIT(cy2, 0, ABL_BG_POINTS_Y - 2);
xy_int_t c1 { CELL_INDEX(x, current_position.x), CELL_INDEX(y, current_position.y) },
c2 { CELL_INDEX(x, destination.x), CELL_INDEX(y, destination.y) };
LIMIT(c1.x, 0, ABL_BG_POINTS_X - 2);
LIMIT(c1.y, 0, ABL_BG_POINTS_Y - 2);
LIMIT(c2.x, 0, ABL_BG_POINTS_X - 2);
LIMIT(c2.y, 0, ABL_BG_POINTS_Y - 2);
// Start and end in the same cell? No split needed.
if (cx1 == cx2 && cy1 == cy2) {
set_current_from_destination();
if (c1 == c2) {
current_position = destination;
line_to_current_position(scaled_fr_mm_s);
return;
}
#define LINE_SEGMENT_END(A) (current_position[_AXIS(A)] + (destination[_AXIS(A)] - current_position[_AXIS(A)]) * normalized_dist)
#define LINE_SEGMENT_END(A) (current_position.A + (destination.A - current_position.A) * normalized_dist)
float normalized_dist, end[XYZE];
const int8_t gcx = _MAX(cx1, cx2), gcy = _MAX(cy1, cy2);
float normalized_dist;
xyze_pos_t end;
const xy_int8_t gc { _MAX(c1.x, c2.x), _MAX(c1.y, c2.y) };
// Crosses on the X and not already split on this X?
// The x_splits flags are insurance against rounding errors.
if (cx2 != cx1 && TEST(x_splits, gcx)) {
if (c2.x != c1.x && TEST(x_splits, gc.x)) {
// Split on the X grid line
CBI(x_splits, gcx);
COPY(end, destination);
destination[X_AXIS] = bilinear_start[X_AXIS] + ABL_BG_SPACING(X_AXIS) * gcx;
normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]);
destination[Y_AXIS] = LINE_SEGMENT_END(Y);
CBI(x_splits, gc.x);
end = destination;
destination.x = bilinear_start.x + ABL_BG_SPACING(x) * gc.x;
normalized_dist = (destination.x - current_position.x) / (end.x - current_position.x);
destination.y = LINE_SEGMENT_END(y);
}
// Crosses on the Y and not already split on this Y?
else if (cy2 != cy1 && TEST(y_splits, gcy)) {
else if (c2.y != c1.y && TEST(y_splits, gc.y)) {
// Split on the Y grid line
CBI(y_splits, gcy);
COPY(end, destination);
destination[Y_AXIS] = bilinear_start[Y_AXIS] + ABL_BG_SPACING(Y_AXIS) * gcy;
normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]);
destination[X_AXIS] = LINE_SEGMENT_END(X);
CBI(y_splits, gc.y);
end = destination;
destination.y = bilinear_start.y + ABL_BG_SPACING(y) * gc.y;
normalized_dist = (destination.y - current_position.y) / (end.y - current_position.y);
destination.x = LINE_SEGMENT_END(x);
}
else {
// Must already have been split on these border(s)
// This should be a rare case.
set_current_from_destination();
current_position = destination;
line_to_current_position(scaled_fr_mm_s);
return;
}
destination[Z_AXIS] = LINE_SEGMENT_END(Z);
destination[E_AXIS] = LINE_SEGMENT_END(E);
destination.z = LINE_SEGMENT_END(z);
destination.e = LINE_SEGMENT_END(e);
// Do the split and look for more borders
bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
// Restore destination from stack
COPY(destination, end);
destination = end;
bilinear_line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
}

View File

@ -23,10 +23,10 @@
#include "../../../inc/MarlinConfigPre.h"
extern int bilinear_grid_spacing[2], bilinear_start[2];
extern float bilinear_grid_factor[2],
z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
float bilinear_z_offset(const float raw[XYZ]);
extern xy_int_t bilinear_grid_spacing, bilinear_start;
extern xy_float_t bilinear_grid_factor;
extern bed_mesh_t z_values;
float bilinear_z_offset(const xy_pos_t &raw);
void extrapolate_unprobed_bed_level();
void print_bilinear_leveling_grid();
@ -40,6 +40,6 @@ void refresh_bed_level();
void bilinear_line_to_destination(const feedRate_t &scaled_fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF);
#endif
#define _GET_MESH_X(I) (bilinear_start[X_AXIS] + (I) * bilinear_grid_spacing[X_AXIS])
#define _GET_MESH_Y(J) (bilinear_start[Y_AXIS] + (J) * bilinear_grid_spacing[Y_AXIS])
#define _GET_MESH_X(I) float(bilinear_start.x + (I) * bilinear_grid_spacing.x)
#define _GET_MESH_Y(J) float(bilinear_start.y + (J) * bilinear_grid_spacing.y)
#define Z_VALUES_ARR z_values

View File

@ -51,7 +51,7 @@ bool leveling_is_valid() {
#if ENABLED(MESH_BED_LEVELING)
mbl.has_mesh()
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
!!bilinear_grid_spacing[X_AXIS]
!!bilinear_grid_spacing.x
#elif ENABLED(AUTO_BED_LEVELING_UBL)
ubl.mesh_is_valid()
#else // 3POINT, LINEAR
@ -81,13 +81,13 @@ void set_bed_leveling_enabled(const bool enable/*=true*/) {
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
// Force bilinear_z_offset to re-calculate next time
const float reset[XYZ] = { -9999.999, -9999.999, 0 };
const xyz_pos_t reset { -9999.999, -9999.999, 0 };
(void)bilinear_z_offset(reset);
#endif
if (planner.leveling_active) { // leveling from on to off
// change unleveled current_position to physical current_position without moving steppers.
planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
planner.apply_leveling(current_position);
planner.leveling_active = false; // disable only AFTER calling apply_leveling
}
else { // leveling from off to on
@ -116,9 +116,9 @@ TemporaryBedLevelingState::TemporaryBedLevelingState(const bool enable) : saved(
planner.set_z_fade_height(zfh);
if (leveling_was_active) {
const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] };
const xyz_pos_t oldpos = current_position;
set_bed_leveling_enabled(true);
if (do_report && memcmp(oldpos, current_position, sizeof(oldpos)))
if (do_report && oldpos != current_position)
report_current_position();
}
}
@ -137,8 +137,8 @@ void reset_bed_level() {
#if ENABLED(MESH_BED_LEVELING)
mbl.reset();
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
bilinear_start[X_AXIS] = bilinear_start[Y_AXIS] =
bilinear_grid_spacing[X_AXIS] = bilinear_grid_spacing[Y_AXIS] = 0;
bilinear_start.reset();
bilinear_grid_spacing.reset();
for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++)
for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) {
z_values[x][y] = NAN;
@ -223,25 +223,25 @@ void reset_bed_level() {
#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY)
void _manual_goto_xy(const float &rx, const float &ry) {
void _manual_goto_xy(const xy_pos_t &pos) {
#ifdef MANUAL_PROBE_START_Z
constexpr float startz = _MAX(0, MANUAL_PROBE_START_Z);
#if MANUAL_PROBE_HEIGHT > 0
do_blocking_move_to(rx, ry, MANUAL_PROBE_HEIGHT);
do_blocking_move_to_z(_MAX(0,MANUAL_PROBE_START_Z));
do_blocking_move_to(pos, MANUAL_PROBE_HEIGHT);
do_blocking_move_to_z(startz);
#else
do_blocking_move_to(rx, ry, _MAX(0,MANUAL_PROBE_START_Z));
do_blocking_move_to(pos, startz);
#endif
#elif MANUAL_PROBE_HEIGHT > 0
const float prev_z = current_position[Z_AXIS];
do_blocking_move_to(rx, ry, MANUAL_PROBE_HEIGHT);
const float prev_z = current_position.z;
do_blocking_move_to(pos, MANUAL_PROBE_HEIGHT);
do_blocking_move_to_z(prev_z);
#else
do_blocking_move_to_xy(rx, ry);
do_blocking_move_to_xy(pos);
#endif
current_position[X_AXIS] = rx;
current_position[Y_AXIS] = ry;
current_position = pos;
#if ENABLED(LCD_BED_LEVELING)
ui.wait_for_bl_move = false;

View File

@ -38,7 +38,7 @@ void reset_bed_level();
#endif
#if EITHER(MESH_BED_LEVELING, PROBE_MANUALLY)
void _manual_goto_xy(const float &x, const float &y);
void _manual_goto_xy(const xy_pos_t &pos);
#endif
/**
@ -57,11 +57,6 @@ class TemporaryBedLevelingState {
typedef float bed_mesh_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
typedef struct {
int8_t x_index, y_index;
float distance; // When populated, the distance from the search location
} mesh_index_pair;
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
#include "abl/abl.h"
#elif ENABLED(AUTO_BED_LEVELING_UBL)
@ -71,6 +66,7 @@ class TemporaryBedLevelingState {
#endif
#define Z_VALUES(X,Y) Z_VALUES_ARR[X][Y]
#define _GET_MESH_POS(M) { _GET_MESH_X(M.a), _GET_MESH_Y(M.b) }
#if EITHER(AUTO_BED_LEVELING_BILINEAR, MESH_BED_LEVELING)
@ -85,4 +81,18 @@ class TemporaryBedLevelingState {
#endif
struct mesh_index_pair {
xy_int8_t pos;
float distance; // When populated, the distance from the search location
void invalidate() { pos = -1; }
bool valid() const { return pos.x >= 0 && pos.y >= 0; }
#if ENABLED(AUTO_BED_LEVELING_UBL)
xy_pos_t meshpos() {
return { ubl.mesh_index_to_xpos(pos.x), ubl.mesh_index_to_ypos(pos.y) };
}
#endif
operator xy_int8_t&() { return pos; }
operator const xy_int8_t&() const { return pos; }
};
#endif

View File

@ -24,10 +24,9 @@
#if ENABLED(MESH_BED_LEVELING)
#include "mesh_bed_leveling.h"
#include "../bedlevel.h"
#include "../../../module/motion.h"
#include "../../../feature/bedlevel/bedlevel.h"
#if ENABLED(EXTENSIBLE_UI)
#include "../../../lcd/extensible_ui/ui_api.h"
@ -66,62 +65,60 @@
*/
void mesh_bed_leveling::line_to_destination(const feedRate_t &scaled_fr_mm_s, uint8_t x_splits, uint8_t y_splits) {
// Get current and destination cells for this line
int cx1 = cell_index_x(current_position[X_AXIS]),
cy1 = cell_index_y(current_position[Y_AXIS]),
cx2 = cell_index_x(destination[X_AXIS]),
cy2 = cell_index_y(destination[Y_AXIS]);
NOMORE(cx1, GRID_MAX_POINTS_X - 2);
NOMORE(cy1, GRID_MAX_POINTS_Y - 2);
NOMORE(cx2, GRID_MAX_POINTS_X - 2);
NOMORE(cy2, GRID_MAX_POINTS_Y - 2);
xy_int8_t scel = cell_indexes(current_position), ecel = cell_indexes(destination);
NOMORE(scel.x, GRID_MAX_POINTS_X - 2);
NOMORE(scel.y, GRID_MAX_POINTS_Y - 2);
NOMORE(ecel.x, GRID_MAX_POINTS_X - 2);
NOMORE(ecel.y, GRID_MAX_POINTS_Y - 2);
// Start and end in the same cell? No split needed.
if (cx1 == cx2 && cy1 == cy2) {
if (scel == ecel) {
line_to_destination(scaled_fr_mm_s);
set_current_from_destination();
current_position = destination;
return;
}
#define MBL_SEGMENT_END(A) (current_position[_AXIS(A)] + (destination[_AXIS(A)] - current_position[_AXIS(A)]) * normalized_dist)
#define MBL_SEGMENT_END(A) (current_position.A + (destination.A - current_position.A) * normalized_dist)
float normalized_dist, end[XYZE];
const int8_t gcx = _MAX(cx1, cx2), gcy = _MAX(cy1, cy2);
float normalized_dist;
xyze_pos_t dest;
const int8_t gcx = _MAX(scel.x, ecel.x), gcy = _MAX(scel.y, ecel.y);
// Crosses on the X and not already split on this X?
// The x_splits flags are insurance against rounding errors.
if (cx2 != cx1 && TEST(x_splits, gcx)) {
if (ecel.x != scel.x && TEST(x_splits, gcx)) {
// Split on the X grid line
CBI(x_splits, gcx);
COPY(end, destination);
destination[X_AXIS] = index_to_xpos[gcx];
normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]);
destination[Y_AXIS] = MBL_SEGMENT_END(Y);
dest = destination;
destination.x = index_to_xpos[gcx];
normalized_dist = (destination.x - current_position.x) / (dest.x - current_position.x);
destination.y = MBL_SEGMENT_END(y);
}
// Crosses on the Y and not already split on this Y?
else if (cy2 != cy1 && TEST(y_splits, gcy)) {
else if (ecel.y != scel.y && TEST(y_splits, gcy)) {
// Split on the Y grid line
CBI(y_splits, gcy);
COPY(end, destination);
destination[Y_AXIS] = index_to_ypos[gcy];
normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]);
destination[X_AXIS] = MBL_SEGMENT_END(X);
dest = destination;
destination.y = index_to_ypos[gcy];
normalized_dist = (destination.y - current_position.y) / (dest.y - current_position.y);
destination.x = MBL_SEGMENT_END(x);
}
else {
// Must already have been split on these border(s)
// This should be a rare case.
line_to_destination(scaled_fr_mm_s);
set_current_from_destination();
current_position = destination;
return;
}
destination[Z_AXIS] = MBL_SEGMENT_END(Z);
destination[E_AXIS] = MBL_SEGMENT_END(E);
destination.z = MBL_SEGMENT_END(z);
destination.e = MBL_SEGMENT_END(e);
// Do the split and look for more borders
line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
// Restore destination from stack
COPY(destination, end);
destination = dest;
line_to_destination(scaled_fr_mm_s, x_splits, y_splits);
}

View File

@ -76,21 +76,27 @@ public:
int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST);
return constrain(cx, 0, (GRID_MAX_POINTS_X) - 2);
}
static int8_t cell_index_y(const float &y) {
int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 2);
}
static inline xy_int8_t cell_indexes(const float &x, const float &y) {
return { cell_index_x(x), cell_index_y(y) };
}
static inline xy_int8_t cell_indexes(const xy_pos_t &xy) { return cell_indexes(xy.x, xy.y); }
static int8_t probe_index_x(const float &x) {
int8_t px = (x - (MESH_MIN_X) + 0.5f * (MESH_X_DIST)) * RECIPROCAL(MESH_X_DIST);
return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
}
static int8_t probe_index_y(const float &y) {
int8_t py = (y - (MESH_MIN_Y) + 0.5f * (MESH_Y_DIST)) * RECIPROCAL(MESH_Y_DIST);
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
}
static inline xy_int8_t probe_indexes(const float &x, const float &y) {
return { probe_index_x(x), probe_index_y(y) };
}
static inline xy_int8_t probe_indexes(const xy_pos_t &xy) { return probe_indexes(xy.x, xy.y); }
static float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
const float delta_z = (z2 - z1) / (a2 - a1),
@ -98,21 +104,21 @@ public:
return z1 + delta_a * delta_z;
}
static float get_z(const float &x0, const float &y0
static float get_z(const xy_pos_t &pos
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
, const float &factor
, const float &factor=1.0f
#endif
) {
const int8_t cx = cell_index_x(x0), cy = cell_index_y(y0);
const float z1 = calc_z0(x0, index_to_xpos[cx], z_values[cx][cy], index_to_xpos[cx + 1], z_values[cx + 1][cy]),
z2 = calc_z0(x0, index_to_xpos[cx], z_values[cx][cy + 1], index_to_xpos[cx + 1], z_values[cx + 1][cy + 1]),
z0 = calc_z0(y0, index_to_ypos[cy], z1, index_to_ypos[cy + 1], z2);
#if DISABLED(ENABLE_LEVELING_FADE_HEIGHT)
constexpr float factor = 1.0f;
#endif
const xy_int8_t ind = cell_indexes(pos);
const float x1 = index_to_xpos[ind.x], x2 = index_to_xpos[ind.x+1],
y1 = index_to_xpos[ind.y], y2 = index_to_xpos[ind.y+1],
z1 = calc_z0(pos.x, x1, z_values[ind.x][ind.y ], x2, z_values[ind.x+1][ind.y ]),
z2 = calc_z0(pos.x, x1, z_values[ind.x][ind.y+1], x2, z_values[ind.x+1][ind.y+1]);
return z_offset + z0
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
* factor
#endif
;
return z_offset + calc_z0(pos.y, y1, z1, y2, z2) * factor;
}
#if IS_CARTESIAN && DISABLED(SEGMENT_LEVELED_MOVES)

View File

@ -176,8 +176,7 @@
// Add XY probe offset from extruder because probe_at_point() subtracts them when
// moving to the XY position to be measured. This ensures better agreement between
// the current Z position after G28 and the mesh values.
const float current_xi = find_closest_x_index(current_position[X_AXIS] + probe_offset[X_AXIS]),
current_yi = find_closest_y_index(current_position[Y_AXIS] + probe_offset[Y_AXIS]);
const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + xy_pos_t(probe_offset));
if (!lcd) SERIAL_EOL();
for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) {
@ -193,7 +192,7 @@
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
// Opening Brace or Space
const bool is_current = i == current_xi && j == current_yi;
const bool is_current = i == curr.x && j == curr.y;
if (human) SERIAL_CHAR(is_current ? '[' : ' ');
// Z Value at current I, J

View File

@ -32,15 +32,12 @@
#define UBL_OK false
#define UBL_ERR true
#define USE_NOZZLE_AS_REFERENCE 0
#define USE_PROBE_AS_REFERENCE 1
// ubl_G29.cpp
enum MeshPointType : char { INVALID, REAL, SET_IN_BITMAP };
// External references
struct mesh_index_pair;
#define MESH_X_DIST (float(MESH_MAX_X - (MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
#define MESH_Y_DIST (float(MESH_MAX_Y - (MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
@ -52,10 +49,11 @@ class unified_bed_leveling {
g29_repetition_cnt,
g29_storage_slot,
g29_map_type;
static bool g29_c_flag, g29_x_flag, g29_y_flag;
static float g29_x_pos, g29_y_pos,
g29_card_thickness,
static bool g29_c_flag;
static float g29_card_thickness,
g29_constant;
static xy_pos_t g29_pos;
static xy_bool_t xy_seen;
#if HAS_BED_PROBE
static int g29_grid_size;
@ -65,16 +63,19 @@ class unified_bed_leveling {
static void move_z_with_encoder(const float &multiplier);
static float measure_point_with_encoder();
static float measure_business_card_thickness(float in_height);
static void manually_probe_remaining_mesh(const float&, const float&, const float&, const float&, const bool) _O0;
static void fine_tune_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map) _O0;
static void manually_probe_remaining_mesh(const xy_pos_t&, const float&, const float&, const bool) _O0;
static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) _O0;
#endif
static bool g29_parameter_parsing() _O0;
static void shift_mesh_height();
static void probe_entire_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
static void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3);
static void tilt_mesh_based_on_probed_grid(const bool do_ubl_mesh_map);
static bool smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir);
static inline bool smart_fill_one(const xy_uint8_t &pos, const xy_uint8_t &dir) {
return smart_fill_one(pos.x, pos.y, dir.x, dir.y);
}
static void smart_fill_mesh();
#if ENABLED(UBL_DEVEL_DEBUGGING)
@ -91,7 +92,7 @@ class unified_bed_leveling {
static void save_ubl_active_state_and_disable();
static void restore_ubl_active_state_and_leave();
static void display_map(const int) _O0;
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const float&, const float&, const bool, uint16_t[16]) _O0;
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const xy_pos_t&, const bool=false, MeshFlags *done_flags=nullptr) _O0;
static mesh_index_pair find_furthest_invalid_mesh_point() _O0;
static void reset();
static void invalidate();
@ -118,14 +119,14 @@ class unified_bed_leveling {
FORCE_INLINE static void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
static int8_t get_cell_index_x(const float &x) {
static int8_t cell_index_x(const float &x) {
const int8_t cx = (x - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST);
return constrain(cx, 0, (GRID_MAX_POINTS_X) - 1); // -1 is appropriate if we want all movement to the X_MAX
} // position. But with this defined this way, it is possible
// to extrapolate off of this point even further out. Probably
// that is OK because something else should be keeping that from
// happening and should not be worried about at this level.
static int8_t get_cell_index_y(const float &y) {
static int8_t cell_index_y(const float &y) {
const int8_t cy = (y - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 1); // -1 is appropriate if we want all movement to the Y_MAX
} // position. But with this defined this way, it is possible
@ -133,15 +134,22 @@ class unified_bed_leveling {
// that is OK because something else should be keeping that from
// happening and should not be worried about at this level.
static int8_t find_closest_x_index(const float &x) {
static inline xy_int8_t cell_indexes(const float &x, const float &y) {
return { cell_index_x(x), cell_index_y(y) };
}
static inline xy_int8_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);
return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
}
static int8_t find_closest_y_index(const float &y) {
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);
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
}
static inline xy_int8_t closest_indexes(const xy_pos_t &xy) {
return { closest_x_index(xy.x), closest_y_index(xy.y) };
}
/**
* z2 --|
@ -228,8 +236,7 @@ class unified_bed_leveling {
* on the Y position within the cell.
*/
static float get_z_correction(const float &rx0, const float &ry0) {
const int8_t cx = get_cell_index_x(rx0),
cy = get_cell_index_y(ry0); // return values are clamped
const int8_t cx = cell_index_x(rx0), cy = cell_index_y(ry0); // return values are clamped
/**
* Check if the requested location is off the mesh. If so, and
@ -275,11 +282,11 @@ class unified_bed_leveling {
}
return z0;
}
static inline float get_z_correction(const xy_pos_t &pos) { return get_z_correction(pos.x, pos.y); }
static inline float mesh_index_to_xpos(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 inline float mesh_index_to_ypos(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);
}

View File

@ -53,8 +53,6 @@
#define UBL_G29_P31
extern float destination[XYZE], current_position[XYZE];
#if HAS_LCD_MENU
void _lcd_ubl_output_map_lcd();
#endif
@ -67,13 +65,11 @@
unified_bed_leveling::g29_repetition_cnt,
unified_bed_leveling::g29_storage_slot = 0,
unified_bed_leveling::g29_map_type;
bool unified_bed_leveling::g29_c_flag,
unified_bed_leveling::g29_x_flag,
unified_bed_leveling::g29_y_flag;
float unified_bed_leveling::g29_x_pos,
unified_bed_leveling::g29_y_pos,
unified_bed_leveling::g29_card_thickness = 0,
bool unified_bed_leveling::g29_c_flag;
float unified_bed_leveling::g29_card_thickness = 0,
unified_bed_leveling::g29_constant = 0;
xy_bool_t unified_bed_leveling::xy_seen;
xy_pos_t unified_bed_leveling::g29_pos;
#if HAS_BED_PROBE
int unified_bed_leveling::g29_grid_size;
@ -330,18 +326,19 @@
else {
while (g29_repetition_cnt--) {
if (cnt > 20) { cnt = 0; idle(); }
const mesh_index_pair location = find_closest_mesh_point_of_type(REAL, g29_x_pos, g29_y_pos, USE_NOZZLE_AS_REFERENCE, nullptr);
if (location.x_index < 0) {
// No more REACHABLE mesh points to invalidate, so we ASSUME the user
const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, g29_pos);
const xy_int8_t &cpos = closest.pos;
if (cpos.x < 0) {
// No more REAL mesh points to invalidate, so we ASSUME the user
// meant to invalidate the ENTIRE mesh, which cannot be done with
// find_closest_mesh_point loop which only returns REACHABLE points.
// find_closest_mesh_point loop which only returns REAL points.
set_all_mesh_points_to_value(NAN);
SERIAL_ECHOLNPGM("Entire Mesh invalidated.\n");
break; // No more invalid Mesh Points to populate
}
z_values[location.x_index][location.y_index] = NAN;
z_values[cpos.x][cpos.y] = NAN;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(location.x_index, location.y_index, 0);
ExtUI::onMeshUpdate(closest, 0);
#endif
cnt++;
}
@ -448,13 +445,13 @@
SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh.");
}
if (g29_verbose_level > 1) {
SERIAL_ECHOPAIR("Probing around (", g29_x_pos);
SERIAL_ECHOPAIR("Probing around (", g29_pos.x);
SERIAL_CHAR(',');
SERIAL_ECHO(g29_y_pos);
SERIAL_ECHO(g29_pos.y);
SERIAL_ECHOLNPGM(").\n");
}
probe_entire_mesh(g29_x_pos + probe_offset[X_AXIS], g29_y_pos + probe_offset[Y_AXIS],
parser.seen('T'), parser.seen('E'), parser.seen('U'));
const xy_pos_t near = g29_pos + probe_offset;
probe_entire_mesh(near, parser.seen('T'), parser.seen('E'), parser.seen('U'));
report_current_position();
probe_deployed = true;
@ -470,7 +467,7 @@
SERIAL_ECHOLNPGM("Manually probing unreachable mesh locations.");
do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES);
if (parser.seen('C') && !g29_x_flag && !g29_y_flag) {
if (parser.seen('C') && !xy_seen) {
/**
* Use a good default location for the path.
* The flipped > and < operators in these comparisons is intentional.
@ -478,13 +475,14 @@
* It may make sense to have Delta printers default to the center of the bed.
* Until that is decided, this can be forced with the X and Y parameters.
*/
#if IS_KINEMATIC
g29_x_pos = X_HOME_POS;
g29_y_pos = Y_HOME_POS;
#else // cartesian
g29_x_pos = probe_offset[X_AXIS] > 0 ? X_BED_SIZE : 0;
g29_y_pos = probe_offset[Y_AXIS] < 0 ? Y_BED_SIZE : 0;
#endif
g29_pos.set(
#if IS_KINEMATIC
X_HOME_POS, Y_HOME_POS
#else
probe_offset.x > 0 ? X_BED_SIZE : 0,
probe_offset.y < 0 ? Y_BED_SIZE : 0
#endif
);
}
if (parser.seen('B')) {
@ -496,13 +494,13 @@
probe_deployed = true;
}
if (!position_is_reachable(g29_x_pos, g29_y_pos)) {
if (!position_is_reachable(g29_pos)) {
SERIAL_ECHOLNPGM("XY outside printable radius.");
return;
}
const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES);
manually_probe_remaining_mesh(g29_x_pos, g29_y_pos, height, g29_card_thickness, parser.seen('T'));
manually_probe_remaining_mesh(g29_pos, height, g29_card_thickness, parser.seen('T'));
SERIAL_ECHOLNPGM("G29 P2 finished.");
@ -530,20 +528,22 @@
}
else {
while (g29_repetition_cnt--) { // this only populates reachable mesh points near
const mesh_index_pair location = find_closest_mesh_point_of_type(INVALID, g29_x_pos, g29_y_pos, USE_NOZZLE_AS_REFERENCE, nullptr);
if (location.x_index < 0) {
// No more REACHABLE INVALID mesh points to populate, so we ASSUME
const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, g29_pos);
const xy_int8_t &cpos = closest.pos;
if (cpos.x < 0) {
// No more REAL INVALID mesh points to populate, so we ASSUME
// user meant to populate ALL INVALID mesh points to value
for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++)
for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++)
if (isnan(z_values[x][y]))
z_values[x][y] = g29_constant;
if (isnan(z_values[x][y])) z_values[x][y] = g29_constant;
break; // No more invalid Mesh Points to populate
}
z_values[location.x_index][location.y_index] = g29_constant;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(location.x_index, location.y_index, z_values[location.x_index][location.y_index]);
#endif
else {
z_values[cpos.x][cpos.y] = g29_constant;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(closest, g29_constant);
#endif
}
}
}
}
@ -576,7 +576,7 @@
case 4: // Fine Tune (i.e., Edit) the Mesh
#if HAS_LCD_MENU
fine_tune_mesh(g29_x_pos, g29_y_pos, parser.seen('T'));
fine_tune_mesh(g29_pos, parser.seen('T'));
#else
SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present.");
return;
@ -740,9 +740,7 @@
* Probe all invalidated locations of the mesh that can be reached by the probe.
* This attempts to fill in locations closest to the nozzle's start location first.
*/
void unified_bed_leveling::probe_entire_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) {
mesh_index_pair location;
void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) {
#if HAS_LCD_MENU
ui.capture();
#endif
@ -752,6 +750,7 @@
uint8_t count = GRID_MAX_POINTS;
mesh_index_pair best;
do {
if (do_ubl_mesh_map) display_map(g29_map_type);
@ -773,23 +772,23 @@
}
#endif
if (do_furthest)
location = find_furthest_invalid_mesh_point();
else
location = find_closest_mesh_point_of_type(INVALID, rx, ry, USE_PROBE_AS_REFERENCE, nullptr);
best = do_furthest
? find_furthest_invalid_mesh_point()
: find_closest_mesh_point_of_type(INVALID, near, true);
if (location.x_index >= 0) { // mesh point found and is reachable by probe
const float rawx = mesh_index_to_xpos(location.x_index),
rawy = mesh_index_to_ypos(location.y_index),
measured_z = probe_at_point(rawx, rawy, stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level); // TODO: Needs error handling
z_values[location.x_index][location.y_index] = measured_z;
if (best.pos.x >= 0) { // mesh point found and is reachable by probe
const float measured_z = probe_at_point(
best.meshpos(),
stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level
);
z_values[best.pos.x][best.pos.y] = measured_z;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(location.x_index, location.y_index, measured_z);
ExtUI::onMeshUpdate(best, measured_z);
#endif
}
SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
} while (location.x_index >= 0 && --count);
} while (best.pos.x >= 0 && --count);
STOW_PROBE();
@ -800,8 +799,8 @@
restore_ubl_active_state_and_leave();
do_blocking_move_to_xy(
constrain(rx - probe_offset[X_AXIS], MESH_MIN_X, MESH_MAX_X),
constrain(ry - probe_offset[Y_AXIS], MESH_MIN_Y, MESH_MAX_Y)
constrain(near.x - probe_offset.x, MESH_MIN_X, MESH_MAX_X),
constrain(near.y - probe_offset.y, MESH_MIN_Y, MESH_MAX_Y)
);
}
@ -835,7 +834,7 @@
idle();
gcode.reset_stepper_timeout(); // Keep steppers powered
if (encoder_diff) {
do_blocking_move_to_z(current_position[Z_AXIS] + float(encoder_diff) * multiplier);
do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier);
encoder_diff = 0;
}
}
@ -844,7 +843,7 @@
float unified_bed_leveling::measure_point_with_encoder() {
KEEPALIVE_STATE(PAUSED_FOR_USER);
move_z_with_encoder(0.01f);
return current_position[Z_AXIS];
return current_position.z;
}
static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); }
@ -863,7 +862,7 @@
echo_and_take_a_measurement();
const float z1 = measure_point_with_encoder();
do_blocking_move_to_z(current_position[Z_AXIS] + SIZE_OF_LITTLE_RAISE);
do_blocking_move_to_z(current_position.z + SIZE_OF_LITTLE_RAISE);
planner.synchronize();
SERIAL_ECHOPGM("Remove shim");
@ -872,7 +871,7 @@
const float z2 = measure_point_with_encoder();
do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES);
do_blocking_move_to_z(current_position.z + Z_CLEARANCE_BETWEEN_PROBES);
const float thickness = ABS(z1 - z2);
@ -888,29 +887,33 @@
return thickness;
}
void unified_bed_leveling::manually_probe_remaining_mesh(const float &rx, const float &ry, const float &z_clearance, const float &thick, const bool do_ubl_mesh_map) {
void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const float &z_clearance, const float &thick, const bool do_ubl_mesh_map) {
ui.capture();
save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained
do_blocking_move_to(current_position[X_AXIS], current_position[Y_AXIS], z_clearance);
do_blocking_move_to(current_position.x, current_position.y, z_clearance);
ui.return_to_status();
mesh_index_pair location;
xy_int8_t &lpos = location.pos;
do {
location = find_closest_mesh_point_of_type(INVALID, rx, ry, USE_NOZZLE_AS_REFERENCE, nullptr);
location = find_closest_mesh_point_of_type(INVALID, pos);
// It doesn't matter if the probe can't reach the NAN location. This is a manual probe.
if (location.x_index < 0 && location.y_index < 0) continue;
if (!location.valid()) continue;
const float xProbe = mesh_index_to_xpos(location.x_index),
yProbe = mesh_index_to_ypos(location.y_index);
const xyz_pos_t ppos = {
mesh_index_to_xpos(lpos.x),
mesh_index_to_ypos(lpos.y),
Z_CLEARANCE_BETWEEN_PROBES
};
if (!position_is_reachable(xProbe, yProbe)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points)
if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points)
LCD_MESSAGEPGM(MSG_UBL_MOVING_TO_NEXT);
do_blocking_move_to(xProbe, yProbe, Z_CLEARANCE_BETWEEN_PROBES);
do_blocking_move_to(ppos);
do_blocking_move_to_z(z_clearance);
KEEPALIVE_STATE(PAUSED_FOR_USER);
@ -932,20 +935,20 @@
return restore_ubl_active_state_and_leave();
}
z_values[location.x_index][location.y_index] = current_position[Z_AXIS] - thick;
z_values[lpos.x][lpos.y] = current_position.z - thick;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(location.x_index, location.y_index, z_values[location.x_index][location.y_index]);
ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y]);
#endif
if (g29_verbose_level > 2)
SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[location.x_index][location.y_index], 6);
SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[lpos.x][lpos.y], 6);
SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
} while (location.x_index >= 0 && location.y_index >= 0);
} while (location.valid());
if (do_ubl_mesh_map) display_map(g29_map_type); // show user where we're probing
restore_ubl_active_state_and_leave();
do_blocking_move_to(rx, ry, Z_CLEARANCE_DEPLOY_PROBE);
do_blocking_move_to(pos, Z_CLEARANCE_DEPLOY_PROBE);
}
inline void set_message_with_feedback(PGM_P const msg_P) {
@ -959,8 +962,8 @@
set_message_with_feedback(PSTR(MSG_EDITING_STOPPED));
}
void unified_bed_leveling::fine_tune_mesh(const float &rx, const float &ry, const bool do_ubl_mesh_map) {
if (!parser.seen('R')) // fine_tune_mesh() is special. If no repetition count flag is specified
void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) {
if (!parser.seen('R')) // fine_tune_mesh() is special. If no repetition count flag is specified
g29_repetition_cnt = 1; // do exactly one mesh location. Otherwise use what the parser decided.
#if ENABLED(UBL_MESH_EDIT_MOVES_Z)
@ -973,7 +976,7 @@
mesh_index_pair location;
if (!position_is_reachable(rx, ry)) {
if (!position_is_reachable(pos)) {
SERIAL_ECHOLNPGM("(X,Y) outside printable radius.");
return;
}
@ -981,76 +984,78 @@
save_ubl_active_state_and_disable();
LCD_MESSAGEPGM(MSG_UBL_FINE_TUNE_MESH);
ui.capture(); // Take over control of the LCD encoder
ui.capture(); // Take over control of the LCD encoder
do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES); // Move to the given XY with probe clearance
do_blocking_move_to(pos, Z_CLEARANCE_BETWEEN_PROBES); // Move to the given XY with probe clearance
#if ENABLED(UBL_MESH_EDIT_MOVES_Z)
do_blocking_move_to_z(h_offset); // Move Z to the given 'H' offset
do_blocking_move_to_z(h_offset); // Move Z to the given 'H' offset
#endif
uint16_t not_done[16];
memset(not_done, 0xFF, sizeof(not_done));
MeshFlags done_flags{0};
xy_int8_t &lpos = location.pos;
do {
location = find_closest_mesh_point_of_type(SET_IN_BITMAP, rx, ry, USE_NOZZLE_AS_REFERENCE, not_done);
location = find_closest_mesh_point_of_type(SET_IN_BITMAP, pos, false, &done_flags);
if (location.x_index < 0) break; // Stop when there are no more reachable points
if (lpos.x < 0) break; // Stop when there are no more reachable points
bitmap_clear(not_done, location.x_index, location.y_index); // Mark this location as 'adjusted' so a new
// location is used on the next loop
done_flags.mark(lpos); // Mark this location as 'adjusted' so a new
// location is used on the next loop
const xyz_pos_t raw = {
mesh_index_to_xpos(lpos.x),
mesh_index_to_ypos(lpos.y),
Z_CLEARANCE_BETWEEN_PROBES
};
const float rawx = mesh_index_to_xpos(location.x_index),
rawy = mesh_index_to_ypos(location.y_index);
if (!position_is_reachable(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable)
if (!position_is_reachable(rawx, rawy)) break; // SHOULD NOT OCCUR because find_closest_mesh_point_of_type will only return reachable
do_blocking_move_to(rawx, rawy, Z_CLEARANCE_BETWEEN_PROBES); // Move the nozzle to the edit point with probe clearance
do_blocking_move_to(raw); // Move the nozzle to the edit point with probe clearance
#if ENABLED(UBL_MESH_EDIT_MOVES_Z)
do_blocking_move_to_z(h_offset); // Move Z to the given 'H' offset before editing
do_blocking_move_to_z(h_offset); // Move Z to the given 'H' offset before editing
#endif
KEEPALIVE_STATE(PAUSED_FOR_USER);
if (do_ubl_mesh_map) display_map(g29_map_type); // Display the current point
if (do_ubl_mesh_map) display_map(g29_map_type); // Display the current point
ui.refresh();
float new_z = z_values[location.x_index][location.y_index];
if (isnan(new_z)) new_z = 0; // Invalid points begin at 0
new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place
float new_z = z_values[lpos.x][lpos.y];
if (isnan(new_z)) new_z = 0; // Invalid points begin at 0
new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place
lcd_mesh_edit_setup(new_z);
do {
new_z = lcd_mesh_edit();
#if ENABLED(UBL_MESH_EDIT_MOVES_Z)
do_blocking_move_to_z(h_offset + new_z); // Move the nozzle as the point is edited
do_blocking_move_to_z(h_offset + new_z); // Move the nozzle as the point is edited
#endif
idle();
SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
} while (!ui.button_pressed());
if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status
if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status
if (click_and_hold(abort_fine_tune)) break; // Button held down? Abort editing
if (click_and_hold(abort_fine_tune)) break; // Button held down? Abort editing
z_values[location.x_index][location.y_index] = new_z; // Save the updated Z value
z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(location.x_index, location.y_index, new_z);
ExtUI::onMeshUpdate(location, new_z);
#endif
serial_delay(20); // No switch noise
serial_delay(20); // No switch noise
ui.refresh();
} while (location.x_index >= 0 && --g29_repetition_cnt > 0);
} while (lpos.x >= 0 && --g29_repetition_cnt > 0);
ui.release();
if (do_ubl_mesh_map) display_map(g29_map_type);
restore_ubl_active_state_and_leave();
do_blocking_move_to(rx, ry, Z_CLEARANCE_BETWEEN_PROBES);
do_blocking_move_to(pos, Z_CLEARANCE_BETWEEN_PROBES);
LCD_MESSAGEPGM(MSG_UBL_DONE_EDITING_MESH);
SERIAL_ECHOLNPGM("Done Editing Mesh");
@ -1073,11 +1078,6 @@
g29_constant = 0;
g29_repetition_cnt = 0;
g29_x_flag = parser.seenval('X');
g29_x_pos = g29_x_flag ? parser.value_float() : current_position[X_AXIS];
g29_y_flag = parser.seenval('Y');
g29_y_pos = g29_y_flag ? parser.value_float() : current_position[Y_AXIS];
if (parser.seen('R')) {
g29_repetition_cnt = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS;
NOMORE(g29_repetition_cnt, GRID_MAX_POINTS);
@ -1124,17 +1124,24 @@
#endif
}
if (g29_x_flag != g29_y_flag) {
xy_seen.x = parser.seenval('X');
float sx = xy_seen.x ? parser.value_float() : current_position.x;
xy_seen.y = parser.seenval('Y');
float sy = xy_seen.y ? parser.value_float() : current_position.y;
if (xy_seen.x != xy_seen.y) {
SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n");
err_flag = true;
}
// If X or Y are not valid, use center of the bed values
if (!WITHIN(g29_x_pos, X_MIN_BED, X_MAX_BED)) g29_x_pos = X_CENTER;
if (!WITHIN(g29_y_pos, Y_MIN_BED, Y_MAX_BED)) g29_y_pos = Y_CENTER;
if (!WITHIN(sx, X_MIN_BED, X_MAX_BED)) sx = X_CENTER;
if (!WITHIN(sy, Y_MIN_BED, Y_MAX_BED)) sy = Y_CENTER;
if (err_flag) return UBL_ERR;
g29_pos.set(sx, sy);
/**
* Activate or deactivate UBL
* Note: UBL's G29 restores the state set here when done.
@ -1213,26 +1220,22 @@
mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() {
bool found_a_NAN = false, found_a_real = false;
bool found_a_NAN = false, found_a_real = false;
mesh_index_pair out_mesh;
out_mesh.x_index = out_mesh.y_index = -1;
out_mesh.distance = -99999.99f;
mesh_index_pair farthest { -1, -1, -99999.99 };
for (int8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
for (int8_t j = 0; j < GRID_MAX_POINTS_Y; j++) {
if (isnan(z_values[i][j])) { // Check to see if this location holds an invalid mesh point
if (isnan(z_values[i][j])) { // Invalid mesh point?
const float mx = mesh_index_to_xpos(i),
my = mesh_index_to_ypos(j);
if (!position_is_reachable_by_probe(mx, my)) // make sure the probe can get to the mesh point
// Skip points the probe can't reach
if (!position_is_reachable_by_probe(mesh_index_to_xpos(i), mesh_index_to_ypos(j)))
continue;
found_a_NAN = true;
int8_t closest_x = -1, closest_y = -1;
xy_int8_t near { -1, -1 };
float d1, d2 = 99999.9f;
for (int8_t k = 0; k < GRID_MAX_POINTS_X; k++) {
for (int8_t l = 0; l < GRID_MAX_POINTS_Y; l++) {
@ -1245,84 +1248,75 @@
d1 = HYPOT(i - k, j - l) + (1.0f / ((millis() % 47) + 13));
if (d1 < d2) { // found a closer distance from invalid mesh point at (i,j) to defined mesh point at (k,l)
d2 = d1; // found a closer location with
closest_x = i; // an assigned mesh point value
closest_y = j;
if (d1 < d2) { // Invalid mesh point (i,j) is closer to the defined point (k,l)
d2 = d1;
near.set(i, j);
}
}
}
}
//
// At this point d2 should have the closest defined mesh point to invalid mesh point (i,j)
// At this point d2 should have the near defined mesh point to invalid mesh point (i,j)
//
if (found_a_real && (closest_x >= 0) && (d2 > out_mesh.distance)) {
out_mesh.distance = d2; // found an invalid location with a greater distance
out_mesh.x_index = closest_x; // to a defined mesh point
out_mesh.y_index = closest_y;
if (found_a_real && near.x >= 0 && d2 > farthest.distance) {
farthest.pos = near; // Found an invalid location farther from the defined mesh point
farthest.distance = d2;
}
}
} // for j
} // for i
if (!found_a_real && found_a_NAN) { // if the mesh is totally unpopulated, start the probing
out_mesh.x_index = GRID_MAX_POINTS_X / 2;
out_mesh.y_index = GRID_MAX_POINTS_Y / 2;
out_mesh.distance = 1;
farthest.pos.set(GRID_MAX_POINTS_X / 2, GRID_MAX_POINTS_Y / 2);
farthest.distance = 1;
}
return out_mesh;
return farthest;
}
mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const float &rx, const float &ry, const bool probe_as_reference, uint16_t bits[16]) {
mesh_index_pair out_mesh;
out_mesh.x_index = out_mesh.y_index = -1;
out_mesh.distance = -99999.9f;
mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) {
mesh_index_pair closest;
closest.invalidate();
closest.distance = -99999.9f;
// Get our reference position. Either the nozzle or probe location.
const float px = rx + (probe_as_reference == USE_PROBE_AS_REFERENCE ? probe_offset[X_AXIS] : 0),
py = ry + (probe_as_reference == USE_PROBE_AS_REFERENCE ? probe_offset[Y_AXIS] : 0);
// Get the reference position, either nozzle or probe
const xy_pos_t ref = probe_relative ? pos + probe_offset : pos;
float best_so_far = 99999.99f;
for (int8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
for (int8_t j = 0; j < GRID_MAX_POINTS_Y; j++) {
if ( (type == INVALID && isnan(z_values[i][j])) // Check to see if this location holds the right thing
|| (type == REAL && !isnan(z_values[i][j]))
|| (type == SET_IN_BITMAP && is_bitmap_set(bits, i, j))
if ( (type == (isnan(z_values[i][j]) ? INVALID : REAL))
|| (type == SET_IN_BITMAP && !done_flags->marked(i, j))
) {
// We only get here if we found a Mesh Point of the specified type
const float mx = mesh_index_to_xpos(i),
my = mesh_index_to_ypos(j);
// Found a Mesh Point of the specified type!
const xy_pos_t mpos = { mesh_index_to_xpos(i), mesh_index_to_ypos(j) };
// If using the probe as the reference there are some unreachable locations.
// Also for round beds, there are grid points outside the bed the nozzle can't reach.
// Prune them from the list and ignore them till the next Phase (manual nozzle probing).
if (probe_as_reference ? !position_is_reachable_by_probe(mx, my) : !position_is_reachable(mx, my))
if (probe_relative ? !position_is_reachable_by_probe(mpos) : !position_is_reachable(mpos))
continue;
// Reachable. Check if it's the best_so_far location to the nozzle.
float distance = HYPOT(px - mx, py - my);
const xy_pos_t diff = current_position - mpos;
const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f;
// factor in the distance from the current location for the normal case
// so the nozzle isn't running all over the bed.
distance += HYPOT(current_position[X_AXIS] - mx, current_position[Y_AXIS] - my) * 0.1f;
if (distance < best_so_far) {
best_so_far = distance; // We found a closer location with
out_mesh.x_index = i; // the specified type of mesh value.
out_mesh.y_index = j;
out_mesh.distance = best_so_far;
best_so_far = distance; // Found a closer location with the desired value type.
closest.pos.set(i, j);
closest.distance = best_so_far;
}
}
} // for j
} // for i
return out_mesh;
return closest;
}
/**
@ -1332,20 +1326,20 @@
*/
bool unified_bed_leveling::smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) {
const int8_t x1 = x + xdir, x2 = x1 + xdir,
y1 = y + ydir, y2 = y1 + ydir;
// A NAN next to a pair of real values?
if (isnan(z_values[x][y]) && !isnan(z_values[x1][y1]) && !isnan(z_values[x2][y2])) {
if (z_values[x1][y1] < z_values[x2][y2]) // Angled downward?
z_values[x][y] = z_values[x1][y1]; // Use nearest (maybe a little too high.)
else
z_values[x][y] = 2.0f * z_values[x1][y1] - z_values[x2][y2]; // Angled upward...
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, y, z_values[x][y]);
#endif
return true;
const float v = z_values[x][y];
if (isnan(v)) { // A NAN...
const int8_t dx = x + xdir, dy = y + ydir;
const float v1 = z_values[dx][dy];
if (!isnan(v1)) { // ...next to a pair of real values?
const float v2 = z_values[dx + xdir][dy + ydir];
if (!isnan(v2)) {
z_values[x][y] = v1 < v2 ? v1 : v1 + v1 - v2;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(x, y, z_values[pos.x][pos.y]);
#endif
return true;
}
}
}
return false;
}
@ -1391,15 +1385,15 @@
dx = (x_max - x_min) / (g29_grid_size - 1),
dy = (y_max - y_min) / (g29_grid_size - 1);
vector_3 points[3] = {
const vector_3 points[3] = {
#if ENABLED(HAS_FIXED_3POINT)
vector_3(PROBE_PT_1_X, PROBE_PT_1_Y, 0),
vector_3(PROBE_PT_2_X, PROBE_PT_2_Y, 0),
vector_3(PROBE_PT_3_X, PROBE_PT_3_Y, 0)
{ PROBE_PT_1_X, PROBE_PT_1_Y, 0 },
{ PROBE_PT_2_X, PROBE_PT_2_Y, 0 },
{ PROBE_PT_3_X, PROBE_PT_3_Y, 0 }
#else
vector_3(x_min, y_min, 0),
vector_3(x_max, y_min, 0),
vector_3((x_max - x_min) / 2, y_max, 0)
{ x_min, y_min, 0 },
{ x_max, y_min, 0 },
{ (x_max - x_min) / 2, y_max, 0 }
#endif
};
@ -1419,11 +1413,11 @@
ui.status_printf_P(0, PSTR(MSG_LCD_TILTING_MESH " 1/3"));
#endif
measured_z = probe_at_point(points[0].x, points[0].y, PROBE_PT_RAISE, g29_verbose_level);
measured_z = probe_at_point(points[0], PROBE_PT_RAISE, g29_verbose_level);
if (isnan(measured_z))
abort_flag = true;
else {
measured_z -= get_z_correction(points[0].x, points[0].y);
measured_z -= get_z_correction(points[0]);
#ifdef VALIDATE_MESH_TILT
z1 = measured_z;
#endif
@ -1431,7 +1425,7 @@
serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
}
incremental_LSF(&lsf_results, points[0].x, points[0].y, measured_z);
incremental_LSF(&lsf_results, points[0], measured_z);
}
if (!abort_flag) {
@ -1440,19 +1434,19 @@
ui.status_printf_P(0, PSTR(MSG_LCD_TILTING_MESH " 2/3"));
#endif
measured_z = probe_at_point(points[1].x, points[1].y, PROBE_PT_RAISE, g29_verbose_level);
measured_z = probe_at_point(points[1], PROBE_PT_RAISE, g29_verbose_level);
#ifdef VALIDATE_MESH_TILT
z2 = measured_z;
#endif
if (isnan(measured_z))
abort_flag = true;
else {
measured_z -= get_z_correction(points[1].x, points[1].y);
measured_z -= get_z_correction(points[1]);
if (g29_verbose_level > 3) {
serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
}
incremental_LSF(&lsf_results, points[1].x, points[1].y, measured_z);
incremental_LSF(&lsf_results, points[1], measured_z);
}
}
@ -1462,19 +1456,19 @@
ui.status_printf_P(0, PSTR(MSG_LCD_TILTING_MESH " 3/3"));
#endif
measured_z = probe_at_point(points[2].x, points[2].y, PROBE_PT_STOW, g29_verbose_level);
measured_z = probe_at_point(points[2], PROBE_PT_STOW, g29_verbose_level);
#ifdef VALIDATE_MESH_TILT
z3 = measured_z;
#endif
if (isnan(measured_z))
abort_flag = true;
else {
measured_z -= get_z_correction(points[2].x, points[2].y);
measured_z -= get_z_correction(points[2]);
if (g29_verbose_level > 3) {
serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
}
incremental_LSF(&lsf_results, points[2].x, points[2].y, measured_z);
incremental_LSF(&lsf_results, points[2], measured_z);
}
}
@ -1494,10 +1488,11 @@
uint16_t total_points = g29_grid_size * g29_grid_size, point_num = 1;
xy_pos_t rpos;
for (uint8_t ix = 0; ix < g29_grid_size; ix++) {
const float rx = x_min + ix * dx;
rpos.x = x_min + ix * dx;
for (int8_t iy = 0; iy < g29_grid_size; iy++) {
const float ry = y_min + dy * (zig_zag ? g29_grid_size - 1 - iy : iy);
rpos.y = y_min + dy * (zig_zag ? g29_grid_size - 1 - iy : iy);
if (!abort_flag) {
SERIAL_ECHOLNPAIR("Tilting mesh point ", point_num, "/", total_points, "\n");
@ -1505,24 +1500,24 @@
ui.status_printf_P(0, PSTR(MSG_LCD_TILTING_MESH " %i/%i"), point_num, total_points);
#endif
measured_z = probe_at_point(rx, ry, parser.seen('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level); // TODO: Needs error handling
measured_z = probe_at_point(rpos, parser.seen('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level); // TODO: Needs error handling
abort_flag = isnan(measured_z);
if (DEBUGGING(LEVELING)) {
const xy_pos_t lpos = rpos.asLogical();
DEBUG_CHAR('(');
DEBUG_ECHO_F(rx, 7);
DEBUG_ECHO_F(rpos.x, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(ry, 7);
DEBUG_ECHOPGM(") logical: (");
DEBUG_ECHO_F(LOGICAL_X_POSITION(rx), 7);
DEBUG_ECHO_F(rpos.y, 7);
DEBUG_ECHOPAIR_F(") logical: (", lpos.x, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(LOGICAL_Y_POSITION(ry), 7);
DEBUG_ECHO_F(lpos.y, 7);
DEBUG_ECHOPAIR_F(") measured: ", measured_z, 7);
DEBUG_ECHOPAIR_F(" correction: ", get_z_correction(rx, ry), 7);
DEBUG_ECHOPAIR_F(" correction: ", get_z_correction(rpos), 7);
}
measured_z -= get_z_correction(rx, ry) /* + probe_offset[Z_AXIS] */ ;
measured_z -= get_z_correction(rpos) /* + probe_offset.z */ ;
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_F(" final >>>---> ", measured_z, 7);
@ -1530,7 +1525,7 @@
serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
}
incremental_LSF(&lsf_results, rx, ry, measured_z);
incremental_LSF(&lsf_results, rpos, measured_z);
}
point_num++;
@ -1564,33 +1559,33 @@
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) {
float x_tmp = mesh_index_to_xpos(i),
y_tmp = mesh_index_to_ypos(j),
z_tmp = z_values[i][j];
float mx = mesh_index_to_xpos(i),
my = mesh_index_to_ypos(j),
mz = z_values[i][j];
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPAIR_F("before rotation = [", x_tmp, 7);
DEBUG_ECHOPAIR_F("before rotation = [", mx, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(y_tmp, 7);
DEBUG_ECHO_F(my, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(z_tmp, 7);
DEBUG_ECHO_F(mz, 7);
DEBUG_ECHOPGM("] ---> ");
DEBUG_DELAY(20);
}
apply_rotation_xyz(rotation, x_tmp, y_tmp, z_tmp);
apply_rotation_xyz(rotation, mx, my, mz);
if (DEBUGGING(LEVELING)) {
DEBUG_ECHOPAIR_F("after rotation = [", x_tmp, 7);
DEBUG_ECHOPAIR_F("after rotation = [", mx, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(y_tmp, 7);
DEBUG_ECHO_F(my, 7);
DEBUG_CHAR(',');
DEBUG_ECHO_F(z_tmp, 7);
DEBUG_ECHO_F(mz, 7);
DEBUG_ECHOLNPGM("]");
DEBUG_DELAY(55);
DEBUG_DELAY(20);
}
z_values[i][j] = z_tmp - lsf_results.D;
z_values[i][j] = mz - lsf_results.D;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(i, j, z_values[i][j]);
#endif
@ -1613,41 +1608,32 @@
DEBUG_EOL();
/**
* The following code can be used to check the validity of the mesh tilting algorithm.
* When a 3-Point Mesh Tilt is done, the same algorithm is used as the grid based tilting.
* The only difference is just 3 points are used in the calculations. That fact guarantees
* each probed point should have an exact match when a get_z_correction() for that location
* is calculated. The Z error between the probed point locations and the get_z_correction()
* Use the code below to check the validity of the mesh tilting algorithm.
* 3-Point Mesh Tilt uses the same algorithm as grid-based tilting, but only
* three points are used in the calculation. This guarantees that each probed point
* has an exact match when get_z_correction() for that location is calculated.
* The Z error between the probed point locations and the get_z_correction()
* numbers for those locations should be 0.
*/
#ifdef VALIDATE_MESH_TILT
float t, t1, d;
t = normal.x * x_min + normal.y * y_min;
d = t + normal.z * z1;
DEBUG_ECHOPAIR_F("D from 1st point: ", d, 6);
DEBUG_ECHOLNPAIR_F(" Z error: ", normal.z * z1 - get_z_correction(x_min, y_min), 6);
t = normal.x * x_max + normal.y * y_min;
d = t + normal.z * z2;
DEBUG_EOL();
DEBUG_ECHOPAIR_F("D from 2nd point: ", d, 6);
DEBUG_ECHOLNPAIR_F(" Z error: ", normal.z * z2 - get_z_correction(x_max, y_min), 6);
t = normal.x * ((x_max - x_min) / 2) + normal.y * (y_min);
d = t + normal.z * z3;
DEBUG_ECHOPAIR_F("D from 3rd point: ", d, 6);
DEBUG_ECHOLNPAIR_F(" Z error: ", normal.z * z3 - get_z_correction((x_max - x_min) / 2, y_max), 6);
t = normal.x * (Z_SAFE_HOMING_X_POINT) + normal.y * (Z_SAFE_HOMING_Y_POINT);
d = t + normal.z * 0;
DEBUG_ECHOLNPAIR_F("D from home location with Z=0 : ", d, 6);
t = normal.x * (Z_SAFE_HOMING_X_POINT) + normal.y * (Z_SAFE_HOMING_Y_POINT);
d = t + get_z_correction(Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT); // normal.z * 0;
DEBUG_ECHOPAIR_F("D from home location using mesh value for Z: ", d, 6);
auto d_from = []() { DEBUG_ECHOPGM("D from "); };
auto normed = [&](const xy_pos_t &pos, const float &zadd) {
return normal.x * pos.x + normal.y * pos.y + zadd;
};
auto debug_pt = [](PGM_P const pre, const xy_pos_t &pos, const float &zadd) {
d_from(); serialprintPGM(pre);
DEBUG_ECHO_F(normed(pos, zadd), 6);
DEBUG_ECHOLNPAIR_F(" Z error: ", zadd - get_z_correction(pos), 6);
};
debug_pt(PSTR("1st point: "), probe_pt[0], normal.z * z1);
debug_pt(PSTR("2nd point: "), probe_pt[1], normal.z * z2);
debug_pt(PSTR("3rd point: "), probe_pt[2], normal.z * z3);
d_from(); DEBUG_ECHOPGM("safe home with Z=");
DEBUG_ECHOLNPAIR_F("0 : ", normed(safe_homing_xy, 0), 6);
d_from(); DEBUG_ECHOPGM("safe home with Z=");
DEBUG_ECHOLNPAIR_F("mesh value ", normed(safe_homing_xy, get_z_correction(safe_homing_xy)), 6);
DEBUG_ECHOPAIR(" Z error: (", Z_SAFE_HOMING_X_POINT, ",", Z_SAFE_HOMING_Y_POINT);
DEBUG_ECHOLNPAIR_F(") = ", get_z_correction(Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT), 6);
DEBUG_ECHOLNPAIR_F(") = ", get_z_correction(safe_homing_xy), 6);
#endif
} // DEBUGGING(LEVELING)
@ -1676,21 +1662,23 @@
if (!isnan(z_values[jx][jy]))
SBI(bitmap[jx], jy);
xy_pos_t ppos;
for (uint8_t ix = 0; ix < GRID_MAX_POINTS_X; ix++) {
const float px = mesh_index_to_xpos(ix);
ppos.x = mesh_index_to_xpos(ix);
for (uint8_t iy = 0; iy < GRID_MAX_POINTS_Y; iy++) {
const float py = mesh_index_to_ypos(iy);
ppos.y = mesh_index_to_ypos(iy);
if (isnan(z_values[ix][iy])) {
// undefined mesh point at (px,py), compute weighted LSF from original valid mesh points.
// undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points.
incremental_LSF_reset(&lsf_results);
xy_pos_t rpos;
for (uint8_t jx = 0; jx < GRID_MAX_POINTS_X; jx++) {
const float rx = mesh_index_to_xpos(jx);
rpos.x = mesh_index_to_xpos(jx);
for (uint8_t jy = 0; jy < GRID_MAX_POINTS_Y; jy++) {
if (TEST(bitmap[jx], jy)) {
const float ry = mesh_index_to_ypos(jy),
rz = z_values[jx][jy],
w = 1 + weight_scaled / HYPOT((rx - px), (ry - py));
incremental_WLSF(&lsf_results, rx, ry, rz, w);
rpos.y = mesh_index_to_ypos(jy);
const float rz = z_values[jx][jy],
w = 1.0f + weight_scaled / (rpos - ppos).magnitude();
incremental_WLSF(&lsf_results, rpos, rz, w);
}
}
}
@ -1698,12 +1686,12 @@
SERIAL_ECHOLNPGM("Insufficient data");
return;
}
const float ez = -lsf_results.D - lsf_results.A * px - lsf_results.B * py;
const float ez = -lsf_results.D - lsf_results.A * ppos.x - lsf_results.B * ppos.y;
z_values[ix][iy] = ez;
#if ENABLED(EXTENSIBLE_UI)
ExtUI::onMeshUpdate(ix, iy, z_values[ix][iy]);
#endif
idle(); // housekeeping
idle(); // housekeeping
}
}
}
@ -1734,7 +1722,7 @@
adjust_mesh_to_mean(g29_c_flag, g29_constant);
#if HAS_BED_PROBE
SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe_offset[Z_AXIS], 7);
SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe_offset.z, 7);
#endif
SERIAL_ECHOLNPAIR("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50);

View File

@ -35,12 +35,6 @@
#include "../../../Marlin.h"
#include <math.h>
#if AVR_AT90USB1286_FAMILY // Teensyduino & Printrboard IDE extensions have compile errors without this
inline void set_current_from_destination() { COPY(current_position, destination); }
#else
extern void set_current_from_destination();
#endif
#if !UBL_SEGMENTED
void unified_bed_leveling::line_to_destination_cartesian(const feedRate_t &scaled_fr_mm_s, const uint8_t extruder) {
@ -50,60 +44,57 @@
* just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave
*/
#if HAS_POSITION_MODIFIERS
float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS] },
end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS] };
xyze_pos_t start = current_position, end = destination;
planner.apply_modifiers(start);
planner.apply_modifiers(end);
#else
const float (&start)[XYZE] = current_position,
(&end)[XYZE] = destination;
const xyze_pos_t &start = current_position, &end = destination;
#endif
const int cell_start_xi = get_cell_index_x(start[X_AXIS]),
cell_start_yi = get_cell_index_y(start[Y_AXIS]),
cell_dest_xi = get_cell_index_x(end[X_AXIS]),
cell_dest_yi = get_cell_index_y(end[Y_AXIS]);
const xy_int8_t istart = cell_indexes(start), iend = cell_indexes(end);
// A move within the same cell needs no splitting
if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) {
if (istart == iend) {
// For a move off the bed, use a constant Z raise
if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) {
if (!WITHIN(iend.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iend.y, 0, GRID_MAX_POINTS_Y - 1)) {
// Note: There is no Z Correction in this case. We are off the grid and don't know what
// a reasonable correction would be. If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH
// value, that will be used instead of a calculated (Bi-Linear interpolation) correction.
const float z_raise = 0.0
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
+ UBL_Z_RAISE_WHEN_OFF_MESH
#endif
;
planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z_raise, end[E_AXIS], scaled_fr_mm_s, extruder);
set_current_from_destination();
#ifdef UBL_Z_RAISE_WHEN_OFF_MESH
end.z += UBL_Z_RAISE_WHEN_OFF_MESH;
#endif
planner.buffer_segment(end, scaled_fr_mm_s, extruder);
current_position = destination;
return;
}
FINAL_MOVE:
// The distance is always MESH_X_DIST so multiply by the constant reciprocal.
const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * RECIPROCAL(MESH_X_DIST);
const float xratio = (end.x - mesh_index_to_xpos(iend.x)) * RECIPROCAL(MESH_X_DIST);
float z1 = z_values[cell_dest_xi ][cell_dest_yi ] + xratio *
(z_values[cell_dest_xi + 1][cell_dest_yi ] - z_values[cell_dest_xi][cell_dest_yi ]),
z2 = z_values[cell_dest_xi ][cell_dest_yi + 1] + xratio *
(z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]);
if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0;
float z1, z2;
if (iend.x >= GRID_MAX_POINTS_X - 1)
z1 = z2 = 0.0;
else {
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]);
}
// X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset.
const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * RECIPROCAL(MESH_Y_DIST),
z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0;
const float yratio = (end.y - mesh_index_to_ypos(iend.y)) * RECIPROCAL(MESH_Y_DIST),
z0 = iend.y < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end.z) : 0.0;
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + (isnan(z0) ? 0.0 : z0), end[E_AXIS], scaled_fr_mm_s, extruder);
set_current_from_destination();
if (!isnan(z0)) end.z += z0;
planner.buffer_segment(end, scaled_fr_mm_s, extruder);
current_position = destination;
return;
}
@ -112,17 +103,11 @@
* case - crossing only one X or Y line - after details are worked out to reduce computation.
*/
const float dx = end[X_AXIS] - start[X_AXIS],
dy = end[Y_AXIS] - start[Y_AXIS];
const int left_flag = dx < 0.0 ? 1 : 0,
down_flag = dy < 0.0 ? 1 : 0;
const float adx = left_flag ? -dx : dx,
ady = down_flag ? -dy : dy;
const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1,
dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1;
const xy_float_t dist = end - start;
const xy_bool_t neg { dist.x < 0, dist.y < 0 };
const xy_int8_t ineg { int8_t(neg.x), int8_t(neg.y) };
const xy_float_t sign { neg.x ? -1.0f : 1.0f, neg.y ? -1.0f : 1.0f };
const xy_int8_t iadd { int8_t(iend.x == istart.x ? 0 : sign.x), int8_t(iend.y == istart.y ? 0 : sign.y) };
/**
* Compute the extruder scaling factor for each partial move, checking for
@ -132,64 +117,64 @@
* components. The larger of the two is used to preserve precision.
*/
const bool use_x_dist = adx > ady;
const xy_float_t ad = sign * dist;
const bool use_x_dist = ad.x > ad.y;
float on_axis_distance = use_x_dist ? dx : dy,
e_position = end[E_AXIS] - start[E_AXIS],
z_position = end[Z_AXIS] - start[Z_AXIS];
float on_axis_distance = use_x_dist ? dist.x : dist.y,
e_position = end.e - start.e,
z_position = end.z - start.z;
const float e_normalized_dist = e_position / on_axis_distance,
const float e_normalized_dist = e_position / on_axis_distance, // Allow divide by zero
z_normalized_dist = z_position / on_axis_distance;
int current_xi = cell_start_xi,
current_yi = cell_start_yi;
xy_int8_t icell = istart;
const float m = dy / dx,
c = start[Y_AXIS] - m * start[X_AXIS];
const float ratio = dist.y / dist.x, // Allow divide by zero
c = start.y - ratio * start.x;
const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0),
inf_m_flag = (isinf(m) != 0);
const bool inf_normalized_flag = isinf(e_normalized_dist),
inf_ratio_flag = isinf(ratio);
/**
* Handle vertical lines that stay within one column.
* These need not be perfectly vertical.
*/
if (dxi == 0) { // Vertical line?
current_yi += down_flag; // Line going down? Just go to the bottom.
while (current_yi != cell_dest_yi + down_flag) {
current_yi += dyi;
const float next_mesh_line_y = mesh_index_to_ypos(current_yi);
if (iadd.x == 0) { // Vertical line?
icell.y += ineg.y; // Line going down? Just go to the bottom.
while (icell.y != iend.y + ineg.y) {
icell.y += iadd.y;
const float next_mesh_line_y = mesh_index_to_ypos(icell.y);
/**
* Skip the calculations for an infinite slope.
* For others the next X is the same so this can continue.
* Calculate X at the next Y mesh line.
*/
const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m;
const float rx = inf_ratio_flag ? start.x : (next_mesh_line_y - c) / ratio;
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi)
* planner.fade_scaling_factor_for_z(end[Z_AXIS]);
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x, icell.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
const float ry = mesh_index_to_ypos(current_yi);
const float ry = mesh_index_to_ypos(icell.y);
/**
* Without this check, it's possible to generate a zero length move, as in the case where
* the line is heading down, starting exactly on a mesh line boundary. Since this is rare
* it might be fine to remove this check and let planner.buffer_segment() filter it out.
*/
if (ry != start[Y_AXIS]) {
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS];
e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist;
z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
if (ry != start.y) {
if (!inf_normalized_flag) { // fall-through faster than branch
on_axis_distance = use_x_dist ? rx - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end[E_AXIS];
z_position = end[Z_AXIS];
e_position = end.e;
z_position = end.z;
}
planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder);
@ -197,10 +182,10 @@
}
// At the final destination? Usually not, but when on a Y Mesh Line it's completed.
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
if (xy_pos_t(current_position) != xy_pos_t(end))
goto FINAL_MOVE;
set_current_from_destination();
current_position = destination;
return;
}
@ -208,36 +193,34 @@
* Handle horizontal lines that stay within one row.
* These need not be perfectly horizontal.
*/
if (dyi == 0) { // Horizontal line?
current_xi += left_flag; // Heading left? Just go to the left edge of the cell for the first move.
while (current_xi != cell_dest_xi + left_flag) {
current_xi += dxi;
const float next_mesh_line_x = mesh_index_to_xpos(current_xi),
ry = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line
if (iadd.y == 0) { // Horizontal line?
icell.x += ineg.x; // Heading left? Just go to the left edge of the cell for the first move.
while (icell.x != iend.x + ineg.x) {
icell.x += iadd.x;
const float rx = mesh_index_to_xpos(icell.x);
const float ry = ratio * rx + c; // Calculate Y at the next X mesh line
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi)
* planner.fade_scaling_factor_for_z(end[Z_AXIS]);
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x, icell.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
const float rx = mesh_index_to_xpos(current_xi);
/**
* Without this check, it's possible to generate a zero length move, as in the case where
* the line is heading left, starting exactly on a mesh line boundary. Since this is rare
* it might be fine to remove this check and let planner.buffer_segment() filter it out.
*/
if (rx != start[X_AXIS]) {
if (rx != start.x) {
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS];
e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move
z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? rx - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move
z_position = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end[E_AXIS];
z_position = end[Z_AXIS];
e_position = end.e;
z_position = end.z;
}
if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder))
@ -245,93 +228,88 @@
} //else printf("FIRST MOVE PRUNED ");
}
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
if (xy_pos_t(current_position) != xy_pos_t(end))
goto FINAL_MOVE;
set_current_from_destination();
current_position = destination;
return;
}
/**
*
* Handle the generic case of a line crossing both X and Y Mesh lines.
* Generic case of a line crossing both X and Y Mesh lines.
*
*/
int xi_cnt = cell_start_xi - cell_dest_xi,
yi_cnt = cell_start_yi - cell_dest_yi;
xy_int8_t cnt = (istart - iend).ABS();
if (xi_cnt < 0) xi_cnt = -xi_cnt;
if (yi_cnt < 0) yi_cnt = -yi_cnt;
icell += ineg;
current_xi += left_flag;
current_yi += down_flag;
while (cnt) {
while (xi_cnt || yi_cnt) {
const float next_mesh_line_x = mesh_index_to_xpos(icell.x + iadd.x),
next_mesh_line_y = mesh_index_to_ypos(icell.y + iadd.y),
ry = ratio * next_mesh_line_x + c, // Calculate Y at the next X mesh line
rx = (next_mesh_line_y - c) / ratio; // Calculate X at the next Y mesh line
// (No need to worry about ratio == 0.
// In that case, it was already detected
// as a vertical line move above.)
const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi),
next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi),
ry = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line
rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line
// (No need to worry about m being zero.
// If that was the case, it was already detected
// as a vertical line move above.)
if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first
if (neg.x == (rx > next_mesh_line_x)) { // Check if we hit the Y line first
// Yes! Crossing a Y Mesh Line next
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi)
* planner.fade_scaling_factor_for_z(end[Z_AXIS]);
float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, icell.x - ineg.x, icell.y + iadd.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS];
e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist;
z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? rx - start.x : next_mesh_line_y - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end[E_AXIS];
z_position = end[Z_AXIS];
e_position = end.e;
z_position = end.z;
}
if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, scaled_fr_mm_s, extruder))
break;
current_yi += dyi;
yi_cnt--;
icell.y += iadd.y;
cnt.y--;
}
else {
// Yes! Crossing a X Mesh Line next
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag)
* planner.fade_scaling_factor_for_z(end[Z_AXIS]);
float z0 = z_correction_for_y_on_vertical_mesh_line(ry, icell.x + iadd.x, icell.y - ineg.y)
* planner.fade_scaling_factor_for_z(end.z);
// Undefined parts of the Mesh in z_values[][] are NAN.
// Replace NAN corrections with 0.0 to prevent NAN propagation.
if (isnan(z0)) z0 = 0.0;
if (!inf_normalized_flag) {
on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS];
e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist;
z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist;
on_axis_distance = use_x_dist ? next_mesh_line_x - start.x : ry - start.y;
e_position = start.e + on_axis_distance * e_normalized_dist;
z_position = start.z + on_axis_distance * z_normalized_dist;
}
else {
e_position = end[E_AXIS];
z_position = end[Z_AXIS];
e_position = end.e;
z_position = end.z;
}
if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, scaled_fr_mm_s, extruder))
break;
current_xi += dxi;
xi_cnt--;
icell.x += iadd.x;
cnt.x--;
}
if (xi_cnt < 0 || yi_cnt < 0) break; // Too far! Exit the loop and go to FINAL_MOVE
if (cnt.x < 0 || cnt.y < 0) break; // Too far! Exit the loop and go to FINAL_MOVE
}
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
if (xy_pos_t(current_position) != xy_pos_t(end))
goto FINAL_MOVE;
set_current_from_destination();
current_position = destination;
}
#else // UBL_SEGMENTED
@ -356,56 +334,42 @@
bool _O2 unified_bed_leveling::line_to_destination_segmented(const feedRate_t &scaled_fr_mm_s) {
if (!position_is_reachable(destination[X_AXIS], destination[Y_AXIS])) // fail if moving outside reachable boundary
return true; // did not move, so current_position still accurate
if (!position_is_reachable(destination)) // fail if moving outside reachable boundary
return true; // did not move, so current_position still accurate
const float total[XYZE] = {
destination[X_AXIS] - current_position[X_AXIS],
destination[Y_AXIS] - current_position[Y_AXIS],
destination[Z_AXIS] - current_position[Z_AXIS],
destination[E_AXIS] - current_position[E_AXIS]
};
const xyze_pos_t total = destination - current_position;
const float cartesian_xy_mm = HYPOT(total[X_AXIS], total[Y_AXIS]); // total horizontal xy distance
const float cart_xy_mm_2 = HYPOT2(total.x, total.y),
cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance
#if IS_KINEMATIC
const float seconds = cartesian_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
uint16_t segments = LROUND(delta_segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
seglimit = LROUND(cartesian_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
uint16_t segments = LROUND(delta_segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
seglimit = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
#else
uint16_t segments = LROUND(cartesian_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // cartesian fixed segment length
uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length
#endif
NOLESS(segments, 1U); // must have at least one segment
const float inv_segments = 1.0f / segments; // divide once, multiply thereafter
NOLESS(segments, 1U); // Must have at least one segment
const float inv_segments = 1.0f / segments, // Reciprocal to save calculation
segment_xyz_mm = SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments; // Length of each segment
const float segment_xyz_mm = HYPOT(cartesian_xy_mm, total[Z_AXIS]) * inv_segments; // length of each segment
#if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = scaled_fr_mm_s / segment_xyz_mm;
#endif
const float diff[XYZE] = {
total[X_AXIS] * inv_segments,
total[Y_AXIS] * inv_segments,
total[Z_AXIS] * inv_segments,
total[E_AXIS] * inv_segments
};
xyze_float_t diff = total * inv_segments;
// Note that E segment distance could vary slightly as z mesh height
// changes for each segment, but small enough to ignore.
float raw[XYZE] = {
current_position[X_AXIS],
current_position[Y_AXIS],
current_position[Z_AXIS],
current_position[E_AXIS]
};
xyze_pos_t raw = current_position;
// Just do plain segmentation if UBL is inactive or the target is above the fade height
if (!planner.leveling_active || !planner.leveling_active_at_z(destination[Z_AXIS])) {
if (!planner.leveling_active || !planner.leveling_active_at_z(destination.z)) {
while (--segments) {
LOOP_XYZE(i) raw[i] += diff[i];
raw += diff;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm
#if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
@ -417,17 +381,17 @@
, inv_duration
#endif
);
return false; // moved but did not set_current_from_destination();
return false; // Did not set current from destination
}
// Otherwise perform per-segment leveling
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
const float fade_scaling_factor = planner.fade_scaling_factor_for_z(destination[Z_AXIS]);
const float fade_scaling_factor = planner.fade_scaling_factor_for_z(destination.z);
#endif
// increment to first segment destination
LOOP_XYZE(i) raw[i] += diff[i];
// Move to first segment destination
raw += diff;
for (;;) { // for each mesh cell encountered during the move
@ -438,75 +402,68 @@
// in top of loop and again re-find same adjacent cell and use it, just less efficient
// for mesh inset area.
int8_t cell_xi = (raw[X_AXIS] - (MESH_MIN_X)) * RECIPROCAL(MESH_X_DIST),
cell_yi = (raw[Y_AXIS] - (MESH_MIN_Y)) * RECIPROCAL(MESH_Y_DIST);
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))
};
LIMIT(icell.x, 0, (GRID_MAX_POINTS_X) - 1);
LIMIT(icell.y, 0, (GRID_MAX_POINTS_Y) - 1);
LIMIT(cell_xi, 0, (GRID_MAX_POINTS_X) - 1);
LIMIT(cell_yi, 0, (GRID_MAX_POINTS_Y) - 1);
const float x0 = mesh_index_to_xpos(cell_xi), // 64 byte table lookup avoids mul+add
y0 = mesh_index_to_ypos(cell_yi);
float z_x0y0 = z_values[cell_xi ][cell_yi ], // z at lower left corner
z_x1y0 = z_values[cell_xi+1][cell_yi ], // z at upper left corner
z_x0y1 = z_values[cell_xi ][cell_yi+1], // z at lower right corner
z_x1y1 = z_values[cell_xi+1][cell_yi+1]; // z at upper right corner
float z_x0y0 = z_values[icell.x ][icell.y ], // z at lower left corner
z_x1y0 = z_values[icell.x+1][icell.y ], // z at upper left corner
z_x0y1 = z_values[icell.x ][icell.y+1], // z at lower right corner
z_x1y1 = z_values[icell.x+1][icell.y+1]; // z at upper right corner
if (isnan(z_x0y0)) z_x0y0 = 0; // ideally activating planner.leveling_active (G29 A)
if (isnan(z_x1y0)) z_x1y0 = 0; // should refuse if any invalid mesh points
if (isnan(z_x0y1)) z_x0y1 = 0; // in order to avoid isnan tests per cell,
if (isnan(z_x1y1)) z_x1y1 = 0; // thus guessing zero for undefined points
float cx = raw[X_AXIS] - x0, // cell-relative x and y
cy = raw[Y_AXIS] - y0;
const xy_pos_t pos = { mesh_index_to_xpos(icell.x), mesh_index_to_ypos(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)
float z_cxy0 = z_x0y0 + z_xmy0 * cx; // z height along y0 at cx (changes for each cx in cell)
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 * cx, // z height along y1 at cx
z_cxyd = z_cxy1 - z_cxy0; // z height difference along cx from y0 to y1
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 cx from y0 to y1 (changes for each cx in cell)
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_cxcy = z_cxy0 + z_cxym * cy; // interpolated mesh z height along cx at cy (do inside the segment loop)
// float z_cxcy = z_cxy0 + z_cxym * cell.y; // interpolated mesh z height along cell.x at cell.y (do inside the segment loop)
// As subsequent segments step through this cell, the z_cxy0 intercept will change
// and the z_cxym slope will change, both as a function of cx within the cell, and
// 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_AXIS], // per-segment adjustment to z_cxy0
z_sxym = (z_xmy1 - z_xmy0) * RECIPROCAL(MESH_Y_DIST) * diff[X_AXIS]; // per-segment adjustment to z_cxym
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
for (;;) { // for all segments within this mesh cell
if (--segments == 0) COPY(raw, destination); // if this is last segment, use destination for exact
if (--segments == 0) raw = destination; // if this is last segment, use destination for exact
const float z_cxcy = (z_cxy0 + z_cxym * cy) // interpolated mesh z height along cx at cy
const float z_cxcy = (z_cxy0 + z_cxym * cell.y) // interpolated mesh z height along cell.x at cell.y
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
* fade_scaling_factor // apply fade factor to interpolated mesh height
#endif
;
const float z = raw[Z_AXIS];
raw[Z_AXIS] += z_cxcy;
planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, segment_xyz_mm
planner.buffer_line(raw.x, raw.y, raw.z + z_cxcy, raw.e, scaled_fr_mm_s, active_extruder, segment_xyz_mm
#if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
#endif
);
raw[Z_AXIS] = z;
if (segments == 0) // done with last segment
return false; // did not set_current_from_destination()
return false; // didn't set current from destination
LOOP_XYZE(i) raw[i] += diff[i];
raw += diff;
cell += diff;
cx += diff[X_AXIS];
cy += diff[Y_AXIS];
if (!WITHIN(cx, 0, MESH_X_DIST) || !WITHIN(cy, 0, MESH_Y_DIST)) // done within this cell, break to next
if (!WITHIN(cell.x, 0, MESH_X_DIST) || !WITHIN(cell.y, 0, MESH_Y_DIST)) // done within this cell, break to next
break;
// Next segment still within same mesh cell, adjust the per-segment