Siggraph Presentation

This guide will be officially introduced at Siggraph 2023 - Houdini Hive on Wednesday, 9. of August 2023 at 11:00 AM PST.

Composition Arcs

In this section we'll cover how to create composition arcs via code. To check our how composition arcs interact with each other, check out our Composition Strength Ordering (LIVRPS) section.

Please read out fundamentals section as we often refer to it on this page.

Table of Contents

  1. Composition Arcs In-A-Nutshell
  2. What should I use it for?
  3. Resources
  4. Overview
  5. Composition Arcs
    1. Sublayers / Local Opinions
      1. Value Clips
    2. Inherits
    3. Variants
    4. References
      1. References File
      2. References Internal
    5. Payloads
    6. Specializes

TL;DR - Composition Arcs In-A-Nutshell

  • Creating composition arcs is straight forward, the high level API wraps the low level list editable ops with a thin wrappers. We can access the high level API via the Usd.Prim.Get<ArcName> syntax.
  • Editing via the low level API is often just as easy, if not easier, especially when creating (nested) variants. The list editable ops can be set via Sdf.PrimSpec.SetInfo(<arcType>, <listEditableOp>) or via the properties e.g. Sdf.PrimSpec.<arcType>List.

What should I use it for?

Tip

We'll be using the below code, whenever we write composition arcs in USD.

Resources

Overview

This section will focus on how to create each composition arc via the high and low level API.

Composition Arcs

All arcs that make use of list-editable ops, take of of these tokens as an optional position keyword argument via the high level API.

  • Usd.ListPositionFrontOfAppendList: Prepend to append list, the same as Sdf.<Type>ListOp.appendedItems.insert(0, item)
  • Usd.ListPositionBackOfAppendList: Append to append list, the same as Sdf.<Type>ListOp.appendedItems.append(item)
  • Usd.ListPositionFrontOfPrependList: Prepend to prepend list, the same as Sdf.<Type>ListOp.appendedItems.insert(0, item)
  • Usd.ListPositionBackOfPrependList: Append to prepend list, the same as Sdf.<Type>ListOp.appendedItems.append(item)

As we can see, all arc APIs, except for sublayers, in the high level API, are thin wrappers around the list editable op of the arc.

Sublayers / Local Opinions

Pro Tip | What do I use sublayer arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the sublayer arc is used for.

# For sublayering we modify the .subLayerPaths attribute on a layer.
# This is the same for both the high and low level API.
### High Level & Low Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
# Layer onto root layer
layer_a = Sdf.Layer.CreateAnonymous()
layer_b = Sdf.Layer.CreateAnonymous()
root_layer = stage.GetRootLayer()
# Here we pass in the file paths (=layer identifiers).
root_layer.subLayerPaths.append(layer_a.identifier)
root_layer.subLayerPaths.append(layer_b.identifier)
# Once we have added the sublayers, we can also access their layer offsets:
print(root_layer.subLayerOffsets) # Returns: [Sdf.LayerOffset(), Sdf.LayerOffset()]
# Since layer offsets are ready only copies, we need to assign a newly created 
# layer offset if we want to modify them. We also can't replace the whole list, as
# it needs to keep a pointer to the array.
layer_offset_a = root_layer.subLayerOffsets[0]
root_layer.subLayerOffsets[0] = Sdf.LayerOffset(offset=layer_offset_a.offset + 10, 
                                                scale=layer_offset_a.scale * 2)
layer_offset_b = root_layer.subLayerOffsets[1]
root_layer.subLayerOffsets[1] = Sdf.LayerOffset(offset=layer_offset_b.offset - 10, 
                                                scale=layer_offset_b.scale * 0.5)
print(root_layer.subLayerOffsets) # Returns: [Sdf.LayerOffset(10, 2), Sdf.LayerOffset(-10, 0.5)]

# If we want to sublayer on the active layer, we just add it there.
layer_c = Sdf.Layer.CreateAnonymous()
active_layer = stage.GetEditTarget().GetLayer()
root_layer.subLayerPaths.append(layer_c.identifier)

When working in Houdini, we can't directly sublayer onto the root layer as with native USD, due to Houdini's layer caching mechanism, that makes node based stage editing possible. Layering on the active layer works as usual though.

### High Level & Low Level ###
import loputils
from pxr import Sdf
# Hou LOP Node https://www.sidefx.com/docs/houdini/hom/hou/LopNode.html
# See $HFS/houdini/python3.9libs/loputils.py
"""
def createPythonLayer(node, savepath=None):
    # Tag the layer as "LOP" so we know it was created by LOPs.
    layer = Sdf.Layer.CreateAnonymous('LOP')
    # Add a Houdini Layer Info prim where we can put the save path.
    p = Sdf.CreatePrimInLayer(layer, '/HoudiniLayerInfo')
    p.specifier = Sdf.SpecifierDef
    p.typeName = 'HoudiniLayerInfo'/stage/list_editable_ops/pythonscript6
    if savepath:
        p.customData['HoudiniSavePath'] = hou.text.expandString(savepath)
        p.customData['HoudiniSaveControl'] = 'Explicit'
    # Let everyone know what node created this layer.
    p.customData['HoudiniCreatorNode'] = node.sessionId()
    p.customData['HoudiniEditorNodes'] = Vt.IntArray([node.sessionId()])
    node.addHeldLayer(layer.identifier)
    return layer
"""
# Sublayer onto root layer via Python LOP node
node = hou.pwd()
stage = node.editableStage()
layer = loputils.createPythonLayer(node, '$HIP/myfile.usda')
node.addSubLayer(layer.identifier)
# This doesn't seem to work at the moment, as Houdini does some custom root layer handeling
# print(root_layer.subLayerPaths) # Our added layer does not show up. So we have to use the `sublayer` node.
# root_layer = stage.GetRootLayer()
# root_layer.subLayerOffsets[0] = Sdf.LayerOffset(offset=10, scale=1)

# Sublayer onto active layer via Python LOP node, here we can do the usual.
node = hou.pwd()
layer = node.editableLayer()
layer_a = loputils.createPythonLayer(node, '$HIP/myfile.usda')
layer.subLayerPaths.append(layer_a.identifier)
layer.subLayerOffsets[0] = Sdf.LayerOffset(offset=10, scale=1)

# Since layers are automatically garbage collected once they go out of scope,
# we can tag them to keep them persistently in memory for the active session.
layer_b = Sdf.Layer.CreateAnonymous()
node.addHeldLayer(layer_b.identifier)
# This can then be re-used via the standard anywhere in Houdini.
layer_b = Sdf.Layer.FindOrOpen(layer_b.identifier)

Here is the result:

Alt text

Value Clips

Pro Tip | What do I use value clips for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what value clips are used for.

We cover value clips in our animation section. Their opinion strength is lower than direct (sublayer) opinions, but higher than anything else.

The write them via metadata entries as covered here in our value clips section.

Inherits

Pro Tip | What do I use inherit arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the inherit arc is used for.

Inherits, like specializes, don't have a object representation, they directly edit the list-editable op list.

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
cube_prim_path = Sdf.Path("/cube")
cube_prim = stage.DefinePrim(cube_prim_path, "Cube")
inherits_api = bicycle_prim.GetInherits()
inherits_api.AddInherit(cube_prim_path, position=Usd.ListPositionFrontOfAppendList)
# inherits_api.SetInherits() # Clears the list editable ops and authors an Sdf.PathListOp.CreateExplicit([])
# inherits_api.RemoveInherit(cube_prim_path)
# inherits_api.ClearInherits() # Sdf.PathListOp.Clear()
# inherits_api.GetAllDirectInherits() # Returns all inherits generated in the active layer stack
### Low Level ###
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_path = Sdf.Path("/cube")
cube_prim_spec = Sdf.CreatePrimInLayer(layer, cube_prim_path)
cube_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_spec.typeName = "Cube"
bicycle_prim_spec.inheritPathList.appendedItems = [cube_prim_path]

Variants

Pro Tip | What do I use variant arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the variant arc is used for.

Variant sets (the variant set->variant name mapping) are also managed via list editable ops. The actual variant set data is not though. It is written "in-line" into the prim spec via the Sdf.VariantSetSpec/Sdf.VariantSpec specs, so that's why we have dedicated specs.

This means we can add variant data, but hide it by not adding the variant set name to the variantSetsmetadata.

For example here we added it:

def Xform "car" (
    variants = {
        string color = "colorA"
    }
    prepend variantSets = "color"
)
{
    variantSet "color" = {
        "colorA" {
            def Cube "cube"
            {
            }
        }
        "colorB" {
            def Sphere "sphere"
            {
            }
        }
    }
}

Here we skipped it, by commenting out the: car_prim_spec.SetInfo("variantSetNames", Sdf.StringListOp.Create(prependedItems=["color"])) line in the below code. This will make it not appear in UIs for variant selections.

def Xform "car" (
    variants = {
        string color = "colorA"
    }
)
{
    variantSet "color" = {
        "colorA" {
            def Cube "cube"
            {
            }
        }
        "colorB" {
            def Sphere "sphere"
            {
            }
        }
    }
}
### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()

bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path, "Xform")
## Methods of Usd.VariantSets
# Has: 'HasVariantSet'
# Get: 'GetNames', 'GetVariantSet', 'GetVariantSelection', 'GetAllVariantSelections'
# Set: 'AddVariantSet', 'SetSelection'
variant_sets_api = bicycle_prim.GetVariantSets()
## Methods of Usd.VariantSet
# Has: 'HasAuthoredVariant', 'HasAuthoredVariantSelection'
# Get: 'GetName', 'GetVariantNames', 'GetVariantSelection', 'GetVariantEditContext', 'GetVariantEditTarget'
# Set: 'AddVariant', 'SetVariantSelection'
# Clear: 'BlockVariantSelection', 'ClearVariantSelection'
variant_set_api = variant_sets_api.AddVariantSet("color", position=Usd.ListPositionBackOfPrependList)
variant_set_api.AddVariant("colorA")
# If we want to author on the selected variant, we have to select it first
variant_set_api.SetVariantSelection("colorA")
with variant_set_api.GetVariantEditContext():
    # Anything we write in the context, goes into the variant (prims and properties)
    cube_prim_path = bicycle_prim_path.AppendChild("cube")
    cube_prim = stage.DefinePrim(cube_prim_path, "Cube")
# We can also generate the edit target ourselves, but we still need to set the
# variant selection, seems like a bug. Changing variants is a heavy op ...
variant_set_api.AddVariant("colorB")
variant_set_api.SetVariantSelection("colorB")
variant_prim_path = bicycle_prim_path.AppendVariantSelection("color", "colorB") 
layer = stage.GetEditTarget().GetLayer()
edit_target = Usd.EditTarget.ForLocalDirectVariant(layer, variant_prim_path)
# Or
edit_target = variant_set_api.GetVariantEditTarget()
edit_context = Usd.EditContext(stage, edit_target) 
with edit_context as ctx:
    sphere_prim_path = bicycle_prim_path.AppendChild("sphere")
    sphere_prim = stage.DefinePrim("/bicycle/sphere", "Sphere")
### Low Level ###
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
bicycle_prim_spec.typeName = "Xform"
# Variants
cube_prim_path = bicycle_prim_path.AppendVariantSelection("color", "colorA").AppendChild("cube")
cube_prim_spec = Sdf.CreatePrimInLayer(layer, cube_prim_path)
cube_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_spec.typeName = "Cube"
sphere_prim_path = bicycle_prim_path.AppendVariantSelection("color", "colorB").AppendChild("sphere")
sphere_prim_spec = Sdf.CreatePrimInLayer(layer, sphere_prim_path)
sphere_prim_spec.specifier = Sdf.SpecifierDef
sphere_prim_spec.typeName = "Sphere"
# Variant Selection
bicycle_prim_spec.variantSelections["color"] = "colorA"

# We can also author the variants via variant specs
layer = Sdf.Layer.CreateAnonymous()
car_prim_path = Sdf.Path("/car")
car_prim_spec = Sdf.CreatePrimInLayer(layer, car_prim_path)
car_prim_spec.specifier = Sdf.SpecifierDef
car_prim_spec.typeName = "Xform"
# Variants
variant_set_spec = Sdf.VariantSetSpec(car_prim_spec, "color")
variant_spec = Sdf.VariantSpec(variant_set_spec, "colorA")
cube_prim_spec = Sdf.PrimSpec(variant_spec.primSpec, "cube", Sdf.SpecifierDef)
cube_prim_spec.typeName = "Cube"
variant_spec = Sdf.VariantSpec(variant_set_spec, "colorB")
cube_prim_spec = Sdf.PrimSpec(variant_spec.primSpec, "sphere", Sdf.SpecifierDef)
cube_prim_spec.typeName = "Sphere"
# Ironically this does not setup the variant set names metadata, so we have to author it ourselves.
car_prim_spec.SetInfo("variantSetNames", Sdf.StringListOp.Create(prependedItems=["color"]))
# Variant Selection
car_prim_spec.variantSelections["color"] = "colorA"

Pro Tip | Copying layer content into a variant

When editing variants, we can also move layer content into a (nested) variant very easily via the Sdf.CopySpec command. This is a very powerful feature!

from pxr import Sdf
# Spawn other layer, this usually comes from other stages, that your DCC creates/owns.
some_other_layer = Sdf.Layer.CreateAnonymous()
root_prim_path = Sdf.Path("/root")
cube_prim_path = Sdf.Path("/root/cube")
cube_prim_spec = Sdf.CreatePrimInLayer(some_other_layer, cube_prim_path)
cube_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_spec.typeName = "Cube"
# Create demo layer
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
bicycle_prim_spec.typeName = "Xform"
# Copy content into variant
variant_set_spec = Sdf.VariantSetSpec(bicycle_prim_spec, "color")
variant_spec = Sdf.VariantSpec(variant_set_spec, "colorA")
variant_prim_path = bicycle_prim_path.AppendVariantSelection("color", "colorA")
Sdf.CopySpec(some_other_layer, root_prim_path, layer, variant_prim_path)
# Variant selection
bicycle_prim_spec.SetInfo("variantSetNames", Sdf.StringListOp.Create(prependedItems=["color"]))
bicycle_prim_spec.variantSelections["color"] = "colorA"

Here is how we can created nested variant sets via the high level and low level API. As you can see it is quite a bit easier with the low level API.

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()

def variant_nested_edit_context(prim, variant_selections, position=Usd.ListPositionBackOfPrependList):
    """Author nested variants
    Args:
        prim (Usd.Prim): The prim to author on.
        variant_selections (list): A list of tuples with [('variant_set_name', 'variant_name')] data.
        position (Usd.ListPosition): The list position of the variant set.
    Returns:
        Usd.EditContext: An edit context manager.
    """
    
    if not variant_selections:
        raise Exception("No valid variant selections defined!")
        
    def _recursive_variant_context(prim, variant_selections, position):
        variant_sets_api = bicycle_prim.GetVariantSets()
        
        variant_selection = variant_selections.pop(-1)
        variant_set_name, variant_name = variant_selection
        
        variant_set_api = variant_sets_api.AddVariantSet(variant_set_name, position=position)
        variant_set_api.AddVariant(variant_name)
        # Be aware, this authors the selection in the variant
        # ToDo make this a context manager that cleans up the selection authoring.
        variant_set_api.SetVariantSelection(variant_name)
        
        if not variant_selections:
            return variant_set_api.GetVariantEditContext()
        else:
            with variant_set_api.GetVariantEditContext():
                return _recursive_variant_context(prim, variant_selections, position)
    
    variant_selections = variant_selections[::-1]
    return _recursive_variant_context(prim, variant_selections, position)

bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path, "Xform")
# Variants
variant_selections = [("model", "old"), ("LOD", "lowRes")]
edit_context = variant_nested_edit_context(bicycle_prim, variant_selections)
with edit_context as ctx:
    sphere_prim_path = bicycle_prim_path.AppendChild("sphere")
    sphere_prim = stage.DefinePrim("/bicycle/sphere", "Sphere")
variant_selections = [("model", "old"), ("LOD", "highRes")]
edit_context = variant_nested_edit_context(bicycle_prim, variant_selections)
with edit_context as ctx:
    sphere_prim_path = bicycle_prim_path.AppendChild("cube")
    sphere_prim = stage.DefinePrim("/bicycle/cube", "Cube")
variant_selections = [("model", "new"), ("LOD", "lowRes")]
edit_context = variant_nested_edit_context(bicycle_prim, variant_selections)
with edit_context as ctx:
    sphere_prim_path = bicycle_prim_path.AppendChild("cylinder")
    sphere_prim = stage.DefinePrim("/bicycle/cube", "Cylinder")
# Variant selections
# Be sure to explicitly set the overall selection, otherwise if will derive from,
# the nested variant selections.
variant_sets_api = bicycle_prim.GetVariantSets()
variant_sets_api.SetSelection("model", "old")
variant_sets_api.SetSelection("LOD", "lowRes")

### Low Level ###
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
def variant_nested_prim_path(prim_path, variant_selections):
    variant_prim_path = prim_path
    for variant_set_name, variant_name in variant_selections:
        variant_prim_path = variant_prim_path.AppendVariantSelection(variant_set_name, variant_name)
    return variant_prim_path
    
def define_prim_spec(layer, prim_path, type_name):
    prim_spec = Sdf.CreatePrimInLayer(layer, prim_path)
    prim_spec.specifier = Sdf.SpecifierDef
    prim_spec.typeName = type_name

bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
bicycle_prim_spec.typeName = "Xform"
# Variants
variant_selections = [("model", "old"), ("LOD", "lowRes")]
variant_prim_path = variant_nested_prim_path(bicycle_prim_path, variant_selections)
define_prim_spec(layer, variant_prim_path.AppendChild("sphere"), "Sphere")
variant_selections = [("model", "old"), ("LOD", "highRes")]
variant_prim_path = variant_nested_prim_path(bicycle_prim_path, variant_selections)
define_prim_spec(layer, variant_prim_path.AppendChild("cube"), "Cube")
variant_selections = [("model", "new"), ("LOD", "lowRes")]
variant_prim_path = variant_nested_prim_path(bicycle_prim_path, variant_selections)
define_prim_spec(layer, variant_prim_path.AppendChild("cylinder"), "Cylinder")
# Variant selections
# The low level API has the benefit of not setting variant selections
# in the nested variants.
bicycle_prim_spec.variantSelections["model"] = "old"
bicycle_prim_spec.variantSelections["LOD"] = "highRes"

References

Pro Tip | What do I use reference arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the reference arc is used for.

The Sdf.Reference class creates a read-only reference description object:

from pxr import Sdf
ref = Sdf.Reference("/file/path.usd", "/prim/path", Sdf.LayerOffset(offset=10, scale=1))
# The reference object is a read only instance.
print(ref.assetPath) # Returns: "/file/path.usd"
print(ref.primPath) # Returns: "/prim/path"
print(ref.layerOffset) # Returns: Sdf.LayerOffset(offset=10, scale=1)
try: 
    ref.assetPath = "/some/other/file/path.usd"
except Exception:
    print("Read only Sdf.Reference!")

References File

Here is how we add external references (references that load data from other files):

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
# Spawn temp layer
reference_layer = Sdf.Layer.CreateAnonymous("ReferenceExample")
reference_bicycle_prim_path = Sdf.Path("/bicycle")
reference_bicycle_prim_spec = Sdf.CreatePrimInLayer(reference_layer, reference_bicycle_prim_path)
reference_bicycle_prim_spec.specifier = Sdf.SpecifierDef
reference_bicycle_prim_spec.typeName = "Cube"
# Set the default prim to use when we specify no primpath. It can't be a prim path, it must be a root prim.
reference_layer.defaultPrim = reference_bicycle_prim_path.name
# Reference
reference_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
reference = Sdf.Reference(reference_layer.identifier, reference_bicycle_prim_path, reference_layer_offset)
# Or: If we don't specify a prim, the default prim will get used, as set above
reference = Sdf.Reference(reference_layer.identifier, layerOffset=reference_layer_offset)
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
references_api = bicycle_prim.GetReferences()
references_api.AddReference(reference, position=Usd.ListPositionFrontOfAppendList)
# references_api.SetReferences() # Clears the list editable ops and authors an Sdf.ReferenceListOp.CreateExplicit([])
# references_api.RemoveReference(cube_prim_path)
# references_api.ClearReferences() # Sdf.ReferenceListOp.Clear()
### Low Level ###
from pxr import Sdf
# Spawn temp layer
reference_layer = Sdf.Layer.CreateAnonymous("ReferenceExample")
reference_bicycle_prim_path = Sdf.Path("/bicycle")
reference_bicycle_prim_spec = Sdf.CreatePrimInLayer(reference_layer, reference_bicycle_prim_path)
reference_bicycle_prim_spec.specifier = Sdf.SpecifierDef
reference_bicycle_prim_spec.typeName = "Cube"
reference_layer.defaultPrim = reference_bicycle_prim_path.name
# In Houdini add, otherwise the layer will be garbage collected.
# node.addHeldLayer(reference_layer.identifier)
# Reference
layer = Sdf.Layer.CreateAnonymous()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
reference_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
reference = Sdf.Reference(reference_layer.identifier, reference_bicycle_prim_path, reference_layer_offset)
# Or: If we don't specify a prim, the default prim will get used, as set above
reference = Sdf.Reference(reference_layer.identifier, layerOffset=reference_layer_offset)
bicycle_prim_spec.referenceList.appendedItems = [reference]

References Internal

Here is how we add internal references (references that load data from another part of the hierarchy) :

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
# Spawn hierarchy
cube_prim_path = Sdf.Path("/cube")
cube_prim = stage.DefinePrim(cube_prim_path, "Cube")
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
# Reference
reference_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
reference = Sdf.Reference("", cube_prim_path, reference_layer_offset)
references_api = bicycle_prim.GetReferences()
references_api.AddReference(reference, position=Usd.ListPositionFrontOfAppendList)
# Or:
references_api.AddInternalReference(cube_prim_path, reference_layer_offset, position=Usd.ListPositionFrontOfAppendList)
### Low Level ###
from pxr import Sdf
# Spawn hierarchy
layer = Sdf.Layer.CreateAnonymous()
cube_prim_path = Sdf.Path("/cube")
cube_prim_spec = Sdf.CreatePrimInLayer(layer, cube_prim_path)
cube_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_spec.typeName = "Cube"
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
# Reference
reference_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
reference = Sdf.Reference("", cube_prim_path, reference_layer_offset)
bicycle_prim_spec.referenceList.appendedItems = [reference]

Payloads

Pro Tip | What do I use payload arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the payload arc is used for.

The Sdf.Payload class creates a read-only payload description object:

from pxr import Sdf
payload = Sdf.Payload("/file/path.usd", "/prim/path", Sdf.LayerOffset(offset=10, scale=1))
# The reference object is a read only instance.
print(payload.assetPath) # Returns: "/file/path.usd"
print(payload.primPath) # Returns: "/prim/path"
print(payload.layerOffset) # Returns: Sdf.LayerOffset(offset=10, scale=1)
try: 
    payload.assetPath = "/some/other/file/path.usd"
except Exception:
    print("Read only Sdf.Payload!")

Here is how we add payloads. Payloads always load data from other files:

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
# Spawn temp layer
payload_layer = Sdf.Layer.CreateAnonymous("PayloadExample")
payload_bicycle_prim_path = Sdf.Path("/bicycle")
payload_bicycle_prim_spec = Sdf.CreatePrimInLayer(payload_layer, payload_bicycle_prim_path)
payload_bicycle_prim_spec.specifier = Sdf.SpecifierDef
payload_bicycle_prim_spec.typeName = "Cube"
# Set the default prim to use when we specify no primpath. It can't be a prim path, it must be a root prim.
payload_layer.defaultPrim = payload_bicycle_prim_path.name
# Payload
payload_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
payload = Sdf.Payload(payload_layer.identifier, payload_bicycle_prim_path, payload_layer_offset)
# Or: If we don't specify a prim, the default prim will get used, as set above
payload = Sdf.Payload(payload_layer.identifier, layerOffset=payload_layer_offset)
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
payloads_api = bicycle_prim.GetPayloads()
payloads_api.AddPayload(payload, position=Usd.ListPositionFrontOfAppendList)
# payloads_api.SetPayloads() # Clears the list editable ops and authors an Sdf.PayloadListOp.CreateExplicit([])
# payloads_api.RemovePayload(cube_prim_path)
# payloads_api.ClearPayloads() # Sdf.PayloadListOp.Clear()
### Low Level ###
from pxr import Sdf
# Spawn temp layer
payload_layer = Sdf.Layer.CreateAnonymous("PayLoadExample")
payload_bicycle_prim_path = Sdf.Path("/bicycle")
payload_bicycle_prim_spec = Sdf.CreatePrimInLayer(payload_layer, payload_bicycle_prim_path)
payload_bicycle_prim_spec.specifier = Sdf.SpecifierDef
payload_bicycle_prim_spec.typeName = "Cube"
payload_layer.defaultPrim = payload_bicycle_prim_path.name
# In Houdini add, otherwise the layer will be garbage collected.
# node.addHeldLayer(payload_layer.identifier)
# Payload
layer = Sdf.Layer.CreateAnonymous()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
payload_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
payload = Sdf.Payload(payload_layer.identifier, payload_bicycle_prim_path, payload_layer_offset)
# Or: If we don't specify a prim, the default prim will get used, as set above
payload = Sdf.Payload(payload_layer.identifier, layerOffset=payload_layer_offset)
bicycle_prim_spec.payloadList.appendedItems = [payload]

Specializes

Pro Tip | What do I use specialize arcs for?

In our Composition Strength Ordering (LIVRPS) section we cover in detail, with production related examples, what the specialize arc is used for.

Specializes, like inherits, don't have a object representation, they directly edit the list-editable op list.

### High Level ###
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
cube_prim_path = Sdf.Path("/cube")
cube_prim = stage.DefinePrim(cube_prim_path, "Cube")
specializes_api = bicycle_prim.GetSpecializes()
specializes_api.AddSpecialize(cube_prim_path, position=Usd.ListPositionFrontOfAppendList)
# inherits_api.SetSpecializes() # Clears the list editable ops and authors an Sdf.PathListOp.CreateExplicit([])
# inherits_api.RemoveSpecialize(cube_prim_path)
# inherits_api.ClearSpecializes() # Sdf.PathListOp.Clear()
### Low Level ###
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
bicycle_prim_path = Sdf.Path("/bicycle")
bicycle_prim_spec = Sdf.CreatePrimInLayer(layer, bicycle_prim_path)
bicycle_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_path = Sdf.Path("/cube")
cube_prim_spec = Sdf.CreatePrimInLayer(layer, cube_prim_path)
cube_prim_spec.specifier = Sdf.SpecifierDef
cube_prim_spec.typeName = "Cube"
bicycle_prim_spec.specializesList.appendedItems = [cube_prim_path]