From e8701195e66f2d27ffe17fb514eae8173795aaf7 Mon Sep 17 00:00:00 2001 From: Georgiy Bondarenko <69736697+nehilo@users.noreply.github.com> Date: Thu, 4 Mar 2021 22:54:23 +0500 Subject: Initial commit --- Marlin/src/gcode/bedlevel/G26.cpp | 872 +++++++++++++++++++++++++++++++ Marlin/src/gcode/bedlevel/G35.cpp | 167 ++++++ Marlin/src/gcode/bedlevel/G42.cpp | 73 +++ Marlin/src/gcode/bedlevel/M420.cpp | 245 +++++++++ Marlin/src/gcode/bedlevel/abl/G29.cpp | 901 +++++++++++++++++++++++++++++++++ Marlin/src/gcode/bedlevel/abl/M421.cpp | 74 +++ Marlin/src/gcode/bedlevel/mbl/G29.cpp | 193 +++++++ Marlin/src/gcode/bedlevel/mbl/M421.cpp | 59 +++ Marlin/src/gcode/bedlevel/ubl/G29.cpp | 36 ++ Marlin/src/gcode/bedlevel/ubl/M421.cpp | 70 +++ 10 files changed, 2690 insertions(+) create mode 100644 Marlin/src/gcode/bedlevel/G26.cpp create mode 100644 Marlin/src/gcode/bedlevel/G35.cpp create mode 100644 Marlin/src/gcode/bedlevel/G42.cpp create mode 100644 Marlin/src/gcode/bedlevel/M420.cpp create mode 100644 Marlin/src/gcode/bedlevel/abl/G29.cpp create mode 100644 Marlin/src/gcode/bedlevel/abl/M421.cpp create mode 100644 Marlin/src/gcode/bedlevel/mbl/G29.cpp create mode 100644 Marlin/src/gcode/bedlevel/mbl/M421.cpp create mode 100644 Marlin/src/gcode/bedlevel/ubl/G29.cpp create mode 100644 Marlin/src/gcode/bedlevel/ubl/M421.cpp (limited to 'Marlin/src/gcode/bedlevel') diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp new file mode 100644 index 0000000..5a79aaa --- /dev/null +++ b/Marlin/src/gcode/bedlevel/G26.cpp @@ -0,0 +1,872 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * 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 behavior 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. + * + * I # Preset Heat the Nozzle and Bed based on a Material Preset (if material presets are defined). + * + * 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 + * un-drawn circle is still done. But the distance to the location for each circle has a + * random number of the specified size added to it. Specifying S50 will give an interesting + * deviation from the normal behavior 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. + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(G26_MESH_VALIDATION) + +#define G26_OK false +#define G26_ERR true + +#include "../../gcode/gcode.h" +#include "../../feature/bedlevel/bedlevel.h" + +#include "../../MarlinCore.h" +#include "../../module/planner.h" +#include "../../module/stepper.h" +#include "../../module/motion.h" +#include "../../module/tool_change.h" +#include "../../module/temperature.h" +#include "../../lcd/marlinui.h" + +#define EXTRUSION_MULTIPLIER 1.0 +#define PRIME_LENGTH 10.0 +#define OOZE_AMOUNT 0.3 + +#define INTERSECTION_CIRCLE_RADIUS 5 +#define CROSSHAIRS_SIZE 3 + +#ifndef G26_RETRACT_MULTIPLIER + #define G26_RETRACT_MULTIPLIER 1.0 // x 1mm +#endif + +#ifndef G26_XY_FEEDRATE + #define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE() / 3.0) +#endif + +#ifndef G26_XY_FEEDRATE_TRAVEL + #define G26_XY_FEEDRATE_TRAVEL (PLANNER_XY_FEEDRATE() / 1.5) +#endif + +#if CROSSHAIRS_SIZE >= INTERSECTION_CIRCLE_RADIUS + #error "CROSSHAIRS_SIZE must be less than INTERSECTION_CIRCLE_RADIUS." +#endif + +#define G26_OK false +#define G26_ERR true + +#if ENABLED(ARC_SUPPORT) + void plan_arc(const xyze_pos_t&, const ab_float_t&, const bool, const uint8_t); +#endif + +constexpr float g26_e_axis_feedrate = 0.025; + +static MeshFlags circle_flags, horizontal_mesh_line_flags, vertical_mesh_line_flags; +float g26_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 g26_extrusion_multiplier, + g26_retraction_multiplier, + g26_layer_height, + g26_prime_length; + +xy_pos_t g26_xy_pos; // = { 0, 0 } + +int16_t g26_bed_temp, + g26_hotend_temp; + +int8_t g26_prime_flag; + +#if HAS_LCD_MENU + + /** + * If the LCD is clicked, cancel, wait for release, return true + */ + bool user_canceled() { + if (!ui.button_pressed()) return false; // Return if the button isn't pressed + ui.set_status_P(GET_TEXT(MSG_G26_CANCELED), 99); + TERN_(HAS_LCD_MENU, ui.quick_feedback()); + ui.wait_for_release(); + return true; + } + +#endif + +mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { + float closest = 99999.99; + mesh_index_pair out_point; + + out_point.pos = -1; + + GRID_LOOP(i, j) { + if (!circle_flags.marked(i, j)) { + // We found a circle that needs to be printed + const xy_pos_t m = { _GET_MESH_X(i), _GET_MESH_Y(j) }; + + // Get the distance to this intersection + float f = (pos - m).magnitude(); + + // 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 += (g26_xy_pos - m).magnitude() / 15.0f; + + // Add the specified amount of Random Noise to our search + if (g26_random_deviation > 1.0) f += random(0.0, g26_random_deviation); + + if (f < closest) { + closest = f; // Found a closer un-printed location + out_point.pos.set(i, j); // Save its data + out_point.distance = closest; + } + } + } + circle_flags.mark(out_point); // Mark this location as done. + return out_point; +} + +void move_to(const float &rx, const float &ry, const float &z, const float &e_delta) { + static float last_z = -999.99; + + const xy_pos_t dest = { rx, ry }; + + const bool has_xy_component = dest != current_position; // Check if X or Y is involved in the movement. + const bool has_e_component = e_delta != 0.0; + + destination = current_position; + + if (z != last_z) { + last_z = destination.z = z; + const feedRate_t fr_mm_s = planner.settings.max_feedrate_mm_s[Z_AXIS] * 0.5f; // Use half of the Z_AXIS max feed rate + prepare_internal_move_to_destination(fr_mm_s); + } + + // If X or Y in combination with E is involved do a 'normal' move. + // If X or Y with no E is involved do a 'fast' move + // Otherwise retract/recover/hop. + destination = dest; + destination.e += e_delta; + const feedRate_t fr_mm_s = has_xy_component + ? (has_e_component ? feedRate_t(G26_XY_FEEDRATE) : feedRate_t(G26_XY_FEEDRATE_TRAVEL)) + : planner.settings.max_feedrate_mm_s[E_AXIS] * 0.666f; + prepare_internal_move_to_destination(fr_mm_s); +} + +FORCE_INLINE void move_to(const xyz_pos_t &where, const float &de) { move_to(where.x, where.y, where.z, de); } + +void retract_filament(const xyz_pos_t &where) { + if (!g26_retracted) { // Only retract if we are not already retracted! + g26_retracted = true; + move_to(where, -1.0f * g26_retraction_multiplier); + } +} + +// TODO: Parameterize the Z lift with a define +void retract_lift_move(const xyz_pos_t &s) { + retract_filament(destination); + move_to(current_position.x, current_position.y, current_position.z + 0.5f, 0.0); // Z lift to minimize scraping + move_to(s.x, s.y, s.z + 0.5f, 0.0); // Get to the starting point with no extrusion while lifted +} + +void recover_filament(const xyz_pos_t &where) { + if (g26_retracted) { // Only un-retract if we are retracted. + move_to(where, 1.2f * 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 print_line_from_here_to_there(const xyz_pos_t &s, const xyz_pos_t &e) { + + // Distances to the start / end of the line + xy_float_t svec = current_position - s, evec = current_position - e; + + const float dist_start = HYPOT2(svec.x, svec.y), + dist_end = HYPOT2(evec.x, evec.y), + line_length = HYPOT(e.x - s.x, e.y - s.y); + + // 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 && (INTERSECTION_CIRCLE_RADIUS) < ABS(line_length)) + return print_line_from_here_to_there(e, s); + + // Decide whether to retract & lift + if (dist_start > 2.0) retract_lift_move(s); + + move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift + + const float e_pos_delta = line_length * g26_e_axis_feedrate * g26_extrusion_multiplier; + + recover_filament(destination); + move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion +} + +inline bool look_for_lines_to_connect() { + xyz_pos_t s, e; + s.z = e.z = g26_layer_height; + + GRID_LOOP(i, j) { + + if (TERN0(HAS_LCD_MENU, user_canceled())) return true; + + if (i < (GRID_MAX_POINTS_X)) { // Can't connect to anything farther to the right than GRID_MAX_POINTS_X. + // Already a half circle at the edge of the bed. + + if (circle_flags.marked(i, j) && circle_flags.marked(i + 1, j)) { // Test whether a leftward line can be done + if (!horizontal_mesh_line_flags.marked(i, j)) { + // Two circles need a horizontal line to connect them + s.x = _GET_MESH_X( i ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // right edge + e.x = _GET_MESH_X(i + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // left edge + + LIMIT(s.x, X_MIN_POS + 1, X_MAX_POS - 1); + s.y = e.y = constrain(_GET_MESH_Y(j), Y_MIN_POS + 1, Y_MAX_POS - 1); + LIMIT(e.x, X_MIN_POS + 1, X_MAX_POS - 1); + + if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y)) + print_line_from_here_to_there(s, e); + + horizontal_mesh_line_flags.mark(i, j); // Mark done, even if skipped + } + } + + if (j < (GRID_MAX_POINTS_Y)) { // Can't connect to anything further back than GRID_MAX_POINTS_Y. + // Already a half circle at the edge of the bed. + + if (circle_flags.marked(i, j) && circle_flags.marked(i, j + 1)) { // Test whether a downward line can be done + if (!vertical_mesh_line_flags.marked(i, j)) { + // Two circles that need a vertical line to connect them + s.y = _GET_MESH_Y( j ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // top edge + e.y = _GET_MESH_Y(j + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // bottom edge + + s.x = e.x = constrain(_GET_MESH_X(i), X_MIN_POS + 1, X_MAX_POS - 1); + LIMIT(s.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + LIMIT(e.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + + if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y)) + print_line_from_here_to_there(s, e); + + vertical_mesh_line_flags.mark(i, j); // Mark done, even if skipped + } + } + } + } + } + return false; +} + +/** + * Turn on the bed and nozzle heat and + * wait for them to get up to temperature. + */ +inline bool turn_on_heaters() { + + SERIAL_ECHOLNPGM("Waiting for heatup."); + + #if HAS_HEATED_BED + + if (g26_bed_temp > 25) { + #if HAS_WIRED_LCD + ui.set_status_P(GET_TEXT(MSG_G26_HEATING_BED), 99); + ui.quick_feedback(); + TERN_(HAS_LCD_MENU, ui.capture()); + #endif + thermalManager.setTargetBed(g26_bed_temp); + + // Wait for the temperature to stabilize + if (!thermalManager.wait_for_bed(true + #if G26_CLICK_CAN_CANCEL + , true + #endif + ) + ) return G26_ERR; + } + + #endif // HAS_HEATED_BED + + // Start heating the active nozzle + #if HAS_WIRED_LCD + ui.set_status_P(GET_TEXT(MSG_G26_HEATING_NOZZLE), 99); + ui.quick_feedback(); + #endif + thermalManager.setTargetHotend(g26_hotend_temp, active_extruder); + + // Wait for the temperature to stabilize + if (!thermalManager.wait_for_hotend(active_extruder, true + #if G26_CLICK_CAN_CANCEL + , true + #endif + )) return G26_ERR; + + #if HAS_WIRED_LCD + ui.reset_status(); + ui.quick_feedback(); + #endif + + return G26_OK; +} + +/** + * Prime the nozzle if needed. Return true on error. + */ +inline bool prime_nozzle() { + + const feedRate_t fr_slow_e = planner.settings.max_feedrate_mm_s[E_AXIS] / 15.0f; + #if HAS_LCD_MENU && !HAS_TOUCH_BUTTONS // ui.button_pressed issue with touchscreen + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + float Total_Prime = 0.0; + #endif + + if (g26_prime_flag == -1) { // The user wants to control how much filament gets purged + ui.capture(); + ui.set_status_P(GET_TEXT(MSG_G26_MANUAL_PRIME), 99); + ui.chirp(); + + destination = current_position; + + recover_filament(destination); // Make sure G26 doesn't think the filament is retracted(). + + while (!ui.button_pressed()) { + ui.chirp(); + destination.e += 0.25; + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + Total_Prime += 0.25; + if (Total_Prime >= EXTRUDE_MAXLENGTH) { + ui.release(); + return G26_ERR; + } + #endif + prepare_internal_move_to_destination(fr_slow_e); + destination = current_position; + planner.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'. + } + + ui.wait_for_release(); + + ui.set_status_P(GET_TEXT(MSG_G26_PRIME_DONE), 99); + ui.quick_feedback(); + ui.release(); + } + else + #endif + { + #if HAS_WIRED_LCD + ui.set_status_P(GET_TEXT(MSG_G26_FIXED_LENGTH), 99); + ui.quick_feedback(); + #endif + destination = current_position; + destination.e += g26_prime_length; + prepare_internal_move_to_destination(fr_slow_e); + destination.e -= g26_prime_length; + retract_filament(destination); + } + + return G26_OK; +} + +/** + * G26: Mesh Validation Pattern generation. + * + * Used to interactively edit the mesh by placing the + * nozzle in a problem area and doing a G29 P4 R command. + * + * Parameters: + * + * B Bed Temperature + * C Continue from the Closest mesh point + * D Disable leveling before starting + * F Filament diameter + * H Hotend Temperature + * K Keep heaters on when completed + * L Layer Height + * O Ooze extrusion length + * P Prime length + * Q Retraction multiplier + * R Repetitions (number of grid points) + * S Nozzle Size (diameter) in mm + * T Tool index to change to, if included + * U Random deviation (50 if no value given) + * X X position + * Y Y position + */ +void GcodeSuite::G26() { + SERIAL_ECHOLNPGM("G26 starting..."); + + // Don't allow Mesh Validation without homing first, + // or if the parameter parsing did not go OK, abort + if (homing_needed_error()) return; + + // Change the tool first, if specified + if (parser.seenval('T')) tool_change(parser.value_int()); + + g26_extrusion_multiplier = EXTRUSION_MULTIPLIER; + g26_retraction_multiplier = G26_RETRACT_MULTIPLIER; + g26_layer_height = MESH_TEST_LAYER_HEIGHT; + g26_prime_length = PRIME_LENGTH; + g26_bed_temp = MESH_TEST_BED_TEMP; + g26_hotend_temp = MESH_TEST_HOTEND_TEMP; + g26_prime_flag = 0; + + float g26_nozzle = MESH_TEST_NOZZLE_SIZE, + g26_filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA, + g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT); + + bool g26_continue_with_closest = parser.boolval('C'), + g26_keep_heaters_on = parser.boolval('K'); + + // Accept 'I' if temperature presets are defined + #if PREHEAT_COUNT + const uint8_t preset_index = parser.seenval('I') ? _MIN(parser.value_byte(), PREHEAT_COUNT - 1) + 1 : 0; + #endif + + #if HAS_HEATED_BED + + // Get a temperature from 'I' or 'B' + int16_t bedtemp = 0; + + // Use the 'I' index if temperature presets are defined + #if PREHEAT_COUNT + if (preset_index) bedtemp = ui.material_preset[preset_index - 1].bed_temp; + #endif + + // Look for 'B' Bed Temperature + if (parser.seenval('B')) bedtemp = parser.value_celsius(); + + if (bedtemp) { + if (!WITHIN(bedtemp, 40, BED_MAX_TARGET)) { + SERIAL_ECHOLNPAIR("?Specified bed temperature not plausible (40-", int(BED_MAX_TARGET), "C)."); + return; + } + g26_bed_temp = bedtemp; + } + + #endif // HAS_HEATED_BED + + if (parser.seenval('L')) { + g26_layer_height = parser.value_linear_units(); + if (!WITHIN(g26_layer_height, 0.0, 2.0)) { + SERIAL_ECHOLNPGM("?Specified layer height not plausible."); + return; + } + } + + if (parser.seen('Q')) { + if (parser.has_value()) { + g26_retraction_multiplier = parser.value_float(); + if (!WITHIN(g26_retraction_multiplier, 0.05, 15.0)) { + SERIAL_ECHOLNPGM("?Specified Retraction Multiplier not plausible."); + return; + } + } + else { + SERIAL_ECHOLNPGM("?Retraction Multiplier must be specified."); + return; + } + } + + if (parser.seenval('S')) { + g26_nozzle = parser.value_float(); + if (!WITHIN(g26_nozzle, 0.1, 2.0)) { + SERIAL_ECHOLNPGM("?Specified nozzle size not plausible."); + return; + } + } + + if (parser.seen('P')) { + if (!parser.has_value()) { + #if HAS_LCD_MENU + g26_prime_flag = -1; + #else + SERIAL_ECHOLNPGM("?Prime length must be specified when not using an LCD."); + return; + #endif + } + else { + g26_prime_flag++; + g26_prime_length = parser.value_linear_units(); + if (!WITHIN(g26_prime_length, 0.0, 25.0)) { + SERIAL_ECHOLNPGM("?Specified prime length not plausible."); + return; + } + } + } + + if (parser.seenval('F')) { + g26_filament_diameter = parser.value_linear_units(); + if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) { + SERIAL_ECHOLNPGM("?Specified filament size not plausible."); + return; + } + } + 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 + + // Get a temperature from 'I' or 'H' + int16_t noztemp = 0; + + // Accept 'I' if temperature presets are defined + #if PREHEAT_COUNT + if (preset_index) noztemp = ui.material_preset[preset_index - 1].hotend_temp; + #endif + + // Look for 'H' Hotend Temperature + if (parser.seenval('H')) noztemp = parser.value_celsius(); + + // If any preset or temperature was specified + if (noztemp) { + if (!WITHIN(noztemp, 165, (HEATER_0_MAXTEMP) - (HOTEND_OVERSHOOT))) { + SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible."); + return; + } + g26_hotend_temp = noztemp; + } + + // 'U' to Randomize and optionally set circle deviation + if (parser.seen('U')) { + randomSeed(millis()); + // This setting will persist for the next G26 + g26_random_deviation = parser.has_value() ? parser.value_float() : 50.0; + } + + // Get repeat from 'R', otherwise do one full circuit + int16_t g26_repeats; + #if HAS_LCD_MENU + g26_repeats = parser.intval('R', GRID_MAX_POINTS + 1); + #else + if (!parser.seen('R')) { + SERIAL_ECHOLNPGM("?(R)epeat must be specified when not using an LCD."); + return; + } + else + g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1; + #endif + if (g26_repeats < 1) { + SERIAL_ECHOLNPGM("?(R)epeat value not plausible; must be at least 1."); + return; + } + + // Set a position with 'X' and/or 'Y'. Default: current_position + g26_xy_pos.set(parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : current_position.x, + parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : current_position.y); + if (!position_is_reachable(g26_xy_pos)) { + SERIAL_ECHOLNPGM("?Specified X,Y coordinate out of bounds."); + return; + } + + /** + * Wait until all parameters are verified before altering the state! + */ + set_bed_leveling_enabled(!parser.seen('D')); + + do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); + + #if DISABLED(NO_VOLUMETRICS) + bool volumetric_was_enabled = parser.volumetric_enabled; + parser.volumetric_enabled = false; + planner.calculate_volumetric_multipliers(); + #endif + + if (turn_on_heaters() != G26_OK) goto LEAVE; + + current_position.e = 0.0; + sync_plan_position_e(); + + if (g26_prime_flag && prime_nozzle() != G26_OK) goto LEAVE; + + /** + * Bed is preheated + * + * Nozzle is at temperature + * + * Filament is primed! + * + * It's "Show Time" !!! + */ + + circle_flags.reset(); + horizontal_mesh_line_flags.reset(); + vertical_mesh_line_flags.reset(); + + // Move nozzle to the specified height for the first layer + destination = current_position; + destination.z = g26_layer_height; + move_to(destination, 0.0); + move_to(destination, g26_ooze_amount); + + TERN_(HAS_LCD_MENU, ui.capture()); + + #if DISABLED(ARC_SUPPORT) + + /** + * Pre-generate radius offset values at 30 degree intervals to reduce CPU load. + */ + #define A_INT 30 + #define _ANGS (360 / A_INT) + #define A_CNT (_ANGS / 2) + #define _IND(A) ((A + _ANGS * 8) % _ANGS) + #define _COS(A) (trig_table[_IND(A) % A_CNT] * (_IND(A) >= A_CNT ? -1 : 1)) + #define _SIN(A) (-_COS((A + A_CNT / 2) % _ANGS)) + #if A_CNT & 1 + #error "A_CNT must be a positive value. Please change A_INT." + #endif + float trig_table[A_CNT]; + LOOP_L_N(i, A_CNT) + trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT)); + + #endif // !ARC_SUPPORT + + mesh_index_pair location; + do { + // Find the nearest confluence + location = find_closest_circle_to_print(g26_continue_with_closest ? xy_pos_t(current_position) : g26_xy_pos); + + if (location.valid()) { + const xy_pos_t circle = _GET_MESH_POS(location.pos); + + // If this mesh location is outside the printable radius, skip it. + if (!position_is_reachable(circle)) continue; + + // Determine where to start and end the circle, + // which is always drawn counter-clockwise. + const xy_int8_t st = location; + const bool f = st.y == 0, + r = st.x >= GRID_MAX_POINTS_X - 1, + b = st.y >= GRID_MAX_POINTS_Y - 1; + + #if ENABLED(ARC_SUPPORT) + + #define ARC_LENGTH(quarters) (INTERSECTION_CIRCLE_RADIUS * M_PI * (quarters) / 2) + #define INTERSECTION_CIRCLE_DIAM ((INTERSECTION_CIRCLE_RADIUS) * 2) + + xy_float_t e = { circle.x + INTERSECTION_CIRCLE_RADIUS, circle.y }; + xyz_float_t s = e; + + // Figure out where to start and end the arc - we always print counterclockwise + float arc_length = ARC_LENGTH(4); + if (st.x == 0) { // left edge + if (!f) { s.x = circle.x; s.y -= INTERSECTION_CIRCLE_RADIUS; } + if (!b) { e.x = circle.x; e.y += INTERSECTION_CIRCLE_RADIUS; } + arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); + } + else if (r) { // right edge + if (b) s.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y); + else s.set(circle.x, circle.y + INTERSECTION_CIRCLE_RADIUS); + if (f) e.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y); + else e.set(circle.x, circle.y - (INTERSECTION_CIRCLE_RADIUS)); + arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); + } + else if (f) { + e.x -= INTERSECTION_CIRCLE_DIAM; + arc_length = ARC_LENGTH(2); + } + else if (b) { + s.x -= INTERSECTION_CIRCLE_DIAM; + arc_length = ARC_LENGTH(2); + } + + const ab_float_t arc_offset = circle - s; + const xy_float_t dist = current_position - s; // Distance from the start of the actual circle + const float dist_start = HYPOT2(dist.x, dist.y); + const xyze_pos_t endpoint = { + e.x, e.y, g26_layer_height, + current_position.e + (arc_length * g26_e_axis_feedrate * g26_extrusion_multiplier) + }; + + if (dist_start > 2.0) { + s.z = g26_layer_height + 0.5f; + retract_lift_move(s); + } + + s.z = g26_layer_height; + move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift + + recover_filament(destination); + + const feedRate_t old_feedrate = feedrate_mm_s; + feedrate_mm_s = PLANNER_XY_FEEDRATE() * 0.1f; + plan_arc(endpoint, arc_offset, false, 0); // Draw a counter-clockwise arc + feedrate_mm_s = old_feedrate; + destination = current_position; + + if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation + + #else // !ARC_SUPPORT + + int8_t start_ind = -2, end_ind = 9; // Assume a full circle (from 5:00 to 5:00) + if (st.x == 0) { // Left edge? Just right half. + start_ind = f ? 0 : -3; // 03:00 to 12:00 for front-left + end_ind = b ? 0 : 2; // 06:00 to 03:00 for back-left + } + else if (r) { // Right edge? Just left half. + start_ind = b ? 6 : 3; // 12:00 to 09:00 for front-right + end_ind = f ? 5 : 8; // 09:00 to 06:00 for back-right + } + else if (f) { // Front edge? Just back half. + start_ind = 0; // 03:00 + end_ind = 5; // 09:00 + } + else if (b) { // Back edge? Just front half. + start_ind = 6; // 09:00 + end_ind = 11; // 03:00 + } + + for (int8_t ind = start_ind; ind <= end_ind; ind++) { + + if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation + + xyz_float_t p = { circle.x + _COS(ind ), circle.y + _SIN(ind ), g26_layer_height }, + q = { circle.x + _COS(ind + 1), circle.y + _SIN(ind + 1), g26_layer_height }; + + #if IS_KINEMATIC + // Check to make sure this segment is entirely on the bed, skip if not. + if (!position_is_reachable(p) || !position_is_reachable(q)) continue; + #else + LIMIT(p.x, X_MIN_POS + 1, X_MAX_POS - 1); // Prevent hitting the endstops + LIMIT(p.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + LIMIT(q.x, X_MIN_POS + 1, X_MAX_POS - 1); + LIMIT(q.y, Y_MIN_POS + 1, Y_MAX_POS - 1); + #endif + + print_line_from_here_to_there(p, q); + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } + + #endif // !ARC_SUPPORT + + if (look_for_lines_to_connect()) goto LEAVE; + } + + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + + } while (--g26_repeats && location.valid()); + + LEAVE: + ui.set_status_P(GET_TEXT(MSG_G26_LEAVING), -1); + + retract_filament(destination); + destination.z = Z_CLEARANCE_BETWEEN_PROBES; + move_to(destination, 0); // Raise the nozzle + + destination = g26_xy_pos; // Move back to the starting XY position + move_to(destination, 0); // Move back to the starting position + + #if DISABLED(NO_VOLUMETRICS) + parser.volumetric_enabled = volumetric_was_enabled; + planner.calculate_volumetric_multipliers(); + #endif + + TERN_(HAS_LCD_MENU, ui.release()); // Give back control of the LCD + + if (!g26_keep_heaters_on) { + TERN_(HAS_HEATED_BED, thermalManager.setTargetBed(0)); + thermalManager.setTargetHotend(active_extruder, 0); + } +} + +#endif // G26_MESH_VALIDATION diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp new file mode 100644 index 0000000..46f75f2 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/G35.cpp @@ -0,0 +1,167 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if ENABLED(ASSISTED_TRAMMING) + +#include "../gcode.h" +#include "../../module/planner.h" +#include "../../module/probe.h" +#include "../../feature/bedlevel/bedlevel.h" + +#if HAS_MULTI_HOTEND + #include "../../module/tool_change.h" +#endif + +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../core/debug_out.h" + +// +// Define tramming point names. +// + +#include "../../feature/tramming.h" + +/** + * G35: Read bed corners to help adjust bed screws + * + * S + * + * Screw thread: 30 - Clockwise M3 + * 31 - Counter-Clockwise M3 + * 40 - Clockwise M4 + * 41 - Counter-Clockwise M4 + * 50 - Clockwise M5 + * 51 - Counter-Clockwise M5 + **/ +void GcodeSuite::G35() { + DEBUG_SECTION(log_G35, "G35", DEBUGGING(LEVELING)); + + if (DEBUGGING(LEVELING)) log_machine_info(); + + float z_measured[G35_PROBE_COUNT] = { 0 }; + + const uint8_t screw_thread = parser.byteval('S', TRAMMING_SCREW_THREAD); + if (!WITHIN(screw_thread, 30, 51) || screw_thread % 10 > 1) { + SERIAL_ECHOLNPGM("?(S)crew thread must be 30, 31, 40, 41, 50, or 51."); + return; + } + + // Wait for planner moves to finish! + planner.synchronize(); + + // Disable the leveling matrix before auto-aligning + #if HAS_LEVELING + TERN_(RESTORE_LEVELING_AFTER_G35, const bool leveling_was_active = planner.leveling_active); + set_bed_leveling_enabled(false); + #endif + + #if ENABLED(CNC_WORKSPACE_PLANES) + workspace_plane = PLANE_XY; + #endif + + // Always home with tool 0 active + #if HAS_MULTI_HOTEND + const uint8_t old_tool_index = active_extruder; + tool_change(0, true); + #endif + + // Disable duplication mode on homing + TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false)); + + // Home all before this procedure + home_all_axes(); + + bool err_break = false; + + // Probe all positions + LOOP_L_N(i, G35_PROBE_COUNT) { + + // In BLTOUCH HS mode, the probe travels in a deployed state. + // Users of G35 might have a badly misaligned bed, so raise Z by the + // length of the deployed pin (BLTOUCH stroke < 7mm) + do_blocking_move_to_z((Z_CLEARANCE_BETWEEN_PROBES) + TERN0(BLTOUCH_HS_MODE, 7)); + const float z_probed_height = probe.probe_at_point(screws_tilt_adjust_pos[i], PROBE_PT_RAISE, 0, true); + + if (isnan(z_probed_height)) { + SERIAL_ECHOPAIR("G35 failed at point ", int(i), " ("); + SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i])); + SERIAL_CHAR(')'); + SERIAL_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y); + err_break = true; + break; + } + + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOPAIR("Probing point ", int(i), " ("); + DEBUG_PRINT_P((char *)pgm_read_ptr(&tramming_point_name[i])); + DEBUG_CHAR(')'); + DEBUG_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y, SP_Z_STR, z_probed_height); + } + + z_measured[i] = z_probed_height; + } + + if (!err_break) { + const float threads_factor[] = { 0.5, 0.7, 0.8 }; + + // Calculate adjusts + LOOP_S_L_N(i, 1, G35_PROBE_COUNT) { + const float diff = z_measured[0] - z_measured[i], + adjust = abs(diff) < 0.001f ? 0 : diff / threads_factor[(screw_thread - 30) / 10]; + + const int full_turns = trunc(adjust); + const float decimal_part = adjust - float(full_turns); + const int minutes = trunc(decimal_part * 60.0f); + + SERIAL_ECHOPGM("Turn "); + SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i])); + SERIAL_ECHOPAIR(" ", (screw_thread & 1) == (adjust > 0) ? "CCW" : "CW", " by ", abs(full_turns), " turns"); + if (minutes) SERIAL_ECHOPAIR(" and ", abs(minutes), " minutes"); + if (ENABLED(REPORT_TRAMMING_MM)) SERIAL_ECHOPAIR(" (", -diff, "mm)"); + SERIAL_EOL(); + } + } + else + SERIAL_ECHOLNPGM("G35 aborted."); + + // Restore the active tool after homing + #if HAS_MULTI_HOTEND + tool_change(old_tool_index, DISABLED(PARKING_EXTRUDER)); // Fetch previous toolhead if not PARKING_EXTRUDER + #endif + + #if BOTH(HAS_LEVELING, RESTORE_LEVELING_AFTER_G35) + set_bed_leveling_enabled(leveling_was_active); + #endif + + // Stow the probe, as the last call to probe.probe_at_point(...) left + // the probe deployed if it was successful. + probe.stow(); + + move_to_tramming_wait_pos(); + + // After this operation the Z position needs correction + set_axis_never_homed(Z_AXIS); +} + +#endif // ASSISTED_TRAMMING diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp new file mode 100644 index 0000000..a2896ed --- /dev/null +++ b/Marlin/src/gcode/bedlevel/G42.cpp @@ -0,0 +1,73 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_MESH + +#include "../gcode.h" +#include "../../MarlinCore.h" // for IsRunning() +#include "../../module/motion.h" +#include "../../module/probe.h" // for probe.offset +#include "../../feature/bedlevel/bedlevel.h" + +/** + * G42: Move X & Y axes to mesh coordinates (I & J) + */ +void GcodeSuite::G42() { + if (MOTION_CONDITIONS) { + const bool hasI = parser.seenval('I'); + const int8_t ix = hasI ? parser.value_int() : 0; + const bool hasJ = parser.seenval('J'); + const int8_t iy = hasJ ? parser.value_int() : 0; + + if ((hasI && !WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) || (hasJ && !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1))) { + SERIAL_ECHOLNPGM(STR_ERR_MESH_XY); + return; + } + + // Move to current_position, as modified by I, J, P parameters + destination = current_position; + + if (hasI) destination.x = _GET_MESH_X(ix); + if (hasJ) destination.y = _GET_MESH_Y(iy); + + #if HAS_PROBE_XY_OFFSET + if (parser.boolval('P')) { + if (hasI) destination.x -= probe.offset_xy.x; + if (hasJ) destination.y -= probe.offset_xy.y; + } + #endif + + const feedRate_t fval = parser.linearval('F'), + fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f); + + // SCARA kinematic has "safe" XY raw moves + #if IS_SCARA + prepare_internal_fast_move_to_destination(fr_mm_s); + #else + prepare_internal_move_to_destination(fr_mm_s); + #endif + } +} + +#endif // HAS_MESH diff --git a/Marlin/src/gcode/bedlevel/M420.cpp b/Marlin/src/gcode/bedlevel/M420.cpp new file mode 100644 index 0000000..96122c1 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/M420.cpp @@ -0,0 +1,245 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +#include "../../inc/MarlinConfig.h" + +#if HAS_LEVELING + +#include "../gcode.h" +#include "../../feature/bedlevel/bedlevel.h" +#include "../../module/planner.h" +#include "../../module/probe.h" + +#if ENABLED(EEPROM_SETTINGS) + #include "../../module/settings.h" +#endif + +#if ENABLED(EXTENSIBLE_UI) + #include "../../lcd/extui/ui_api.h" +#endif + +//#define M420_C_USE_MEAN + +/** + * M420: Enable/Disable Bed Leveling and/or set the Z fade height. + * + * S[bool] Turns leveling on or off + * Z[height] Sets the Z fade height (0 or none to disable) + * V[bool] Verbose - Print the leveling grid + * + * With AUTO_BED_LEVELING_UBL only: + * + * L[index] Load UBL mesh from index (0 is default) + * T[map] 0:Human-readable 1:CSV 2:"LCD" 4:Compact + * + * With mesh-based leveling only: + * + * C Center mesh on the mean of the lowest and highest + * + * With MARLIN_DEV_MODE: + * S2 Create a simple random mesh and enable + */ +void GcodeSuite::M420() { + const bool seen_S = parser.seen('S'), + to_enable = seen_S ? parser.value_bool() : planner.leveling_active; + + #if ENABLED(MARLIN_DEV_MODE) + if (parser.intval('S') == 2) { + const float x_min = probe.min_x(), x_max = probe.max_x(), + y_min = probe.min_y(), y_max = probe.max_y(); + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + bilinear_start.set(x_min, y_min); + bilinear_grid_spacing.set((x_max - x_min) / (GRID_MAX_POINTS_X - 1), + (y_max - y_min) / (GRID_MAX_POINTS_Y - 1)); + #endif + GRID_LOOP(x, y) { + Z_VALUES(x, y) = 0.001 * random(-200, 200); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y))); + } + SERIAL_ECHOPGM("Simulated " STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh "); + SERIAL_ECHOPAIR(" (", x_min); + SERIAL_CHAR(','); SERIAL_ECHO(y_min); + SERIAL_ECHOPAIR(")-(", x_max); + SERIAL_CHAR(','); SERIAL_ECHO(y_max); + SERIAL_ECHOLNPGM(")"); + } + #endif + + xyz_pos_t oldpos = current_position; + + // If disabling leveling do it right away + // (Don't disable for just M420 or M420 V) + if (seen_S && !to_enable) set_bed_leveling_enabled(false); + + #if ENABLED(AUTO_BED_LEVELING_UBL) + + // L to load a mesh from the EEPROM + if (parser.seen('L')) { + + set_bed_leveling_enabled(false); + + #if ENABLED(EEPROM_SETTINGS) + const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.storage_slot; + const int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } + + if (!WITHIN(storage_slot, 0, a - 1)) { + SERIAL_ECHOLNPGM("?Invalid storage slot."); + SERIAL_ECHOLNPAIR("?Use 0 to ", a - 1); + return; + } + + settings.load_mesh(storage_slot); + ubl.storage_slot = storage_slot; + + #else + + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + + #endif + } + + // L or V display the map info + if (parser.seen("LV")) { + ubl.display_map(parser.byteval('T')); + SERIAL_ECHOPGM("Mesh is "); + if (!ubl.mesh_is_valid()) SERIAL_ECHOPGM("in"); + SERIAL_ECHOLNPAIR("valid\nStorage slot: ", ubl.storage_slot); + } + + #endif // AUTO_BED_LEVELING_UBL + + const bool seenV = parser.seen('V'); + + #if HAS_MESH + + if (leveling_is_valid()) { + + // Subtract the given value or the mean from all mesh values + if (parser.seen('C')) { + const float cval = parser.value_float(); + #if ENABLED(AUTO_BED_LEVELING_UBL) + + set_bed_leveling_enabled(false); + ubl.adjust_mesh_to_mean(true, cval); + + #else + + #if ENABLED(M420_C_USE_MEAN) + + // Get the sum and average of all mesh values + float mesh_sum = 0; + GRID_LOOP(x, y) mesh_sum += Z_VALUES(x, y); + const float zmean = mesh_sum / float(GRID_MAX_POINTS); + + #else + + // Find the low and high mesh values + float lo_val = 100, hi_val = -100; + GRID_LOOP(x, y) { + const float z = Z_VALUES(x, y); + NOMORE(lo_val, z); + NOLESS(hi_val, z); + } + // Take the mean of the lowest and highest + const float zmean = (lo_val + hi_val) / 2.0 + cval; + + #endif + + // If not very close to 0, adjust the mesh + if (!NEAR_ZERO(zmean)) { + set_bed_leveling_enabled(false); + // Subtract the mean from all values + GRID_LOOP(x, y) { + Z_VALUES(x, y) -= zmean; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y))); + } + TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + } + + #endif + } + + } + else if (to_enable || seenV) { + SERIAL_ECHO_MSG("Invalid mesh."); + goto EXIT_M420; + } + + #endif // HAS_MESH + + // V to print the matrix or mesh + if (seenV) { + #if ABL_PLANAR + planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:")); + #else + if (leveling_is_valid()) { + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + print_bilinear_leveling_grid(); + TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt()); + #elif ENABLED(MESH_BED_LEVELING) + SERIAL_ECHOLNPGM("Mesh Bed Level data:"); + mbl.report_mesh(); + #endif + } + #endif + } + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units(), false); + #endif + + // Enable leveling if specified, or if previously active + set_bed_leveling_enabled(to_enable); + + #if HAS_MESH + EXIT_M420: + #endif + + // Error if leveling failed to enable or reenable + if (to_enable && !planner.leveling_active) + SERIAL_ERROR_MSG(STR_ERR_M420_FAILED); + + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Bed Leveling "); + serialprintln_onoff(planner.leveling_active); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Fade Height "); + if (planner.z_fade_height > 0.0) + SERIAL_ECHOLN(planner.z_fade_height); + else + SERIAL_ECHOLNPGM(STR_OFF); + #endif + + // Report change in position + if (oldpos != current_position) + report_current_position(); +} + +#endif // HAS_LEVELING diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp new file mode 100644 index 0000000..2e80f09 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -0,0 +1,901 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * G29.cpp - Auto Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if HAS_ABL_NOT_UBL + +#include "../../gcode.h" +#include "../../../feature/bedlevel/bedlevel.h" +#include "../../../module/motion.h" +#include "../../../module/planner.h" +#include "../../../module/stepper.h" +#include "../../../module/probe.h" +#include "../../queue.h" + +#if ENABLED(PROBE_TEMP_COMPENSATION) + #include "../../../feature/probe_temp_comp.h" + #include "../../../module/temperature.h" +#endif + +#if HAS_DISPLAY + #include "../../../lcd/marlinui.h" +#endif + +#if ENABLED(AUTO_BED_LEVELING_LINEAR) + #include "../../../libs/least_squares_fit.h" +#endif + +#if ABL_PLANAR + #include "../../../libs/vector_3.h" +#endif + +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../../core/debug_out.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +#if ENABLED(DWIN_CREALITY_LCD) + #include "../../../lcd/dwin/e3v2/dwin.h" +#endif + +#if HAS_MULTI_HOTEND + #include "../../../module/tool_change.h" +#endif + +#if ABL_GRID + #if ENABLED(PROBE_Y_FIRST) + #define PR_OUTER_VAR meshCount.x + #define PR_OUTER_END abl_grid_points.x + #define PR_INNER_VAR meshCount.y + #define PR_INNER_END abl_grid_points.y + #else + #define PR_OUTER_VAR meshCount.y + #define PR_OUTER_END abl_grid_points.y + #define PR_INNER_VAR meshCount.x + #define PR_INNER_END abl_grid_points.x + #endif +#endif + +#define G29_RETURN(b) return TERN_(G29_RETRY_AND_RECOVER, b) + +/** + * G29: Detailed Z probe, probes the bed at 3 or more points. + * Will fail if the printer has not been homed with G28. + * + * Enhanced G29 Auto Bed Leveling Probe Routine + * + * O Auto-level only if needed + * + * D Dry-Run mode. Just evaluate the bed Topology - Don't apply + * or alter the bed level data. Useful to check the topology + * after a first run of G29. + * + * J Jettison current bed leveling data + * + * V Set the verbose level (0-4). Example: "G29 V3" + * + * Parameters With LINEAR leveling only: + * + * P Set the size of the grid that will be probed (P x P points). + * Example: "G29 P4" + * + * X Set the X size of the grid that will be probed (X x Y points). + * Example: "G29 X7 Y5" + * + * Y Set the Y size of the grid that will be probed (X x Y points). + * + * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report. + * This is useful for manual bed leveling and finding flaws in the bed (to + * assist with part placement). + * Not supported by non-linear delta printer bed leveling. + * + * Parameters With LINEAR and BILINEAR leveling only: + * + * S Set the XY travel speed between probe points (in units/min) + * + * H Set bounds to a centered square H x H units in size + * + * -or- + * + * F Set the Front limit of the probing grid + * B Set the Back limit of the probing grid + * L Set the Left limit of the probing grid + * R Set the Right limit of the probing grid + * + * Parameters with DEBUG_LEVELING_FEATURE only: + * + * C Make a totally fake grid with no actual probing. + * For use in testing when no probing is possible. + * + * Parameters with BILINEAR leveling only: + * + * Z Supply an additional Z probe offset + * + * Extra parameters with PROBE_MANUALLY: + * + * To do manual probing simply repeat G29 until the procedure is complete. + * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. + * + * Q Query leveling and G29 state + * + * A Abort current leveling procedure + * + * Extra parameters with BILINEAR only: + * + * W Write a mesh point. (If G29 is idle.) + * I X index for mesh point + * J Y index for mesh point + * X X for mesh point, overrides I + * Y Y for mesh point, overrides J + * Z Z for mesh point. Otherwise, raw current Z. + * + * Without PROBE_MANUALLY: + * + * E By default G29 will engage the Z probe, test the bed, then disengage. + * Include "E" to engage/disengage the Z probe for each sample. + * There's no extra effect if you have a fixed Z probe. + */ +G29_TYPE GcodeSuite::G29() { + + reset_stepper_timeout(); + + const bool seenQ = EITHER(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen('Q'); + + // G29 Q is also available if debugging + #if ENABLED(DEBUG_LEVELING_FEATURE) + const uint8_t old_debug_flags = marlin_debug_flags; + if (seenQ) marlin_debug_flags |= MARLIN_DEBUG_LEVELING; + DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING)); + if (DEBUGGING(LEVELING)) log_machine_info(); + marlin_debug_flags = old_debug_flags; + if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false); + #endif + + const bool seenA = TERN0(PROBE_MANUALLY, parser.seen('A')), + no_action = seenA || seenQ, + faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; + + if (!no_action && planner.leveling_active && parser.boolval('O')) { // Auto-level only if needed + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); + G29_RETURN(false); + } + + // Send 'N' to force homing before G29 (internal only) + if (parser.seen('N')) + process_subcommands_now_P(TERN(G28_L0_ENSURES_LEVELING_OFF, PSTR("G28L0"), G28_STR)); + + // Don't allow auto-leveling without homing first + if (homing_needed_error()) G29_RETURN(false); + + // Define local vars 'static' for manual probing, 'auto' otherwise + #define ABL_VAR TERN_(PROBE_MANUALLY, static) + + ABL_VAR int verbose_level; + ABL_VAR xy_pos_t probePos; + ABL_VAR float measured_z; + ABL_VAR bool dryrun, abl_should_enable; + + #if EITHER(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + ABL_VAR int abl_probe_index; + #endif + + #if ABL_GRID + + #if ENABLED(PROBE_MANUALLY) + ABL_VAR xy_int8_t meshCount; + #endif + + ABL_VAR xy_pos_t probe_position_lf, probe_position_rb; + ABL_VAR xy_float_t gridSpacing = { 0, 0 }; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + ABL_VAR bool do_topography_map; + ABL_VAR xy_uint8_t abl_grid_points; + #else // Bilinear + constexpr xy_uint8_t abl_grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y }; + #endif + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + ABL_VAR int abl_points; + #else + int constexpr abl_points = GRID_MAX_POINTS; + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + ABL_VAR float zoffset; + + #elif ENABLED(AUTO_BED_LEVELING_LINEAR) + + ABL_VAR int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + + ABL_VAR float eqnAMatrix[(GRID_MAX_POINTS) * 3], // "A" matrix of the linear system of equations + eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points + mean; + #endif + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + #if ENABLED(PROBE_MANUALLY) + int constexpr abl_points = 3; // used to show total points + #endif + + vector_3 points[3]; + probe.get_three_points(points); + + #endif // AUTO_BED_LEVELING_3POINT + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + struct linear_fit_data lsf_results; + #endif + + /** + * On the initial G29 fetch command parameters. + */ + if (!g29_in_progress) { + + TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0)); + + #if EITHER(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + abl_probe_index = -1; + #endif + + abl_should_enable = planner.leveling_active; + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + const bool seen_w = parser.seen('W'); + if (seen_w) { + if (!leveling_is_valid()) { + SERIAL_ERROR_MSG("No bilinear grid"); + G29_RETURN(false); + } + + const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; + if (!WITHIN(rz, -10, 10)) { + SERIAL_ERROR_MSG("Bad Z value"); + G29_RETURN(false); + } + + const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), + ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); + int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); + + if (!isnan(rx) && !isnan(ry)) { + // Get nearest i / j from rx / ry + i = (rx - bilinear_start.x + 0.5 * gridSpacing.x) / gridSpacing.x; + j = (ry - bilinear_start.y + 0.5 * gridSpacing.y) / gridSpacing.y; + LIMIT(i, 0, GRID_MAX_POINTS_X - 1); + LIMIT(j, 0, GRID_MAX_POINTS_Y - 1); + } + if (WITHIN(i, 0, GRID_MAX_POINTS_X - 1) && WITHIN(j, 0, GRID_MAX_POINTS_Y)) { + set_bed_leveling_enabled(false); + z_values[i][j] = rz; + TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); + set_bed_leveling_enabled(abl_should_enable); + if (abl_should_enable) report_current_position(); + } + G29_RETURN(false); + } // parser.seen('W') + + #else + + constexpr bool seen_w = false; + + #endif + + // Jettison bed leveling data + if (!seen_w && parser.seen('J')) { + reset_bed_level(); + G29_RETURN(false); + } + + verbose_level = parser.intval('V'); + if (!WITHIN(verbose_level, 0, 4)) { + SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4)."); + G29_RETURN(false); + } + + dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action); + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + incremental_LSF_reset(&lsf_results); + + do_topography_map = verbose_level > 2 || parser.boolval('T'); + + // X and Y specify points in each direction, overriding the default + // These values may be saved with the completed mesh + abl_grid_points.set( + parser.byteval('X', GRID_MAX_POINTS_X), + parser.byteval('Y', GRID_MAX_POINTS_Y) + ); + if (parser.seenval('P')) abl_grid_points.x = abl_grid_points.y = parser.value_int(); + + if (!WITHIN(abl_grid_points.x, 2, GRID_MAX_POINTS_X)) { + SERIAL_ECHOLNPGM("?Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ")."); + G29_RETURN(false); + } + if (!WITHIN(abl_grid_points.y, 2, GRID_MAX_POINTS_Y)) { + SERIAL_ECHOLNPGM("?Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ")."); + G29_RETURN(false); + } + + abl_points = abl_grid_points.x * abl_grid_points.y; + mean = 0; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + zoffset = parser.linearval('Z'); + + #endif + + #if ABL_GRID + + xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_SPEED)); + + const float x_min = probe.min_x(), x_max = probe.max_x(), + y_min = probe.min_y(), y_max = probe.max_y(); + + if (parser.seen('H')) { + const int16_t size = (int16_t)parser.value_linear_units(); + probe_position_lf.set(_MAX((X_CENTER) - size / 2, x_min), _MAX((Y_CENTER) - size / 2, y_min)); + probe_position_rb.set(_MIN(probe_position_lf.x + size, x_max), _MIN(probe_position_lf.y + size, y_max)); + } + else { + probe_position_lf.set(parser.linearval('L', x_min), parser.linearval('F', y_min)); + probe_position_rb.set(parser.linearval('R', x_max), parser.linearval('B', y_max)); + } + + if (!probe.good_bounds(probe_position_lf, probe_position_rb)) { + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLNPAIR("G29 L", probe_position_lf.x, " R", probe_position_rb.x, + " F", probe_position_lf.y, " B", probe_position_rb.y); + } + SERIAL_ECHOLNPGM("? (L,R,F,B) out of bounds."); + G29_RETURN(false); + } + + // Probe at the points of a lattice grid + gridSpacing.set((probe_position_rb.x - probe_position_lf.x) / (abl_grid_points.x - 1), + (probe_position_rb.y - probe_position_lf.y) / (abl_grid_points.y - 1)); + + #endif // ABL_GRID + + if (verbose_level > 0) { + SERIAL_ECHOPGM("G29 Auto Bed Leveling"); + if (dryrun) SERIAL_ECHOPGM(" (DRYRUN)"); + SERIAL_EOL(); + } + + planner.synchronize(); + + #if ENABLED(AUTO_BED_LEVELING_3POINT) + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling"); + points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points + #endif + + #if BOTH(AUTO_BED_LEVELING_BILINEAR, EXTENSIBLE_UI) + ExtUI::onMeshLevelingStart(); + #endif + + if (!faux) { + remember_feedrate_scaling_off(); + + #if ENABLED(PREHEAT_BEFORE_LEVELING) + if (!dryrun) probe.preheat_for_probing(LEVELING_NOZZLE_TEMP, LEVELING_BED_TEMP); + #endif + } + + // Disable auto bed leveling during G29. + // Be formal so G29 can be done successively without G28. + if (!no_action) set_bed_leveling_enabled(false); + + // Deploy certain probes before starting probing + #if HAS_BED_PROBE + if (ENABLED(BLTOUCH)) + do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + else if (probe.deploy()) { + set_bed_leveling_enabled(abl_should_enable); + G29_RETURN(false); + } + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + if (TERN1(PROBE_MANUALLY, !no_action) + && (gridSpacing != bilinear_grid_spacing || probe_position_lf != bilinear_start) + ) { + // Reset grid to 0.0 or "not probed". (Also disables ABL) + reset_bed_level(); + + // Initialize a grid with the given dimensions + bilinear_grid_spacing = gridSpacing; + bilinear_start = probe_position_lf; + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + #endif // AUTO_BED_LEVELING_BILINEAR + + } // !g29_in_progress + + #if ENABLED(PROBE_MANUALLY) + + // For manual probing, get the next index to probe now. + // On the first probe this will be incremented to 0. + if (!no_action) { + ++abl_probe_index; + g29_in_progress = true; + } + + // Abort current G29 procedure, go back to idle state + if (seenA && g29_in_progress) { + SERIAL_ECHOLNPGM("Manual G29 aborted"); + SET_SOFT_ENDSTOP_LOOSE(false); + set_bed_leveling_enabled(abl_should_enable); + g29_in_progress = false; + TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + } + + // Query G29 status + if (verbose_level || seenQ) { + SERIAL_ECHOPGM("Manual G29 "); + if (g29_in_progress) { + SERIAL_ECHOPAIR("point ", _MIN(abl_probe_index + 1, abl_points)); + SERIAL_ECHOLNPAIR(" of ", abl_points); + } + else + SERIAL_ECHOLNPGM("idle"); + } + + if (no_action) G29_RETURN(false); + + if (abl_probe_index == 0) { + // For the initial G29 S2 save software endstop state + SET_SOFT_ENDSTOP_LOOSE(true); + // Move close to the bed before the first point + do_blocking_move_to_z(0); + } + else { + + #if EITHER(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) + const uint16_t index = abl_probe_index - 1; + #endif + + // For G29 after adjusting Z. + // Save the previous Z before going to the next point + measured_z = current_position.z; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + mean += measured_z; + eqnBVector[index] = measured_z; + eqnAMatrix[index + 0 * abl_points] = probePos.x; + eqnAMatrix[index + 1 * abl_points] = probePos.y; + eqnAMatrix[index + 2 * abl_points] = 1; + + incremental_LSF(&lsf_results, probePos, measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + points[index].z = measured_z; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + const float newz = measured_z + zoffset; + z_values[meshCount.x][meshCount.y] = newz; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, newz)); + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_P(PSTR("Save X"), meshCount.x, SP_Y_STR, meshCount.y, SP_Z_STR, measured_z + zoffset); + + #endif + } + + // + // If there's another point to sample, move there with optional lift. + // + + #if ABL_GRID + + // Skip any unreachable points + while (abl_probe_index < abl_points) { + + // Set meshCount.x, meshCount.y based on abl_probe_index, with zig-zag + PR_OUTER_VAR = abl_probe_index / PR_INNER_END; + PR_INNER_VAR = abl_probe_index - (PR_OUTER_VAR * PR_INNER_END); + + // Probe in reverse order for every other row/column + const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1); + if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR; + + probePos = probe_position_lf + gridSpacing * meshCount.asFloat(); + + TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = abl_probe_index); + + // Keep looping till a reachable point is found + if (position_is_reachable(probePos)) break; + ++abl_probe_index; + } + + // Is there a next point to move to? + if (abl_probe_index < abl_points) { + _manual_goto_xy(probePos); // Can be used here too! + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + SET_SOFT_ENDSTOP_LOOSE(true); + G29_RETURN(false); + } + else { + // Leveling done! Fall through to G29 finishing code below + SERIAL_ECHOLNPGM("Grid probing done."); + // Re-enable software endstops, if needed + SET_SOFT_ENDSTOP_LOOSE(false); + } + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + if (abl_probe_index < abl_points) { + probePos = points[abl_probe_index]; + _manual_goto_xy(probePos); + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + SET_SOFT_ENDSTOP_LOOSE(true); + G29_RETURN(false); + } + else { + + SERIAL_ECHOLNPGM("3-point probing done."); + + // Re-enable software endstops, if needed + SET_SOFT_ENDSTOP_LOOSE(false); + + if (!dryrun) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) planeNormal *= -1; + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + + } + + #endif // AUTO_BED_LEVELING_3POINT + + #else // !PROBE_MANUALLY + { + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; + + measured_z = 0; + + #if ABL_GRID + + bool zig = PR_OUTER_END & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION + + measured_z = 0; + + xy_int8_t meshCount; + + // Outer loop is X with PROBE_Y_FIRST enabled + // Outer loop is Y with PROBE_Y_FIRST disabled + for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) { + + int8_t inStart, inStop, inInc; + + if (zig) { // Zig away from origin + inStart = 0; // Left or front + inStop = PR_INNER_END; // Right or back + inInc = 1; // Zig right + } + else { // Zag towards origin + inStart = PR_INNER_END - 1; // Right or back + inStop = -1; // Left or front + inInc = -1; // Zag left + } + + zig ^= true; // zag + + // An index to print current state + uint8_t pt_index = (PR_OUTER_VAR) * (PR_INNER_END) + 1; + + // Inner loop is Y with PROBE_Y_FIRST enabled + // Inner loop is X with PROBE_Y_FIRST disabled + for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; pt_index++, PR_INNER_VAR += inInc) { + + probePos = probe_position_lf + gridSpacing * meshCount.asFloat(); + + TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = ++abl_probe_index); // 0... + + // Avoid probing outside the round or hexagonal area + if (TERN0(IS_KINEMATIC, !probe.can_reach(probePos))) continue; + + if (verbose_level) SERIAL_ECHOLNPAIR("Probing mesh point ", int(pt_index), "/", abl_points, "."); + TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_MESH), int(pt_index), int(abl_points))); + + measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(probePos, raise_after, verbose_level); + + if (isnan(measured_z)) { + set_bed_leveling_enabled(abl_should_enable); + break; // Breaks out of both loops + } + + #if ENABLED(PROBE_TEMP_COMPENSATION) + temp_comp.compensate_measurement(TSI_BED, thermalManager.degBed(), measured_z); + temp_comp.compensate_measurement(TSI_PROBE, thermalManager.degProbe(), measured_z); + TERN_(USE_TEMP_EXT_COMPENSATION, temp_comp.compensate_measurement(TSI_EXT, thermalManager.degHotend(), measured_z)); + #endif + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + mean += measured_z; + eqnBVector[abl_probe_index] = measured_z; + eqnAMatrix[abl_probe_index + 0 * abl_points] = probePos.x; + eqnAMatrix[abl_probe_index + 1 * abl_points] = probePos.y; + eqnAMatrix[abl_probe_index + 2 * abl_points] = 1; + + incremental_LSF(&lsf_results, probePos, measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + const float z = measured_z + zoffset; + z_values[meshCount.x][meshCount.y] = z; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, z)); + + #endif + + abl_should_enable = false; + idle_no_sleep(); + + } // inner + } // outer + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + + LOOP_L_N(i, 3) { + if (verbose_level) SERIAL_ECHOLNPAIR("Probing point ", int(i + 1), "/3."); + TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/3"), GET_TEXT(MSG_PROBING_MESH), int(i + 1))); + + // Retain the last probe position + probePos = points[i]; + measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(probePos, raise_after, verbose_level); + if (isnan(measured_z)) { + set_bed_leveling_enabled(abl_should_enable); + break; + } + points[i].z = measured_z; + } + + if (!dryrun && !isnan(measured_z)) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) planeNormal *= -1; + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + + #endif // AUTO_BED_LEVELING_3POINT + + TERN_(HAS_DISPLAY, ui.reset_status()); + + // Stow the probe. No raise for FIX_MOUNTED_PROBE. + if (probe.stow()) { + set_bed_leveling_enabled(abl_should_enable); + measured_z = NAN; + } + } + #endif // !PROBE_MANUALLY + + // + // G29 Finishing Code + // + // Unless this is a dry run, auto bed leveling will + // definitely be enabled after this point. + // + // If code above wants to continue leveling, it should + // return or loop before this point. + // + + if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); + + #if ENABLED(PROBE_MANUALLY) + g29_in_progress = false; + TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + #endif + + // Calculate leveling, print reports, correct the position + if (!isnan(measured_z)) { + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (!dryrun) extrapolate_unprobed_bed_level(); + print_bilinear_leveling_grid(); + + refresh_bed_level(); + + TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt()); + + #elif ENABLED(AUTO_BED_LEVELING_LINEAR) + + // For LINEAR leveling calculate matrix, print reports, correct the position + + /** + * solve the plane equation ax + by + d = z + * A is the matrix with rows [x y 1] for all the probed points + * B is the vector of the Z positions + * the normal vector to the plane is formed by the coefficients of the + * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0 + * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z + */ + struct { float a, b, d; } plane_equation_coefficients; + + finish_incremental_LSF(&lsf_results); + plane_equation_coefficients.a = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below + plane_equation_coefficients.b = -lsf_results.B; // but that is not yet tested. + plane_equation_coefficients.d = -lsf_results.D; + + mean /= abl_points; + + if (verbose_level) { + SERIAL_ECHOPAIR_F("Eqn coefficients: a: ", plane_equation_coefficients.a, 8); + SERIAL_ECHOPAIR_F(" b: ", plane_equation_coefficients.b, 8); + SERIAL_ECHOPAIR_F(" d: ", plane_equation_coefficients.d, 8); + if (verbose_level > 2) + SERIAL_ECHOPAIR_F("\nMean of sampled points: ", mean, 8); + SERIAL_EOL(); + } + + // Create the matrix but don't correct the position yet + if (!dryrun) + planner.bed_level_matrix = matrix_3x3::create_look_at( + vector_3(-plane_equation_coefficients.a, -plane_equation_coefficients.b, 1) // We can eliminate the '-' here and up above + ); + + // Show the Topography map if enabled + if (do_topography_map) { + + float min_diff = 999; + + auto print_topo_map = [&](PGM_P const title, const bool get_min) { + serialprintPGM(title); + for (int8_t yy = abl_grid_points.y - 1; yy >= 0; yy--) { + LOOP_L_N(xx, abl_grid_points.x) { + const int ind = indexIntoAB[xx][yy]; + xyz_float_t tmp = { eqnAMatrix[ind + 0 * abl_points], + eqnAMatrix[ind + 1 * abl_points], 0 }; + apply_rotation_xyz(planner.bed_level_matrix, tmp); + if (get_min) NOMORE(min_diff, eqnBVector[ind] - tmp.z); + const float subval = get_min ? mean : tmp.z + min_diff, + diff = eqnBVector[ind] - subval; + SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+'); // Include + for column alignment + SERIAL_ECHO_F(diff, 5); + } // xx + SERIAL_EOL(); + } // yy + SERIAL_EOL(); + }; + + print_topo_map(PSTR("\nBed Height Topography:\n" + " +--- BACK --+\n" + " | |\n" + " L | (+) | R\n" + " E | | I\n" + " F | (-) N (+) | G\n" + " T | | H\n" + " | (-) | T\n" + " | |\n" + " O-- FRONT --+\n" + " (0,0)\n"), true); + if (verbose_level > 3) + print_topo_map(PSTR("\nCorrected Bed Height vs. Bed Topology:\n"), false); + + } //do_topography_map + + #endif // AUTO_BED_LEVELING_LINEAR + + #if ABL_PLANAR + + // For LINEAR and 3POINT leveling correct the current position + + if (verbose_level > 0) + planner.bed_level_matrix.debug(PSTR("\n\nBed Level Correction Matrix:")); + + if (!dryrun) { + // + // Correct the current XYZ position based on the tilted plane. + // + + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); + + xyze_pos_t converted = current_position; + planner.force_unapply_leveling(converted); // use conversion machinery + + // Use the last measured distance to the bed, if possible + if ( NEAR(current_position.x, probePos.x - probe.offset_xy.x) + && NEAR(current_position.y, probePos.y - probe.offset_xy.y) + ) { + const float simple_z = current_position.z - measured_z; + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z); + converted.z = simple_z; + } + + // The rotated XY and corrected Z are now current_position + current_position = converted; + + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); + } + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (!dryrun) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("G29 uncorrected Z:", current_position.z); + + // Unapply the offset because it is going to be immediately applied + // and cause compensation movement in Z + const float fade_scaling_factor = TERN(ENABLE_LEVELING_FADE_HEIGHT, planner.fade_scaling_factor_for_z(current_position.z), 1); + current_position.z -= fade_scaling_factor * bilinear_z_offset(current_position); + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR(" corrected Z:", current_position.z); + } + + #endif // ABL_PLANAR + + // Auto Bed Leveling is complete! Enable if possible. + planner.leveling_active = dryrun ? abl_should_enable : true; + } // !isnan(measured_z) + + // Restore state after probing + if (!faux) restore_feedrate_and_scaling(); + + // Sync the planner from the current_position + if (planner.leveling_active) sync_plan_position(); + + #if HAS_BED_PROBE + probe.move_z_after_probing(); + #endif + + #ifdef Z_PROBE_END_SCRIPT + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT); + planner.synchronize(); + process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT)); + #endif + + #if ENABLED(DWIN_CREALITY_LCD) + DWIN_CompletedLeveling(); + #endif + + report_current_position(); + + G29_RETURN(isnan(measured_z)); +} + +#endif // HAS_ABL_NOT_UBL diff --git a/Marlin/src/gcode/bedlevel/abl/M421.cpp b/Marlin/src/gcode/bedlevel/abl/M421.cpp new file mode 100644 index 0000000..182dc32 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/abl/M421.cpp @@ -0,0 +1,74 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * M421.cpp - Auto Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) + +#include "../../gcode.h" +#include "../../../feature/bedlevel/bedlevel.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +/** + * M421: Set one or more Mesh Bed Leveling Z coordinates + * + * Usage: + * M421 I J Z + * M421 I J Q + * + * - If I is omitted, set the entire row + * - If J is omitted, set the entire column + * - If both I and J are omitted, set all + */ +void GcodeSuite::M421() { + int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); + const bool hasZ = parser.seenval('Z'), + hasQ = !hasZ && parser.seenval('Q'); + + if (hasZ || hasQ) { + if (WITHIN(ix, -1, GRID_MAX_POINTS_X - 1) && WITHIN(iy, -1, GRID_MAX_POINTS_Y - 1)) { + const float zval = parser.value_linear_units(); + uint8_t sx = ix >= 0 ? ix : 0, ex = ix >= 0 ? ix : GRID_MAX_POINTS_X - 1, + sy = iy >= 0 ? iy : 0, ey = iy >= 0 ? iy : GRID_MAX_POINTS_Y - 1; + LOOP_S_LE_N(x, sx, ex) { + LOOP_S_LE_N(y, sy, ey) { + z_values[x][y] = zval + (hasQ ? z_values[x][y] : 0); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + } + TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); + } + else + SERIAL_ERROR_MSG(STR_ERR_MESH_XY); + } + else + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); +} + +#endif // AUTO_BED_LEVELING_BILINEAR diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp new file mode 100644 index 0000000..cf27c14 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp @@ -0,0 +1,193 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * G29.cpp - Mesh Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(MESH_BED_LEVELING) + +#include "../../../feature/bedlevel/bedlevel.h" + +#include "../../gcode.h" +#include "../../queue.h" + +#include "../../../libs/buzzer.h" +#include "../../../lcd/marlinui.h" +#include "../../../module/motion.h" +#include "../../../module/stepper.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +// Save 130 bytes with non-duplication of PSTR +inline void echo_not_entered(const char c) { SERIAL_CHAR(c); SERIAL_ECHOLNPGM(" not entered."); } + +/** + * G29: Mesh-based Z probe, probes a grid and produces a + * mesh to compensate for variable bed height + * + * Parameters With MESH_BED_LEVELING: + * + * S0 Report the current mesh values + * S1 Start probing mesh points + * S2 Probe the next mesh point + * S3 In Jn Zn.nn Manually modify a single point + * S4 Zn.nn Set z offset. Positive away from bed, negative closer to bed. + * S5 Reset and disable mesh + */ +void GcodeSuite::G29() { + + static int mbl_probe_index = -1; + + MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport); + if (!WITHIN(state, 0, 5)) { + SERIAL_ECHOLNPGM("S out of range (0-5)."); + return; + } + + int8_t ix, iy; + + switch (state) { + case MeshReport: + SERIAL_ECHOPGM("Mesh Bed Leveling "); + if (leveling_is_valid()) { + serialprintln_onoff(planner.leveling_active); + mbl.report_mesh(); + } + else + SERIAL_ECHOLNPGM("has no data."); + break; + + case MeshStart: + mbl.reset(); + mbl_probe_index = 0; + if (!ui.wait_for_move) { + queue.inject_P(parser.seen('N') ? PSTR("G28" TERN(G28_L0_ENSURES_LEVELING_OFF, "L0", "") "\nG29S2") : PSTR("G29S2")); + return; + } + state = MeshNext; + + case MeshNext: + if (mbl_probe_index < 0) { + SERIAL_ECHOLNPGM("Start mesh probing with \"G29 S1\" first."); + return; + } + // For each G29 S2... + if (mbl_probe_index == 0) { + // Move close to the bed before the first point + do_blocking_move_to_z(0); + } + else { + // Save Z for the previous mesh position + mbl.set_zigzag_z(mbl_probe_index - 1, current_position.z); + SET_SOFT_ENDSTOP_LOOSE(false); + } + // If there's another point to sample, move there with optional lift. + if (mbl_probe_index < GRID_MAX_POINTS) { + // Disable software endstops to allow manual adjustment + // If G29 is left hanging without completion they won't be re-enabled! + SET_SOFT_ENDSTOP_LOOSE(true); + mbl.zigzag(mbl_probe_index++, ix, iy); + _manual_goto_xy({ mbl.index_to_xpos[ix], mbl.index_to_ypos[iy] }); + } + else { + // One last "return to the bed" (as originally coded) at completion + current_position.z = MANUAL_PROBE_HEIGHT; + line_to_current_position(); + planner.synchronize(); + + // After recording the last point, activate home and activate + mbl_probe_index = -1; + SERIAL_ECHOLNPGM("Mesh probing done."); + BUZZ(100, 659); + BUZZ(100, 698); + + home_all_axes(); + set_bed_leveling_enabled(true); + + #if ENABLED(MESH_G28_REST_ORIGIN) + current_position.z = 0; + line_to_current_position(homing_feedrate(Z_AXIS)); + planner.synchronize(); + #endif + + TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + } + break; + + case MeshSet: + if (parser.seenval('I')) { + ix = parser.value_int(); + if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) { + SERIAL_ECHOPAIR("I out of range (0-", int(GRID_MAX_POINTS_X - 1)); + SERIAL_ECHOLNPGM(")"); + return; + } + } + else + return echo_not_entered('J'); + + if (parser.seenval('J')) { + iy = parser.value_int(); + if (!WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { + SERIAL_ECHOPAIR("J out of range (0-", int(GRID_MAX_POINTS_Y - 1)); + SERIAL_ECHOLNPGM(")"); + return; + } + } + else + return echo_not_entered('J'); + + if (parser.seenval('Z')) { + mbl.z_values[ix][iy] = parser.value_linear_units(); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, mbl.z_values[ix][iy])); + } + else + return echo_not_entered('Z'); + break; + + case MeshSetZOffset: + if (parser.seenval('Z')) + mbl.z_offset = parser.value_linear_units(); + else + return echo_not_entered('Z'); + break; + + case MeshReset: + reset_bed_level(); + break; + + } // switch(state) + + if (state == MeshNext) { + SERIAL_ECHOPAIR("MBL G29 point ", _MIN(mbl_probe_index, GRID_MAX_POINTS)); + SERIAL_ECHOLNPAIR(" of ", int(GRID_MAX_POINTS)); + } + + report_current_position(); +} + +#endif // MESH_BED_LEVELING diff --git a/Marlin/src/gcode/bedlevel/mbl/M421.cpp b/Marlin/src/gcode/bedlevel/mbl/M421.cpp new file mode 100644 index 0000000..1368ab0 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/mbl/M421.cpp @@ -0,0 +1,59 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * M421.cpp - Mesh Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(MESH_BED_LEVELING) + +#include "../../gcode.h" +#include "../../../module/motion.h" +#include "../../../feature/bedlevel/mbl/mesh_bed_leveling.h" + +/** + * M421: Set a single Mesh Bed Leveling Z coordinate + * + * Usage: + * M421 X Y Z + * M421 X Y Q + * M421 I J Z + * M421 I J Q + */ +void GcodeSuite::M421() { + const bool hasX = parser.seen('X'), hasI = parser.seen('I'); + const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(RAW_X_POSITION(parser.value_linear_units())) : -1; + const bool hasY = parser.seen('Y'), hasJ = parser.seen('J'); + const int8_t iy = hasJ ? parser.value_int() : hasY ? mbl.probe_index_y(RAW_Y_POSITION(parser.value_linear_units())) : -1; + const bool hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); + + if (int(hasI && hasJ) + int(hasX && hasY) != 1 || !(hasZ || hasQ)) + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + else if (ix < 0 || iy < 0) + SERIAL_ERROR_MSG(STR_ERR_MESH_XY); + else + mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0)); +} + +#endif // MESH_BED_LEVELING diff --git a/Marlin/src/gcode/bedlevel/ubl/G29.cpp b/Marlin/src/gcode/bedlevel/ubl/G29.cpp new file mode 100644 index 0000000..2ef3ab4 --- /dev/null +++ b/Marlin/src/gcode/bedlevel/ubl/G29.cpp @@ -0,0 +1,36 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * G29.cpp - Unified Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(AUTO_BED_LEVELING_UBL) + +#include "../../gcode.h" +#include "../../../feature/bedlevel/bedlevel.h" + +void GcodeSuite::G29() { ubl.G29(); } + +#endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp new file mode 100644 index 0000000..600c1fc --- /dev/null +++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp @@ -0,0 +1,70 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 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 . + * + */ + +/** + * unified.cpp - Unified Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(AUTO_BED_LEVELING_UBL) + +#include "../../gcode.h" +#include "../../../feature/bedlevel/bedlevel.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +/** + * M421: Set a single Mesh Bed Leveling Z coordinate + * + * Usage: + * M421 I J Z + * M421 I J Q + * M421 I J N + * M421 C Z + * M421 C Q + */ +void GcodeSuite::M421() { + xy_int8_t ij = { int8_t(parser.intval('I', -1)), int8_t(parser.intval('J', -1)) }; + const bool hasI = ij.x >= 0, + hasJ = ij.y >= 0, + hasC = parser.seen('C'), + hasN = parser.seen('N'), + hasZ = parser.seen('Z'), + hasQ = !hasZ && parser.seen('Q'); + + if (hasC) ij = ubl.find_closest_mesh_point_of_type(REAL, current_position); + + if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ || hasN)) + SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS); + else if (!WITHIN(ij.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(ij.y, 0, GRID_MAX_POINTS_Y - 1)) + SERIAL_ERROR_MSG(STR_ERR_MESH_XY); + else { + float &zval = ubl.z_values[ij.x][ij.y]; + zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval)); + } +} + +#endif // AUTO_BED_LEVELING_UBL -- cgit v1.2.3