Skip to content

Commit c20098d

Browse files
committed
input shaper for idex printers
1 parent 23937fa commit c20098d

8 files changed

Lines changed: 360 additions & 15 deletions

File tree

docs/Config_Reference.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,13 +2096,19 @@ the [command reference](G-Codes.md#input_shaper).
20962096
# input shapers, this parameter can be set from different
20972097
# considerations. The default value is 0, which disables input
20982098
# shaping for X axis.
2099+
# For printers with dual toolheads (dual_carriage), this parameter can
2100+
# be specified as a comma-separated list to provide different values for
2101+
# each toolhead/mode: "freq_toolhead0, freq_toolhead1" or
2102+
# "freq_toolhead0_primary, freq_toolhead1_primary, freq_copy, freq_mirror".
20992103
#shaper_freq_y: 0
21002104
# A frequency (in Hz) of the input shaper for Y axis. This is
21012105
# usually a resonance frequency of Y axis that the input shaper
21022106
# should suppress. For more complex shapers, like 2- and 3-hump EI
21032107
# input shapers, this parameter can be set from different
21042108
# considerations. The default value is 0, which disables input
21052109
# shaping for Y axis.
2110+
# For printers with dual toolheads, this parameter supports comma-separated
2111+
# lists as described in shaper_freq_x.
21062112
#shaper_type: mzv
21072113
# A type of the input shaper to use for both X and Y axes. Supported
21082114
# shapers are zv, mzv, zvd, ei, 2hump_ei, and 3hump_ei. The default
@@ -2112,12 +2118,18 @@ the [command reference](G-Codes.md#input_shaper).
21122118
# If shaper_type is not set, these two parameters can be used to
21132119
# configure different input shapers for X and Y axes. The same
21142120
# values are supported as for shaper_type parameter.
2121+
# For printers with dual toolheads (dual_carriage), these parameters can
2122+
# be specified as comma-separated lists to provide different values for
2123+
# each toolhead/mode, following the same format as described in shaper_freq_x.
21152124
#damping_ratio_x: 0.1
21162125
#damping_ratio_y: 0.1
21172126
# Damping ratios of vibrations of X and Y axes used by input shapers
21182127
# to improve vibration suppression. Default value is 0.1 which is a
21192128
# good all-round value for most printers. In most circumstances this
21202129
# parameter requires no tuning and should not be changed.
2130+
# For printers with dual toolheads (dual_carriage), these parameters can
2131+
# be specified as comma-separated lists to provide different values for
2132+
# each toolhead/mode, following the same format as described in shaper_freq_x.
21212133
```
21222134

21232135
### [adxl345]

docs/G-Codes.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -941,14 +941,27 @@ been enabled (also see the
941941
`SET_INPUT_SHAPER [SHAPER_FREQ_X=<shaper_freq_x>]
942942
[SHAPER_FREQ_Y=<shaper_freq_y>] [DAMPING_RATIO_X=<damping_ratio_x>]
943943
[DAMPING_RATIO_Y=<damping_ratio_y>] [SHAPER_TYPE=<shaper>]
944-
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]`:
944+
[SHAPER_TYPE_X=<shaper_type_x>] [SHAPER_TYPE_Y=<shaper_type_y>]
945+
[TOOLHEAD=<toolhead_index>]`:
945946
Modify input shaper parameters. Note that SHAPER_TYPE parameter resets
946947
input shaper for both X and Y axes even if different shaper types have
947948
been configured in [input_shaper] section. SHAPER_TYPE cannot be used
948949
together with either of SHAPER_TYPE_X and SHAPER_TYPE_Y parameters.
950+
951+
For printers with dual toolheads (dual_carriage), the TOOLHEAD parameter
952+
can be used to specify which toolhead's input shaper parameters should be
953+
modified (0 or 1). If not specified, the parameters for all toolheads will
954+
be updated.
955+
949956
See [config reference](Config_Reference.md#input_shaper) for more
950957
details on each of these parameters.
951958

959+
#### GET_INPUT_SHAPER
960+
`GET_INPUT_SHAPER`:
961+
Reports the current input shaper settings. For printers with dual toolheads,
962+
it displays the active toolhead and mode along with the current input shaper
963+
parameters for each axis.
964+
952965
### [manual_probe]
953966

954967
The manual_probe module is automatically loaded.

docs/Kalico_Additions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
- [`canbus_query.py`](./CANBUS.md#finding-the-canbus_uuid-for-new-micro-controllers) now responds with all Kalico devices, even after they've been assigned a node_id.
2222
- Input shaper calibration now warns about active fans that may affect measurement accuracy.
23+
- Input shaper now supports multiple toolheads for dual carriage configurations, allowing different input shaper parameters for each toolhead and mode (PRIMARY/COPY/MIRROR).
2324
- [`BED_MESH_CHECK`](./G-Codes.md#bed_mesh_check) validates the current bed mesh against specified criteria, allowing you to check maximum deviation and slope between adjacent points before printing.
2425

2526
## New Kalico Modules

klippy/extras/input_shaper.py

Lines changed: 144 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,52 @@ class InputShaperParams:
1313
def __init__(self, axis, config):
1414
self.axis = axis
1515
self.shapers = {s.name: s.init_func for s in shaper_defs.INPUT_SHAPERS}
16+
17+
# parse shaper types
1618
shaper_type = config.get("shaper_type", "mzv")
17-
self.shaper_type = config.get("shaper_type_" + axis, shaper_type)
18-
if self.shaper_type not in self.shapers:
19-
raise config.error(
20-
"Unsupported shaper type: %s" % (self.shaper_type,)
21-
)
22-
self.damping_ratio = config.getfloat(
23-
"damping_ratio_" + axis,
24-
shaper_defs.DEFAULT_DAMPING_RATIO,
25-
minval=0.0,
26-
maxval=1.0,
27-
)
28-
self.shaper_freq = config.getfloat(
29-
"shaper_freq_" + axis, 0.0, minval=0.0
19+
shaper_type_axis = config.get("shaper_type_" + axis, shaper_type)
20+
self.shaper_types = [
21+
s.strip() for s in shaper_type_axis.split(",") if s.strip()
22+
]
23+
if not self.shaper_types:
24+
self.shaper_types = [shaper_type]
25+
26+
for st in self.shaper_types:
27+
if st not in self.shapers:
28+
raise config.error("Unsupported shaper type: %s" % (st,))
29+
30+
# parse damping ratios
31+
damping_ratio = config.get(
32+
"damping_ratio_" + axis, str(shaper_defs.DEFAULT_DAMPING_RATIO)
3033
)
34+
self.damping_ratios = [
35+
float(dr.strip()) for dr in damping_ratio.split(",") if dr.strip()
36+
]
37+
if not self.damping_ratios:
38+
self.damping_ratios = [shaper_defs.DEFAULT_DAMPING_RATIO]
39+
40+
for dr in self.damping_ratios:
41+
if dr < 0.0 or dr > 1.0:
42+
raise config.error(
43+
"Damping ratio must be between 0.0 and 1.0: %s" % (dr,)
44+
)
45+
46+
# parse frequencies
47+
freq = config.get("shaper_freq_" + axis, "0.0")
48+
self.shaper_freqs = [
49+
float(f.strip()) for f in freq.split(",") if f.strip()
50+
]
51+
if not self.shaper_freqs:
52+
self.shaper_freqs = [0.0]
53+
54+
for f in self.shaper_freqs:
55+
if f < 0.0:
56+
raise config.error("Frequency must be non-negative: %s" % (f,))
57+
58+
# use first value as default
59+
self.damping_ratio = self.damping_ratios[0]
60+
self.shaper_type = self.shaper_types[0]
61+
self.shaper_freq = self.shaper_freqs[0]
3162

3263
def update(self, gcmd):
3364
axis = self.axis.upper()
@@ -69,6 +100,8 @@ def __init__(self, axis, config):
69100
self.params = InputShaperParams(axis, config)
70101
self.n, self.A, self.T = self.params.get_shaper()
71102
self.saved = None
103+
self.toolhead_idx = 0
104+
self.mode_idx = 0 # 0 = PRIMARY, 1 = COPY/MIRROR
72105

73106
def get_name(self):
74107
return "shaper_" + self.axis
@@ -80,6 +113,45 @@ def update(self, gcmd):
80113
self.params.update(gcmd)
81114
self.n, self.A, self.T = self.params.get_shaper()
82115

116+
def set_toolhead_mode(self, toolhead_idx, mode_idx):
117+
self.toolhead_idx = toolhead_idx
118+
self.mode_idx = mode_idx
119+
param_idx = self._get_param_index()
120+
self._update_params_for_index(param_idx)
121+
122+
def _get_param_index(self):
123+
# for single toolhead or single parameter, always use index 0
124+
if len(self.params.shaper_types) == 1:
125+
return 0
126+
127+
# for dual toolhead with 2 parameters, use toolhead index
128+
elif len(self.params.shaper_types) == 2:
129+
return self.toolhead_idx
130+
131+
# for dual toolhead with 4 parameters, calculate based on toolhead and mode
132+
elif len(self.params.shaper_types) == 4:
133+
if self.mode_idx == 0: # PRIMARY mode
134+
return self.toolhead_idx # 0 for toolhead 0, 1 for toolhead 1
135+
else: # COPY/MIRROR mode
136+
return (
137+
self.mode_idx + 1
138+
) # 2 for COPY (mode_idx=1), 3 for MIRROR (mode_idx=2)
139+
140+
# Default to first parameter
141+
return 0
142+
143+
def _update_params_for_index(self, idx):
144+
idx = min(idx, len(self.params.shaper_types) - 1)
145+
146+
self.params.shaper_type = self.params.shaper_types[idx]
147+
self.params.damping_ratio = self.params.damping_ratios[
148+
min(idx, len(self.params.damping_ratios) - 1)
149+
]
150+
self.params.shaper_freq = self.params.shaper_freqs[
151+
min(idx, len(self.params.shaper_freqs) - 1)
152+
]
153+
self.n, self.A, self.T = self.params.get_shaper()
154+
83155
def set_shaper_kinematics(self, sk):
84156
ffi_main, ffi_lib = chelper.get_ffi()
85157
success = (
@@ -129,19 +201,38 @@ def __init__(self, config):
129201
]
130202
self.input_shaper_stepper_kinematics = []
131203
self.orig_stepper_kinematics = []
204+
self.dual_carriage = None
205+
self.active_toolhead = 0
206+
self.active_mode = 0 # 0 = PRIMARY, 1 = COPY/MIRROR
207+
132208
# Register gcode commands
133209
gcode = self.printer.lookup_object("gcode")
134210
gcode.register_command(
135211
"SET_INPUT_SHAPER",
136212
self.cmd_SET_INPUT_SHAPER,
137213
desc=self.cmd_SET_INPUT_SHAPER_help,
138214
)
215+
gcode.register_command(
216+
"GET_INPUT_SHAPER",
217+
self.cmd_GET_INPUT_SHAPER,
218+
desc=self.cmd_GET_INPUT_SHAPER_help,
219+
)
220+
221+
self.printer.register_event_handler(
222+
"dual_carriage:mode_change", self.handle_dual_carriage_mode_change
223+
)
139224

140225
def get_shapers(self):
141226
return self.shapers
142227

143228
def connect(self):
144229
self.toolhead = self.printer.lookup_object("toolhead")
230+
231+
try:
232+
self.dual_carriage = self.printer.lookup_object("dual_carriage")
233+
except self.printer.config_error:
234+
self.dual_carriage = None
235+
145236
# Configure initial values
146237
self._update_input_shaping(error=self.printer.config_error)
147238

@@ -204,15 +295,55 @@ def enable_shaping(self):
204295
self._update_input_shaping()
205296

206297
cmd_SET_INPUT_SHAPER_help = "Set cartesian parameters for input shaper"
298+
cmd_GET_INPUT_SHAPER_help = "Get current input shaper parameters"
299+
300+
def handle_dual_carriage_mode_change(self, carriage_idx, mode):
301+
self.active_toolhead = carriage_idx
302+
self.active_mode = 0 if mode == "PRIMARY" else 1
303+
304+
for shaper in self.shapers:
305+
shaper.set_toolhead_mode(self.active_toolhead, self.active_mode)
306+
307+
self._update_input_shaping()
207308

208309
def cmd_SET_INPUT_SHAPER(self, gcmd):
209310
if gcmd.get_command_parameters():
311+
toolhead_idx = gcmd.get_int("TOOLHEAD", None)
312+
mode_idx = gcmd.get_int("MODE", None)
313+
314+
if toolhead_idx is not None or mode_idx is not None:
315+
th_idx = (
316+
self.active_toolhead
317+
if toolhead_idx is None
318+
else toolhead_idx
319+
)
320+
m_idx = self.active_mode if mode_idx is None else mode_idx
321+
322+
if th_idx not in [0, 1]:
323+
raise gcmd.error("Invalid TOOLHEAD index: %d" % th_idx)
324+
if m_idx not in [0, 1]:
325+
raise gcmd.error("Invalid MODE index: %d" % m_idx)
326+
327+
for shaper in self.shapers:
328+
shaper.set_toolhead_mode(th_idx, m_idx)
329+
210330
for shaper in self.shapers:
211331
shaper.update(gcmd)
212332
self._update_input_shaping()
213333
for shaper in self.shapers:
214334
shaper.report(gcmd)
215335

336+
def cmd_GET_INPUT_SHAPER(self, gcmd):
337+
gcmd.respond_info(
338+
"Active toolhead: %d, Mode: %s"
339+
% (
340+
self.active_toolhead,
341+
"PRIMARY" if self.active_mode == 0 else "COPY/MIRROR",
342+
)
343+
)
344+
for shaper in self.shapers:
345+
shaper.report(gcmd)
346+
216347

217348
def load_config(config):
218349
return InputShaper(config)

klippy/kinematics/idex_modes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ def activate_dc_mode(self, index, mode):
184184
self.dc[index].activate(mode, toolhead.get_position())
185185
kin.update_limits(self.axis, self.get_kin_range(toolhead, mode))
186186

187+
self.printer.send_event("dual_carriage:mode_change", index, mode)
188+
187189
def _handle_ready(self):
188190
# Apply the transform later during Klipper initialization to make sure
189191
# that input shaping can pick up the correct stepper kinematic flags.

test/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ def pytest_sessionstart(session):
3434

3535
@session.config.add_cleanup
3636
def clean_symlink():
37-
os.unlink(link_path)
37+
try:
38+
os.unlink(link_path)
39+
except FileNotFoundError:
40+
pass
3841

3942

4043
@pytest.fixture

0 commit comments

Comments
 (0)