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

类型

元素个数

含义

points

point3f[]

2359

顶点坐标

faceVertexCounts

int[]

2453

每个面的顶点数(这里全是 3,即三角形)

faceVertexIndices

int[]

7359

各面依次引用的顶点编号

normals

normal3f[]

7359

法线

primvars:st

texCoord2f[]

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 个(本站实测)。

延伸阅读#

版本说明#

Isaac Sim 5.1.0 内置 USD 0.24.5,usd-core 为 26.8(本站实测),本页用到的 API 在两者上行为一致。Isaac Lab 3.0 的相关变化见 8.1 3.0 改了什么、为什么改。