USD 核心概念#
学习目标
打开一个机器人 USD 文件,说出其中的 Stage、Prim、Attribute、Relationship、Path 各是什么
用 Python 打印任意 USD 文件的 Prim 树和属性
检查一个资产的单位和坐标轴,看懂 Isaac Lab 配置里的
prim_path
前置知识
本页例子来自 Galbot One Golf 描述仓库[1](Apache-2.0,commit 2d496b0),片段左侧是原文件行号。
Stage:合成后的整个场景#
这一节回答:打开一个 USD 文件,得到的是什么?
Stage 是若干 Layer 合成(composition)的结果[2]。打开 galbot_one_golf.usda,USD 顺着引用、载荷和变体把十几个文件合成为一个 Stage,你读写的是这个合成结果。
用本页的示例脚本打开它,前三层是这样的(节选):
/ 根(伪根)
└─ galbot_one_golf Xform 81 个子节点
├─ base_link Xform 底盘,下有 visuals / collisions
├─ leg_link1…5 Xform 腿部连杆
├─ joints Scope 77 个 PhysicsRevoluteJoint
├─ left_arm_link1 Xform …其余连杆同理
└─ …
└─ Render Kit 的渲染设置,与机器人无关
Galbot 的 Prim 树(前三层)。连杆(link)都是 galbot_one_golf 的直接子节点,关节集中放在 joints 下。本站实测:默认遍历共 619 个 Prim;加上实例内部的共 1517 个。
Prim:场景里的节点#
这一节回答:树上的每个节点是什么?
Prim 是 USD 中承载场景描述的基本容器[2]。每个 Prim 有:
类型(type):决定它是什么、有哪些内置属性。Galbot 里常见的有
Xform(只带位姿的变换节点)、Mesh(网格)、Scope(纯分组,不带位姿)、PhysicsRevoluteJoint(转动关节)。说明符(specifier):
def、over或class三者之一[2]。def表示"在这里定义它",over表示"如果别处定义了它,就在这里覆盖一些值"。
同一个关节在两个文件里分别出现,正好说明两者的区别。physics.usda 用 def 定义关节,physx.usda 用 over 给它追加 PhysX 专用的参数:
98 over "left_arm_joint1" (
99 prepend apiSchemas = ["PhysxJointAPI"]
100 )
101 {
102 float physxJoint:maxJointVelocity = 85.943665
103 }
usd/payloads/Physics/physx.usda L98–L103[3]。
Property:Attribute 与 Relationship#
这一节回答:Prim 上挂着哪些数据?
Property 分两种[2]:
Attribute(属性):有类型、有值,比如
float、point3f、token。Relationship(关系):没有类型的"指针",指向其他 Prim 的路径。
关节 left_arm_joint1 的定义同时包含这两种:
254 def PhysicsRevoluteJoint "left_arm_joint1" (
255 prepend apiSchemas = ["PhysicsJointStateAPI:angular", "PhysicsDriveAPI:angular"]
256 )
257 {
258 float drive:angular:physics:damping = 10000
259 float drive:angular:physics:maxForce = 1000
260 float drive:angular:physics:stiffness = 100000
261 float drive:angular:physics:targetPosition = 0
262 uniform token drive:angular:physics:type = "force"
263 uniform token physics:axis = "Z"
264 custom rel physics:body0
265 prepend rel physics:body0 = </galbot_one_golf/leg_link5>
266 custom rel physics:body1
267 prepend rel physics:body1 = </galbot_one_golf/left_arm_link1>
usd/payloads/Physics/physics.usda L254–L268(后面还有限位、局部位姿等 10 个属性)[4]。
drive:…、physics:axis 是 Attribute;physics:body0 和 physics:body1 是 Relationship,指明这个关节连接哪两个连杆。属性名里的冒号是命名空间,drive:angular:physics: 前缀说明它来自角向驱动 Schema(见 2.4)。
网格 Prim 的属性则是几何数据。示例脚本打印 left_arm_link1 的视觉网格 mesh_22,得到:
Attribute |
类型 |
元素个数 |
含义 |
|---|---|---|---|
|
|
2359 |
顶点坐标 |
|
|
2453 |
每个面的顶点数(这里全是 3,即三角形) |
|
|
7359 |
各面依次引用的顶点编号 |
|
|
7359 |
法线 |
|
|
7359 |
贴图坐标 |
表 1:mesh_22 的主要属性(本站实测)。另有 Relationship material:binding,指向它使用的材质。
Path:Prim 与属性的地址#
这一节回答:怎么指明"某个 Prim"或"某个属性"?
Path 标识 Prim 和属性在 Stage 命名空间中的位置[2],写法类似文件路径:
/galbot_one_golf/joints/left_arm_joint1:一个 Prim;/galbot_one_golf/joints/left_arm_joint1.physics:axis:这个 Prim 上的一个属性,用.隔开;@./instances.usda@</Instances/mesh_22>:引用另一个文件中的某个 Prim,@…@是文件,<…>是其中的路径。
最后一种写法出现在连杆的定义里:
526 def Xform "visuals" (
527 instanceable = false
528 )
529 {
530 def Xform "link1" (
531 instanceable = true
532 prepend references = @./instances.usda@</Instances/mesh_22>
533 )
usd/payloads/base.usda L526–L533[5]。instanceable = true 表示这个 Prim 以实例方式共享数据,因此默认遍历不会进入它的内部(见"常见坑")。
Metadata:defaultPrim、upAxis、metersPerUnit#
这一节回答:文件级的"说明信息"有哪些,为什么重要?
Metadata 是描述 Prim、属性或整个 Layer 的附加信息,与属性值分开存放[2]。机器人资产最重要的三项写在文件头:
29 defaultPrim = "galbot_one_golf"
35 metersPerUnit = 1
36 upAxis = "Z"
usd/galbot_one_golf.usda L29、L35–L36[6]。
defaultPrim:别的文件引用这个文件、又没写具体路径时,引用的就是它。Isaac Lab 加载机器人正是这样引用的(见下文)。
metersPerUnit:1 个单位等于多少米。
upAxis:哪个轴朝上。
两种查看方式#
这一节回答:手上有一个 USD 文件,怎么看它的结构?
用 Python。 本站示例 examples/isaaclab-2.3/2.2-usd-concepts/inspect_usd.py 打印元数据、Prim 树、类型统计和一个关节、一个网格的全部属性。pip 安装的 Isaac Sim 中,pxr 必须在 SimulationApp 启动之后才能导入;直接 from pxr import Usd 会报 No module named 'pxr'(本站实测)。只想看文件时,另建一个装有 usd-core 的小环境更快:
uv venv --python 3.11 usd-env && uv pip install --python usd-env/bin/python usd-core
usd-env/bin/python examples/isaaclab-2.3/2.2-usd-concepts/inspect_usd.py
# 在已激活的 env_isaaclab 中运行;脚本自动 headless 启动 SimulationApp
python examples/isaaclab-2.3/2.2-usd-concepts/inspect_usd.py
两种方式本站都跑通(usd-core 约 0.1 秒,Isaac Sim 约 10 秒),打印的 Prim 树和属性值一致;只是 usd-core 不认识 PhysX 与 Isaac 的扩展 Schema,applied schemas 列表更短。
用 Isaac Sim 界面。 File → Open 打开 USD 文件后,Stage 窗口列出当前场景中的全部 Prim,Property 面板显示选中 Prim 的详细属性[7]。这是最直观的方式,适合边看边改。
单位与坐标系#
这一节回答:为什么要在意 metersPerUnit 和 upAxis?
Isaac Sim 的约定是:长度用米,质量用千克,时间用秒;世界坐标系右手系,+Z 朝上[8]。
但 USD 本身的默认值不是这样。文件里没写这两项时,USD 取 upAxis = Y、metersPerUnit = 0.01(厘米)[9],本站用空 Stage 实测也是如此。而且把不同单位的资产组装到一起时,USD 不会自动换算,由组装方负责修正[9]。所以拿到一个新资产,先查这两项。
另一处容易踩的是角度:USD 里的角度用度,而 Isaac Sim 的 Python API 与 PhysX 内部用弧度[8]。Galbot 关节文件里的 physics:upperLimit = 172.1352 是度,换成弧度约 3.0。
与 Isaac Lab 的连接#
这一节回答:Path 在 Isaac Lab 配置里是什么样子?
Isaac Lab 的资产配置都有一个 prim_path,例如 "{ENV_REGEX_NS}/Robot"。其中 {ENV_REGEX_NS} 会被替换为 /World/envs/env_.*,即"每个环境下的 Robot"[10]。Isaac Lab 把机器人的 USD 文件作为引用挂到这个路径上,没有指定引用路径,因此引用的是文件的 defaultPrim[11]。
以 Galbot 为例:它的 defaultPrim 是 /galbot_one_golf,挂到 /World/envs/env_0/Robot 后,关节的路径就变成 /World/envs/env_0/Robot/joints/left_arm_joint1(本站用 usd-core 实测)。场景与资产配置的细节见 4.5 InteractiveScene:场景作为配置。
常见坑#
坑一:路径大小写写错,拿到的是无效 Prim
Path 区分大小写。/galbot_one_golf/Joints/left_arm_joint1(大写 J)找不到任何东西,GetPrimAtPath 也不会报错,而是返回一个无效的 Prim(IsValid() 为 False)。拿到 Prim 后先检查有效性(本站实测)。
坑二:资产没有 defaultPrim,引用后是空的
引用一个没写 defaultPrim 的文件、又没指定路径时,被引用的 Prim 下面什么都没有,USD 只打印一条 Unresolved reference prim path … <defaultPrim> 警告(本站实测)。在 Isaac Lab 里表现为机器人"加载了但看不见"。自己导出资产时,确认文件头有 defaultPrim。
坑三:单位不一致,机器人大了或小了 100 倍
从 DCC 软件导出的资产常以厘米为单位(metersPerUnit = 0.01)或 Y 轴朝上。USD 不会自动换算[9],直接放进以米为单位的场景,尺寸或朝向就会出错。导入前先查元数据。
坑四:遍历时漏掉了网格
stage.Traverse() 默认不进入实例(instanceable)内部。Galbot 的网格都在实例里,默认遍历只数到 6 个 Mesh,加上 Usd.TraverseInstanceProxies() 才数到 190 个(本站实测)。
延伸阅读#
官方文档:OpenUSD 术语表、Isaac Sim 5.1.0:Isaac Sim Conventions、Isaac Sim 5.1.0:OpenUSD Fundamentals
源码与资产:Galbot One Golf 描述仓库(Apache-2.0)
中文翻译(中文翻译站,译自最新版 Isaac Sim 文档,与主线 5.1.0 可能有差异):Isaac Sim 文档首页
下一步:2.3 Layer 与合成 讲本页一带而过的引用、载荷与变体
版本说明#
Isaac Sim 5.1.0 内置 USD 0.24.5,usd-core 为 26.8(本站实测),本页用到的 API 在两者上行为一致。Isaac Lab 3.0 的相关变化见 8.1 3.0 改了什么、为什么改。