Skip to content

Commit

Permalink
Independent X & Y Accelerations (#4)
Browse files Browse the repository at this point in the history
* kin limited_corexy and limited_cartesian

Adds velocity and acceleration limits on X and Y for these kinematics.

* motan: support limited_ prefix for kinematics names

* kin limited_*: documentation

* toolhead: fix cornering for dynamic accel limits

With limited_ kinematics the acceleration may change between moves.
In order to preserve behavior junction_deviation should be recomputed
each time. Since the  `jd * accel` term is constant for a given scv, it
can replace junction_deviation when computing cornering
limits.
This term is the squared toolhead velocity during a 60° cornering.

* kin limited_*: scale_xy_accel indep. of max_accel

Computes the max XY reachable acceleration for the scale_xy_accel
denominator, instead of relying on config [printer] max_accel.
(`hypot(x, y) ` for Cartesian, `max(X,Y)` for CoreXY)

Removes config limit in SET_KINEMATICS_LIMIT for easier tuning.

* kin limited_*: update documentation

* kin limited_*: add tests

* resonance tester: disable limits (not optimal)
  • Loading branch information
Piezoid authored Jan 28, 2024
1 parent 261da3c commit 1d8ae0b
Show file tree
Hide file tree
Showing 13 changed files with 670 additions and 39 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ If you're feeling adventurous, take a peek at the extra features in the bleeding

- [input_shaper: new print_ringing_tower utility](https://github.com/DangerKlippers/danger-klipper/pull/69)

- [Independent X & Y Acceleration and velocity settings](https://github.com/DangerKlippers/danger-klipper/pull/4)

## Switch to Danger Klipper

> [!NOTE]
Expand Down
6 changes: 6 additions & 0 deletions docs/Config_Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ All dates in this document are approximate.

## Changes

20240128: `printer.kinematics` now accepts `limited_cartesian` and
`limited_cartesian` and `limited_corexy` that enables `max_{x,y}_accel` and
`max_{x,y}_velocity` (only for `limited_cartesian`). In the future, this
functionality may be moved into the original kinematics module (as optional
settings).

20240123: The output_pin SET_PIN CYCLE_TIME parameter has been
removed. Use the new
[pwm_cycle_time](Config_Reference.md#pwm_cycle_time) module if it is
Expand Down
101 changes: 93 additions & 8 deletions docs/Config_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,16 +292,70 @@ max_z_accel:
[stepper_z]
```

### Linear Delta Kinematics
### ⚠️ Cartesian Kinematics with limits for X and Y axes

See [example-delta.cfg](../config/example-delta.cfg) for an example
linear delta kinematics config file. See the
[delta calibrate guide](Delta_Calibrate.md) for information on
calibration.
Behaves exactly the as cartesian kinematics, but allows to set a velocity and
acceleration limit for X and Y axis. This also makes command [`SET_KINEMATICS_LIMIT`](./G-Codes.md#⚠️-set_kinematics_limit) available to sets these limits at runtime.

Only parameters specific to linear delta printers are described here -
see [common kinematic settings](#common-kinematic-settings) for
available parameters.

```
[printer]
kinematics: limited_cartesian
max_x_velocity:
# This sets the maximum velocity (in mm/s) of movement along the x
# axis. This setting can be used to restrict the maximum speed of
# the x stepper motor. The default is to use max_velocity for
# max_x_velocity.
max_y_velocity:
# This sets the maximum velocity (in mm/s) of movement along the y
# axis. This setting can be used to restrict the maximum speed of
# the y stepper motor. The default is to use max_velocity for
# max_x_velocity.
max_z_velocity:
# See cartesian above.
max_velocity:
# In order to get maximum velocity gains on diagonals, this should be equal or
# greater than the hypotenuse (sqrt(x*x + y*y)) of max_x_velocity and
# max_y_velocity.
max_x_accel:
# This sets the maximum acceleration (in mm/s^2) of movement along
# the x axis. It limits the acceleration of the x stepper motor. The
# default is to use max_accel for max_x_accel.
max_y_accel:
# This sets the maximum acceleration (in mm/s^2) of movement along
# the y axis. It limits the acceleration of the y stepper motor. The
# default is to use max_accel for max_y_accel.
max_z_accel:
# See cartesian above.
max_accel:
# See cartesian above.
scale_xy_accel: False
# When true, scales the XY limits by the current tool head acceleration.
# The factor is: slicer accel / hypot(max_x_accel, max_y_accel).
# See below.
```

If scale_xy_accel is `False`, the acceleration set by `max_accel`, M204 or
SET_VELOCITY_LIMIT, acts as a third limit. In that case, this module doesn't
apply limitations on moves having an acceleration lower than `max_x_accel`` and
`max_y_accel`. When scale_xy_accel is `True`, `max_x_accel` and `max_y_accel`
are scaled by the ratio of the dynamically set acceleration and the hypotenuse
of max_x_accel and `max_y_accel`, as reported from `SET_KINEMATICS_LIMIT`. This
implies that the actual acceleration will always depend on the direction. For
example, these settings:

```
[printer]
max_x_accel: 12000
max_y_accel: 9000
scale_xy_accel: true
```

`SET_KINEMATICS_LIMIT` will report a maximum acceleration of 15000 mm/s^2 on 37°
diagonals. If the slicer emit `M204 S3000` (3000 mm/s^2 accel). On these 37° and
143° diagonals, the toolhead will accelerate at 3000 mm/s^2. On the X axis, the
acceleration will be 12000 * 3000 / 15000 = 2400 mm/s^2, and 18000 mm/s^2 for
pure Y moves.

```
[printer]
Expand Down Expand Up @@ -488,6 +542,37 @@ max_z_accel:
[stepper_z]
```

### ⚠️ CoreXY Kinematics with limits for X and Y axes

Behaves exactly the as CoreXY kinematics, but allows to set a acceleration limit
for X and Y axis.

There is no velocity limits for X and Y, since on a CoreXY the pull-out velocity
are identical on both axes.


```
[printer]
kinematics: limited_corexy
max_z_velocity:
# See CoreXY above.
max_x_accel:
# This sets the maximum acceleration (in mm/s^2) of movement along
# the x axis. It limits the acceleration of the x stepper motor. The
# default is to use max_accel for max_x_accel.
max_y_accel:
# This sets the maximum acceleration (in mm/s^2) of movement along
# the y axis. It limits the acceleration of the y stepper motor. The
# default is to use max_accel for max_y_accel.
max_z_accel:
# See CoreXY above.
max_accel:
# See CoreXY above..
scale_xy_accel:
# When True, scales the XY limits by the current tool head acceleration.
# The factor is: slicer accel / max(max_x_accel, max_y_accel).
```

### CoreXZ Kinematics

See [example-corexz.cfg](../config/example-corexz.cfg) for an example
Expand Down
13 changes: 13 additions & 0 deletions docs/G-Codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,19 @@ printer config file. See the
[printer config section](Config_Reference.md#printer) for a
description of each parameter.

#### ⚠️ SET_KINEMATICS_LIMIT

`SET_KINEMATICS_LIMIT [<X,Y,Z>_ACCEL=<value>] [<X,Y,Z>_VELOCITY=<value>]
[SCALE=<0:1>]`: change the per-axis limits.

This command is only available when `kinematics` is set to either
[`limited_cartesian`](./Config_Reference.md#⚠️-cartesian-kinematics-with-limits-for-x-and-y-axes)
or
[`limited_corexy`](./Config_Reference.md#⚠️-corexy-kinematics-with-limits-for-x-and-y-axes).
The velocity argument is not available on CoreXY. With no arguments, this
command responds with the movement direction with the most acceleration or
velocity.

### [tuning_tower]

The tuning_tower module is automatically loaded.
Expand Down
103 changes: 77 additions & 26 deletions klippy/extras/resonance_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import math
import os
import time
from contextlib import contextmanager
from . import shaper_calibrate


Expand Down Expand Up @@ -52,6 +53,73 @@ def _parse_axis(gcmd, raw_axis):
return TestAxis(vib_dir=(dir_x, dir_y))


@contextmanager
def suspend_limits(printer, max_accel, max_velocity, input_shaping):
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
gcode = printer.lookup_object("gcode")
input_shaper = printer.lookup_object("input_shaper", None)
if input_shaper is not None and not input_shaping:
input_shaper.disable_shaping()
gcode.respond_info("Disabled [input_shaper] for resonance testing")
else:
input_shaper = None
toolhead = printer.lookup_object("toolhead")
systime = printer.get_reactor().monotonic()
toolhead_info = toolhead.get_status(systime)
old_max_accel = toolhead_info["max_accel"]
old_max_accel_to_decel = toolhead_info["max_accel_to_decel"]
old_max_velocity = toolhead_info["max_velocity"]
gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f VELOCITY=%.3f"
% (max_accel, max_accel, max_velocity)
)
kin = toolhead.get_kinematics()
old_max_velocities = getattr(kin, "max_velocities", None)
if old_max_velocities is not None:
kin.max_velocities = [
max_velocity,
max_velocity,
old_max_velocities[-1],
]
old_max_accels = getattr(kin, "max_accels", None)
if old_max_accels is not None:
kin.max_accels = [max_accel, max_accel, old_max_accels[-1]]
# FIXME: could be cleaner if limited_corexy were using the same format than
# limited_cartesian
old_max_x_accel = getattr(kin, "max_x_accel", None)
if old_max_x_accel is not None:
kin.max_x_accel = max_accel
old_max_y_accel = getattr(kin, "max_y_accel", None)
if old_max_y_accel is not None:
kin.max_y_accel = max_accel
old_scale_per_axis = getattr(kin, "scale_per_axis", None)
if old_scale_per_axis is not None:
kin.scale_per_axis = False
try:
yield
finally:
# Restore input shaper if it was disabled for resonance testing
if input_shaper is not None:
input_shaper.enable_shaping()
gcode.respond_info("Re-enabled [input_shaper]")
# Restore the original acceleration values
gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f VELOCITY=%.3f"
% (old_max_accel, old_max_accel_to_decel, old_max_velocity)
)
if old_max_velocities is not None:
kin.max_velocities = old_max_velocities
if old_max_accels is not None:
kin.max_accels = old_max_accels
if old_max_x_accel is not None:
kin.max_x_accel = old_max_x_accel
if old_max_y_accel is not None:
kin.max_y_accel = old_max_y_accel
if old_scale_per_axis is not None:
kin.scale_per_axis = old_scale_per_axis


class VibrationPulseTest:
def __init__(self, config):
self.printer = config.get_printer()
Expand Down Expand Up @@ -85,27 +153,19 @@ def prepare_test(self, gcmd):
)

def run_test(self, axis, gcmd):
with suspend_limits(
self.printer,
self.freq_end * self.accel_per_hz + 10.0,
self.accel_per_hz * 0.25 + 1.0,
gcmd.get_int("INPUT_SHAPING", 0),
):
self._run_test(axis, gcmd)

def _run_test(self, axis, gcmd):
toolhead = self.printer.lookup_object("toolhead")
X, Y, Z, E = toolhead.get_position()
sign = 1.0
freq = self.freq_start
# Override maximum acceleration and acceleration to
# deceleration based on the maximum test frequency
systime = self.printer.get_reactor().monotonic()
toolhead_info = toolhead.get_status(systime)
old_max_accel = toolhead_info["max_accel"]
old_max_accel_to_decel = toolhead_info["max_accel_to_decel"]
max_accel = self.freq_end * self.accel_per_hz
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f"
% (max_accel, max_accel)
)
input_shaper = self.printer.lookup_object("input_shaper", None)
if input_shaper is not None and not gcmd.get_int("INPUT_SHAPING", 0):
input_shaper.disable_shaping()
gcmd.respond_info("Disabled [input_shaper] for resonance testing")
else:
input_shaper = None
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
while freq <= self.freq_end + 0.000001:
t_seg = 0.25 / freq
Expand All @@ -125,15 +185,6 @@ def run_test(self, axis, gcmd):
freq += 2.0 * t_seg * self.hz_per_sec
if math.floor(freq) > math.floor(old_freq):
gcmd.respond_info("Testing frequency %.0f Hz" % (freq,))
# Restore the original acceleration values
self.gcode.run_script_from_command(
"SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f"
% (old_max_accel, old_max_accel_to_decel)
)
# Restore input shaper if it was disabled for resonance testing
if input_shaper is not None:
input_shaper.enable_shaping()
gcmd.respond_info("Re-enabled [input_shaper]")


class ResonanceTester:
Expand Down
Loading

0 comments on commit 1d8ae0b

Please sign in to comment.