> This page is for version Release 2.1.
> For other versions, use one of these documentation indexes:
> - Release 3.0 (default): https://docs.nvidia.com/isaaclab/3.0/llms.txt
> - Release 2.3: https://docs.nvidia.com/isaaclab/2.3/llms.txt
> - Release 2.2: https://docs.nvidia.com/isaaclab/2.2/llms.txt
> - Release 2.1: https://docs.nvidia.com/isaaclab/2.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/isaaclab/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/isaaclab/_mcp/server.

# Recording Animations of Simulations

Omniverse includes tools to record animations of physics simulations. The [Stage Recorder](https://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html) extension
listens to all the motion and USD property changes within a USD stage and records them to a USD file.
This file contains the time samples of the changes, which can be played back to render the animation.

The timeSampled USD file only contains the changes to the stage. It uses the same hierarchy as the original
stage at the time of recording. This allows adding the animation to the original stage, or to a different
stage with the same hierarchy. The timeSampled file can be directly added as a sublayer to the original stage
to play back the animation.

> **Note**
>
> Omniverse only supports playing animation or playing physics on a USD prim at the same time. If you want to
> play back the animation of a USD prim, you need to disable the physics simulation on the prim.

In Isaac Lab, we directly use the [Stage Recorder](https://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html) extension to record the animation of the physics simulation.
This is available as a feature in the `~isaaclab.envs.ui.BaseEnvWindow` class.
However, to record the animation of a simulation, you need to disable [Fabric](https://docs.omniverse.nvidia.com/kit/docs/usdrt/latest/docs/usd_fabric_usdrt.html) to allow reading and writing
all the changes (such as motion and USD properties) to the USD stage.

## Stage Recorder Settings

Isaac Lab integration of the [Stage Recorder](https://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html) extension assumes certain default settings. If you want to change the
settings, you can directly use the [Stage Recorder](https://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html) extension in the Omniverse Create application.

```python
def _toggle_recording_animation_fn(self, value: bool):
    """Toggles the animation recording."""
    if value:
        # log directory to save the recording
        if not hasattr(self, "animation_log_dir"):
            # create a new log directory
            log_dir = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
            self.animation_log_dir = os.path.join(os.getcwd(), "recordings", log_dir)
        # start the recording
        _ = omni.kit.commands.execute(
            "StartRecording",
            target_paths=[("/World", True)],
            live_mode=True,
            use_frame_range=False,
            start_frame=0,
            end_frame=0,
            use_preroll=False,
            preroll_frame=0,
            record_to="FILE",
            fps=0,
            apply_root_anim=False,
            increment_name=True,
            record_folder=self.animation_log_dir,
            take_name="TimeSample",
        )
    else:
        # stop the recording
        _ = omni.kit.commands.execute("StopRecording")
        # save the current stage
        stage = omni.usd.get_context().get_stage()
        source_layer = stage.GetRootLayer()
        # output the stage to a file
        stage_usd_path = os.path.join(self.animation_log_dir, "Stage.usd")
        source_prim_path = "/"
        # creates empty anon layer
        temp_layer = Sdf.Find(stage_usd_path)
        if temp_layer is None:
            temp_layer = Sdf.Layer.CreateNew(stage_usd_path)
        temp_stage = Usd.Stage.Open(temp_layer)
        # update stage data
        UsdGeom.SetStageUpAxis(temp_stage, UsdGeom.GetStageUpAxis(stage))
        UsdGeom.SetStageMetersPerUnit(temp_stage, UsdGeom.GetStageMetersPerUnit(stage))
        # copy the prim
        Sdf.CreatePrimInLayer(temp_layer, source_prim_path)
        Sdf.CopySpec(source_layer, source_prim_path, temp_layer, source_prim_path)
        # set the default prim
        temp_layer.defaultPrim = Sdf.Path(source_prim_path).name
        # remove all physics from the stage
        for prim in temp_stage.TraverseAll():
            # skip if the prim is an instance
            if prim.IsInstanceable():
                continue
            # if prim has articulation then disable it
            if prim.HasAPI(UsdPhysics.ArticulationRootAPI):
                prim.RemoveAPI(UsdPhysics.ArticulationRootAPI)
                prim.RemoveAPI(PhysxSchema.PhysxArticulationAPI)
            # if prim has rigid body then disable it
            if prim.HasAPI(UsdPhysics.RigidBodyAPI):
                prim.RemoveAPI(UsdPhysics.RigidBodyAPI)
                prim.RemoveAPI(PhysxSchema.PhysxRigidBodyAPI)
            # if prim is a joint type then disable it
            if prim.IsA(UsdPhysics.Joint):
                prim.GetAttribute("physics:jointEnabled").Set(False)
        # resolve all paths relative to layer path
        omni.usd.resolve_paths(source_layer.identifier, temp_layer.identifier)
        # save the stage
        temp_layer.Save()
        # print the path to the saved stage
        print("Recording completed.")
        print(f"\tSaved recorded stage to    : {stage_usd_path}")
        print(f"\tSaved recorded animation to: {os.path.join(self.animation_log_dir, 'TimeSample_tk001.usd')}")
        print("\nTo play the animation, check the instructions in the following link:")
        print(
            "\thttps://docs.omniverse.nvidia.com/extensions/latest/ext_animation_stage-recorder.html#using-the-captured-timesamples"
        )
        print("\n")
        # reset the log directory
        self.animation_log_dir = None
```

## Example Usage

In all environment standalone scripts, Fabric can be disabled by passing the `--disable_fabric` flag to the script.
Here we run the state-machine example and record the animation of the simulation.

```bash
./isaaclab.sh -p scripts/environments/state_machine/lift_cube_sm.py --num_envs 8 --device cpu --disable_fabric
```

On running the script, the Isaac Lab UI window opens with the button "Record Animation" in the toolbar.
Clicking this button starts recording the animation of the simulation. On clicking the button again, the
recording stops. The recorded animation and the original stage (with all physics disabled) are saved
to the `recordings` folder in the current working directory. The files are stored in the `usd` format:

* `Stage.usd`: The original stage with all physics disabled
* `TimeSample_tk001.usd`: The timeSampled file containing the recorded animation

You can open Omniverse Isaac Sim application to play back the animation. There are many ways to launch
the application (such as from terminal or [Omniverse Launcher](https://docs.omniverse.nvidia.com/launcher/latest/index.html)). Here we use the terminal to open the
application and play the animation.

```bash
./isaaclab.sh -s  # Opens Isaac Sim application through _isaac_sim/isaac-sim.sh
```

On a new stage, add the `Stage.usd` as a sublayer and then add the `TimeSample_tk001.usd` as a sublayer.
You can do this by dragging and dropping the files from the file explorer to the stage. Please check out
the [tutorial on layering in Omniverse](https://www.youtube.com/watch?v=LTwmNkSDh-c\&ab_channel=NVIDIAOmniverse) for more details.

You can then play the animation by pressing the play button.