OpenUSD PrimHints 权威指南:用 uiHints 控制 Prim 在 UI 中的显示分组与条件展示
2026/9/17 7:45:35 网站建设 项目流程

OpenUSD PrimHints 权威指南:用 uiHints 控制 Prim 在 UI 中的显示分组与条件展示

【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD

PrimHints 是 OpenUSD(Universal Scene Description)中一类面向 Prim 级别的 UI 提示(UI Hints)机制,用于告诉 DCC 工具或应用程序:Prim 下的属性应当如何按"显示分组(display groups)"组织、哪些分组默认展开或折叠、以及哪些分组在什么条件下才显示。本文将以 docs/user_guides/schemas/usdUI/PrimHints.md 为骨架,结合仓库中pxr/usd/usdUI的源码实现与测试用例,系统讲解 PrimHints 的字段语义、USDA 编写方式、Python API 用法,以及与ObjectHintsPropertyHints的协作关系。读完本文,你将能够为任意 Prim 编写一套可被工具直接消费的 UI 呈现规范。

图注:一个 mock 场景浏览器中应用 PrimHints 后的 UI 效果示意,其中 "Controller" 分组默认展开,"Widget Settings" 分组默认折叠(详见下文示例)。

PrimHints 的定位:UI Hints 体系中的 Prim 级入口

在 OpenUSD 的 UsdUI 域中,UI Hints 是一组"类 schema"(schema-like)的元数据约定,它们统一存放在对象的uiHints字典元数据(metadata)中,用于描述一个对象在 UI 中的呈现方式。UI Hints 按作用对象划分为四层,见 docs/user_guides/schemas/usdUI/overview.md:

  • ObjectHints:适用于任何 Prim 或属性(Property)的通用提示,如用户可见的displayName、是否在 UI 中隐藏的hidden
  • PrimHints(本文主题):Prim 级提示,例如displayGroupsExpanded(哪些显示分组默认展开)与displayGroupsShownIf(哪些显示分组在什么条件下才显示)。
  • PropertyHints:属性级提示,如属性归属的displayGroup、控制属性是否显示的shownIf表达式。
  • AttributeHints:属性值级提示,如枚举值标签valueLabels及其顺序valueLabelsOrder

从源码看,PrimHints 对应 C++ 类UsdUIPrimHints,定义于 pxr/usd/usdUI/primHints.h,其继承自UsdUIObjectHints(pxr/usd/usdUI/objectHints.h),即 Prim 天然同时具备 ObjectHints 的能力。UsdUIPrimHints的注释明确指出:它是"schema-like"包装器,解释UsdPrim实例上uiHints字典中的字段并提供便捷 API,并非正式 schema,不派生自UsdSchemaBase

文档 PrimHints.md 开头也强调:该文件不是自动生成的(文件头部注释注明 "This file isnotgenerated, as UIHints are not defined in schema.usda")。也就是说,displayGroupsExpandeddisplayGroupsShownIf这类 PrimHints 字段并未像普通 schema 属性那样定义在 pxr/usd/usdUI/schema.usda 中,而是由UsdUIPrimHints等 API 按约定解释uiHints字典键值。字典键的 token 定义在 pxr/usd/usdUI/objectHints.h 的USDUI_HINT_KEYS宏中,包括uiHintsdisplayNamedisplayGrouphiddenshownIfvalueLabelsvalueLabelsOrderdisplayGroupsExpandeddisplayGroupsShownIf

一个直观的最小示例

PrimHints 文档给出了如下示例:一个名为ControllerA的 Prim,拥有多个属性,并分属两个显示分组 "Controller" 和 "Widget Settings"。Prim 级uiHints指出:DCC 工具初始应完整展开 "Controller" 分组、不展开 "Widget Settings" 分组;且 "Widget Settings" 仅在表达式widgetReadOnlyMode == 0求值为真时才显示。

def "ControllerA" ( uiHints = { # UI hints from ObjectHints string displayName = "ControllerA" bool hidden = 0 dictionary displayGroupsExpanded = { bool "Controller" = 1 bool "Widget Settings" = 0 } dictionary displayGroupsShownIf = { string "Widget Settings" = "widgetReadOnlyMode == 0" } } ) { float controlValue = 1.0 ( uiHints = { string displayName = "value" string displayGroup = "Controller" } ) bool controlIsOffset = false ( uiHints = { string displayName = "is offset" string displayGroup = "Controller" } ) bool showWidget = true ( uiHints = { string displayName = "show" string displayGroup = "Widget Settings" } ) color3f[] widgetColor = (1.0, 0.5, 0.5) ( uiHints = { string displayName = "color" string displayGroup = "Widget Settings" } ) float widgetSize = 10.0 ( uiHints = { string displayName = "size" string displayGroup = "Widget Settings" } ) bool widgetReadOnlyMode = 0 }

注意:displayNamehidden属于 ObjectHints(在uiHints字典内以注释标明来源);displayGroup属于 PropertyHints,标注每个属性归入哪个显示分组。PrimHints 的两组字典则控制这些分组在 UI 中的整体呈现。文档同时给出了该 Prim 在场景浏览器中的 mock 效果图(uihints-primhints.svg),其中widgetReadOnlyModefalse(即 0)时,"Widget Settings" 分组可见。

PrimHints 字段详解

displayGroupsExpanded

USD 类型dictionary

以显示分组名称为键的字典,指示各显示分组在 UI 中默认是展开(1/true)还是折叠(0/false)。未在字典中列出的分组,工具可自行决定默认状态(见下文源码默认值分析)。

对应 C++ API 位于 pxr/usd/usdUI/primHints.h:

  • VtDictionary GetDisplayGroupsExpanded() const:返回整个展开字典;
  • bool SetDisplayGroupsExpanded(const VtDictionary& expanded):整体写入展开字典;
  • bool GetDisplayGroupExpanded(const std::string& group) const:查询单个分组是否默认展开;
  • bool SetDisplayGroupExpanded(const std::string& group, bool expanded):设置单个分组的展开状态。

从 pxr/usd/usdUI/primHints.cpp 的实现可以看到几个关键细节:

  1. 读取通过_prim.GetMetadataByDictKey(UsdUIHintKeys->UIHints, UsdUIHintKeys->DisplayGroupsExpanded, &dict)uiHints字典中按键取子字典;若未authoring,返回空字典。
  2. 写入校验SetDisplayGroupsExpanded会遍历字典所有条目,若存在非bool值则通过TF_CODING_ERROR报错并拒绝写入(测试用例 testUsdUIHints.py 中也验证了传非 bool 值的错误路径)。
  3. 单分组查询默认值GetDisplayGroupExpanded使用VtDictionaryGet<bool>(expandedDict, group, VtDefault = false),即未authoring 的分组默认按false(折叠)处理。测试同样断言了GetDisplayGroupExpanded('non-existent')返回False(见 testUsdUIHints.py)。
  4. 嵌套分组的扁平化存储SetDisplayGroupExpanded特意不使用SetMetadataByDictKey直接写单键,而是"先读整字典、改条目、再整字典写回"。源码注释解释得很清楚:因为分组名本身可能含冒号分隔符(如"A:B:C"),直接按字典键写入会把它们变成嵌套子字典;而我们希望所有分组条目平铺在displayGroupsExpanded顶层
dictionary displayGroupsExpanded = { bool "A" = 1 bool "A:B" = 1 bool "A:B:C" = 1 }

而不是:

dictionary displayGroupsExpanded = { dictionary A = { dictionary B = { bool C = 1 } } }
  1. 组合语义:该字段是字典值,其合成值(composed value)是所有相关编辑目标(edit targets)中条目级(per-entry)覆盖合并的结果,而非整个字典整体覆盖。这一语义同样适用于displayGroupsShownIf,在头文件注释中明确说明。

displayGroupsShownIf

USD 类型dictionary

以显示分组名称为键的字典,键对应的值是基于SdfBooleanExpression(pxr/usd/sdf/booleanExpression.h)的表达式字符串。表达式求值为真时,对应显示分组才在 UI 中显示。

对应 C++ API(pxr/usd/usdUI/primHints.h):

  • VtDictionary GetDisplayGroupsShownIf() const
  • bool SetDisplayGroupsShownIf(const VtDictionary& shownIf)
  • std::string GetDisplayGroupShownIf(const std::string& group) const
  • bool SetDisplayGroupShownIf(const std::string& group, const std::string& shownIf)

primHints.cpp 中SetDisplayGroupsShownIf的写入校验要求所有值必须是std::string,否则TF_CODING_ERROR拒绝写入。测试用例 testUsdUIHints.py 中同样覆盖了"给DisplayGroupsShownIf传 int 值报错"的场景。

displayGroupsExpanded不同,GetDisplayGroupShownIf/SetDisplayGroupShownIf的单分组读写使用_MakeKeyPath(UsdUIHintKeys->DisplayGroupsShownIf, TfToken(group))拼接键路径(_MakeKeyPath以命名空间分隔符:连接,见 pxr/usd/usdUI/objectHints.h),即直接按displayGroupsShownIf:<group>字典键读写。测试验证:未设置的分组GetDisplayGroupShownIf返回空字符串''

显示分组(Display Groups)与条件表达式的完整语义

PrimHints 并非孤立存在,它与显示分组机制、布尔表达式机制紧密耦合。以下语义出自 overview.md 的 "Display Groups and Property Order" 与 "Working With Conditional UI Hints" 两节,是理解 PrimHints 两字段的前提。

分组归属与嵌套

  • 属性通过 PropertyHints 的displayGroup声明自己归属的显示分组(PropertyHints.md),一个属性只能属于一个分组。
  • 分组可嵌套:分组名中使用:分隔符,例如"GroupA:NestedGroup"表示NestedGroupGroupA的子分组。
  • 分组是可选的,但能帮助工具把相关属性聚合展示、提升操作效率。

Python API 设置分组归属:

property = prim.GetProperty("myProperty") hints = UsdUI.PropertyHints(property) hints.SetDisplayGroup("Custom Properties")

分组呈现由 Prim 控制

Prim 通过displayGroupsExpandeddisplayGroupsShownIf控制分组在 UI 中的呈现,这正是 PrimHints 的核心职责。一个更丰富的综合示例(来自 overview.md):

def "TreeA" ( uiHints = { string displayName = "Tree template" dictionary displayGroupsExpanded = { bool "Trunk settings" = 1 bool "Body settings" = 1 bool "Body settings:Branch settings" = 0 bool "Body settings:Leaf settings" = 1 } dictionary displayGroupsShownIf = { string "Body settings:Leaf settings" = "trunkSize != 1" } } ) { color3f trunkColor = (0.6, 0.3, 0.0) ( uiHints = { string displayName = "color" string displayGroup = "Trunk settings" } ) int trunkSize = 2 ( uiHints = { string displayName = "size" string displayGroup = "Trunk settings" dictionary valueLabels = { int huge = 3 int sapling = 1 int standard = 2 } token[] valueLabelsOrder = ["sapling", "standard", "huge"] } ) float bodyRadius = 5.0 ( uiHints = { string displayName = "size" string displayGroup = "Body settings" } ) float branchDensity = 1.0 ( uiHints = { string displayName = "density" string displayGroup = "Body settings:Branch settings" } ) float branchLength = 5.0 ( uiHints = { string displayName = "length" string displayGroup = "Body settings:Branch settings" } ) color3f leafColor = (0.4, 0.7, 0.25) ( uiHints = { string displayName = "color" string displayGroup = "Body settings:Leaf settings" } ) float leafComplexity = 1.0 ( uiHints = { string displayName = "complexity" string displayGroup = "Body settings:Leaf settings" string shownIf = "leafStyle == 1" } ) int leafStyle = 1 ( uiHints = { string displayName = "style" string displayGroup = "Body settings:Leaf settings" dictionary valueLabels = { int acute = 1 int obtuse = 2 int truncate = 3 } token[] valueLabelsOrder = ["acute", "obtuse", "truncate"] } ) string tempNotes = "" ( uiHints = { bool hidden = 1 } ) reorder properties = ["trunkColor", "trunkSize", "bodyRadius", "branchDensity", "branchLength", "leafColor", "leafStyle", "leafComplexity"] }

这个例子展示了 UI Hints 四层协同:ObjectHints(displayNamehidden)、PropertyHints(displayGroupshownIf)、AttributeHints(valueLabelsvalueLabelsOrder)与 PrimHints(displayGroupsExpandeddisplayGroupsShownIf)。其中"Body settings:Leaf settings"分组同时受displayGroupsShownIf(表达式trunkSize != 1)和组内属性leafComplexityshownIfleafStyle == 1)双重条件控制。该示例的 UI mock 见 uihints-example-mock.svg。

属性顺序对分组的影响

Prim 通过reorder propertiesUsd.Prim.SetPropertyOrder()控制属性在 UI 中的顺序。带显示分组时,工具应"按属性首次引用分组的位置排列分组,并按 Prim 属性顺序排列组内属性"。例如:

def "PropertyOrderPrimWithDisplayGroups" ( uiHints = { string displayName = "Example" dictionary displayGroupsExpanded = { bool "Group A" = 1 bool "Group B" = 1 } } ) { reorder properties = ["attribute4", "attribute2", "attribute1", "attribute3"] int attribute1 = 1 ( uiHints = { string displayGroup = "Group B" } ) int attribute2 = 2 int attribute3 = 3 ( uiHints = { string displayGroup = "Group B" } ) int attribute4 = 4 ( uiHints = { string displayGroup = "Group A" } ) }

其 UI 呈现顺序的 mock 图见 uihints-propertyorder.svg。另外注意:旧的displayGroupOrderPrim 元数据字段已废弃,不应与属性顺序/分组相关 UI Hints 混用。

布尔表达式语法与求值语义

displayGroupsShownIf的值(以及 PropertyHints 的shownIf)是布尔表达式字符串,求值基于SdfBooleanExpression。表达式通常用于测试包含该 Prim 的某个属性的合成值(resolved value)。支持的操作符(见 overview.md 与 booleanExpression.h 中的运算符枚举文档):

操作符含义
==等于
!=不等于
<小于
<=小于等于
>大于
>=大于等于
&&逻辑与
\|\|逻辑或
!一元逻辑非

表达式支持一元!与括号分组。例如!(status == "active" || level > 5)表示:仅当status不等于"active"level小于等于 5 时才显示。

条件表达式示例(overview.md):

def "PrimUsingExpressions" ( uiHints = { dictionary displayGroupsShownIf = { string "Deformation parameters" = "materialHardness <= 2.0" } } ) { float bendAmount = 0.0 ( uiHints = { string displayGroup = "Deformation parameters" string displayName = "Bend amount" } ) float bendDirection = 0.0 ( uiHints = { string displayGroup = "Deformation parameters" string displayName = "Bend direction" } ) float fractureAmount = 0.0 ( uiHints = { string displayGroup = "Deformation parameters" string displayName = "Fracture amount" string shownIf = "isFractured == true" } ) float materialHardness = 10.0 bool isFractured = false }

这里"Deformation parameters"分组的显示条件是materialHardness <= 2.0,而组内fractureAmount属性还要满足isFractured == true才会显示。

visibility 判定规则:对象级hidden提示始终与shownIf一起参与判定。即属性在 UI 中可见,当且仅当shownIf表达式求值为真hidden不为真。这一规则同样适用于分组:displayGroupsShownIf表达式为假,或分组(Prim)的hidden为真,都会导致分组不可见。

用 API 读写 PrimHints(C++ / Python)

C++ API

构造:UsdUIPrimHints hints(prim);,读取/写入见上文字段对应 API。要点:

  • 未authoring 时,GetDisplayGroupsExpanded()返回空VtDictionaryGetDisplayGroupExpanded(group)返回false(默认折叠),GetDisplayGroupShownIf(group)返回空字符串。
  • 写入字典时,displayGroupsExpanded要求全部bool值、displayGroupsShownIf要求全部string值,否则报TF_CODING_ERROR
  • 字典字段的合成值按条目覆盖(per-entry override)合并,跨编辑目标时不是整体替换。

Python API

UI Hints 的 Python 绑定在 pxr/usd/usdUI 目录的wrap*文件中(如 wrapPrimHints.cpp)。典型用法:

from pxr import Usd, UsdUI stage = Usd.Stage.CreateInMemory() prim = stage.DefinePrim("/MyPrim") hints = UsdUI.PrimHints(prim) hints.SetDisplayGroupsExpanded({"Controller": True, "Widget Settings": False}) hints.SetDisplayGroupShownIf("Widget Settings", "widgetReadOnlyMode == 0") print(hints.GetDisplayGroupExpanded("Controller")) # True print(hints.GetDisplayGroupShownIf("Widget Settings")) # "widgetReadOnlyMode == 0"

推荐使用 API 而非直接读写uiHints元数据:API 在未authoring 时提供合理的回退值。overview.md 中给出的例子:对一个uiHints为空的 Prim,UsdUI.ObjectHints(prim).GetDisplayName()返回空字符串,而直接prim.GetMetadata("uiHints").get("displayName")返回None。只有自定义 UI hint 键等场景才需要直接访问uiHints字典。

兼容性与迁移注意

overview.md 明确指出:displayNamehiddendisplayGroup过去是独立的元数据字段(通过UsdObject/UsdProperty提供),现已废弃。UI Hints API 在uiHints字典中未authoring 对应值时,会回退查找这些旧字段以保持向后兼容;但新内容不应再author 这些独立字段,应统一使用 UI Hints API 或uiHints字典。这一回退行为在 objectHints.h 与 propertyHints.h 的注释中被标注为"临时的,将在未来版本移除"。

测试验证:从 testUsdUIHints.py 看行为契约

仓库测试 pxr/usd/usdUI/testenv/testUsdUIHints.py 直接覆盖了 PrimHints 的关键行为契约:

  • GetDisplayGroupsExpanded()返回与写入一致的字典;GetDisplayGroupExpanded(k)对每个键返回对应值;对不存在的分组'non-existent'返回False(默认折叠)。
  • GetDisplayGroupsShownIf()/GetDisplayGroupShownIf(k)同理,不存在的分组返回空字符串''
  • SetDisplayGroupsShownIf传入非字符串值(如{'group': 5})会触发错误拒绝写入。
  • group:subgroup这类嵌套分组名,SetDisplayGroupExpanded('group', True)GetDisplayGroupExpanded('group')GetDisplayGroupExpanded('group:subgroup')均独立生效,且整字典按平铺结构读取——印证了上文"嵌套分组名平铺存储"的实现细节。
  • 未authoring 的 Prim,其 PrimHints 相关读取均返回空字典/False/空字符串,验证了默认回退行为。

测试同时覆盖了 ObjectHints(displayNamehidden)与 PropertyHints(displayGroupshownIf)的回退与读写,可用于在改动 UI Hints 行为后做回归验证。

小结

PrimHints 通过displayGroupsExpandeddisplayGroupsShownIf两个字典字段,把"显示分组如何呈现"的决定权交给数据本身:前者声明各分组默认展开/折叠状态,后者用SdfBooleanExpression表达式条件化控制分组可见性。它与 ObjectHints(displayName/hidden)、PropertyHints(displayGroup/shownIf)、AttributeHints(valueLabels/valueLabelsOrder)共同构成完整的 UI Hints 体系,全部承载在uiHints字典元数据中,由UsdUIPrimHints等类 schema API 解释读写。

需要强调的边界:UI Hints 本质是"建议"(suggestions),最终呈现方式由消费它的工具或应用程序决定(overview.md 明确说明)。因此,在 DCC 工具、资产浏览器或节点编辑器中消费这些提示时,应把 PrimHints 作为默认布局的输入,同时允许用户覆盖。本文所有字段语义、默认值(未设置分组默认折叠、未设置表达式默认显示)、类型校验与兼容回退行为,均可在 primHints.h、primHints.cpp 与 testUsdUIHints.py 中找到直接依据。

【免费下载链接】OpenUSDUniversal Scene Description项目地址: https://gitcode.com/GitHub_Trending/ope/OpenUSD

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询