OpenUSD 模式类生成实战:用 usdGenSchema 从 schema.usda 生成 IsA 与 API Schema

发布时间:2026/9/16 15:06:09
OpenUSD 模式类生成实战:用 usdGenSchema 从 schema.usda 生成 IsA 与 API Schema OpenUSD 模式类生成实战用 usdGenSchema 从 schema.usda 生成 IsA 与 API Schema【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD本文基于 OpenUSD 官方教程 Generating New Schema Classes 展开完整覆盖模式Schema类的概念分类、schema.usda编写规范、usdGenSchema生成流程、插件构建与 C/Python 使用方式并结合仓库中 usdGenSchema 脚本源码 和 usdSchemaExamples 示例工程 的生成产物补充命令行参数、生成物结构与运行时注册机制的源码级细节帮助读者从零完成一个可编译、可加载、可注册的自定义 USD 模式插件。什么是 Schema 类Schema 类本质上是一个UsdPrim容器它在底层场景图之上提供了一层具名的、专门的 API。USD 提供了代码生成脚本usdGenSchema源码位于 pxr/usd/usd/usdGenSchema.py用于创建新的 schema 类UsdModelAPI、UsdGeomImageable、UsdGeomMesh等都是用它生成的典型 schema 类仓库中pxr/usd/下的usdGeom、usdRi、usdShade等模块也包含大量此类模式。Schema 类的两种类型模式类分为IsA schema与API schema两大类二者在是否给 prim 赋予typeName上存在本质区别IsA SchemaIsA schema 既能给 prim 赋予typeName也提供对 prim 属性的访问接口。所有 IsA schema 必须继承核心类UsdTyped所有 typed schema 的基类。IsA schema 可以是具体的concrete/instantiable或非具体的abstract当schema.usda中的声明同时提供schema 名称加引号与typeName时该 IsA schema 是具体的可被实例化。例如UsdGeomScope当声明只提供名称而没有typeName时该 IsA schema 是非具体的抽象的不能作为超类服务于一组相关的具体 IsA schema。例如UsdGeomImageable。API SchemaAPI schema 只提供对 prim 属性继承结构、attributes、relationships 等的访问接口不为底层 prim 指定typeName因此被视为非具体的。按惯例API schema 的 C/Python 类名必须以 API 结尾属于 某 API schema 的属性通常以该 schema 基名驼峰化作为命名空间。例如UsdRiMaterialAPI::CreateSurfaceAttr()会创建名为outputs:ri:surface的属性。核心 USD 中的UsdModelAPI是 API schema 的例子RenderMan 模块的UsdRiMaterialAPI则从通用的UsdShadeMaterial类型 prim 上增删 RenderMan 专属着色信息。API schema 进一步细分为两种子类型非应用型Non-appliedAPI schema如果 API schema 只是为某些核心元数据提供接口如设置 model kind 的UsdModelAPI、设置 value-clips 元数据的UsdClipsAPI或者没有必要记录该 API 是否被应用到某个 prim例如为了 interchange就做成非应用型。例子包括UsdModelAPI、UsdClipsAPI、UsdShadeConnectableAPI、UsdGeomPrimvarsAPI。非应用型 API schema 通常可应用于任意 prim 类型如UsdClipsAPI或应用于一组固定的已知 prim 类型如只可应用于具备 connectability 行为的 shader 与 light 的UsdShadeConnectableAPI。应用型AppliedAPI schema如果需要记录并发现某 API schema 是否已应用到 prim 上就做成应用型。其特点应用后schema 会以附加内置属性的形式赋予 prim会为应用型 API schema 自动生成公开的Apply()方法应用之后prim.HasAPIAPISchemaType()返回true必须通过调用Apply()应用后schema 对象经显式 bool 转换才求值为true。例子包括UsdCollectionAPI、UsdGeomModelAPI、UsdGeomMotionAPI。所有 API schema 必须直接继承自UsdAPISchemaBase不能继承自其他 schema。应用型 API schema 又分两类Single-Apply只能以单实例方式应用到 prim 上。虽然 API schema 之间不能相互继承但 single-apply schema 可以把其他 API schema 作为 built-ins 包含进来。例子UsdGeomModelAPI、UsdGeomMotionAPIMultiple-Apply可以带不同的instance name在同一个 prim 上多次应用。例如UsdCollectionAPI必须按 prim 拥有的每个 collection 各应用一次其属性命名空间为schema 命名空间前缀 实例名。需要在schema.usda中书写特定 metadata 来指明 multi-apply 的前缀且 multi-apply schema 的属性应不带前缀列出。从 usdGenSchema 源码 可以确认这些约定对应的 customData 键名customData 键含义apiSchemaTypeAPI schema 类型取值nonApplied/singleApply/multipleApplypropertyNamespacePrefixmulti-apply schema 的属性命名空间前缀apiSchemaAutoApplyTosingle-apply schema 会自动应用到哪些类型化 schemaapiSchemaCanOnlyApplyTo限定 API schema 只能应用的类型apiSchemaAllowedInstanceNames允许使用的实例名apiSchemaInstancesschema 实例声明模式注册表与 fallback 值IsA schema 的定义会在运行时发布到一个可内省的schema definition registry核心 Usd 在执行属性值解析即在给定UsdTime上取属性值时会查询它。这使得 IsA schema 能为其属性提供fallback values——即使没有任何 authored 值属性也会拥有该默认值。API schema 的作者必须在创建时通过 token 值 customDataapiSchemaType决定其类型可取nonApplied、singleApply或multipleApply未指定时默认为singleApply。API schema 只能继承自customData[apiSchemaType]匹配的其他兼容 API schema或直接继承自*/APISchemaBase——这一点由usdGenSchema强制检查。singleApply或multipleApply的 API schema 同样会在运行时填充UsdSchemaRegistry正确应用到 prim 后与 prim 的 IsA 类型一起构成 prim 的UsdPrimTypeInfo完整类型签名用它作为键取出 prim 的UsdPrimDefinition。USD 正是凭此在属性值解析时发现 fallback 值——因此通过应用 API schema可以为任意 prim 增加带 fallback 的内置属性。自定义方法可跨重新生成保留虽然本教程不深入展开但你USD 核心 schema 作者也是这样做的可以为任何生成的 IsA 或 API schema 添加自定义方法重新运行usdGenSchema时这些方法会被保留。自定义方法适合提供计算逻辑或需要一次性协调写入多个属性值的 authoring 操作。前置条件与环境配置Python 依赖与环境变量处理 schema 生成有几个前置要求usdGenSchema依赖jinja2模板替换模块和argparse模块二者必须安装并位于 Python 的sys.path中。若构建/安装 USD 时缺少这些依赖usdGenSchema将不会被安装脚本头部 直接 import jinja2 可见该硬依赖。必须正确配置 Python 环境使其能找到 USD 的 Python 模块。将 USD 安装位置记作USD_INSTALL_ROOT它由构建时的 cmake 参数-DCMAKE_INSTALL_PREFIX决定。更多细节见 构建文档。环境变量含义值PYTHONPATHPython 用于查找模块的路径列表$PYTHONPATH:**USD_INSTALL_ROOT/lib/python/**schema.usda 的硬性要求usdGenSchema由一个 USD layer通常命名为schema.usda驱动。为了让生成的代码成功编译并与 USD 核心配合工作每个schema.usda必须满足必须将libraryName指定为 layer metadatausd/schema.usda必须存在于 layer stack 中不要求是直接 subLayerSchema 类型名在所有库之间必须唯一属性名与 token 必须是驼峰化的合法标识符例外情况及useLiteralIdentifiermetadata 的用法见 schema 生成的 API 文档。基础层Base Layer示例本教程示例使用下面的基础层作为创建新 schema 类的起点以满足上述前两条要求。它对应仓库中的 示例 schema.usda 的头部#usda 1.0 ( This file describes an example schema for code generation using usdGenSchema. subLayers [ # To refer to schema types defined in schema.usda files from other # libraries, simply add comma-separated lines of the form # library name/schema.usda. In this example, were referring # to schema types from usd. If you were adding sub-classes of # UsdGeom schema types, you would use usdGeom/schema.usda instead. usd/schema.usda ] ) over GLOBAL ( customData { string libraryName usdSchemaExamples string libraryPath ./ string libraryPrefix UsdSchemaExamples } ) { }其中subLayers里的usd/schema.usda满足第 2 条要求要引用其他库如usdGeom中定义的 schema 类型只需按library name/schema.usda形式添加逗号分隔的引用行。/GLOBALprim 上的customData提供库级元数据——从 脚本源码 可以看到_GetLibMetadata会读取/GLOBALprim 的 customData 并强制要求libraryName存在缺失时直接抛出异常。三种示例 Schema 的完整定义示例工程中定义了三个 schema 类完整内容见 extras/usd/examples/usdSchemaExamples/schema.usda。示例一Typed、非具体的 IsA Schema一个带一个属性和一个关系的简单非具体 IsA schema primclass SimplePrim ( doc An example of an untyped schema prim. Note that it does not specify a typeName # IsA schemas should derive from /Typed, which is defined in the # sublayer usd/schema.usda. inherits /Typed customData { # Provide a different class name for the C and python schema # classes. This will be prefixed with libraryPrefix. # In this case, the class name becomes UsdSchemaExamplesSimple. string className Simple } ) { int intAttr 0 ( doc An integer attribute with fallback value of 0. ) rel target ( doc A relationship called target that could point to another prim or a property ) }要点该 schema 是 Typed 的但 SimplePrim不能在UsdStage上实例化——它注定是其他具体 schema 的基类它继承/Typed定义在 sublayerusd/schema.usda中customData 的className Simple指定 C/Python 类名会与libraryPrefix前缀拼成UsdSchemaExamplesSimple。这一点可从生成的 simple.h 得到印证class UsdSchemaExamplesSimple : public UsdTyped且其schemaKind静态常量被标记为UsdSchemaKind::AbstractTyped。示例二具体的 IsA Schemaclass ComplexPrim ComplexPrim ( doc An example of a untyped IsA schema prim # Inherits from /SimplePrim defined in simple.usda. inherits /SimplePrim customData { string className Complex } ) { string complexString somethingComplex }这个类型化 IsA schema 类继承上面定义的非具体/SimplePrim指定了具体typeNameComplexPrim并新增一个带 fallback 值的字符串属性。注意类声明形式class ComplexPrim ComplexPrim——第一个是 schema 类标识第二个加引号才是赋予 prim 的 typeName两者同时出现正是具体 IsA schema的标志。示例三应用型 API Schema# API schemas only provide an interface to the prims qualities. # They are not allowed to specify a typeName. class ParamsAPI ( # IsA schemas should derive from /APISchemaBase, which is defined in # the sublayer usd/schema.usda. inherits /APISchemaBase customData { token apiSchemaType singleApply } ) { double params:mass ( # Informs schema generator to create GetMassAttr() instead of # GetParamsMassAttr() method customData { string apiName mass } doc Double value denoting mass ) double params:velocity ( customData { string apiName velocity } doc Double value denoting velocity ) double params:volume ( customData { string apiName volume } doc Double value denoting volume ) }这是一个为三个自定义 double 属性提供操作接口的简单 API schema。注意两点API schema 与 Typed schema 的区别在于它必须继承自/APISchemaBase不允许指定 typeNameapiSchemaType singleApply明确声明其为 single-apply 类型不写时默认也是singleApply属性上的apiName告诉生成器生成GetMassAttr()而不是冗长的GetParamsMassAttr()。运行 usdGenSchema 生成代码三个 schema 类都位于 extras/usd/examples/usdSchemaExamples/schema.usda运行usdGenSchema可生成全部所需文件若文件已存在则原地更新$ usdGenSchema schema.usda . Processing schema classes: SimplePrim, ComplexPrim, ParamsAPI Loading Templates Writing Schema Tokens: unchanged extras/usd/examples/usdSchemaExamples/tokens.h unchanged extras/usd/examples/usdSchemaExamples/tokens.cpp unchanged extras/usd/examples/usdSchemaExamples/wrapTokens.cpp Generating Classes: unchanged extras/usd/examples/usdSchemaExamples/simple.h unchanged extras/usd/examples/usdSchemaExamples/simple.cpp unchanged extras/usd/examples/usdSchemaExamples/wrapSimple.cpp unchanged extras/usd/examples/usdSchemaExamples/complex.h unchanged extras/usd/examples/usdSchemaExamples/complex.cpp unchanged extras/usd/examples/usdSchemaExamples/wrapComplex.cpp unchanged extras/usd/examples/usdSchemaExamples/paramsAPI.h unchanged extras/usd/examples/usdSchemaExamples/paramsAPI.cpp unchanged extras/usd/examples/usdSchemaExamples/wrapParamsAPI.cpp unchanged extras/usd/examples/usdSchemaExamples/plugInfo.json Generating Schematics: unchanged extras/usd/examples/usdSchemaExamples/generatedSchema.usda从 参数解析源码 可以核对usdGenSchema的完整命令行接口参数默认值说明schemaPath./schema.usda定义 schema 类的源 USD 文件codeGenPath.代码生成的目标目录-v, --validate关校验源文件是否未发生变化有 diff 即失败适合放进 CI-q, --quiet关执行期间不输出文本-n, --namespace无将代码包裹在指定命名空间中多个参数视为嵌套命名空间-t, --templates脚本旁/插件目录的codegenTemplates覆盖模板目录-hts, --headerTerminatorString#endif自定义代码区之后的头文件终止串此外生成入口 会检查目标目录是否存在__init__.py、CMakeLists.txt、module.cpp三个构建支撑文件缺失时会提示先用usdInitSchema初始化构建脚手架——示例工程里这三个文件齐全见 CMakeLists.txt。脚本启动时还会设置环境变量USD_DISABLE_PRIM_DEFINITIONS_FOR_USDGENSCHEMA1见 源码 L43禁用 schema registry 在生成期间加载generatedSchema.usda从而保证即使生成了格式不佳的文件也能用该工具本身去修复。生成产物结构tokens.h / tokens.cpp / wrapTokens.cppschema 相关 token 的 C 与 Python 绑定每类一对class.h/.cpp加wrapClass.cpp如 simple.h、complex.h、paramsAPI.hwrap*.cpp是 Python 绑定plugInfo.json插件注册文件。查看 示例的 plugInfo.json其中每个生成类型都带schemaKind标记——UsdSchemaExamplesSimple是abstractTyped、UsdSchemaExamplesComplex是concreteTyped、UsdSchemaExamplesParamsAPI是singleApplyAPI并记录了继承基类bases与UsdSchemaBase别名generatedSchema.usda供运行时注册用的生成图层。查看 示例产物 可见它是只读生成文件文件头标注 DO NOT EDIT继承关系被压平ComplexPrim内联了父类的intAttr与targetdoc被转换为截断后的userDocBriefcustomData。另外生成的头文件尾部保留了固定的自定义代码区simple.h 中可以看到 Feel free to add custom code below this line, it will be preserved by the code generator 的注释块与--(BEGIN CUSTOM CODE)--标记——这正是自定义方法可跨重新生成保留机制的落点。构建插件USD 安装位置中包含带构建结果的 build 目录。重建插件只需进入 USD build 目录根部install location/build/USD运行cmake --build . --target install --config Release示例插件的构建由 CMakeLists.txt 描述通过pxr_plugin宏声明usdSchemaExamples包链接tf、sdf、usd、vt四个库并使用INCLUDE_SCHEMA_FILES将 schema 文件打入资源目录。使用 Schema 类注册外部插件由于该 schema 是外部插件在使用前必须先告诉 USD 构建到哪里找它两种方式任选其一将PXR_PLUGINPATH_NAME环境变量设置为插件的resources目录位置。例如从 USD 源码树构建usdSchemaExamples插件时位置为prefix/share/usd/examples/plugin/usdSchemaExamples/resources或者把usdSchemaExamples.soWindows 上是usdSchemaExamples.dll与.lib以及usdSchemaExamples目录复制到prefix/plugin/usd。Windows 注意在 Python 3.8 上运行时可能遇到 ImportError DLL load failed原因是 DLL 目录未被加入可信位置。解决办法是在 import 前手动执行os.add_dll_directory(/path/to/plugin/resources/directory)。编写测试场景 Test.usda创建名为Test.usda的 usd 文件内容如下#usda 1.0 def ComplexPrim Complex { string complexString a really complex string int intAttr 10 add rel target /Object } def Xform Object ( prepend apiSchemas [ParamsAPI] ) { custom double params:mass 1.0; custom double params:velocity 10.0; custom double params:volume 4.0; }该文件应能在 usdview 中加载且无警告或错误。注意/Object上通过prepend apiSchemas [ParamsAPI]应用了 API schema三个custom属性命名空间与 schema 定义中的params:*前缀对应。C 使用示例以下 C 代码加载上面的测试场景构造 schema prim 并使用 schema 代码生成提供的 APIUsdStageRefPtr stage UsdStage::Open(/path/to/testenv/Test.usda); UsdPrim cp stage-GetPrimAtPath(/Complex); UsdSchemaExamplesSimple simple(cp); UsdRelationship target simple.GetTargetRel(); UsdAttribute intAttr simple.GetIntAttrAttr(); UsdSchemaExamplesComplex complex(cp); std::cout complex.GetComplexStringAttr().Getstring() std::endl; UsdPrim obj stage-GetPrimAtPath(/Object); UsdSchemaExamplesParamsAPI paramsAPI UsdSchemaExamplesParamsAPI::Apply(obj); assert(obj.HasAPIUsdSchemaExamplesParamsAPI()); std::cout mass: paramsAPI.GetMassAttr().Getdouble() std::endl; std::cout velocity: paramsAPI.GetVelocityAttr().Getdouble() std::endl; std::cout volume: paramsAPI.GetVolumeAttr().Getdouble() std::endl;可以看到生成的访问器命名规则intAttr属性 →GetIntAttrAttr()target关系 →GetTargetRel()apiName重写的params:mass→GetMassAttr()应用型 schema 通过静态Apply()应用并用HasAPI验证。Python 使用示例from pxr import Usd, UsdSchemaExamples stage Usd.Stage.Open(Test.usda) cp stage.GetPrimAtPath(/Complex) simple UsdSchemaExamples.Simple(cp) target simple.GetTargetRel() intAttr simple.GetIntAttrAttr() complex UsdSchemaExamples.Complex(cp) print(complexString: %s % complex.GetComplexStringAttr().Get()) obj stage.GetPrimAtPath(/Object) paramsAPI UsdSchemaExamples.ParamsAPI.Apply(obj) assert obj.HasAPI(UsdSchemaExamples.ParamsAPI) print(mass: %s % paramsAPI.GetMassAttr().Get()) print(velocity: %s % paramsAPI.GetVelocityAttr().Get()) print(volume: %s % paramsAPI.GetVolumeAttr().Get())Python 侧通过pxr.UsdSchemaExamples模块访问对应示例工程中的__init__.py与wrap*.cpp绑定接口与 C 一一对应注意HasAPI传入类型对象而非模板参数。Codeless Schemas免编译的动态模式客户端还有另一种选择完全不为某个 schema 生成代码——为其设置skipCodeGenerationmetadata 为 True这样usdGenSchema将只生成generatedSchema.usda与plugInfo.json而这两者正是运行时 schema 注册所必需的产物对应 源码中skipCodeGen的分支跳过 token 与类代码生成但GeneratePlugInfo与GenerateRegistry始终执行。由于 codeless schema 不提供任何代码客户端无需重新编译 USD即可使用或更新这些 schema。这种动态特性正是使用 codeless schema 的核心动机——适合迭代频繁、希望以纯图层方式交付模式的场景。小结Schema 类是对UsdPrim的类型化封装分为 IsA可赋予 typeName继承UsdTyped与 API不提供 typeName继承UsdAPISchemaBase类名以 API 结尾两大族API schema 需经apiSchemaType声明为nonApplied/singleApply/multipleApply默认 single-applysingle/multi-apply 类型参与运行时UsdSchemaRegistry使 API 应用后的 prim 获得带 fallback 的内置属性编写schema.usda的四条硬性要求libraryName、usd/schema.usda在 stack 中、typeName 全局唯一、驼峰化标识符是代码可编译的前提usdGenSchema schema.usda .一次生成 tokens、类头源文件、Python 绑定、plugInfo.json与generatedSchema.usda-v校验模式可保证生成物不漂移生成文件中的自定义代码区可安全承载手工方法插件通过cmake --build . --target install --config Release构建再用PXR_PLUGINPATH_NAME或拷贝到prefix/plugin/usd完成注册对不需要 C 代码的模式用skipCodeGeneration生成 codeless schema实现免编译更新。仓库内可继续深入的材料教程原文、示例 schema 定义、生成器源码、插件构建脚本 与 构建说明。【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考