Authoring USD

The foundation of USD (Universal Scene Description) is the “layer” – usually a file on disk. Most DCCs that support USD provide a way to import/export USD layers, but the exports are usually “all or nothing. Houdini Solaris and Bifrost USD provide the ability to use the Composition Arcs to build a structured layer stack; the USD Python API provides the same in a cross-platform way.

The contents and definition of an Asset vary by studio. For example, an character Asset may contain a model, hair groom, materials, and a rig. Depending on the complexity of the character, they may have multiple variants of each. These components are contributed by multiple artists/departments, and must be composed to be used in Shots.

Whether an Asset is composed JIT in a Shot, or pre-built, a build script would be the preferred method to perform the composition. Otherwise, artists would have to do it themselves, which is untenable at any level of complexity.

Consider this simplified example of an Asset.

#usda 1.0
(
)

def "main" (
    assetInfo = {
        string identifier = "asset.id"
        string name = "asset.name"
        string version = "asset.version"
    }
    variants = {
        string repr = "usd"
    }
    prepend variantSets = "repr"
)
{
    variantSet "repr" = {
        "maya" {
            def MayaReference "rig"
            {
                bool mayaAutoEdit = 1
                string mayaNamespace = "rig"
                asset mayaReference = @rig.mb@
            }

        }
        "usd" {
            def Xform "geo" (
                prepend payload = @model.usd@
            )
            {
            }

        }
    }
}

This Asset has a “repr” variant set, which allows the user to choose how to present the Asset in their DCC. It has two variants – “usd” and “maya”. The “usd” variant provides the USD model as an opinion, which will work in most DCCs. The “maya” variant provides the Maya rig as an opinion, which will be loaded as a reference in Maya.

Most tutorials and examples would show you how to author this using the Usd.Stage Python module, as shown below.

from pxr import Sdf, Usd

stage = Usd.Stage.CreateInMemory()

main = stage.DefinePrim('/main', 'Xform')

model_api = Usd.ModelAPI(main)
model_api.SetAssetName('asset.name')
model_api.SetAssetIdentifier('asset.id')
model_api.SetAssetVersion('asset.version')

var_set = main.GetVariantSets().AddVariantSet('repr')
var_set.AddVariant('usd')
var_set.AddVariant('maya')

var_set.SetVariantSelection('rig')

with var_set.GetVariantEditContext():
    rig = stage.DefinePrim(main.GetPath().AppendChild('rig'), 'MayaReference')

    rig.CreateAttribute('mayaReference', Sdf.ValueTypeNames.Asset).Set('rig.mb')
    rig.CreateAttribute('mayaNamespace', Sdf.ValueTypeNames.String).Set('rig')
    rig.CreateAttribute('mayaAutoEdit', Sdf.ValueTypeNames.Bool).Set(True)
    
var_set.SetVariantSelection('usd')

with var_set.GetVariantEditContext():
    geo = stage.DefinePrim(main.GetPath().AppendChild('geo'), 'Xform')
    geo.SetPayload(Sdf.Payload('model.usd'))

stage.GetRootLayer().Export('asset.usda')

However, you can get the same results authoring the layer using the Sdf.Layer Python module, as shown below.

layer = Sdf.Layer.CreateAnonymous()

main = Sdf.PrimSpec(layer.pseudoRoot, 'main', Sdf.SpecifierDef)
main.assetInfo['name'] = 'asset.name'
main.assetInfo['identifier'] = 'asset.id'
main.assetInfo['version'] = 'asset.version'

var_set = Sdf.VariantSetSpec(main, 'repr')
main.variantSetNameList.Prepend(var_set.name)

maya_var = Sdf.VariantSpec(var_set, 'maya')
rig = Sdf.PrimSpec(maya_var.primSpec, 'rig', Sdf.SpecifierDef)
rig.typeName = 'MayaReference'

Sdf.AttributeSpec(rig, 'mayaReference', Sdf.ValueTypeNames.Asset).default = 'rig.mb'
Sdf.AttributeSpec(rig, 'mayaNamespace', Sdf.ValueTypeNames.String).default = 'rig'
Sdf.AttributeSpec(rig, 'mayaAutoEdit', Sdf.ValueTypeNames.Bool).default = True

usd_var = Sdf.VariantSpec(var_set, 'usd')
geo = Sdf.PrimSpec(usd_var.primSpec, 'geo', Sdf.SpecifierDef)
geo.typeName = 'Xform'
geo.payloadList.Prepend(Sdf.Payload('model.usd'))

main.variantSelections[var_set.name] = usd_var.name

layer.Save('asset.usda')

Authoring a layer using Usd.Stage gives you a live view of the Stage, which can be expensive and slow for large or complex scenes. Authoring a layer using Sdf.Layer gives you a data view of the current layer only, which means there’s no overhead from the composition engine when you make changes.

So which should you use?

If your script need to make decisions based on the final composition, use Usd.Stage. If your script is a rote operation, use Sdf.Layer. Anecdotally, I improved the longest run time of the asset build script at one studio from 20 minutes to 20 seconds by switching from Usd.Stage to Sdf.Layer.

Leave a comment