Move 'feature' files
This commit is contained in:
892
Marlin/src/feature/ubl/G26_Mesh_Validation_Tool.cpp
Normal file
892
Marlin/src/feature/ubl/G26_Mesh_Validation_Tool.cpp
Normal file
@@ -0,0 +1,892 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Marlin Firmware -- G26 - Mesh Validation Tool
|
||||
*/
|
||||
|
||||
#include "MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(UBL_G26_MESH_VALIDATION)
|
||||
|
||||
#include "ubl.h"
|
||||
#include "Marlin.h"
|
||||
#include "planner.h"
|
||||
#include "stepper.h"
|
||||
#include "temperature.h"
|
||||
#include "ultralcd.h"
|
||||
#include "gcode.h"
|
||||
|
||||
#define EXTRUSION_MULTIPLIER 1.0
|
||||
#define RETRACTION_MULTIPLIER 1.0
|
||||
#define NOZZLE 0.4
|
||||
#define FILAMENT 1.75
|
||||
#define LAYER_HEIGHT 0.2
|
||||
#define PRIME_LENGTH 10.0
|
||||
#define BED_TEMP 60.0
|
||||
#define HOTEND_TEMP 205.0
|
||||
#define OOZE_AMOUNT 0.3
|
||||
|
||||
#define SIZE_OF_INTERSECTION_CIRCLES 5
|
||||
#define SIZE_OF_CROSSHAIRS 3
|
||||
|
||||
#if SIZE_OF_CROSSHAIRS >= SIZE_OF_INTERSECTION_CIRCLES
|
||||
#error "SIZE_OF_CROSSHAIRS must be less than SIZE_OF_INTERSECTION_CIRCLES."
|
||||
#endif
|
||||
|
||||
/**
|
||||
* G26 Mesh Validation Tool
|
||||
*
|
||||
* G26 is a Mesh Validation Tool intended to provide support for the Marlin Unified Bed Leveling System.
|
||||
* In order to fully utilize and benefit from the Marlin Unified Bed Leveling System an accurate Mesh must
|
||||
* be defined. G29 is designed to allow the user to quickly validate the correctness of her Mesh. It will
|
||||
* first heat the bed and nozzle. It will then print lines and circles along the Mesh Cell boundaries and
|
||||
* the intersections of those lines (respectively).
|
||||
*
|
||||
* This action allows the user to immediately see where the Mesh is properly defined and where it needs to
|
||||
* be edited. The command will generate the Mesh lines closest to the nozzle's starting position. Alternatively
|
||||
* the user can specify the X and Y position of interest with command parameters. This allows the user to
|
||||
* focus on a particular area of the Mesh where attention is needed.
|
||||
*
|
||||
* B # Bed Set the Bed Temperature. If not specified, a default of 60 C. will be assumed.
|
||||
*
|
||||
* C Current When searching for Mesh Intersection points to draw, use the current nozzle location
|
||||
* as the base for any distance comparison.
|
||||
*
|
||||
* D Disable Disable the Unified Bed Leveling System. In the normal case the user is invoking this
|
||||
* command to see how well a Mesh as been adjusted to match a print surface. In order to do
|
||||
* this the Unified Bed Leveling System is turned on by the G26 command. The D parameter
|
||||
* alters the command's normal behaviour and disables the Unified Bed Leveling System even if
|
||||
* it is on.
|
||||
*
|
||||
* H # Hotend Set the Nozzle Temperature. If not specified, a default of 205 C. will be assumed.
|
||||
*
|
||||
* F # Filament Used to specify the diameter of the filament being used. If not specified
|
||||
* 1.75mm filament is assumed. If you are not getting acceptable results by using the
|
||||
* 'correct' numbers, you can scale this number up or down a little bit to change the amount
|
||||
* of filament that is being extruded during the printing of the various lines on the bed.
|
||||
*
|
||||
* K Keep-On Keep the heaters turned on at the end of the command.
|
||||
*
|
||||
* L # Layer Layer height. (Height of nozzle above bed) If not specified .20mm will be used.
|
||||
*
|
||||
* O # Ooooze How much your nozzle will Ooooze filament while getting in position to print. This
|
||||
* is over kill, but using this parameter will let you get the very first 'circle' perfect
|
||||
* so you have a trophy to peel off of the bed and hang up to show how perfectly you have your
|
||||
* Mesh calibrated. If not specified, a filament length of .3mm is assumed.
|
||||
*
|
||||
* P # Prime Prime the nozzle with specified length of filament. If this parameter is not
|
||||
* given, no prime action will take place. If the parameter specifies an amount, that much
|
||||
* will be purged before continuing. If no amount is specified the command will start
|
||||
* purging filament until the user provides an LCD Click and then it will continue with
|
||||
* printing the Mesh. You can carefully remove the spent filament with a needle nose
|
||||
* pliers while holding the LCD Click wheel in a depressed state. If you do not have
|
||||
* an LCD, you must specify a value if you use P.
|
||||
*
|
||||
* Q # Multiplier Retraction Multiplier. Normally not needed. Retraction defaults to 1.0mm and
|
||||
* un-retraction is at 1.2mm These numbers will be scaled by the specified amount
|
||||
*
|
||||
* R # Repeat Prints the number of patterns given as a parameter, starting at the current location.
|
||||
* If a parameter isn't given, every point will be printed unless G26 is interrupted.
|
||||
* This works the same way that the UBL G29 P4 R parameter works.
|
||||
*
|
||||
* NOTE: If you do not have an LCD, you -must- specify R. This is to ensure that you are
|
||||
* aware that there's some risk associated with printing without the ability to abort in
|
||||
* cases where mesh point Z value may be inaccurate. As above, if you do not include a
|
||||
* parameter, every point will be printed.
|
||||
*
|
||||
* S # Nozzle Used to control the size of nozzle diameter. If not specified, a .4mm nozzle is assumed.
|
||||
*
|
||||
* U # Random Randomize the order that the circles are drawn on the bed. The search for the closest
|
||||
* undrawn cicle is still done. But the distance to the location for each circle has a
|
||||
* random number of the size specified added to it. Specifying S50 will give an interesting
|
||||
* deviation from the normal behaviour on a 10 x 10 Mesh.
|
||||
*
|
||||
* X # X Coord. Specify the starting location of the drawing activity.
|
||||
*
|
||||
* Y # Y Coord. Specify the starting location of the drawing activity.
|
||||
*/
|
||||
|
||||
// External references
|
||||
|
||||
extern float feedrate_mm_s; // must set before calling prepare_move_to_destination
|
||||
extern Planner planner;
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
extern char lcd_status_message[];
|
||||
#endif
|
||||
extern float destination[XYZE];
|
||||
extern void set_destination_to_current() { COPY(destination, current_position); }
|
||||
void prepare_move_to_destination();
|
||||
#if AVR_AT90USB1286_FAMILY // Teensyduino & Printrboard IDE extensions have compile errors without this
|
||||
inline void sync_plan_position_e() { planner.set_e_position_mm(current_position[E_AXIS]); }
|
||||
inline void set_current_to_destination() { COPY(current_position, destination); }
|
||||
#else
|
||||
extern void sync_plan_position_e() { planner.set_e_position_mm(current_position[E_AXIS]); }
|
||||
extern void set_current_to_destination() { COPY(current_position, destination); }
|
||||
#endif
|
||||
#if ENABLED(NEWPANEL)
|
||||
void lcd_setstatusPGM(const char* const message, const int8_t level);
|
||||
void chirp_at_user();
|
||||
#endif
|
||||
|
||||
// Private functions
|
||||
|
||||
static uint16_t circle_flags[16], horizontal_mesh_line_flags[16], vertical_mesh_line_flags[16];
|
||||
float g26_e_axis_feedrate = 0.020,
|
||||
random_deviation = 0.0;
|
||||
|
||||
static bool g26_retracted = false; // Track the retracted state of the nozzle so mismatched
|
||||
// retracts/recovers won't result in a bad state.
|
||||
|
||||
float valid_trig_angle(float);
|
||||
|
||||
float unified_bed_leveling::g26_extrusion_multiplier,
|
||||
unified_bed_leveling::g26_retraction_multiplier,
|
||||
unified_bed_leveling::g26_nozzle,
|
||||
unified_bed_leveling::g26_filament_diameter,
|
||||
unified_bed_leveling::g26_layer_height,
|
||||
unified_bed_leveling::g26_prime_length,
|
||||
unified_bed_leveling::g26_x_pos,
|
||||
unified_bed_leveling::g26_y_pos,
|
||||
unified_bed_leveling::g26_ooze_amount;
|
||||
|
||||
int16_t unified_bed_leveling::g26_bed_temp,
|
||||
unified_bed_leveling::g26_hotend_temp;
|
||||
|
||||
int8_t unified_bed_leveling::g26_prime_flag;
|
||||
|
||||
bool unified_bed_leveling::g26_continue_with_closest,
|
||||
unified_bed_leveling::g26_keep_heaters_on;
|
||||
|
||||
int16_t unified_bed_leveling::g26_repeats;
|
||||
|
||||
void unified_bed_leveling::G26_line_to_destination(const float &feed_rate) {
|
||||
const float save_feedrate = feedrate_mm_s;
|
||||
feedrate_mm_s = feed_rate; // use specified feed rate
|
||||
prepare_move_to_destination(); // will ultimately call ubl.line_to_destination_cartesian or ubl.prepare_linear_move_to for UBL_DELTA
|
||||
feedrate_mm_s = save_feedrate; // restore global feed rate
|
||||
}
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
/**
|
||||
* Detect ubl_lcd_clicked, debounce it, and return true for cancel
|
||||
*/
|
||||
bool user_canceled() {
|
||||
if (!ubl_lcd_clicked()) return false;
|
||||
safe_delay(10); // Wait for click to settle
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
lcd_setstatusPGM(PSTR("Mesh Validation Stopped."), 99);
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
|
||||
while (!ubl_lcd_clicked()) idle(); // Wait for button release
|
||||
|
||||
// If the button is suddenly pressed again,
|
||||
// ask the user to resolve the issue
|
||||
lcd_setstatusPGM(PSTR("Release button"), 99); // will never appear...
|
||||
while (ubl_lcd_clicked()) idle(); // unless this loop happens
|
||||
lcd_reset_status();
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* G26: Mesh Validation Pattern generation.
|
||||
*
|
||||
* Used to interactively edit UBL's Mesh by placing the
|
||||
* nozzle in a problem area and doing a G29 P4 R command.
|
||||
*/
|
||||
void unified_bed_leveling::G26() {
|
||||
SERIAL_ECHOLNPGM("G26 command started. Waiting for heater(s).");
|
||||
float tmp, start_angle, end_angle;
|
||||
int i, xi, yi;
|
||||
mesh_index_pair location;
|
||||
|
||||
// Don't allow Mesh Validation without homing first,
|
||||
// or if the parameter parsing did not go OK, abort
|
||||
if (axis_unhomed_error() || parse_G26_parameters()) return;
|
||||
|
||||
if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) {
|
||||
do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES);
|
||||
stepper.synchronize();
|
||||
set_current_to_destination();
|
||||
}
|
||||
|
||||
if (turn_on_heaters()) goto LEAVE;
|
||||
|
||||
current_position[E_AXIS] = 0.0;
|
||||
sync_plan_position_e();
|
||||
|
||||
if (g26_prime_flag && prime_nozzle()) goto LEAVE;
|
||||
|
||||
/**
|
||||
* Bed is preheated
|
||||
*
|
||||
* Nozzle is at temperature
|
||||
*
|
||||
* Filament is primed!
|
||||
*
|
||||
* It's "Show Time" !!!
|
||||
*/
|
||||
|
||||
ZERO(circle_flags);
|
||||
ZERO(horizontal_mesh_line_flags);
|
||||
ZERO(vertical_mesh_line_flags);
|
||||
|
||||
// Move nozzle to the specified height for the first layer
|
||||
set_destination_to_current();
|
||||
destination[Z_AXIS] = g26_layer_height;
|
||||
move_to(destination, 0.0);
|
||||
move_to(destination, g26_ooze_amount);
|
||||
|
||||
has_control_of_lcd_panel = true;
|
||||
//debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern."));
|
||||
|
||||
/**
|
||||
* Declare and generate a sin() & cos() table to be used during the circle drawing. This will lighten
|
||||
* the CPU load and make the arc drawing faster and more smooth
|
||||
*/
|
||||
float sin_table[360 / 30 + 1], cos_table[360 / 30 + 1];
|
||||
for (i = 0; i <= 360 / 30; i++) {
|
||||
cos_table[i] = SIZE_OF_INTERSECTION_CIRCLES * cos(RADIANS(valid_trig_angle(i * 30.0)));
|
||||
sin_table[i] = SIZE_OF_INTERSECTION_CIRCLES * sin(RADIANS(valid_trig_angle(i * 30.0)));
|
||||
}
|
||||
|
||||
do {
|
||||
location = g26_continue_with_closest
|
||||
? find_closest_circle_to_print(current_position[X_AXIS], current_position[Y_AXIS])
|
||||
: find_closest_circle_to_print(g26_x_pos, g26_y_pos); // Find the closest Mesh Intersection to where we are now.
|
||||
|
||||
if (location.x_index >= 0 && location.y_index >= 0) {
|
||||
const float circle_x = mesh_index_to_xpos(location.x_index),
|
||||
circle_y = mesh_index_to_ypos(location.y_index);
|
||||
|
||||
// If this mesh location is outside the printable_radius, skip it.
|
||||
|
||||
if (!position_is_reachable_raw_xy(circle_x, circle_y)) continue;
|
||||
|
||||
xi = location.x_index; // Just to shrink the next few lines and make them easier to understand
|
||||
yi = location.y_index;
|
||||
|
||||
if (g26_debug_flag) {
|
||||
SERIAL_ECHOPAIR(" Doing circle at: (xi=", xi);
|
||||
SERIAL_ECHOPAIR(", yi=", yi);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
}
|
||||
|
||||
start_angle = 0.0; // assume it is going to be a full circle
|
||||
end_angle = 360.0;
|
||||
if (xi == 0) { // Check for bottom edge
|
||||
start_angle = -90.0;
|
||||
end_angle = 90.0;
|
||||
if (yi == 0) // it is an edge, check for the two left corners
|
||||
start_angle = 0.0;
|
||||
else if (yi == GRID_MAX_POINTS_Y - 1)
|
||||
end_angle = 0.0;
|
||||
}
|
||||
else if (xi == GRID_MAX_POINTS_X - 1) { // Check for top edge
|
||||
start_angle = 90.0;
|
||||
end_angle = 270.0;
|
||||
if (yi == 0) // it is an edge, check for the two right corners
|
||||
end_angle = 180.0;
|
||||
else if (yi == GRID_MAX_POINTS_Y - 1)
|
||||
start_angle = 180.0;
|
||||
}
|
||||
else if (yi == 0) {
|
||||
start_angle = 0.0; // only do the top side of the cirlce
|
||||
end_angle = 180.0;
|
||||
}
|
||||
else if (yi == GRID_MAX_POINTS_Y - 1) {
|
||||
start_angle = 180.0; // only do the bottom side of the cirlce
|
||||
end_angle = 360.0;
|
||||
}
|
||||
|
||||
for (tmp = start_angle; tmp < end_angle - 0.1; tmp += 30.0) {
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation
|
||||
#endif
|
||||
|
||||
int tmp_div_30 = tmp / 30.0;
|
||||
if (tmp_div_30 < 0) tmp_div_30 += 360 / 30;
|
||||
if (tmp_div_30 > 11) tmp_div_30 -= 360 / 30;
|
||||
|
||||
float x = circle_x + cos_table[tmp_div_30], // for speed, these are now a lookup table entry
|
||||
y = circle_y + sin_table[tmp_div_30],
|
||||
xe = circle_x + cos_table[tmp_div_30 + 1],
|
||||
ye = circle_y + sin_table[tmp_div_30 + 1];
|
||||
#if IS_KINEMATIC
|
||||
// Check to make sure this segment is entirely on the bed, skip if not.
|
||||
if (!position_is_reachable_raw_xy(x, y) || !position_is_reachable_raw_xy(xe, ye)) continue;
|
||||
#else // not, we need to skip
|
||||
x = constrain(x, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops
|
||||
y = constrain(y, Y_MIN_POS + 1, Y_MAX_POS - 1);
|
||||
xe = constrain(xe, X_MIN_POS + 1, X_MAX_POS - 1);
|
||||
ye = constrain(ye, Y_MIN_POS + 1, Y_MAX_POS - 1);
|
||||
#endif
|
||||
|
||||
//if (g26_debug_flag) {
|
||||
// char ccc, *cptr, seg_msg[50], seg_num[10];
|
||||
// strcpy(seg_msg, " segment: ");
|
||||
// strcpy(seg_num, " \n");
|
||||
// cptr = (char*) "01234567890ABCDEF????????";
|
||||
// ccc = cptr[tmp_div_30];
|
||||
// seg_num[1] = ccc;
|
||||
// strcat(seg_msg, seg_num);
|
||||
// debug_current_and_destination(seg_msg);
|
||||
//}
|
||||
|
||||
print_line_from_here_to_there(LOGICAL_X_POSITION(x), LOGICAL_Y_POSITION(y), g26_layer_height, LOGICAL_X_POSITION(xe), LOGICAL_Y_POSITION(ye), g26_layer_height);
|
||||
|
||||
}
|
||||
if (look_for_lines_to_connect())
|
||||
goto LEAVE;
|
||||
}
|
||||
} while (--g26_repeats && location.x_index >= 0 && location.y_index >= 0);
|
||||
|
||||
LEAVE:
|
||||
lcd_setstatusPGM(PSTR("Leaving G26"), -1);
|
||||
|
||||
retract_filament(destination);
|
||||
destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES;
|
||||
|
||||
//debug_current_and_destination(PSTR("ready to do Z-Raise."));
|
||||
move_to(destination, 0); // Raise the nozzle
|
||||
//debug_current_and_destination(PSTR("done doing Z-Raise."));
|
||||
|
||||
destination[X_AXIS] = g26_x_pos; // Move back to the starting position
|
||||
destination[Y_AXIS] = g26_y_pos;
|
||||
//destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; // Keep the nozzle where it is
|
||||
|
||||
move_to(destination, 0); // Move back to the starting position
|
||||
//debug_current_and_destination(PSTR("done doing X/Y move."));
|
||||
|
||||
has_control_of_lcd_panel = false; // Give back control of the LCD Panel!
|
||||
|
||||
if (!g26_keep_heaters_on) {
|
||||
#if HAS_TEMP_BED
|
||||
thermalManager.setTargetBed(0);
|
||||
#endif
|
||||
thermalManager.setTargetHotend(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
float valid_trig_angle(float d) {
|
||||
while (d > 360.0) d -= 360.0;
|
||||
while (d < 0.0) d += 360.0;
|
||||
return d;
|
||||
}
|
||||
|
||||
mesh_index_pair unified_bed_leveling::find_closest_circle_to_print(const float &X, const float &Y) {
|
||||
float closest = 99999.99;
|
||||
mesh_index_pair return_val;
|
||||
|
||||
return_val.x_index = return_val.y_index = -1;
|
||||
|
||||
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
|
||||
for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) {
|
||||
if (!is_bit_set(circle_flags, i, j)) {
|
||||
const float mx = mesh_index_to_xpos(i), // We found a circle that needs to be printed
|
||||
my = mesh_index_to_ypos(j);
|
||||
|
||||
// Get the distance to this intersection
|
||||
float f = HYPOT(X - mx, Y - my);
|
||||
|
||||
// It is possible that we are being called with the values
|
||||
// to let us find the closest circle to the start position.
|
||||
// But if this is not the case, add a small weighting to the
|
||||
// distance calculation to help it choose a better place to continue.
|
||||
f += HYPOT(g26_x_pos - mx, g26_y_pos - my) / 15.0;
|
||||
|
||||
// Add in the specified amount of Random Noise to our search
|
||||
if (random_deviation > 1.0)
|
||||
f += random(0.0, random_deviation);
|
||||
|
||||
if (f < closest) {
|
||||
closest = f; // We found a closer location that is still
|
||||
return_val.x_index = i; // un-printed --- save the data for it
|
||||
return_val.y_index = j;
|
||||
return_val.distance = closest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bit_set(circle_flags, return_val.x_index, return_val.y_index); // Mark this location as done.
|
||||
return return_val;
|
||||
}
|
||||
|
||||
bool unified_bed_leveling::look_for_lines_to_connect() {
|
||||
float sx, sy, ex, ey;
|
||||
|
||||
for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) {
|
||||
for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) {
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
if (user_canceled()) return true; // Check if the user wants to stop the Mesh Validation
|
||||
#endif
|
||||
|
||||
if (i < GRID_MAX_POINTS_X) { // We can't connect to anything to the right than GRID_MAX_POINTS_X.
|
||||
// This is already a half circle because we are at the edge of the bed.
|
||||
|
||||
if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i + 1, j)) { // check if we can do a line to the left
|
||||
if (!is_bit_set(horizontal_mesh_line_flags, i, j)) {
|
||||
|
||||
//
|
||||
// We found two circles that need a horizontal line to connect them
|
||||
// Print it!
|
||||
//
|
||||
sx = mesh_index_to_xpos( i ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // right edge
|
||||
ex = mesh_index_to_xpos(i + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // left edge
|
||||
|
||||
sx = constrain(sx, X_MIN_POS + 1, X_MAX_POS - 1);
|
||||
sy = ey = constrain(mesh_index_to_ypos(j), Y_MIN_POS + 1, Y_MAX_POS - 1);
|
||||
ex = constrain(ex, X_MIN_POS + 1, X_MAX_POS - 1);
|
||||
|
||||
if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) {
|
||||
|
||||
if (g26_debug_flag) {
|
||||
SERIAL_ECHOPAIR(" Connecting with horizontal line (sx=", sx);
|
||||
SERIAL_ECHOPAIR(", sy=", sy);
|
||||
SERIAL_ECHOPAIR(") -> (ex=", ex);
|
||||
SERIAL_ECHOPAIR(", ey=", ey);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
//debug_current_and_destination(PSTR("Connecting horizontal line."));
|
||||
}
|
||||
|
||||
print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height);
|
||||
}
|
||||
bit_set(horizontal_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if we skipped it
|
||||
}
|
||||
}
|
||||
|
||||
if (j < GRID_MAX_POINTS_Y) { // We can't connect to anything further back than GRID_MAX_POINTS_Y.
|
||||
// This is already a half circle because we are at the edge of the bed.
|
||||
|
||||
if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i, j + 1)) { // check if we can do a line straight down
|
||||
if (!is_bit_set( vertical_mesh_line_flags, i, j)) {
|
||||
//
|
||||
// We found two circles that need a vertical line to connect them
|
||||
// Print it!
|
||||
//
|
||||
sy = mesh_index_to_ypos( j ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // top edge
|
||||
ey = mesh_index_to_ypos(j + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // bottom edge
|
||||
|
||||
sx = ex = constrain(mesh_index_to_xpos(i), X_MIN_POS + 1, X_MAX_POS - 1);
|
||||
sy = constrain(sy, Y_MIN_POS + 1, Y_MAX_POS - 1);
|
||||
ey = constrain(ey, Y_MIN_POS + 1, Y_MAX_POS - 1);
|
||||
|
||||
if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) {
|
||||
|
||||
if (g26_debug_flag) {
|
||||
SERIAL_ECHOPAIR(" Connecting with vertical line (sx=", sx);
|
||||
SERIAL_ECHOPAIR(", sy=", sy);
|
||||
SERIAL_ECHOPAIR(") -> (ex=", ex);
|
||||
SERIAL_ECHOPAIR(", ey=", ey);
|
||||
SERIAL_CHAR(')');
|
||||
SERIAL_EOL();
|
||||
debug_current_and_destination(PSTR("Connecting vertical line."));
|
||||
}
|
||||
print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height);
|
||||
}
|
||||
bit_set(vertical_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if skipped
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void unified_bed_leveling::move_to(const float &x, const float &y, const float &z, const float &e_delta) {
|
||||
float feed_value;
|
||||
static float last_z = -999.99;
|
||||
|
||||
bool has_xy_component = (x != current_position[X_AXIS] || y != current_position[Y_AXIS]); // Check if X or Y is involved in the movement.
|
||||
|
||||
if (z != last_z) {
|
||||
last_z = z;
|
||||
feed_value = planner.max_feedrate_mm_s[Z_AXIS]/(3.0); // Base the feed rate off of the configured Z_AXIS feed rate
|
||||
|
||||
destination[X_AXIS] = current_position[X_AXIS];
|
||||
destination[Y_AXIS] = current_position[Y_AXIS];
|
||||
destination[Z_AXIS] = z; // We know the last_z==z or we wouldn't be in this block of code.
|
||||
destination[E_AXIS] = current_position[E_AXIS];
|
||||
|
||||
G26_line_to_destination(feed_value);
|
||||
|
||||
stepper.synchronize();
|
||||
set_destination_to_current();
|
||||
}
|
||||
|
||||
// Check if X or Y is involved in the movement.
|
||||
// Yes: a 'normal' movement. No: a retract() or recover()
|
||||
feed_value = has_xy_component ? PLANNER_XY_FEEDRATE() / 10.0 : planner.max_feedrate_mm_s[E_AXIS] / 1.5;
|
||||
|
||||
if (g26_debug_flag) SERIAL_ECHOLNPAIR("in move_to() feed_value for XY:", feed_value);
|
||||
|
||||
destination[X_AXIS] = x;
|
||||
destination[Y_AXIS] = y;
|
||||
destination[E_AXIS] += e_delta;
|
||||
|
||||
G26_line_to_destination(feed_value);
|
||||
|
||||
stepper.synchronize();
|
||||
set_destination_to_current();
|
||||
|
||||
}
|
||||
|
||||
void unified_bed_leveling::retract_filament(const float where[XYZE]) {
|
||||
if (!g26_retracted) { // Only retract if we are not already retracted!
|
||||
g26_retracted = true;
|
||||
move_to(where, -1.0 * g26_retraction_multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
void unified_bed_leveling::recover_filament(const float where[XYZE]) {
|
||||
if (g26_retracted) { // Only un-retract if we are retracted.
|
||||
move_to(where, 1.2 * g26_retraction_multiplier);
|
||||
g26_retracted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* print_line_from_here_to_there() takes two cartesian coordinates and draws a line from one
|
||||
* to the other. But there are really three sets of coordinates involved. The first coordinate
|
||||
* is the present location of the nozzle. We don't necessarily want to print from this location.
|
||||
* We first need to move the nozzle to the start of line segment where we want to print. Once
|
||||
* there, we can use the two coordinates supplied to draw the line.
|
||||
*
|
||||
* Note: Although we assume the first set of coordinates is the start of the line and the second
|
||||
* set of coordinates is the end of the line, it does not always work out that way. This function
|
||||
* optimizes the movement to minimize the travel distance before it can start printing. This saves
|
||||
* a lot of time and eliminates a lot of nonsensical movement of the nozzle. However, it does
|
||||
* cause a lot of very little short retracement of th nozzle when it draws the very first line
|
||||
* segment of a 'circle'. The time this requires is very short and is easily saved by the other
|
||||
* cases where the optimization comes into play.
|
||||
*/
|
||||
void unified_bed_leveling::print_line_from_here_to_there(const float &sx, const float &sy, const float &sz, const float &ex, const float &ey, const float &ez) {
|
||||
const float dx_s = current_position[X_AXIS] - sx, // find our distance from the start of the actual line segment
|
||||
dy_s = current_position[Y_AXIS] - sy,
|
||||
dist_start = HYPOT2(dx_s, dy_s), // We don't need to do a sqrt(), we can compare the distance^2
|
||||
// to save computation time
|
||||
dx_e = current_position[X_AXIS] - ex, // find our distance from the end of the actual line segment
|
||||
dy_e = current_position[Y_AXIS] - ey,
|
||||
dist_end = HYPOT2(dx_e, dy_e),
|
||||
|
||||
line_length = HYPOT(ex - sx, ey - sy);
|
||||
|
||||
// If the end point of the line is closer to the nozzle, flip the direction,
|
||||
// moving from the end to the start. On very small lines the optimization isn't worth it.
|
||||
if (dist_end < dist_start && (SIZE_OF_INTERSECTION_CIRCLES) < FABS(line_length)) {
|
||||
return print_line_from_here_to_there(ex, ey, ez, sx, sy, sz);
|
||||
}
|
||||
|
||||
// Decide whether to retract & bump
|
||||
|
||||
if (dist_start > 2.0) {
|
||||
retract_filament(destination);
|
||||
//todo: parameterize the bump height with a define
|
||||
move_to(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + 0.500, 0.0); // Z bump to minimize scraping
|
||||
move_to(sx, sy, sz + 0.500, 0.0); // Get to the starting point with no extrusion while bumped
|
||||
}
|
||||
|
||||
move_to(sx, sy, sz, 0.0); // Get to the starting point with no extrusion / un-Z bump
|
||||
|
||||
const float e_pos_delta = line_length * g26_e_axis_feedrate * g26_extrusion_multiplier;
|
||||
|
||||
recover_filament(destination);
|
||||
move_to(ex, ey, ez, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion
|
||||
}
|
||||
|
||||
/**
|
||||
* This function used to be inline code in G26. But there are so many
|
||||
* parameters it made sense to turn them into static globals and get
|
||||
* this code out of sight of the main routine.
|
||||
*/
|
||||
bool unified_bed_leveling::parse_G26_parameters() {
|
||||
|
||||
g26_extrusion_multiplier = EXTRUSION_MULTIPLIER;
|
||||
g26_retraction_multiplier = RETRACTION_MULTIPLIER;
|
||||
g26_nozzle = NOZZLE;
|
||||
g26_filament_diameter = FILAMENT;
|
||||
g26_layer_height = LAYER_HEIGHT;
|
||||
g26_prime_length = PRIME_LENGTH;
|
||||
g26_bed_temp = BED_TEMP;
|
||||
g26_hotend_temp = HOTEND_TEMP;
|
||||
g26_prime_flag = 0;
|
||||
|
||||
g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT);
|
||||
g26_keep_heaters_on = parser.boolval('K');
|
||||
g26_continue_with_closest = parser.boolval('C');
|
||||
|
||||
if (parser.seenval('B')) {
|
||||
g26_bed_temp = parser.value_celsius();
|
||||
if (!WITHIN(g26_bed_temp, 15, 140)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified bed temperature not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seenval('L')) {
|
||||
g26_layer_height = parser.value_linear_units();
|
||||
if (!WITHIN(g26_layer_height, 0.0, 2.0)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified layer height not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seen('Q')) {
|
||||
if (parser.has_value()) {
|
||||
g26_retraction_multiplier = parser.value_float();
|
||||
if (!WITHIN(g26_retraction_multiplier, 0.05, 15.0)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified Retraction Multiplier not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SERIAL_PROTOCOLLNPGM("?Retraction Multiplier must be specified.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seenval('S')) {
|
||||
g26_nozzle = parser.value_float();
|
||||
if (!WITHIN(g26_nozzle, 0.1, 1.0)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified nozzle size not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seen('P')) {
|
||||
if (!parser.has_value()) {
|
||||
#if ENABLED(NEWPANEL)
|
||||
g26_prime_flag = -1;
|
||||
#else
|
||||
SERIAL_PROTOCOLLNPGM("?Prime length must be specified when not using an LCD.");
|
||||
return UBL_ERR;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
g26_prime_flag++;
|
||||
g26_prime_length = parser.value_linear_units();
|
||||
if (!WITHIN(g26_prime_length, 0.0, 25.0)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified prime length not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seenval('F')) {
|
||||
g26_filament_diameter = parser.value_linear_units();
|
||||
if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified filament size not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
g26_extrusion_multiplier *= sq(1.75) / sq(g26_filament_diameter); // If we aren't using 1.75mm filament, we need to
|
||||
// scale up or down the length needed to get the
|
||||
// same volume of filament
|
||||
|
||||
g26_extrusion_multiplier *= g26_filament_diameter * sq(g26_nozzle) / sq(0.3); // Scale up by nozzle size
|
||||
|
||||
if (parser.seenval('H')) {
|
||||
g26_hotend_temp = parser.value_celsius();
|
||||
if (!WITHIN(g26_hotend_temp, 165, 280)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified nozzle temperature not plausible.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.seen('U')) {
|
||||
randomSeed(millis());
|
||||
// This setting will persist for the next G26
|
||||
random_deviation = parser.has_value() ? parser.value_float() : 50.0;
|
||||
}
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
g26_repeats = parser.intval('R', GRID_MAX_POINTS + 1);
|
||||
#else
|
||||
if (!parser.seen('R')) {
|
||||
SERIAL_PROTOCOLLNPGM("?(R)epeat must be specified when not using an LCD.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
else
|
||||
g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1;
|
||||
#endif
|
||||
if (g26_repeats < 1) {
|
||||
SERIAL_PROTOCOLLNPGM("?(R)epeat value not plausible; must be at least 1.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
|
||||
g26_x_pos = parser.linearval('X', current_position[X_AXIS]);
|
||||
g26_y_pos = parser.linearval('Y', current_position[Y_AXIS]);
|
||||
if (!position_is_reachable_xy(g26_x_pos, g26_y_pos)) {
|
||||
SERIAL_PROTOCOLLNPGM("?Specified X,Y coordinate out of bounds.");
|
||||
return UBL_ERR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until all parameters are verified before altering the state!
|
||||
*/
|
||||
set_bed_leveling_enabled(!parser.seen('D'));
|
||||
|
||||
return UBL_OK;
|
||||
}
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
bool unified_bed_leveling::exit_from_g26() {
|
||||
lcd_setstatusPGM(PSTR("Leaving G26"), -1);
|
||||
while (ubl_lcd_clicked()) idle();
|
||||
return UBL_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Turn on the bed and nozzle heat and
|
||||
* wait for them to get up to temperature.
|
||||
*/
|
||||
bool unified_bed_leveling::turn_on_heaters() {
|
||||
millis_t next = millis() + 5000UL;
|
||||
#if HAS_TEMP_BED
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
if (g26_bed_temp > 25) {
|
||||
lcd_setstatusPGM(PSTR("G26 Heating Bed."), 99);
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
has_control_of_lcd_panel = true;
|
||||
thermalManager.setTargetBed(g26_bed_temp);
|
||||
while (abs(thermalManager.degBed() - g26_bed_temp) > 3) {
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
if (ubl_lcd_clicked()) return exit_from_g26();
|
||||
#endif
|
||||
|
||||
if (ELAPSED(millis(), next)) {
|
||||
next = millis() + 5000UL;
|
||||
print_heaterstates();
|
||||
SERIAL_EOL();
|
||||
}
|
||||
idle();
|
||||
}
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
}
|
||||
lcd_setstatusPGM(PSTR("G26 Heating Nozzle."), 99);
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Start heating the nozzle and wait for it to reach temperature.
|
||||
thermalManager.setTargetHotend(g26_hotend_temp, 0);
|
||||
while (abs(thermalManager.degHotend(0) - g26_hotend_temp) > 3) {
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
if (ubl_lcd_clicked()) return exit_from_g26();
|
||||
#endif
|
||||
|
||||
if (ELAPSED(millis(), next)) {
|
||||
next = millis() + 5000UL;
|
||||
print_heaterstates();
|
||||
SERIAL_EOL();
|
||||
}
|
||||
idle();
|
||||
}
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
lcd_reset_status();
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
|
||||
return UBL_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prime the nozzle if needed. Return true on error.
|
||||
*/
|
||||
bool unified_bed_leveling::prime_nozzle() {
|
||||
|
||||
#if ENABLED(NEWPANEL)
|
||||
float Total_Prime = 0.0;
|
||||
|
||||
if (g26_prime_flag == -1) { // The user wants to control how much filament gets purged
|
||||
|
||||
has_control_of_lcd_panel = true;
|
||||
lcd_setstatusPGM(PSTR("User-Controlled Prime"), 99);
|
||||
chirp_at_user();
|
||||
|
||||
set_destination_to_current();
|
||||
|
||||
recover_filament(destination); // Make sure G26 doesn't think the filament is retracted().
|
||||
|
||||
while (!ubl_lcd_clicked()) {
|
||||
chirp_at_user();
|
||||
destination[E_AXIS] += 0.25;
|
||||
#ifdef PREVENT_LENGTHY_EXTRUDE
|
||||
Total_Prime += 0.25;
|
||||
if (Total_Prime >= EXTRUDE_MAXLENGTH) return UBL_ERR;
|
||||
#endif
|
||||
G26_line_to_destination(planner.max_feedrate_mm_s[E_AXIS] / 15.0);
|
||||
|
||||
stepper.synchronize(); // Without this synchronize, the purge is more consistent,
|
||||
// but because the planner has a buffer, we won't be able
|
||||
// to stop as quickly. So we put up with the less smooth
|
||||
// action to give the user a more responsive 'Stop'.
|
||||
set_destination_to_current();
|
||||
idle();
|
||||
}
|
||||
|
||||
while (ubl_lcd_clicked()) idle(); // Debounce Encoder Wheel
|
||||
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
strcpy_P(lcd_status_message, PSTR("Done Priming")); // We can't do lcd_setstatusPGM() without having it continue;
|
||||
// So... We cheat to get a message up.
|
||||
lcd_setstatusPGM(PSTR("Done Priming"), 99);
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
|
||||
has_control_of_lcd_panel = false;
|
||||
|
||||
}
|
||||
else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
#if ENABLED(ULTRA_LCD)
|
||||
lcd_setstatusPGM(PSTR("Fixed Length Prime."), 99);
|
||||
lcd_quick_feedback();
|
||||
#endif
|
||||
set_destination_to_current();
|
||||
destination[E_AXIS] += g26_prime_length;
|
||||
G26_line_to_destination(planner.max_feedrate_mm_s[E_AXIS] / 15.0);
|
||||
stepper.synchronize();
|
||||
set_destination_to_current();
|
||||
retract_filament(destination);
|
||||
}
|
||||
|
||||
return UBL_OK;
|
||||
}
|
||||
|
||||
#endif // AUTO_BED_LEVELING_UBL && UBL_G26_MESH_VALIDATION
|
||||
196
Marlin/src/feature/ubl/ubl.cpp
Normal file
196
Marlin/src/feature/ubl/ubl.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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 "Marlin.h"
|
||||
#include "math.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
|
||||
#include "ubl.h"
|
||||
#include "hex_print_routines.h"
|
||||
#include "temperature.h"
|
||||
|
||||
extern Planner planner;
|
||||
|
||||
/**
|
||||
* 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], uint8_t x, uint8_t y) { CBI(bits[y], x); }
|
||||
void bit_set(uint16_t bits[16], uint8_t x, uint8_t y) { SBI(bits[y], x); }
|
||||
bool is_bit_set(uint16_t bits[16], uint8_t x, 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;
|
||||
|
||||
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
|
||||
409
Marlin/src/feature/ubl/ubl.h
Normal file
409
Marlin/src/feature/ubl/ubl.h
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 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 "MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
#include "Marlin.h"
|
||||
#include "planner.h"
|
||||
#include "math.h"
|
||||
#include "vector_3.h"
|
||||
#include "configuration_store.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], uint8_t x, uint8_t y);
|
||||
void bit_set(uint16_t bits[16], uint8_t x, uint8_t y);
|
||||
bool is_bit_set(uint16_t bits[16], uint8_t x, 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();
|
||||
void home_all_axes();
|
||||
|
||||
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;
|
||||
|
||||
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)) {
|
||||
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();
|
||||
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)) {
|
||||
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();
|
||||
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);
|
||||
|
||||
}; // class unified_bed_leveling
|
||||
|
||||
extern unified_bed_leveling ubl;
|
||||
|
||||
#if ENABLED(UBL_G26_MESH_VALIDATION)
|
||||
FORCE_INLINE void gcode_G26() { ubl.G26(); }
|
||||
#endif
|
||||
|
||||
FORCE_INLINE void gcode_G29() { ubl.G29(); }
|
||||
|
||||
#endif // AUTO_BED_LEVELING_UBL
|
||||
#endif // UNIFIED_BED_LEVELING_H
|
||||
1825
Marlin/src/feature/ubl/ubl_G29.cpp
Normal file
1825
Marlin/src/feature/ubl/ubl_G29.cpp
Normal file
File diff suppressed because it is too large
Load Diff
738
Marlin/src/feature/ubl/ubl_motion.cpp
Normal file
738
Marlin/src/feature/ubl/ubl_motion.cpp
Normal file
@@ -0,0 +1,738 @@
|
||||
/**
|
||||
* 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 "MarlinConfig.h"
|
||||
|
||||
#if ENABLED(AUTO_BED_LEVELING_UBL)
|
||||
|
||||
#include "Marlin.h"
|
||||
#include "ubl.h"
|
||||
#include "planner.h"
|
||||
#include "stepper.h"
|
||||
#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
|
||||
|
||||
#if ENABLED(DELTA)
|
||||
|
||||
extern float delta[ABC],
|
||||
endstop_adj[ABC];
|
||||
|
||||
extern float delta_radius,
|
||||
delta_tower_angle_trim[2],
|
||||
delta_tower[ABC][2],
|
||||
delta_diagonal_rod,
|
||||
delta_calibration_radius,
|
||||
delta_diagonal_rod_2_tower[ABC],
|
||||
delta_segments_per_second,
|
||||
delta_clip_start_height;
|
||||
|
||||
extern float delta_safe_distance_from_top();
|
||||
|
||||
#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