Skip to content

Commit

Permalink
axis_twist_compensation: Implement Y-axis support
Browse files Browse the repository at this point in the history
This commit implements support for the Y-axis in the axis_twist_compensation
module. This update enables the module to handle corrections for printers
with a twisted Y rail.

Signed-off-by: Jorge Apaza Merma <[email protected]>
  • Loading branch information
yochiwarez committed Nov 2, 2024
1 parent 11f04ba commit 43467a1
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 48 deletions.
1 change: 1 addition & 0 deletions docs/Axis_Twist_Compensation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
19 changes: 18 additions & 1 deletion docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2035,7 +2035,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.

Expand All @@ -2060,6 +2060,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
Expand Down
4 changes: 2 additions & 2 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<value>]`: Initiates the X
`AXIS_TWIST_COMPENSATION_CALIBRATE [SAMPLE_COUNT=<value>] [AXIS=<X or Y, default X>]`: 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]

Expand Down
185 changes: 140 additions & 45 deletions klippy/extras/axis_twist_compensation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand Down Expand Up @@ -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 "
Expand Down

0 comments on commit 43467a1

Please sign in to comment.