NOTE: This contains tests that show behavior changes introduced in newer versions of USD.
These tests focus on demonstrating valid and invalid uses of unencapsulated relationship targets. A relationship target is unencapsulated when the target prim path is outside of the scope of a reference target prim and its descendants. When these sorts of relationships are referenced, the composition engine is unable to resolve and update the prim path of the relationship target because the target no longer exists in the new context. This can happen due to:
When you add a reference with an unencapsulated relationship, you will see an error like this in the console:
Warning (secondary thread): in _ReportErrors at line 3157 of W:\257786efc4f464db\USD\pxr\usd\usd\stage.cpp -- In </World/washer.material:binding>: The relationship target </World/Looks/metal> from </World/Geometry/washer.material:binding> in layer @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/ExternalReferenceBadTargetTest/washer.usda@ refers to a path outside the scope of the reference from </World/washer>. Ignoring. (getting targets for relationship </World/washer.material:binding> on stage @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/ExternalReferenceBadTargetTest.usda@ <0000027BA0602B10>)
These tests all use the material:binding
relationship for demonstration since it is a very common property in OpenUSD and often where the illustrated issues will be found, but this applies to all relationships. An added benefit of using material bindings for these tests is that we can visually validate the issue because any unencapsulated binding targets will be discarded and the renderer will show the mesh with no material.
The ExternalReferenceBadTargetTest.usda
stage references a washer.usda
component asset with a valid asset structure. It defines a default prim, World
, so that when this layer is referenced using the default prim, it properly brings along its geometry and materials. This test illustrates an issue where an end-user of the asset decided to only reference a specific mesh from the asset and causes a broken relationship for the bound material on the mesh since the material is not referenced.
washer.usda
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) def Xform "World" ( kind = "component" ) { def Scope "Looks" { def Material "metal" { ... } } def Scope "Geometry" { def Mesh "washer" ( active = true prepend apiSchemas = ["MaterialBindingAPI"] ) { # Relationship to a properly encapsulated prim. rel material:binding = </World/Looks/metal> ( bindMaterialAs = "weakerThanDescendants" ) ... } } }
Stage
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) def Xform "World" { def "washer" ( # Only references the mesh and leaves out the material # causing an unencapsulated relationship as the material:binding # now points to a prim outside of the referenced hierarchy. prepend references = @./ExternalReferenceBadTargetTest/washer.usda@</World/Geometry/washer> ) { } }
Example Console Output
Warning (secondary thread): in _ReportErrors at line 3157 of W:\257786efc4f464db\USD\pxr\usd\usd\stage.cpp -- In </World/washer.material:binding>: The relationship target </World/Looks/metal> from </World/Geometry/washer.material:binding> in layer @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/ExternalReferenceBadTargetTest/washer.usda@ refers to a path outside the scope of the reference from </World/washer>. Ignoring. (getting targets for relationship </World/washer.material:binding> on stage @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/ExternalReferenceBadTargetTest.usda@ <0000027BA0602B10>)
NOTE: This test shows behavior changes introduced in newer versions of USD (23.05+).
The InternalReferenceTest.usda
stage is an assembly asset with some prototype component assets defined inline. These prototype assets are then used as internal references and potentially natively instanced as a workflow and performance optimization. This is a useful technique for DCCs that may want to export an assembly asset as a single layer, but still identify component assets within the assembly say a car from a CAD application with fully modeled hardware that is repeated many times with the car asset.
This can get tricky when a developer wants to share one material across multiple component assets (e.g. a copper metal material shared between bolts and washers). One valid, but tedious approach is to bind the shared material after the prototypes have been referenced within the assembly.
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) class Scope "Prototypes" { def Xform "bolt" (kind = "component") { ... } } def Xform "World" (kind = "assembly") { def Scope "Looks" { def Material "metal" { ... } } def "bolt_01" (prepend references = </Prototypes/bolt>) { def Mesh "bolt" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { rel material:binding = </World/Looks/metal> ( bindMaterialAs = "weakerThanDescendants" ) } } def "bolt_02" (prepend references = </Prototypes/bolt>) { def Mesh "bolt" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { rel material:binding = </World/Looks/metal> ( bindMaterialAs = "weakerThanDescendants" ) } } }
Another approach would be to create an unencapsulated relationship for the material bindings where the component assets are defined.
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) class Scope "Prototypes" { def Xform "bolt" (kind = "component") { def Mesh "bolt" ( prepend apiSchemas = ["MaterialBindingAPI"] ) { # This relationship is reaching outside of the # component model encapsulation. rel material:binding = </World/Looks/metal> ( bindMaterialAs = "weakerThanDescendants" ) ... } } } def Xform "World" ( kind = "assembly" ) { def Scope "Looks" { def Material "metal" { ... } } def "bolt_01" (prepend references = </Prototypes/bolt>) { ... } def "bolt_02" (prepend references = </Prototypes/bolt>) { ... } }
This is bad, right? Well, as of USD 23.05 unencapsulated relationships are allowed for internal references. In this test, we will show the expected results for USD version before 23.05 and 23.05+.
Example Console Output
Warning (secondary thread): in _ReportErrors at line 2874 of W:\e7f6a7475fd8ed53\USD\pxr\usd\usd\stage.cpp -- In </World/bolt_02/bolt.material:binding>: The relationship target </World/Looks/metal> from </Prototypes/bolt/bolt.material:binding> in layer @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/InternalReferenceTest.usda@ refers to a path outside the scope of the reference from </World/bolt_02>. Ignoring. (getting targets for relationship </World/bolt_02/bolt.material:binding> on stage @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/InternalReferenceTest.usda@ <000001C7DC011F70>)
NOTE: This test shows behavior changes introduced in newer versions of USD (23.05+).
This is similar to the regular internal reference example, but extends the example to utilize sublayers to show that the unencapsulated material binding in an internal reference is respected for the whole LayerStack as of 23.05. The example shows an assembly where the developer wanted to separate the modeling and shading work into sublayers. The component model prototypes and the assembly layout are defined in a *.modeling.usda
sublayer and a shared material is created and bound to the prototypes in a *.shading.usda
sublayer.
Example Console Output
Warning (secondary thread): in _ReportErrors at line 2874 of W:\e7f6a7475fd8ed53\USD\pxr\usd\usd\stage.cpp -- In </World/bolt_02/bolt.material:binding>: The relationship target </World/Looks/metal> from </Prototypes/bolt/bolt.material:binding> in layer @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/SublayeredInternalReferenceTest/hardware.shading.usda@ refers to a path outside the scope of the reference from </World/bolt_02>. Ignoring. (getting targets for relationship </World/bolt_02/bolt.material:binding> on stage @c:/code_pub/assets/test_assets/RelationshipEncapsulationTests/SublayeredInternalReferenceTest.usda@ <00000289F3B12090>)
This builds from the regular internal reference example, but asks the question, “What happens if the prototypes are defined outside of the defaultPrim
? Do the internal references to the prototypes break when the assembly is externally referenced?” This is not a question of unencapsulated relationships, but unencapsulated internal references.
InteralReferenceEncapsulatedPrototypes.usda
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) def Xform "World" ( kind = "assembly" ) { def Scope "Looks" { def Material "metal" { ... } } # References prototype underneath the defaultPrim def "bolt_01" (prepend references = </World/Prototypes/bolt>) { ... } def "bolt_02" (prepend references = </World/Prototypes/bolt>) { ... } class Scope "Prototypes" { def Xform "bolt" (kind = "component") { ... } } }
InteralReferenceUnencapsulatedPrototypes.usda
#usda 1.0 ( defaultPrim = "World" metersPerUnit = 0.01 upAxis = "Z" ) def Xform "World" ( kind = "assembly" ) { def Scope "Looks" { def Material "metal" { ... } } # References prototype outside of the defaultPrim def "bolt_01" (prepend references = </Prototypes/bolt>) { ... } def "bolt_02" (prepend references = </Prototypes/bolt>) { ... } } class Scope "Prototypes" { def Xform "bolt" (kind = "component") { ... } }
As expected, the unencapsulated material binding in the internal references will not work in USD versions older than 23.05, but the unencapsulated internal references (i.e. referencing a prototype prim outside of the default prim) did not break, so this case is marked Valid
.
These assets are provided under the Apache 2.0 license.
#usda 1.0
(
defaultPrim = "World"
endTimeCode = 100
metersPerUnit = 0.01
startTimeCode = 0
subLayers = [
@../utils/Environment.usda@
]
timeCodesPerSecond = 60
upAxis = "Z"
)
class Scope "Prototypes"
{
def Xform "bolt" (
kind = "component"
)
{
custom string userProperties:blenderName:object = "bolt"
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (0, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
def Mesh "bolt" (
active = true
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
float3[] extent = [(-0.015267535, -0.017632386, -0.013132782), (0.015269997, 0.01762932,... (truncated)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,... (truncated)]
int[] faceVertexIndices = [44, 42, 4, 26, 100, 97, 6, 27, 129, 118, 40, 101, 88, 85, 8, ... (truncated)]
rel material:binding = </World/Looks/metal> (
bindMaterialAs = "weakerThanDescendants"
)
normal3f[] normals = [(0.49999985, 0.86602545, 0), (0.49999985, 0.86602545, 0), (0.49999... (truncated)]
interpolation = "faceVarying"
)
point3f[] points = [(0.015269997, 0.008045348, 0.0069556003), (0.0000012307436, 0.017629... (truncated)]
bool[] primvars:sharp_face = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1... (truncated)]
interpolation = "uniform"
)
texCoord2f[] primvars:st = [(0.842978, 0.97208655), (0.84297794, 1), (0.8333333, 1), (0.... (truncated)]
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "catmullClark"
custom string userProperties:blenderName:data = "Cylinder.001"
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (0, 0, 0.01268314113730921)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale... (truncated)]
}
}
def Xform "washer" (
kind = "component"
)
{
custom string userProperties:blenderName:object = "washer"
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (0, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
def Mesh "washer" (
active = true
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
float3[] extent = [(-0.0174625, -0.0174625, 0), (0.0174625, 0.0174625, 0.0026102306)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,... (truncated)]
int[] faceVertexIndices = [53, 55, 8, 4, 0, 4, 5, 1, 45, 22, 18, 44, 2, 6, 7, 3, 55, 48,... (truncated)]
rel material:binding = </World/Looks/metal> (
bindMaterialAs = "weakerThanDescendants"
)
normal3f[] normals = [(0.70710677, 0.70710677, 0), (-3.0803932e-8, 1, 0), (3.192741e-9, ... (truncated)]
interpolation = "faceVarying"
)
point3f[] points = [(0.0174625, 0, 0.0026102306), (0.0071437503, 0, 0.0026102306), (0.00... (truncated)]
bool[] primvars:sharp_face = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1... (truncated)]
interpolation = "uniform"
)
texCoord2f[] primvars:st = [(0.625, 0.63989735), (0.75, 0.63989735), (0.75, 0.6666667), ... (truncated)]
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "catmullClark"
custom string userProperties:blenderName:data = "washer"
}
}
}
def Xform "World" (
kind = "assembly"
)
{
def Scope "Looks"
{
def Material "metal"
{
token outputs:displacement.connect = </World/Looks/metal/Shader.outputs:displacement>
token outputs:surface.connect = </World/Looks/metal/Shader.outputs:surface>
def Shader "Shader"
{
uniform token info:id = "UsdPreviewSurface"
float inputs:clearcoat = 0 (
customData = {
float default = 0
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
float inputs:clearcoatRoughness = 0.01 (
customData = {
float default = 0.01
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
color3f inputs:diffuseColor = (0.38613862, 0.2797061, 0.07901187) (
customData = {
float3 default = (0.18, 0.18, 0.18)
}
hidden = false
renderType = "color"
)
float inputs:displacement = 0 (
customData = {
float default = 0
}
hidden = false
)
color3f inputs:emissiveColor = (0, 0, 0) (
customData = {
float3 default = (0, 0, 0)
}
hidden = false
renderType = "color"
)
float inputs:ior = 1.5 (
customData = {
float default = 1.5
dictionary range = {
float max = 3.4028235e38
float min = 0
}
dictionary soft_range = {
float max = 5
float min = 1
}
}
hidden = false
)
float inputs:metallic = 1 (
customData = {
float default = 0
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
normal3f inputs:normal = (0, 0, 1) (
customData = {
float3 default = (0, 0, 1)
dictionary range = {
float3 max = (1, 1, 1)
float3 min = (-1, -1, -1)
}
}
hidden = false
)
float inputs:occlusion = 1 (
customData = {
float default = 1
dictionary range = {
float max = 1
float min = 0
}
}
doc = """This parameter is unused."""
hidden = false
)
float inputs:opacity = 1 (
customData = {
float default = 1
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
float inputs:opacityThreshold = 0 (
connectability = "interfaceOnly"
customData = {
float default = 0
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
float inputs:roughness = 0.26999998 (
customData = {
float default = 0.5
dictionary range = {
float max = 1
float min = 0
}
}
hidden = false
)
color3f inputs:specularColor = (0, 0, 0) (
customData = {
float3 default = (0, 0, 0)
}
hidden = false
renderType = "color"
)
int inputs:useSpecularWorkflow = 0 (
connectability = "interfaceOnly"
customData = {
int default = 0
dictionary range = {
int max = 1
int min = 0
}
string widgetType = "checkBox"
}
hidden = false
)
token outputs:displacement (
renderType = "material"
)
token outputs:surface (
renderType = "material"
)
}
}
}
def "bolt_01" (
prepend references = </Prototypes/bolt>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (3.415397779335273, 3.793283326999029, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
def "bolt_02" (
prepend references = </Prototypes/bolt>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (3.415397779335273, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
def "bolt_03" (
prepend references = </Prototypes/bolt>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (3.415397779335273, -3.737699819079148, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
def "washer_01" (
prepend references = </Prototypes/washer>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (0, 3.793283326999029, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
def "washer_02" (
prepend references = </Prototypes/washer>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (0, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
def "washer_03" (
prepend references = </Prototypes/washer>
)
{
float3 xformOp:rotateXYZ = (0, 0, 0)
float3 xformOp:scale = (100, 100, 100)
double3 xformOp:translate = (0, -3.7377, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
}