USD 场景描述和组件 Component 契约
场景数据与运行逻辑
LambdaSim 基于 USD(Universal Scene Description,通用场景描述)组织和保存场景。机器人、料箱、货架等对象的层级、变换、几何和材质,以及 LambdaSim 自定义的业务数据,都可以随场景保存。
USD 承载场景描述,LambdaSim 解释自己的业务数据,Component 提供对象的具体功能。
USD 本身也定义变换、几何、材质等标准数据的语义,并提供场景组合机制。因此,“不解释数据”在这里是指:USD 不会根据 LambdaSim 的自定义字段自动执行抓取、IK 或仿真逻辑。参见 OpenUSD 官方介绍。
例如,USD 可以保存料箱上的抓取区域。加载后,LambdaSim 将这些记录解析为 GripperSlot;采样与 IK 模块再使用它们计算抓取目标和机器人关节状态。
LambdaSim 的 Component 组件系统
Component 是 LambdaSim 提供的功能扩展机制,不属于 USD 官方规范。
每个组件可以理解为附着在场景节点上的小程序,例如抓取标定、机器人运动控制或仿真计算。组件的描述和参数随 USD 保存;LambdaSim 加载场景后,根据自己的组件契约恢复并执行对应功能。
因此,其他软件可以读取 USD 中的场景,但要使用这些组件的功能,需要 LambdaSim 提供相应的解释与运行支持。
组件的层级结构示意
组件所需的数据按照场景中的归属位置保存。存成 USD 后,节点层级及可持久化的组件描述也随之保留。再次加载时,LambdaSim / LambdaEngine 根据描述匹配已注册的组件实现,读取参数,恢复运行时组件;文件保存的是描述和参数,执行代码由运行环境提供。
因此,主要工作流是:配置好 USD 场景及组件 → LambdaSim 加载并解释 → 执行仿真计算。USD 场景描述与 LambdaSim 组件描述共同表达仿真配置,具体计算由已配置的组件及求解器执行。
| 组件 | 用途 |
|---|---|
| Transform | 查看和编辑节点的位置、旋转、缩放。 |
| Material | 查看和编辑材质参数。 |
| MakeGripperSlot | 标定物体的抓取区域,编辑位置、方向、宽度和角度范围。 |
选择对象后,可以在参数面板查看已有组件,通过 Add Component 添加适用于当前节点的组件。支持可视化编辑的组件还会提供 Visual Guide 或 Gizmo,帮助用户在视口中调整参数。
组件与数据格式各有职责:组件负责如何使用和编辑数据,数据契约负责这些数据具体表示什么。
Example 写入 GripperSlot 并保存 USD
原始料箱资产随仓库提供,路径为 tests/asset/factory/lx_raw.usda,其中没有 GripperSlot 标定或组件记录。
本例选择料箱一侧的长边作为抓取区域:沿边长允许移动,夹爪从上向下插入,并允许在标定方向两侧各偏转 30°。配图中的数值与下方代码一致。
从仓库根目录运行以下代码,读取 lx_raw.usda,写入标定,再将结果保存到当前目录的 lx_raw_with_gripper_slot.usda。整个过程不需要打开编辑器。
import ektro as ek
from lambdasim.scene.scene import Scene
from lambdasim.robotics import GripperSlot, write_gripper_slots
from lambda_engine.components import ComponentDocument
# 加载不含抓取标定的原始料箱。Load the raw box without grasp calibration.
scene = Scene()
box = scene.add_usd__ektro("tests/asset/factory/lx_raw.usda", name="box")
# 以下位置和方向均以料箱根节点的局部坐标系为基准。All positions and directions below are expressed in the box root node's local frame.
slot = GripperSlot()
slot.center = ek.vec3(0.138, 0.0, 0.13)
slot.forward = ek.vec3(0.0, 0.0, -1.0)
slot.span_axis = ek.vec3(-1.0, 0.0, 0.0)
slot.up_range = 0.30 # 沿派生 Up 的纵向总范围,单位米。Total range along derived Up, in meters.
slot.slot_input_angle = 60.0
# 写入 schema,并保存场景及组件描述。Write the schema and save the scene with component descriptions.
node = box.model.geo_domain.get_xform_node(box.root_xform_node_id)
write_gripper_slots(node, [slot])
ComponentDocument.for_scene(scene).save_usd("lx_raw_with_gripper_slot.usda", overwrite=True)
write_gripper_slots() 整体设置该节点的抓取区域列表,本例将其设为一个 Slot。up_range = 0.30 表示纵向抓取总长度为 0.30 米,即中心两侧各 0.15 米;Up 方向由 normalize(cross(forward, span_axis)) 推导;插入角度总范围为 60°。
ComponentDocument.save_usd() 会调用组件的序列化逻辑,将场景和组件描述一起写入当前目录的 lx_raw_with_gripper_slot.usda,保留源资产文件。保存后的场景可在 LambdaEngine 中重新加载,恢复 MakeGripperSlot 组件继续编辑。
Lambda Engine 编辑器中配置 GripperSlot

如图(上): 在大纲中选中料箱的 Box 节点,通过参数面板的 Add Component 添加 MakeGripperSlot 组件,将抓取标定信息附加到该节点。

如图(上): 在 Slots 列表中添加两个抓取区域,分别标定料箱两侧开口处的边缘。通过 Center、Rotation 和 Up Range 设置区域的位置、朝向及纵向抓取范围,也可以直接拖动视口中的 gizmo 调整。橙色表示当前选中的GripperSlot,白色表示另一个GripperSlot。

如图(上): 将两个抓取区域的 Input Angle 均设为 60°,视口中会显示对应的扇形,表示允许相对标定方向偏转 −30°~+30° 插入。 将两者的 GroupName 均设为 Pair_A,表示它们属于同一组。后续采样会同步选择组内各区域的纵向偏移和插入角度,生成配对的抓取位姿,适用于双臂协同抓取。
Component 数据契约
每个组件需要明确自己的数据契约,包括:
- 保存位置:数据属于哪个对象、节点、材质或相机。
- 字段与类型:字段名称、数据类型,以及哪些字段必填。
- 空间与单位:位置属于局部还是世界坐标,方向轴如何定义,角度和长度使用什么单位。
- 版本与读取规则:保存的数据如何恢复,遇到不支持的版本如何处理。
参数面板中显示一个字段,并不意味着它会自动保存。组件开发者需要明确实现读取和写入逻辑,保证保存的数据可以被正确恢复。
运行时的对象引用、缓存和编辑器控件等,也不会因为是组件成员就自动写入 USD。
以 MakeGripperSlot 组件为例
一个料箱可以有多个抓取区域。它们的业务数据位于节点的 metadata 中:
metadata["lambdasim"]["gripper_slots"]
该字段保存 GripperSlot 记录的列表。列表中的每条记录描述一个抓取区域,而不是一个机器人或一条运动轨迹。
MakeGripperSlot 组件负责编辑这些记录,并显示对应的 Visual Guide。保存时,还会写入该组件的类型、版本和恢复所需的数据,组件记录位于:
metadata["lambda_components"]["lambda_engine.make_gripper_slot"]
这里展示的是程序中的 metadata 访问形式,USD 文件中的编码由读写层完成。
lambdasim 下的数据供业务模块使用,lambda_components 下的记录用于组件恢复;这两处数据由组件的读写逻辑维护。使用者无需手动同步,也无需在重新加载后重复添加同一个组件。
这些名称和字段属于 LambdaSim / LambdaEngine 的自定义数据契约,不是 USD 官方的抓取 Schema。字段定义见 GripperSlot 组件契约。
编辑、保存与重新加载
- 加载场景:读取 USD 中的场景描述,在 LambdaSim 中构建对象及其数据。
- 恢复组件:LambdaEngine 根据已注册组件的识别规则读取数据,恢复组件及其参数。
- 编辑参数:通过参数面板或 Gizmo 修改组件对应的业务数据。
- 保存场景:编辑器调用组件的序列化逻辑,将需要持久化的数据写入保存副本,再输出 USD 文件。
重新加载时,恢复的是保存的数据及其对应组件。机器人运动、IK 求解和动力学仿真仍需要相应的运行逻辑;仅保存一个抓取区域,不会自动让机器人开始抓取。
组件开发者入口
组件基类定义在 lambda_engine/src/lambda_engine/components/base.py。保存与恢复对应以下接口:
@classmethod
def on_deserialization(cls, context: DeserializationContext) -> Component:
...
def on_serialization(self, context: SerializationContext) -> None:
...
on_deserialization():从约定的数据位置读取和校验数据,创建完整组件。on_serialization():将当前需要保存的数据写入框架提供的保存目标。
框架负责定位来源和保存目标,具体字段由组件解释。组件的 UI 参数声明与持久化数据格式分别定义,不能依靠界面字段推断保存内容。
实际示例见 MakeGripperSlot 源码。

