在应用程序之间传输 3D 数据一直是一项挑战,尤其是原生计算机辅助设计 (CAD) 文件等专有格式。CAD 模型对于工程和物理产品的制造至关重要,但它们并不是为机器人或自动驾驶汽车 (AV) 等物理 AI 应用所需的复杂、协作和仿真密集型环境而设计的。
这就是 OpenUSD(通用场景描述)变得至关重要的原因。OpenUSD 是一个开放、可扩展的生态系统,用于在 3D 世界中进行描述、合成、模拟和协作。通过将精确的 CAD 数据转换为 OpenUSD,开发者可以解锁其物理设计,进行高级 AI 仿真,简化工作流并确保工具的一致性。这一过程是将物理 AI 概念从设计转化为现实的关键。
本文将引导您完成将 CAD 模型转换为管道就绪 USD 素材的完整互操作工作流,并重点介绍 OpenUSD 在无损编辑和无缝协作方面的优势。它还解释了 Omniverse Kit SDK 如何使这些工作流更加易于使用。此过程作为一种可能的流程的高级演示,您的具体步骤可能会因您的特定软件和资产复杂性而有所不同。
现代 3D 工作流中的 OpenUSD 应用场景
从开放式 CAD 格式转换为可用的、丰富的 USD 素材,这是一种学习体验。虽然转换器提供了一个坚实的基础,但真正的力量来自理解 OpenUSD 结构并根据您的特定数据管线需求定制数据。
OpenUSD 能够分层更改、添加自定义数据并使用 Python 编写交互脚本,因此在应对数据互操作性的复杂挑战时,它具有令人难以置信的强大功能。这并非总是按按钮就能解决的问题,因为有许多工作流使用 OpenUSD 灵活框架。
OpenUSD 提供了一个框架,通过数据互操作性和数据聚合协作这两个关键功能来应对 3D 数据传输的挑战。
数据互操作性
OpenUSD 通过为 3D 数据提供大量内置架构来提高互操作性。这些模式作为标准化蓝图,定义了如何表示网格、摄像头和光线等常见概念。此外,可扩展框架使开发者能够整合新的或未映射的数据模型,从而实现强大的工作流,例如转换不同的文件格式以用于 3D 环境。
自 OpenUSD 首次开源以来,UsdPhysics 和 UsdSemantics 等架构已引入对刚体物理和语义标签的支持。对于开发者而言,这些模式创建了一种通用语言,使应用程序能够读取数据并理解其意图。
这种一致的结构解锁了专门的工具集、数据验证和真正的语义互操作性,远远超出了视觉表示的范畴。这种可扩展性实现了强大的自定义集成,例如允许开发者添加将 CAD 零件与 PLM 系统中的记录关联起来的属性,以一种原始数据无法实现的方式丰富模型。
通过数据聚合实现协作
OpenUSD 架构在组装大型复杂场景时尤其有效。它允许团队并行处理单独的组件,然后使用 References 和 Payloads 将这些组件聚合到更大的场景中。这种方法避免了数据重复,并允许更新在整个工作流中顺利传播。此外,OpenUSD 非破坏性分层系统支持强大的协作工作流,因此团队可以基于彼此的工作进行构建,而不会覆盖彼此的工作。
如需了解更多信息,请参阅如何使用 OpenUSD,并查看 NVIDIA 深度学习培训中心自学课程:学习 OpenUSD:创建合成弧。
CAD 到 USD 示例工作流
此示例工作流首先将 CAD 数据导出为 Jupiter Tessellation (JT) 文件。JT 是国际标准化组织 (ISO) 广泛使用的 3D 可视化和协作标准。它擅长表示复杂的装配体和零件元数据,但不是原生 USD。首先使用 CAD 资产(例如 OnShape 上的 Nova Carter),并将其导出为 JT 文件。

将文件转换为 USD 格式
接下来,JT 文件被转换为 USD 文件。这一步有多个商业工具和一些新兴开源工具。此示例使用 Kit 扩展程序 omni.kit.conveter.cad 和 omni.kit.convert.jt_core 将 JT 文件转换为 USD。我们使用无头方法进行导出,但您也可以使用 Omniverse GUI。
以下示例遵循 Omniverse CAD Converter CLI 说明来转换 JT 素材:
- 在扩展管理器中启用扩展。
- 导航到包含 Kit 可执行文件的目录。
- 在 dependencies 部分添加 omni.services.convert.cad = {}。
- 构建应用程序并运行以下命令。如果您自己运行此命令,请务必调整文件路径。
./kit.exe `
--allow-root `
--enable omni.kit.converter.jt_core `
--exec `
--/app/fastShutdown=1 `
'"C:\Users\juslin\AppData\Local\ov\data\Kit\Justine_Composer\0.1\exts\3\omni.services.convert.cad-506.2.3+107.3.0.u353/omni/services/convert/cad/services/process/jt_main.py" --input-path "C:\Users\juslin\Downloads\Release2021.1Carterv2.0ASM.jt" --output-path "C:\Users\juslin\Downloads\NovaCarter.usd" --config-path "C:\Users\juslin\Downloads\jt_config.json"'`
--info
分析转换过程
转换完成后,将创建一个全新的 USD 文件。可以通过将此新文件加载到 Omniverse Kit 应用程序中来打开它。您可以使用 Kit App 模板 GitHub repo 构建应用程序,将生成的场景图与原始 JT 装配结构进行比较。CAD 模型的核心本质应该保持不变,现在它穿上了新的 USD 原生外衣。

哪些内容被干净地转换了
设计结构和实质(最关键的信息)在转换过程中不会丢失任何信息或元数据。
保留层次结构
在这个示例中,最重要的成功之处在于保留了装配体的树形结构。原始 JT 文件的嵌套层次结构在 USD 场景中得到了完美镜像。当展开顶级装配体 prim 时,RMPLite2201 会列出所有相同的子装配体和零件,就像在源 CAD 数据中一样。这一点至关重要,因为它可以保持设计意图和组件的逻辑分组。

Prim 等效性
源和输出之间存在清晰且可预测的关系。对于原始 JT 模型中的每个组件实例,转换器会在 USD 场景中生成相应的 prim。这种一对一映射使数据可预测且可编写脚本,为任何下游工作(如应用材质或动画)奠定了坚实基础。
可识别的命名方式
虽然名称不完全相同(详情见下文),但每个原件的核心名称直接来自原始 CAD 组件名称。这使得场景图立即可被人阅读并易于导航。请注意,要查看 CAD 模型中的原始组件名称,请导航到 Stage 选项卡菜单并启用 Show Display Names。这样可以更轻松地比较原生版本和 USD 版本。
差异:转码指纹
在这个例子中,差异与其说是错误,不如说是自动转码过程留下的“指纹”。这些变化是 USD 工作方式以及转换器如何确保有效、高性能场景的产物。
命名前缀和后缀
每个 prim 名称都更改了:
tn_
前缀:所有 prim 的前缀均为tn_
。omni.transcoding 等转换器通常会创建命名空间,以便轻松识别场景的哪些部分是自动生成的,哪些部分是后来手动添加或修改的。- 标识符后缀:Prim 名称还带有看似随机的字符串(例如
tn_Bolt_A_Xform_01_2aef1d
)。这些也是 omni.transcoding 写入的编码字符串的一部分。 - 字符串:OpenUSD 原生体和其他标识符中仍有几种字符串类型不允许使用。有关更多信息,请参阅无效标识符的双向转码。
场景图 USD 实例化
场景图 USD 实例化是本示例中最显著、最具优势的差异。原始 JT 文件包含许多重复的零件,例如螺栓和紧固件。转换器利用实例化(USD 最强大的功能之一)来代替为每个实例创建螺栓几何图形的完整副本。为螺栓几何图形创建了一个高效的网格,然后创建了轻量级 prim,这些 prim 只需指向该网格,每个 prim 都有自己独特的位置和方向。
这是一项巨大的优化,可显著减少文件大小和内存占用,从而使场景在实时应用中的性能大幅提升。这些有针对性的结构化技术通过实例化来高效地表示重复的零件,从而创建了一个干净的 USD 场景。

从原始数据到生产资产:优化、材质和结构化处理
从 JT 到 USD 的初始转换提供了结构健全但原始的资产。几何图形以密集的直接来自 CAD 的镶嵌形式移动,材质不超过简单的颜色,整个数据集是一个整体。
如何为实时渲染、仿真或协作工作流准备这些数据?它需要使用一些功能强大的 OpenUSD 功能(如有效负载和引用)以及 Omniverse 功能(如场景优化器)进行优化、丰富和构建。
优化
转换为多边形网格的 CAD 几何图形通常非常密集。转换后的模型包含数百万个多边形,因此过于庞大,无法高效地进行实时使用。解决方案是使用场景优化器(例如 Omniverse Kit 中的核心技术 omni.scene.optimizer 扩展程序)创建几何图形的优化版本。此扩展程序提供了一个功能强大的 Python API,可通过编程方式处理繁重的 USD 文件并生成轻量级版本。
Omniverse Kit 应用程序中有一个内置的脚本编辑器。这使得场景优化器能够在三个主要操作中进行优化,配置如下:
Merge_params
:合并所有共享相同材质的网格。这对于 CAD 模型来说是一项巨大的性能提升,因为 CAD 模型通常包含许多使用相同材料的独立部件。除了实例化之外,这是另一种减少场景中基本图形数量的方法,可以提高性能。Hidden_mesh_params
:使用findHiddenMeshes
识别并删除完全遮挡的内部网格。这非常适合 CAD 模型,因为 CAD 模型通常包含复杂的内部组件,这些组件虽然不可见,但仍然会占用内存和处理能力。Decimation_params
: 降低网格镶嵌密度。将max_mean_error
设置为 0.1 可确保简化网格表面与原始高多边形模型的偏差不超过 0.1 场景单位(通常为厘米),从而保持视觉保真度。
import omni.usd
import omni.kit.commands
import omni.scene.optimizer.core
import time
import json
import carb
# Get the UsdContext, which manages the application's scenes.
usd_context = omni.usd.get_context()
# Get the stage that is currently open in the application.
stage = usd_context.get_stage()
if not stage:
carb.log_error("No stage is open. Please open your USD file first and run this script again.")
else:
optimizer_interface = None
try:
# Get the optimizer interface.
optimizer_interface = omni.scene.optimizer.core.acquire_interface()
# Create the execution context.
execution_context = omni.scene.optimizer.core.ExecutionContext()
# Get the unique ID of the currently open stage.
stage_id = usd_context.get_stage_id()
execution_context.usdStageId = stage_id
# -----------------------------------------
# Define parameters.
merge_params = {
"merge_materials": True,
"merge_visuals_only": True
}
hidden_mesh_params = {
"operation": "findHiddenMeshes",
"paths": [],
"gridResolution": 100.0,
"action": 0, #This flag '0' sets the mesh to deactivate
"useGpu": False
}
# Max mean error prevents the new mesh surface from deviating more than this amount from the original.
decimation_params = {
"decimation_filter_prim_type": "Mesh",
"max_mean_error": 0.1
}
carb.log_info("Context prepared. Executing operations...")
# --- Execute Step 1: Merge Meshes ---
carb.log_info("Running Step 1: Merge Meshes...")
result = optimizer_interface.execute_operation("merge", execution_context, json.dumps(merge_params))
if not result[0]:
carb.log_error(f"Merge operation failed: {result[1]}")
else:
carb.log_info(f"Merge operation successful. Log: {result[1]}")
# --- Execute Step 2: Find Hidden Meshes ---
carb.log_info("Running Step 2: Find Hidden Meshes...")
result = optimizer_interface.execute_operation("findHiddenMeshes", execution_context, json.dumps(hidden_mesh_params))
if not result[0]:
print("Find hidden meshes operation failed: ", result[1])
else:
print("Find hidden meshes successful. Log: ", result[1])
# --- Execute Step 3: Decimate Meshes by Error ---
carb.log_info("Running Step 3: Decimate Meshes...")
result = optimizer_interface.execute_operation("decimateMeshes", execution_context, json.dumps(decimation_params))
if not result[0]:
carb.log_error(f"Decimate meshes operation failed: {result[1]}")
else:
carb.log_info(f"Decimate meshes successful. Log: {result[1]}")
print("--- Script finished. ---")
运行此脚本后,可以创建七个合并网格。优化过程的第一步是识别数百个共享相同材质的单独网格,并将它们组合成七个高效的网格。这大大减少了渲染器需要处理的对象数量,从而提高了场景性能。
[INFO] merge: Created 7 Merged Meshes
然后,优化器找到了 17 个完全被遮挡的内部网格并将其停用,从而释放了额外的内存和处理资源,而不会影响模型的视觉外观:
[INFO] findHiddenMeshes: Found 17 hidden meshes
通过将模型的几何复杂度降低 82%(就顶点而言),降采样还能提升性能。这些改进使资产能够用于实时应用。
[INFO] decimateMeshes:
VertexCount: 309164 -> 54374 (82.412567)
FaceCount: 253304 -> 106740 (57.860912)
为逼真度分配 vMaterial(材质)
初始转换将 JT 文件中的基本零件颜色映射到简单的 displayColor
属性或基本 UsdPreviewSurface
材质。但是,要实现逼真的效果,需要使用基于物理性质的材质来提升效果。
无需从头开始构建材质,只需在 Kit Material Browser 中利用 NVIDIA vMaterial 库,该库中包含一系列高质量、物理准确的 PBR 材质。这可以通过 USD 分层功能以非破坏性方式完成,并且该功能内置于 Omniverse Kit SDK 中,因此可以轻松地将材质拖放到基于 Kit 的应用程序中的场景中。

运行以下脚本,将铝金属效果应用到 Nova Carter 的身体上:
import omni.usd
from pxr import Sdf, Usd, UsdGeom, UsdShade
import carb
#The full path to the parent prim
TARGET_PRIM_PATH = "/tn__Release20211Carterv20ASM_WPDKOkCO/tn__BodyBoxASM1_lB8CzCl0/tn__BodyBoxEnclosure_qG8"
#The full path to the material prim
MATERIAL_PRIM_PATH = "/tn__Release20211Carterv20ASM_WPDKOkCO/Looks/Aluminum_Brushed_Light_Brushing"
def apply_material_to_prim_and_children(target_path: str, material_path: str):
"""Finds a prim and applies a material to all geometry descendants."""
stage = omni.usd.get_context().get_stage()
parent_prim = stage.GetPrimAtPath(target_path)
material_prim = stage.GetPrimAtPath(material_path)
material_to_bind = UsdShade.Material(material_prim)
# --- Iterate and Bind ---
bound_count = 0
for prim in Usd.PrimRange(parent_prim):
if prim.IsA(UsdGeom.Mesh):
# Bind the specific material object, not the generic prim.
UsdShade.MaterialBindingAPI.Apply(prim).Bind(material_to_bind)
carb.log_info(f"Successfully bound material to: {prim.GetPath()}")
bound_count += 1
if bound_count > 0:
carb.log_info(f"--- Process Complete. Applied material to {bound_count} geometry prim(s). ---")
else:
carb.log_warning(f"--- Script Finished, but no geometry (Gprims) were found under '{target_path}'. ---")
# --- Run the main function with your configuration ---
if __name__ == "__main__":
apply_material_to_prim_and_children(
target_path=TARGET_PRIM_PATH,
material_path=MATERIAL_PRIM_PATH
)

用于性能和协作的有效负载和资产结构
要使资产真正做好生产准备,需要为性能和协作构建场景,而这正是 USD 组合的优势所在。利用组合技术,可以实现高度可扩展的解决方案,将关注点分离成不同的层:一个层用于容纳完整的几何数据,另一个层是一个新的轻量级层,用作资产的接口。
几何图形层 (NovaCarter.usd)
用于转换和优化的文件现在作为专用几何图层发挥着新的作用。它的唯一职责是包含重型视觉数据,即经过优化的网格、复杂的层次结构和保真度变量。
资产接口层 (nova.usd)
然后,创建了一个新的超轻量级文件,在本示例中称为 nova.usd。这是精简资产接口层。
- 用途:此文件的目的是为整个机器人资产提供一个干净的高级接口。它不包含任何繁重数据(实际 3D 模型几何图形和纹理),这意味着它可以即时加载。
- 结构:在这个文件中,定义了一个父 prim(例如 /nova_carter)。该 prim 充当整个机器人的挂钩。然后,将有效负载应用于此 prim,这是一个指向 NovaCarter.usd 文件的特殊指针。
接口层现在充当轻量级代理。用户可以参考 nova.usd,在场景中移动 /nova_carter,而无需支付加载完整机器人几何图形的内存成本,直到最终渲染绝对需要它。
另一个优势是,无法对转换后的 Nova Carter 数据进行破坏性更改,例如删除核心几何图形、覆盖材质分配或更改模型的默认层次结构。
from pxr import Usd, Sdf
# 1. Define the path to your heavy geometry layer.
geometry_layer_path = './NovaCarter.usd'
# 2. Define the name for the new, slim interface layer we are creating.
# Note: make sure to adjust filepaths if you run this yourself
interface_layer_path = 'C:/Users/juslin/Downloads/nova.usd'
# Create a new, empty USD stage for our interface layer
stage = Usd.Stage.CreateNew(interface_layer_path)
# Define the main prim that will act as the "handle" for our robot
robot_prim_path = '/nova_carter'
robot_prim = stage.DefinePrim(robot_prim_path)
# Get the 'payloads' API on the prim
payloads_api = robot_prim.GetPayloads()
# Add the payload reference. This points to the geometry layer but does not
# load it immediately.
payloads_api.AddPayload(assetPath=geometry_layer_path)
print(f"Created interface layer and set payload to '{geometry_layer_path}'.")
# (Optional) Set the default variant to use when the payload is loaded.
variant_set = robot_prim.GetVariantSets().AddVariantSet('fidelity')
variant_set.SetVariantSelection('lightweight')
print("Set default variant to 'lightweight'.")
# Save the new interface layer. It will be a very small text file.
stage.GetRootLayer().Save()
print(f"Slim asset interface layer '{interface_layer_path}' created successfully.")

/nova_carter
作为 OpenUSD Payload 进行管理时,效率和性能会得到提升图 7。当 tg_24 作为 OpenUSD Payload 进行管理时,效率和性能会得到提升图 7。当 tg_24 作为 OpenUSD Payload 进行管理时,效率和性能会得到提升这里的关键优势在于,/nova_carter
作为有效载荷进行管理,在场景需要之前,其繁重的几何图形保持卸载状态。这带来了两大好处:即时提升应用性能,以及更高效的流程。在构建工厂数字孪生等大规模应用中,这种工作流至关重要。单个组件或整个装配线都可以作为有效负载进行管理,确保高性能,并确保团队可以并行工作。
开始使用 OpenUSD
通过新的 OpenUSD 开发认证,在 3D 开发职业生涯中迈出下一步。在 SIGGRAPH 2025 上亲自获得 USD 认证。
在 SIGGRAPH 大会上,探索物理 AI 的未来,参加 NVIDIA Research 主题演讲、OpenUSD Day、动手实验室和开发者见面会等重要活动。
使用开发者入门套件快速开发和增强您自己的应用和服务。
订阅 NVIDIA 新闻,并在 Discord 和 YouTube 上关注 NVIDIA Omniverse,及时了解最新动态。
- 访问我们的 Omniverse 开发者页面,获取开始使用所需的全部基本信息
- 访问一系列 OpenUSD 资源,包括新的自学式 OpenUSD 培训课程
- 观看即将举行的 OpenUSD Insiders 直播,并与 NVIDIA 开发者社区联系