Group UBL parameters, add comments

This commit is contained in:
Scott Lahteine 2021-03-24 03:28:48 -05:00 committed by Scott Lahteine
parent 8fca37f373
commit da4b6896f7
5 changed files with 223 additions and 209 deletions

View File

@ -45,23 +45,26 @@ struct mesh_index_pair;
typedef int16_t mesh_store_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; typedef int16_t mesh_store_t[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
#endif #endif
typedef struct {
bool C_seen;
int8_t V_verbosity,
P_phase,
R_repetition,
KLS_storage_slot,
T_map_type;
float B_shim_thickness,
C_constant;
xy_pos_t XY_pos;
xy_bool_t XY_seen;
#if HAS_BED_PROBE
int grid_size;
#endif
} G29_parameters_t;
class unified_bed_leveling { class unified_bed_leveling {
private: private:
static int g29_verbose_level, static G29_parameters_t param;
g29_phase_value,
g29_repetition_cnt,
g29_storage_slot,
g29_map_type;
static bool g29_c_flag;
static float g29_card_thickness,
g29_constant;
static xy_pos_t g29_pos;
static xy_bool_t xy_seen;
#if HAS_BED_PROBE
static int g29_grid_size;
#endif
#if IS_NEWPANEL #if IS_NEWPANEL
static void move_z_with_encoder(const float &multiplier); static void move_z_with_encoder(const float &multiplier);
@ -71,7 +74,7 @@ private:
static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) _O0; static void fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) _O0;
#endif #endif
static bool g29_parameter_parsing() _O0; static bool G29_parse_parameters() _O0;
static void shift_mesh_height(); static void shift_mesh_height();
static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0; static void probe_entire_mesh(const xy_pos_t &near, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) _O0;
static void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3); static void tilt_mesh_based_on_3pts(const float &z1, const float &z2, const float &z3);
@ -129,7 +132,7 @@ public:
static inline void steppers_were_disabled() {} static inline void steppers_were_disabled() {}
#endif #endif
static volatile int16_t encoder_diff; // Volatile because buttons may changed it at interrupt time static volatile int16_t encoder_diff; // Volatile because buttons may change it at interrupt time
unified_bed_leveling(); unified_bed_leveling();

View File

@ -71,21 +71,6 @@
#define SIZE_OF_LITTLE_RAISE 1 #define SIZE_OF_LITTLE_RAISE 1
#define BIG_RAISE_NOT_NEEDED 0 #define BIG_RAISE_NOT_NEEDED 0
int unified_bed_leveling::g29_verbose_level,
unified_bed_leveling::g29_phase_value,
unified_bed_leveling::g29_repetition_cnt,
unified_bed_leveling::g29_storage_slot = 0,
unified_bed_leveling::g29_map_type;
bool unified_bed_leveling::g29_c_flag;
float unified_bed_leveling::g29_card_thickness = 0,
unified_bed_leveling::g29_constant = 0;
xy_bool_t unified_bed_leveling::xy_seen;
xy_pos_t unified_bed_leveling::g29_pos;
#if HAS_BED_PROBE
int unified_bed_leveling::g29_grid_size;
#endif
/** /**
* G29: Unified Bed Leveling by Roxy * G29: Unified Bed Leveling by Roxy
* *
@ -309,10 +294,12 @@ xy_pos_t unified_bed_leveling::g29_pos;
* features of all three systems combined. * features of all three systems combined.
*/ */
G29_parameters_t unified_bed_leveling::param;
void unified_bed_leveling::G29() { void unified_bed_leveling::G29() {
bool probe_deployed = false; bool probe_deployed = false;
if (g29_parameter_parsing()) return; // Abort on parameter error if (G29_parse_parameters()) return; // Abort on parameter error
const int8_t p_val = parser.intval('P', -1); const int8_t p_val = parser.intval('P', -1);
const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen('J'); const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen('J');
@ -326,33 +313,29 @@ void unified_bed_leveling::G29() {
TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0)); TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0));
} }
// Invalidate Mesh Points. This command is a little bit asymmetrical because // Invalidate one or more nearby mesh points, possibly all.
// it directly specifies the repetition count and does not use the 'R' parameter.
if (parser.seen('I')) { if (parser.seen('I')) {
uint8_t cnt = 0; int16_t count = parser.has_value() ? parser.value_int() : 1;
g29_repetition_cnt = parser.has_value() ? parser.value_int() : 1; bool invalidate_all = count >= GRID_MAX_POINTS;
if (g29_repetition_cnt >= GRID_MAX_POINTS) { if (!invalidate_all) {
set_all_mesh_points_to_value(NAN); while (count--) {
} if ((count & 0x0F) == 0x0F) idle();
else { const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, param.XY_pos);
while (g29_repetition_cnt--) { // No more REAL mesh points to invalidate? Assume the user meant
if (cnt > 20) { cnt = 0; idle(); } // to invalidate the ENTIRE mesh, which can't be done with
const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, g29_pos); // find_closest_mesh_point (which only returns REAL points).
const xy_int8_t &cpos = closest.pos; if (closest.pos.x < 0) { invalidate_all = true; break; }
if (cpos.x < 0) { z_values[closest.pos.x][closest.pos.y] = NAN;
// No more REAL mesh points to invalidate, so we ASSUME the user TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(closest.pos, 0.0f));
// meant to invalidate the ENTIRE mesh, which cannot be done with
// find_closest_mesh_point loop which only returns REAL points.
set_all_mesh_points_to_value(NAN);
SERIAL_ECHOLNPGM("Entire Mesh invalidated.\n");
break; // No more invalid Mesh Points to populate
}
z_values[cpos.x][cpos.y] = NAN;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, 0.0f));
cnt++;
} }
} }
SERIAL_ECHOLNPGM("Locations invalidated.\n"); if (invalidate_all) {
invalidate();
SERIAL_ECHOPGM("Entire Mesh");
}
else
SERIAL_ECHOPGM("Locations");
SERIAL_ECHOLNPGM(" invalidated.\n");
} }
if (parser.seen('Q')) { if (parser.seen('Q')) {
@ -364,11 +347,7 @@ void unified_bed_leveling::G29() {
SERIAL_ECHOLNPGM("Loading test_pattern values.\n"); SERIAL_ECHOLNPGM("Loading test_pattern values.\n");
switch (test_pattern) { switch (test_pattern) {
#if ENABLED(UBL_DEVEL_DEBUGGING) case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break;
case -1:
g29_eeprom_dump();
break;
#endif
case 0: case 0:
GRID_LOOP(x, y) { // Create a bowl shape similar to a poorly-calibrated Delta GRID_LOOP(x, y) { // Create a bowl shape similar to a poorly-calibrated Delta
@ -395,7 +374,7 @@ void unified_bed_leveling::G29() {
// Allow the user to specify the height because 10mm is a little extreme in some cases. // Allow the user to specify the height because 10mm is a little extreme in some cases.
for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in
for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed
z_values[x][y] += parser.seen('C') ? g29_constant : 9.99f; z_values[x][y] += parser.seen('C') ? param.C_constant : 9.99f;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
} }
break; break;
@ -406,7 +385,7 @@ void unified_bed_leveling::G29() {
if (parser.seen('J')) { if (parser.seen('J')) {
save_ubl_active_state_and_disable(); save_ubl_active_state_and_disable();
tilt_mesh_based_on_probed_grid(g29_grid_size == 0); // Zero size does 3-Point tilt_mesh_based_on_probed_grid(param.grid_size == 0); // Zero size does 3-Point
restore_ubl_active_state_and_leave(); restore_ubl_active_state_and_leave();
#if ENABLED(UBL_G29_J_RECENTER) #if ENABLED(UBL_G29_J_RECENTER)
do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y))); do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y)));
@ -418,12 +397,12 @@ void unified_bed_leveling::G29() {
#endif // HAS_BED_PROBE #endif // HAS_BED_PROBE
if (parser.seen('P')) { if (parser.seen('P')) {
if (WITHIN(g29_phase_value, 0, 1) && storage_slot == -1) { if (WITHIN(param.P_phase, 0, 1) && storage_slot == -1) {
storage_slot = 0; storage_slot = 0;
SERIAL_ECHOLNPGM("Default storage slot 0 selected."); SERIAL_ECHOLNPGM("Default storage slot 0 selected.");
} }
switch (g29_phase_value) { switch (param.P_phase) {
case 0: case 0:
// //
// Zero Mesh Data // Zero Mesh Data
@ -442,13 +421,13 @@ void unified_bed_leveling::G29() {
invalidate(); invalidate();
SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh.");
} }
if (g29_verbose_level > 1) { if (param.V_verbosity > 1) {
SERIAL_ECHOPAIR("Probing around (", g29_pos.x); SERIAL_ECHOPAIR("Probing around (", param.XY_pos.x);
SERIAL_CHAR(','); SERIAL_CHAR(',');
SERIAL_DECIMAL(g29_pos.y); SERIAL_DECIMAL(param.XY_pos.y);
SERIAL_ECHOLNPGM(").\n"); SERIAL_ECHOLNPGM(").\n");
} }
const xy_pos_t near_probe_xy = g29_pos + probe.offset_xy; const xy_pos_t near_probe_xy = param.XY_pos + probe.offset_xy;
probe_entire_mesh(near_probe_xy, parser.seen('T'), parser.seen('E'), parser.seen('U')); probe_entire_mesh(near_probe_xy, parser.seen('T'), parser.seen('E'), parser.seen('U'));
report_current_position(); report_current_position();
@ -465,7 +444,7 @@ void unified_bed_leveling::G29() {
SERIAL_ECHOLNPGM("Manually probing unreachable points."); SERIAL_ECHOLNPGM("Manually probing unreachable points.");
do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES);
if (parser.seen('C') && !xy_seen) { if (parser.seen('C') && !param.XY_seen) {
/** /**
* Use a good default location for the path. * Use a good default location for the path.
@ -474,7 +453,7 @@ void unified_bed_leveling::G29() {
* It may make sense to have Delta printers default to the center of the bed. * It may make sense to have Delta printers default to the center of the bed.
* Until that is decided, this can be forced with the X and Y parameters. * Until that is decided, this can be forced with the X and Y parameters.
*/ */
g29_pos.set( param.XY_pos.set(
#if IS_KINEMATIC #if IS_KINEMATIC
X_HOME_POS, Y_HOME_POS X_HOME_POS, Y_HOME_POS
#else #else
@ -485,21 +464,21 @@ void unified_bed_leveling::G29() {
} }
if (parser.seen('B')) { if (parser.seen('B')) {
g29_card_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(); param.B_shim_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness();
if (ABS(g29_card_thickness) > 1.5f) { if (ABS(param.B_shim_thickness) > 1.5f) {
SERIAL_ECHOLNPGM("?Error in Business Card measurement."); SERIAL_ECHOLNPGM("?Error in Business Card measurement.");
return; return;
} }
probe_deployed = true; probe_deployed = true;
} }
if (!position_is_reachable(g29_pos)) { if (!position_is_reachable(param.XY_pos)) {
SERIAL_ECHOLNPGM("XY outside printable radius."); SERIAL_ECHOLNPGM("XY outside printable radius.");
return; return;
} }
const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES);
manually_probe_remaining_mesh(g29_pos, height, g29_card_thickness, parser.seen('T')); manually_probe_remaining_mesh(param.XY_pos, height, param.B_shim_thickness, parser.seen('T'));
SERIAL_ECHOLNPGM("G29 P2 finished."); SERIAL_ECHOLNPGM("G29 P2 finished.");
@ -521,23 +500,23 @@ void unified_bed_leveling::G29() {
* - Allow 'G29 P3' to choose a 'reasonable' constant. * - Allow 'G29 P3' to choose a 'reasonable' constant.
*/ */
if (g29_c_flag) { if (param.C_seen) {
if (g29_repetition_cnt >= GRID_MAX_POINTS) { if (param.R_repetition >= GRID_MAX_POINTS) {
set_all_mesh_points_to_value(g29_constant); set_all_mesh_points_to_value(param.C_constant);
} }
else { else {
while (g29_repetition_cnt--) { // this only populates reachable mesh points near while (param.R_repetition--) { // this only populates reachable mesh points near
const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, g29_pos); const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, param.XY_pos);
const xy_int8_t &cpos = closest.pos; const xy_int8_t &cpos = closest.pos;
if (cpos.x < 0) { if (cpos.x < 0) {
// No more REAL INVALID mesh points to populate, so we ASSUME // No more REAL INVALID mesh points to populate, so we ASSUME
// user meant to populate ALL INVALID mesh points to value // user meant to populate ALL INVALID mesh points to value
GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = g29_constant; GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = param.C_constant;
break; // No more invalid Mesh Points to populate break; // No more invalid Mesh Points to populate
} }
else { else {
z_values[cpos.x][cpos.y] = g29_constant; z_values[cpos.x][cpos.y] = param.C_constant;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, g29_constant)); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, param.C_constant));
} }
} }
} }
@ -571,14 +550,14 @@ void unified_bed_leveling::G29() {
case 4: // Fine Tune (i.e., Edit) the Mesh case 4: // Fine Tune (i.e., Edit) the Mesh
#if HAS_LCD_MENU #if HAS_LCD_MENU
fine_tune_mesh(g29_pos, parser.seen('T')); fine_tune_mesh(param.XY_pos, parser.seen('T'));
#else #else
SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present.");
return; return;
#endif #endif
break; break;
case 5: adjust_mesh_to_mean(g29_c_flag, g29_constant); break; case 5: adjust_mesh_to_mean(param.C_seen, param.C_constant); break;
case 6: shift_mesh_height(); break; case 6: shift_mesh_height(); break;
} }
@ -608,7 +587,7 @@ void unified_bed_leveling::G29() {
// //
if (parser.seen('L')) { // Load Current Mesh Data if (parser.seen('L')) { // Load Current Mesh Data
g29_storage_slot = parser.has_value() ? parser.value_int() : storage_slot; param.KLS_storage_slot = parser.has_value() ? parser.value_int() : storage_slot;
int16_t a = settings.calc_num_meshes(); int16_t a = settings.calc_num_meshes();
@ -617,13 +596,13 @@ void unified_bed_leveling::G29() {
return; return;
} }
if (!WITHIN(g29_storage_slot, 0, a - 1)) { if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) {
SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1);
return; return;
} }
settings.load_mesh(g29_storage_slot); settings.load_mesh(param.KLS_storage_slot);
storage_slot = g29_storage_slot; storage_slot = param.KLS_storage_slot;
SERIAL_ECHOLNPGM("Done."); SERIAL_ECHOLNPGM("Done.");
} }
@ -633,9 +612,9 @@ void unified_bed_leveling::G29() {
// //
if (parser.seen('S')) { // Store (or Save) Current Mesh Data if (parser.seen('S')) { // Store (or Save) Current Mesh Data
g29_storage_slot = parser.has_value() ? parser.value_int() : storage_slot; param.KLS_storage_slot = parser.has_value() ? parser.value_int() : storage_slot;
if (g29_storage_slot == -1) // Special case, the user wants to 'Export' the mesh to the if (param.KLS_storage_slot == -1) // Special case, the user wants to 'Export' the mesh to the
return report_current_mesh(); // host program to be saved on the user's computer return report_current_mesh(); // host program to be saved on the user's computer
int16_t a = settings.calc_num_meshes(); int16_t a = settings.calc_num_meshes();
@ -645,19 +624,19 @@ void unified_bed_leveling::G29() {
goto LEAVE; goto LEAVE;
} }
if (!WITHIN(g29_storage_slot, 0, a - 1)) { if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) {
SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1);
goto LEAVE; goto LEAVE;
} }
settings.store_mesh(g29_storage_slot); settings.store_mesh(param.KLS_storage_slot);
storage_slot = g29_storage_slot; storage_slot = param.KLS_storage_slot;
SERIAL_ECHOLNPGM("Done."); SERIAL_ECHOLNPGM("Done.");
} }
if (parser.seen('T')) if (parser.seen('T'))
display_map(g29_map_type); display_map(param.T_map_type);
LEAVE: LEAVE:
@ -682,7 +661,12 @@ void unified_bed_leveling::G29() {
return; return;
} }
void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const float value) { /**
* M420 C<value>
* G29 P5 C<value> : Adjust Mesh To Mean (and subtract the given offset).
* Find the mean average and shift the mesh to center on that value.
*/
void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const float offset) {
float sum = 0; float sum = 0;
int n = 0; int n = 0;
GRID_LOOP(x, y) GRID_LOOP(x, y)
@ -710,21 +694,25 @@ void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const float val
if (cflag) if (cflag)
GRID_LOOP(x, y) GRID_LOOP(x, y)
if (!isnan(z_values[x][y])) { if (!isnan(z_values[x][y])) {
z_values[x][y] -= mean + value; z_values[x][y] -= mean + offset;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
} }
} }
/**
* G29 P6 C<offset> : Shift Mesh Height by a uniform constant.
*/
void unified_bed_leveling::shift_mesh_height() { void unified_bed_leveling::shift_mesh_height() {
GRID_LOOP(x, y) GRID_LOOP(x, y)
if (!isnan(z_values[x][y])) { if (!isnan(z_values[x][y])) {
z_values[x][y] += g29_constant; z_values[x][y] += param.C_constant;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
} }
} }
#if HAS_BED_PROBE #if HAS_BED_PROBE
/** /**
* G29 P1 T<maptype> V<verbosity> : Probe Entire Mesh
* Probe all invalidated locations of the mesh that can be reached by the probe. * Probe all invalidated locations of the mesh that can be reached by the probe.
* This attempts to fill in locations closest to the nozzle's start location first. * This attempts to fill in locations closest to the nozzle's start location first.
*/ */
@ -739,7 +727,7 @@ void unified_bed_leveling::shift_mesh_height() {
mesh_index_pair best; mesh_index_pair best;
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::MESH_START)); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::MESH_START));
do { do {
if (do_ubl_mesh_map) display_map(g29_map_type); if (do_ubl_mesh_map) display_map(param.T_map_type);
const int point_num = (GRID_MAX_POINTS) - count + 1; const int point_num = (GRID_MAX_POINTS) - count + 1;
SERIAL_ECHOLNPAIR("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, "."); SERIAL_ECHOLNPAIR("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, ".");
@ -767,7 +755,7 @@ void unified_bed_leveling::shift_mesh_height() {
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::PROBE_START)); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::PROBE_START));
const float measured_z = probe.probe_at_point( const float measured_z = probe.probe_at_point(
best.meshpos(), best.meshpos(),
stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity
); );
z_values[best.pos.x][best.pos.y] = measured_z; z_values[best.pos.x][best.pos.y] = measured_z;
#if ENABLED(EXTENSIBLE_UI) #if ENABLED(EXTENSIBLE_UI)
@ -798,11 +786,20 @@ void unified_bed_leveling::shift_mesh_height() {
#endif // HAS_BED_PROBE #endif // HAS_BED_PROBE
void set_message_with_feedback(PGM_P const msg_P) {
#if HAS_LCD_MENU
ui.set_status_P(msg_P);
ui.quick_feedback();
#else
UNUSED(msg_P);
#endif
}
#if HAS_LCD_MENU #if HAS_LCD_MENU
typedef void (*clickFunc_t)(); typedef void (*clickFunc_t)();
bool click_and_hold(const clickFunc_t func=nullptr) { bool _click_and_hold(const clickFunc_t func=nullptr) {
if (ui.button_pressed()) { if (ui.button_pressed()) {
ui.quick_feedback(false); // Preserve button state for click-and-hold ui.quick_feedback(false); // Preserve button state for click-and-hold
const millis_t nxt = millis() + 1500UL; const millis_t nxt = millis() + 1500UL;
@ -834,7 +831,8 @@ void unified_bed_leveling::shift_mesh_height() {
float unified_bed_leveling::measure_point_with_encoder() { float unified_bed_leveling::measure_point_with_encoder() {
KEEPALIVE_STATE(PAUSED_FOR_USER); KEEPALIVE_STATE(PAUSED_FOR_USER);
move_z_with_encoder(0.01f); const float z_step = 0.01f;
move_z_with_encoder(z_step);
return current_position.z; return current_position.z;
} }
@ -866,7 +864,7 @@ void unified_bed_leveling::shift_mesh_height() {
const float thickness = ABS(z1 - z2); const float thickness = ABS(z1 - z2);
if (g29_verbose_level > 1) { if (param.V_verbosity > 1) {
SERIAL_ECHOPAIR_F("Business Card is ", thickness, 4); SERIAL_ECHOPAIR_F("Business Card is ", thickness, 4);
SERIAL_ECHOLNPGM("mm thick."); SERIAL_ECHOLNPGM("mm thick.");
} }
@ -876,6 +874,11 @@ void unified_bed_leveling::shift_mesh_height() {
return thickness; return thickness;
} }
/**
* G29 P2 : Manually Probe Remaining Mesh Points.
* Move to INVALID points and
* NOTE: Blocks the G-code queue and captures Marlin UI during use.
*/
void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const float &z_clearance, const float &thick, const bool do_ubl_mesh_map) { void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const float &z_clearance, const float &thick, const bool do_ubl_mesh_map) {
ui.capture(); ui.capture();
@ -907,7 +910,7 @@ void unified_bed_leveling::shift_mesh_height() {
KEEPALIVE_STATE(PAUSED_FOR_USER); KEEPALIVE_STATE(PAUSED_FOR_USER);
ui.capture(); ui.capture();
if (do_ubl_mesh_map) display_map(g29_map_type); // show user where we're probing if (do_ubl_mesh_map) display_map(param.T_map_type); // Show user where we're probing
if (parser.seen('B')) { if (parser.seen('B')) {
SERIAL_ECHOPGM_P(GET_TEXT(MSG_UBL_BC_INSERT)); SERIAL_ECHOPGM_P(GET_TEXT(MSG_UBL_BC_INSERT));
@ -918,45 +921,38 @@ void unified_bed_leveling::shift_mesh_height() {
LCD_MESSAGEPGM(MSG_UBL_BC_INSERT2); LCD_MESSAGEPGM(MSG_UBL_BC_INSERT2);
} }
const float z_step = 0.01f; // existing behavior: 0.01mm per click, occasionally step const float z_step = 0.01f; // 0.01mm per encoder tick, occasionally step
//const float z_step = planner.steps_to_mm[Z_AXIS]; // approx one step each click
move_z_with_encoder(z_step); move_z_with_encoder(z_step);
if (click_and_hold()) { if (_click_and_hold([]{
SERIAL_ECHOLNPGM("\nMesh only partially populated."); SERIAL_ECHOLNPGM("\nMesh only partially populated.");
do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE);
return restore_ubl_active_state_and_leave(); })) return restore_ubl_active_state_and_leave();
}
// Store the Z position minus the shim height
z_values[lpos.x][lpos.y] = current_position.z - thick; z_values[lpos.x][lpos.y] = current_position.z - thick;
// Tell the external UI to update
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y]));
if (g29_verbose_level > 2) if (param.V_verbosity > 2)
SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[lpos.x][lpos.y], 6); SERIAL_ECHOLNPAIR_F("Mesh Point Measured at: ", z_values[lpos.x][lpos.y], 6);
SERIAL_FLUSH(); // Prevent host M105 buffer overrun. SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
} while (location.valid()); } while (location.valid());
if (do_ubl_mesh_map) display_map(g29_map_type); // show user where we're probing if (do_ubl_mesh_map) display_map(param.T_map_type); // show user where we're probing
restore_ubl_active_state_and_leave(); restore_ubl_active_state_and_leave();
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE);
} }
inline void set_message_with_feedback(PGM_P const msg_P) { /**
ui.set_status_P(msg_P); * G29 P4 : Mesh Fine-Tuning. Go to point(s) and adjust values with the LCD.
ui.quick_feedback(); * NOTE: Blocks the G-code queue and captures Marlin UI during use.
} */
void abort_fine_tune() {
ui.return_to_status();
do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES);
set_message_with_feedback(GET_TEXT(MSG_EDITING_STOPPED));
}
void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) { void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) {
if (!parser.seen('R')) // fine_tune_mesh() is special. If no repetition count flag is specified if (!parser.seen('R')) // fine_tune_mesh() is special. If no repetition count flag is specified
g29_repetition_cnt = 1; // do exactly one mesh location. Otherwise use what the parser decided. param.R_repetition = 1; // do exactly one mesh location. Otherwise use what the parser decided.
#if ENABLED(UBL_MESH_EDIT_MOVES_Z) #if ENABLED(UBL_MESH_EDIT_MOVES_Z)
const float h_offset = parser.seenval('H') ? parser.value_linear_units() : MANUAL_PROBE_START_Z; const float h_offset = parser.seenval('H') ? parser.value_linear_units() : MANUAL_PROBE_START_Z;
@ -984,7 +980,7 @@ void unified_bed_leveling::shift_mesh_height() {
const xy_int8_t &lpos = location.pos; const xy_int8_t &lpos = location.pos;
#if IS_TFTGLCD_PANEL #if IS_TFTGLCD_PANEL
lcd_mesh_edit_setup(0); // Change current screen before calling ui.ubl_plot ui.ubl_mesh_edit_start(0); // Change current screen before calling ui.ubl_plot
safe_delay(50); safe_delay(50);
#endif #endif
@ -1009,7 +1005,7 @@ void unified_bed_leveling::shift_mesh_height() {
KEEPALIVE_STATE(PAUSED_FOR_USER); KEEPALIVE_STATE(PAUSED_FOR_USER);
if (do_ubl_mesh_map) display_map(g29_map_type); // Display the current point if (do_ubl_mesh_map) display_map(param.T_map_type); // Display the current point
#if IS_TFTGLCD_PANEL #if IS_TFTGLCD_PANEL
ui.ubl_plot(lpos.x, lpos.y); // update plot screen ui.ubl_plot(lpos.x, lpos.y); // update plot screen
@ -1021,13 +1017,13 @@ void unified_bed_leveling::shift_mesh_height() {
if (isnan(new_z)) new_z = 0; // Invalid points begin at 0 if (isnan(new_z)) new_z = 0; // Invalid points begin at 0
new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place
lcd_mesh_edit_setup(new_z); ui.ubl_mesh_edit_start(new_z);
SET_SOFT_ENDSTOP_LOOSE(true); SET_SOFT_ENDSTOP_LOOSE(true);
do { do {
idle(); idle();
new_z = lcd_mesh_edit(); new_z = ui.ubl_mesh_value();
TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset + new_z)); // Move the nozzle as the point is edited TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset + new_z)); // Move the nozzle as the point is edited
SERIAL_FLUSH(); // Prevent host M105 buffer overrun. SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
} while (!ui.button_pressed()); } while (!ui.button_pressed());
@ -1036,17 +1032,27 @@ void unified_bed_leveling::shift_mesh_height() {
if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status
if (click_and_hold(abort_fine_tune)) break; // Button held down? Abort editing // Button held down? Abort editing
if (_click_and_hold([]{
ui.return_to_status();
do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES);
set_message_with_feedback(GET_TEXT(MSG_EDITING_STOPPED));
})) break;
// TODO: Disable leveling here so the Z value becomes the 'native' Z value.
z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value
// TODO: Re-enable leveling here so Z is correctly based on the updated mesh.
TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z)); TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z));
serial_delay(20); // No switch noise serial_delay(20); // No switch noise
ui.refresh(); ui.refresh();
} while (lpos.x >= 0 && --g29_repetition_cnt > 0); } while (lpos.x >= 0 && --param.R_repetition > 0);
if (do_ubl_mesh_map) display_map(g29_map_type); if (do_ubl_mesh_map) display_map(param.T_map_type);
restore_ubl_active_state_and_leave(); restore_ubl_active_state_and_leave();
do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES); do_blocking_move_to_xy_z(pos, Z_CLEARANCE_BETWEEN_PROBES);
@ -1062,25 +1068,28 @@ void unified_bed_leveling::shift_mesh_height() {
#endif // HAS_LCD_MENU #endif // HAS_LCD_MENU
bool unified_bed_leveling::g29_parameter_parsing() { /**
* Parse and validate most G29 parameters, store for use by G29 functions.
*/
bool unified_bed_leveling::G29_parse_parameters() {
bool err_flag = false; bool err_flag = false;
TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_DOING_G29))); set_message_with_feedback(GET_TEXT(MSG_UBL_DOING_G29));
g29_constant = 0; param.C_constant = 0;
g29_repetition_cnt = 0; param.R_repetition = 0;
if (parser.seen('R')) { if (parser.seen('R')) {
g29_repetition_cnt = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS; param.R_repetition = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS;
NOMORE(g29_repetition_cnt, GRID_MAX_POINTS); NOMORE(param.R_repetition, GRID_MAX_POINTS);
if (g29_repetition_cnt < 1) { if (param.R_repetition < 1) {
SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n"); SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n");
return UBL_ERR; return UBL_ERR;
} }
} }
g29_verbose_level = parser.seen('V') ? parser.value_int() : 0; param.V_verbosity = parser.seen('V') ? parser.value_int() : 0;
if (!WITHIN(g29_verbose_level, 0, 4)) { if (!WITHIN(param.V_verbosity, 0, 4)) {
SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n"); SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n");
err_flag = true; err_flag = true;
} }
@ -1095,8 +1104,8 @@ bool unified_bed_leveling::g29_parameter_parsing() {
else else
#endif #endif
{ {
g29_phase_value = pv; param.P_phase = pv;
if (!WITHIN(g29_phase_value, 0, 6)) { if (!WITHIN(param.P_phase, 0, 6)) {
SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n");
err_flag = true; err_flag = true;
} }
@ -1105,8 +1114,8 @@ bool unified_bed_leveling::g29_parameter_parsing() {
if (parser.seen('J')) { if (parser.seen('J')) {
#if HAS_BED_PROBE #if HAS_BED_PROBE
g29_grid_size = parser.has_value() ? parser.value_int() : 0; param.grid_size = parser.has_value() ? parser.value_int() : 0;
if (g29_grid_size && !WITHIN(g29_grid_size, 2, 9)) { if (param.grid_size && !WITHIN(param.grid_size, 2, 9)) {
SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n"); SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n");
err_flag = true; err_flag = true;
} }
@ -1116,12 +1125,12 @@ bool unified_bed_leveling::g29_parameter_parsing() {
#endif #endif
} }
xy_seen.x = parser.seenval('X'); param.XY_seen.x = parser.seenval('X');
float sx = xy_seen.x ? parser.value_float() : current_position.x; float sx = param.XY_seen.x ? parser.value_float() : current_position.x;
xy_seen.y = parser.seenval('Y'); param.XY_seen.y = parser.seenval('Y');
float sy = xy_seen.y ? parser.value_float() : current_position.y; float sy = param.XY_seen.y ? parser.value_float() : current_position.y;
if (xy_seen.x != xy_seen.y) { if (param.XY_seen.x != param.XY_seen.y) {
SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n");
err_flag = true; err_flag = true;
} }
@ -1132,7 +1141,7 @@ bool unified_bed_leveling::g29_parameter_parsing() {
if (err_flag) return UBL_ERR; if (err_flag) return UBL_ERR;
g29_pos.set(sx, sy); param.XY_pos.set(sx, sy);
/** /**
* Activate or deactivate UBL * Activate or deactivate UBL
@ -1154,8 +1163,8 @@ bool unified_bed_leveling::g29_parameter_parsing() {
} }
// Set global 'C' flag and its value // Set global 'C' flag and its value
if ((g29_c_flag = parser.seen('C'))) if ((param.C_seen = parser.seen('C')))
g29_constant = parser.value_float(); param.C_constant = parser.value_float();
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
if (parser.seenval('F')) { if (parser.seenval('F')) {
@ -1168,8 +1177,8 @@ bool unified_bed_leveling::g29_parameter_parsing() {
} }
#endif #endif
g29_map_type = parser.intval('T'); param.T_map_type = parser.intval('T');
if (!WITHIN(g29_map_type, 0, 2)) { if (!WITHIN(param.T_map_type, 0, 2)) {
SERIAL_ECHOLNPGM("Invalid map type.\n"); SERIAL_ECHOLNPGM("Invalid map type.\n");
return UBL_ERR; return UBL_ERR;
} }
@ -1187,7 +1196,7 @@ void unified_bed_leveling::save_ubl_active_state_and_disable() {
ubl_state_recursion_chk++; ubl_state_recursion_chk++;
if (ubl_state_recursion_chk != 1) { if (ubl_state_recursion_chk != 1) {
SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row.");
TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_SAVE_ERROR))); set_message_with_feedback(GET_TEXT(MSG_UBL_SAVE_ERROR));
return; return;
} }
#endif #endif
@ -1200,7 +1209,7 @@ void unified_bed_leveling::restore_ubl_active_state_and_leave() {
#if ENABLED(UBL_DEVEL_DEBUGGING) #if ENABLED(UBL_DEVEL_DEBUGGING)
if (--ubl_state_recursion_chk) { if (--ubl_state_recursion_chk) {
SERIAL_ECHOLNPGM("restore_ubl_active_state_and_leave() called too many times."); SERIAL_ECHOLNPGM("restore_ubl_active_state_and_leave() called too many times.");
TERN_(HAS_LCD_MENU, set_message_with_feedback(GET_TEXT(MSG_UBL_RESTORE_ERROR))); set_message_with_feedback(GET_TEXT(MSG_UBL_RESTORE_ERROR));
return; return;
} }
#endif #endif
@ -1411,8 +1420,8 @@ void unified_bed_leveling::smart_fill_mesh() {
void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) { void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) {
const float x_min = probe.min_x(), x_max = probe.max_x(), const float x_min = probe.min_x(), x_max = probe.max_x(),
y_min = probe.min_y(), y_max = probe.max_y(), y_min = probe.min_y(), y_max = probe.max_y(),
dx = (x_max - x_min) / (g29_grid_size - 1), dx = (x_max - x_min) / (param.grid_size - 1),
dy = (y_max - y_min) / (g29_grid_size - 1); dy = (y_max - y_min) / (param.grid_size - 1);
xy_float_t points[3]; xy_float_t points[3];
probe.get_three_points(points); probe.get_three_points(points);
@ -1431,7 +1440,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOLNPGM("Tilting mesh (1/3)"); SERIAL_ECHOLNPGM("Tilting mesh (1/3)");
TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 1/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 1/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[0], PROBE_PT_RAISE, g29_verbose_level); measured_z = probe.probe_at_point(points[0], PROBE_PT_RAISE, param.V_verbosity);
if (isnan(measured_z)) if (isnan(measured_z))
abort_flag = true; abort_flag = true;
else { else {
@ -1439,7 +1448,7 @@ void unified_bed_leveling::smart_fill_mesh() {
#ifdef VALIDATE_MESH_TILT #ifdef VALIDATE_MESH_TILT
z1 = measured_z; z1 = measured_z;
#endif #endif
if (g29_verbose_level > 3) { if (param.V_verbosity > 3) {
serial_spaces(16); serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
} }
@ -1450,7 +1459,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOLNPGM("Tilting mesh (2/3)"); SERIAL_ECHOLNPGM("Tilting mesh (2/3)");
TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 2/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 2/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[1], PROBE_PT_RAISE, g29_verbose_level); measured_z = probe.probe_at_point(points[1], PROBE_PT_RAISE, param.V_verbosity);
#ifdef VALIDATE_MESH_TILT #ifdef VALIDATE_MESH_TILT
z2 = measured_z; z2 = measured_z;
#endif #endif
@ -1458,7 +1467,7 @@ void unified_bed_leveling::smart_fill_mesh() {
abort_flag = true; abort_flag = true;
else { else {
measured_z -= get_z_correction(points[1]); measured_z -= get_z_correction(points[1]);
if (g29_verbose_level > 3) { if (param.V_verbosity > 3) {
serial_spaces(16); serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
} }
@ -1470,7 +1479,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOLNPGM("Tilting mesh (3/3)"); SERIAL_ECHOLNPGM("Tilting mesh (3/3)");
TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 3/3"), GET_TEXT(MSG_LCD_TILTING_MESH))); TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " 3/3"), GET_TEXT(MSG_LCD_TILTING_MESH)));
measured_z = probe.probe_at_point(points[2], PROBE_PT_STOW, g29_verbose_level); measured_z = probe.probe_at_point(points[2], PROBE_PT_STOW, param.V_verbosity);
#ifdef VALIDATE_MESH_TILT #ifdef VALIDATE_MESH_TILT
z3 = measured_z; z3 = measured_z;
#endif #endif
@ -1478,7 +1487,7 @@ void unified_bed_leveling::smart_fill_mesh() {
abort_flag = true; abort_flag = true;
else { else {
measured_z -= get_z_correction(points[2]); measured_z -= get_z_correction(points[2]);
if (g29_verbose_level > 3) { if (param.V_verbosity > 3) {
serial_spaces(16); serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
} }
@ -1498,20 +1507,20 @@ void unified_bed_leveling::smart_fill_mesh() {
bool zig_zag = false; bool zig_zag = false;
const uint16_t total_points = sq(g29_grid_size); const uint16_t total_points = sq(param.grid_size);
uint16_t point_num = 1; uint16_t point_num = 1;
xy_pos_t rpos; xy_pos_t rpos;
LOOP_L_N(ix, g29_grid_size) { LOOP_L_N(ix, param.grid_size) {
rpos.x = x_min + ix * dx; rpos.x = x_min + ix * dx;
LOOP_L_N(iy, g29_grid_size) { LOOP_L_N(iy, param.grid_size) {
rpos.y = y_min + dy * (zig_zag ? g29_grid_size - 1 - iy : iy); rpos.y = y_min + dy * (zig_zag ? param.grid_size - 1 - iy : iy);
if (!abort_flag) { if (!abort_flag) {
SERIAL_ECHOLNPAIR("Tilting mesh point ", point_num, "/", total_points, "\n"); SERIAL_ECHOLNPAIR("Tilting mesh point ", point_num, "/", total_points, "\n");
TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points)); TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_LCD_TILTING_MESH), point_num, total_points));
measured_z = probe.probe_at_point(rpos, parser.seen('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, g29_verbose_level); // TODO: Needs error handling measured_z = probe.probe_at_point(rpos, parser.seen('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); // TODO: Needs error handling
abort_flag = isnan(measured_z); abort_flag = isnan(measured_z);
@ -1534,7 +1543,7 @@ void unified_bed_leveling::smart_fill_mesh() {
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_F(" final >>>---> ", measured_z, 7); if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_F(" final >>>---> ", measured_z, 7);
if (g29_verbose_level > 3) { if (param.V_verbosity > 3) {
serial_spaces(16); serial_spaces(16);
SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z); SERIAL_ECHOLNPAIR("Corrected_Z=", measured_z);
} }
@ -1557,7 +1566,7 @@ void unified_bed_leveling::smart_fill_mesh() {
vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal(); vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal();
if (g29_verbose_level > 2) { if (param.V_verbosity > 2) {
SERIAL_ECHOPAIR_F("bed plane normal = [", normal.x, 7); SERIAL_ECHOPAIR_F("bed plane normal = [", normal.x, 7);
SERIAL_CHAR(','); SERIAL_CHAR(',');
SERIAL_ECHO_F(normal.y, 7); SERIAL_ECHO_F(normal.y, 7);
@ -1721,7 +1730,7 @@ void unified_bed_leveling::smart_fill_mesh() {
SERIAL_ECHOLNPAIR_F("Fade Height M420 Z", planner.z_fade_height, 4); SERIAL_ECHOLNPAIR_F("Fade Height M420 Z", planner.z_fade_height, 4);
#endif #endif
adjust_mesh_to_mean(g29_c_flag, g29_constant); adjust_mesh_to_mean(param.C_seen, param.C_constant);
#if HAS_BED_PROBE #if HAS_BED_PROBE
SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe.offset.z, 7); SERIAL_ECHOLNPAIR_F("Probe Offset M851 Z", probe.offset.z, 7);
@ -1819,17 +1828,17 @@ void unified_bed_leveling::smart_fill_mesh() {
return; return;
} }
if (!parser.has_value() || !WITHIN(g29_storage_slot, 0, a - 1)) { if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) {
SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1); SERIAL_ECHOLNPAIR("?Invalid storage slot.\n?Use 0 to ", a - 1);
return; return;
} }
g29_storage_slot = parser.value_int(); param.KLS_storage_slot = parser.value_int();
float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
settings.load_mesh(g29_storage_slot, &tmp_z_values); settings.load_mesh(param.KLS_storage_slot, &tmp_z_values);
SERIAL_ECHOLNPAIR("Subtracting mesh in slot ", g29_storage_slot, " from current mesh."); SERIAL_ECHOLNPAIR("Subtracting mesh in slot ", param.KLS_storage_slot, " from current mesh.");
GRID_LOOP(x, y) { GRID_LOOP(x, y) {
z_values[x][y] -= tmp_z_values[x][y]; z_values[x][y] -= tmp_z_values[x][y];

View File

@ -156,16 +156,16 @@ void GcodeSuite::M420() {
GRID_LOOP(x, y) mesh_sum += Z_VALUES(x, y); GRID_LOOP(x, y) mesh_sum += Z_VALUES(x, y);
const float zmean = mesh_sum / float(GRID_MAX_POINTS); const float zmean = mesh_sum / float(GRID_MAX_POINTS);
#else #else // midrange
// Find the low and high mesh values // Find the low and high mesh values.
float lo_val = 100, hi_val = -100; float lo_val = 100, hi_val = -100;
GRID_LOOP(x, y) { GRID_LOOP(x, y) {
const float z = Z_VALUES(x, y); const float z = Z_VALUES(x, y);
NOMORE(lo_val, z); NOMORE(lo_val, z);
NOLESS(hi_val, z); NOLESS(hi_val, z);
} }
// Take the mean of the lowest and highest // Get the midrange plus C value. (The median may be better.)
const float zmean = (lo_val + hi_val) / 2.0 + cval; const float zmean = (lo_val + hi_val) / 2.0 + cval;
#endif #endif

View File

@ -85,11 +85,6 @@
typedef void (*screenFunc_t)(); typedef void (*screenFunc_t)();
typedef void (*menuAction_t)(); typedef void (*menuAction_t)();
#if ENABLED(AUTO_BED_LEVELING_UBL)
void lcd_mesh_edit_setup(const float &initial);
float lcd_mesh_edit();
#endif
#endif // HAS_LCD_MENU #endif // HAS_LCD_MENU
#endif // HAS_WIRED_LCD #endif // HAS_WIRED_LCD
@ -488,6 +483,11 @@ public:
static void ubl_plot(const uint8_t x_plot, const uint8_t y_plot); static void ubl_plot(const uint8_t x_plot, const uint8_t y_plot);
#endif #endif
#if ENABLED(AUTO_BED_LEVELING_UBL)
static void ubl_mesh_edit_start(const float &initial);
static float ubl_mesh_value();
#endif
static void draw_select_screen_prompt(PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr); static void draw_select_screen_prompt(PGM_P const pref, const char * const string=nullptr, PGM_P const suff=nullptr);
#elif HAS_WIRED_LCD #elif HAS_WIRED_LCD

View File

@ -56,12 +56,24 @@ inline float rounded_mesh_value() {
return float(rounded - (rounded % 5L)) / 1000; return float(rounded - (rounded % 5L)) / 1000;
} }
static void _lcd_mesh_fine_tune(PGM_P const msg) { /**
* This screen displays the temporary mesh value and updates it based on encoder
* movement. While this screen is active ubl.fine_tune_mesh sits in a loop getting
* the current value via ubl_mesh_value, moves the Z axis, and updates the mesh
* value until the encoder button is pressed.
*
* - Update the 'mesh_edit_accumulator' from encoder rotation
* - Draw the mesh value (with draw_edit_screen)
* - Draw the graphical overlay, if enabled.
* - Update the 'refresh' state according to the display type
*/
void _lcd_mesh_fine_tune(PGM_P const msg) {
constexpr float mesh_edit_step = 1.0f / 200.0f;
ui.defer_status_screen(); ui.defer_status_screen();
if (ubl.encoder_diff) { if (ubl.encoder_diff) {
mesh_edit_accumulator += TERN(IS_TFTGLCD_PANEL, mesh_edit_accumulator += TERN(IS_TFTGLCD_PANEL,
ubl.encoder_diff * 0.005f / ENCODER_PULSES_PER_STEP, ubl.encoder_diff * mesh_edit_step / ENCODER_PULSES_PER_STEP,
ubl.encoder_diff > 0 ? 0.005f : -0.005f ubl.encoder_diff > 0 ? mesh_edit_step : -mesh_edit_step
); );
ubl.encoder_diff = 0; ubl.encoder_diff = 0;
IF_DISABLED(IS_TFTGLCD_PANEL, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT)); IF_DISABLED(IS_TFTGLCD_PANEL, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
@ -77,29 +89,19 @@ static void _lcd_mesh_fine_tune(PGM_P const msg) {
} }
// //
// Called external to the menu system to acquire the result of an edit. // Init mesh editing and go to the fine tuning screen (ubl.fine_tune_mesh)
// To capture encoder events UBL will also call ui.capture and ui.release.
// //
float lcd_mesh_edit() { return rounded_mesh_value(); } void MarlinUI::ubl_mesh_edit_start(const float &initial) {
TERN_(HAS_GRAPHICAL_TFT, clear_lcd());
void lcd_mesh_edit_setup(const float &initial) {
TERN_(HAS_GRAPHICAL_TFT, ui.clear_lcd());
mesh_edit_accumulator = initial; mesh_edit_accumulator = initial;
ui.goto_screen([]{ _lcd_mesh_fine_tune(GET_TEXT(MSG_MESH_EDIT_Z)); }); goto_screen([]{ _lcd_mesh_fine_tune(GET_TEXT(MSG_MESH_EDIT_Z)); });
} }
void _lcd_z_offset_edit() { //
_lcd_mesh_fine_tune(GET_TEXT(MSG_UBL_Z_OFFSET)); // Get the mesh value within a Z adjustment loop (ubl.fine_tune_mesh)
} //
float MarlinUI::ubl_mesh_value() { return rounded_mesh_value(); }
float lcd_z_offset_edit() {
ui.goto_screen(_lcd_z_offset_edit);
return rounded_mesh_value();
}
void lcd_z_offset_edit_setup(const float &initial) {
mesh_edit_accumulator = initial;
ui.goto_screen(_lcd_z_offset_edit);
}
/** /**
* UBL Build Custom Mesh Command * UBL Build Custom Mesh Command