Ver Fonte

Improvement of the bed skew calibration.

bubnikv há 8 anos atrás
pai
commit
58b2aa9fb8

+ 6 - 3
Firmware/Configuration.h

@@ -22,10 +22,13 @@
 // uint32_t
 #define EEPROM_TOTALTIME 4077
 
-#define EEPROM_BED_CALIBRATION_CENTER    (EEPROM_TOTALTIME-2*4)
-#define EEPROM_BED_CALIBRATION_VEC_X     (EEPROM_BED_CALIBRATION_CENTER-2*4)
-#define EEPROM_BED_CALIBRATION_VEC_Y     (EEPROM_BED_CALIBRATION_VEC_X-2*4)
+#define EEPROM_BED_CALIBRATION_CENTER     (EEPROM_TOTALTIME-2*4)
+#define EEPROM_BED_CALIBRATION_VEC_X      (EEPROM_BED_CALIBRATION_CENTER-2*4)
+#define EEPROM_BED_CALIBRATION_VEC_Y      (EEPROM_BED_CALIBRATION_VEC_X-2*4)
 
+// Offsets of the Z heiths of the calibration points from the first point.
+// The offsets are saved as 16bit signed int, scaled to tenths of microns.
+#define EEPROM_BED_CALIBRATION_Z_JITTER   (EEPROM_BED_CALIBRATION_VEC_Y-2*8)
 
 // This configuration file contains the basic settings.
 // Advanced settings can be found in Configuration_adv.h

+ 3 - 1
Firmware/Marlin.h

@@ -212,7 +212,7 @@ void ClearToSend();
 
 void get_coordinates();
 void prepare_move();
-void kill();
+void kill(const char *full_screen_message = NULL);
 void Stop();
 
 bool IsStopped();
@@ -257,6 +257,8 @@ extern float max_pos[3];
 extern bool axis_known_position[3];
 extern float zprobe_zoffset;
 extern int fanSpeed;
+extern void homeaxis(int axis);
+
 
 #ifdef FAN_SOFT_PWM
 extern unsigned char fanSpeedSoftPwm;

+ 128 - 51
Firmware/Marlin_main.cpp

@@ -1469,7 +1469,7 @@ static float probe_pt(float x, float y, float z_before) {
 
 #endif // #ifdef ENABLE_AUTO_BED_LEVELING
 
-static void homeaxis(int axis) {
+void homeaxis(int axis) {
 #define HOMEAXIS_DO(LETTER) \
   ((LETTER##_MIN_PIN > -1 && LETTER##_HOME_DIR==-1) || (LETTER##_MAX_PIN > -1 && LETTER##_HOME_DIR==1))
 
@@ -1996,18 +1996,19 @@ void process_commands()
       			  } 
               // 1st mesh bed leveling measurement point, corrected.
               world2machine_initialize();
-              destination[X_AXIS] = world2machine_rotation_and_skew[0][0] * pgm_read_float(bed_ref_points) + world2machine_rotation_and_skew[0][1] * pgm_read_float(bed_ref_points+1) + world2machine_shift[0];
-              destination[Y_AXIS] = world2machine_rotation_and_skew[1][0] * pgm_read_float(bed_ref_points) + world2machine_rotation_and_skew[1][1] * pgm_read_float(bed_ref_points+1) + world2machine_shift[1];
+              world2machine(pgm_read_float(bed_ref_points), pgm_read_float(bed_ref_points+1), destination[X_AXIS], destination[Y_AXIS]);
               world2machine_reset();
               destination[Z_AXIS] = MESH_HOME_Z_SEARCH;    // Set destination away from bed
               feedrate = homing_feedrate[Z_AXIS]/10;
               current_position[Z_AXIS] = 0;
-              
+              enable_endstops(false);
               plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
               plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate, active_extruder);
               st_synchronize();
               current_position[X_AXIS] = destination[X_AXIS];
               current_position[Y_AXIS] = destination[Y_AXIS];
+              enable_endstops(true);
+              endstops_hit_on_purpose();
               homeaxis(Z_AXIS);
   		        _doMeshL = true;
             #else // MESH_BED_LEVELING
@@ -2342,19 +2343,32 @@ void process_commands()
             int XY_AXIS_FEEDRATE = homing_feedrate[X_AXIS]/20;
             int Z_PROBE_FEEDRATE = homing_feedrate[Z_AXIS]/60;
             int Z_LIFT_FEEDRATE = homing_feedrate[Z_AXIS]/40;
+            bool has_z = is_bed_z_jitter_data_valid();
             setup_for_endstop_move();
+            const char *kill_message = NULL;
             while (mesh_point != MESH_MEAS_NUM_X_POINTS * MESH_MEAS_NUM_Y_POINTS) {
+                // Get coords of a measuring point.
+                ix = mesh_point % MESH_MEAS_NUM_X_POINTS;
+                iy = mesh_point / MESH_MEAS_NUM_X_POINTS;
+                if (iy & 1) ix = (MESH_MEAS_NUM_X_POINTS - 1) - ix; // Zig zag
+                float z0 = 0.f;
+                if (has_z && mesh_point > 0) {
+                    uint16_t z_offset_u = eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER + 2 * (ix + iy * 3 - 1)));
+                    z0 = mbl.z_values[0][0] + *reinterpret_cast<int16_t*>(&z_offset_u) * 0.01;
+                    #if 0
+                    SERIAL_ECHOPGM("Bed leveling, point: ");
+                    MYSERIAL.print(mesh_point);
+                    SERIAL_ECHOPGM(", calibration z: ");
+                    MYSERIAL.print(z0, 5);
+                    SERIAL_ECHOLNPGM("");
+                    #endif
+                }
             
                 // Move Z to proper distance
                 current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
                 plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], Z_LIFT_FEEDRATE, active_extruder);
                 st_synchronize();
-                
-                // Get cords of measuring point
-                ix = mesh_point % MESH_MEAS_NUM_X_POINTS;
-                iy = mesh_point / MESH_MEAS_NUM_X_POINTS;
-                if (iy & 1) ix = (MESH_MEAS_NUM_X_POINTS - 1) - ix; // Zig zag
-                
+
                 current_position[X_AXIS] = pgm_read_float(bed_ref_points+2*mesh_point);
                 current_position[Y_AXIS] = pgm_read_float(bed_ref_points+2*mesh_point+1);
 //                mbl.get_meas_xy(ix, iy, current_position[X_AXIS], current_position[Y_AXIS], false);
@@ -2363,9 +2377,18 @@ void process_commands()
                 st_synchronize();
                 
                 // Go down until endstop is hit
-                find_bed_induction_sensor_point_z();
-                                
+                const float Z_CALIBRATION_THRESHOLD = 0.5f;
+                if (! find_bed_induction_sensor_point_z((has_z && mesh_point > 0) ? z0 - Z_CALIBRATION_THRESHOLD : -10.f)) {
+                    kill_message = MSG_BED_LEVELING_FAILED_POINT_LOW;
+                    break;
+                }
+                if (has_z && fabs(z0 - current_position[Z_AXIS]) > Z_CALIBRATION_THRESHOLD) {
+                    kill_message = MSG_BED_LEVELING_FAILED_POINT_HIGH;
+                    break;
+                }
+
                 mbl.set_z(ix, iy, current_position[Z_AXIS]);
+
         				if (!IS_SD_PRINTING)
         				{
         					custom_message_state--;
@@ -2373,9 +2396,13 @@ void process_commands()
                 mesh_point++;
                 
             }
-            clean_up_after_endstop_move();
             current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
             plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], Z_LIFT_FEEDRATE, active_extruder);
+            if (mesh_point != MESH_MEAS_NUM_X_POINTS * MESH_MEAS_NUM_Y_POINTS) {
+                st_synchronize();
+                kill(kill_message);
+            }
+            clean_up_after_endstop_move();
             mbl.upsample_3x3();
             mbl.active = 1;
             current_position[X_AXIS] = X_MIN_POS+0.2;
@@ -2769,33 +2796,36 @@ void process_commands()
                 char c = strchr_pointer[1];
                 verbosity_level = (c == ' ' || c == '\t' || c == 0) ? 1 : code_value_short();
             }
-            bool success = find_bed_offset_and_skew(verbosity_level);
-            clean_up_after_endstop_move();
-            // Print head up.
-            current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
-            plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40, active_extruder);
-            st_synchronize();
-
-            // Second half: The fine adjustment.
-            // Let the planner use the uncorrected coordinates.
-            mbl.reset();
-            world2machine_reset();
-            // Home in the XY plane.
-            setup_for_endstop_move();
-            home_xy();
-            success = improve_bed_offset_and_skew(1, verbosity_level);
+            BedSkewOffsetDetectionResultType result = find_bed_offset_and_skew(verbosity_level);
             clean_up_after_endstop_move();
             // Print head up.
             current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
             plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40, active_extruder);
             st_synchronize();
-            if (success) {
+            if (result != BED_SKEW_OFFSET_DETECTION_FAILED) {
+                // Second half: The fine adjustment.
+                // Let the planner use the uncorrected coordinates.
+                mbl.reset();
+                world2machine_reset();
+                // Home in the XY plane.
+                setup_for_endstop_move();
+                home_xy();
+                result = improve_bed_offset_and_skew(1, verbosity_level);
+                clean_up_after_endstop_move();
+                // Print head up.
+                current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
+                plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS],current_position[Z_AXIS] , current_position[E_AXIS], homing_feedrate[Z_AXIS]/40, active_extruder);
+                st_synchronize();
+            }
+            lcd_bed_calibration_show_result(result);
+            /*
+            if (result != BED_SKEW_OFFSET_DETECTION_FAILED) {
                 // Mesh bed leveling.
                 // Push the commands to the front of the message queue in the reverse order!
                 // There shall be always enough space reserved for these commands.
                 enquecommand_front_P((PSTR("G80")));
             }
-
+            */
             lcd_update_enable(true);
             lcd_implementation_clear();
             // lcd_return_to_status();
@@ -2844,7 +2874,7 @@ void process_commands()
         break;
     }
 
-#if 0
+#if 1
     case 48: // M48: scan the bed induction sensor points, print the sensor trigger coordinates to the serial line for visualization on the PC.
     {
         // Disable the default update procedure of the display. We will do a modal dialog.
@@ -4686,24 +4716,66 @@ void get_arc_coordinates()
 
 void clamp_to_software_endstops(float target[3])
 {
-  if (min_software_endstops) {
-    if (target[X_AXIS] < min_pos[X_AXIS]) target[X_AXIS] = min_pos[X_AXIS];
-    if (target[Y_AXIS] < min_pos[Y_AXIS]) target[Y_AXIS] = min_pos[Y_AXIS];
-    
-    float negative_z_offset = 0;
-    #ifdef ENABLE_AUTO_BED_LEVELING
-      if (Z_PROBE_OFFSET_FROM_EXTRUDER < 0) negative_z_offset = negative_z_offset + Z_PROBE_OFFSET_FROM_EXTRUDER;
-      if (add_homing[Z_AXIS] < 0) negative_z_offset = negative_z_offset + add_homing[Z_AXIS];
-    #endif
-    
-    if (target[Z_AXIS] < min_pos[Z_AXIS]+negative_z_offset) target[Z_AXIS] = min_pos[Z_AXIS]+negative_z_offset;
-  }
+    if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_NONE || world2machine_correction_mode == WORLD2MACHINE_CORRECTION_SHIFT) {
+        // No correction or only a shift correction.
+        // Save computational cycles by not performing the skew correction.
+        if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_SHIFT) {
+            target[0] += world2machine_shift[0];
+            target[1] += world2machine_shift[1];
+        }
+        if (min_software_endstops) {
+            if (target[X_AXIS] < min_pos[X_AXIS]) target[X_AXIS] = min_pos[X_AXIS];
+            if (target[Y_AXIS] < min_pos[Y_AXIS]) target[Y_AXIS] = min_pos[Y_AXIS];
+        }
+        if (max_software_endstops) {
+            if (target[X_AXIS] > max_pos[X_AXIS]) target[X_AXIS] = max_pos[X_AXIS];
+            if (target[Y_AXIS] > max_pos[Y_AXIS]) target[Y_AXIS] = max_pos[Y_AXIS];
+        }
+        if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_SHIFT) {
+            target[0] -= world2machine_shift[0];
+            target[1] -= world2machine_shift[1];
+        }
+    } else {
+        // Skew correction is in action.
+        float x, y;
+        world2machine(target[0], target[1], x, y);
+        bool clamped = false;
+        if (min_software_endstops) {
+            if (x < min_pos[X_AXIS]) {
+                x = min_pos[X_AXIS];
+                clamped = true;
+            }
+            if (y < min_pos[Y_AXIS]) {
+                y = min_pos[Y_AXIS];
+                clamped = true;
+            }
+        }
+        if (max_software_endstops) {
+            if (x > max_pos[X_AXIS]) {
+                x = max_pos[X_AXIS];
+                clamped = true;
+            }
+            if (y > max_pos[Y_AXIS]) {
+                y = max_pos[Y_AXIS];
+                clamped = true;
+            }
+        }
+        if (clamped)
+            machine2world(x, y, target[X_AXIS], target[Y_AXIS]);
+    }
 
-  if (max_software_endstops) {
-    if (target[X_AXIS] > max_pos[X_AXIS]) target[X_AXIS] = max_pos[X_AXIS];
-    if (target[Y_AXIS] > max_pos[Y_AXIS]) target[Y_AXIS] = max_pos[Y_AXIS];
-    if (target[Z_AXIS] > max_pos[Z_AXIS]) target[Z_AXIS] = max_pos[Z_AXIS];
-  }
+    // Clamp the Z coordinate.
+    if (min_software_endstops) {
+        float negative_z_offset = 0;
+        #ifdef ENABLE_AUTO_BED_LEVELING
+            if (Z_PROBE_OFFSET_FROM_EXTRUDER < 0) negative_z_offset = negative_z_offset + Z_PROBE_OFFSET_FROM_EXTRUDER;
+            if (add_homing[Z_AXIS] < 0) negative_z_offset = negative_z_offset + add_homing[Z_AXIS];
+        #endif
+        if (target[Z_AXIS] < min_pos[Z_AXIS]+negative_z_offset) target[Z_AXIS] = min_pos[Z_AXIS]+negative_z_offset;
+    }
+    if (max_software_endstops) {
+        if (target[Z_AXIS] > max_pos[Z_AXIS]) target[Z_AXIS] = max_pos[Z_AXIS];
+    }
 }
 
 #ifdef MESH_BED_LEVELING
@@ -4996,7 +5068,7 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) //default argument s
   check_axes_activity();
 }
 
-void kill()
+void kill(const char *full_screen_message)
 {
   cli(); // Stop interrupts
   disable_heater();
@@ -5014,8 +5086,13 @@ void kill()
 #endif
   SERIAL_ERROR_START;
   SERIAL_ERRORLNRPGM(MSG_ERR_KILLED);
-  LCD_ALERTMESSAGERPGM(MSG_KILLED);
-  
+  if (full_screen_message != NULL) {
+      SERIAL_ERRORLNRPGM(full_screen_message);
+      lcd_display_message_fullscreen_P(full_screen_message);
+  } else {
+      LCD_ALERTMESSAGERPGM(MSG_KILLED);
+  }
+
   // FMC small patch to update the LCD before ending
   sei();   // enable interrupts
   for ( int i=5; i--; lcd_update())

+ 143 - 26
Firmware/language_all.cpp

@@ -252,6 +252,110 @@ const char * const MSG_BED_HEATING_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_BED_HEATING_PL
 };
 
+const char MSG_BED_LEVELING_FAILED_POINT_HIGH_EN[] PROGMEM = "Bed leveling failed. Sensor triggered too high. Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_HIGH_CZ[] PROGMEM = "Kalibrace Z selhala. Sensor sepnul prilis vysoko. Cekam na reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_HIGH_IT[] PROGMEM = "Bed leveling failed. Sensor triggered too high. Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_HIGH_ES[] PROGMEM = "Bed leveling failed. Sensor triggered too high. Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_HIGH_PL[] PROGMEM = "Bed leveling failed. Sensor triggered too high. Waiting for reset.";
+const char * const MSG_BED_LEVELING_FAILED_POINT_HIGH_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_LEVELING_FAILED_POINT_HIGH_EN,
+	MSG_BED_LEVELING_FAILED_POINT_HIGH_CZ,
+	MSG_BED_LEVELING_FAILED_POINT_HIGH_IT,
+	MSG_BED_LEVELING_FAILED_POINT_HIGH_ES,
+	MSG_BED_LEVELING_FAILED_POINT_HIGH_PL
+};
+
+const char MSG_BED_LEVELING_FAILED_POINT_LOW_EN[] PROGMEM = "Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_LOW_CZ[] PROGMEM = "Kalibrace Z selhala. Sensor nesepnul. Znecistena tryska? Cekam na reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_LOW_IT[] PROGMEM = "Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_LOW_ES[] PROGMEM = "Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset.";
+const char MSG_BED_LEVELING_FAILED_POINT_LOW_PL[] PROGMEM = "Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset.";
+const char * const MSG_BED_LEVELING_FAILED_POINT_LOW_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_LEVELING_FAILED_POINT_LOW_EN,
+	MSG_BED_LEVELING_FAILED_POINT_LOW_CZ,
+	MSG_BED_LEVELING_FAILED_POINT_LOW_IT,
+	MSG_BED_LEVELING_FAILED_POINT_LOW_ES,
+	MSG_BED_LEVELING_FAILED_POINT_LOW_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_FAILED_EN[] PROGMEM = "X/Y calibration failed. Please consult manual.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FAILED_CZ[] PROGMEM = "Kalibrace X/Y selhala. Nahlednete do manualu.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FAILED_IT[] PROGMEM = "X/Y calibration failed. Please consult manual.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FAILED_ES[] PROGMEM = "X/Y calibration failed. Please consult manual.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FAILED_PL[] PROGMEM = "X/Y calibration failed. Please consult manual.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_FAILED_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_FAILED_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_FAILED_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_FAILED_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_FAILED_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_FAILED_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_EN[] PROGMEM = "X/Y calibration bad. Left front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_CZ[] PROGMEM = "Kalibrace selhala. Levy predni bod moc vpredu. Srovnejte tiskarnu.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_IT[] PROGMEM = "X/Y calibration bad. Left front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_ES[] PROGMEM = "X/Y calibration bad. Left front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_PL[] PROGMEM = "X/Y calibration bad. Left front corner not reachable. Fix the printer.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_EN[] PROGMEM = "X/Y calibration bad. Right front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_CZ[] PROGMEM = "Kalibrace selhala. Pravy predni bod moc vpredu. Srovnejte tiskarnu.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_IT[] PROGMEM = "X/Y calibration bad. Right front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_ES[] PROGMEM = "X/Y calibration bad. Right front corner not reachable. Fix the printer.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_PL[] PROGMEM = "X/Y calibration bad. Right front corner not reachable. Fix the printer.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_EN[] PROGMEM = "X/Y calibration ok. X/Y axes are perpendicular.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_CZ[] PROGMEM = "Kalibrace X/Y perfektni. X/Y osy jsou kolme.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_IT[] PROGMEM = "X/Y calibration ok. X/Y axes are perpendicular.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_ES[] PROGMEM = "X/Y calibration ok. X/Y axes are perpendicular.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_PL[] PROGMEM = "X/Y calibration ok. X/Y axes are perpendicular.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_EN[] PROGMEM = "X/Y skewed severly. Skew will be corrected automatically.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_CZ[] PROGMEM = "X/Y osy jsou silne zkosene. Zkoseni bude automaticky vyrovnano pri tisku.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_IT[] PROGMEM = "X/Y skewed severly. Skew will be corrected automatically.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_ES[] PROGMEM = "X/Y skewed severly. Skew will be corrected automatically.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_PL[] PROGMEM = "X/Y skewed severly. Skew will be corrected automatically.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_PL
+};
+
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_EN[] PROGMEM = "X/Y calibration all right. X/Y axes are slightly skewed.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_CZ[] PROGMEM = "Kalibrace X/Y v poradku. X/Y osy mirne zkosene.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_IT[] PROGMEM = "X/Y calibration all right. X/Y axes are slightly skewed.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_ES[] PROGMEM = "X/Y calibration all right. X/Y axes are slightly skewed.";
+const char MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_PL[] PROGMEM = "X/Y calibration all right. X/Y axes are slightly skewed.";
+const char * const MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_EN,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_CZ,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_IT,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_ES,
+	MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_PL
+};
+
 const char MSG_BEGIN_FILE_LIST_EN[] PROGMEM = "Begin file list";
 const char MSG_BEGIN_FILE_LIST_CZ[] PROGMEM = "Begin file list";
 const char MSG_BEGIN_FILE_LIST_IT[] PROGMEM = "Begin file list";
@@ -278,11 +382,11 @@ const char * const MSG_BROWNOUT_RESET_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_BROWNOUT_RESET_PL
 };
 
-const char MSG_CALIBRATE_BED_EN[] PROGMEM = "Calibrate bed";
-const char MSG_CALIBRATE_BED_CZ[] PROGMEM = "Calibrate bed";
-const char MSG_CALIBRATE_BED_IT[] PROGMEM = "Calibrate bed";
-const char MSG_CALIBRATE_BED_ES[] PROGMEM = "Calibrate bed";
-const char MSG_CALIBRATE_BED_PL[] PROGMEM = "Calibrate bed";
+const char MSG_CALIBRATE_BED_EN[] PROGMEM = "Calibrate X/Y";
+const char MSG_CALIBRATE_BED_CZ[] PROGMEM = "Kalibrace X/Y";
+const char MSG_CALIBRATE_BED_IT[] PROGMEM = "Calibrate X/Y";
+const char MSG_CALIBRATE_BED_ES[] PROGMEM = "Calibrate X/Y";
+const char MSG_CALIBRATE_BED_PL[] PROGMEM = "Calibrate X/Y";
 const char * const MSG_CALIBRATE_BED_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_CALIBRATE_BED_EN,
 	MSG_CALIBRATE_BED_CZ,
@@ -291,11 +395,11 @@ const char * const MSG_CALIBRATE_BED_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_CALIBRATE_BED_PL
 };
 
-const char MSG_CALIBRATE_BED_RESET_EN[] PROGMEM = "Reset bed calibration";
-const char MSG_CALIBRATE_BED_RESET_CZ[] PROGMEM = "Reset bed calibration";
-const char MSG_CALIBRATE_BED_RESET_IT[] PROGMEM = "Reset bed calibration";
-const char MSG_CALIBRATE_BED_RESET_ES[] PROGMEM = "Reset bed calibration";
-const char MSG_CALIBRATE_BED_RESET_PL[] PROGMEM = "Reset bed calibration";
+const char MSG_CALIBRATE_BED_RESET_EN[] PROGMEM = "Reset X/Y calibr.";
+const char MSG_CALIBRATE_BED_RESET_CZ[] PROGMEM = "Reset X/Y kalibr.";
+const char MSG_CALIBRATE_BED_RESET_IT[] PROGMEM = "Reset X/Y calibr.";
+const char MSG_CALIBRATE_BED_RESET_ES[] PROGMEM = "Reset X/Y calibr.";
+const char MSG_CALIBRATE_BED_RESET_PL[] PROGMEM = "Reset X/Y calibr.";
 const char * const MSG_CALIBRATE_BED_RESET_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_CALIBRATE_BED_RESET_EN,
 	MSG_CALIBRATE_BED_RESET_CZ,
@@ -370,7 +474,7 @@ const char * const MSG_CONFIGURATION_VER_LANG_TABLE[LANG_NUM] PROGMEM = {
 };
 
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_EN[] PROGMEM = "Are left and right";
-const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_CZ[] PROGMEM = "Are left and right";
+const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_CZ[] PROGMEM = "Dojely oba Z voziky";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_IT[] PROGMEM = "Are left and right";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_ES[] PROGMEM = "Are left and right";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_PL[] PROGMEM = "Are left and right";
@@ -383,7 +487,7 @@ const char * const MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1_LANG_TABLE[LANG_NUM] PR
 };
 
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_EN[] PROGMEM = "Z carriages all up?";
-const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_CZ[] PROGMEM = "Z carriages all up?";
+const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_CZ[] PROGMEM = "k hornimu dorazu?";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_IT[] PROGMEM = "Z carriages all up?";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_ES[] PROGMEM = "Z carriages all up?";
 const char MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2_PL[] PROGMEM = "Z carriages all up?";
@@ -942,7 +1046,7 @@ const char * const MSG_FILE_SAVED_LANG_TABLE[LANG_NUM] PROGMEM = {
 };
 
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_EN[] PROGMEM = "Searching bed";
-const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_CZ[] PROGMEM = "Searching bed";
+const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_CZ[] PROGMEM = "Hledam kalibracni";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_IT[] PROGMEM = "Searching bed";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_ES[] PROGMEM = "Searching bed";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_PL[] PROGMEM = "Searching bed";
@@ -955,7 +1059,7 @@ const char * const MSG_FIND_BED_OFFSET_AND_SKEW_LINE1_LANG_TABLE[LANG_NUM] PROGM
 };
 
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_EN[] PROGMEM = "calibration point";
-const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_CZ[] PROGMEM = "calibration point";
+const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_CZ[] PROGMEM = "bod podlozky";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_IT[] PROGMEM = "calibration point";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_ES[] PROGMEM = "calibration point";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_PL[] PROGMEM = "calibration point";
@@ -968,7 +1072,7 @@ const char * const MSG_FIND_BED_OFFSET_AND_SKEW_LINE2_LANG_TABLE[LANG_NUM] PROGM
 };
 
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_EN[] PROGMEM = " of 4";
-const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_CZ[] PROGMEM = " of 4";
+const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_CZ[] PROGMEM = " z 4";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_IT[] PROGMEM = " of 4";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_ES[] PROGMEM = " of 4";
 const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE3_PL[] PROGMEM = " of 4";
@@ -1124,7 +1228,7 @@ const char * const MSG_HOTEND_OFFSET_LANG_TABLE[LANG_NUM] PROGMEM = {
 };
 
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_EN[] PROGMEM = "Improving bed";
-const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_CZ[] PROGMEM = "Improving bed";
+const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_CZ[] PROGMEM = "Zlepsuji presnost";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_IT[] PROGMEM = "Improving bed";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_ES[] PROGMEM = "Improving bed";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_PL[] PROGMEM = "Improving bed";
@@ -1137,7 +1241,7 @@ const char * const MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1_LANG_TABLE[LANG_NUM] PR
 };
 
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_EN[] PROGMEM = "calibration point";
-const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_CZ[] PROGMEM = "calibration point";
+const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_CZ[] PROGMEM = "kalibracniho bodu";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_IT[] PROGMEM = "calibration point";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_ES[] PROGMEM = "calibration point";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_PL[] PROGMEM = "calibration point";
@@ -1150,7 +1254,7 @@ const char * const MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2_LANG_TABLE[LANG_NUM] PR
 };
 
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_EN[] PROGMEM = " of 9";
-const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_CZ[] PROGMEM = " of 9";
+const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_CZ[] PROGMEM = " z 9";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_IT[] PROGMEM = " of 9";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_ES[] PROGMEM = " of 9";
 const char MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3_PL[] PROGMEM = " of 9";
@@ -1500,11 +1604,11 @@ const char * const MSG_MOVE_AXIS_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_MOVE_AXIS_PL
 };
 
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_EN[] PROGMEM = "Calibrating bed.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_CZ[] PROGMEM = "Calibrating bed.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_IT[] PROGMEM = "Calibrating bed.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_ES[] PROGMEM = "Calibrating bed.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_PL[] PROGMEM = "Calibrating bed.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_EN[] PROGMEM = "Calibrating X/Y.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_CZ[] PROGMEM = "Kalibrace X/Y";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_IT[] PROGMEM = "Calibrating X/Y.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_ES[] PROGMEM = "Calibrating X/Y.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_PL[] PROGMEM = "Calibrating X/Y.";
 const char * const MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_EN,
 	MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_CZ,
@@ -1514,7 +1618,7 @@ const char * const MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1_LANG_TABLE[LANG_NUM] PROGM
 };
 
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_EN[] PROGMEM = "Move Z carriage up";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_CZ[] PROGMEM = "Move Z carriage up";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_CZ[] PROGMEM = "Posunte prosim Z osu";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_IT[] PROGMEM = "Move Z carriage up";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_ES[] PROGMEM = "Move Z carriage up";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_PL[] PROGMEM = "Move Z carriage up";
@@ -1527,7 +1631,7 @@ const char * const MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2_LANG_TABLE[LANG_NUM] PROGM
 };
 
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_EN[] PROGMEM = "to the end stoppers.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_CZ[] PROGMEM = "to the end stoppers.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_CZ[] PROGMEM = "az k hornimu dorazu.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_IT[] PROGMEM = "to the end stoppers.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_ES[] PROGMEM = "to the end stoppers.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_PL[] PROGMEM = "to the end stoppers.";
@@ -1540,7 +1644,7 @@ const char * const MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3_LANG_TABLE[LANG_NUM] PROGM
 };
 
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_EN[] PROGMEM = "Click when done.";
-const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_CZ[] PROGMEM = "Click when done.";
+const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_CZ[] PROGMEM = "Potvrdte tlacitkem.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_IT[] PROGMEM = "Click when done.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_ES[] PROGMEM = "Click when done.";
 const char MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4_PL[] PROGMEM = "Click when done.";
@@ -2839,6 +2943,19 @@ const char * const MSG_SET_ORIGIN_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_SET_ORIGIN_PL
 };
 
+const char MSG_SHOW_END_STOPS_EN[] PROGMEM = "Show end stops";
+const char MSG_SHOW_END_STOPS_CZ[] PROGMEM = "Zobraz konc. spinace";
+const char MSG_SHOW_END_STOPS_IT[] PROGMEM = "Show end stops";
+const char MSG_SHOW_END_STOPS_ES[] PROGMEM = "Show end stops";
+const char MSG_SHOW_END_STOPS_PL[] PROGMEM = "Show end stops";
+const char * const MSG_SHOW_END_STOPS_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_SHOW_END_STOPS_EN,
+	MSG_SHOW_END_STOPS_CZ,
+	MSG_SHOW_END_STOPS_IT,
+	MSG_SHOW_END_STOPS_ES,
+	MSG_SHOW_END_STOPS_PL
+};
+
 const char MSG_SILENT_MODE_OFF_EN[] PROGMEM = "Mode [high power]";
 const char MSG_SILENT_MODE_OFF_CZ[] PROGMEM = "Mod  [vys. vykon]";
 const char MSG_SILENT_MODE_OFF_IT[] PROGMEM = "Modo [piu forza]";

+ 18 - 0
Firmware/language_all.h

@@ -46,6 +46,22 @@ extern const char* const MSG_BED_DONE_LANG_TABLE[LANG_NUM];
 #define MSG_BED_DONE LANG_TABLE_SELECT(MSG_BED_DONE_LANG_TABLE)
 extern const char* const MSG_BED_HEATING_LANG_TABLE[LANG_NUM];
 #define MSG_BED_HEATING LANG_TABLE_SELECT(MSG_BED_HEATING_LANG_TABLE)
+extern const char* const MSG_BED_LEVELING_FAILED_POINT_HIGH_LANG_TABLE[LANG_NUM];
+#define MSG_BED_LEVELING_FAILED_POINT_HIGH LANG_TABLE_SELECT(MSG_BED_LEVELING_FAILED_POINT_HIGH_LANG_TABLE)
+extern const char* const MSG_BED_LEVELING_FAILED_POINT_LOW_LANG_TABLE[LANG_NUM];
+#define MSG_BED_LEVELING_FAILED_POINT_LOW LANG_TABLE_SELECT(MSG_BED_LEVELING_FAILED_POINT_LOW_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_FAILED_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_FAILED LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_FAILED_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_PERFECT LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_PERFECT_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME_LANG_TABLE)
+extern const char* const MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_LANG_TABLE[LANG_NUM];
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD LANG_TABLE_SELECT(MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD_LANG_TABLE)
 extern const char* const MSG_BEGIN_FILE_LIST_LANG_TABLE[LANG_NUM];
 #define MSG_BEGIN_FILE_LIST LANG_TABLE_SELECT(MSG_BEGIN_FILE_LIST_LANG_TABLE)
 extern const char* const MSG_BROWNOUT_RESET_LANG_TABLE[LANG_NUM];
@@ -446,6 +462,8 @@ extern const char* const MSG_SET_HOME_OFFSETS_LANG_TABLE[LANG_NUM];
 #define MSG_SET_HOME_OFFSETS LANG_TABLE_SELECT(MSG_SET_HOME_OFFSETS_LANG_TABLE)
 extern const char* const MSG_SET_ORIGIN_LANG_TABLE[LANG_NUM];
 #define MSG_SET_ORIGIN LANG_TABLE_SELECT(MSG_SET_ORIGIN_LANG_TABLE)
+extern const char* const MSG_SHOW_END_STOPS_LANG_TABLE[LANG_NUM];
+#define MSG_SHOW_END_STOPS LANG_TABLE_SELECT(MSG_SHOW_END_STOPS_LANG_TABLE)
 extern const char* const MSG_SILENT_MODE_OFF_LANG_TABLE[LANG_NUM];
 #define MSG_SILENT_MODE_OFF LANG_TABLE_SELECT(MSG_SILENT_MODE_OFF_LANG_TABLE)
 extern const char* const MSG_SILENT_MODE_ON_LANG_TABLE[LANG_NUM];

+ 29 - 0
Firmware/language_cz.h

@@ -278,4 +278,33 @@
 #define MSG_STATISTICS						"Statistika  "
 #define MSG_USB_PRINTING					"Tisk z USB  "
 
+#define MSG_SHOW_END_STOPS					"Zobraz konc. spinace"
+#define MSG_CALIBRATE_BED					"Kalibrace X/Y"
+#define MSG_CALIBRATE_BED_RESET				"Reset X/Y kalibr."
+
+#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1	"Kalibrace X/Y"
+#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2	"Posunte prosim Z osu"
+#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3	"az k hornimu dorazu."
+#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4	"Potvrdte tlacitkem."
+
+#define MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE1	"Dojely oba Z voziky"
+#define MSG_CONFIRM_CARRIAGE_AT_THE_TOP_LINE2	"k hornimu dorazu?"
+
+#define MSG_FIND_BED_OFFSET_AND_SKEW_LINE1	"Hledam kalibracni"
+#define MSG_FIND_BED_OFFSET_AND_SKEW_LINE2	"bod podlozky"
+#define MSG_FIND_BED_OFFSET_AND_SKEW_LINE3	" z 4"
+#define MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1	"Zlepsuji presnost"
+#define MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2	"kalibracniho bodu"
+#define MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3	" z 9"
+
+#define MSG_BED_SKEW_OFFSET_DETECTION_FAILED			"Kalibrace X/Y selhala. Nahlednete do manualu."
+#define MSG_BED_SKEW_OFFSET_DETECTION_PERFECT			"Kalibrace X/Y perfektni. X/Y osy jsou kolme."
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD			"Kalibrace X/Y v poradku. X/Y osy mirne zkosene."
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME		"X/Y osy jsou silne zkosene. Zkoseni bude automaticky vyrovnano pri tisku."
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR	"Kalibrace selhala. Levy predni bod moc vpredu. Srovnejte tiskarnu."
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR	"Kalibrace selhala. Pravy predni bod moc vpredu. Srovnejte tiskarnu."
+
+#define MSG_BED_LEVELING_FAILED_POINT_LOW				"Kalibrace Z selhala. Sensor nesepnul. Znecistena tryska? Cekam na reset."
+#define MSG_BED_LEVELING_FAILED_POINT_HIGH				"Kalibrace Z selhala. Sensor sepnul prilis vysoko. Cekam na reset."
+
 #endif // LANGUAGE_EN_H

+ 14 - 3
Firmware/language_en.h

@@ -271,10 +271,11 @@
 #define MSG_HOMEYZ_PROGRESS                 "Calibrating Z"
 #define MSG_HOMEYZ_DONE		                "Calibration done"
 
-#define MSG_CALIBRATE_BED					"Calibrate bed"
-#define MSG_CALIBRATE_BED_RESET				"Reset bed calibration"
+#define MSG_SHOW_END_STOPS					"Show end stops"
+#define MSG_CALIBRATE_BED					"Calibrate X/Y"
+#define MSG_CALIBRATE_BED_RESET				"Reset X/Y calibr."
 
-#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1	"Calibrating bed."
+#define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE1	"Calibrating X/Y."
 #define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE2	"Move Z carriage up"
 #define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE3	"to the end stoppers."
 #define MSG_MOVE_CARRIAGE_TO_THE_TOP_LINE4	"Click when done."
@@ -289,4 +290,14 @@
 #define MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE2	"calibration point"
 #define MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE3	" of 9"
 
+#define MSG_BED_SKEW_OFFSET_DETECTION_FAILED			"X/Y calibration failed. Please consult manual."
+#define MSG_BED_SKEW_OFFSET_DETECTION_PERFECT			"X/Y calibration ok. X/Y axes are perpendicular."
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD			"X/Y calibration all right. X/Y axes are slightly skewed."
+#define MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME		"X/Y skewed severly. Skew will be corrected automatically."
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR	"X/Y calibration bad. Left front corner not reachable. Fix the printer."
+#define MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR	"X/Y calibration bad. Right front corner not reachable. Fix the printer."
+
+#define MSG_BED_LEVELING_FAILED_POINT_LOW				"Bed leveling failed. Sensor didnt trigger. Debris on nozzle? Waiting for reset."
+#define MSG_BED_LEVELING_FAILED_POINT_HIGH				"Bed leveling failed. Sensor triggered too high. Waiting for reset."
+
 #endif // LANGUAGE_EN_H

+ 322 - 126
Firmware/mesh_bed_calibration.cpp

@@ -9,8 +9,10 @@
 
 extern float home_retract_mm_ext(int axis);
 
-float world2machine_rotation_and_skew[2][2];
-float world2machine_shift[2];
+uint8_t world2machine_correction_mode;
+float   world2machine_rotation_and_skew[2][2];
+float   world2machine_rotation_and_skew_inv[2][2];
+float   world2machine_shift[2];
 
 // Weight of the Y coordinate for the least squares fitting of the bed induction sensor targets.
 // Only used for the first row of the points, which may not befully in reach of the sensor.
@@ -24,6 +26,13 @@ float world2machine_shift[2];
 #define MACHINE_AXIS_SCALE_X ((250.f + 0.5f) / 250.f)
 #define MACHINE_AXIS_SCALE_Y ((250.f + 0.5f) / 250.f)
 
+#define BED_SKEW_ANGLE_MILD         (0.12f * M_PI / 180.f)
+#define BED_SKEW_ANGLE_EXTREME      (0.25f * M_PI / 180.f)
+
+#define BED_CALIBRATION_POINT_OFFSET_MAX_EUCLIDIAN  (0.8f)
+#define BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X  (0.8f)
+#define BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y  (1.5f)
+
 // Positions of the bed reference points in the machine coordinates, referenced to the P.I.N.D.A sensor.
 // The points are ordered in a zig-zag fashion to speed up the calibration.
 const float bed_ref_points[] PROGMEM = {
@@ -352,7 +361,7 @@ bool calculate_machine_skew_and_offset_LS(
 // using the Gauss-Newton method.
 // This method will maintain a unity length of the machine axes,
 // which is the correct approach if the sensor points are not measured precisely.
-bool calculate_machine_skew_and_offset_LS(
+BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
     // Matrix of maximum 9 2D points (18 floats)
     const float  *measured_pts,
     uint8_t       npts,
@@ -533,6 +542,18 @@ bool calculate_machine_skew_and_offset_LS(
     vec_y[0] = -sin(a2) * MACHINE_AXIS_SCALE_Y;
     vec_y[1] =  cos(a2) * MACHINE_AXIS_SCALE_Y;
 
+    BedSkewOffsetDetectionResultType result = BED_SKEW_OFFSET_DETECTION_PERFECT;
+    {
+        float angleDiff = fabs(a2 - a1);
+        if (angleDiff > BED_SKEW_ANGLE_MILD)
+            result = (angleDiff > BED_SKEW_ANGLE_EXTREME) ?
+                BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME :
+                BED_SKEW_OFFSET_DETECTION_SKEW_MILD;
+        if (fabs(a1) > BED_SKEW_ANGLE_EXTREME ||
+            fabs(a2) > BED_SKEW_ANGLE_EXTREME)
+            result = BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME;
+    }
+
     if (verbosity_level >= 1) {
         SERIAL_ECHOPGM("correction angles: ");
         MYSERIAL.print(180.f * a1 / M_PI, 5);
@@ -563,9 +584,24 @@ bool calculate_machine_skew_and_offset_LS(
         delay_keep_alive(100);
 
         SERIAL_ECHOLNPGM("Error after correction: ");
-        for (uint8_t i = 0; i < npts; ++i) {
-            float x = vec_x[0] * measured_pts[i * 2] + vec_y[0] * measured_pts[i * 2 + 1] + cntr[0];
-            float y = vec_x[1] * measured_pts[i * 2] + vec_y[1] * measured_pts[i * 2 + 1] + cntr[1];
+    }
+
+    // Measure the error after correction.
+    for (uint8_t i = 0; i < npts; ++i) {
+        float x = vec_x[0] * measured_pts[i * 2] + vec_y[0] * measured_pts[i * 2 + 1] + cntr[0];
+        float y = vec_x[1] * measured_pts[i * 2] + vec_y[1] * measured_pts[i * 2 + 1] + cntr[1];
+        float errX = sqr(pgm_read_float(true_pts + i * 2) - x);
+        float errY = sqr(pgm_read_float(true_pts + i * 2 + 1) - y);
+        float err = sqrt(errX + errY);
+        if (i < 3) {
+            if (sqrt(errX) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X ||
+                sqrt(errY) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y)
+                result = BED_SKEW_OFFSET_DETECTION_FAILED;
+        } else {
+            if (err > BED_CALIBRATION_POINT_OFFSET_MAX_EUCLIDIAN)
+                result = BED_SKEW_OFFSET_DETECTION_FAILED;
+        }
+        if (verbosity_level >= 10) {
             SERIAL_ECHOPGM("point #");
             MYSERIAL.print(int(i));
             SERIAL_ECHOPGM(" measured: (");
@@ -581,11 +617,20 @@ bool calculate_machine_skew_and_offset_LS(
             SERIAL_ECHOPGM(", ");
             MYSERIAL.print(pgm_read_float(true_pts + i * 2 + 1), 5);
             SERIAL_ECHOPGM("), error: ");
-            MYSERIAL.print(sqrt(sqr(pgm_read_float(true_pts + i * 2) - x) + sqr(pgm_read_float(true_pts + i * 2 + 1) - y)));
+            MYSERIAL.print(err);
             SERIAL_ECHOLNPGM("");
         }
     }
 
+    if (result == BED_SKEW_OFFSET_DETECTION_PERFECT && fabs(a1) < BED_SKEW_ANGLE_MILD && fabs(a2) < BED_SKEW_ANGLE_MILD) {
+        if (verbosity_level > 0)
+            SERIAL_ECHOLNPGM("Very little skew detected. Disabling skew correction.");
+        vec_x[0] = MACHINE_AXIS_SCALE_X;
+        vec_x[1] = 0.f;
+        vec_y[0] = 0.f;
+        vec_y[1] = MACHINE_AXIS_SCALE_Y;
+    }
+
     // Invert the transformation matrix made of vec_x, vec_y and cntr.
     {
         float d = vec_x[0] * vec_y[1] - vec_x[1] * vec_y[0];
@@ -653,7 +698,7 @@ bool calculate_machine_skew_and_offset_LS(
         delay_keep_alive(100);
     }
 
-    return true;
+    return result;
 }
 
 #endif
@@ -666,18 +711,57 @@ void reset_bed_offset_and_skew()
     eeprom_update_dword((uint32_t*)(EEPROM_BED_CALIBRATION_VEC_X +4), 0x0FFFFFFFF);
     eeprom_update_dword((uint32_t*)(EEPROM_BED_CALIBRATION_VEC_Y +0), 0x0FFFFFFFF);
     eeprom_update_dword((uint32_t*)(EEPROM_BED_CALIBRATION_VEC_Y +4), 0x0FFFFFFFF);
+
+    // Reset the 8 16bit offsets.
+    for (int8_t i = 0; i < 4; ++ i)
+        eeprom_update_dword((uint32_t*)(EEPROM_BED_CALIBRATION_Z_JITTER+i*4), 0x0FFFFFFFF);
+}
+
+bool is_bed_z_jitter_data_valid()
+{
+    for (int8_t i = 0; i < 8; ++ i)
+        if (eeprom_read_word((uint16_t*)(EEPROM_BED_CALIBRATION_Z_JITTER+i*2)) == 0x0FFFF)
+            return false;
+    return true;
+}
+
+static void world2machine_update(const float vec_x[2], const float vec_y[2], const float cntr[2])
+{
+    world2machine_rotation_and_skew[0][0] = vec_x[0];
+    world2machine_rotation_and_skew[1][0] = vec_x[1];
+    world2machine_rotation_and_skew[0][1] = vec_y[0];
+    world2machine_rotation_and_skew[1][1] = vec_y[1];
+    world2machine_shift[0] = cntr[0];
+    world2machine_shift[1] = cntr[1];
+    // No correction.
+    world2machine_correction_mode = WORLD2MACHINE_CORRECTION_NONE;
+    if (world2machine_shift[0] != 0.f || world2machine_shift[1] != 0.f)
+        // Shift correction.
+        world2machine_correction_mode |= WORLD2MACHINE_CORRECTION_SHIFT;
+    if (world2machine_rotation_and_skew[0][0] != 1.f || world2machine_rotation_and_skew[0][1] != 0.f ||
+        world2machine_rotation_and_skew[1][0] != 0.f || world2machine_rotation_and_skew[1][1] != 1.f) {
+        // Rotation & skew correction.
+        world2machine_correction_mode |= WORLD2MACHINE_CORRECTION_SKEW;
+        // Invert the world2machine matrix.
+        float d = world2machine_rotation_and_skew[0][0] * world2machine_rotation_and_skew[1][1] - world2machine_rotation_and_skew[1][0] * world2machine_rotation_and_skew[0][1];
+        world2machine_rotation_and_skew_inv[0][0] =  world2machine_rotation_and_skew[1][1] / d;
+        world2machine_rotation_and_skew_inv[0][1] = -world2machine_rotation_and_skew[0][1] / d;
+        world2machine_rotation_and_skew_inv[1][0] = -world2machine_rotation_and_skew[1][0] / d;
+        world2machine_rotation_and_skew_inv[1][1] =  world2machine_rotation_and_skew[0][0] / d;
+    } else {
+        world2machine_rotation_and_skew_inv[0][0] = 1.f;
+        world2machine_rotation_and_skew_inv[0][1] = 0.f;
+        world2machine_rotation_and_skew_inv[1][0] = 0.f;
+        world2machine_rotation_and_skew_inv[1][1] = 1.f;
+    }
 }
 
 void world2machine_reset()
 {
-    // Identity transformation.
-    world2machine_rotation_and_skew[0][0] = 1.f;
-    world2machine_rotation_and_skew[0][1] = 0.f;
-    world2machine_rotation_and_skew[1][0] = 0.f;
-    world2machine_rotation_and_skew[1][1] = 1.f;
-    // Zero shift.
-    world2machine_shift[0] = 0.f;
-    world2machine_shift[1] = 0.f;
+    const float vx[] = { 1.f, 0.f };
+    const float vy[] = { 0.f, 1.f };
+    const float cntr[] = { 0.f, 0.f };
+    world2machine_update(vx, vy, cntr);
 }
 
 static inline bool vec_undef(const float v[2])
@@ -688,6 +772,7 @@ static inline bool vec_undef(const float v[2])
 
 void world2machine_initialize()
 {
+    SERIAL_ECHOLNPGM("world2machine_initialize()");
     float cntr[2] = {
         eeprom_read_float((float*)(EEPROM_BED_CALIBRATION_CENTER+0)),
         eeprom_read_float((float*)(EEPROM_BED_CALIBRATION_CENTER+4))
@@ -734,16 +819,24 @@ void world2machine_initialize()
     }
 
     if (reset) {
-        // SERIAL_ECHOLNPGM("Invalid bed correction matrix. Resetting to identity.");
+        SERIAL_ECHOLNPGM("Invalid bed correction matrix. Resetting to identity.");
         reset_bed_offset_and_skew();
         world2machine_reset();
     } else {
-        world2machine_rotation_and_skew[0][0] = vec_x[0];
-        world2machine_rotation_and_skew[1][0] = vec_x[1];
-        world2machine_rotation_and_skew[0][1] = vec_y[0];
-        world2machine_rotation_and_skew[1][1] = vec_y[1];
-        world2machine_shift[0] = cntr[0];
-        world2machine_shift[1] = cntr[1];
+        world2machine_update(vec_x, vec_y, cntr);
+        SERIAL_ECHOPGM("world2machine_initialize() loaded: ");
+        MYSERIAL.print(world2machine_rotation_and_skew[0][0], 5);
+        SERIAL_ECHOPGM(", ");
+        MYSERIAL.print(world2machine_rotation_and_skew[0][1], 5);
+        SERIAL_ECHOPGM(", ");
+        MYSERIAL.print(world2machine_rotation_and_skew[1][0], 5);
+        SERIAL_ECHOPGM(", ");
+        MYSERIAL.print(world2machine_rotation_and_skew[1][1], 5);
+        SERIAL_ECHOPGM(", offset ");
+        MYSERIAL.print(world2machine_shift[0], 5);
+        SERIAL_ECHOPGM(", ");
+        MYSERIAL.print(world2machine_shift[1], 5);
+        SERIAL_ECHOLNPGM("");
     }
 }
 
@@ -753,16 +846,10 @@ void world2machine_initialize()
 // and stores the result into current_position[x,y].
 void world2machine_update_current()
 {
-    // Invert the transformation matrix made of vec_x, vec_y and cntr.
-    float d = world2machine_rotation_and_skew[0][0] * world2machine_rotation_and_skew[1][1] - world2machine_rotation_and_skew[1][0] * world2machine_rotation_and_skew[0][1];
-    float Ainv[2][2] = { 
-        {   world2machine_rotation_and_skew[1][1] / d, - world2machine_rotation_and_skew[0][1] / d },
-        { - world2machine_rotation_and_skew[1][0] / d,   world2machine_rotation_and_skew[0][0] / d }
-    };
     float x = current_position[X_AXIS] - world2machine_shift[0];
     float y = current_position[Y_AXIS] - world2machine_shift[1];
-    current_position[X_AXIS] = Ainv[0][0] * x + Ainv[0][1] * y;
-    current_position[Y_AXIS] = Ainv[1][0] * x + Ainv[1][1] * y;
+    current_position[X_AXIS] = world2machine_rotation_and_skew_inv[0][0] * x + world2machine_rotation_and_skew_inv[0][1] * y;
+    current_position[Y_AXIS] = world2machine_rotation_and_skew_inv[1][0] * x + world2machine_rotation_and_skew_inv[1][1] * y;
 }
 
 static inline void go_xyz(float x, float y, float z, float fr)
@@ -798,16 +885,19 @@ static inline void update_current_position_z()
 }
 
 // At the current position, find the Z stop.
-inline void find_bed_induction_sensor_point_z() 
+inline bool find_bed_induction_sensor_point_z(float minimum_z) 
 {
     bool endstops_enabled  = enable_endstops(true);
     bool endstop_z_enabled = enable_z_endstop(false);
+    endstop_z_hit_on_purpose();
 
     // move down until you find the bed
-    current_position[Z_AXIS] = -10;
+    current_position[Z_AXIS] = minimum_z;
     go_to_current(homing_feedrate[Z_AXIS]/60);
     // we have to let the planner know where we are right now as it is not where we said to go.
     update_current_position_z();
+    if (! endstop_z_hit_on_purpose())
+        goto error;
 
     // move up the retract distance
     current_position[Z_AXIS] += home_retract_mm_ext(Z_AXIS);
@@ -815,12 +905,21 @@ inline void find_bed_induction_sensor_point_z()
     
     // move back down slowly to find bed
     current_position[Z_AXIS] -= home_retract_mm_ext(Z_AXIS) * 2;
+    current_position[Z_AXIS] = min(current_position[Z_AXIS], minimum_z);
     go_to_current(homing_feedrate[Z_AXIS]/(4*60));
     // we have to let the planner know where we are right now as it is not where we said to go.
     update_current_position_z();
+    if (! endstop_z_hit_on_purpose())
+        goto error;
 
     enable_endstops(endstops_enabled);
     enable_z_endstop(endstop_z_enabled);
+    return true;
+
+error:
+    enable_endstops(endstops_enabled);
+    enable_z_endstop(endstop_z_enabled);
+    return false;
 }
 
 // Search around the current_position[X,Y],
@@ -1097,11 +1196,24 @@ inline bool improve_bed_induction_sensor_point()
     return found;
 }
 
+static inline void debug_output_point(const char *type, const float &x, const float &y, const float &z)
+{
+    SERIAL_ECHOPGM("Measured ");
+    SERIAL_ECHORPGM(type);
+    SERIAL_ECHOPGM(" ");
+    MYSERIAL.print(x, 5);
+    SERIAL_ECHOPGM(", ");
+    MYSERIAL.print(y, 5);
+    SERIAL_ECHOPGM(", ");
+    MYSERIAL.print(z, 5);
+    SERIAL_ECHOLNPGM("");
+}
+
 // Search around the current_position[X,Y,Z].
 // It is expected, that the induction sensor is switched on at the current position.
 // Look around this center point by painting a star around the point.
 #define IMPROVE_BED_INDUCTION_SENSOR_SEARCH_RADIUS (8.f)
-inline bool improve_bed_induction_sensor_point2(bool lift_z_on_min_y)
+inline bool improve_bed_induction_sensor_point2(bool lift_z_on_min_y, int8_t verbosity_level)
 {
     float center_old_x = current_position[X_AXIS];
     float center_old_y = current_position[Y_AXIS];
@@ -1138,6 +1250,10 @@ inline bool improve_bed_induction_sensor_point2(bool lift_z_on_min_y)
             goto canceled;
         }
         b = current_position[X_AXIS];
+        if (verbosity_level >= 5) {
+            debug_output_point(PSTR("left" ), a, current_position[Y_AXIS], current_position[Z_AXIS]);
+            debug_output_point(PSTR("right"), b, current_position[Y_AXIS], current_position[Z_AXIS]);
+        }
 
         // Go to the center.
         enable_z_endstop(false);
@@ -1188,6 +1304,10 @@ inline bool improve_bed_induction_sensor_point2(bool lift_z_on_min_y)
             goto canceled;
         }
         b = current_position[Y_AXIS];
+        if (verbosity_level >= 5) {
+            debug_output_point(PSTR("top" ), current_position[X_AXIS], a, current_position[Z_AXIS]);
+            debug_output_point(PSTR("bottom"), current_position[X_AXIS], b, current_position[Z_AXIS]);
+        }
 
         // Go to the center.
         enable_z_endstop(false);
@@ -1208,11 +1328,20 @@ canceled:
 // Searching in a zig-zag movement in a plane for the maximum width of the response.
 #define IMPROVE_BED_INDUCTION_SENSOR_POINT3_SEARCH_RADIUS (4.f)
 #define IMPROVE_BED_INDUCTION_SENSOR_POINT3_SEARCH_STEP_FINE_Y (0.1f)
-inline bool improve_bed_induction_sensor_point3(int verbosity_level)
+enum InductionSensorPointStatusType
+{
+    INDUCTION_SENSOR_POINT_FAILED = -1,
+    INDUCTION_SENSOR_POINT_OK = 0,
+    INDUCTION_SENSOR_POINT_FAR,
+};
+inline InductionSensorPointStatusType improve_bed_induction_sensor_point3(int verbosity_level)
 {
     float center_old_x = current_position[X_AXIS];
     float center_old_y = current_position[Y_AXIS];
     float a, b;
+    // Was the sensor point detected too far in the minus Y axis?
+    // If yes, the center of the induction point cannot be reached by the machine.
+    bool y_too_far = false;
     {
         float x0 = center_old_x - IMPROVE_BED_INDUCTION_SENSOR_POINT3_SEARCH_RADIUS;
         float x1 = center_old_x + IMPROVE_BED_INDUCTION_SENSOR_POINT3_SEARCH_RADIUS;
@@ -1266,16 +1395,9 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
                 // goto canceled;
             }
             b = current_position[X_AXIS];
-            if (verbosity_level > 20) {
-                SERIAL_ECHOPGM("Measured left ");
-                MYSERIAL.print(a, 5);
-                SERIAL_ECHOPGM("right ");
-                MYSERIAL.print(b, 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(y, 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(current_position[Z_AXIS], 5);
-                SERIAL_ECHOLNPGM("");
+            if (verbosity_level >= 5) {
+                debug_output_point(PSTR("left" ), a, current_position[Y_AXIS], current_position[Z_AXIS]);
+                debug_output_point(PSTR("right"), b, current_position[Y_AXIS], current_position[Z_AXIS]);
             }
             float d = b - a;
             if (d > dmax) {
@@ -1291,7 +1413,7 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
             goto canceled;
         }
 
-        SERIAL_PROTOCOLPGM("ok 1\n");
+        // SERIAL_PROTOCOLPGM("ok 1\n");
         // Search in the negative Y direction, until a maximum diameter is found.
         dmax = 0.;
         if (y0 + 1.f < y1)
@@ -1325,16 +1447,9 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
                 */
             }
             b = current_position[X_AXIS];
-            if (verbosity_level > 20) {
-                SERIAL_ECHOPGM("Measured left ");
-                MYSERIAL.print(a, 5);
-                SERIAL_ECHOPGM("right ");
-                MYSERIAL.print(b, 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(y, 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(current_position[Z_AXIS], 5);
-                SERIAL_ECHOLNPGM("");
+            if (verbosity_level >= 5) {
+                debug_output_point(PSTR("left" ), a, current_position[Y_AXIS], current_position[Z_AXIS]);
+                debug_output_point(PSTR("right"), b, current_position[Y_AXIS], current_position[Z_AXIS]);
             }
             float d = b - a;
             if (d > dmax) {
@@ -1352,6 +1467,7 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
             // Found only the point going from ymin to ymax.
             current_position[X_AXIS] = xmax1;
             current_position[Y_AXIS] = y0;
+            y_too_far = true;
         } else {
             // Both points found (from ymin to ymax and from ymax to ymin).
             float p = 0.5f;
@@ -1403,6 +1519,10 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
             goto canceled;
         }
         b = current_position[X_AXIS];
+        if (verbosity_level >= 5) {
+            debug_output_point(PSTR("left" ), a, current_position[Y_AXIS], current_position[Z_AXIS]);
+            debug_output_point(PSTR("right"), b, current_position[Y_AXIS], current_position[Z_AXIS]);
+        }
 
         // Go to the center.
         enable_z_endstop(false);
@@ -1410,13 +1530,13 @@ inline bool improve_bed_induction_sensor_point3(int verbosity_level)
         go_xy(current_position[X_AXIS], current_position[Y_AXIS], homing_feedrate[X_AXIS] / 60.f);
     }
 
-    return true;
+    return y_too_far ? INDUCTION_SENSOR_POINT_FAR : INDUCTION_SENSOR_POINT_OK;
 
 canceled:
     // Go back to the center.
     enable_z_endstop(false);
     go_xy(current_position[X_AXIS], current_position[Y_AXIS], homing_feedrate[X_AXIS] / 60.f);
-    return false;
+    return INDUCTION_SENSOR_POINT_FAILED;
 }
 
 // Scan the mesh bed induction points one by one by a left-right zig-zag movement,
@@ -1447,29 +1567,15 @@ inline void scan_bed_induction_sensor_point()
         enable_z_endstop(true);
         go_xy(x1, y, homing_feedrate[X_AXIS] / 60.f);
         update_current_position_xyz();
-        if (endstop_z_hit_on_purpose()) {
-            SERIAL_ECHOPGM("Measured left: ");
-            MYSERIAL.print(current_position[X_AXIS], 5);
-            SERIAL_ECHOPGM(", ");
-            MYSERIAL.print(y, 5);
-            SERIAL_ECHOPGM(", ");
-            MYSERIAL.print(current_position[Z_AXIS], 5);
-            SERIAL_ECHOLNPGM("");
-        }
+        if (endstop_z_hit_on_purpose())
+            debug_output_point(PSTR("left" ), current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
         enable_z_endstop(false);
         go_xy(x1, y, homing_feedrate[X_AXIS] / 60.f);
         enable_z_endstop(true);
         go_xy(x0, y, homing_feedrate[X_AXIS] / 60.f);
         update_current_position_xyz();
-        if (endstop_z_hit_on_purpose()) {
-            SERIAL_ECHOPGM("Measured right: ");
-            MYSERIAL.print(current_position[X_AXIS], 5);
-            SERIAL_ECHOPGM(", ");
-            MYSERIAL.print(y, 5);
-            SERIAL_ECHOPGM(", ");
-            MYSERIAL.print(current_position[Z_AXIS], 5);
-            SERIAL_ECHOLNPGM("");
-        }
+        if (endstop_z_hit_on_purpose())
+            debug_output_point(PSTR("right"), current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
     }
 
     enable_z_endstop(false);
@@ -1480,7 +1586,7 @@ inline void scan_bed_induction_sensor_point()
 
 #define MESH_BED_CALIBRATION_SHOW_LCD
 
-bool find_bed_offset_and_skew(int8_t verbosity_level)
+BedSkewOffsetDetectionResultType find_bed_offset_and_skew(int8_t verbosity_level)
 {
     // Don't let the manage_inactivity() function remove power from the motors.
     refresh_cmd_timeout();
@@ -1533,17 +1639,17 @@ bool find_bed_offset_and_skew(int8_t verbosity_level)
         if (verbosity_level >= 10)
             delay_keep_alive(3000);
         if (! find_bed_induction_sensor_point_xy())
-            return false;
+            return BED_SKEW_OFFSET_DETECTION_FAILED;
         find_bed_induction_sensor_point_z();
 #if 1
         if (k == 0) {
             // Improve the position of the 1st row sensor points by a zig-zag movement.
             int8_t i = 4;
             for (;;) {
-                if (improve_bed_induction_sensor_point3(verbosity_level))
+                if (improve_bed_induction_sensor_point3(verbosity_level) != INDUCTION_SENSOR_POINT_FAILED)
                     break;
                 if (-- i == 0)
-                    return false;
+                    return BED_SKEW_OFFSET_DETECTION_FAILED;
                 // Try to move the Z axis down a bit to increase a chance of the sensor to trigger.
                 current_position[Z_AXIS] -= 0.025f;
                 enable_endstops(false);
@@ -1552,7 +1658,7 @@ bool find_bed_offset_and_skew(int8_t verbosity_level)
             }
             if (i == 0)
                 // not found
-                return false;
+                return BED_SKEW_OFFSET_DETECTION_FAILED;
         }
 #endif
         if (verbosity_level >= 10)
@@ -1587,12 +1693,7 @@ bool find_bed_offset_and_skew(int8_t verbosity_level)
     }
 
     calculate_machine_skew_and_offset_LS(pts, 4, bed_ref_points_4, vec_x, vec_y, cntr, verbosity_level);
-    world2machine_rotation_and_skew[0][0] = vec_x[0];
-    world2machine_rotation_and_skew[1][0] = vec_x[1];
-    world2machine_rotation_and_skew[0][1] = vec_y[0];
-    world2machine_rotation_and_skew[1][1] = vec_y[1];
-    world2machine_shift[0] = cntr[0];
-    world2machine_shift[1] = cntr[1];
+    world2machine_update(vec_x, vec_y, cntr);
 #if 1
     // Fearlessly store the calibration values into the eeprom.
     eeprom_update_float((float*)(EEPROM_BED_CALIBRATION_CENTER+0), cntr [0]);
@@ -1621,10 +1722,10 @@ bool find_bed_offset_and_skew(int8_t verbosity_level)
         }
     }
 
-    return true;
+    return BED_SKEW_OFFSET_DETECTION_PERFECT;
 }
 
-bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
+BedSkewOffsetDetectionResultType improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
 {
     // Don't let the manage_inactivity() function remove power from the motors.
     refresh_cmd_timeout();
@@ -1657,6 +1758,9 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
 #endif /* MESH_BED_CALIBRATION_SHOW_LCD */
 
     // Collect a matrix of 9x9 points.
+    bool leftFrontTooFar = false;
+    bool rightFrontTooFar = false;
+    BedSkewOffsetDetectionResultType result = BED_SKEW_OFFSET_DETECTION_PERFECT;
     for (int8_t mesh_point = 0; mesh_point < 9; ++ mesh_point) {
         // Don't let the manage_inactivity() function remove power from the motors.
         refresh_cmd_timeout();
@@ -1715,11 +1819,19 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
                 // of the sensor points, the y position cannot be measured
                 // by a cross center method.
                 // Use a zig-zag search for the first row of the points.
-                found = improve_bed_induction_sensor_point3(verbosity_level);
+                InductionSensorPointStatusType status = improve_bed_induction_sensor_point3(verbosity_level);
+                if (status == INDUCTION_SENSOR_POINT_FAILED) {
+                    found = false;
+                } else {
+                    found = true;
+                    if (iter == 7 && INDUCTION_SENSOR_POINT_FAR && mesh_point != 1)
+                        // Remember, which side of the bed is shifted too far in the minus y direction.
+                        result = (mesh_point == 0) ? BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR : BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR;
+                }
             } else {
                 switch (method) {
                     case 0: found = improve_bed_induction_sensor_point(); break;
-                    case 1: found = improve_bed_induction_sensor_point2(mesh_point < 3); break;
+                    case 1: found = improve_bed_induction_sensor_point2(mesh_point < 3, verbosity_level); break;
                     default: break;
                 }
             }
@@ -1763,7 +1875,7 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
     enable_endstops(false);
     enable_z_endstop(false);
 
-    if (verbosity_level >= 10) {
+    if (verbosity_level >= 5) {
         // Test the positions. Are the positions reproducible?
         for (int8_t mesh_point = 0; mesh_point < 9; ++ mesh_point) {
             // Don't let the manage_inactivity() function remove power from the motors.
@@ -1772,29 +1884,25 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
             // Use the coorrected coordinate, which is a result of find_bed_offset_and_skew().
             current_position[X_AXIS] = pts[mesh_point*2];
             current_position[Y_AXIS] = pts[mesh_point*2+1];
-            go_to_current(homing_feedrate[X_AXIS]/60);
-            delay_keep_alive(3000);
-            #if 0
-            if (verbosity_level > 20) {
-                SERIAL_ECHOPGM("Final measured bed point ");
-                SERIAL_ECHO(mesh_point);
-                SERIAL_ECHOPGM(": ");
-                MYSERIAL.print(current_position[X_AXIS], 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(current_position[Y_AXIS], 5);
-                SERIAL_ECHOLNPGM("");
+            if (verbosity_level >= 10) {
+                go_to_current(homing_feedrate[X_AXIS]/60);
+                delay_keep_alive(3000);
             }
-            #endif
+            SERIAL_ECHOPGM("Final measured bed point ");
+            SERIAL_ECHO(mesh_point);
+            SERIAL_ECHOPGM(": ");
+            MYSERIAL.print(current_position[X_AXIS], 5);
+            SERIAL_ECHOPGM(", ");
+            MYSERIAL.print(current_position[Y_AXIS], 5);
+            SERIAL_ECHOLNPGM("");
         }
     }
 
-    calculate_machine_skew_and_offset_LS(pts, 9, bed_ref_points, vec_x, vec_y, cntr, verbosity_level);
-    world2machine_rotation_and_skew[0][0] = vec_x[0];
-    world2machine_rotation_and_skew[1][0] = vec_x[1];
-    world2machine_rotation_and_skew[0][1] = vec_y[0];
-    world2machine_rotation_and_skew[1][1] = vec_y[1];
-    world2machine_shift[0] = cntr[0];
-    world2machine_shift[1] = cntr[1];
+    result = calculate_machine_skew_and_offset_LS(pts, 9, bed_ref_points, vec_x, vec_y, cntr, verbosity_level);
+    if (result == BED_SKEW_OFFSET_DETECTION_FAILED)
+        goto canceled;
+
+    world2machine_update(vec_x, vec_y, cntr);
 #if 1
     // Fearlessly store the calibration values into the eeprom.
     eeprom_update_float((float*)(EEPROM_BED_CALIBRATION_CENTER+0), cntr [0]);
@@ -1811,7 +1919,7 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
     enable_endstops(false);
     enable_z_endstop(false);
 
-    if (verbosity_level >= 10) {
+    if (verbosity_level >= 5) {
         // Test the positions. Are the positions reproducible? Now the calibration is active in the planner.
         delay_keep_alive(3000);
         for (int8_t mesh_point = 0; mesh_point < 9; ++ mesh_point) {
@@ -1821,37 +1929,125 @@ bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level)
             // Use the coorrected coordinate, which is a result of find_bed_offset_and_skew().
             current_position[X_AXIS] = pgm_read_float(bed_ref_points+mesh_point*2);
             current_position[Y_AXIS] = pgm_read_float(bed_ref_points+mesh_point*2+1);
-            go_to_current(homing_feedrate[X_AXIS]/60);
-            delay_keep_alive(3000);
-            #if 0
-            if (verbosity_level > 20) {
-                SERIAL_ECHOPGM("Final calculated bed point ");
-                SERIAL_ECHO(mesh_point);
-                SERIAL_ECHOPGM(": ");
-                MYSERIAL.print(st_get_position_mm(X_AXIS), 5);
-                SERIAL_ECHOPGM(", ");
-                MYSERIAL.print(st_get_position_mm(Y_AXIS), 5);
-                SERIAL_ECHOLNPGM("");
+            if (verbosity_level >= 10) {
+                go_to_current(homing_feedrate[X_AXIS]/60);
+                delay_keep_alive(3000);
             }
-            #endif
+            SERIAL_ECHOPGM("Final calculated bed point ");
+            SERIAL_ECHO(mesh_point);
+            SERIAL_ECHOPGM(": ");
+            MYSERIAL.print(st_get_position_mm(X_AXIS), 5);
+            SERIAL_ECHOPGM(", ");
+            MYSERIAL.print(st_get_position_mm(Y_AXIS), 5);
+            SERIAL_ECHOLNPGM("");
         }
     }
 
+    // Sample Z heights for the mesh bed leveling.
+    // In addition, store the results into an eeprom, to be used later for verification of the bed leveling process.
+    {
+        // The first point defines the reference.
+        current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
+        go_to_current(homing_feedrate[Z_AXIS]/60);
+        current_position[X_AXIS] = pgm_read_float(bed_ref_points);
+        current_position[Y_AXIS] = pgm_read_float(bed_ref_points+1);
+        go_to_current(homing_feedrate[X_AXIS]/60);
+        memcpy(destination, current_position, sizeof(destination));
+        enable_endstops(true);
+        homeaxis(Z_AXIS);
+        mbl.set_z(0, 0, current_position[Z_AXIS]);
+        enable_endstops(false);
+    }
+    for (int8_t mesh_point = 1; mesh_point != MESH_MEAS_NUM_X_POINTS * MESH_MEAS_NUM_Y_POINTS; ++ mesh_point) {
+        current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
+        go_to_current(homing_feedrate[Z_AXIS]/60);
+        current_position[X_AXIS] = pgm_read_float(bed_ref_points+2*mesh_point);
+        current_position[Y_AXIS] = pgm_read_float(bed_ref_points+2*mesh_point+1);
+        go_to_current(homing_feedrate[X_AXIS]/60);
+        find_bed_induction_sensor_point_z();
+        // Get cords of measuring point
+        int8_t ix = mesh_point % MESH_MEAS_NUM_X_POINTS;
+        int8_t iy = mesh_point / MESH_MEAS_NUM_X_POINTS;
+        if (iy & 1) ix = (MESH_MEAS_NUM_X_POINTS - 1) - ix; // Zig zag
+        mbl.set_z(ix, iy, current_position[Z_AXIS]);
+    }
+    {
+        // Verify the span of the Z values.
+        float zmin = mbl.z_values[0][0];
+        float zmax = zmax;
+        for (int8_t j = 0; j < 3; ++ j)
+           for (int8_t i = 0; i < 3; ++ i) {
+                zmin = min(zmin, mbl.z_values[j][i]);
+                zmax = min(zmax, mbl.z_values[j][i]);
+           }
+        if (zmax - zmin > 3.f) {
+            // The span of the Z offsets is extreme. Give up.
+            // Homing failed on some of the points.
+            SERIAL_PROTOCOLLNPGM("Exreme span of the Z values!");
+            goto canceled;
+        }
+    }
+
+    // Store the correction values to EEPROM.
+    // Offsets of the Z heiths of the calibration points from the first point.
+    // The offsets are saved as 16bit signed int, scaled to tenths of microns.
+    {
+        uint16_t addr = EEPROM_BED_CALIBRATION_Z_JITTER;
+        for (int8_t j = 0; j < 3; ++ j)
+            for (int8_t i = 0; i < 3; ++ i) {
+                if (i == 0 && j == 0)
+                    continue;
+                float dif = mbl.z_values[j][i] - mbl.z_values[0][0];
+                int16_t dif_quantized = int16_t(floor(dif * 100.f + 0.5f));
+                eeprom_update_word((uint16_t*)addr, *reinterpret_cast<uint16_t*>(&dif_quantized));
+                {
+                    uint16_t z_offset_u = eeprom_read_word((uint16_t*)addr);
+                    float dif2 = *reinterpret_cast<int16_t*>(&z_offset_u) * 0.01;
+
+                    SERIAL_ECHOPGM("Bed point ");
+                    SERIAL_ECHO(i);
+                    SERIAL_ECHOPGM(",");
+                    SERIAL_ECHO(j);
+                    SERIAL_ECHOPGM(", differences: written ");
+                    MYSERIAL.print(dif, 5);
+                    SERIAL_ECHOPGM(", read: ");
+                    MYSERIAL.print(dif2, 5);
+                    SERIAL_ECHOLNPGM("");
+                }
+                addr += 2;
+            }
+    }
+
+    mbl.upsample_3x3();
+    mbl.active = true;
+
     // Don't let the manage_inactivity() function remove power from the motors.
     refresh_cmd_timeout();
 
+    // Go home.
+    current_position[Z_AXIS] = Z_MIN_POS;
+    go_to_current(homing_feedrate[Z_AXIS]/60);
+    current_position[X_AXIS] = X_MIN_POS+0.2;
+    current_position[Y_AXIS] = Y_MIN_POS+0.2;
+    go_to_current(homing_feedrate[X_AXIS]/60);
+
     enable_endstops(endstops_enabled);
     enable_z_endstop(endstop_z_enabled);
-    return true;
+    // Don't let the manage_inactivity() function remove power from the motors.
+    refresh_cmd_timeout();
+    return result;
 
 canceled:
     // Don't let the manage_inactivity() function remove power from the motors.
     refresh_cmd_timeout();
+    // Print head up.
+    current_position[Z_AXIS] = MESH_HOME_Z_SEARCH;
+    go_to_current(homing_feedrate[Z_AXIS]/60);
     // Store the identity matrix to EEPROM.
     reset_bed_offset_and_skew();
     enable_endstops(endstops_enabled);
     enable_z_endstop(endstop_z_enabled);
-    return false;
+    return BED_SKEW_OFFSET_DETECTION_FAILED;
 }
 
 bool scan_bed_induction_points(int8_t verbosity_level)

+ 109 - 3
Firmware/mesh_bed_calibration.h

@@ -6,10 +6,19 @@
 // is built properly, the end stops are at the correct positions and the axes are perpendicular.
 extern const float bed_ref_points[] PROGMEM;
 
+// Is the world2machine correction activated?
+enum World2MachineCorrectionMode
+{
+	WORLD2MACHINE_CORRECTION_NONE  = 0,
+	WORLD2MACHINE_CORRECTION_SHIFT = 1,
+	WORLD2MACHINE_CORRECTION_SKEW  = 2,
+};
+extern uint8_t world2machine_correction_mode;
 // 2x2 transformation matrix from the world coordinates to the machine coordinates.
 // Corrects for the rotation and skew of the machine axes.
 // Used by the planner's plan_buffer_line() and plan_set_position().
 extern float world2machine_rotation_and_skew[2][2];
+extern float world2machine_rotation_and_skew_inv[2][2];
 // Shift of the machine zero point, in the machine coordinates.
 extern float world2machine_shift[2];
 
@@ -23,13 +32,110 @@ extern void world2machine_initialize();
 // to current_position[x,y].
 extern void world2machine_update_current();
 
+inline void world2machine(const float &x, const float &y, float &out_x, float &out_y)
+{
+	if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_NONE) {
+		// No correction.
+		out_x = x;
+		out_y = y;
+	} else {
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SKEW) {
+			// Firs the skew & rotation correction.
+			out_x = world2machine_rotation_and_skew[0][0] * x + world2machine_rotation_and_skew[0][1] * y;
+			out_y = world2machine_rotation_and_skew[1][0] * x + world2machine_rotation_and_skew[1][1] * y;
+		}
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SHIFT) {
+			// Then add the offset.
+			out_x += world2machine_shift[0];
+			out_y += world2machine_shift[1];
+		}
+	}
+}
 
-extern void find_bed_induction_sensor_point_z();
+inline void world2machine(float &x, float &y)
+{
+	if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_NONE) {
+		// No correction.
+	} else {
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SKEW) {
+			// Firs the skew & rotation correction.
+			float out_x = world2machine_rotation_and_skew[0][0] * x + world2machine_rotation_and_skew[0][1] * y;
+			float out_y = world2machine_rotation_and_skew[1][0] * x + world2machine_rotation_and_skew[1][1] * y;
+			x = out_x;
+			y = out_y;
+		}
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SHIFT) {
+			// Then add the offset.
+			x += world2machine_shift[0];
+			y += world2machine_shift[1];
+		}
+	}
+}
+
+inline void machine2world(float x, float y, float &out_x, float &out_y)
+{
+	if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_NONE) {
+		// No correction.
+		out_x = x;
+		out_y = y;
+	} else {
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SHIFT) {
+			// Then add the offset.
+			x -= world2machine_shift[0];
+			y -= world2machine_shift[1];
+		}
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SKEW) {
+			// Firs the skew & rotation correction.
+			out_x = world2machine_rotation_and_skew_inv[0][0] * x + world2machine_rotation_and_skew_inv[0][1] * y;
+			out_y = world2machine_rotation_and_skew_inv[1][0] * x + world2machine_rotation_and_skew_inv[1][1] * y;
+		}
+	}
+}
+
+inline void machine2world(float &x, float &y)
+{
+	if (world2machine_correction_mode == WORLD2MACHINE_CORRECTION_NONE) {
+		// No correction.
+	} else {
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SHIFT) {
+			// Then add the offset.
+			x -= world2machine_shift[0];
+			y -= world2machine_shift[1];
+		}
+		if (world2machine_correction_mode & WORLD2MACHINE_CORRECTION_SKEW) {
+			// Firs the skew & rotation correction.
+			float out_x = world2machine_rotation_and_skew_inv[0][0] * x + world2machine_rotation_and_skew_inv[0][1] * y;
+			float out_y = world2machine_rotation_and_skew_inv[1][0] * x + world2machine_rotation_and_skew_inv[1][1] * y;
+			x = out_x;
+			y = out_y;
+		}
+	}
+}
+
+extern bool find_bed_induction_sensor_point_z(float minimum_z = -10.f);
 extern bool find_bed_induction_sensor_point_xy();
 
-extern bool find_bed_offset_and_skew(int8_t verbosity_level);
-extern bool improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level);
+// Positive or zero: ok
+// Negative: failed
+enum BedSkewOffsetDetectionResultType {
+	// Detection failed, some point was not found.
+	BED_SKEW_OFFSET_DETECTION_FAILED = -1,
+
+	// Detection finished with success.
+	BED_SKEW_OFFSET_DETECTION_PERFECT = 0,
+	BED_SKEW_OFFSET_DETECTION_SKEW_MILD,
+	BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME,
+	// Detection finished with success, but it is recommended to fix the printer mechanically.
+	BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR,
+	BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR
+};
+
+extern BedSkewOffsetDetectionResultType find_bed_offset_and_skew(int8_t verbosity_level);
+extern BedSkewOffsetDetectionResultType improve_bed_offset_and_skew(int8_t method, int8_t verbosity_level);
+
+
 extern void reset_bed_offset_and_skew();
+extern bool is_bed_z_jitter_data_valid();
 
 // Scan the mesh bed induction points one by one by a left-right zig-zag movement,
 // write the trigger coordinates to the serial line.

+ 1 - 4
Firmware/planner.cpp

@@ -567,10 +567,7 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate
         SERIAL_ECHOLNPGM("");
       #endif
 
-        float tmpx = x;
-        float tmpy = y;
-        x = world2machine_rotation_and_skew[0][0] * tmpx + world2machine_rotation_and_skew[0][1] * tmpy + world2machine_shift[0];
-        y = world2machine_rotation_and_skew[1][0] * tmpx + world2machine_rotation_and_skew[1][1] * tmpy + world2machine_shift[1];
+        world2machine(x, y);
 
       #if 0
         SERIAL_ECHOPGM("Planner, target position, corrected: ");

+ 77 - 1
Firmware/ultralcd.cpp

@@ -1316,6 +1316,82 @@ canceled:
     return false;
 }
 
+static inline bool pgm_is_whitespace(const char *c)
+{
+    return pgm_read_byte(c) == ' ' || pgm_read_byte(c) == '\t' || pgm_read_byte(c) == '\r' || pgm_read_byte(c) == '\n';
+}
+
+void lcd_display_message_fullscreen_P(const char *msg)
+{
+    // Disable update of the screen by the usual lcd_update() routine. 
+    lcd_update_enable(false);
+    lcd_implementation_clear();
+    lcd.setCursor(0, 0);
+    for (int8_t row = 0; row < 4; ++ row) {
+        while (pgm_is_whitespace(msg))
+            ++ msg;
+        if (pgm_read_byte(msg) == 0)
+            // End of the message.
+            break;
+        lcd.setCursor(0, row);
+        const char *msgend2 = msg + min(strlen_P(msg), 20);
+        const char *msgend = msgend2;
+        if (pgm_read_byte(msgend) != 0 && ! pgm_is_whitespace(msgend)) {
+              // Splitting a word. Find the start of the current word.
+            while (msgend > msg && ! pgm_is_whitespace(msgend - 1))
+                 -- msgend;
+            if (msgend == msg)
+                // Found a single long word, which cannot be split. Just cut it.
+                msgend = msgend2;
+        }
+        for (; msg < msgend; ++ msg) {
+            char c = char(pgm_read_byte(msg));
+            if (c == '~')
+                c = ' ';
+            lcd.print(c);
+        }
+    }
+}
+
+void lcd_bed_calibration_show_result(BedSkewOffsetDetectionResultType result)
+{
+    const char *msg = NULL;
+    switch (result) {
+        case BED_SKEW_OFFSET_DETECTION_FAILED:
+        default:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_FAILED;
+            break;
+        case BED_SKEW_OFFSET_DETECTION_PERFECT:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_PERFECT;
+            break;
+        case BED_SKEW_OFFSET_DETECTION_SKEW_MILD:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_SKEW_MILD;
+            break;
+        case BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_SKEW_EXTREME;
+            break;
+        case BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_FRONT_LEFT_FAR;
+            break;
+        case BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR:
+            msg = MSG_BED_SKEW_OFFSET_DETECTION_FRONT_RIGHT_FAR;
+            break;
+    }
+
+    lcd_display_message_fullscreen_P(msg);
+
+    // Until confirmed by a button click.
+    for (;;) {
+        delay_keep_alive(50);
+        if (lcd_clicked()) {
+            while (lcd_clicked()) ;
+            delay(10);
+            while (lcd_clicked()) ;
+            break;
+        }
+    }
+}
+
 static void lcd_show_end_stops() {
     lcd.setCursor(0, 0);
     lcd_printPGM((PSTR("End stops diag")));
@@ -1596,7 +1672,7 @@ static void lcd_settings_menu()
 	if (!isPrintPaused)
 	{
 		MENU_ITEM(submenu, MSG_SELFTEST, lcd_selftest);
-    MENU_ITEM(submenu, PSTR("Show end stops"), menu_show_end_stops);
+    MENU_ITEM(submenu, MSG_SHOW_END_STOPS, menu_show_end_stops);
     MENU_ITEM(submenu, MSG_CALIBRATE_BED, lcd_mesh_calibration);
     MENU_ITEM(gcode, MSG_CALIBRATE_BED_RESET, PSTR("M44"));
 	}

+ 8 - 0
Firmware/ultralcd.h

@@ -2,6 +2,7 @@
 #define ULTRALCD_H
 
 #include "Marlin.h"
+#include "mesh_bed_calibration.h"
 
 #ifdef ULTRA_LCD
 
@@ -37,7 +38,14 @@
   static void lcd_selftest_error(int _error_no, const char *_error_1, const char *_error_2);
   static void lcd_menu_statistics();
 
+  extern void lcd_display_message_fullscreen_P(const char *msg);
+
+  // Ask the user to move the Z axis up to the end stoppers and let
+  // the user confirm that it has been done.
   extern bool lcd_calibrate_z_end_stop_manual();
+  // Show the result of the calibration process on the LCD screen.
+  extern void lcd_bed_calibration_show_result(BedSkewOffsetDetectionResultType result);
+
   extern void lcd_diag_show_end_stops();
 
 #ifdef DOGLCD