1010import weakref
1111from abc import ABC , abstractmethod
1212from collections .abc import Sequence
13+ from dataclasses import fields
1314from typing import TYPE_CHECKING , Any
1415
1516import isaaclab .utils .string as string_utils
1617from isaaclab .physics import PhysicsEvent , PhysicsManager
1718from isaaclab .utils import class_to_dict , string_to_callable
19+ from isaaclab .utils .modifiers import ModifierCfg
1820
1921from .manager_term_cfg import ManagerTermBaseCfg
2022from .scene_entity_cfg import SceneEntityCfg
@@ -334,8 +336,7 @@ def _resolve_common_term_cfg(self, term_name: str, term_cfg: ManagerTermBaseCfg,
334336 )
335337
336338 # get the corresponding function or functional class
337- if isinstance (term_cfg .func , str ):
338- term_cfg .func = string_to_callable (term_cfg .func )
339+ term_cfg .func = self ._resolve_param_value (term_name , "func" , term_cfg .func , resolve_callable = True )
339340 # check if function is callable
340341 if not callable (term_cfg .func ):
341342 raise AttributeError (f"The term '{ term_name } ' is not callable. Received: { term_cfg .func } " )
@@ -381,7 +382,7 @@ def _process_term_cfg_at_play(self, term_name: str, term_cfg: ManagerTermBaseCfg
381382 This function is called when the simulation starts playing. It is used to process the term
382383 configuration at runtime. This includes:
383384
384- * Resolving the scene entity configuration for the term.
385+ * Resolving scene entity configurations and nested terms throughout the term configuration .
385386 * Initializing the term if it is a class.
386387
387388 Since the above steps rely on PhysX to parse over the simulation scene, they are deferred
@@ -391,27 +392,49 @@ def _process_term_cfg_at_play(self, term_name: str, term_cfg: ManagerTermBaseCfg
391392 term_name: The name of the term.
392393 term_cfg: The term configuration.
393394 """
394- for key , value in term_cfg .params .items ():
395- self ._resolve_param_value (term_name , key , value )
395+ for field in fields (term_cfg ):
396+ value = getattr (term_cfg , field .name )
397+ resolved_value = self ._resolve_param_value (
398+ term_name , field .name , value , resolve_callable = field .name == "func"
399+ )
400+ if resolved_value is not value :
401+ setattr (term_cfg , field .name , resolved_value )
396402
397- # resolve string func references then initialize class-based terms
398- if isinstance (term_cfg .func , str ):
399- term_cfg .func = string_to_callable (term_cfg .func )
403+ # initialize class-based terms
400404 if inspect .isclass (term_cfg .func ):
401405 term_cfg .func = term_cfg .func (cfg = term_cfg , env = self ._env )
402406
403- def _resolve_param_value (self , term_name : str , key : str | int , value : Any ):
404- """Recursively resolve a single param value (SceneEntityCfg, nested term cfgs, dicts, lists)."""
407+ def _resolve_param_value (
408+ self , term_name : str , key : str | int , value : Any , * , resolve_callable : bool = False
409+ ) -> Any :
410+ """Recursively resolve manager-owned values in a term configuration."""
411+ if resolve_callable and isinstance (value , str ):
412+ return string_to_callable (value )
405413 if isinstance (value , SceneEntityCfg ):
406414 try :
407415 value .resolve (self ._env .scene )
408416 except ValueError as e :
409417 raise ValueError (f"Error while parsing '{ term_name } :{ key } '. { e } " )
410418 elif isinstance (value , ManagerTermBaseCfg ):
411419 self ._process_term_cfg_at_play (f"{ term_name } .{ key } " , value )
420+ elif isinstance (value , ModifierCfg ):
421+ for field in fields (value ):
422+ field_value = getattr (value , field .name )
423+ resolved_value = self ._resolve_param_value (
424+ f"{ term_name } .{ key } " , field .name , field_value , resolve_callable = field .name == "func"
425+ )
426+ if resolved_value is not field_value :
427+ setattr (value , field .name , resolved_value )
412428 elif isinstance (value , dict ):
413429 for sub_key , sub_value in value .items ():
414- self ._resolve_param_value (f"{ term_name } .{ key } " , sub_key , sub_value )
415- elif isinstance (value , ( list , tuple ) ):
430+ value [ sub_key ] = self ._resolve_param_value (f"{ term_name } .{ key } " , sub_key , sub_value )
431+ elif isinstance (value , list ):
416432 for i , item in enumerate (value ):
417- self ._resolve_param_value (f"{ term_name } .{ key } " , i , item )
433+ value [i ] = self ._resolve_param_value (f"{ term_name } .{ key } " , i , item )
434+ elif isinstance (value , tuple ):
435+ resolved_items = tuple (
436+ self ._resolve_param_value (f"{ term_name } .{ key } " , i , item ) for i , item in enumerate (value )
437+ )
438+ if any (resolved is not original for resolved , original in zip (resolved_items , value , strict = True )):
439+ value = resolved_items
440+ return value
0 commit comments