Consolidate "bedlevel" code
This commit is contained in:
425
Marlin/src/feature/bedlevel/abl/abl.cpp
Normal file
425
Marlin/src/feature/bedlevel/abl/abl.cpp
Normal file
@ -0,0 +1,425 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
|
||||
|
||||
#include "abl.h"
|
||||
|
||||
#include "../../../module/motion.h"
|
||||
|
||||
int bilinear_grid_spacing[2], bilinear_start[2];
|
||||
float bilinear_grid_factor[2],
|
||||
z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
|
||||
|
||||
/**
|
||||
* Extrapolate a single point from its neighbors
|
||||
*/
|
||||
static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) {
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(LEVELING)) {
|
||||
SERIAL_ECHOPGM("Extrapolate [");
|
||||
if (x < 10) SERIAL_CHAR(' ');
|
||||
SERIAL_ECHO((int)x);
|
||||
SERIAL_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' ');
|
||||
SERIAL_CHAR(' ');
|
||||
if (y < 10) SERIAL_CHAR(' ');
|
||||
SERIAL_ECHO((int)y);
|
||||
SERIAL_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' ');
|
||||
SERIAL_CHAR(']');
|
||||
}
|
||||
#endif
|
||||
if (!isnan(z_values[x][y])) {
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM(" (done)");
|
||||
#endif
|
||||
return; // Don't overwrite good values.
|
||||
}
|
||||
SERIAL_EOL();
|
||||
|
||||
// Get X neighbors, Y neighbors, and XY neighbors
|
||||
const uint8_t x1 = x + xdir, y1 = y + ydir, x2 = x1 + xdir, y2 = y1 + ydir;
|
||||
float a1 = z_values[x1][y ], a2 = z_values[x2][y ],
|
||||
b1 = z_values[x ][y1], b2 = z_values[x ][y2],
|
||||
c1 = z_values[x1][y1], c2 = z_values[x2][y2];
|
||||
|
||||
// Treat far unprobed points as zero, near as equal to far
|
||||
if (isnan(a2)) a2 = 0.0; if (isnan(a1)) a1 = a2;
|
||||
if (isnan(b2)) b2 = 0.0; if (isnan(b1)) b1 = b2;
|
||||
if (isnan(c2)) c2 = 0.0; if (isnan(c1)) c1 = c2;
|
||||
|
||||
const float a = 2 * a1 - a2, b = 2 * b1 - b2, c = 2 * c1 - c2;
|
||||
|
||||
// Take the average instead of the median
|
||||
z_values[x][y] = (a + b + c) / 3.0;
|
||||
|
||||
// Median is robust (ignores outliers).
|
||||
// z_values[x][y] = (a < b) ? ((b < c) ? b : (c < a) ? a : c)
|
||||
// : ((c < b) ? b : (a < c) ? a : c);
|
||||
}
|
||||
|
||||
//Enable this if your SCARA uses 180° of total area
|
||||
//#define EXTRAPOLATE_FROM_EDGE
|
||||
|
||||
#if ENABLED(EXTRAPOLATE_FROM_EDGE)
|
||||
#if GRID_MAX_POINTS_X < GRID_MAX_POINTS_Y
|
||||
#define HALF_IN_X
|
||||
#elif GRID_MAX_POINTS_Y < GRID_MAX_POINTS_X
|
||||
#define HALF_IN_Y
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Fill in the unprobed points (corners of circular print surface)
|
||||
* using linear extrapolation, away from the center.
|
||||
*/
|
||||
void extrapolate_unprobed_bed_level() {
|
||||
#ifdef HALF_IN_X
|
||||
constexpr uint8_t ctrx2 = 0, xlen = GRID_MAX_POINTS_X - 1;
|
||||
#else
|
||||
constexpr uint8_t ctrx1 = (GRID_MAX_POINTS_X - 1) / 2, // left-of-center
|
||||
ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center
|
||||
xlen = ctrx1;
|
||||
#endif
|
||||
|
||||
#ifdef HALF_IN_Y
|
||||
constexpr uint8_t ctry2 = 0, ylen = GRID_MAX_POINTS_Y - 1;
|
||||
#else
|
||||
constexpr uint8_t ctry1 = (GRID_MAX_POINTS_Y - 1) / 2, // top-of-center
|
||||
ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center
|
||||
ylen = ctry1;
|
||||
#endif
|
||||
|
||||
for (uint8_t xo = 0; xo <= xlen; xo++)
|
||||
for (uint8_t yo = 0; yo <= ylen; yo++) {
|
||||
uint8_t x2 = ctrx2 + xo, y2 = ctry2 + yo;
|
||||
#ifndef HALF_IN_X
|
||||
const uint8_t x1 = ctrx1 - xo;
|
||||
#endif
|
||||
#ifndef HALF_IN_Y
|
||||
const uint8_t y1 = ctry1 - yo;
|
||||
#ifndef HALF_IN_X
|
||||
extrapolate_one_point(x1, y1, +1, +1); // left-below + +
|
||||
#endif
|
||||
extrapolate_one_point(x2, y1, -1, +1); // right-below - +
|
||||
#endif
|
||||
#ifndef HALF_IN_X
|
||||
extrapolate_one_point(x1, y2, +1, -1); // left-above + -
|
||||
#endif
|
||||
extrapolate_one_point(x2, y2, -1, -1); // right-above - -
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void print_bilinear_leveling_grid() {
|
||||
SERIAL_ECHOLNPGM("Bilinear Leveling Grid:");
|
||||
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3,
|
||||
[](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; }
|
||||
);
|
||||
}
|
||||
|
||||
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
|
||||
|
||||
#define ABL_GRID_POINTS_VIRT_X (GRID_MAX_POINTS_X - 1) * (BILINEAR_SUBDIVISIONS) + 1
|
||||
#define ABL_GRID_POINTS_VIRT_Y (GRID_MAX_POINTS_Y - 1) * (BILINEAR_SUBDIVISIONS) + 1
|
||||
#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 };
|
||||
|
||||
void print_bilinear_leveling_grid_virt() {
|
||||
SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:");
|
||||
print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5,
|
||||
[](const uint8_t ix, const uint8_t iy) { return z_values_virt[ix][iy]; }
|
||||
);
|
||||
}
|
||||
|
||||
#define LINEAR_EXTRAPOLATION(E, I) ((E) * 2 - (I))
|
||||
float bed_level_virt_coord(const uint8_t x, const uint8_t y) {
|
||||
uint8_t ep = 0, ip = 1;
|
||||
if (!x || x == ABL_TEMP_POINTS_X - 1) {
|
||||
if (x) {
|
||||
ep = GRID_MAX_POINTS_X - 1;
|
||||
ip = GRID_MAX_POINTS_X - 2;
|
||||
}
|
||||
if (WITHIN(y, 1, ABL_TEMP_POINTS_Y - 2))
|
||||
return LINEAR_EXTRAPOLATION(
|
||||
z_values[ep][y - 1],
|
||||
z_values[ip][y - 1]
|
||||
);
|
||||
else
|
||||
return LINEAR_EXTRAPOLATION(
|
||||
bed_level_virt_coord(ep + 1, y),
|
||||
bed_level_virt_coord(ip + 1, y)
|
||||
);
|
||||
}
|
||||
if (!y || y == ABL_TEMP_POINTS_Y - 1) {
|
||||
if (y) {
|
||||
ep = GRID_MAX_POINTS_Y - 1;
|
||||
ip = GRID_MAX_POINTS_Y - 2;
|
||||
}
|
||||
if (WITHIN(x, 1, ABL_TEMP_POINTS_X - 2))
|
||||
return LINEAR_EXTRAPOLATION(
|
||||
z_values[x - 1][ep],
|
||||
z_values[x - 1][ip]
|
||||
);
|
||||
else
|
||||
return LINEAR_EXTRAPOLATION(
|
||||
bed_level_virt_coord(x, ep + 1),
|
||||
bed_level_virt_coord(x, ip + 1)
|
||||
);
|
||||
}
|
||||
return z_values[x - 1][y - 1];
|
||||
}
|
||||
|
||||
static float bed_level_virt_cmr(const float p[4], const uint8_t i, const float t) {
|
||||
return (
|
||||
p[i-1] * -t * sq(1 - t)
|
||||
+ 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;
|
||||
}
|
||||
|
||||
static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const float &tx, const float &ty) {
|
||||
float row[4], column[4];
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
for (uint8_t j = 0; j < 4; j++) {
|
||||
column[j] = bed_level_virt_coord(i + x - 1, j + y - 1);
|
||||
}
|
||||
row[i] = bed_level_virt_cmr(column, 1, ty);
|
||||
}
|
||||
return bed_level_virt_cmr(row, 1, tx);
|
||||
}
|
||||
|
||||
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]);
|
||||
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++)
|
||||
for (uint8_t tx = 0; tx < BILINEAR_SUBDIVISIONS; tx++) {
|
||||
if ((ty && y == GRID_MAX_POINTS_Y - 1) || (tx && x == GRID_MAX_POINTS_X - 1))
|
||||
continue;
|
||||
z_values_virt[x * (BILINEAR_SUBDIVISIONS) + tx][y * (BILINEAR_SUBDIVISIONS) + ty] =
|
||||
bed_level_virt_2cmr(
|
||||
x + 1,
|
||||
y + 1,
|
||||
(float)tx / (BILINEAR_SUBDIVISIONS),
|
||||
(float)ty / (BILINEAR_SUBDIVISIONS)
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif // ABL_BILINEAR_SUBDIVISION
|
||||
|
||||
// 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]);
|
||||
#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_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_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 logical[XYZ]) {
|
||||
|
||||
static float z1, d2, z3, d4, L, D, ratio_x, ratio_y,
|
||||
last_x = -999.999, last_y = -999.999;
|
||||
|
||||
// Whole units for the grid line indices. Constrained within bounds.
|
||||
static int8_t gridx, gridy, nextx, nexty,
|
||||
last_gridx = -99, last_gridy = -99;
|
||||
|
||||
// XY relative to the probed area
|
||||
const float x = RAW_X_POSITION(logical[X_AXIS]) - bilinear_start[X_AXIS],
|
||||
y = RAW_Y_POSITION(logical[Y_AXIS]) - bilinear_start[Y_AXIS];
|
||||
|
||||
#if ENABLED(EXTRAPOLATE_BEYOND_GRID)
|
||||
// Keep using the last grid box
|
||||
#define FAR_EDGE_OR_BOX 2
|
||||
#else
|
||||
// Just use the grid far edge
|
||||
#define FAR_EDGE_OR_BOX 1
|
||||
#endif
|
||||
|
||||
if (last_x != x) {
|
||||
last_x = x;
|
||||
ratio_x = x * 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 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.)
|
||||
#endif
|
||||
|
||||
gridx = gx;
|
||||
nextx = min(gridx + 1, ABL_BG_POINTS_X - 1);
|
||||
}
|
||||
|
||||
if (last_y != y || last_gridx != gridx) {
|
||||
|
||||
if (last_y != y) {
|
||||
last_y = y;
|
||||
ratio_y = y * 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 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.)
|
||||
#endif
|
||||
|
||||
gridy = gy;
|
||||
nexty = min(gridy + 1, ABL_BG_POINTS_Y - 1);
|
||||
}
|
||||
|
||||
if (last_gridx != gridx || last_gridy != gridy) {
|
||||
last_gridx = gridx;
|
||||
last_gridy = gridy;
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Bilinear interpolate. Needed since y or gridx 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
|
||||
|
||||
/*
|
||||
static float last_offset = 0;
|
||||
if (FABS(last_offset - offset) > 0.2) {
|
||||
SERIAL_ECHOPGM("Sudden Shift at ");
|
||||
SERIAL_ECHOPAIR("x=", x);
|
||||
SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[X_AXIS]);
|
||||
SERIAL_ECHOLNPAIR(" -> gridx=", gridx);
|
||||
SERIAL_ECHOPAIR(" y=", y);
|
||||
SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[Y_AXIS]);
|
||||
SERIAL_ECHOLNPAIR(" -> gridy=", gridy);
|
||||
SERIAL_ECHOPAIR(" ratio_x=", ratio_x);
|
||||
SERIAL_ECHOLNPAIR(" ratio_y=", ratio_y);
|
||||
SERIAL_ECHOPAIR(" z1=", z1);
|
||||
SERIAL_ECHOPAIR(" z2=", z2);
|
||||
SERIAL_ECHOPAIR(" z3=", z3);
|
||||
SERIAL_ECHOLNPAIR(" z4=", z4);
|
||||
SERIAL_ECHOPAIR(" L=", L);
|
||||
SERIAL_ECHOPAIR(" R=", R);
|
||||
SERIAL_ECHOLNPAIR(" offset=", offset);
|
||||
}
|
||||
last_offset = offset;
|
||||
//*/
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
#if !IS_KINEMATIC
|
||||
|
||||
#define CELL_INDEX(A,V) ((RAW_##A##_POSITION(V) - bilinear_start[A##_AXIS]) * ABL_BG_FACTOR(A##_AXIS))
|
||||
|
||||
/**
|
||||
* Prepare a bilinear-leveled linear move on Cartesian,
|
||||
* splitting the move where it crosses grid borders.
|
||||
*/
|
||||
void bilinear_line_to_destination(const float fr_mm_s, uint16_t x_splits, uint16_t y_splits) {
|
||||
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]);
|
||||
cx1 = constrain(cx1, 0, ABL_BG_POINTS_X - 2);
|
||||
cy1 = constrain(cy1, 0, ABL_BG_POINTS_Y - 2);
|
||||
cx2 = constrain(cx2, 0, ABL_BG_POINTS_X - 2);
|
||||
cy2 = constrain(cy2, 0, ABL_BG_POINTS_Y - 2);
|
||||
|
||||
if (cx1 == cx2 && cy1 == cy2) {
|
||||
// Start and end on same mesh square
|
||||
line_to_destination(fr_mm_s);
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
#define LINE_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist)
|
||||
|
||||
float normalized_dist, end[XYZE];
|
||||
|
||||
// Split at the left/front border of the right/top square
|
||||
const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2);
|
||||
if (cx2 != cx1 && TEST(x_splits, gcx)) {
|
||||
COPY(end, destination);
|
||||
destination[X_AXIS] = LOGICAL_X_POSITION(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, gcx);
|
||||
}
|
||||
else if (cy2 != cy1 && TEST(y_splits, gcy)) {
|
||||
COPY(end, destination);
|
||||
destination[Y_AXIS] = LOGICAL_Y_POSITION(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, gcy);
|
||||
}
|
||||
else {
|
||||
// Already split on a border
|
||||
line_to_destination(fr_mm_s);
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
destination[Z_AXIS] = LINE_SEGMENT_END(Z);
|
||||
destination[E_AXIS] = LINE_SEGMENT_END(E);
|
||||
|
||||
// Do the split and look for more borders
|
||||
bilinear_line_to_destination(fr_mm_s, x_splits, y_splits);
|
||||
|
||||
// Restore destination from stack
|
||||
COPY(destination, end);
|
||||
bilinear_line_to_destination(fr_mm_s, x_splits, y_splits);
|
||||
}
|
||||
|
||||
#endif // !IS_KINEMATIC
|
||||
|
||||
#endif // AUTO_BED_LEVELING_BILINEAR
|
51
Marlin/src/feature/bedlevel/abl/abl.h
Normal file
51
Marlin/src/feature/bedlevel/abl/abl.h
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __ABL_H__
|
||||
#define __ABL_H__
|
||||
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
|
||||
|
||||
#include "../bedlevel.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 logical[XYZ]);
|
||||
|
||||
void extrapolate_unprobed_bed_level();
|
||||
void print_bilinear_leveling_grid();
|
||||
void refresh_bed_level();
|
||||
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
|
||||
void print_bilinear_leveling_grid_virt();
|
||||
void bed_level_virt_interpolate();
|
||||
#endif
|
||||
|
||||
#if !IS_KINEMATIC
|
||||
void bilinear_line_to_destination(const float fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF);
|
||||
#endif
|
||||
|
||||
#endif // AUTO_BED_LEVELING_BILINEAR
|
||||
|
||||
#endif // __ABL_H__
|
314
Marlin/src/feature/bedlevel/bedlevel.cpp
Normal file
314
Marlin/src/feature/bedlevel/bedlevel.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
#if HAS_LEVELING
|
||||
|
||||
#include "bedlevel.h"
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY)
|
||||
#include "../../module/stepper.h"
|
||||
#endif
|
||||
|
||||
#if PLANNER_LEVELING
|
||||
#include "../../module/planner.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(PROBE_MANUALLY)
|
||||
bool g29_in_progress = false;
|
||||
#if ENABLED(LCD_BED_LEVELING)
|
||||
#include "../../lcd/ultralcd.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool leveling_is_valid() {
|
||||
return
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
mbl.has_mesh()
|
||||
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
|
||||
!!bilinear_grid_spacing[X_AXIS]
|
||||
#elif ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
true
|
||||
#else // 3POINT, LINEAR
|
||||
true
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
bool leveling_is_active() {
|
||||
return
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
mbl.active()
|
||||
#elif ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
ubl.state.active
|
||||
#else // OLDSCHOOL_ABL
|
||||
planner.abl_enabled
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn bed leveling on or off, fixing the current
|
||||
* position as-needed.
|
||||
*
|
||||
* Disable: Current position = physical position
|
||||
* Enable: Current position = "unleveled" physical position
|
||||
*/
|
||||
void set_bed_leveling_enabled(const bool enable/*=true*/) {
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
|
||||
const bool can_change = (!enable || leveling_is_valid());
|
||||
#else
|
||||
constexpr bool can_change = true;
|
||||
#endif
|
||||
|
||||
if (can_change && enable != leveling_is_active()) {
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
|
||||
if (!enable)
|
||||
planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
|
||||
|
||||
const bool enabling = enable && leveling_is_valid();
|
||||
mbl.set_active(enabling);
|
||||
if (enabling) planner.unapply_leveling(current_position);
|
||||
|
||||
#elif ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
#if PLANNER_LEVELING
|
||||
if (ubl.state.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]);
|
||||
ubl.state.active = false; // disable only AFTER calling apply_leveling
|
||||
}
|
||||
else { // leveling from off to on
|
||||
ubl.state.active = true; // enable BEFORE calling unapply_leveling, otherwise ignored
|
||||
// change physical current_position to unleveled current_position without moving steppers.
|
||||
planner.unapply_leveling(current_position);
|
||||
}
|
||||
#else
|
||||
ubl.state.active = enable; // just flip the bit, current_position will be wrong until next move.
|
||||
#endif
|
||||
|
||||
#else // OLDSCHOOL_ABL
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
|
||||
// Force bilinear_z_offset to re-calculate next time
|
||||
const float reset[XYZ] = { -9999.999, -9999.999, 0 };
|
||||
(void)bilinear_z_offset(reset);
|
||||
#endif
|
||||
|
||||
// Enable or disable leveling compensation in the planner
|
||||
planner.abl_enabled = enable;
|
||||
|
||||
if (!enable)
|
||||
// When disabling just get the current position from the steppers.
|
||||
// This will yield the smallest error when first converted back to steps.
|
||||
set_current_from_steppers_for_axis(
|
||||
#if ABL_PLANAR
|
||||
ALL_AXES
|
||||
#else
|
||||
Z_AXIS
|
||||
#endif
|
||||
);
|
||||
else
|
||||
// When enabling, remove compensation from the current position,
|
||||
// so compensation will give the right stepper counts.
|
||||
planner.unapply_leveling(current_position);
|
||||
|
||||
#endif // OLDSCHOOL_ABL
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
|
||||
void set_z_fade_height(const float zfh) {
|
||||
|
||||
const bool level_active = leveling_is_active();
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
|
||||
if (level_active)
|
||||
set_bed_leveling_enabled(false); // turn off before changing fade height for proper apply/unapply leveling to maintain current_position
|
||||
planner.z_fade_height = zfh;
|
||||
planner.inverse_z_fade_height = RECIPROCAL(zfh);
|
||||
if (level_active)
|
||||
set_bed_leveling_enabled(true); // turn back on after changing fade height
|
||||
|
||||
#else
|
||||
|
||||
planner.z_fade_height = zfh;
|
||||
planner.inverse_z_fade_height = RECIPROCAL(zfh);
|
||||
|
||||
if (level_active) {
|
||||
set_current_from_steppers_for_axis(
|
||||
#if ABL_PLANAR
|
||||
ALL_AXES
|
||||
#else
|
||||
Z_AXIS
|
||||
#endif
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // ENABLE_LEVELING_FADE_HEIGHT
|
||||
|
||||
/**
|
||||
* Reset calibration results to zero.
|
||||
*/
|
||||
void reset_bed_level() {
|
||||
set_bed_leveling_enabled(false);
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
if (leveling_is_valid()) {
|
||||
mbl.reset();
|
||||
mbl.set_has_mesh(false);
|
||||
}
|
||||
#else
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("reset_bed_level");
|
||||
#endif
|
||||
#if ABL_PLANAR
|
||||
planner.bed_level_matrix.set_to_identity();
|
||||
#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;
|
||||
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;
|
||||
#elif ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
ubl.reset();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(MESH_BED_LEVELING)
|
||||
|
||||
/**
|
||||
* Enable to produce output in JSON format suitable
|
||||
* for SCAD or JavaScript mesh visualizers.
|
||||
*
|
||||
* Visualize meshes in OpenSCAD using the included script.
|
||||
*
|
||||
* buildroot/shared/scripts/MarlinMesh.scad
|
||||
*/
|
||||
//#define SCAD_MESH_OUTPUT
|
||||
|
||||
/**
|
||||
* Print calibration results for plotting or manual frame adjustment.
|
||||
*/
|
||||
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn) {
|
||||
#ifndef SCAD_MESH_OUTPUT
|
||||
for (uint8_t x = 0; x < sx; x++) {
|
||||
for (uint8_t i = 0; i < precision + 2 + (x < 10 ? 1 : 0); i++)
|
||||
SERIAL_PROTOCOLCHAR(' ');
|
||||
SERIAL_PROTOCOL((int)x);
|
||||
}
|
||||
SERIAL_EOL();
|
||||
#endif
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
SERIAL_PROTOCOLLNPGM("measured_z = ["); // open 2D array
|
||||
#endif
|
||||
for (uint8_t y = 0; y < sy; y++) {
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
SERIAL_PROTOCOLPGM(" ["); // open sub-array
|
||||
#else
|
||||
if (y < 10) SERIAL_PROTOCOLCHAR(' ');
|
||||
SERIAL_PROTOCOL((int)y);
|
||||
#endif
|
||||
for (uint8_t x = 0; x < sx; x++) {
|
||||
SERIAL_PROTOCOLCHAR(' ');
|
||||
const float offset = fn(x, y);
|
||||
if (!isnan(offset)) {
|
||||
if (offset >= 0) SERIAL_PROTOCOLCHAR('+');
|
||||
SERIAL_PROTOCOL_F(offset, precision);
|
||||
}
|
||||
else {
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
for (uint8_t i = 3; i < precision + 3; i++)
|
||||
SERIAL_PROTOCOLCHAR(' ');
|
||||
SERIAL_PROTOCOLPGM("NAN");
|
||||
#else
|
||||
for (uint8_t i = 0; i < precision + 3; i++)
|
||||
SERIAL_PROTOCOLCHAR(i ? '=' : ' ');
|
||||
#endif
|
||||
}
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
if (x < sx - 1) SERIAL_PROTOCOLCHAR(',');
|
||||
#endif
|
||||
}
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
SERIAL_PROTOCOLCHAR(' ');
|
||||
SERIAL_PROTOCOLCHAR(']'); // close sub-array
|
||||
if (y < sy - 1) SERIAL_PROTOCOLCHAR(',');
|
||||
#endif
|
||||
SERIAL_EOL();
|
||||
}
|
||||
#ifdef SCAD_MESH_OUTPUT
|
||||
SERIAL_PROTOCOLPGM("];"); // close 2D array
|
||||
#endif
|
||||
SERIAL_EOL();
|
||||
}
|
||||
|
||||
#endif // AUTO_BED_LEVELING_BILINEAR || MESH_BED_LEVELING
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY)
|
||||
|
||||
void _manual_goto_xy(const float &x, const float &y) {
|
||||
const float old_feedrate_mm_s = feedrate_mm_s;
|
||||
#if MANUAL_PROBE_HEIGHT > 0
|
||||
const float prev_z = current_position[Z_AXIS];
|
||||
feedrate_mm_s = homing_feedrate(Z_AXIS);
|
||||
current_position[Z_AXIS] = LOGICAL_Z_POSITION(MANUAL_PROBE_HEIGHT);
|
||||
line_to_current_position();
|
||||
#endif
|
||||
|
||||
feedrate_mm_s = MMM_TO_MMS(XY_PROBE_SPEED);
|
||||
current_position[X_AXIS] = LOGICAL_X_POSITION(x);
|
||||
current_position[Y_AXIS] = LOGICAL_Y_POSITION(y);
|
||||
line_to_current_position();
|
||||
|
||||
#if MANUAL_PROBE_HEIGHT > 0
|
||||
feedrate_mm_s = homing_feedrate(Z_AXIS);
|
||||
current_position[Z_AXIS] = prev_z; // move back to the previous Z.
|
||||
line_to_current_position();
|
||||
#endif
|
||||
|
||||
feedrate_mm_s = old_feedrate_mm_s;
|
||||
stepper.synchronize();
|
||||
|
||||
#if ENABLED(PROBE_MANUALLY) && ENABLED(LCD_BED_LEVELING)
|
||||
lcd_wait_for_move = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if HAS_PROBING_PROCEDURE
|
||||
void out_of_range_error(const char* p_edge) {
|
||||
SERIAL_PROTOCOLPGM("?Probe ");
|
||||
serialprintPGM(p_edge);
|
||||
SERIAL_PROTOCOLLNPGM(" position out of range.");
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HAS_LEVELING
|
72
Marlin/src/feature/bedlevel/bedlevel.h
Normal file
72
Marlin/src/feature/bedlevel/bedlevel.h
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __BEDLEVEL_H__
|
||||
#define __BEDLEVEL_H__
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
#include "mbl/mesh_bed_leveling.h"
|
||||
#elif ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
#include "ubl/ubl.h"
|
||||
#elif HAS_ABL
|
||||
#include "abl/abl.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(PROBE_MANUALLY)
|
||||
extern bool g29_in_progress;
|
||||
#else
|
||||
constexpr bool g29_in_progress = false;
|
||||
#endif
|
||||
|
||||
bool leveling_is_valid();
|
||||
bool leveling_is_active();
|
||||
void set_bed_leveling_enabled(const bool enable=true);
|
||||
void reset_bed_level();
|
||||
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
void set_z_fade_height(const float zfh);
|
||||
#endif
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(MESH_BED_LEVELING)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef float (*element_2d_fn)(const uint8_t, const uint8_t);
|
||||
|
||||
/**
|
||||
* Print calibration results for plotting or manual frame adjustment.
|
||||
*/
|
||||
void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, element_2d_fn fn);
|
||||
|
||||
#endif
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY)
|
||||
void _manual_goto_xy(const float &x, const float &y);
|
||||
#endif
|
||||
|
||||
#if HAS_PROBING_PROCEDURE
|
||||
void out_of_range_error(const char* p_edge);
|
||||
#endif
|
||||
|
||||
#endif // __BEDLEVEL_H__
|
123
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
Normal file
123
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(MESH_BED_LEVELING)
|
||||
|
||||
#include "mesh_bed_leveling.h"
|
||||
|
||||
#include "../../../module/motion.h"
|
||||
#include "../../../feature/bedlevel/bedlevel.h"
|
||||
|
||||
mesh_bed_leveling mbl;
|
||||
|
||||
uint8_t mesh_bed_leveling::status;
|
||||
|
||||
float mesh_bed_leveling::z_offset,
|
||||
mesh_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y],
|
||||
mesh_bed_leveling::index_to_xpos[GRID_MAX_POINTS_X],
|
||||
mesh_bed_leveling::index_to_ypos[GRID_MAX_POINTS_Y];
|
||||
|
||||
mesh_bed_leveling::mesh_bed_leveling() {
|
||||
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i)
|
||||
index_to_xpos[i] = MESH_MIN_X + i * (MESH_X_DIST);
|
||||
for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i)
|
||||
index_to_ypos[i] = MESH_MIN_Y + i * (MESH_Y_DIST);
|
||||
reset();
|
||||
}
|
||||
|
||||
void mesh_bed_leveling::reset() {
|
||||
status = MBL_STATUS_NONE;
|
||||
z_offset = 0;
|
||||
ZERO(z_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a mesh-leveled linear move in a Cartesian setup,
|
||||
* splitting the move where it crosses mesh borders.
|
||||
*/
|
||||
void mesh_line_to_destination(const float fr_mm_s, uint8_t x_splits, uint8_t y_splits) {
|
||||
int cx1 = mbl.cell_index_x(RAW_CURRENT_POSITION(X)),
|
||||
cy1 = mbl.cell_index_y(RAW_CURRENT_POSITION(Y)),
|
||||
cx2 = mbl.cell_index_x(RAW_X_POSITION(destination[X_AXIS])),
|
||||
cy2 = mbl.cell_index_y(RAW_Y_POSITION(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);
|
||||
|
||||
if (cx1 == cx2 && cy1 == cy2) {
|
||||
// Start and end on same mesh square
|
||||
line_to_destination(fr_mm_s);
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
#define MBL_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist)
|
||||
|
||||
float normalized_dist, end[XYZE];
|
||||
|
||||
// Split at the left/front border of the right/top square
|
||||
const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2);
|
||||
if (cx2 != cx1 && TEST(x_splits, gcx)) {
|
||||
COPY(end, destination);
|
||||
destination[X_AXIS] = LOGICAL_X_POSITION(mbl.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);
|
||||
CBI(x_splits, gcx);
|
||||
}
|
||||
else if (cy2 != cy1 && TEST(y_splits, gcy)) {
|
||||
COPY(end, destination);
|
||||
destination[Y_AXIS] = LOGICAL_Y_POSITION(mbl.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);
|
||||
CBI(y_splits, gcy);
|
||||
}
|
||||
else {
|
||||
// Already split on a border
|
||||
line_to_destination(fr_mm_s);
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
destination[Z_AXIS] = MBL_SEGMENT_END(Z);
|
||||
destination[E_AXIS] = MBL_SEGMENT_END(E);
|
||||
|
||||
// Do the split and look for more borders
|
||||
mesh_line_to_destination(fr_mm_s, x_splits, y_splits);
|
||||
|
||||
// Restore destination from stack
|
||||
COPY(destination, end);
|
||||
mesh_line_to_destination(fr_mm_s, x_splits, y_splits);
|
||||
}
|
||||
|
||||
void mbl_mesh_report() {
|
||||
SERIAL_PROTOCOLLNPGM("Num X,Y: " STRINGIFY(GRID_MAX_POINTS_X) "," STRINGIFY(GRID_MAX_POINTS_Y));
|
||||
SERIAL_PROTOCOLPGM("Z offset: "); SERIAL_PROTOCOL_F(mbl.z_offset, 5);
|
||||
SERIAL_PROTOCOLLNPGM("\nMeasured points:");
|
||||
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5,
|
||||
[](const uint8_t ix, const uint8_t iy) { return mbl.z_values[ix][iy]; }
|
||||
);
|
||||
}
|
||||
|
||||
#endif // MESH_BED_LEVELING
|
129
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h
Normal file
129
Marlin/src/feature/bedlevel/mbl/mesh_bed_leveling.h
Normal file
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MESH_BED_LEVELING_H_
|
||||
#define _MESH_BED_LEVELING_H_
|
||||
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
enum MeshLevelingState {
|
||||
MeshReport,
|
||||
MeshStart,
|
||||
MeshNext,
|
||||
MeshSet,
|
||||
MeshSetZOffset,
|
||||
MeshReset
|
||||
};
|
||||
|
||||
enum MBLStatus {
|
||||
MBL_STATUS_NONE = 0,
|
||||
MBL_STATUS_HAS_MESH_BIT = 0,
|
||||
MBL_STATUS_ACTIVE_BIT = 1
|
||||
};
|
||||
|
||||
#define MESH_X_DIST ((MESH_MAX_X - (MESH_MIN_X)) / (GRID_MAX_POINTS_X - 1))
|
||||
#define MESH_Y_DIST ((MESH_MAX_Y - (MESH_MIN_Y)) / (GRID_MAX_POINTS_Y - 1))
|
||||
|
||||
class mesh_bed_leveling {
|
||||
public:
|
||||
static uint8_t status; // Has Mesh and Is Active bits
|
||||
static float z_offset,
|
||||
z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y],
|
||||
index_to_xpos[GRID_MAX_POINTS_X],
|
||||
index_to_ypos[GRID_MAX_POINTS_Y];
|
||||
|
||||
mesh_bed_leveling();
|
||||
|
||||
static void reset();
|
||||
|
||||
static void set_z(const int8_t px, const int8_t py, const float &z) { z_values[px][py] = z; }
|
||||
|
||||
static bool active() { return TEST(status, MBL_STATUS_ACTIVE_BIT); }
|
||||
static void set_active(const bool onOff) { onOff ? SBI(status, MBL_STATUS_ACTIVE_BIT) : CBI(status, MBL_STATUS_ACTIVE_BIT); }
|
||||
static bool has_mesh() { return TEST(status, MBL_STATUS_HAS_MESH_BIT); }
|
||||
static void set_has_mesh(const bool onOff) { onOff ? SBI(status, MBL_STATUS_HAS_MESH_BIT) : CBI(status, MBL_STATUS_HAS_MESH_BIT); }
|
||||
|
||||
static inline void zigzag(const int8_t index, int8_t &px, int8_t &py) {
|
||||
px = index % (GRID_MAX_POINTS_X);
|
||||
py = index / (GRID_MAX_POINTS_X);
|
||||
if (py & 1) px = (GRID_MAX_POINTS_X - 1) - px; // Zig zag
|
||||
}
|
||||
|
||||
static void set_zigzag_z(const int8_t index, const float &z) {
|
||||
int8_t px, py;
|
||||
zigzag(index, px, py);
|
||||
set_z(px, py, z);
|
||||
}
|
||||
|
||||
static int8_t cell_index_x(const float &x) {
|
||||
int8_t cx = (x - (MESH_MIN_X)) * (1.0 / (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)) * (1.0 / (MESH_Y_DIST));
|
||||
return constrain(cy, 0, (GRID_MAX_POINTS_Y) - 2);
|
||||
}
|
||||
|
||||
static int8_t probe_index_x(const float &x) {
|
||||
int8_t px = (x - (MESH_MIN_X) + 0.5 * (MESH_X_DIST)) * (1.0 / (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.5 * (MESH_Y_DIST)) * (1.0 / (MESH_Y_DIST));
|
||||
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
|
||||
}
|
||||
|
||||
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),
|
||||
delta_a = a0 - a1;
|
||||
return z1 + delta_a * delta_z;
|
||||
}
|
||||
|
||||
static float get_z(const float &x0, const float &y0
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
, const float &factor
|
||||
#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);
|
||||
|
||||
return z_offset + z0
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
* factor
|
||||
#endif
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
extern mesh_bed_leveling mbl;
|
||||
|
||||
// Support functions, which may be embedded in the class later
|
||||
|
||||
void mesh_line_to_destination(const float fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF);
|
||||
|
||||
void mbl_mesh_report();
|
||||
|
||||
#endif // _MESH_BED_LEVELING_H_
|
203
Marlin/src/feature/bedlevel/ubl/ubl.cpp
Normal file
203
Marlin/src/feature/bedlevel/ubl/ubl.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
|
||||
#include "ubl.h"
|
||||
unified_bed_leveling ubl;
|
||||
|
||||
#include "../../../module/configuration_store.h"
|
||||
#include "../../../module/planner.h"
|
||||
#include "../../../module/motion.h"
|
||||
#include "../../bedlevel/bedlevel.h"
|
||||
|
||||
#include "math.h"
|
||||
|
||||
/**
|
||||
* These support functions allow the use of large bit arrays of flags that take very
|
||||
* little RAM. Currently they are limited to being 16x16 in size. Changing the declaration
|
||||
* to unsigned long will allow us to go to 32x32 if higher resolution Mesh's are needed
|
||||
* in the future.
|
||||
*/
|
||||
void bit_clear(uint16_t bits[16], const uint8_t x, const uint8_t y) { CBI(bits[y], x); }
|
||||
void bit_set(uint16_t bits[16], const uint8_t x, const uint8_t y) { SBI(bits[y], x); }
|
||||
bool is_bit_set(uint16_t bits[16], const uint8_t x, const uint8_t y) { return TEST(bits[y], x); }
|
||||
|
||||
uint8_t ubl_cnt = 0;
|
||||
|
||||
void unified_bed_leveling::echo_name() { SERIAL_PROTOCOLPGM("Unified Bed Leveling"); }
|
||||
|
||||
void unified_bed_leveling::report_state() {
|
||||
echo_name();
|
||||
SERIAL_PROTOCOLPGM(" System v" UBL_VERSION " ");
|
||||
if (!state.active) SERIAL_PROTOCOLPGM("in");
|
||||
SERIAL_PROTOCOLLNPGM("active.");
|
||||
safe_delay(50);
|
||||
}
|
||||
|
||||
static void serial_echo_xy(const int16_t x, const int16_t y) {
|
||||
SERIAL_CHAR('(');
|
||||
SERIAL_ECHO(x);
|
||||
SERIAL_CHAR(',');
|
||||
SERIAL_ECHO(y);
|
||||
SERIAL_CHAR(')');
|
||||
safe_delay(10);
|
||||
}
|
||||
|
||||
ubl_state unified_bed_leveling::state;
|
||||
|
||||
float unified_bed_leveling::z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y],
|
||||
unified_bed_leveling::last_specified_z;
|
||||
|
||||
// 15 is the maximum nubmer of grid points supported + 1 safety margin for now,
|
||||
// until determinism prevails
|
||||
constexpr float unified_bed_leveling::_mesh_index_to_xpos[16],
|
||||
unified_bed_leveling::_mesh_index_to_ypos[16];
|
||||
|
||||
bool unified_bed_leveling::g26_debug_flag = false,
|
||||
unified_bed_leveling::has_control_of_lcd_panel = false;
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
bool unified_bed_leveling::lcd_map_control = false;
|
||||
#endif
|
||||
|
||||
volatile int unified_bed_leveling::encoder_diff;
|
||||
|
||||
unified_bed_leveling::unified_bed_leveling() {
|
||||
ubl_cnt++; // Debug counter to insure we only have one UBL object present in memory. We can eliminate this (and all references to ubl_cnt) very soon.
|
||||
reset();
|
||||
}
|
||||
|
||||
void unified_bed_leveling::reset() {
|
||||
set_bed_leveling_enabled(false);
|
||||
state.z_offset = 0;
|
||||
state.storage_slot = -1;
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
planner.z_fade_height = 10.0;
|
||||
#endif
|
||||
ZERO(z_values);
|
||||
last_specified_z = -999.9;
|
||||
}
|
||||
|
||||
void unified_bed_leveling::invalidate() {
|
||||
set_bed_leveling_enabled(false);
|
||||
state.z_offset = 0;
|
||||
set_all_mesh_points_to_value(NAN);
|
||||
}
|
||||
|
||||
void unified_bed_leveling::set_all_mesh_points_to_value(float value) {
|
||||
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] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display_map() currently produces three different mesh map types
|
||||
// 0 : suitable for PronterFace and Repetier's serial console
|
||||
// 1 : .CSV file suitable for importation into various spread sheets
|
||||
// 2 : disply of the map data on a RepRap Graphical LCD Panel
|
||||
|
||||
void unified_bed_leveling::display_map(const int map_type) {
|
||||
constexpr uint8_t spaces = 8 * (GRID_MAX_POINTS_X - 2);
|
||||
|
||||
SERIAL_PROTOCOLPGM("\nBed Topography Report");
|
||||
if (map_type == 0) {
|
||||
SERIAL_PROTOCOLPGM(":\n\n");
|
||||
serial_echo_xy(0, GRID_MAX_POINTS_Y - 1);
|
||||
SERIAL_ECHO_SP(spaces + 3);
|
||||
serial_echo_xy(GRID_MAX_POINTS_X - 1, GRID_MAX_POINTS_Y - 1);
|
||||
SERIAL_EOL();
|
||||
serial_echo_xy(UBL_MESH_MIN_X, UBL_MESH_MAX_Y);
|
||||
SERIAL_ECHO_SP(spaces);
|
||||
serial_echo_xy(UBL_MESH_MAX_X, UBL_MESH_MAX_Y);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
else {
|
||||
SERIAL_PROTOCOLPGM(" for ");
|
||||
serialprintPGM(map_type == 1 ? PSTR("CSV:\n\n") : PSTR("LCD:\n\n"));
|
||||
}
|
||||
|
||||
const float current_xi = get_cell_index_x(current_position[X_AXIS] + (MESH_X_DIST) / 2.0),
|
||||
current_yi = get_cell_index_y(current_position[Y_AXIS] + (MESH_Y_DIST) / 2.0);
|
||||
|
||||
for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) {
|
||||
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
|
||||
const bool is_current = i == current_xi && j == current_yi;
|
||||
|
||||
// is the nozzle here? then mark the number
|
||||
if (map_type == 0) SERIAL_CHAR(is_current ? '[' : ' ');
|
||||
|
||||
const float f = z_values[i][j];
|
||||
if (isnan(f)) {
|
||||
serialprintPGM(map_type == 0 ? PSTR(" . ") : PSTR("NAN"));
|
||||
}
|
||||
else if (map_type <= 1) {
|
||||
// if we don't do this, the columns won't line up nicely
|
||||
if (map_type == 0 && f >= 0.0) SERIAL_CHAR(' ');
|
||||
SERIAL_PROTOCOL_F(f, 3);
|
||||
}
|
||||
idle();
|
||||
if (map_type == 1 && i < GRID_MAX_POINTS_X - 1) SERIAL_CHAR(',');
|
||||
|
||||
#if TX_BUFFER_SIZE > 0
|
||||
MYSERIAL.flushTX();
|
||||
#endif
|
||||
safe_delay(15);
|
||||
if (map_type == 0) {
|
||||
SERIAL_CHAR(is_current ? ']' : ' ');
|
||||
SERIAL_CHAR(' ');
|
||||
}
|
||||
}
|
||||
SERIAL_EOL();
|
||||
if (j && map_type == 0) { // we want the (0,0) up tight against the block of numbers
|
||||
SERIAL_CHAR(' ');
|
||||
SERIAL_EOL();
|
||||
}
|
||||
}
|
||||
|
||||
if (map_type == 0) {
|
||||
serial_echo_xy(UBL_MESH_MIN_X, UBL_MESH_MIN_Y);
|
||||
SERIAL_ECHO_SP(spaces + 4);
|
||||
serial_echo_xy(UBL_MESH_MAX_X, UBL_MESH_MIN_Y);
|
||||
SERIAL_EOL();
|
||||
serial_echo_xy(0, 0);
|
||||
SERIAL_ECHO_SP(spaces + 5);
|
||||
serial_echo_xy(GRID_MAX_POINTS_X - 1, 0);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
}
|
||||
|
||||
bool unified_bed_leveling::sanity_check() {
|
||||
uint8_t error_flag = 0;
|
||||
|
||||
if (settings.calc_num_meshes() < 1) {
|
||||
SERIAL_PROTOCOLLNPGM("?Insufficient EEPROM storage for a mesh of this size.");
|
||||
error_flag++;
|
||||
}
|
||||
|
||||
return !!error_flag;
|
||||
}
|
||||
|
||||
#endif // AUTO_BED_LEVELING_UBL
|
421
Marlin/src/feature/bedlevel/ubl/ubl.h
Normal file
421
Marlin/src/feature/bedlevel/ubl/ubl.h
Normal file
@ -0,0 +1,421 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef UNIFIED_BED_LEVELING_H
|
||||
#define UNIFIED_BED_LEVELING_H
|
||||
|
||||
#include "../../../Marlin.h"
|
||||
#include "../../../module/planner.h"
|
||||
#include "../../../module/motion.h"
|
||||
|
||||
#define UBL_VERSION "1.01"
|
||||
#define UBL_OK false
|
||||
#define UBL_ERR true
|
||||
|
||||
#define USE_NOZZLE_AS_REFERENCE 0
|
||||
#define USE_PROBE_AS_REFERENCE 1
|
||||
|
||||
typedef struct {
|
||||
int8_t x_index, y_index;
|
||||
float distance; // When populated, the distance from the search location
|
||||
} mesh_index_pair;
|
||||
|
||||
// ubl.cpp
|
||||
|
||||
void bit_clear(uint16_t bits[16], const uint8_t x, const uint8_t y);
|
||||
void bit_set(uint16_t bits[16], const uint8_t x, const uint8_t y);
|
||||
bool is_bit_set(uint16_t bits[16], const uint8_t x, const uint8_t y);
|
||||
|
||||
// ubl_motion.cpp
|
||||
|
||||
void debug_current_and_destination(const char * const title);
|
||||
|
||||
// ubl_G29.cpp
|
||||
|
||||
enum MeshPointType { INVALID, REAL, SET_IN_BITMAP };
|
||||
|
||||
// External references
|
||||
|
||||
char *ftostr43sign(const float&, char);
|
||||
bool ubl_lcd_clicked();
|
||||
|
||||
extern uint8_t ubl_cnt;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
extern char lcd_status_message[];
|
||||
void lcd_quick_feedback();
|
||||
#endif
|
||||
|
||||
#define MESH_X_DIST (float(UBL_MESH_MAX_X - (UBL_MESH_MIN_X)) / float(GRID_MAX_POINTS_X - 1))
|
||||
#define MESH_Y_DIST (float(UBL_MESH_MAX_Y - (UBL_MESH_MIN_Y)) / float(GRID_MAX_POINTS_Y - 1))
|
||||
|
||||
typedef struct {
|
||||
bool active = false;
|
||||
float z_offset = 0.0;
|
||||
int8_t storage_slot = -1;
|
||||
} ubl_state;
|
||||
|
||||
class unified_bed_leveling {
|
||||
private:
|
||||
|
||||
static float last_specified_z;
|
||||
|
||||
static int g29_verbose_level,
|
||||
g29_phase_value,
|
||||
g29_repetition_cnt,
|
||||
g29_storage_slot,
|
||||
g29_map_type,
|
||||
g29_grid_size;
|
||||
static bool g29_c_flag, g29_x_flag, g29_y_flag;
|
||||
static float g29_x_pos, g29_y_pos,
|
||||
g29_card_thickness,
|
||||
g29_constant;
|
||||
|
||||
#if ENABLED(UBL_G26_MESH_VALIDATION)
|
||||
static float g26_extrusion_multiplier,
|
||||
g26_retraction_multiplier,
|
||||
g26_nozzle,
|
||||
g26_filament_diameter,
|
||||
g26_prime_length,
|
||||
g26_x_pos, g26_y_pos,
|
||||
g26_ooze_amount,
|
||||
g26_layer_height;
|
||||
static int16_t g26_bed_temp,
|
||||
g26_hotend_temp,
|
||||
g26_repeats;
|
||||
static int8_t g26_prime_flag;
|
||||
static bool g26_continue_with_closest, g26_keep_heaters_on;
|
||||
#endif
|
||||
|
||||
static float measure_point_with_encoder();
|
||||
static float measure_business_card_thickness(float);
|
||||
static bool g29_parameter_parsing();
|
||||
static void find_mean_mesh_height();
|
||||
static void shift_mesh_height();
|
||||
static void probe_entire_mesh(const float &lx, const float &ly, const bool do_ubl_mesh_map, const bool stow_probe, bool do_furthest);
|
||||
static void manually_probe_remaining_mesh(const float&, const float&, const float&, const float&, const bool);
|
||||
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 void g29_what_command();
|
||||
static void g29_eeprom_dump();
|
||||
static void g29_compare_current_mesh_to_stored_mesh();
|
||||
static void fine_tune_mesh(const float &lx, const float &ly, 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 void smart_fill_mesh();
|
||||
|
||||
#if ENABLED(UBL_G26_MESH_VALIDATION)
|
||||
static bool exit_from_g26();
|
||||
static bool parse_G26_parameters();
|
||||
static void G26_line_to_destination(const float &feed_rate);
|
||||
static mesh_index_pair find_closest_circle_to_print(const float&, const float&);
|
||||
static bool look_for_lines_to_connect();
|
||||
static bool turn_on_heaters();
|
||||
static bool prime_nozzle();
|
||||
static void retract_filament(const float where[XYZE]);
|
||||
static void recover_filament(const float where[XYZE]);
|
||||
static void print_line_from_here_to_there(const float&, const float&, const float&, const float&, const float&, const float&);
|
||||
static void move_to(const float&, const float&, const float&, const float&);
|
||||
inline static void move_to(const float where[XYZE], const float &de) { move_to(where[X_AXIS], where[Y_AXIS], where[Z_AXIS], de); }
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
static void echo_name();
|
||||
static void report_state();
|
||||
static void save_ubl_active_state_and_disable();
|
||||
static void restore_ubl_active_state_and_leave();
|
||||
static void display_map(const int);
|
||||
static mesh_index_pair find_closest_mesh_point_of_type(const MeshPointType, const float&, const float&, const bool, uint16_t[16], bool);
|
||||
static void reset();
|
||||
static void invalidate();
|
||||
static void set_all_mesh_points_to_value(float);
|
||||
static bool sanity_check();
|
||||
|
||||
static void G29() _O0; // O0 for no optimization
|
||||
static void smart_fill_wlsf(const float &) _O2; // O2 gives smaller code than Os on A2560
|
||||
|
||||
#if ENABLED(UBL_G26_MESH_VALIDATION)
|
||||
static void G26();
|
||||
#endif
|
||||
|
||||
static ubl_state state;
|
||||
|
||||
static float z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
|
||||
|
||||
// 15 is the maximum nubmer of grid points supported + 1 safety margin for now,
|
||||
// until determinism prevails
|
||||
constexpr static float _mesh_index_to_xpos[16] PROGMEM = {
|
||||
UBL_MESH_MIN_X + 0 * (MESH_X_DIST), UBL_MESH_MIN_X + 1 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 2 * (MESH_X_DIST), UBL_MESH_MIN_X + 3 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 4 * (MESH_X_DIST), UBL_MESH_MIN_X + 5 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 6 * (MESH_X_DIST), UBL_MESH_MIN_X + 7 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 8 * (MESH_X_DIST), UBL_MESH_MIN_X + 9 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 10 * (MESH_X_DIST), UBL_MESH_MIN_X + 11 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 12 * (MESH_X_DIST), UBL_MESH_MIN_X + 13 * (MESH_X_DIST),
|
||||
UBL_MESH_MIN_X + 14 * (MESH_X_DIST), UBL_MESH_MIN_X + 15 * (MESH_X_DIST)
|
||||
};
|
||||
|
||||
constexpr static float _mesh_index_to_ypos[16] PROGMEM = {
|
||||
UBL_MESH_MIN_Y + 0 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 1 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 2 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 3 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 4 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 5 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 6 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 7 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 8 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 9 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 10 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 11 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 12 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 13 * (MESH_Y_DIST),
|
||||
UBL_MESH_MIN_Y + 14 * (MESH_Y_DIST), UBL_MESH_MIN_Y + 15 * (MESH_Y_DIST)
|
||||
};
|
||||
|
||||
static bool g26_debug_flag, has_control_of_lcd_panel;
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
static bool lcd_map_control;
|
||||
#endif
|
||||
|
||||
static volatile int encoder_diff; // Volatile because it's changed at interrupt time.
|
||||
|
||||
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) {
|
||||
const int8_t cx = (x - (UBL_MESH_MIN_X)) * (1.0 / (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) {
|
||||
const int8_t cy = (y - (UBL_MESH_MIN_Y)) * (1.0 / (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
|
||||
// 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 find_closest_x_index(const float &x) {
|
||||
const int8_t px = (x - (UBL_MESH_MIN_X) + (MESH_X_DIST) * 0.5) * (1.0 / (MESH_X_DIST));
|
||||
return WITHIN(px, 0, GRID_MAX_POINTS_X - 1) ? px : -1;
|
||||
}
|
||||
|
||||
static int8_t find_closest_y_index(const float &y) {
|
||||
const int8_t py = (y - (UBL_MESH_MIN_Y) + (MESH_Y_DIST) * 0.5) * (1.0 / (MESH_Y_DIST));
|
||||
return WITHIN(py, 0, GRID_MAX_POINTS_Y - 1) ? py : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* z2 --|
|
||||
* z0 | |
|
||||
* | | + (z2-z1)
|
||||
* z1 | | |
|
||||
* ---+-------------+--------+-- --|
|
||||
* a1 a0 a2
|
||||
* |<---delta_a---------->|
|
||||
*
|
||||
* calc_z0 is the basis for all the Mesh Based correction. It is used to
|
||||
* find the expected Z Height at a position between two known Z-Height locations.
|
||||
*
|
||||
* It is fairly expensive with its 4 floating point additions and 2 floating point
|
||||
* multiplications.
|
||||
*/
|
||||
FORCE_INLINE static float calc_z0(const float &a0, const float &a1, const float &z1, const float &a2, const float &z2) {
|
||||
return z1 + (z2 - z1) * (a0 - a1) / (a2 - a1);
|
||||
}
|
||||
|
||||
/**
|
||||
* z_correction_for_x_on_horizontal_mesh_line is an optimization for
|
||||
* the case where the printer is making a vertical line that only crosses horizontal mesh lines.
|
||||
*/
|
||||
inline static float z_correction_for_x_on_horizontal_mesh_line(const float &lx0, const int x1_i, const int yi) {
|
||||
if (!WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 2) || !WITHIN(yi, 0, GRID_MAX_POINTS_Y - 1)) {
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(LEVELING)) {
|
||||
serialprintPGM( !WITHIN(x1_i, 0, GRID_MAX_POINTS_X - 1) ? PSTR("x1l_i") : PSTR("yi") );
|
||||
SERIAL_ECHOPAIR(" out of bounds in z_correction_for_x_on_horizontal_mesh_line(lx0=", lx0);
|
||||
SERIAL_ECHOPAIR(",x1_i=", x1_i);
|
||||
SERIAL_ECHOPAIR(",yi=", yi);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
}
|
||||
#endif
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const float xratio = (RAW_X_POSITION(lx0) - mesh_index_to_xpos(x1_i)) * (1.0 / (MESH_X_DIST)),
|
||||
z1 = z_values[x1_i][yi];
|
||||
|
||||
return z1 + xratio * (z_values[x1_i + 1][yi] - z1);
|
||||
}
|
||||
|
||||
//
|
||||
// See comments above for z_correction_for_x_on_horizontal_mesh_line
|
||||
//
|
||||
inline static float z_correction_for_y_on_vertical_mesh_line(const float &ly0, const int xi, const int y1_i) {
|
||||
if (!WITHIN(xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(y1_i, 0, GRID_MAX_POINTS_Y - 2)) {
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(LEVELING)) {
|
||||
serialprintPGM( !WITHIN(xi, 0, GRID_MAX_POINTS_X - 1) ? PSTR("xi") : PSTR("yl_i") );
|
||||
SERIAL_ECHOPAIR(" out of bounds in z_correction_for_y_on_vertical_mesh_line(ly0=", ly0);
|
||||
SERIAL_ECHOPAIR(", xi=", xi);
|
||||
SERIAL_ECHOPAIR(", y1_i=", y1_i);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
}
|
||||
#endif
|
||||
return NAN;
|
||||
}
|
||||
|
||||
const float yratio = (RAW_Y_POSITION(ly0) - mesh_index_to_ypos(y1_i)) * (1.0 / (MESH_Y_DIST)),
|
||||
z1 = z_values[xi][y1_i];
|
||||
|
||||
return z1 + yratio * (z_values[xi][y1_i + 1] - z1);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the generic Z-Correction. It works anywhere within a Mesh Cell. It first
|
||||
* does a linear interpolation along both of the bounding X-Mesh-Lines to find the
|
||||
* Z-Height at both ends. Then it does a linear interpolation of these heights based
|
||||
* on the Y position within the cell.
|
||||
*/
|
||||
static float get_z_correction(const float &lx0, const float &ly0) {
|
||||
const int8_t cx = get_cell_index_x(RAW_X_POSITION(lx0)),
|
||||
cy = get_cell_index_y(RAW_Y_POSITION(ly0));
|
||||
|
||||
if (!WITHIN(cx, 0, GRID_MAX_POINTS_X - 2) || !WITHIN(cy, 0, GRID_MAX_POINTS_Y - 2)) {
|
||||
|
||||
SERIAL_ECHOPAIR("? in get_z_correction(lx0=", lx0);
|
||||
SERIAL_ECHOPAIR(", ly0=", ly0);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
strcpy(lcd_status_message, "get_z_correction() indexes out of range.");
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
return NAN; // this used to return state.z_offset
|
||||
}
|
||||
|
||||
const float z1 = calc_z0(RAW_X_POSITION(lx0),
|
||||
mesh_index_to_xpos(cx), z_values[cx][cy],
|
||||
mesh_index_to_xpos(cx + 1), z_values[cx + 1][cy]);
|
||||
|
||||
const float z2 = calc_z0(RAW_X_POSITION(lx0),
|
||||
mesh_index_to_xpos(cx), z_values[cx][cy + 1],
|
||||
mesh_index_to_xpos(cx + 1), z_values[cx + 1][cy + 1]);
|
||||
|
||||
float z0 = calc_z0(RAW_Y_POSITION(ly0),
|
||||
mesh_index_to_ypos(cy), z1,
|
||||
mesh_index_to_ypos(cy + 1), z2);
|
||||
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(MESH_ADJUST)) {
|
||||
SERIAL_ECHOPAIR(" raw get_z_correction(", lx0);
|
||||
SERIAL_CHAR(',');
|
||||
SERIAL_ECHO(ly0);
|
||||
SERIAL_ECHOPGM(") = ");
|
||||
SERIAL_ECHO_F(z0, 6);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(MESH_ADJUST)) {
|
||||
SERIAL_ECHOPGM(" >>>---> ");
|
||||
SERIAL_ECHO_F(z0, 6);
|
||||
SERIAL_EOL();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isnan(z0)) { // if part of the Mesh is undefined, it will show up as NAN
|
||||
z0 = 0.0; // in ubl.z_values[][] and propagate through the
|
||||
// calculations. If our correction is NAN, we throw it out
|
||||
// because part of the Mesh is undefined and we don't have the
|
||||
// information we need to complete the height correction.
|
||||
|
||||
#if ENABLED(DEBUG_LEVELING_FEATURE)
|
||||
if (DEBUGGING(MESH_ADJUST)) {
|
||||
SERIAL_ECHOPAIR("??? Yikes! NAN in get_z_correction(", lx0);
|
||||
SERIAL_CHAR(',');
|
||||
SERIAL_ECHO(ly0);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return z0; // there used to be a +state.z_offset on this line
|
||||
}
|
||||
|
||||
/**
|
||||
* This function sets the Z leveling fade factor based on the given Z height,
|
||||
* only re-calculating when necessary.
|
||||
*
|
||||
* Returns 1.0 if planner.z_fade_height is 0.0.
|
||||
* Returns 0.0 if Z is past the specified 'Fade Height'.
|
||||
*/
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
static inline float fade_scaling_factor_for_z(const float &lz) {
|
||||
if (planner.z_fade_height == 0.0) return 1.0;
|
||||
static float fade_scaling_factor = 1.0;
|
||||
const float rz = RAW_Z_POSITION(lz);
|
||||
if (last_specified_z != rz) {
|
||||
last_specified_z = rz;
|
||||
fade_scaling_factor =
|
||||
rz < planner.z_fade_height
|
||||
? 1.0 - (rz * planner.inverse_z_fade_height)
|
||||
: 0.0;
|
||||
}
|
||||
return fade_scaling_factor;
|
||||
}
|
||||
#else
|
||||
FORCE_INLINE static float fade_scaling_factor_for_z(const float &lz) { return 1.0; }
|
||||
#endif
|
||||
|
||||
FORCE_INLINE static float mesh_index_to_xpos(const uint8_t i) {
|
||||
return i < GRID_MAX_POINTS_X ? pgm_read_float(&_mesh_index_to_xpos[i]) : UBL_MESH_MIN_X + i * (MESH_X_DIST);
|
||||
}
|
||||
|
||||
FORCE_INLINE static float mesh_index_to_ypos(const uint8_t i) {
|
||||
return i < GRID_MAX_POINTS_Y ? pgm_read_float(&_mesh_index_to_ypos[i]) : UBL_MESH_MIN_Y + i * (MESH_Y_DIST);
|
||||
}
|
||||
|
||||
static bool prepare_segmented_line_to(const float ltarget[XYZE], const float &feedrate);
|
||||
static void line_to_destination_cartesian(const float &fr, uint8_t e);
|
||||
|
||||
#define _CMPZ(a,b) (z_values[a][b] == z_values[a][b+1])
|
||||
#define CMPZ(a) (_CMPZ(a, 0) && _CMPZ(a, 1))
|
||||
#define ZZER(a) (z_values[a][0] == 0)
|
||||
|
||||
FORCE_INLINE bool mesh_is_valid() {
|
||||
return !(
|
||||
( CMPZ(0) && CMPZ(1) && CMPZ(2) // adjacent z values all equal?
|
||||
&& ZZER(0) && ZZER(1) && ZZER(2) // all zero at the edge?
|
||||
)
|
||||
|| isnan(z_values[0][0])
|
||||
);
|
||||
}
|
||||
|
||||
}; // class unified_bed_leveling
|
||||
|
||||
extern unified_bed_leveling ubl;
|
||||
|
||||
#endif // UNIFIED_BED_LEVELING_H
|
1826
Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
Normal file
1826
Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
Normal file
File diff suppressed because it is too large
Load Diff
726
Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
Normal file
726
Marlin/src/feature/bedlevel/ubl/ubl_motion.cpp
Normal file
@ -0,0 +1,726 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include "../../../inc/MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
|
||||
#include "ubl.h"
|
||||
|
||||
#include "../../../Marlin.h"
|
||||
#include "../../../module/planner.h"
|
||||
#include "../../../module/stepper.h"
|
||||
#include "../../../module/motion.h"
|
||||
|
||||
#if ENABLED(DELTA)
|
||||
#include "../../../module/delta.h"
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
|
||||
extern float destination[XYZE];
|
||||
|
||||
#if AVR_AT90USB1286_FAMILY // Teensyduino & Printrboard IDE extensions have compile errors without this
|
||||
inline void set_current_to_destination() { COPY(current_position, destination); }
|
||||
#else
|
||||
extern void set_current_to_destination();
|
||||
#endif
|
||||
|
||||
static void debug_echo_axis(const AxisEnum axis) {
|
||||
if (current_position[axis] == destination[axis])
|
||||
SERIAL_ECHOPGM("-------------");
|
||||
else
|
||||
SERIAL_ECHO_F(destination[X_AXIS], 6);
|
||||
}
|
||||
|
||||
void debug_current_and_destination(const char *title) {
|
||||
|
||||
// if the title message starts with a '!' it is so important, we are going to
|
||||
// ignore the status of the g26_debug_flag
|
||||
if (*title != '!' && !ubl.g26_debug_flag) return;
|
||||
|
||||
const float de = destination[E_AXIS] - current_position[E_AXIS];
|
||||
|
||||
if (de == 0.0) return; // Printing moves only
|
||||
|
||||
const float dx = destination[X_AXIS] - current_position[X_AXIS],
|
||||
dy = destination[Y_AXIS] - current_position[Y_AXIS],
|
||||
xy_dist = HYPOT(dx, dy);
|
||||
|
||||
if (xy_dist == 0.0) return;
|
||||
|
||||
SERIAL_ECHOPGM(" fpmm=");
|
||||
const float fpmm = de / xy_dist;
|
||||
SERIAL_ECHO_F(fpmm, 6);
|
||||
|
||||
SERIAL_ECHOPGM(" current=( ");
|
||||
SERIAL_ECHO_F(current_position[X_AXIS], 6);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
SERIAL_ECHO_F(current_position[Y_AXIS], 6);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
SERIAL_ECHO_F(current_position[Z_AXIS], 6);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
SERIAL_ECHO_F(current_position[E_AXIS], 6);
|
||||
SERIAL_ECHOPGM(" ) destination=( ");
|
||||
debug_echo_axis(X_AXIS);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
debug_echo_axis(Y_AXIS);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
debug_echo_axis(Z_AXIS);
|
||||
SERIAL_ECHOPGM(", ");
|
||||
debug_echo_axis(E_AXIS);
|
||||
SERIAL_ECHOPGM(" ) ");
|
||||
SERIAL_ECHO(title);
|
||||
SERIAL_EOL();
|
||||
|
||||
}
|
||||
|
||||
void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, uint8_t extruder) {
|
||||
/**
|
||||
* Much of the nozzle movement will be within the same cell. So we will do as little computation
|
||||
* as possible to determine if this is the case. If this move is within the same cell, we will
|
||||
* just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave
|
||||
*/
|
||||
const 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]
|
||||
};
|
||||
|
||||
const int cell_start_xi = get_cell_index_x(RAW_X_POSITION(start[X_AXIS])),
|
||||
cell_start_yi = get_cell_index_y(RAW_Y_POSITION(start[Y_AXIS])),
|
||||
cell_dest_xi = get_cell_index_x(RAW_X_POSITION(end[X_AXIS])),
|
||||
cell_dest_yi = get_cell_index_y(RAW_Y_POSITION(end[Y_AXIS]));
|
||||
|
||||
if (g26_debug_flag) {
|
||||
SERIAL_ECHOPAIR(" ubl.line_to_destination(xe=", end[X_AXIS]);
|
||||
SERIAL_ECHOPAIR(", ye=", end[Y_AXIS]);
|
||||
SERIAL_ECHOPAIR(", ze=", end[Z_AXIS]);
|
||||
SERIAL_ECHOPAIR(", ee=", end[E_AXIS]);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
debug_current_and_destination(PSTR("Start of ubl.line_to_destination()"));
|
||||
}
|
||||
|
||||
if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) { // if the whole move is within the same cell,
|
||||
/**
|
||||
* we don't need to break up the move
|
||||
*
|
||||
* If we are moving off the print bed, we are going to allow the move at this level.
|
||||
* But we detect it and isolate it. For now, we just pass along the request.
|
||||
*/
|
||||
|
||||
if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 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.
|
||||
|
||||
planner._buffer_line(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + state.z_offset, end[E_AXIS], feed_rate, extruder);
|
||||
set_current_to_destination();
|
||||
|
||||
if (g26_debug_flag)
|
||||
debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination()"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
FINAL_MOVE:
|
||||
|
||||
/**
|
||||
* Optimize some floating point operations here. We could call float get_z_correction(float x0, float y0) to
|
||||
* generate the correction for us. But we can lighten the load on the CPU by doing a modified version of the function.
|
||||
* We are going to only calculate the amount we are from the first mesh line towards the second mesh line once.
|
||||
* We will use this fraction in both of the original two Z Height calculations for the bi-linear interpolation. And,
|
||||
* instead of doing a generic divide of the distance, we know the distance is MESH_X_DIST so we can use the preprocessor
|
||||
* to create a 1-over number for us. That will allow us to do a floating point multiply instead of a floating point divide.
|
||||
*/
|
||||
|
||||
const float xratio = (RAW_X_POSITION(end[X_AXIS]) - mesh_index_to_xpos(cell_dest_xi)) * (1.0 / (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;
|
||||
|
||||
// we are done with the fractional X distance into the cell. Now with the two Z-Heights we have calculated, we
|
||||
// are going to apply the Y-Distance into the cell to interpolate the final Z correction.
|
||||
|
||||
const float yratio = (RAW_Y_POSITION(end[Y_AXIS]) - mesh_index_to_ypos(cell_dest_yi)) * (1.0 / (MESH_Y_DIST));
|
||||
float z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0;
|
||||
|
||||
/**
|
||||
* If part of the Mesh is undefined, it will show up as NAN
|
||||
* in z_values[][] and propagate through the
|
||||
* calculations. If our correction is NAN, we throw it out
|
||||
* because part of the Mesh is undefined and we don't have the
|
||||
* information we need to complete the height correction.
|
||||
*/
|
||||
if (isnan(z0)) z0 = 0.0;
|
||||
|
||||
planner._buffer_line(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z0 + state.z_offset, end[E_AXIS], feed_rate, extruder);
|
||||
|
||||
if (g26_debug_flag)
|
||||
debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination()"));
|
||||
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we get here, we are processing a move that crosses at least one Mesh Line. We will check
|
||||
* for the simple case of just crossing X or just crossing Y Mesh Lines after we get all the details
|
||||
* of the move figured out. We can process the easy case of just crossing an X or Y Mesh Line with less
|
||||
* computation and in fact most lines are of this nature. We will check for that in the following
|
||||
* blocks of code:
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Compute the scaling factor for the extruder for each partial move.
|
||||
* We need to watch out for zero length moves because it will cause us to
|
||||
* have an infinate scaling factor. We are stuck doing a floating point
|
||||
* divide to get our scaling factor, but after that, we just multiply by this
|
||||
* number. We also pick our scaling factor based on whether the X or Y
|
||||
* component is larger. We use the biggest of the two to preserve precision.
|
||||
*/
|
||||
|
||||
const bool use_x_dist = adx > ady;
|
||||
|
||||
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];
|
||||
|
||||
const float e_normalized_dist = e_position / on_axis_distance,
|
||||
z_normalized_dist = z_position / on_axis_distance;
|
||||
|
||||
int current_xi = cell_start_xi,
|
||||
current_yi = cell_start_yi;
|
||||
|
||||
const float m = dy / dx,
|
||||
c = start[Y_AXIS] - m * start[X_AXIS];
|
||||
|
||||
const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0),
|
||||
inf_m_flag = (isinf(m) != 0);
|
||||
/**
|
||||
* This block handles vertical lines. These are lines that stay within the same
|
||||
* X Cell column. They do not need to be perfectly vertical. They just can
|
||||
* not cross into another X Cell column.
|
||||
*/
|
||||
if (dxi == 0) { // Check for a vertical line
|
||||
current_yi += down_flag; // Line is heading down, we just want to go to the bottom
|
||||
while (current_yi != cell_dest_yi + down_flag) {
|
||||
current_yi += dyi;
|
||||
const float next_mesh_line_y = LOGICAL_Y_POSITION(mesh_index_to_ypos(current_yi));
|
||||
|
||||
/**
|
||||
* if the slope of the line is infinite, we won't do the calculations
|
||||
* else, we know the next X is the same so we can recover and continue!
|
||||
* Calculate X at the next Y mesh line
|
||||
*/
|
||||
const float x = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m;
|
||||
|
||||
float z0 = z_correction_for_x_on_horizontal_mesh_line(x, current_xi, current_yi);
|
||||
|
||||
z0 *= fade_scaling_factor_for_z(end[Z_AXIS]);
|
||||
|
||||
/**
|
||||
* If part of the Mesh is undefined, it will show up as NAN
|
||||
* in z_values[][] and propagate through the
|
||||
* calculations. If our correction is NAN, we throw it out
|
||||
* because part of the Mesh is undefined and we don't have the
|
||||
* information we need to complete the height correction.
|
||||
*/
|
||||
if (isnan(z0)) z0 = 0.0;
|
||||
|
||||
const float y = LOGICAL_Y_POSITION(mesh_index_to_ypos(current_yi));
|
||||
|
||||
/**
|
||||
* Without this check, it is possible for the algorithm to generate a zero length move in the case
|
||||
* where the line is heading down and it is starting right on a Mesh Line boundary. For how often that
|
||||
* happens, it might be best to remove the check and always 'schedule' the move because
|
||||
* the planner._buffer_line() routine will filter it if that happens.
|
||||
*/
|
||||
if (y != start[Y_AXIS]) {
|
||||
if (!inf_normalized_flag) {
|
||||
on_axis_distance = use_x_dist ? x - start[X_AXIS] : 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;
|
||||
}
|
||||
else {
|
||||
e_position = end[E_AXIS];
|
||||
z_position = end[Z_AXIS];
|
||||
}
|
||||
|
||||
planner._buffer_line(x, y, z_position + z0 + state.z_offset, e_position, feed_rate, extruder);
|
||||
} //else printf("FIRST MOVE PRUNED ");
|
||||
}
|
||||
|
||||
if (g26_debug_flag)
|
||||
debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination()"));
|
||||
|
||||
//
|
||||
// Check if we are at the final destination. Usually, we won't be, but if it is on a Y Mesh Line, we are done.
|
||||
//
|
||||
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
|
||||
goto FINAL_MOVE;
|
||||
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This block handles horizontal lines. These are lines that stay within the same
|
||||
* Y Cell row. They do not need to be perfectly horizontal. They just can
|
||||
* not cross into another Y Cell row.
|
||||
*
|
||||
*/
|
||||
|
||||
if (dyi == 0) { // Check for a horizontal line
|
||||
current_xi += left_flag; // Line is heading left, we just want to go to the left
|
||||
// edge of this cell for the first move.
|
||||
while (current_xi != cell_dest_xi + left_flag) {
|
||||
current_xi += dxi;
|
||||
const float next_mesh_line_x = LOGICAL_X_POSITION(mesh_index_to_xpos(current_xi)),
|
||||
y = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line
|
||||
|
||||
float z0 = z_correction_for_y_on_vertical_mesh_line(y, current_xi, current_yi);
|
||||
|
||||
z0 *= fade_scaling_factor_for_z(end[Z_AXIS]);
|
||||
|
||||
/**
|
||||
* If part of the Mesh is undefined, it will show up as NAN
|
||||
* in z_values[][] and propagate through the
|
||||
* calculations. If our correction is NAN, we throw it out
|
||||
* because part of the Mesh is undefined and we don't have the
|
||||
* information we need to complete the height correction.
|
||||
*/
|
||||
if (isnan(z0)) z0 = 0.0;
|
||||
|
||||
const float x = LOGICAL_X_POSITION(mesh_index_to_xpos(current_xi));
|
||||
|
||||
/**
|
||||
* Without this check, it is possible for the algorithm to generate a zero length move in the case
|
||||
* where the line is heading left and it is starting right on a Mesh Line boundary. For how often
|
||||
* that happens, it might be best to remove the check and always 'schedule' the move because
|
||||
* the planner._buffer_line() routine will filter it if that happens.
|
||||
*/
|
||||
if (x != start[X_AXIS]) {
|
||||
if (!inf_normalized_flag) {
|
||||
on_axis_distance = use_x_dist ? x - start[X_AXIS] : y - 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;
|
||||
}
|
||||
else {
|
||||
e_position = end[E_AXIS];
|
||||
z_position = end[Z_AXIS];
|
||||
}
|
||||
|
||||
planner._buffer_line(x, y, z_position + z0 + state.z_offset, e_position, feed_rate, extruder);
|
||||
} //else printf("FIRST MOVE PRUNED ");
|
||||
}
|
||||
|
||||
if (g26_debug_flag)
|
||||
debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination()"));
|
||||
|
||||
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
|
||||
goto FINAL_MOVE;
|
||||
|
||||
set_current_to_destination();
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This block handles the 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;
|
||||
|
||||
if (xi_cnt < 0) xi_cnt = -xi_cnt;
|
||||
if (yi_cnt < 0) yi_cnt = -yi_cnt;
|
||||
|
||||
current_xi += left_flag;
|
||||
current_yi += down_flag;
|
||||
|
||||
while (xi_cnt > 0 || yi_cnt > 0) {
|
||||
|
||||
const float next_mesh_line_x = LOGICAL_X_POSITION(mesh_index_to_xpos(current_xi + dxi)),
|
||||
next_mesh_line_y = LOGICAL_Y_POSITION(mesh_index_to_ypos(current_yi + dyi)),
|
||||
y = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line
|
||||
x = (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 == (x > 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(x, current_xi - left_flag, current_yi + dyi);
|
||||
|
||||
z0 *= fade_scaling_factor_for_z(end[Z_AXIS]);
|
||||
|
||||
/**
|
||||
* If part of the Mesh is undefined, it will show up as NAN
|
||||
* in z_values[][] and propagate through the
|
||||
* calculations. If our correction is NAN, we throw it out
|
||||
* because part of the Mesh is undefined and we don't have the
|
||||
* information we need to complete the height correction.
|
||||
*/
|
||||
if (isnan(z0)) z0 = 0.0;
|
||||
|
||||
if (!inf_normalized_flag) {
|
||||
on_axis_distance = use_x_dist ? x - 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;
|
||||
}
|
||||
else {
|
||||
e_position = end[E_AXIS];
|
||||
z_position = end[Z_AXIS];
|
||||
}
|
||||
planner._buffer_line(x, next_mesh_line_y, z_position + z0 + state.z_offset, e_position, feed_rate, extruder);
|
||||
current_yi += dyi;
|
||||
yi_cnt--;
|
||||
}
|
||||
else {
|
||||
// Yes! Crossing a X Mesh Line next
|
||||
float z0 = z_correction_for_y_on_vertical_mesh_line(y, current_xi + dxi, current_yi - down_flag);
|
||||
|
||||
z0 *= fade_scaling_factor_for_z(end[Z_AXIS]);
|
||||
|
||||
/**
|
||||
* If part of the Mesh is undefined, it will show up as NAN
|
||||
* in z_values[][] and propagate through the
|
||||
* calculations. If our correction is NAN, we throw it out
|
||||
* because part of the Mesh is undefined and we don't have the
|
||||
* information we need to complete the height correction.
|
||||
*/
|
||||
if (isnan(z0)) z0 = 0.0;
|
||||
|
||||
if (!inf_normalized_flag) {
|
||||
on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : 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;
|
||||
}
|
||||
else {
|
||||
e_position = end[E_AXIS];
|
||||
z_position = end[Z_AXIS];
|
||||
}
|
||||
|
||||
planner._buffer_line(next_mesh_line_x, y, z_position + z0 + state.z_offset, e_position, feed_rate, extruder);
|
||||
current_xi += dxi;
|
||||
xi_cnt--;
|
||||
}
|
||||
|
||||
if (xi_cnt < 0 || yi_cnt < 0) break; // we've gone too far, so exit the loop and move on to FINAL_MOVE
|
||||
}
|
||||
|
||||
if (g26_debug_flag)
|
||||
debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination()"));
|
||||
|
||||
if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS])
|
||||
goto FINAL_MOVE;
|
||||
|
||||
set_current_to_destination();
|
||||
}
|
||||
|
||||
#if UBL_DELTA
|
||||
|
||||
// macro to inline copy exactly 4 floats, don't rely on sizeof operator
|
||||
#define COPY_XYZE( target, source ) { \
|
||||
target[X_AXIS] = source[X_AXIS]; \
|
||||
target[Y_AXIS] = source[Y_AXIS]; \
|
||||
target[Z_AXIS] = source[Z_AXIS]; \
|
||||
target[E_AXIS] = source[E_AXIS]; \
|
||||
}
|
||||
|
||||
#if IS_SCARA // scale the feed rate from mm/s to degrees/s
|
||||
static float scara_feed_factor, scara_oldA, scara_oldB;
|
||||
#endif
|
||||
|
||||
// We don't want additional apply_leveling() performed by regular buffer_line or buffer_line_kinematic,
|
||||
// so we call _buffer_line directly here. Per-segmented leveling and kinematics performed first.
|
||||
|
||||
inline void _O2 ubl_buffer_segment_raw( float rx, float ry, float rz, float le, float fr ) {
|
||||
|
||||
#if ENABLED(DELTA) // apply delta inverse_kinematics
|
||||
|
||||
const float delta_A = rz + SQRT( delta_diagonal_rod_2_tower[A_AXIS]
|
||||
- HYPOT2( delta_tower[A_AXIS][X_AXIS] - rx,
|
||||
delta_tower[A_AXIS][Y_AXIS] - ry ));
|
||||
|
||||
const float delta_B = rz + SQRT( delta_diagonal_rod_2_tower[B_AXIS]
|
||||
- HYPOT2( delta_tower[B_AXIS][X_AXIS] - rx,
|
||||
delta_tower[B_AXIS][Y_AXIS] - ry ));
|
||||
|
||||
const float delta_C = rz + SQRT( delta_diagonal_rod_2_tower[C_AXIS]
|
||||
- HYPOT2( delta_tower[C_AXIS][X_AXIS] - rx,
|
||||
delta_tower[C_AXIS][Y_AXIS] - ry ));
|
||||
|
||||
planner._buffer_line(delta_A, delta_B, delta_C, le, fr, active_extruder);
|
||||
|
||||
#elif IS_SCARA // apply scara inverse_kinematics (should be changed to save raw->logical->raw)
|
||||
|
||||
const float lseg[XYZ] = { LOGICAL_X_POSITION(rx),
|
||||
LOGICAL_Y_POSITION(ry),
|
||||
LOGICAL_Z_POSITION(rz)
|
||||
};
|
||||
|
||||
inverse_kinematics(lseg); // this writes delta[ABC] from lseg[XYZ]
|
||||
// should move the feedrate scaling to scara inverse_kinematics
|
||||
|
||||
const float adiff = FABS(delta[A_AXIS] - scara_oldA),
|
||||
bdiff = FABS(delta[B_AXIS] - scara_oldB);
|
||||
scara_oldA = delta[A_AXIS];
|
||||
scara_oldB = delta[B_AXIS];
|
||||
float s_feedrate = max(adiff, bdiff) * scara_feed_factor;
|
||||
|
||||
planner._buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], le, s_feedrate, active_extruder);
|
||||
|
||||
#else // CARTESIAN
|
||||
|
||||
// Cartesian _buffer_line seems to take LOGICAL, not RAW coordinates
|
||||
|
||||
const float lx = LOGICAL_X_POSITION(rx),
|
||||
ly = LOGICAL_Y_POSITION(ry),
|
||||
lz = LOGICAL_Z_POSITION(rz);
|
||||
|
||||
planner._buffer_line(lx, ly, lz, le, fr, active_extruder);
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare a segmented linear move for DELTA/SCARA/CARTESIAN with UBL and FADE semantics.
|
||||
* This calls planner._buffer_line multiple times for small incremental moves.
|
||||
* Returns true if did NOT move, false if moved (requires current_position update).
|
||||
*/
|
||||
|
||||
bool _O2 unified_bed_leveling::prepare_segmented_line_to(const float ltarget[XYZE], const float &feedrate) {
|
||||
|
||||
if (!position_is_reachable_xy(ltarget[X_AXIS], ltarget[Y_AXIS])) // fail if moving outside reachable boundary
|
||||
return true; // did not move, so current_position still accurate
|
||||
|
||||
const float tot_dx = ltarget[X_AXIS] - current_position[X_AXIS],
|
||||
tot_dy = ltarget[Y_AXIS] - current_position[Y_AXIS],
|
||||
tot_dz = ltarget[Z_AXIS] - current_position[Z_AXIS],
|
||||
tot_de = ltarget[E_AXIS] - current_position[E_AXIS];
|
||||
|
||||
const float cartesian_xy_mm = HYPOT(tot_dx, tot_dy); // total horizontal xy distance
|
||||
|
||||
#if IS_KINEMATIC
|
||||
const float seconds = cartesian_xy_mm / feedrate; // seconds to move xy distance at requested rate
|
||||
uint16_t segments = lroundf(delta_segments_per_second * seconds), // preferred number of segments for distance @ feedrate
|
||||
seglimit = lroundf(cartesian_xy_mm * (1.0 / (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 = lroundf(cartesian_xy_mm * (1.0 / (DELTA_SEGMENT_MIN_LENGTH))); // cartesian fixed segment length
|
||||
#endif
|
||||
|
||||
NOLESS(segments, 1); // must have at least one segment
|
||||
const float inv_segments = 1.0 / segments; // divide once, multiply thereafter
|
||||
|
||||
#if IS_SCARA // scale the feed rate from mm/s to degrees/s
|
||||
scara_feed_factor = cartesian_xy_mm * inv_segments * feedrate;
|
||||
scara_oldA = stepper.get_axis_position_degrees(A_AXIS);
|
||||
scara_oldB = stepper.get_axis_position_degrees(B_AXIS);
|
||||
#endif
|
||||
|
||||
const float seg_dx = tot_dx * inv_segments,
|
||||
seg_dy = tot_dy * inv_segments,
|
||||
seg_dz = tot_dz * inv_segments,
|
||||
seg_de = tot_de * inv_segments;
|
||||
|
||||
// Note that E segment distance could vary slightly as z mesh height
|
||||
// changes for each segment, but small enough to ignore.
|
||||
|
||||
float seg_rx = RAW_X_POSITION(current_position[X_AXIS]),
|
||||
seg_ry = RAW_Y_POSITION(current_position[Y_AXIS]),
|
||||
seg_rz = RAW_Z_POSITION(current_position[Z_AXIS]),
|
||||
seg_le = current_position[E_AXIS];
|
||||
|
||||
const bool above_fade_height = (
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
planner.z_fade_height != 0 && planner.z_fade_height < RAW_Z_POSITION(ltarget[Z_AXIS])
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
|
||||
// Only compute leveling per segment if ubl active and target below z_fade_height.
|
||||
|
||||
if (!state.active || above_fade_height) { // no mesh leveling
|
||||
|
||||
const float z_offset = state.active ? state.z_offset : 0.0;
|
||||
|
||||
do {
|
||||
|
||||
if (--segments) { // not the last segment
|
||||
seg_rx += seg_dx;
|
||||
seg_ry += seg_dy;
|
||||
seg_rz += seg_dz;
|
||||
seg_le += seg_de;
|
||||
} else { // last segment, use exact destination
|
||||
seg_rx = RAW_X_POSITION(ltarget[X_AXIS]);
|
||||
seg_ry = RAW_Y_POSITION(ltarget[Y_AXIS]);
|
||||
seg_rz = RAW_Z_POSITION(ltarget[Z_AXIS]);
|
||||
seg_le = ltarget[E_AXIS];
|
||||
}
|
||||
|
||||
ubl_buffer_segment_raw( seg_rx, seg_ry, seg_rz + z_offset, seg_le, feedrate );
|
||||
|
||||
} while (segments);
|
||||
|
||||
return false; // moved but did not set_current_to_destination();
|
||||
}
|
||||
|
||||
// Otherwise perform per-segment leveling
|
||||
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
const float fade_scaling_factor = fade_scaling_factor_for_z(ltarget[Z_AXIS]);
|
||||
#endif
|
||||
|
||||
// increment to first segment destination
|
||||
seg_rx += seg_dx;
|
||||
seg_ry += seg_dy;
|
||||
seg_rz += seg_dz;
|
||||
seg_le += seg_de;
|
||||
|
||||
for(;;) { // for each mesh cell encountered during the move
|
||||
|
||||
// Compute mesh cell invariants that remain constant for all segments within cell.
|
||||
// Note for cell index, if point is outside the mesh grid (in MESH_INSET perimeter)
|
||||
// the bilinear interpolation from the adjacent cell within the mesh will still work.
|
||||
// Inner loop will exit each time (because out of cell bounds) but will come back
|
||||
// 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 = (seg_rx - (UBL_MESH_MIN_X)) * (1.0 / (MESH_X_DIST)),
|
||||
cell_yi = (seg_ry - (UBL_MESH_MIN_Y)) * (1.0 / (MESH_X_DIST));
|
||||
|
||||
cell_xi = constrain(cell_xi, 0, (GRID_MAX_POINTS_X) - 1);
|
||||
cell_yi = constrain(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
|
||||
|
||||
if (isnan(z_x0y0)) z_x0y0 = 0; // ideally activating state.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 = seg_rx - x0, // cell-relative x and y
|
||||
cy = seg_ry - y0;
|
||||
|
||||
const float z_xmy0 = (z_x1y0 - z_x0y0) * (1.0 / (MESH_X_DIST)), // z slope per x along y0 (lower left to lower right)
|
||||
z_xmy1 = (z_x1y1 - z_x0y1) * (1.0 / (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)
|
||||
|
||||
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
|
||||
|
||||
float z_cxym = z_cxyd * (1.0 / (MESH_Y_DIST)); // z slope per y along cx from y0 to y1 (changes for each cx in cell)
|
||||
|
||||
// float z_cxcy = z_cxy0 + z_cxym * cy; // interpolated mesh z height along cx at cy (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
|
||||
// each change by a constant for fixed segment lengths.
|
||||
|
||||
const float z_sxy0 = z_xmy0 * seg_dx, // per-segment adjustment to z_cxy0
|
||||
z_sxym = (z_xmy1 - z_xmy0) * (1.0 / (MESH_Y_DIST)) * seg_dx; // per-segment adjustment to z_cxym
|
||||
|
||||
for(;;) { // for all segments within this mesh cell
|
||||
|
||||
float z_cxcy = z_cxy0 + z_cxym * cy; // interpolated mesh z height along cx at cy
|
||||
|
||||
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
|
||||
z_cxcy *= fade_scaling_factor; // apply fade factor to interpolated mesh height
|
||||
#endif
|
||||
|
||||
z_cxcy += state.z_offset; // add fixed mesh offset from G29 Z
|
||||
|
||||
if (--segments == 0) { // if this is last segment, use ltarget for exact
|
||||
seg_rx = RAW_X_POSITION(ltarget[X_AXIS]);
|
||||
seg_ry = RAW_Y_POSITION(ltarget[Y_AXIS]);
|
||||
seg_rz = RAW_Z_POSITION(ltarget[Z_AXIS]);
|
||||
seg_le = ltarget[E_AXIS];
|
||||
}
|
||||
|
||||
ubl_buffer_segment_raw( seg_rx, seg_ry, seg_rz + z_cxcy, seg_le, feedrate );
|
||||
|
||||
if (segments == 0 ) // done with last segment
|
||||
return false; // did not set_current_to_destination()
|
||||
|
||||
seg_rx += seg_dx;
|
||||
seg_ry += seg_dy;
|
||||
seg_rz += seg_dz;
|
||||
seg_le += seg_de;
|
||||
|
||||
cx += seg_dx;
|
||||
cy += seg_dy;
|
||||
|
||||
if (!WITHIN(cx, 0, MESH_X_DIST) || !WITHIN(cy, 0, MESH_Y_DIST)) { // done within this cell, break to next
|
||||
break;
|
||||
}
|
||||
|
||||
// Next segment still within same mesh cell, adjust the per-segment
|
||||
// slope and intercept to compute next z height.
|
||||
|
||||
z_cxy0 += z_sxy0; // adjust z_cxy0 by per-segment z_sxy0
|
||||
z_cxym += z_sxym; // adjust z_cxym by per-segment z_sxym
|
||||
|
||||
} // segment loop
|
||||
} // cell loop
|
||||
}
|
||||
|
||||
#endif // UBL_DELTA
|
||||
|
||||
#endif // AUTO_BED_LEVELING_UBL
|
Reference in New Issue
Block a user