@@ -112,7 +112,7 @@ def __call__(self, *args) -> Any:
112
112
Returns:
113
113
The value of the term.
114
114
"""
115
- raise NotImplementedError
115
+ raise NotImplementedError ( "The method '__call__' should be implemented by the subclass." )
116
116
117
117
118
118
class ManagerBase (ABC ):
@@ -136,32 +136,34 @@ def __init__(self, cfg: object, env: ManagerBasedEnv):
136
136
self .cfg = copy .deepcopy (cfg )
137
137
self ._env = env
138
138
139
- # parse config to create terms information
140
- if self .cfg :
141
- self ._prepare_terms ()
142
-
143
139
# if the simulation is not playing, we use callbacks to trigger the resolution of the scene
144
140
# entities configuration. this is needed for cases where the manager is created after the
145
141
# simulation, but before the simulation is playing.
142
+ # FIXME: Once Isaac Sim supports storing this information as USD schema, we can remove this
143
+ # callback and resolve the scene entities directly inside `_prepare_terms`.
146
144
if not self ._env .sim .is_playing ():
147
145
# note: Use weakref on all callbacks to ensure that this object can be deleted when its destructor
148
146
# is called
149
147
# The order is set to 20 to allow asset/sensor initialization to complete before the scene entities
150
148
# are resolved. Those have the order 10.
151
149
timeline_event_stream = omni .timeline .get_timeline_interface ().get_timeline_event_stream ()
152
- self ._resolve_scene_entities_handle = timeline_event_stream .create_subscription_to_pop_by_type (
150
+ self ._resolve_terms_handle = timeline_event_stream .create_subscription_to_pop_by_type (
153
151
int (omni .timeline .TimelineEventType .PLAY ),
154
- lambda event , obj = weakref .proxy (self ): obj ._resolve_scene_entities_callback (event ),
152
+ lambda event , obj = weakref .proxy (self ): obj ._resolve_terms_callback (event ),
155
153
order = 20 ,
156
154
)
157
155
else :
158
- self ._resolve_scene_entities_handle = None
156
+ self ._resolve_terms_handle = None
157
+
158
+ # parse config to create terms information
159
+ if self .cfg :
160
+ self ._prepare_terms ()
159
161
160
162
def __del__ (self ):
161
163
"""Delete the manager."""
162
- if self ._resolve_scene_entities_handle :
163
- self ._resolve_scene_entities_handle .unsubscribe ()
164
- self ._resolve_scene_entities_handle = None
164
+ if self ._resolve_terms_handle :
165
+ self ._resolve_terms_handle .unsubscribe ()
166
+ self ._resolve_terms_handle = None
165
167
166
168
"""
167
169
Properties.
@@ -206,7 +208,7 @@ def find_terms(self, name_keys: str | Sequence[str]) -> list[str]:
206
208
specified as regular expressions or a list of regular expressions. The search is
207
209
performed on the active terms in the manager.
208
210
209
- Please check the :meth:`isaaclab.utils.string_utils.resolve_matching_names` function for more
211
+ Please check the :meth:`~ isaaclab.utils.string_utils.resolve_matching_names` function for more
210
212
information on the name matching.
211
213
212
214
Args:
@@ -249,11 +251,10 @@ def _prepare_terms(self):
249
251
Internal callbacks.
250
252
"""
251
253
252
- def _resolve_scene_entities_callback (self , event ):
253
- """Resolve the scene entities configuration .
254
+ def _resolve_terms_callback (self , event ):
255
+ """Resolve configurations of terms once the simulation starts .
254
256
255
- This callback is called when the simulation starts. It is used to resolve the
256
- scene entities configuration for the terms.
257
+ Please check the :meth:`_process_term_cfg_at_play` method for more information.
257
258
"""
258
259
# check if config is dict already
259
260
if isinstance (self .cfg , dict ):
@@ -266,17 +267,26 @@ def _resolve_scene_entities_callback(self, event):
266
267
# check for non config
267
268
if term_cfg is None :
268
269
continue
269
- # resolve the scene entity configuration
270
- self ._resolve_scene_entity_cfg (term_name , term_cfg )
270
+ # process attributes at runtime
271
+ # these properties are only resolvable once the simulation starts playing
272
+ self ._process_term_cfg_at_play (term_name , term_cfg )
271
273
272
274
"""
273
- Helper functions.
275
+ Internal functions.
274
276
"""
275
277
276
278
def _resolve_common_term_cfg (self , term_name : str , term_cfg : ManagerTermBaseCfg , min_argc : int = 1 ):
277
- """Resolve common term configuration.
279
+ """Resolve common attributes of the term configuration.
280
+
281
+ Usually, called by the :meth:`_prepare_terms` method to resolve common attributes of the term
282
+ configuration. These include:
278
283
279
- Usually, called by the :meth:`_prepare_terms` method to resolve common term configuration.
284
+ * Resolving the term function and checking if it is callable.
285
+ * Checking if the term function's arguments are matched by the parameters.
286
+ * Resolving special attributes of the term configuration like ``asset_cfg``, ``sensor_cfg``, etc.
287
+ * Initializing the term if it is a class.
288
+
289
+ The last two steps are only possible once the simulation starts playing.
280
290
281
291
By default, all term functions are expected to have at least one argument, which is the
282
292
environment object. Some other managers may expect functions to take more arguments, for
@@ -303,29 +313,31 @@ def _resolve_common_term_cfg(self, term_name: str, term_cfg: ManagerTermBaseCfg,
303
313
f" Received: '{ type (term_cfg )} '."
304
314
)
305
315
306
- # iterate over all the entities and parse the joint and body names
307
- if self ._env .sim .is_playing ():
308
- self ._resolve_scene_entity_cfg (term_name , term_cfg )
309
-
310
316
# get the corresponding function or functional class
311
317
if isinstance (term_cfg .func , str ):
312
318
term_cfg .func = string_to_callable (term_cfg .func )
319
+ # check if function is callable
320
+ if not callable (term_cfg .func ):
321
+ raise AttributeError (f"The term '{ term_name } ' is not callable. Received: { term_cfg .func } " )
313
322
314
- # initialize the term if it is a class
323
+ # check if the term is a class of valid type
315
324
if inspect .isclass (term_cfg .func ):
316
325
if not issubclass (term_cfg .func , ManagerTermBase ):
317
326
raise TypeError (
318
327
f"Configuration for the term '{ term_name } ' is not of type ManagerTermBase."
319
328
f" Received: '{ type (term_cfg .func )} '."
320
329
)
321
- term_cfg .func = term_cfg .func (cfg = term_cfg , env = self ._env )
330
+ func_static = term_cfg .func .__call__
331
+ min_argc += 1 # forward by 1 to account for 'self' argument
332
+ else :
333
+ func_static = term_cfg .func
322
334
# check if function is callable
323
- if not callable (term_cfg . func ):
335
+ if not callable (func_static ):
324
336
raise AttributeError (f"The term '{ term_name } ' is not callable. Received: { term_cfg .func } " )
325
337
326
- # check if term's arguments are matched by params
338
+ # check statically if the term's arguments are matched by params
327
339
term_params = list (term_cfg .params .keys ())
328
- args = inspect .signature (term_cfg . func ).parameters
340
+ args = inspect .signature (func_static ).parameters
329
341
args_with_defaults = [arg for arg in args if args [arg ].default is not inspect .Parameter .empty ]
330
342
args_without_defaults = [arg for arg in args if args [arg ].default is inspect .Parameter .empty ]
331
343
args = args_without_defaults + args_with_defaults
@@ -338,8 +350,22 @@ def _resolve_common_term_cfg(self, term_name: str, term_cfg: ManagerTermBaseCfg,
338
350
f" and optional parameters: { args_with_defaults } , but received: { term_params } ."
339
351
)
340
352
341
- def _resolve_scene_entity_cfg (self , term_name : str , term_cfg : ManagerTermBaseCfg ):
342
- """Resolve the scene entity configuration for the term.
353
+ # process attributes at runtime
354
+ # these properties are only resolvable once the simulation starts playing
355
+ if self ._env .sim .is_playing ():
356
+ self ._process_term_cfg_at_play (term_name , term_cfg )
357
+
358
+ def _process_term_cfg_at_play (self , term_name : str , term_cfg : ManagerTermBaseCfg ):
359
+ """Process the term configuration at runtime.
360
+
361
+ This function is called when the simulation starts playing. It is used to process the term
362
+ configuration at runtime. This includes:
363
+
364
+ * Resolving the scene entity configuration for the term.
365
+ * Initializing the term if it is a class.
366
+
367
+ Since the above steps rely on PhysX to parse over the simulation scene, they are deferred
368
+ until the simulation starts playing.
343
369
344
370
Args:
345
371
term_name: The name of the term.
@@ -362,3 +388,8 @@ def _resolve_scene_entity_cfg(self, term_name: str, term_cfg: ManagerTermBaseCfg
362
388
omni .log .info (msg )
363
389
# store the entity
364
390
term_cfg .params [key ] = value
391
+
392
+ # initialize the term if it is a class
393
+ if inspect .isclass (term_cfg .func ):
394
+ omni .log .info (f"Initializing term '{ term_name } ' with class '{ term_cfg .func .__name__ } '." )
395
+ term_cfg .func = term_cfg .func (cfg = term_cfg , env = self ._env )
0 commit comments