/** * M900: Get or Set Linear Advance K-factor * * K<factor> Set advance K factor */ void GcodeSuite::M900() { #if EXTRUDERS < 2 constexpr uint8_t tmp_extruder = 0; #else const uint8_t tmp_extruder = parser.seenval('T') ? parser.value_int() : active_extruder; if (tmp_extruder >= EXTRUDERS) { SERIAL_ECHOLNPGM("?T value out of range."); return; } #endif if (parser.seenval('K')) { const float newK = parser.floatval('K'); if (WITHIN(newK, 0, 10)) { planner.synchronize(); planner.extruder_advance_K[tmp_extruder] = newK; } else SERIAL_ECHOLNPGM("?K value out of range (0-10)."); } else { SERIAL_ECHO_START(); #if EXTRUDERS < 2 SERIAL_ECHOLNPAIR("Advance K=", planner.extruder_advance_K[0]); #else SERIAL_ECHOPGM("Advance K"); LOOP_L_N(i, EXTRUDERS) { SERIAL_CHAR(' '); SERIAL_ECHO(int(i)); SERIAL_CHAR('='); SERIAL_ECHO(planner.extruder_advance_K[i]); } SERIAL_EOL(); #endif }
/** * M100 D * Dump the free memory block from __brkval to the stack pointer. * malloc() eats memory from the start of the block and the stack grows * up from the bottom of the block. Solid test bytes indicate nothing has * used that memory yet. There should not be anything but test bytes within * the block. If so, it may indicate memory corruption due to a bad pointer. * Unexpected bytes are flagged in the right column. */ void dump_free_memory(const char *ptr, const char *sp) { // // Start and end the dump on a nice 16 byte boundary // (even though the values are not 16-byte aligned). // ptr = (char *)((uint16_t)ptr & 0xFFF0); // Align to 16-byte boundary sp = (char *)((uint16_t)sp | 0x000F); // Align sp to the 15th byte (at or above sp) // Dump command main loop while (ptr < sp) { print_hex_word((uint16_t)ptr); // Print the address SERIAL_CHAR(':'); for (uint8_t i = 0; i < 16; i++) { // and 16 data bytes if (i == 8) SERIAL_CHAR('-'); print_hex_byte(ptr[i]); SERIAL_CHAR(' '); } safe_delay(25); SERIAL_CHAR('|'); // Point out non test bytes for (uint8_t i = 0; i < 16; i++) { char ccc = (char)ptr[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken if (&ptr[i] >= (const char*)command_queue && &ptr[i] < (const char*)(command_queue + sizeof(command_queue))) { // Print out ASCII in the command buffer area if (!WITHIN(ccc, ' ', 0x7E)) ccc = ' '; } else { // If not in the command buffer area, flag bytes that don't match the test byte ccc = (ccc == TEST_BYTE) ? ' ' : '?'; } SERIAL_CHAR(ccc); } SERIAL_EOL(); ptr += 16; safe_delay(25); idle(); } }
static void serial_echo_xy(const int16_t x, const int16_t y) { SERIAL_CHAR('('); SERIAL_ECHO(x); SERIAL_CHAR(','); SERIAL_ECHO(y); SERIAL_CHAR(')'); safe_delay(10); }
void print_xyz(PGM_P const prefix, PGM_P const suffix, const float x, const float y, const float z) { serialprintPGM(prefix); SERIAL_CHAR('('); SERIAL_ECHO(x); SERIAL_ECHOPAIR(", ", y); SERIAL_ECHOPAIR(", ", z); SERIAL_CHAR(')'); if (suffix) serialprintPGM(suffix); else SERIAL_EOL(); }
void PrintJobRecovery::debug(PGM_P const prefix) { serialprintPGM(prefix); SERIAL_ECHOLNPAIR(" Job Recovery Info...\nvalid_head:", int(info.valid_head), " valid_foot:", int(info.valid_foot)); if (info.valid_head) { if (info.valid_head == info.valid_foot) { SERIAL_ECHOPGM("current_position: "); LOOP_XYZE(i) { SERIAL_ECHO(info.current_position[i]); if (i < E_AXIS) SERIAL_CHAR(','); } SERIAL_EOL(); SERIAL_ECHOLNPAIR("feedrate: ", info.feedrate); #if HOTENDS > 1 SERIAL_ECHOLNPAIR("active_hotend: ", int(info.active_hotend)); #endif SERIAL_ECHOPGM("target_temperature: "); HOTEND_LOOP() { SERIAL_ECHO(info.target_temperature[e]); if (e < HOTENDS - 1) SERIAL_CHAR(','); } SERIAL_EOL(); #if HAS_HEATED_BED SERIAL_ECHOLNPAIR("target_temperature_bed: ", info.target_temperature_bed); #endif #if FAN_COUNT SERIAL_ECHOPGM("fan_speed: "); FANS_LOOP(i) { SERIAL_ECHO(int(info.fan_speed[i])); if (i < FAN_COUNT - 1) SERIAL_CHAR(','); } SERIAL_EOL(); #endif #if HAS_LEVELING SERIAL_ECHOLNPAIR("leveling: ", int(info.leveling), "\n fade: ", int(info.fade)); #endif #if ENABLED(FWRETRACT) SERIAL_ECHOPGM("retract: "); for (int8_t e = 0; e < EXTRUDERS; e++) { SERIAL_ECHO(info.retract[e]); if (e < EXTRUDERS - 1) SERIAL_CHAR(','); } SERIAL_EOL(); SERIAL_ECHOLNPAIR("retract_hop: ", info.retract_hop); #endif SERIAL_ECHOLNPAIR("cmd_queue_index_r: ", int(info.cmd_queue_index_r)); SERIAL_ECHOLNPAIR("commands_in_queue: ", int(info.commands_in_queue)); for (uint8_t i = 0; i < info.commands_in_queue; i++) SERIAL_ECHOLNPAIR("> ", info.command_queue[i]); SERIAL_ECHOLNPAIR("sd_filename: ", info.sd_filename); SERIAL_ECHOLNPAIR("sdpos: ", info.sdpos); SERIAL_ECHOLNPAIR("print_job_elapsed: ", info.print_job_elapsed); } else
void report_xyze(const float pos[XYZE], const uint8_t n = 4, const uint8_t precision = 3) { char str[12]; for (uint8_t i = 0; i < n; i++) { SERIAL_CHAR(' '); SERIAL_CHAR(axis_codes[i]); SERIAL_CHAR(':'); SERIAL_PROTOCOL(dtostrf(pos[i], 8, precision, str)); } SERIAL_EOL(); }
bool set_bltouch_deployed(const bool deploy) { if (deploy && TEST_BLTOUCH()) { // If BL-Touch says it's triggered bltouch_command(BLTOUCH_RESET); // try to reset it. bltouch_command(BLTOUCH_DEPLOY); // Also needs to deploy and stow to bltouch_command(BLTOUCH_STOW); // clear the triggered condition. safe_delay(1500); // Wait for internal self-test to complete. // (Measured completion time was 0.65 seconds // after reset, deploy, and stow sequence) if (TEST_BLTOUCH()) { // If it still claims to be triggered... SERIAL_ERROR_START(); SERIAL_ERRORLNPGM(MSG_STOP_BLTOUCH); stop(); // punt! return true; } } bltouch_command(deploy ? BLTOUCH_DEPLOY : BLTOUCH_STOW); #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("set_bltouch_deployed(", deploy); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif return false; }
int check_for_free_memory_corruption(const char * const title) { SERIAL_ECHO(title); char *ptr = END_OF_HEAP(), *sp = top_of_stack(); int n = sp - ptr; SERIAL_ECHOPAIR("\nfmc() n=", n); SERIAL_ECHOPAIR("\n&__brkval: ", hex_address(&__brkval)); SERIAL_ECHOPAIR("=", hex_address(__brkval)); SERIAL_ECHOPAIR("\n__bss_end: ", hex_address(&__bss_end)); SERIAL_ECHOPAIR(" sp=", hex_address(sp)); if (sp < ptr) { SERIAL_ECHOPGM(" sp < Heap "); // SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board // safe_delay(5); // this code can be enabled to pause the display as soon as the // while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch // idle(); // being on pin-63 which is unassigend and available on most controller // safe_delay(20); // boards. // while ( !READ(63)) // idle(); safe_delay(20); #ifdef M100_FREE_MEMORY_DUMPER M100_dump_routine(" Memory corruption detected with sp<Heap\n", (char*)0x1B80, (char*)0x21FF); #endif } // Scan through the range looking for the biggest block of 0xE5's we can find int block_cnt = 0; for (int i = 0; i < n; i++) { if (ptr[i] == TEST_BYTE) { int16_t j = count_test_bytes(ptr + i); if (j > 8) { // SERIAL_ECHOPAIR("Found ", j); // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(ptr + i)); i += j; block_cnt++; SERIAL_ECHOPAIR(" (", block_cnt); SERIAL_ECHOPAIR(") found=", j); SERIAL_ECHOPGM(" "); } } } SERIAL_ECHOPAIR(" block_found=", block_cnt); if (block_cnt != 1 || __brkval != 0x0000) SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area."); if (block_cnt == 0) // Make sure the special case of no free blocks shows up as an block_cnt = -1; // error to the calling code! SERIAL_ECHOPGM(" return="); if (block_cnt == 1) { SERIAL_CHAR('0'); // if the block_cnt is 1, nothing has broken up the free memory SERIAL_EOL(); // area and it is appropriate to say 'no corruption'. return 0; } SERIAL_ECHOLNPGM("true"); return block_cnt; }
void Mixer::normalize(const uint8_t tool_index) { float cmax = 0; #ifdef MIXER_NORMALIZER_DEBUG float csum = 0; #endif MIXER_STEPPER_LOOP(i) { const float v = collector[i]; NOLESS(cmax, v); #ifdef MIXER_NORMALIZER_DEBUG csum += v; #endif } #ifdef MIXER_NORMALIZER_DEBUG SERIAL_ECHOPGM("Mixer: Old relation : [ "); MIXER_STEPPER_LOOP(i) { SERIAL_ECHO_F(collector[i] / csum, 3); SERIAL_CHAR(' '); } SERIAL_ECHOLNPGM("]"); #endif // Scale all values so their maximum is COLOR_A_MASK const float scale = float(COLOR_A_MASK) / cmax; MIXER_STEPPER_LOOP(i) color[tool_index][i] = collector[i] * scale; #ifdef MIXER_NORMALIZER_DEBUG csum = 0; SERIAL_ECHOPGM("Mixer: Normalize to : [ "); MIXER_STEPPER_LOOP(i) { SERIAL_ECHO(uint16_t(color[tool_index][i])); SERIAL_CHAR(' '); csum += color[tool_index][i]; } SERIAL_ECHOLNPGM("]"); SERIAL_ECHOPGM("Mixer: New relation : [ "); MIXER_STEPPER_LOOP(i) { SERIAL_ECHO_F(uint16_t(color[tool_index][i]) / csum, 3); SERIAL_CHAR(' '); } SERIAL_ECHOLNPGM("]"); #endif #if ENABLED(GRADIENT_MIX) refresh_gradient(); #endif }
static void serial_echo_column_labels(const uint8_t sp) { SERIAL_ECHO_SP(7); for (int8_t i = 0; i < GRID_MAX_POINTS_X; i++) { if (i < 10) SERIAL_CHAR(' '); SERIAL_ECHO(i); SERIAL_ECHO_SP(sp); } serial_delay(10); }
void GCodeParser::debug() { SERIAL_ECHOPAIR("Command: ", command_ptr); SERIAL_ECHOPAIR(" (", command_letter); SERIAL_ECHO(codenum); SERIAL_ECHOLNPGM(")"); #if ENABLED(FASTER_GCODE_PARSER) SERIAL_ECHO(" args: \""); for (char c = 'A'; c <= 'Z'; ++c) if (seen(c)) { SERIAL_CHAR(c); SERIAL_CHAR(' '); } #else SERIAL_ECHOPAIR(" args: \"", command_args); #endif SERIAL_ECHOPGM("\""); if (string_arg) { SERIAL_ECHOPGM(" string: \""); SERIAL_ECHO(string_arg); SERIAL_CHAR('"'); } SERIAL_ECHOPGM("\n\n"); for (char c = 'A'; c <= 'Z'; ++c) { if (seen(c)) { SERIAL_ECHOPAIR("Code '", c); SERIAL_ECHOPGM("':"); if (has_value()) { SERIAL_ECHOPAIR("\n float: ", value_float()); SERIAL_ECHOPAIR("\n long: ", value_long()); SERIAL_ECHOPAIR("\n ulong: ", value_ulong()); SERIAL_ECHOPAIR("\n millis: ", value_millis()); SERIAL_ECHOPAIR("\n sec-ms: ", value_millis_from_seconds()); SERIAL_ECHOPAIR("\n int: ", value_int()); SERIAL_ECHOPAIR("\n ushort: ", value_ushort()); SERIAL_ECHOPAIR("\n byte: ", (int)value_byte()); SERIAL_ECHOPAIR("\n bool: ", (int)value_bool()); SERIAL_ECHOPAIR("\n linear: ", value_linear_units()); SERIAL_ECHOPAIR("\n celsius: ", value_celsius()); } else SERIAL_ECHOPGM(" (no value)"); SERIAL_ECHOPGM("\n\n"); } } }
static void serial_echo_xy(const uint8_t sp, const int16_t x, const int16_t y) { SERIAL_ECHO_SP(sp); SERIAL_CHAR('('); if (x < 100) { SERIAL_CHAR(' '); if (x < 10) SERIAL_CHAR(' '); } SERIAL_ECHO(x); SERIAL_CHAR(','); if (y < 100) { SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); } SERIAL_ECHO(y); SERIAL_CHAR(')'); serial_delay(5); }
void report_M92( #if NUM_SERIAL > 1 const int8_t port, #endif const bool echo=true, const int8_t e=-1 ) { if (echo) SERIAL_ECHO_START_P(port); else SERIAL_CHAR(' '); SERIAL_ECHOPAIR_P(port, " M92 X", LINEAR_UNIT(planner.settings.axis_steps_per_mm[X_AXIS])); SERIAL_ECHOPAIR_P(port, " Y", LINEAR_UNIT(planner.settings.axis_steps_per_mm[Y_AXIS])); SERIAL_ECHOPAIR_P(port, " Z", LINEAR_UNIT(planner.settings.axis_steps_per_mm[Z_AXIS])); #if DISABLED(DISTINCT_E_FACTORS) SERIAL_ECHOPAIR_P(port, " E", VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS])); #endif SERIAL_EOL_P(port); #if ENABLED(DISTINCT_E_FACTORS) for (uint8_t i = 0; i < E_STEPPERS; i++) { if (e >= 0 && i != e) continue; if (echo) SERIAL_ECHO_START_P(port); else SERIAL_CHAR(' '); SERIAL_ECHOPAIR_P(port, " M92 T", (int)i); SERIAL_ECHOLNPAIR_P(port, " E", VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS_N(i)])); } #endif }
/** * Raise Z to a minimum height to make room for a probe to move */ inline void do_probe_raise(const float z_raise) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("do_probe_raise(", z_raise); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif float z_dest = z_raise; if (zprobe_zoffset < 0) z_dest -= zprobe_zoffset; if (z_dest > current_position[Z_AXIS]) do_blocking_move_to_z(z_dest); }
/** * Method to dock/undock a sled designed by Charles Bell. * * stow[in] If false, move to MAX_X and engage the solenoid * If true, move to MAX_X and release the solenoid */ static void dock_sled(bool stow) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPAIR("dock_sled(", stow); SERIAL_CHAR(')'); SERIAL_EOL(); } #endif // Dock sled a bit closer to ensure proper capturing do_blocking_move_to_x(X_MAX_POS + SLED_DOCKING_OFFSET - ((stow) ? 1 : 0)); #if HAS_SOLENOID_1 && DISABLED(EXT_SOLENOID) WRITE(SOL1_PIN, !stow); // switch solenoid #endif }
/** * Extrapolate a single point from its neighbors */ static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) { SERIAL_ECHOPGM("Extrapolate ["); if (x < 10) SERIAL_CHAR(' '); SERIAL_ECHO((int)x); SERIAL_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' '); SERIAL_CHAR(' '); if (y < 10) SERIAL_CHAR(' '); SERIAL_ECHO((int)y); SERIAL_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' '); SERIAL_CHAR(']'); } #endif if (!isnan(z_values[x][y])) { #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM(" (done)"); #endif return; // Don't overwrite good values. } SERIAL_EOL(); // Get X neighbors, Y neighbors, and XY neighbors const uint8_t x1 = x + xdir, y1 = y + ydir, x2 = x1 + xdir, y2 = y1 + ydir; float a1 = z_values[x1][y ], a2 = z_values[x2][y ], b1 = z_values[x ][y1], b2 = z_values[x ][y2], c1 = z_values[x1][y1], c2 = z_values[x2][y2]; // Treat far unprobed points as zero, near as equal to far if (isnan(a2)) a2 = 0.0; if (isnan(a1)) a1 = a2; if (isnan(b2)) b2 = 0.0; if (isnan(b1)) b1 = b2; if (isnan(c2)) c2 = 0.0; if (isnan(c1)) c1 = c2; const float a = 2 * a1 - a2, b = 2 * b1 - b2, c = 2 * c1 - c2; // Take the average instead of the median z_values[x][y] = (a + b + c) / 3.0; // Median is robust (ignores outliers). // z_values[x][y] = (a < b) ? ((b < c) ? b : (c < a) ? a : c) // : ((c < b) ? b : (a < c) ? a : c); }
void serial_spaces(uint8_t count) { count *= (PROPORTIONAL_FONT_RATIO); while (count--) SERIAL_CHAR(' '); }
void serial_echopair_PGM(PGM_P const s_P, char v) { serialprintPGM(s_P); SERIAL_CHAR(v); }
void serialprintPGM(PGM_P str) { while (char ch = pgm_read_byte(str++)) SERIAL_CHAR(ch); }
void log_machine_info() { SERIAL_ECHOLNPGM("Machine Type: " #if ENABLED(DELTA) "Delta" #elif IS_SCARA "SCARA" #elif IS_CORE "Core" #else "Cartesian" #endif ); SERIAL_ECHOLNPGM("Probe: " #if ENABLED(PROBE_MANUALLY) "PROBE_MANUALLY" #elif ENABLED(FIX_MOUNTED_PROBE) "FIX_MOUNTED_PROBE" #elif ENABLED(BLTOUCH) "BLTOUCH" #elif HAS_Z_SERVO_PROBE "SERVO PROBE" #elif ENABLED(Z_PROBE_SLED) "Z_PROBE_SLED" #elif ENABLED(Z_PROBE_ALLEN_KEY) "Z_PROBE_ALLEN_KEY" #else "NONE" #endif ); #if HAS_BED_PROBE SERIAL_ECHOPAIR( "Probe Offset X:" STRINGIFY(X_PROBE_OFFSET_FROM_EXTRUDER) " Y:" STRINGIFY(Y_PROBE_OFFSET_FROM_EXTRUDER) " Z:", zprobe_zoffset ); if ((X_PROBE_OFFSET_FROM_EXTRUDER) > 0) SERIAL_ECHOPGM(" (Right"); else if ((X_PROBE_OFFSET_FROM_EXTRUDER) < 0) SERIAL_ECHOPGM(" (Left"); else if ((Y_PROBE_OFFSET_FROM_EXTRUDER) != 0) SERIAL_ECHOPGM(" (Middle"); else SERIAL_ECHOPGM(" (Aligned With"); if ((Y_PROBE_OFFSET_FROM_EXTRUDER) > 0) { #if IS_SCARA SERIAL_ECHOPGM("-Distal"); #else SERIAL_ECHOPGM("-Back"); #endif } else if ((Y_PROBE_OFFSET_FROM_EXTRUDER) < 0) { #if IS_SCARA SERIAL_ECHOPGM("-Proximal"); #else SERIAL_ECHOPGM("-Front"); #endif } else if ((X_PROBE_OFFSET_FROM_EXTRUDER) != 0) SERIAL_ECHOPGM("-Center"); if (zprobe_zoffset < 0) SERIAL_ECHOPGM(" & Below"); else if (zprobe_zoffset > 0) SERIAL_ECHOPGM(" & Above"); else SERIAL_ECHOPGM(" & Same Z as"); SERIAL_ECHOLNPGM(" Nozzle)"); #endif #if HAS_ABL_OR_UBL SERIAL_ECHOLNPGM("Auto Bed Leveling: " #if ENABLED(AUTO_BED_LEVELING_LINEAR) "LINEAR" #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) "BILINEAR" #elif ENABLED(AUTO_BED_LEVELING_3POINT) "3POINT" #elif ENABLED(AUTO_BED_LEVELING_UBL) "UBL" #endif ); if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) SERIAL_ECHOLNPAIR("Z Fade: ", planner.z_fade_height); #endif #if ABL_PLANAR const float diff[XYZ] = { planner.get_axis_position_mm(X_AXIS) - current_position[X_AXIS], planner.get_axis_position_mm(Y_AXIS) - current_position[Y_AXIS], planner.get_axis_position_mm(Z_AXIS) - current_position[Z_AXIS] }; SERIAL_ECHOPGM("ABL Adjustment X"); if (diff[X_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[X_AXIS]); SERIAL_ECHOPGM(" Y"); if (diff[Y_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[Y_AXIS]); SERIAL_ECHOPGM(" Z"); if (diff[Z_AXIS] > 0) SERIAL_CHAR('+'); SERIAL_ECHO(diff[Z_AXIS]); #else #if ENABLED(AUTO_BED_LEVELING_UBL) SERIAL_ECHOPGM("UBL Adjustment Z"); const float rz = ubl.get_z_correction(current_position[X_AXIS], current_position[Y_AXIS]); #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) SERIAL_ECHOPGM("ABL Adjustment Z"); const float rz = bilinear_z_offset(current_position); #endif SERIAL_ECHO(ftostr43sign(rz, '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { SERIAL_ECHOPAIR(" (", ftostr43sign(rz * planner.fade_scaling_factor_for_z(current_position[Z_AXIS]), '+')); SERIAL_CHAR(')'); } #endif #endif } else SERIAL_ECHOLNPGM(" (disabled)"); SERIAL_EOL(); #elif ENABLED(MESH_BED_LEVELING) SERIAL_ECHOPGM("Mesh Bed Leveling"); if (planner.leveling_active) { SERIAL_ECHOLNPGM(" (enabled)"); SERIAL_ECHOPAIR("MBL Adjustment Z", ftostr43sign(mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS] #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) , 1.0 #endif ), '+')); #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) if (planner.z_fade_height) { SERIAL_ECHOPAIR(" (", ftostr43sign( mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS], planner.fade_scaling_factor_for_z(current_position[Z_AXIS])), '+' )); SERIAL_CHAR(')'); } #endif } else SERIAL_ECHOPGM(" (disabled)"); SERIAL_EOL(); #endif // MESH_BED_LEVELING }
void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, const uint8_t extruder) { /** * Much of the nozzle movement will be within the same cell. So we will do as little computation * as possible to determine if this is the case. If this move is within the same cell, we will * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave */ #if ENABLED(SKEW_CORRECTION) // For skew correction just adjust the destination point and we're done float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS] }, end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS] }; planner.skew(start[X_AXIS], start[Y_AXIS], start[Z_AXIS]); planner.skew(end[X_AXIS], end[Y_AXIS], end[Z_AXIS]); #else const float (&start)[XYZE] = current_position, (&end)[XYZE] = destination; #endif const int cell_start_xi = get_cell_index_x(start[X_AXIS]), cell_start_yi = get_cell_index_y(start[Y_AXIS]), cell_dest_xi = get_cell_index_x(end[X_AXIS]), cell_dest_yi = get_cell_index_y(end[Y_AXIS]); if (g26_debug_flag) { SERIAL_ECHOPAIR(" ubl.line_to_destination_cartesian(xe=", destination[X_AXIS]); SERIAL_ECHOPAIR(", ye=", destination[Y_AXIS]); SERIAL_ECHOPAIR(", ze=", destination[Z_AXIS]); SERIAL_ECHOPAIR(", ee=", destination[E_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Start of ubl.line_to_destination_cartesian()")); } if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) { // if the whole move is within the same cell, /** * we don't need to break up the move * * If we are moving off the print bed, we are going to allow the move at this level. * But we detect it and isolate it. For now, we just pass along the request. */ if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) { // Note: There is no Z Correction in this case. We are off the grid and don't know what // a reasonable correction would be. planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS], end[E_AXIS], feed_rate, extruder); set_current_from_destination(); if (g26_debug_flag) debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination_cartesian()")); return; } FINAL_MOVE: /** * Optimize some floating point operations here. We could call float get_z_correction(float x0, float y0) to * generate the correction for us. But we can lighten the load on the CPU by doing a modified version of the function. * We are going to only calculate the amount we are from the first mesh line towards the second mesh line once. * We will use this fraction in both of the original two Z Height calculations for the bi-linear interpolation. And, * instead of doing a generic divide of the distance, we know the distance is MESH_X_DIST so we can use the preprocessor * to create a 1-over number for us. That will allow us to do a floating point multiply instead of a floating point divide. */ const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * (1.0 / (MESH_X_DIST)); float z1 = z_values[cell_dest_xi ][cell_dest_yi ] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi ] - z_values[cell_dest_xi][cell_dest_yi ]), z2 = z_values[cell_dest_xi ][cell_dest_yi + 1] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]); if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0; // we are done with the fractional X distance into the cell. Now with the two Z-Heights we have calculated, we // are going to apply the Y-Distance into the cell to interpolate the final Z correction. const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * (1.0 / (MESH_Y_DIST)); float z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0; /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z0, end[E_AXIS], feed_rate, extruder); if (g26_debug_flag) debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination_cartesian()")); set_current_from_destination(); return; } /** * If we get here, we are processing a move that crosses at least one Mesh Line. We will check * for the simple case of just crossing X or just crossing Y Mesh Lines after we get all the details * of the move figured out. We can process the easy case of just crossing an X or Y Mesh Line with less * computation and in fact most lines are of this nature. We will check for that in the following * blocks of code: */ const float dx = end[X_AXIS] - start[X_AXIS], dy = end[Y_AXIS] - start[Y_AXIS]; const int left_flag = dx < 0.0 ? 1 : 0, down_flag = dy < 0.0 ? 1 : 0; const float adx = left_flag ? -dx : dx, ady = down_flag ? -dy : dy; const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1, dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1; /** * Compute the scaling factor for the extruder for each partial move. * We need to watch out for zero length moves because it will cause us to * have an infinate scaling factor. We are stuck doing a floating point * divide to get our scaling factor, but after that, we just multiply by this * number. We also pick our scaling factor based on whether the X or Y * component is larger. We use the biggest of the two to preserve precision. */ const bool use_x_dist = adx > ady; float on_axis_distance = use_x_dist ? dx : dy, e_position = end[E_AXIS] - start[E_AXIS], z_position = end[Z_AXIS] - start[Z_AXIS]; const float e_normalized_dist = e_position / on_axis_distance, z_normalized_dist = z_position / on_axis_distance; int current_xi = cell_start_xi, current_yi = cell_start_yi; const float m = dy / dx, c = start[Y_AXIS] - m * start[X_AXIS]; const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0), inf_m_flag = (isinf(m) != 0); /** * This block handles vertical lines. These are lines that stay within the same * X Cell column. They do not need to be perfectly vertical. They just can * not cross into another X Cell column. */ if (dxi == 0) { // Check for a vertical line current_yi += down_flag; // Line is heading down, we just want to go to the bottom while (current_yi != cell_dest_yi + down_flag) { current_yi += dyi; const float next_mesh_line_y = mesh_index_to_ypos(current_yi); /** * if the slope of the line is infinite, we won't do the calculations * else, we know the next X is the same so we can recover and continue! * Calculate X at the next Y mesh line */ const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m; float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; const float ry = mesh_index_to_ypos(current_yi); /** * Without this check, it is possible for the algorithm to generate a zero length move in the case * where the line is heading down and it is starting right on a Mesh Line boundary. For how often that * happens, it might be best to remove the check and always 'schedule' the move because * the planner.buffer_segment() routine will filter it if that happens. */ if (ry != start[Y_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination_cartesian()")); // // Check if we are at the final destination. Usually, we won't be, but if it is on a Y Mesh Line, we are done. // if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * This block handles horizontal lines. These are lines that stay within the same * Y Cell row. They do not need to be perfectly horizontal. They just can * not cross into another Y Cell row. * */ if (dyi == 0) { // Check for a horizontal line current_xi += left_flag; // Line is heading left, we just want to go to the left // edge of this cell for the first move. while (current_xi != cell_dest_xi + left_flag) { current_xi += dxi; const float next_mesh_line_x = mesh_index_to_xpos(current_xi), ry = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; const float rx = mesh_index_to_xpos(current_xi); /** * Without this check, it is possible for the algorithm to generate a zero length move in the case * where the line is heading left and it is starting right on a Mesh Line boundary. For how often * that happens, it might be best to remove the check and always 'schedule' the move because * the planner.buffer_segment() routine will filter it if that happens. */ if (rx != start[X_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * This block handles the generic case of a line crossing both X and Y Mesh lines. * */ int xi_cnt = cell_start_xi - cell_dest_xi, yi_cnt = cell_start_yi - cell_dest_yi; if (xi_cnt < 0) xi_cnt = -xi_cnt; if (yi_cnt < 0) yi_cnt = -yi_cnt; current_xi += left_flag; current_yi += down_flag; while (xi_cnt > 0 || yi_cnt > 0) { const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi), next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi), ry = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line // (No need to worry about m being zero. // If that was the case, it was already detected // as a vertical line move above.) if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first // Yes! Crossing a Y Mesh Line next float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, feed_rate, extruder); current_yi += dyi; yi_cnt--; } else { // Yes! Crossing a X Mesh Line next float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); /** * If part of the Mesh is undefined, it will show up as NAN * in z_values[][] and propagate through the * calculations. If our correction is NAN, we throw it out * because part of the Mesh is undefined and we don't have the * information we need to complete the height correction. */ if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, feed_rate, extruder); current_xi += dxi; xi_cnt--; } if (xi_cnt < 0 || yi_cnt < 0) break; // we've gone too far, so exit the loop and move on to FINAL_MOVE } if (g26_debug_flag) debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); }
inline void invalid_extruder_error(const uint8_t e) { SERIAL_ECHO_START(); SERIAL_CHAR('T'); SERIAL_ECHO(int(e)); SERIAL_CHAR(' '); SERIAL_ECHOLNPGM(MSG_INVALID_EXTRUDER); }
void unified_bed_leveling::line_to_destination_cartesian(const float &feed_rate, const uint8_t extruder) { /** * Much of the nozzle movement will be within the same cell. So we will do as little computation * as possible to determine if this is the case. If this move is within the same cell, we will * just do the required Z-Height correction, call the Planner's buffer_line() routine, and leave */ #if ENABLED(SKEW_CORRECTION) // For skew correction just adjust the destination point and we're done float start[XYZE] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS] }, end[XYZE] = { destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS] }; planner.skew(start[X_AXIS], start[Y_AXIS], start[Z_AXIS]); planner.skew(end[X_AXIS], end[Y_AXIS], end[Z_AXIS]); #else const float (&start)[XYZE] = current_position, (&end)[XYZE] = destination; #endif const int cell_start_xi = get_cell_index_x(start[X_AXIS]), cell_start_yi = get_cell_index_y(start[Y_AXIS]), cell_dest_xi = get_cell_index_x(end[X_AXIS]), cell_dest_yi = get_cell_index_y(end[Y_AXIS]); if (g26_debug_flag) { SERIAL_ECHOPAIR(" ubl.line_to_destination_cartesian(xe=", destination[X_AXIS]); SERIAL_ECHOPAIR(", ye=", destination[Y_AXIS]); SERIAL_ECHOPAIR(", ze=", destination[Z_AXIS]); SERIAL_ECHOPAIR(", ee=", destination[E_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Start of ubl.line_to_destination_cartesian()")); } // A move within the same cell needs no splitting if (cell_start_xi == cell_dest_xi && cell_start_yi == cell_dest_yi) { // For a move off the bed, use a constant Z raise if (!WITHIN(cell_dest_xi, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(cell_dest_yi, 0, GRID_MAX_POINTS_Y - 1)) { // Note: There is no Z Correction in this case. We are off the grid and don't know what // a reasonable correction would be. If the user has specified a UBL_Z_RAISE_WHEN_OFF_MESH // value, that will be used instead of a calculated (Bi-Linear interpolation) correction. const float z_raise = 0.0 #ifdef UBL_Z_RAISE_WHEN_OFF_MESH + UBL_Z_RAISE_WHEN_OFF_MESH #endif ; planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + z_raise, end[E_AXIS], feed_rate, extruder); set_current_from_destination(); if (g26_debug_flag) debug_current_and_destination(PSTR("out of bounds in ubl.line_to_destination_cartesian()")); return; } FINAL_MOVE: // The distance is always MESH_X_DIST so multiply by the constant reciprocal. const float xratio = (end[X_AXIS] - mesh_index_to_xpos(cell_dest_xi)) * (1.0f / (MESH_X_DIST)); float z1 = z_values[cell_dest_xi ][cell_dest_yi ] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi ] - z_values[cell_dest_xi][cell_dest_yi ]), z2 = z_values[cell_dest_xi ][cell_dest_yi + 1] + xratio * (z_values[cell_dest_xi + 1][cell_dest_yi + 1] - z_values[cell_dest_xi][cell_dest_yi + 1]); if (cell_dest_xi >= GRID_MAX_POINTS_X - 1) z1 = z2 = 0.0; // X cell-fraction done. Interpolate the two Z offsets with the Y fraction for the final Z offset. const float yratio = (end[Y_AXIS] - mesh_index_to_ypos(cell_dest_yi)) * (1.0f / (MESH_Y_DIST)), z0 = cell_dest_yi < GRID_MAX_POINTS_Y - 1 ? (z1 + (z2 - z1) * yratio) * planner.fade_scaling_factor_for_z(end[Z_AXIS]) : 0.0; // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. planner.buffer_segment(end[X_AXIS], end[Y_AXIS], end[Z_AXIS] + (isnan(z0) ? 0.0 : z0), end[E_AXIS], feed_rate, extruder); if (g26_debug_flag) debug_current_and_destination(PSTR("FINAL_MOVE in ubl.line_to_destination_cartesian()")); set_current_from_destination(); return; } /** * Past this point the move is known to cross one or more mesh lines. Check for the most common * case - crossing only one X or Y line - after details are worked out to reduce computation. */ const float dx = end[X_AXIS] - start[X_AXIS], dy = end[Y_AXIS] - start[Y_AXIS]; const int left_flag = dx < 0.0 ? 1 : 0, down_flag = dy < 0.0 ? 1 : 0; const float adx = left_flag ? -dx : dx, ady = down_flag ? -dy : dy; const int dxi = cell_start_xi == cell_dest_xi ? 0 : left_flag ? -1 : 1, dyi = cell_start_yi == cell_dest_yi ? 0 : down_flag ? -1 : 1; /** * Compute the extruder scaling factor for each partial move, checking for * zero-length moves that would result in an infinite scaling factor. * A float divide is required for this, but then it just multiplies. * Also select a scaling factor based on the larger of the X and Y * components. The larger of the two is used to preserve precision. */ const bool use_x_dist = adx > ady; float on_axis_distance = use_x_dist ? dx : dy, e_position = end[E_AXIS] - start[E_AXIS], z_position = end[Z_AXIS] - start[Z_AXIS]; const float e_normalized_dist = e_position / on_axis_distance, z_normalized_dist = z_position / on_axis_distance; int current_xi = cell_start_xi, current_yi = cell_start_yi; const float m = dy / dx, c = start[Y_AXIS] - m * start[X_AXIS]; const bool inf_normalized_flag = (isinf(e_normalized_dist) != 0), inf_m_flag = (isinf(m) != 0); /** * Handle vertical lines that stay within one column. * These need not be perfectly vertical. */ if (dxi == 0) { // Vertical line? current_yi += down_flag; // Line going down? Just go to the bottom. while (current_yi != cell_dest_yi + down_flag) { current_yi += dyi; const float next_mesh_line_y = mesh_index_to_ypos(current_yi); /** * Skip the calculations for an infinite slope. * For others the next X is the same so this can continue. * Calculate X at the next Y mesh line. */ const float rx = inf_m_flag ? start[X_AXIS] : (next_mesh_line_y - c) / m; float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; const float ry = mesh_index_to_ypos(current_yi); /** * Without this check, it's possible to generate a zero length move, as in the case where * the line is heading down, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ if (ry != start[Y_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder); } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("vertical move done in ubl.line_to_destination_cartesian()")); // At the final destination? Usually not, but when on a Y Mesh Line it's completed. if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * Handle horizontal lines that stay within one row. * These need not be perfectly horizontal. */ if (dyi == 0) { // Horizontal line? current_xi += left_flag; // Heading left? Just go to the left edge of the cell for the first move. while (current_xi != cell_dest_xi + left_flag) { current_xi += dxi; const float next_mesh_line_x = mesh_index_to_xpos(current_xi), ry = m * next_mesh_line_x + c; // Calculate Y at the next X mesh line float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi, current_yi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; const float rx = mesh_index_to_xpos(current_xi); /** * Without this check, it's possible to generate a zero length move, as in the case where * the line is heading left, starting exactly on a mesh line boundary. Since this is rare * it might be fine to remove this check and let planner.buffer_segment() filter it out. */ if (rx != start[X_AXIS]) { if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; // is based on X or Y because this is a horizontal move z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(rx, ry, z_position + z0, e_position, feed_rate, extruder)) break; } //else printf("FIRST MOVE PRUNED "); } if (g26_debug_flag) debug_current_and_destination(PSTR("horizontal move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); return; } /** * * Handle the generic case of a line crossing both X and Y Mesh lines. * */ int xi_cnt = cell_start_xi - cell_dest_xi, yi_cnt = cell_start_yi - cell_dest_yi; if (xi_cnt < 0) xi_cnt = -xi_cnt; if (yi_cnt < 0) yi_cnt = -yi_cnt; current_xi += left_flag; current_yi += down_flag; while (xi_cnt || yi_cnt) { const float next_mesh_line_x = mesh_index_to_xpos(current_xi + dxi), next_mesh_line_y = mesh_index_to_ypos(current_yi + dyi), ry = m * next_mesh_line_x + c, // Calculate Y at the next X mesh line rx = (next_mesh_line_y - c) / m; // Calculate X at the next Y mesh line // (No need to worry about m being zero. // If that was the case, it was already detected // as a vertical line move above.) if (left_flag == (rx > next_mesh_line_x)) { // Check if we hit the Y line first // Yes! Crossing a Y Mesh Line next float z0 = z_correction_for_x_on_horizontal_mesh_line(rx, current_xi - left_flag, current_yi + dyi) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? rx - start[X_AXIS] : next_mesh_line_y - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(rx, next_mesh_line_y, z_position + z0, e_position, feed_rate, extruder)) break; current_yi += dyi; yi_cnt--; } else { // Yes! Crossing a X Mesh Line next float z0 = z_correction_for_y_on_vertical_mesh_line(ry, current_xi + dxi, current_yi - down_flag) * planner.fade_scaling_factor_for_z(end[Z_AXIS]); // Undefined parts of the Mesh in z_values[][] are NAN. // Replace NAN corrections with 0.0 to prevent NAN propagation. if (isnan(z0)) z0 = 0.0; if (!inf_normalized_flag) { on_axis_distance = use_x_dist ? next_mesh_line_x - start[X_AXIS] : ry - start[Y_AXIS]; e_position = start[E_AXIS] + on_axis_distance * e_normalized_dist; z_position = start[Z_AXIS] + on_axis_distance * z_normalized_dist; } else { e_position = end[E_AXIS]; z_position = end[Z_AXIS]; } if (!planner.buffer_segment(next_mesh_line_x, ry, z_position + z0, e_position, feed_rate, extruder)) break; current_xi += dxi; xi_cnt--; } if (xi_cnt < 0 || yi_cnt < 0) break; // Too far! Exit the loop and go to FINAL_MOVE } if (g26_debug_flag) debug_current_and_destination(PSTR("generic move done in ubl.line_to_destination_cartesian()")); if (current_position[X_AXIS] != end[X_AXIS] || current_position[Y_AXIS] != end[Y_AXIS]) goto FINAL_MOVE; set_current_from_destination(); }
/** * 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) { #if ENABLED(AUTO_BED_LEVELING_BILINEAR) bilinear_start[X_AXIS] = MIN_PROBE_X; bilinear_start[Y_AXIS] = MIN_PROBE_Y; bilinear_grid_spacing[X_AXIS] = (MAX_PROBE_X - (MIN_PROBE_X)) / (GRID_MAX_POINTS_X - 1); bilinear_grid_spacing[Y_AXIS] = (MAX_PROBE_Y - (MIN_PROBE_Y)) / (GRID_MAX_POINTS_Y - 1); #endif for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) Z_VALUES(x, y) = 0.001 * random(-200, 200); SERIAL_ECHOPGM("Simulated " STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_X) " mesh "); SERIAL_ECHOPAIR(" (", MIN_PROBE_X); SERIAL_CHAR(','); SERIAL_ECHO(MIN_PROBE_Y); SERIAL_ECHOPAIR(")-(", MAX_PROBE_X); SERIAL_CHAR(','); SERIAL_ECHO(MAX_PROBE_Y); SERIAL_ECHOLNPGM(")"); } #endif // 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); const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; #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('L') || parser.seen('V')) { 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; for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; 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; for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; 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 for (uint8_t x = GRID_MAX_POINTS_X; x--;) for (uint8_t y = GRID_MAX_POINTS_Y; y--;) Z_VALUES(x, y) -= zmean; #if ENABLED(ABL_BILINEAR_SUBDIVISION) bed_level_virt_interpolate(); #endif } #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(); #if ENABLED(ABL_BILINEAR_SUBDIVISION) print_bilinear_leveling_grid_virt(); #endif #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(MSG_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(MSG_OFF); #endif // Report change in position if (memcmp(oldpos, current_position, sizeof(oldpos))) report_current_position(); }
void unified_bed_leveling::display_map(const int map_type) { constexpr uint8_t spaces = 8 * (GRID_MAX_POINTS_X - 2); SERIAL_PROTOCOLPGM("\nBed Topography Report"); if (map_type == 0) { SERIAL_PROTOCOLPGM(":\n\n"); serial_echo_xy(0, GRID_MAX_POINTS_Y - 1); SERIAL_ECHO_SP(spaces + 3); serial_echo_xy(GRID_MAX_POINTS_X - 1, GRID_MAX_POINTS_Y - 1); SERIAL_EOL(); serial_echo_xy(MESH_MIN_X, MESH_MAX_Y); SERIAL_ECHO_SP(spaces); serial_echo_xy(MESH_MAX_X, MESH_MAX_Y); SERIAL_EOL(); } else { SERIAL_PROTOCOLPGM(" for "); serialprintPGM(map_type == 1 ? PSTR("CSV:\n\n") : PSTR("LCD:\n\n")); } const float current_xi = get_cell_index_x(current_position[X_AXIS] + (MESH_X_DIST) / 2.0), current_yi = get_cell_index_y(current_position[Y_AXIS] + (MESH_Y_DIST) / 2.0); for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) { for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { const bool is_current = i == current_xi && j == current_yi; // is the nozzle here? then mark the number if (map_type == 0) SERIAL_CHAR(is_current ? '[' : ' '); const float f = z_values[i][j]; if (isnan(f)) { serialprintPGM(map_type == 0 ? PSTR(" . ") : PSTR("NAN")); } else if (map_type <= 1) { // if we don't do this, the columns won't line up nicely if (map_type == 0 && f >= 0.0) SERIAL_CHAR(' '); SERIAL_PROTOCOL_F(f, 3); } idle(); if (map_type == 1 && i < GRID_MAX_POINTS_X - 1) SERIAL_CHAR(','); #if TX_BUFFER_SIZE > 0 MYSERIAL.flushTX(); #endif safe_delay(15); if (map_type == 0) { SERIAL_CHAR(is_current ? ']' : ' '); SERIAL_CHAR(' '); } } SERIAL_EOL(); if (j && map_type == 0) { // we want the (0,0) up tight against the block of numbers SERIAL_CHAR(' '); SERIAL_EOL(); } } if (map_type == 0) { serial_echo_xy(MESH_MIN_X, MESH_MIN_Y); SERIAL_ECHO_SP(spaces + 4); serial_echo_xy(MESH_MAX_X, MESH_MIN_Y); SERIAL_EOL(); serial_echo_xy(0, 0); SERIAL_ECHO_SP(spaces + 5); serial_echo_xy(GRID_MAX_POINTS_X - 1, 0); SERIAL_EOL(); } }
/** * 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 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 (axis_unhomed_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 = RETRACTION_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'); if (parser.seenval('B')) { g26_bed_temp = parser.value_celsius(); if (g26_bed_temp && !WITHIN(g26_bed_temp, 40, 140)) { SERIAL_ECHOLNPGM("?Specified bed temperature not plausible (40-140C)."); return; } } 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, 1.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 if (parser.seenval('H')) { g26_hotend_temp = parser.value_celsius(); if (!WITHIN(g26_hotend_temp, 165, 280)) { SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible."); return; } } 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) { SERIAL_ECHOLNPGM("?(R)epeat value not plausible; must be at least 1."); return; } g26_x_pos = parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : current_position[X_AXIS]; g26_y_pos = parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : current_position[Y_AXIS]; if (!position_is_reachable(g26_x_pos, g26_y_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')); if (current_position[Z_AXIS] < Z_CLEARANCE_BETWEEN_PROBES) { do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES); set_current_from_destination(); } if (turn_on_heaters() != G26_OK) goto LEAVE; current_position[E_AXIS] = 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" !!! */ 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_from_current(); destination[Z_AXIS] = g26_layer_height; move_to(destination, 0.0); move_to(destination, g26_ooze_amount); #if HAS_LCD_MENU ui.capture(); #endif //debug_current_and_destination(PSTR("Starting G26 Mesh Validation Pattern.")); #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]; for (uint8_t i = 0; i < A_CNT; i++) trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT)); #endif // !ARC_SUPPORT mesh_index_pair location; 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 = _GET_MESH_X(location.x_index), circle_y = _GET_MESH_Y(location.y_index); // If this mesh location is outside the printable_radius, skip it. if (!position_is_reachable(circle_x, circle_y)) continue; // Determine where to start and end the circle, // which is always drawn counter-clockwise. const uint8_t xi = location.x_index, yi = location.y_index; const bool f = yi == 0, r = xi >= GRID_MAX_POINTS_X - 1, b = yi >= GRID_MAX_POINTS_Y - 1; #if ENABLED(ARC_SUPPORT) #define ARC_LENGTH(quarters) (INTERSECTION_CIRCLE_RADIUS * M_PI * (quarters) / 2) float sx = circle_x + INTERSECTION_CIRCLE_RADIUS, // default to full circle ex = circle_x + INTERSECTION_CIRCLE_RADIUS, sy = circle_y, ey = circle_y, arc_length = ARC_LENGTH(4); // Figure out where to start and end the arc - we always print counterclockwise if (xi == 0) { // left edge sx = f ? circle_x + INTERSECTION_CIRCLE_RADIUS : circle_x; ex = b ? circle_x + INTERSECTION_CIRCLE_RADIUS : circle_x; sy = f ? circle_y : circle_y - (INTERSECTION_CIRCLE_RADIUS); ey = b ? circle_y : circle_y + INTERSECTION_CIRCLE_RADIUS; arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); } else if (r) { // right edge sx = b ? circle_x - (INTERSECTION_CIRCLE_RADIUS) : circle_x; ex = f ? circle_x - (INTERSECTION_CIRCLE_RADIUS) : circle_x; sy = b ? circle_y : circle_y + INTERSECTION_CIRCLE_RADIUS; ey = f ? circle_y : circle_y - (INTERSECTION_CIRCLE_RADIUS); arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2); } else if (f) { sx = circle_x + INTERSECTION_CIRCLE_RADIUS; ex = circle_x - (INTERSECTION_CIRCLE_RADIUS); sy = ey = circle_y; arc_length = ARC_LENGTH(2); } else if (b) { sx = circle_x - (INTERSECTION_CIRCLE_RADIUS); ex = circle_x + INTERSECTION_CIRCLE_RADIUS; sy = ey = circle_y; arc_length = ARC_LENGTH(2); } const float arc_offset[2] = { circle_x - sx, circle_y - sy }; const float dx_s = current_position[X_AXIS] - sx, // find our distance from the start of the actual circle dy_s = current_position[Y_AXIS] - sy, dist_start = HYPOT2(dx_s, dy_s); const float endpoint[XYZE] = { ex, ey, g26_layer_height, current_position[E_AXIS] + (arc_length * g26_e_axis_feedrate * g26_extrusion_multiplier) }; if (dist_start > 2.0) { retract_filament(destination); //todo: parameterize the bump height with a define move_to(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + 0.500, 0.0); // Z bump to minimize scraping move_to(sx, sy, g26_layer_height + 0.500, 0.0); // Get to the starting point with no extrusion while bumped } move_to(sx, sy, g26_layer_height, 0.0); // Get to the starting point with no extrusion / un-Z bump recover_filament(destination); const float save_feedrate = feedrate_mm_s; feedrate_mm_s = PLANNER_XY_FEEDRATE() / 10.0; if (g26_debug_flag) { SERIAL_ECHOPAIR(" plan_arc(ex=", endpoint[X_AXIS]); SERIAL_ECHOPAIR(", ey=", endpoint[Y_AXIS]); SERIAL_ECHOPAIR(", ez=", endpoint[Z_AXIS]); SERIAL_ECHOPAIR(", len=", arc_length); SERIAL_ECHOPAIR(") -> (ex=", current_position[X_AXIS]); SERIAL_ECHOPAIR(", ey=", current_position[Y_AXIS]); SERIAL_ECHOPAIR(", ez=", current_position[Z_AXIS]); SERIAL_CHAR(')'); SERIAL_EOL(); } plan_arc(endpoint, arc_offset, false); // Draw a counter-clockwise arc feedrate_mm_s = save_feedrate; set_destination_from_current(); #if HAS_LCD_MENU if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif #else // !ARC_SUPPORT int8_t start_ind = -2, end_ind = 9; // Assume a full circle (from 5:00 to 5:00) if (xi == 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 HAS_LCD_MENU if (user_canceled()) goto LEAVE; // Check if the user wants to stop the Mesh Validation #endif float rx = circle_x + _COS(ind), // For speed, these are now a lookup table entry ry = circle_y + _SIN(ind), xe = circle_x + _COS(ind + 1), ye = circle_y + _SIN(ind + 1); #if IS_KINEMATIC // Check to make sure this segment is entirely on the bed, skip if not. if (!position_is_reachable(rx, ry) || !position_is_reachable(xe, ye)) continue; #else // not, we need to skip rx = constrain(rx, X_MIN_POS + 1, X_MAX_POS - 1); // This keeps us from bumping the endstops ry = constrain(ry, 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 print_line_from_here_to_there(rx, ry, g26_layer_height, xe, ye, g26_layer_height); 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.x_index >= 0 && location.y_index >= 0); LEAVE: ui.set_status_P(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.")); #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 thermalManager.setTargetHotend(active_extruder, 0); } }
inline bool look_for_lines_to_connect() { float sx, sy, ex, ey; for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) { #if HAS_LCD_MENU if (user_canceled()) return true; // Check if the user wants to stop the Mesh Validation #endif if (i < GRID_MAX_POINTS_X) { // We can't connect to anything to the right than GRID_MAX_POINTS_X. // This is already a half circle because we are at the edge of the bed. if (is_bitmap_set(circle_flags, i, j) && is_bitmap_set(circle_flags, i + 1, j)) { // check if we can do a line to the left if (!is_bitmap_set(horizontal_mesh_line_flags, i, j)) { // // We found two circles that need a horizontal line to connect them // Print it! // sx = _GET_MESH_X( i ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // right edge ex = _GET_MESH_X(i + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // left edge sx = constrain(sx, X_MIN_POS + 1, X_MAX_POS - 1); sy = ey = constrain(_GET_MESH_Y(j), Y_MIN_POS + 1, Y_MAX_POS - 1); ex = constrain(ex, X_MIN_POS + 1, X_MAX_POS - 1); if (position_is_reachable(sx, sy) && position_is_reachable(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with horizontal line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); //debug_current_and_destination(PSTR("Connecting horizontal line.")); } print_line_from_here_to_there(sx, sy, g26_layer_height, ex, ey, g26_layer_height); } bitmap_set(horizontal_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if we skipped it } } if (j < GRID_MAX_POINTS_Y) { // We can't connect to anything further back than GRID_MAX_POINTS_Y. // This is already a half circle because we are at the edge of the bed. if (is_bitmap_set(circle_flags, i, j) && is_bitmap_set(circle_flags, i, j + 1)) { // check if we can do a line straight down if (!is_bitmap_set( vertical_mesh_line_flags, i, j)) { // // We found two circles that need a vertical line to connect them // Print it! // sy = _GET_MESH_Y( j ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // top edge ey = _GET_MESH_Y(j + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // bottom edge sx = ex = constrain(_GET_MESH_X(i), X_MIN_POS + 1, X_MAX_POS - 1); sy = constrain(sy, Y_MIN_POS + 1, Y_MAX_POS - 1); ey = constrain(ey, Y_MIN_POS + 1, Y_MAX_POS - 1); if (position_is_reachable(sx, sy) && position_is_reachable(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with vertical line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); #if ENABLED(AUTO_BED_LEVELING_UBL) debug_current_and_destination(PSTR("Connecting vertical line.")); #endif } print_line_from_here_to_there(sx, sy, g26_layer_height, ex, ey, g26_layer_height); } bitmap_set(vertical_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if skipped } } } } } } return false; }
void GCodeParser::unknown_command_error() { SERIAL_ECHO_START(); SERIAL_ECHOPAIR(MSG_UNKNOWN_COMMAND, command_ptr); SERIAL_CHAR('"'); SERIAL_EOL(); }
bool unified_bed_leveling::look_for_lines_to_connect() { float sx, sy, ex, ey; for (uint8_t i = 0; i < GRID_MAX_POINTS_X; i++) { for (uint8_t j = 0; j < GRID_MAX_POINTS_Y; j++) { #if ENABLED(NEWPANEL) if (user_canceled()) return true; // Check if the user wants to stop the Mesh Validation #endif if (i < GRID_MAX_POINTS_X) { // We can't connect to anything to the right than GRID_MAX_POINTS_X. // This is already a half circle because we are at the edge of the bed. if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i + 1, j)) { // check if we can do a line to the left if (!is_bit_set(horizontal_mesh_line_flags, i, j)) { // // We found two circles that need a horizontal line to connect them // Print it! // sx = mesh_index_to_xpos( i ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // right edge ex = mesh_index_to_xpos(i + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // left edge sx = constrain(sx, X_MIN_POS + 1, X_MAX_POS - 1); sy = ey = constrain(mesh_index_to_ypos(j), Y_MIN_POS + 1, Y_MAX_POS - 1); ex = constrain(ex, X_MIN_POS + 1, X_MAX_POS - 1); if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with horizontal line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); //debug_current_and_destination(PSTR("Connecting horizontal line.")); } print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height); } bit_set(horizontal_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if we skipped it } } if (j < GRID_MAX_POINTS_Y) { // We can't connect to anything further back than GRID_MAX_POINTS_Y. // This is already a half circle because we are at the edge of the bed. if (is_bit_set(circle_flags, i, j) && is_bit_set(circle_flags, i, j + 1)) { // check if we can do a line straight down if (!is_bit_set( vertical_mesh_line_flags, i, j)) { // // We found two circles that need a vertical line to connect them // Print it! // sy = mesh_index_to_ypos( j ) + (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // top edge ey = mesh_index_to_ypos(j + 1) - (SIZE_OF_INTERSECTION_CIRCLES - (SIZE_OF_CROSSHAIRS)); // bottom edge sx = ex = constrain(mesh_index_to_xpos(i), X_MIN_POS + 1, X_MAX_POS - 1); sy = constrain(sy, Y_MIN_POS + 1, Y_MAX_POS - 1); ey = constrain(ey, Y_MIN_POS + 1, Y_MAX_POS - 1); if (position_is_reachable_raw_xy(sx, sy) && position_is_reachable_raw_xy(ex, ey)) { if (g26_debug_flag) { SERIAL_ECHOPAIR(" Connecting with vertical line (sx=", sx); SERIAL_ECHOPAIR(", sy=", sy); SERIAL_ECHOPAIR(") -> (ex=", ex); SERIAL_ECHOPAIR(", ey=", ey); SERIAL_CHAR(')'); SERIAL_EOL(); debug_current_and_destination(PSTR("Connecting vertical line.")); } print_line_from_here_to_there(LOGICAL_X_POSITION(sx), LOGICAL_Y_POSITION(sy), g26_layer_height, LOGICAL_X_POSITION(ex), LOGICAL_Y_POSITION(ey), g26_layer_height); } bit_set(vertical_mesh_line_flags, i, j); // Mark it as done so we don't do it again, even if skipped } } } } } } return false; }