all  /  test_assets  / RelationshipEncapsulationTests  / InternalReferenceTest .usda

Unencapsulated Relationship Tests

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:

  1. Bad asset structure with relationships target prims outside of the default prim.
  2. Misuse of a reference by targeting a prim that wasn’t designed or intended to be a reference target.

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.

External reference with a bad target prim (ExternalReferenceBadTargetTest)

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>
    )
    {

    }
}

Result: (Invalid)

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>)

Internal reference with unencapsulated material binding (InternalReferenceTest)

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+.

Result: (Invalid in <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>)

Result: (Valid in 23.05+)

Internal reference with unencapsulated material bindings across sublayers (SublayeredInternalReferenceTest)

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.

Result: (Invalid in <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/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>)

Result: (Valid in 23.05+)

Referenced assemblies with unencapsulated internal references (ReferencedAssembliesWithInternalReferencesTest)

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") { ... }        
}

Result: (Valid in <23.05)

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.

Result: (Valid in 23.05+)

License

These assets are provided under the Apache 2.0 license.


Edit this page

InternalReferenceTest.usda

#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" (
        prepend apiSchemas = ["MaterialBindingAPI"]
        kind = "component"
    )
    {
        rel material:binding = None (
            bindMaterialAs = "weakerThanDescendants"
        )
        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 = (1, 0.7243665, 0.20462048) (
                    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"]
    }
}