diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp index 28fdf581..457f5b17 100755 --- a/Marlin/src/gcode/bedlevel/G26.cpp +++ b/Marlin/src/gcode/bedlevel/G26.cpp @@ -43,29 +43,31 @@ #include "../../lcd/ultralcd.h" #define EXTRUSION_MULTIPLIER 1.0 -#define PRIME_LENGTH 10.0 -#define OOZE_AMOUNT 0.3 +#define RETRACTION_LENGTH 1 +#define UNRETRACTION_LENGTH 1.2 +#define PRIME_LENGTH 5 +#define OOZE_AMOUNT 2.25 #define INTERSECTION_CIRCLE_RADIUS 5 #define CROSSHAIRS_SIZE 3 #ifndef G26_RETRACT_MULTIPLIER - #define G26_RETRACT_MULTIPLIER 1.0 // x 1mm +#define G26_RETRACT_MULTIPLIER 1.0 // x 1mm #endif #ifndef G26_XY_FEEDRATE - #define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE() / 3.0) +#define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE() / 3.0) #endif #if CROSSHAIRS_SIZE >= INTERSECTION_CIRCLE_RADIUS - #error "CROSSHAIRS_SIZE must be less than 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 &cart, const ab_float_t &offset, const uint8_t clockwise); +void plan_arc(const xyze_pos_t &cart, const ab_float_t &offset, const uint8_t clockwise); #endif /** @@ -117,8 +119,9 @@ * 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 + * Q # Retract Retraction length. Defaults to 1mm if not specified. + * Z # Unretract Unretraction length. Defaults to 1.2mm if not specified. + * Note: If Q is specified but Z isn't, Z defaults to Q * 1.2. * * 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. @@ -153,44 +156,50 @@ static bool g26_retracted = false; // Track the retracted state of the nozzle so // retracts/recovers won't result in a bad state. float g26_extrusion_multiplier, - g26_retraction_multiplier, - g26_layer_height, - g26_prime_length; + g26_retraction_length, + g26_unretraction_length, + g26_layer_height, + g26_prime_length; xy_pos_t g26_xy_pos; // = { 0, 0 } int16_t g26_bed_temp, - g26_hotend_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); - #if HAS_LCD_MENU - ui.quick_feedback(); - #endif - ui.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); +#if HAS_LCD_MENU + ui.quick_feedback(); +#endif + ui.wait_for_release(); + return true; +} #endif -mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { +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)) { + 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) }; + const xy_pos_t m = {_GET_MESH_X(i), _GET_MESH_Y(j)}; // Get the distance to this intersection float f = (pos - m).magnitude(); @@ -202,11 +211,13 @@ mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { f += (g26_xy_pos - m).magnitude() / 15.0f; // Add the specified amount of Random Noise to our search - if (random_deviation > 1.0) f += random(0.0, random_deviation); + if (random_deviation > 1.0) + f += random(0.0, random_deviation); - if (f < closest) { - closest = f; // Found a closer un-printed location - out_point.pos.set(i, j); // Save its data + if (f < closest) + { + closest = f; // Found a closer un-printed location + out_point.pos.set(i, j); // Save its data out_point.distance = closest; } } @@ -215,16 +226,18 @@ mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) { return out_point; } -void move_to(const float &rx, const float &ry, const float &z, const float &e_delta) { +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 xy_pos_t dest = {rx, ry}; const bool has_xy_component = dest != current_position; // Check if X or Y is involved in the movement. destination = current_position; - if (z != last_z) { + if (z != last_z) + { last_z = destination.z = z; const feedRate_t feed_value = planner.settings.max_feedrate_mm_s[Z_AXIS] * 0.5f; // Use half of the Z_AXIS max feed rate prepare_internal_move_to_destination(feed_value); @@ -241,23 +254,28 @@ void move_to(const float &rx, const float &ry, const float &z, const float &e_de 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! +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); + move_to(where, -1.0 * g26_retraction_length); } } // TODO: Parameterize the Z lift with a define -void retract_lift_move(const xyz_pos_t &s) { +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 + 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); +void recover_filament(const xyz_pos_t &where) +{ + if (g26_retracted) + { // Only un-retract if we are retracted. + move_to(where, g26_unretraction_length); g26_retracted = false; } } @@ -277,7 +295,8 @@ void recover_filament(const xyz_pos_t &where) { * 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) { +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; @@ -292,33 +311,40 @@ void print_line_from_here_to_there(const xyz_pos_t &s, const xyz_pos_t &e) { return print_line_from_here_to_there(e, s); // Decide whether to retract & lift - if (dist_start > 2.0) retract_lift_move(s); + 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 + move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion } -inline bool look_for_lines_to_connect() { +inline bool look_for_lines_to_connect() +{ xyz_pos_t s, e; s.z = e.z = g26_layer_height; - GRID_LOOP(i, j) { + GRID_LOOP(i, j) + { - #if HAS_LCD_MENU - if (user_canceled()) return true; - #endif +#if HAS_LCD_MENU + if (user_canceled()) + return true; +#endif - 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 (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)) { + 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 + 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); @@ -332,13 +358,16 @@ inline bool look_for_lines_to_connect() { } } - 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 (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)) { + 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 + 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); @@ -361,52 +390,56 @@ inline bool look_for_lines_to_connect() { * Turn on the bed and nozzle heat and * wait for them to get up to temperature. */ -inline bool turn_on_heaters() { +inline bool turn_on_heaters() +{ SERIAL_ECHOLNPGM("Waiting for heatup."); - #if HAS_HEATED_BED +#if HAS_HEATED_BED - if (g26_bed_temp > 25) { - #if HAS_SPI_LCD - ui.set_status_P(GET_TEXT(MSG_G26_HEATING_BED), 99); - ui.quick_feedback(); - #if HAS_LCD_MENU - ui.capture(); - #endif - #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_SPI_LCD - ui.set_status_P(GET_TEXT(MSG_G26_HEATING_NOZZLE), 99); + if (g26_bed_temp > 25) + { +#if HAS_SPI_LCD + ui.set_status_P(GET_TEXT(MSG_G26_HEATING_BED), 99); ui.quick_feedback(); - #endif +#if HAS_LCD_MENU + ui.capture(); +#endif +#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_SPI_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 G26_CLICK_CAN_CANCEL + , + true +#endif + )) + return G26_ERR; - #if HAS_SPI_LCD - ui.reset_status(); - ui.quick_feedback(); - #endif +#if HAS_SPI_LCD + ui.reset_status(); + ui.quick_feedback(); +#endif return G26_OK; } @@ -414,54 +447,58 @@ inline bool turn_on_heaters() { /** * Prime the nozzle if needed. Return true on error. */ -inline bool prime_nozzle() { +inline bool prime_nozzle() +{ const feedRate_t fr_slow_e = planner.settings.max_feedrate_mm_s[E_AXIS] / 15.0f; - #if HAS_LCD_MENU && DISABLED(TOUCH_BUTTONS) // ui.button_pressed issue with touchscreen - #if ENABLED(PREVENT_LENGTHY_EXTRUDE) - float Total_Prime = 0.0; - #endif +#if HAS_LCD_MENU && DISABLED(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); + 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 = 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'. + destination.e += 0.25; +#if ENABLED(PREVENT_LENGTHY_EXTRUDE) + Total_Prime += 0.25; + if (Total_Prime >= EXTRUDE_MAXLENGTH) + { + ui.release(); + return G26_ERR; } - - ui.wait_for_release(); - - ui.set_status_P(GET_TEXT(MSG_G26_PRIME_DONE), 99); - ui.quick_feedback(); - ui.release(); +#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'. } - else - #endif + + ui.wait_for_release(); + + ui.set_status_P(GET_TEXT(MSG_G26_PRIME_DONE), 99); + ui.quick_feedback(); + ui.release(); + } + else +#endif { - #if HAS_SPI_LCD - ui.set_status_P(GET_TEXT(MSG_G26_FIXED_LENGTH), 99); - ui.quick_feedback(); - #endif +#if HAS_SPI_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); @@ -497,93 +534,150 @@ inline bool prime_nozzle() { * X X position * Y Y position */ -void GcodeSuite::G26() { +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 (axis_unhomed_error()) return; + if (axis_unhomed_error()) + return; // Change the tool first, if specified - if (parser.seenval('T')) tool_change(parser.value_int()); + 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; + g26_extrusion_multiplier = EXTRUSION_MULTIPLIER; + g26_retraction_length = RETRACTION_LENGTH; + g26_unretraction_length = UNRETRACTION_LENGTH; + 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, + float g26_nozzle = MESH_TEST_NOZZLE_SIZE, g26_filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA, - g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT); + g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT); bool g26_continue_with_closest = parser.boolval('C'), - g26_keep_heaters_on = parser.boolval('K'); + g26_keep_heaters_on = parser.boolval('K'); - #if HAS_HEATED_BED - if (parser.seenval('B')) { - g26_bed_temp = parser.value_celsius(); - if (g26_bed_temp && !WITHIN(g26_bed_temp, 40, (BED_MAXTEMP - 10))) { - SERIAL_ECHOLNPAIR("?Specified bed temperature not plausible (40-", int(BED_MAXTEMP - 10), "C)."); - return; - } +#if HAS_HEATED_BED + if (parser.seenval('B')) + { + g26_bed_temp = parser.value_celsius(); + if (g26_bed_temp && !WITHIN(g26_bed_temp, 40, (BED_MAXTEMP - 10))) + { + SERIAL_ECHOLNPAIR("?Specified bed temperature not plausible (40-", int(BED_MAXTEMP - 10), "C)."); + return; } - #endif + } +#endif - if (parser.seenval('L')) { + if (parser.seenval('L')) + { g26_layer_height = parser.value_linear_units(); - if (!WITHIN(g26_layer_height, 0.0, 2.0)) { + 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."); + if (parser.seen('Q')) + { + if (parser.has_value()) + { + g26_retraction_length = parser.value_float(); + if (!WITHIN(g26_retraction_length, 0.05, 15.0)) + { + SERIAL_ECHOLNPGM("?Specified Retraction length not plausible."); return; } } - else { - SERIAL_ECHOLNPGM("?Retraction Multiplier must be specified."); + else + { + SERIAL_ECHOLNPGM("?Retraction length must be specified."); return; } } - if (parser.seenval('S')) { + if (parser.seen('Z')) + { + if (parser.has_value()) + { + g26_unretraction_length = parser.value_float(); + if (!WITHIN(g26_unretraction_length, 0.05, 15.0)) + { + SERIAL_ECHOLNPGM("?Specified Unretraction length not plausible."); + return; + } + } + else + { + SERIAL_ECHOLNPGM("?Unretraction length must be specified."); + return; + } + } + + if (!parser.seen('Z') && parser.seen('Q')) + { + // retraction without unretraction specified, use 1.2 multiplier (preserve Gcode spec) + g26_unretraction_length = g26_retraction_length * 1.2; + SERIAL_ECHOPAIR(" Unretraction amount automatically set to ", g26_unretraction_length); + SERIAL_EOL(); + } + + if (parser.seen('Z') && parser.seen('Q')) + { + // consider typos or unreasonable retract/unretract ratios + float g26_retract_unretract_delta = g26_unretraction_length - g26_retraction_length; + if (!WITHIN(g26_retract_unretract_delta, -5, 5)) + { + SERIAL_ECHOLNPGM("?Invalid Retraction/Unretraction ratio. Must be within 5mm."); + return; + } + } + + if (parser.seenval('S')) + { g26_nozzle = parser.value_float(); - if (!WITHIN(g26_nozzle, 0.1, 2.0)) { + 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 + 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 { + else + { g26_prime_flag++; g26_prime_length = parser.value_linear_units(); - if (!WITHIN(g26_prime_length, 0.0, 25.0)) { + if (!WITHIN(g26_prime_length, 0.0, 25.0)) + { SERIAL_ECHOLNPGM("?Specified prime length not plausible."); return; } } } - if (parser.seenval('F')) { + if (parser.seenval('F')) + { g26_filament_diameter = parser.value_linear_units(); - if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) { + if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) + { SERIAL_ECHOLNPGM("?Specified filament size not plausible."); return; } @@ -594,39 +688,45 @@ void GcodeSuite::G26() { g26_extrusion_multiplier *= g26_filament_diameter * sq(g26_nozzle) / sq(0.3); // Scale up by nozzle size - if (parser.seenval('H')) { + if (parser.seenval('H')) + { g26_hotend_temp = parser.value_celsius(); - if (!WITHIN(g26_hotend_temp, 165, (HEATER_0_MAXTEMP - 15))) { + if (!WITHIN(g26_hotend_temp, 165, (HEATER_0_MAXTEMP - 15))) + { SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible."); return; } } - if (parser.seen('U')) { + if (parser.seen('U')) + { randomSeed(millis()); // This setting will persist for the next G26 random_deviation = parser.has_value() ? parser.value_float() : 50.0; } 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) { +#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; } 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)) { + if (!position_is_reachable(g26_xy_pos)) + { SERIAL_ECHOLNPGM("?Specified X,Y coordinate out of bounds."); return; } @@ -639,18 +739,20 @@ void GcodeSuite::G26() { if (current_position.z < Z_CLEARANCE_BETWEEN_PROBES) do_blocking_move_to_z(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 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; + 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; + if (g26_prime_flag && prime_nozzle() != G26_OK) + goto LEAVE; /** * Bed is preheated @@ -672,40 +774,43 @@ void GcodeSuite::G26() { move_to(destination, 0.0); move_to(destination, g26_ooze_amount); - #if HAS_LCD_MENU - ui.capture(); - #endif +#if HAS_LCD_MENU + ui.capture(); +#endif - #if DISABLED(ARC_SUPPORT) +#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)); +#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 +#endif // !ARC_SUPPORT mesh_index_pair location; - do { + 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()) { + 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; + if (!position_is_reachable(circle)) + continue; // Determine where to start and end the circle, // which is always drawn counter-clockwise. @@ -714,140 +819,166 @@ void GcodeSuite::G26() { r = st.x >= GRID_MAX_POINTS_X - 1, b = st.y >= GRID_MAX_POINTS_Y - 1; - #if ENABLED(ARC_SUPPORT) +#if ENABLED(ARC_SUPPORT) - #define ARC_LENGTH(quarters) (INTERSECTION_CIRCLE_RADIUS * M_PI * (quarters) / 2) - #define INTERSECTION_CIRCLE_DIAM ((INTERSECTION_CIRCLE_RADIUS) * 2) +#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; + 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); + // 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; } - 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); + 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 = { + 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) - }; + 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); - } + 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 + s.z = g26_layer_height; + move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift - recover_filament(destination); + 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); // Draw a counter-clockwise arc - feedrate_mm_s = old_feedrate; - destination = current_position; + const feedRate_t old_feedrate = feedrate_mm_s; + feedrate_mm_s = PLANNER_XY_FEEDRATE() * 0.1f; + plan_arc(endpoint, arc_offset, false); // Draw a counter-clockwise arc + feedrate_mm_s = old_feedrate; + destination = current_position; - #if HAS_LCD_MENU - if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation - #endif +#if HAS_LCD_MENU + if (user_canceled()) + goto LEAVE; // Check if the user wants to stop the Mesh Validation +#endif - #else // !ARC_SUPPORT +#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 - } + 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++) { + for (int8_t ind = start_ind; ind <= end_ind; ind++) + { - #if HAS_LCD_MENU - if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation - #endif +#if HAS_LCD_MENU + if (user_canceled()) + goto LEAVE; // Check if the user wants to stop the Mesh Validation +#endif - 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 }; + 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 +#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. - } + print_line_from_here_to_there(p, q); + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } - #endif // !ARC_SUPPORT +#endif // !ARC_SUPPORT - if (look_for_lines_to_connect()) goto LEAVE; + if (look_for_lines_to_connect()) + goto LEAVE; } SERIAL_FLUSH(); // Prevent host M105 buffer overrun. } while (--g26_repeats && location.valid()); - LEAVE: +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 + 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 + 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 +#if DISABLED(NO_VOLUMETRICS) + parser.volumetric_enabled = volumetric_was_enabled; + planner.calculate_volumetric_multipliers(); +#endif - #if HAS_LCD_MENU - ui.release(); // Give back control of the LCD - #endif +#if HAS_LCD_MENU + ui.release(); // Give back control of the LCD +#endif - if (!g26_keep_heaters_on) { - #if HAS_HEATED_BED - thermalManager.setTargetBed(0); - #endif + if (!g26_keep_heaters_on) + { +#if HAS_HEATED_BED + thermalManager.setTargetBed(0); +#endif thermalManager.setTargetHotend(active_extruder, 0); } }