@@ -239,3 +239,165 @@ def wrapped_step(dt):
239239 else :
240240 # If env creation failed, still clear the singleton
241241 SimulationContext .clear_instance ()
242+
243+
244+ @pytest .mark .parametrize ("env_type" , ["manager_based_env" , "manager_based_rl_env" , "direct_rl_env" ])
245+ def test_env_render_false_skips_rendering (env_type , physics_callback , render_callback ):
246+ """Test that passing render=False to step() skips all rendering while physics continues."""
247+ physics_cb , get_physics_stats = physics_callback
248+ render_cb , get_render_stats = render_callback
249+
250+ env = None
251+ physics_handle = None
252+ original_step = None
253+ viz = None
254+
255+ try :
256+ # create a new stage
257+ sim_utils .create_new_stage ()
258+
259+ # create environment with render_interval=1 so rendering would happen every physics step
260+ if env_type == "manager_based_env" :
261+ env = create_manager_based_env (render_interval = 1 )
262+ elif env_type == "manager_based_rl_env" :
263+ env = create_manager_based_rl_env (render_interval = 1 )
264+ else :
265+ env = create_direct_rl_env (render_interval = 1 )
266+
267+ # enable the flag to render the environment
268+ env .sim .set_setting ("/isaaclab/render/rtx_sensors" , True )
269+
270+ # disable the app from shutting down when the environment is closed
271+ env .sim ._app_control_on_stop_handle = None # type: ignore
272+
273+ # Reset to initialize visualizers
274+ env .reset ()
275+
276+ # Ensure the default Kit visualizer is active for rendering callbacks.
277+ assert isinstance (env .sim .visualizers [0 ], KitVisualizer )
278+
279+ # add physics callback
280+ physics_handle = env .sim .physics_manager .register_callback (
281+ physics_cb , IsaacEvents .POST_PHYSICS_STEP , name = "physics_step"
282+ )
283+
284+ # Wrap visualizer step to track render calls
285+ viz = env .sim .visualizers [0 ]
286+ original_step = viz .step
287+ render_dt = env .cfg .sim .dt * env .cfg .sim .render_interval
288+
289+ def wrapped_step (dt ):
290+ original_step (dt )
291+ render_cb (render_dt )
292+
293+ viz .step = wrapped_step
294+
295+ # create a zero action tensor for stepping the environment
296+ actions = torch .zeros ((env .num_envs , 0 ), device = env .device )
297+
298+ # Step with render=False for several steps
299+ for i in range (10 ):
300+ env .step (action = actions , render = False )
301+
302+ # Physics should still advance normally
303+ _ , num_physics_steps = get_physics_stats ()
304+ assert num_physics_steps == (i + 1 ) * env .cfg .decimation , "Physics steps mismatch with render=False"
305+
306+ # No rendering should have occurred
307+ _ , num_render_steps = get_render_stats ()
308+ assert num_render_steps == 0 , f"Expected 0 render steps with render=False, got { num_render_steps } "
309+
310+ finally :
311+ if viz is not None and original_step is not None :
312+ viz .step = original_step
313+ if physics_handle is not None :
314+ physics_handle .deregister ()
315+ if env is not None :
316+ env .close ()
317+ else :
318+ SimulationContext .clear_instance ()
319+
320+
321+ @pytest .mark .parametrize ("env_type" , ["manager_based_env" , "manager_based_rl_env" , "direct_rl_env" ])
322+ def test_env_render_flag_mixed_steps (env_type , physics_callback , render_callback ):
323+ """Test that render flag can be toggled between steps and rendering counts are correct."""
324+ physics_cb , get_physics_stats = physics_callback
325+ render_cb , get_render_stats = render_callback
326+
327+ env = None
328+ physics_handle = None
329+ original_step = None
330+ viz = None
331+
332+ try :
333+ # create a new stage
334+ sim_utils .create_new_stage ()
335+
336+ # create environment with render_interval=1 so every decimation step renders
337+ if env_type == "manager_based_env" :
338+ env = create_manager_based_env (render_interval = 1 )
339+ elif env_type == "manager_based_rl_env" :
340+ env = create_manager_based_rl_env (render_interval = 1 )
341+ else :
342+ env = create_direct_rl_env (render_interval = 1 )
343+
344+ # enable the flag to render the environment
345+ env .sim .set_setting ("/isaaclab/render/rtx_sensors" , True )
346+
347+ # disable the app from shutting down when the environment is closed
348+ env .sim ._app_control_on_stop_handle = None # type: ignore
349+
350+ # Reset to initialize visualizers
351+ env .reset ()
352+
353+ # Ensure the default Kit visualizer is active for rendering callbacks.
354+ assert isinstance (env .sim .visualizers [0 ], KitVisualizer )
355+
356+ # add physics callback
357+ physics_handle = env .sim .physics_manager .register_callback (
358+ physics_cb , IsaacEvents .POST_PHYSICS_STEP , name = "physics_step"
359+ )
360+
361+ # Wrap visualizer step to track render calls
362+ viz = env .sim .visualizers [0 ]
363+ original_step = viz .step
364+ render_dt = env .cfg .sim .dt * env .cfg .sim .render_interval
365+
366+ def wrapped_step (dt ):
367+ original_step (dt )
368+ render_cb (render_dt )
369+
370+ viz .step = wrapped_step
371+
372+ # create a zero action tensor for stepping the environment
373+ actions = torch .zeros ((env .num_envs , 0 ), device = env .device )
374+
375+ expected_render_steps = 0
376+
377+ # Step 5 times with render=True, then 5 with render=False
378+ for i in range (10 ):
379+ should_render = i < 5
380+ env .step (action = actions , render = should_render )
381+
382+ # Physics always advances
383+ _ , num_physics_steps = get_physics_stats ()
384+ assert num_physics_steps == (i + 1 ) * env .cfg .decimation , "Physics steps mismatch in mixed test"
385+
386+ # Rendering only happens in the first 5 steps
387+ if should_render :
388+ expected_render_steps += env .cfg .decimation # render_interval=1, so renders every decimation step
389+
390+ _ , num_render_steps = get_render_stats ()
391+ assert num_render_steps == expected_render_steps , (
392+ f"Render steps mismatch at step { i } : expected { expected_render_steps } , got { num_render_steps } "
393+ )
394+
395+ finally :
396+ if viz is not None and original_step is not None :
397+ viz .step = original_step
398+ if physics_handle is not None :
399+ physics_handle .deregister ()
400+ if env is not None :
401+ env .close ()
402+ else :
403+ SimulationContext .clear_instance ()
0 commit comments