@@ -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
217348def load_config (config ):
218349 return InputShaper (config )
0 commit comments