录制仿真动画#

Isaac Lab支持两种记录物理仿真动画的方法: 场景录制器OVD录制器 。两者都会生成可以在Omniverse中回放的USD输出,但它们在工作方式和使用场景上略有不同。

场景录制器 扩展监听在仿真过程中场景上所有运动和 USD 属性更改,并将其记录为**时间采样数据**。 最终结果是一个 USD 文件,只捕捉动画更改—**而不是**完整的场景—并且在记录时与原始场景的层次结构相匹配。这使得将其作为子图层进行播放或渲染变得更容易。

这种方法内置在 Isaac Lab 的UI中,通过:class:~isaaclab.envs.ui.BaseEnvWindow。然而,为了记录仿真的动画,您需要禁用 Fabric ,以允许读取和写入所有更改(比如运动和USD属性)到USD场景。

OVD Recorder 旨在为更具可扩展性或自动化的工作流程而设计。它使用 OmniPVD 来捕获从播放场景中仿真的物理效果,然后直接将其“烘焙”到一个动画 USD 文件中。它支持 Fabric 功能,并可以通过 CLI 参数运行。通过在时间轴窗口中拖动来快速重播和查看动画 USD,无需进行昂贵的物理仿真操作。

备注

Omniverse 仅支持在 USD prim 上进行**物理仿真**或**动画播放**,不可同时进行。对希望进行动画的 prim 禁用物理效果。

场景录制器#

在 Isaac Lab 中,场景记录器集成到 BaseEnvWindow 类中。这是捕捉物理仿真的最简单方式,并直接通过 UI 运行。

要记录,必须禁用 Fabric——这样才能让记录器跟踪 USD 的更改并将其写出。

场景录制器设置#

Isaac Lab 在 base_env_window.py 中使用明智的默认设置设置场景记录器。如果需要,您可以通过在 Omniverse Create 中直接使用场景记录器扩展来覆盖或检查这些设置。

在 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

示例用法#

在独立的 Isaac Lab 环境中,传递 --disable_fabric 标志:

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

启动后,Isaac Lab UI 窗口将显示一个 “Record Animation” 按钮。点击开始录制。再次点击以停止。

以下文件被保存到 recordings/ 文件夹中:

  • Stage.usd — 禁用物理的原始场景

  • TimeSample_tk001.usd — 动画(时间采样)图层

要回放:

./isaaclab.sh -s  # Opens Isaac Sim

在“Layers”面板中,插入 Stage.usdTimeSample_tk001.usd 作为子图层。现在,当您点击播放按钮时,动画将开始播放。

查看 Omniverse 中的 图层教程 ,了解更多关于图层工作的信息。

OVD Recorder#

OVD 录制器 使用 OmniPVD 来记录仿真数据,并将其直接烘焙到新的 USD 场景中。这种方法更具可扩展性,更适合大规模训练场景(例如,多环境 RL)。

这不是通过用户界面控制的——整个过程是通过命令行标志启用的,并自动运行。

工作流程总结#

  1. 用户通过CLI运行 Isaac Lab ,启用动画录制。

  2. Isaac Lab 开始仿真

  3. OVD 数据在仿真运行时记录`。

  4. 在指定的停止时间,仿真被烘烤成一个输出的USD文件,并关闭IsaacLab。

  5. 最终结果是一个完全成型的、独立的 USD 动画

示例用法#

要记录动画:

./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

备注

提供的 --anim_recording_stop_time 应大于仿真时间。

警告

当前,最终录制步骤可以从 [omni.usd] 中输出许多警告日志。这是一个已知问题,可以忽略这些警告消息。

停止时间达到后,文件将保存到:

anim_recordings/<timestamp>/baked_animation_recording.usda