/** * G26: Mesh Validation Pattern generation. * * Used to interactively edit UBL's Mesh by placing the * nozzle in a problem area and doing a G29 P4 R command. */ void unified_bed_leveling::G26() { SERIAL_ECHOLNPGM("G26 command started. Waiting for heater(s)."); float tmp, start_angle, end_angle; int i, xi, yi; mesh_index_pair location; // Don't allow Mesh Validation without homing first, // or if the parameter parsing did not go OK, abort if (axis_unhomed_error() || parse_G26_parameters()) return; if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) { do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES); stepper.synchronize(); set_current_to_destination(); } if (turn_on_heaters()) goto LEAVE; current_position[E_AXIS] = 0.0; sync_plan_position_e(); if (g26_prime_flag && prime_nozzle()) goto LEAVE; /** * Bed is preheated * * Nozzle is at temperature * * Filament is primed! * * It's "Show Time" !!! */ ZERO(circle_flags); ZERO(horizontal_mesh_line_flags); ZERO(vertical_mesh_line_flags); // Move nozzle to the specified height for the first layer set_destination_to_current(); destination[Z_AXIS] = g26_layer_height; move_to(destination, 0.0); move_to(destination, g26_ooze_amount); has_control_of_lcd_panel = true; //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern.")); /** * Declare and generate a sin() & cos() table to be used during the circle drawing. This will lighten * the CPU load and make the arc drawing faster and more smooth */ float sin_table[360 / 30 + 1], cos_table[360 / 30 + 1]; for (i = 0; i <= 360 / 30; i++) { cos_table[i] = SIZE_OF_INTERSECTION_CIRCLES * cos(RADIANS(valid_trig_angle(i * 30.0))); sin_table[i] = SIZE_OF_INTERSECTION_CIRCLES * sin(RADIANS(valid_trig_angle(i * 30.0))); } do { location = g26_continue_with_closest ? find_closest_circle_to_print(current_position[X_AXIS], current_position[Y_AXIS]) : find_closest_circle_to_print(g26_x_pos, g26_y_pos); // Find the closest Mesh Intersection to where we are now. if (location.x_index >= 0 && location.y_index >= 0) { const float circle_x = mesh_index_to_xpos(location.x_index), circle_y = mesh_index_to_ypos(location.y_index); // If this mesh location is outside the printable_radius, skip it. if (!position_is_reachable_raw_xy(circle_x, circle_y)) continue; xi = location.x_index; // Just to shrink the next few lines and make them easier to understand yi = location.y_index; if (g26_debug_flag) { SERIAL_ECHOPAIR(" Doing circle at: (xi=", xi); SERIAL_ECHOPAIR(", yi=", yi); SERIAL_CHAR(')'); SERIAL_EOL(); } start_angle = 0.0; // assume it is going to be a full circle end_angle = 360.0; if (xi == 0) { // Check for bottom edge start_angle = -90.0; end_angle = 90.0; if (yi == 0) // it is an edge, check for the two left corners start_angle = 0.0; else if (yi == GRID_MAX_POINTS_Y - 1) end_angle = 0.0; } else if (xi == GRID_MAX_POINTS_X - 1) { // Check for top edge start_angle = 90.0; end_angle = 270.0; if (yi == 0) // it is an edge, check for the two right corners end_angle = 180.0; else if (yi == GRID_MAX_POINTS_Y - 1) start_angle = 180.0; } else if (yi == 0) { start_angle = 0.0; // only do the top side of the cirlce end_angle = 180.0; } else if (yi == GRID_MAX_POINTS_Y - 1) { start_angle = 180.0; // only do the bottom side of the cirlce end_angle = 360.0; } for (tmp = start_angle; tmp < end_angle - 0.1; tmp += 30.0) { #if ENABLED(NEWPANEL) if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif int tmp_div_30 = tmp / 30.0; if (tmp_div_30 < 0) tmp_div_30 += 360 / 30; if (tmp_div_30 > 11) tmp_div_30 -= 360 / 30; float x = circle_x + cos_table[tmp_div_30], // for speed, these are now a lookup table entry y = circle_y + sin_table[tmp_div_30], xe = circle_x + cos_table[tmp_div_30 + 1], ye = circle_y + sin_table[tmp_div_30 + 1]; #if IS_KINEMATIC // Check to make sure this segment is entirely on the bed, skip if not. if (!position_is_reachable_raw_xy(x, y) || !position_is_reachable_raw_xy(xe, ye)) continue; #else // not, we need to skip x = constrain(x, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops y = constrain(y, Y_MIN_POS + 1, Y_MAX_POS - 1); xe = constrain(xe, X_MIN_POS + 1, X_MAX_POS - 1); ye = constrain(ye, Y_MIN_POS + 1, Y_MAX_POS - 1); #endif //if (g26_debug_flag) { // char ccc, *cptr, seg_msg[50], seg_num[10]; // strcpy(seg_msg, " segment: "); // strcpy(seg_num, " \n"); // cptr = (char*) "01234567890ABCDEF????????"; // ccc = cptr[tmp_div_30]; // seg_num[1] = ccc; // strcat(seg_msg, seg_num); // debug_current_and_destination(seg_msg); //} print_line_from_here_to_there(LOGICAL_X_POSITION(x), LOGICAL_Y_POSITION(y), g26_layer_height, LOGICAL_X_POSITION(xe), LOGICAL_Y_POSITION(ye), g26_layer_height); } if (look_for_lines_to_connect()) goto LEAVE; } } while (--g26_repeats && location.x_index >= 0 && location.y_index >= 0); LEAVE: lcd_setstatusPGM(PSTR("Leaving G26"), -1); retract_filament(destination); destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; //debug_current_and_destination(PSTR("ready to do Z-Raise.")); move_to(destination, 0); // Raise the nozzle //debug_current_and_destination(PSTR("done doing Z-Raise.")); destination[X_AXIS] = g26_x_pos; // Move back to the starting position destination[Y_AXIS] = g26_y_pos; //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; // Keep the nozzle where it is move_to(destination, 0); // Move back to the starting position //debug_current_and_destination(PSTR("done doing X/Y move.")); has_control_of_lcd_panel = false; // Give back control of the LCD Panel! if (!g26_keep_heaters_on) { #if HAS_TEMP_BED thermalManager.setTargetBed(0); #endif thermalManager.setTargetHotend(0, 0); } }
/** * G26: Mesh Validation Pattern generation. * * Used to interactively edit UBL's Mesh by placing the * nozzle in a problem area and doing a G29 P4 R command. */ void gcode_G26() { SERIAL_ECHOLNPGM("G26 command started. Waiting for heater(s)."); float tmp, start_angle, end_angle; int i, xi, yi; mesh_index_pair location; // Don't allow Mesh Validation without homing first, // or if the parameter parsing did not go OK, abort if (axis_unhomed_error(true, true, true) || parse_G26_parameters()) return; if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) { do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES); stepper.synchronize(); set_current_to_destination(); } if (turn_on_heaters()) goto LEAVE; current_position[E_AXIS] = 0.0; sync_plan_position_e(); if (prime_flag && prime_nozzle()) goto LEAVE; /** * Bed is preheated * * Nozzle is at temperature * * Filament is primed! * * It's "Show Time" !!! */ ZERO(circle_flags); ZERO(horizontal_mesh_line_flags); ZERO(vertical_mesh_line_flags); // Move nozzle to the specified height for the first layer set_destination_to_current(); destination[Z_AXIS] = layer_height; move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], 0.0); move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], ooze_amount); ubl.has_control_of_lcd_panel = true; //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern.")); /** * Declare and generate a sin() & cos() table to be used during the circle drawing. This will lighten * the CPU load and make the arc drawing faster and more smooth */ float sin_table[360 / 30 + 1], cos_table[360 / 30 + 1]; for (i = 0; i <= 360 / 30; i++) { cos_table[i] = SIZE_OF_INTERSECTION_CIRCLES * cos(RADIANS(valid_trig_angle(i * 30.0))); sin_table[i] = SIZE_OF_INTERSECTION_CIRCLES * sin(RADIANS(valid_trig_angle(i * 30.0))); } do { if (ubl_lcd_clicked()) { // Check if the user wants to stop the Mesh Validation #if ENABLED(ULTRA_LCD) lcd_setstatuspgm(PSTR("Mesh Validation Stopped."), 99); lcd_quick_feedback(); #endif while (!ubl_lcd_clicked()) { // Wait until the user is done pressing the idle(); // Encoder Wheel if that is why we are leaving lcd_reset_alert_level(); lcd_setstatuspgm(PSTR("")); } while (ubl_lcd_clicked()) { // Wait until the user is done pressing the idle(); // Encoder Wheel if that is why we are leaving lcd_setstatuspgm(PSTR("Unpress Wheel"), 99); } goto LEAVE; } location = continue_with_closest ? find_closest_circle_to_print(current_position[X_AXIS], current_position[Y_AXIS]) : find_closest_circle_to_print(x_pos, y_pos); // Find the closest Mesh Intersection to where we are now. if (location.x_index >= 0 && location.y_index >= 0) { const float circle_x = pgm_read_float(&ubl.mesh_index_to_xpos[location.x_index]), circle_y = pgm_read_float(&ubl.mesh_index_to_ypos[location.y_index]); // Let's do a couple of quick sanity checks. We can pull this code out later if we never see it catch a problem #ifdef DELTA if (HYPOT2(circle_x, circle_y) > sq(DELTA_PRINTABLE_RADIUS)) { SERIAL_ERROR_START; SERIAL_ERRORLNPGM("Attempt to print outside of DELTA_PRINTABLE_RADIUS."); goto LEAVE; } #endif // TODO: Change this to use `position_is_reachable` if (!WITHIN(circle_x, X_MIN_POS, X_MAX_POS) || !WITHIN(circle_y, Y_MIN_POS, Y_MAX_POS)) { SERIAL_ERROR_START; SERIAL_ERRORLNPGM("Attempt to print off the bed."); goto LEAVE; } xi = location.x_index; // Just to shrink the next few lines and make them easier to understand yi = location.y_index; if (ubl.g26_debug_flag) { SERIAL_ECHOPAIR(" Doing circle at: (xi=", xi); SERIAL_ECHOPAIR(", yi=", yi); SERIAL_CHAR(')'); SERIAL_EOL; } start_angle = 0.0; // assume it is going to be a full circle end_angle = 360.0; if (xi == 0) { // Check for bottom edge start_angle = -90.0; end_angle = 90.0; if (yi == 0) // it is an edge, check for the two left corners start_angle = 0.0; else if (yi == GRID_MAX_POINTS_Y - 1) end_angle = 0.0; } else if (xi == GRID_MAX_POINTS_X - 1) { // Check for top edge start_angle = 90.0; end_angle = 270.0; if (yi == 0) // it is an edge, check for the two right corners end_angle = 180.0; else if (yi == GRID_MAX_POINTS_Y - 1) start_angle = 180.0; } else if (yi == 0) { start_angle = 0.0; // only do the top side of the cirlce end_angle = 180.0; } else if (yi == GRID_MAX_POINTS_Y - 1) { start_angle = 180.0; // only do the bottom side of the cirlce end_angle = 360.0; } for (tmp = start_angle; tmp < end_angle - 0.1; tmp += 30.0) { int tmp_div_30 = tmp / 30.0; if (tmp_div_30 < 0) tmp_div_30 += 360 / 30; if (tmp_div_30 > 11) tmp_div_30 -= 360 / 30; float x = circle_x + cos_table[tmp_div_30], // for speed, these are now a lookup table entry y = circle_y + sin_table[tmp_div_30], xe = circle_x + cos_table[tmp_div_30 + 1], ye = circle_y + sin_table[tmp_div_30 + 1]; #ifdef DELTA if (HYPOT2(x, y) > sq(DELTA_PRINTABLE_RADIUS)) // Check to make sure this part of continue; // the 'circle' is on the bed. If #else // not, we need to skip x = constrain(x, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops y = constrain(y, Y_MIN_POS + 1, Y_MAX_POS - 1); xe = constrain(xe, X_MIN_POS + 1, X_MAX_POS - 1); ye = constrain(ye, Y_MIN_POS + 1, Y_MAX_POS - 1); #endif //if (ubl.g26_debug_flag) { // char ccc, *cptr, seg_msg[50], seg_num[10]; // strcpy(seg_msg, " segment: "); // strcpy(seg_num, " \n"); // cptr = (char*) "01234567890ABCDEF????????"; // ccc = cptr[tmp_div_30]; // seg_num[1] = ccc; // strcat(seg_msg, seg_num); // debug_current_and_destination(seg_msg); //} print_line_from_here_to_there(LOGICAL_X_POSITION(x), LOGICAL_Y_POSITION(y), layer_height, LOGICAL_X_POSITION(xe), LOGICAL_Y_POSITION(ye), layer_height); } //debug_current_and_destination(PSTR("Looking for lines to connect.")); look_for_lines_to_connect(); //debug_current_and_destination(PSTR("Done with line connect.")); } //debug_current_and_destination(PSTR("Done with current circle.")); } while (location.x_index >= 0 && location.y_index >= 0); LEAVE: lcd_reset_alert_level(); lcd_setstatuspgm(PSTR("Leaving G26")); retract_filament(destination); destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; //debug_current_and_destination(PSTR("ready to do Z-Raise.")); move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], 0); // Raise the nozzle //debug_current_and_destination(PSTR("done doing Z-Raise.")); destination[X_AXIS] = x_pos; // Move back to the starting position destination[Y_AXIS] = y_pos; //destination[Z_AXIS] = Z_CLEARANCE_BETWEEN_PROBES; // Keep the nozzle where it is move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], 0); // Move back to the starting position //debug_current_and_destination(PSTR("done doing X/Y move.")); ubl.has_control_of_lcd_panel = false; // Give back control of the LCD Panel! if (!keep_heaters_on) { #if HAS_TEMP_BED thermalManager.setTargetBed(0); #endif thermalManager.setTargetHotend(0, 0); } }