diff --git a/docs/Axis_Twist_Compensation.md b/docs/Axis_Twist_Compensation.md index 0017a2279de7..ee582aca4c26 100644 --- a/docs/Axis_Twist_Compensation.md +++ b/docs/Axis_Twist_Compensation.md @@ -30,6 +30,7 @@ perform `AXIS_TWIST_COMPENSATION_CALIBRATE` points along the bed * The calibration defaults to 3 points but you can use the option `SAMPLE_COUNT=` to use a different number. +* For Y-axis calibration, use `AXIS_TWIST_COMPENSATION_CALIBRATE AXIS=Y` instead. 2. [Adjust your Z offset](Probe_Calibrate.md#calibrating-probe-z-offset) 3. Perform automatic/probe-based bed tramming operations, such as [Screws Tilt Adjust](G-Codes.md#screws_tilt_adjust), diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 17b466b33c46..cb40991d8e9e 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -2083,7 +2083,7 @@ sensor_type: ldc1612 ### [axis_twist_compensation] -A tool to compensate for inaccurate probe readings due to twist in X gantry. See +A tool to compensate for inaccurate probe readings due to twist in X or Y gantry. See the [Axis Twist Compensation Guide](Axis_Twist_Compensation.md) for more detailed information regarding symptoms, configuration and setup. @@ -2108,6 +2108,23 @@ calibrate_y: 112.5 # This should be the Y coordinate that positions the nozzle during the # calibration process. This parameter must be provided and is recommended to # be near the center of the bed + +# For Y-axis twist compensation, specify the following parameters: +calibrate_start_y: ... +# Defines the minimum Y coordinate of the calibration +# This should be the Y coordinate that positions the nozzle at the starting +# calibration position for the Y axis. This parameter must be provided if +# compensating for Y axis twist. +calibrate_end_y: ... +# Defines the maximum Y coordinate of the calibration +# This should be the Y coordinate that positions the nozzle at the ending +# calibration position for the Y axis. This parameter must be provided if +# compensating for Y axis twist. +calibrate_x: ... +# Defines the X coordinate of the calibration for Y axis twist compensation +# This should be the X coordinate that positions the nozzle during the +# calibration process for Y axis twist compensation. This parameter must be +# provided and is recommended to be near the center of the bed. ``` ## Additional stepper motors and extruders diff --git a/docs/G-Codes.md b/docs/G-Codes.md index b50f2af94eaf..0eb09325a143 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -146,9 +146,9 @@ 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 or Y twist calibration wizard. `SAMPLE_COUNT` specifies the number of points along -the X axis to calibrate at and defaults to 3. +the X or Y axis to calibrate at and defaults to 3. ### [bed_mesh] diff --git a/klippy/extras/axis_twist_compensation.py b/klippy/extras/axis_twist_compensation.py index e7aad52c047f..17437a97ebe9 100644 --- a/klippy/extras/axis_twist_compensation.py +++ b/klippy/extras/axis_twist_compensation.py @@ -23,18 +23,27 @@ def __init__(self, config): self.horizontal_move_z = config.getfloat('horizontal_move_z', DEFAULT_HORIZONTAL_MOVE_Z) self.speed = config.getfloat('speed', DEFAULT_SPEED) - self.calibrate_start_x = config.getfloat('calibrate_start_x') - self.calibrate_end_x = config.getfloat('calibrate_end_x') - self.calibrate_y = config.getfloat('calibrate_y') + self.calibrate_start_x = config.getfloat('calibrate_start_x', + default=None) + self.calibrate_end_x = config.getfloat('calibrate_end_x', default=None) + self.calibrate_y = config.getfloat('calibrate_y', default=None) self.z_compensations = config.getlists('z_compensations', default=[], parser=float) self.compensation_start_x = config.getfloat('compensation_start_x', default=None) - self.compensation_end_x = config.getfloat('compensation_start_y', + self.compensation_end_x = config.getfloat('compensation_end_x', default=None) - self.m = None - self.b = None + self.calibrate_start_y = config.getfloat('calibrate_start_y', + default=None) + self.calibrate_end_y = config.getfloat('calibrate_end_y', default=None) + self.calibrate_x = config.getfloat('calibrate_x', default=None) + self.compensation_start_y = config.getfloat('compensation_start_y', + default=None) + self.compensation_end_y = config.getfloat('compensation_end_y', + default=None) + self.zy_compensations = config.getlists('zy_compensations', + default=[], parser=float) # setup calibrater self.calibrater = Calibrater(self, config) @@ -43,28 +52,46 @@ def __init__(self, config): self._update_z_compensation_value) def _update_z_compensation_value(self, pos): - if not self.z_compensations: - return + if self.z_compensations: + pos[2] += self._get_interpolated_z_compensation( + pos[0], self.z_compensations, + self.compensation_start_x, + self.compensation_end_x + ) + + if self.zy_compensations: + pos[2] += self._get_interpolated_z_compensation( + pos[1], self.zy_compensations, + self.compensation_start_y, + self.compensation_end_y + ) + + def _get_interpolated_z_compensation( + self, coord, z_compensations, + comp_start, + comp_end + ): - x_coord = pos[0] - z_compensations = self.z_compensations sample_count = len(z_compensations) - spacing = ((self.calibrate_end_x - self.calibrate_start_x) + spacing = ((comp_end - comp_start) / (sample_count - 1)) - interpolate_t = (x_coord - self.calibrate_start_x) / spacing + interpolate_t = (coord - comp_start) / spacing interpolate_i = int(math.floor(interpolate_t)) interpolate_i = bed_mesh.constrain(interpolate_i, 0, sample_count - 2) interpolate_t -= interpolate_i interpolated_z_compensation = bed_mesh.lerp( interpolate_t, z_compensations[interpolate_i], z_compensations[interpolate_i + 1]) - pos[2] += interpolated_z_compensation - - def clear_compensations(self): - self.z_compensations = [] - self.m = None - self.b = None + return interpolated_z_compensation + def clear_compensations(self, axis=None): + if axis is None: + self.z_compensations = [] + self.zy_compensations = [] + elif axis == 'X': + self.z_compensations = [] + elif axis == 'Y': + self.zy_compensations = [] class Calibrater: def __init__(self, compensation, config): @@ -80,10 +107,14 @@ def __init__(self, compensation, config): self._handle_connect) self.speed = compensation.speed self.horizontal_move_z = compensation.horizontal_move_z - self.start_point = (compensation.calibrate_start_x, + self.x_start_point = (compensation.calibrate_start_x, compensation.calibrate_y) - self.end_point = (compensation.calibrate_end_x, + self.x_end_point = (compensation.calibrate_end_x, compensation.calibrate_y) + self.y_start_point = (compensation.calibrate_x, + compensation.calibrate_start_y) + self.y_end_point = (compensation.calibrate_x, + compensation.calibrate_end_y) self.results = None self.current_point_index = None self.gcmd = None @@ -119,20 +150,75 @@ def _register_gcode_handlers(self): def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): self.gcmd = gcmd sample_count = gcmd.get_int('SAMPLE_COUNT', DEFAULT_SAMPLE_COUNT) + axis = gcmd.get('AXIS', 'X') # check for valid sample_count - if sample_count is None or sample_count < 2: + if 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 = [] + + if axis == 'X': + + self.compensation.clear_compensations('X') + + if not all([ + self.x_start_point[0], + self.x_end_point[0], + self.x_start_point[1] + ]): + raise self.gcmd.error( + """AXIS_TWIST_COMPENSATION for X axis requires + calibrate_start_x, calibrate_end_x and calibrate_y + to be defined + """ + ) + + start_point = self.x_start_point + end_point = self.x_end_point + + x_axis_range = end_point[0] - start_point[0] + interval_dist = x_axis_range / (sample_count - 1) + + for i in range(sample_count): + x = start_point[0] + i * interval_dist + y = start_point[1] + nozzle_points.append((x, y)) + + elif axis == 'Y': + + self.compensation.clear_compensations('Y') + + if not all([ + self.y_start_point[0], + self.y_end_point[0], + self.y_start_point[1] + ]): + raise self.gcmd.error( + """AXIS_TWIST_COMPENSATION for Y axis requires + calibrate_start_y, calibrate_end_y and calibrate_x + to be defined + """ + ) + + start_point = self.y_start_point + end_point = self.y_end_point + + y_axis_range = end_point[1] - start_point[1] + interval_dist = y_axis_range / (sample_count - 1) + + for i in range(sample_count): + x = start_point[0] + y = start_point[1] + i * interval_dist + nozzle_points.append((x, y)) + + else: + raise self.gcmd.error( + "AXIS_TWIST_COMPENSATION_CALIBRATE: " + "Invalid axis.") - # calculate some values - x_range = self.end_point[0] - self.start_point[0] - interval_dist = x_range / (sample_count - 1) - nozzle_points = self._calculate_nozzle_points(sample_count, - interval_dist) probe_points = self._calculate_probe_points( nozzle_points, self.probe_x_offset, self.probe_y_offset) @@ -142,17 +228,9 @@ def cmd_AXIS_TWIST_COMPENSATION_CALIBRATE(self, gcmd): # begin calibration self.current_point_index = 0 self.results = [] + self.current_axis = axis self._calibration(probe_points, nozzle_points, interval_dist) - def _calculate_nozzle_points(self, sample_count, interval_dist): - # calculate the points to put the probe at, returned as a list of tuples - nozzle_points = [] - for i in range(sample_count): - x = self.start_point[0] + i * interval_dist - y = self.start_point[1] - nozzle_points.append((x, y)) - return nozzle_points - def _calculate_probe_points(self, nozzle_points, probe_x_offset, probe_y_offset): # calculate the points to put the nozzle at @@ -238,14 +316,31 @@ def _finalize_calibration(self): configfile = self.printer.lookup_object('configfile') values_as_str = ', '.join(["{:.6f}".format(x) for x in self.results]) - configfile.set(self.configname, 'z_compensations', values_as_str) - configfile.set(self.configname, 'compensation_start_x', - self.start_point[0]) - configfile.set(self.configname, 'compensation_end_x', - self.end_point[0]) - self.compensation.z_compensations = self.results - self.compensation.compensation_start_x = self.start_point[0] - self.compensation.compensation_end_x = self.end_point[0] + + if(self.current_axis == 'X'): + + configfile.set(self.configname, 'z_compensations', values_as_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]) + + self.compensation.z_compensations = self.results + self.compensation.compensation_start_x = self.x_start_point[0] + self.compensation.compensation_end_x = self.x_end_point[0] + + elif(self.current_axis == 'Y'): + + configfile.set(self.configname, 'zy_compensations', values_as_str) + configfile.set(self.configname, 'compensation_start_y', + self.y_start_point[1]) + configfile.set(self.configname, 'compensation_end_y', + self.y_end_point[1]) + + self.compensation.zy_compensations = self.results + self.compensation.compensation_start_y = self.y_start_point[1] + self.compensation.compensation_end_y = self.y_end_point[1] + self.gcode.respond_info( "AXIS_TWIST_COMPENSATION state has been saved " "for the current session. The SAVE_CONFIG command will "