Recording Animations of Simulations#
Isaac Lab supports two approaches for recording animations of physics simulations: the Stage Recorder and the OVD Recorder. Both generate USD outputs that can be played back in Omniverse, but they differ in how they work and when you’d use them.
The Stage Recorder extension listens to all motion and USD property changes in the stage during simulation and records them as time-sampled data. The result is a USD file that captures only the animated changes—not the full scene—and matches the hierarchy of the original stage at the time of recording. This makes it easy to add as a sublayer for playback or rendering.
This method is built into Isaac Lab’s UI through the BaseEnvWindow
.
However, to record the animation of a simulation, you need to disable Fabric to allow reading and writing
all the changes (such as motion and USD properties) to the USD stage.
The OVD Recorder is designed for more scalable or automated workflows. It uses OmniPVD to capture simulated physics from a played stage and then bakes that directly into an animated USD file. It works with Fabric enabled and runs with CLI arguments. The animated USD can be quickly replayed and reviewed by scrubbing through the timeline window, without simulating expensive physics operations.
Note
Omniverse only supports either physics simulation or animation playback on a USD prim—never both at once. Disable physics on the prims you want to animate.
Stage Recorder#
In Isaac Lab, the Stage Recorder is integrated into the BaseEnvWindow
class.
It’s the easiest way to capture physics simulations visually and works directly through the UI.
To record, Fabric must be disabled—this allows the recorder to track changes to USD and write them out.
Stage Recorder Settings#
Isaac Lab sets up the Stage Recorder with sensible defaults in base_env_window.py
. If needed,
you can override or inspect these by using the Stage Recorder extension directly in Omniverse Create.
Settings used in base_env_window.py
1 def _toggle_recording_animation_fn(self, value: bool):
2 """Toggles the animation recording."""
3 if value:
4 # log directory to save the recording
5 if not hasattr(self, "animation_log_dir"):
6 # create a new log directory
7 log_dir = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
8 self.animation_log_dir = os.path.join(os.getcwd(), "recordings", log_dir)
9 # start the recording
10 _ = omni.kit.commands.execute(
11 "StartRecording",
12 target_paths=[("/World", True)],
13 live_mode=True,
14 use_frame_range=False,
15 start_frame=0,
16 end_frame=0,
17 use_preroll=False,
18 preroll_frame=0,
19 record_to="FILE",
20 fps=0,
21 apply_root_anim=False,
22 increment_name=True,
23 record_folder=self.animation_log_dir,
24 take_name="TimeSample",
25 )
26 else:
27 # stop the recording
28 _ = omni.kit.commands.execute("StopRecording")
29 # save the current stage
30 source_layer = self.stage.GetRootLayer()
31 # output the stage to a file
32 stage_usd_path = os.path.join(self.animation_log_dir, "Stage.usd")
33 source_prim_path = "/"
34 # creates empty anon layer
35 temp_layer = Sdf.Find(stage_usd_path)
36 if temp_layer is None:
37 temp_layer = Sdf.Layer.CreateNew(stage_usd_path)
38 temp_stage = Usd.Stage.Open(temp_layer)
39 # update stage data
40 UsdGeom.SetStageUpAxis(temp_stage, UsdGeom.GetStageUpAxis(self.stage))
41 UsdGeom.SetStageMetersPerUnit(temp_stage, UsdGeom.GetStageMetersPerUnit(self.stage))
42 # copy the prim
43 Sdf.CreatePrimInLayer(temp_layer, source_prim_path)
44 Sdf.CopySpec(source_layer, source_prim_path, temp_layer, source_prim_path)
45 # set the default prim
46 temp_layer.defaultPrim = Sdf.Path(source_prim_path).name
47 # remove all physics from the stage
48 for prim in temp_stage.TraverseAll():
49 # skip if the prim is an instance
50 if prim.IsInstanceable():
51 continue
52 # if prim has articulation then disable it
53 if prim.HasAPI(UsdPhysics.ArticulationRootAPI):
54 prim.RemoveAPI(UsdPhysics.ArticulationRootAPI)
55 prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI)
56 # if prim has rigid body then disable it
57 if prim.HasAPI(UsdPhysics.RigidBodyAPI):
58 prim.RemoveAPI(UsdPhysics.RigidBodyAPI)
59 prim.RemoveAPI(PhysxSchema.PhysxRigidBodyAPI)
60 # if prim is a joint type then disable it
61 if prim.IsA(UsdPhysics.Joint):
62 prim.GetAttribute("physics:jointEnabled").Set(False)
63 # resolve all paths relative to layer path
64 omni.usd.resolve_paths(source_layer.identifier, temp_layer.identifier)
65 # save the stage
66 temp_layer.Save()
67 # print the path to the saved stage
68 print("Recording completed.")
69 print(f"\tSaved recorded stage to : {stage_usd_path}")
70 print(f"\tSaved recorded animation to: {os.path.join(self.animation_log_dir, 'TimeSample_tk001.usd')}")
71 print("\nTo play the animation, check the instructions in the following link:")
72 print(
73 "\thttps://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html#using-the-captured-timesamples"
74 )
75 print("\n")
76 # reset the log directory
77 self.animation_log_dir = None
Example Usage#
In standalone Isaac Lab environments, pass the --disable_fabric
flag:
./isaaclab.sh -p scripts/environments/state_machine/lift_cube_sm.py --num_envs 8 --device cpu --disable_fabric
After launching, the Isaac Lab UI window will display a “Record Animation” button. Click to begin recording. Click again to stop.
The following files are saved to the recordings/
folder:
Stage.usd
— the original stage with physics disabledTimeSample_tk001.usd
— the animation (time-sampled) layer
To play back:
./isaaclab.sh -s # Opens Isaac Sim
Inside the Layers panel, insert both Stage.usd
and TimeSample_tk001.usd
as sublayers.
The animation will now play back when you hit the play button.
See the tutorial on layering in Omniverse for more on working with layers.
OVD Recorder#
The OVD Recorder uses OmniPVD to record simulation data and bake it directly into a new USD stage. This method is more scalable and better suited for large-scale training scenarios (e.g. multi-env RL).
It’s not UI-controlled—the whole process is enabled through CLI flags and runs automatically.
Workflow Summary#
User runs Isaac Lab with animation recording enabled via CLI
Isaac Lab starts simulation
OVD data is recorded as the simulation runs
At the specified stop time, the simulation is baked into an outputted USD file, and IsaacLab is closed
The final result is a fully baked, self-contained USD animation
Example Usage#
To record an animation:
./isaaclab.sh -p scripts/tutorials/03_envs/run_cartpole_rl_env.py \
--anim_recording_enabled \
--anim_recording_start_time 1 \
--anim_recording_stop_time 3
Note
The provided --anim_recording_stop_time
should be greater than the simulation time.
Warning
Currently, the final recording step can output many warning logs from [omni.usd]. This is a known issue, and these warning messages can be ignored.
After the stop time is reached, a file will be saved to:
anim_recordings/<timestamp>/baked_animation_recording.usda