From cabc5b4003ef989713ae160a973b8765de1f2c1b Mon Sep 17 00:00:00 2001 From: yochiwarez Date: Sat, 29 Jun 2024 07:24:34 +0100 Subject: [PATCH] add_auto_compensation_support axis_twist_compensation: new command implementation AXIS_TWIST_COMPENSATION_AUTOCALIBRATE This commit adds automatic calculation support for compensating X and Y axis twist in the axis_twist_compensation module. Signed-off-by: Jorge Apaza Merma --- docs/Axis_Twist_Compensation.md | 8 ++ docs/G-Codes.md | 5 +- klippy/extras/axis_twist_compensation.py | 155 +++++++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) diff --git a/docs/Axis_Twist_Compensation.md b/docs/Axis_Twist_Compensation.md index ee582aca4c26..b052a57f2d7a 100644 --- a/docs/Axis_Twist_Compensation.md +++ b/docs/Axis_Twist_Compensation.md @@ -42,6 +42,14 @@ points along the bed > **Tip:** Bed temperature and nozzle temperature and size do not seem to have > an influence to the calibration process. +## New Command: AXIS_TWIST_COMPENSATION_AUTOCALIBRATE + +The AXIS_TWIST_COMPENSATION_AUTOCALIBRATE command performs automatic calibration to calculate the twist of the X and Y axes without manual measurement. + + * Recommendation: Ensure the bed is completely flat and aligned (without tilt) as much as possible before performing the autocalibration. The autocalibration will take probes and automatically calculate the twist of the X and Y axes. + +User Recommendation: It is recommended to use a glass bed for calibration. + ## [axis_twist_compensation] setup and commands Configuration options for [axis_twist_compensation] can be found in the diff --git a/docs/G-Codes.md b/docs/G-Codes.md index e55fba35db80..11ec22821575 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -146,9 +146,12 @@ The following commands are available when the section](Config_Reference.md#axis_twist_compensation) is enabled. #### AXIS_TWIST_COMPENSATION_CALIBRATE -`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=]`: Initiates the X +`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=] [AXIS=]`: Initiates the X twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along the X axis to calibrate at and defaults to 3. +`axis` can either be X or Y + +`AXIS_TWIST_COMPENSATION_AUTOCALIBRATE` performs automatic calibration to calculate the twist of the X and Y axes without manual measurement. ### [bed_mesh] diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index e0bbb7957260..18012e0a1730 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -140,6 +140,10 @@ def _register_gcode_handlers(self): 'AXIS_TWIST_COMPENSATION_CALIBRATE', self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE, desc=self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help) + self.gcode.register_command( + 'AXIS_TWIST_COMPENSATION_AUTOCALIBRATE', + self.cmd_AXIS_TWIST_COMPENSATION_AUTOCALIBRATE, + desc=self.cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help) cmd_AXIS_TWIST_COMPENSATION_CALIBRATE_help = """ Performs the x twist calibration wizard @@ -218,6 +222,157 @@ def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): self.current_axis = axis self._calibration(probe_points, nozzle_points, interval_dist) + def _calculate_corrections(self, coordinates): + # Extracting x, y, and z values from coordinates + x_coords = [coord[0] for coord in coordinates] + y_coords = [coord[1] for coord in coordinates] + z_coords = [coord[2] for coord in coordinates] + + # Calculate the desired point (average of all corner points in z) + # For a general case, we should extract the unique + # combinations of corner points + z_corners = [z_coords[i] for i, coord in enumerate(coordinates) + if (coord[0] in [x_coords[0], x_coords[-1]]) + and (coord[1] in [y_coords[0], y_coords[-1]])] + z_desired = sum(z_corners) / len(z_corners) + + + # Calculate average deformation per axis + unique_x_coords = sorted(set(x_coords)) + unique_y_coords = sorted(set(y_coords)) + + avg_z_x = [] + for x in unique_x_coords: + indices = [i for i, coord in enumerate(coordinates) + if coord[0] == x] + avg_z = sum(z_coords[i] for i in indices) / len(indices) + avg_z_x.append(avg_z) + + avg_z_y = [] + for y in unique_y_coords: + indices = [i for i, coord in enumerate(coordinates) + if coord[1] == y] + avg_z = sum(z_coords[i] for i in indices) / len(indices) + avg_z_y.append(avg_z) + + # Calculate corrections to reach the desired point + x_corrections = [z_desired - avg for avg in avg_z_x] + y_corrections = [z_desired - avg for avg in avg_z_y] + + return x_corrections, y_corrections + + def cmd_AXIS_TWIST_COMPENSATION_AUTOCALIBRATE(self, gcmd): + self.gcmd = gcmd + sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT) + + + if not all([ + self.x_start_point[0], + self.x_end_point[0], + self.y_start_point[0], + self.y_end_point[0] + ]): + raise self.gcmd.error( + """AXIS_TWIST_COMPENSATION_AUTOCALIBRATE requires + calibrate_start_x, calibrate_end_x, calibrate_start_y + and calibrate_end_y to be defined + """ + ) + + # check for valid sample_count + if sample_count is None or sample_count < 2: + raise self.gcmd.error( + "SAMPLE_COUNT to probe must be at least 2") + + # clear the current config + self.compensation.clear_compensations() + + # calculate the points to put the probe at, returned as a list of tuples + nozzle_points = [] + + min_x = self.x_start_point[0] + max_x = self.x_end_point[0] + min_y = self.y_start_point[0] + max_y = self.y_end_point[0] + + # calculate x positions + spcx = (max_x - min_x) / (sample_count - 1) + xps = [min_x + spcx * i for i in range(sample_count)] + + # Calculate points array + spcy = (max_y - min_y) / (sample_count - 1) + flip = False + + points = [] + for i in range(sample_count): + for j in range(sample_count): + if(not flip): + idx = j + else: + idx = sample_count -1 - j + points.append([xps[i], min_y + spcy * idx ]) + flip = not flip + + # verify no other manual probe is in progress + manual_probe.verify_no_manual_probe(self.printer) + + + # calculate the points to put the nozzle at, and probe + probe_points = [] + + for i in range(len(points)): + x = points[i][0] - self.probe_x_offset + y = points[i][1] - self.probe_y_offset + probe_points.append([x, y, self._auto_calibration((x,y))[2]]) + + # calculate corrections + x_corr, y_corr = self._calculate_corrections(probe_points) + + x_corr_str = ', '.join(["{:.6f}".format(x) + for x in x_corr]) + + y_corr_str = ', '.join(["{:.6f}".format(x) + for x in y_corr]) + + # finalize + configfile = self.printer.lookup_object('configfile') + configfile.set(self.configname, 'zx_compensations', x_corr_str) + configfile.set(self.configname, 'compensation_start_x', + self.x_start_point[0]) + configfile.set(self.configname, 'compensation_end_x', + self.x_end_point[0]) + + + configfile.set(self.configname, 'zy_compensations', y_corr_str) + configfile.set(self.configname, 'compensation_start_y', + self.y_start_point[0]) + configfile.set(self.configname, 'compensation_end_y', + self.y_end_point[0]) + + + self.gcmd.respond_info( + "AXIS_TWIST_COMPENSATION_AUTOCALIBRATE: Calibration complete: ") + self.gcmd.respond_info("\n".join(map(str, [x_corr, y_corr])), log=False) + + def _auto_calibration(self, probe_point): + + # horizontal_move_z (to prevent probe trigger or hitting bed) + self._move_helper((None, None, self.horizontal_move_z)) + + # move to point to probe + self._move_helper((probe_point[0], + probe_point[1], None)) + + # probe the point + pos = probe.run_single_probe(self.probe, self.gcmd) + #self.current_measured_z = pos[2] + + # horizontal_move_z (to prevent probe trigger or hitting bed) + self._move_helper((None, None, self.horizontal_move_z)) + + return pos + + def _calculate_probe_points(self, nozzle_points, probe_x_offset, probe_y_offset): # calculate the points to put the nozzle at