Siggraph Presentation

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

Schemas

Schemas are to USD what classes are to object orient programming. Let's explain schemas with that analogy in mind:

  • Schemas are templates that define default properties and methods. You can think of each prim in your hierarchy being an instance of a class.
  • Each prim must (or rather should, technically it is not enforced) have a type name set (see our prim section). The type name defines the primary class your prim is an instance of. To dynamically subclass your primary classes with additional classes, USD has the concept of API schemas. These then provide extra metadata/properties or methods that can manipulate your prim data.

The examples on this page only talk about how to apply/remove schemas and how to inspect them. In our production and Houdini section we'll look into the most used ones and run through some production examples.

Table of Contents

  1. API Overview In-A-Nutshell
  2. What should I use it for?
  3. Resources
  4. Overview
  5. Creating/Using schemas in your code
  6. Prim Definition
  7. Prim Type Info
  8. Schema Classes
    1. Schema Registry
    2. Schema Kind

TL;DR - Metadata In-A-Nutshell

  • Schemas are like classes in OOP that each prim in your hierarchy then instances. They provide properties (with fallback values) and metadata as well as methods (Get<PropertName>/Set<PropertName>/Utility functions) to manipulate your prim data.
  • There are two different base schema types (See the overview section for more info):
    • Typed Schemas:
      • Define prim type name (OOP: The main class of your prim), like Cube/Mesh/Xform
      • Provide metadata/properties and methods to edit these
      • Checkable via prim.IsA(<SchemaClassName>)
    • API Schemas (Class Naming Convention <SchemaClassName>API):
      • Do not define prim type name (OOP: A subclass that inherits to your main class)
      • Is divided in:
        • Non-Applied API schemas:
          • Add convenience methods to manipulate common prim data like properties and core metadata (like kind/clips).
        • Applied API schemas:
          • Supplement typed schemas by adding additional metadata/properties and methods to edit these
          • Checkable via prim.HasAPI(<SchemaClassName>)
  • A prims composed schema definition can be accessed via prim.GetPrimDefinition(). This defines a prim's full type signature, similar to how you can inherit from multiple classes in OOP class (<TypedSchemaClass>, <AppliedAPISchemaA>, <AppliedAPISchemaA>).
  • You can generate your own as described in our plugin schemas section.

What should I use it for?

Tip

We'll be using schema classes a lot in production, so we recommend familiarizing yourself with the below examples.

They are the main interface for your prims in the high level API that gives you getters/setters for all the standard properties that ship with Usd.

### Typed Schemas ###
# From type name
prim_type_name = prim.GetTypeName()
prim_typed_schema = Usd.SchemaRegistry.GetTypeFromName(prim_type_name).pythonClass(prim)
# From prim type info
prim_typed_schema = prim.GetPrimTypeInfo().GetSchemaType().pythonClass(prim)

### API Schemas ###
# Non-Applied API Schemas
non_applied_api_schema = Usd.ModelAPI(prim)
# Applied API Schemas
applied_api_schema = UsdGeom.MotionAPI.Apply(prim)

The schema classes then give you access to all of the schemas Get/Set methods and utility functions.

Resources

Overview

Here is a flow chart of how the schema inheritance is setup:

flowchart TD
    schemaBase(["Usd.SchemaBase"])
    schemaTyped(["Typed Schemas (Usd.Typed) | Checkable via prim.IsA()"])
    schemaTypedNonConcrete(["Non-Concrete Schemas (Abstract Classes)"]) 
    schemaTypedConcrete(["Concrete Schemas (Define Prim Type Name)"]) 
    schemaAPI(["API Schemas (Usd.APISchemaBase )"]) 
    schemaAPINonApplied([Non-Applied])
    schemaAPIApplied(["Applied | Checkable via prim.HasAPI()"])
    schemaAPIAppliedSingle([Single Applied])
    schemaAPIAppliedMulti([Multi Applied])
    style schemaTypedNonConcrete fill:#57ff5f
    style schemaTypedConcrete fill:#63beff
    style schemaAPINonApplied fill:#63beff
    style schemaAPIAppliedSingle fill:#63beff
    style schemaAPIAppliedMulti fill:#63beff

    schemaBase --> schemaTyped 
    schemaTyped --> schemaTypedNonConcrete
    schemaTypedNonConcrete --> schemaTypedConcrete
    schemaBase --> schemaAPI
    schemaAPI --> schemaAPINonApplied
    schemaAPI --> schemaAPIApplied
    schemaAPIApplied --> schemaAPIAppliedSingle
    schemaAPIApplied --> schemaAPIAppliedMulti

All the blue colored endpoints are the ones you'll set/apply/use via code, the green one you won't instantiate directly, but you can use it to check for inheritance.

  • Typed Schemas (Usd.Typed):
    • The base class for all schemas that define prim types, hence the name Typed Schemas
    • Defines properties and metadata that is attached to prims that have this type.
    • We can check if it is applied to a prim via prim.IsA(<className>)
    • Accessible via SchemaClass(prim) e.g. UsdGeom.Imageable(prim) (Non-concrete), UsdGeom.Xform(prim)(concrete), to get access to the methods. To actually apply the schema, we have to set the type name as described below. Accessing a typed schema on a prim with a different type name will result in errors once you try to get/set data. To actually not guess what the typed Python class is we can run prim.GetPrimTypeInfo().GetSchemaType().pythonClass(prim).
  • Typed Schemas (Usd.Typed) -> Non-Concrete Schemas:
    • The non-concrete schemas are like abstract classes in OOP. They are schemas that concrete schemas can inherit from. The purpose of these is to define common properties/metadata that a certain type of typed schemas need. (For example lights all share a non-concrete schema for the essential properties.)
    • Do not define a type name (hence non-concrete).
  • Typed Schemas (Usd.Typed) -> Non-Concrete Schemas -> Concrete Schemas:
    • Defines a type name
    • In OOP terms you can think of it as the primary base class that your prim is instancing.
    • Applied via Prim.SetTypeName(<typeName>)/PrimSpec.typeName="<typeName>"/SchemaClass.Define(stage, Sdf.Path("/path"))

Here is an example of the inheritance graph of the UsdGeom.Imageable typed non-concrete schema:

Click to expand content

  • API Schemas (Usd.APISchemaBase)
    • The base class for all API Schemas, subclasses must end with API
    • In OOP terms, API schemas are classes that your primary (typed) class can inherit from to gain access to convenience methods, but also additional metadata/properties.
  • API Schemas (Usd.APISchemaBase) -> Non-Applied API Schemas:
    • Provide only methods to manipulate existing prim data like properties and core metadata (like kind/clips). Their common usage is to add convenience methods to manipulate common prim data.
    • They do not define any metadata/properties.
    • The schema name is not written to the apiSchemas metadata, it therefore does not contribute to the prim definition.
    • Code: Applied via SchemaClassAPI(prim) e.g. Usd.ClipsAPI(prim)
  • API Schemas (Usd.APISchemaBase) -> Applied API Schemas:
    • Adds additional metadata/properties to prim and provides methods to manipulate these.
    • The schema name is added to the apiSchemas metadata, it contributes to the prim definition.
    • We can check if it is applied to a prim via prim.HasAPI(<APISchemaType>)
    • Applied via SchemaClassAPI.Apply(prim) e.g. UsdGeom.ModelAPI.Apply(prim)/prim_spec.SetInfo("apiSchemas", Sdf.TokenListOp.Create(prependedItems=["GeomModelAPI"]))
  • API Schemas (Usd.APISchemaBase) -> Applied API Schemas -> Single Apply API Schemas:
    • Can only be applied once per prim
  • API Schemas (Usd.APISchemaBase) -> Applied API Schemas -> Multi Apply API Schemas:
    • Can be applied multiple times with a different instance name, properties are namespaced with the instance name.

If you want to see a list of off the schema classes that ship with USD by default check out the Usd.SchemaBase API docs page, it has a full inheritance diagram.

Tip

As covered in our prim section, Usd has a PrimDefinition/PrimTypeInfo classes we can use to inspect all properties and metadata given through applied and typed schemas on a given prim. This prim definition/type info carry the full type signature of a given prim.

Creating/Using schemas in production

Let's first look at typed schemas:

Pro Tip | Find class from type name

To get the class from the prim, we can run:

# From type name
prim_type_name = prim.GetTypeName()
prim_typed_schema = Usd.SchemaRegistry.GetTypeFromName(prim_type_name).pythonClass(prim)
# From prim type info
prim_typed_schema = prim.GetPrimTypeInfo().GetSchemaType().pythonClass(prim)

This way we don't have to find and import the right class ourselves.

To summarize the below code:

Pro Tip | Best practices how to apply schemas

### Typed Schemas ###
# From type name
prim_type_name = prim.GetTypeName()
prim_typed_schema = Usd.SchemaRegistry.GetTypeFromName(prim_type_name).pythonClass(prim)
# From prim type info
prim_typed_schema = prim.GetPrimTypeInfo().GetSchemaType().pythonClass(prim)

### API Schemas ###
# Non-Applied API Schemas
non_applied_api_schema = Usd.ModelAPI(prim)
# Applied API Schemas
applied_api_schema = UsdGeom.MotionAPI.Apply(prim)
###### Typed Schemas ######
### High Level ###
# Has: 'IsA',
# Get: 'GetTypeName'
# Set: 'SetTypeName'
# Clear: 'ClearTypeName'
from pxr import Sdf, Usd, UsdGeom
stage = Usd.Stage.CreateInMemory()

# Define prim via stage
prim_path = Sdf.Path("/bicycleA")
prim = stage.DefinePrim(prim_path, "Cube")
# Define prim via typed schema
prim_path = Sdf.Path("/bicycleB")
prim_typed_schema = UsdGeom.Cube.Define(stage, prim_path)
# Returns the schema class object, so we have to get the prim
prim = prim_typed_schema.GetPrim()
# Since the "Cube" schema is a subclass of the
# non.concrete typed UsdGeom.Boundable schema, we can check:
print(prim.IsA(UsdGeom.Cube)) # Returns: True
print(prim.IsA(UsdGeom.Boundable)) # Returns: True
# To remove the type, we can call:
# prim.ClearTypeName()
# To access the schema class methods, we give our prim to the 
# class constructor:
prim_typed_schema = UsdGeom.Cube(prim)
# The typed Cube schema for example has a Get/Set method for the schema's size attribute.
prim_typed_schema.GetSizeAttr().Set(5)
# Or we let Usd gives us the Python class
prim_typed_schema = prim.GetPrimTypeInfo().GetSchemaType().pythonClass(prim)
prim_typed_schema.GetSizeAttr().Set(10)
# Or we get it from the type name
prim_typed_schema = Usd.SchemaRegistry.GetTypeFromName(prim.GetTypeName()).pythonClass(prim)

### Low Level ###
# To set typed schemas via the low level API, we just 
# need to set the PrimSpec.typeName = "<SchemaName>"
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
prim_path = Sdf.Path("/bicycle")
prim_spec = Sdf.CreatePrimInLayer(layer, prim_path)
prim_spec.typeName = "Cube"

Important

The 'IsA' check is a very valuable check to see if something is an instance of a (base) class. It is similar to Python's isinstance method.

And the API schemas:

###### API Schemas ######
### High Level ###
# Has: 'HasAPI', 'CanApplyAPI'
# Get: 'GetAppliedSchemas'
# Set: 'AddAppliedSchema', 'ApplyAPI'
# Clear: 'RemoveAppliedSchema', 'RemoveAPI'
from pxr import Sdf, Usd, UsdGeom
stage = Usd.Stage.CreateInMemory()

### Applied Schemas ###
# Define prim via stage
prim_path = Sdf.Path("/bicycleA")
prim = stage.DefinePrim(prim_path, "Cube")
# Check if it can be applied
print(UsdGeom.MotionAPI.CanApply(prim)) # Returns True
# Apply API schema (in active layer),
prim.ApplyAPI("GeomModelAPI") # Returns: True, older USD versions: prim.ApplyAPI("UsdGeomModelAPI")
# Add applied schema
# This does not check if the schema actually exists, 
# you have to use this for codeless schemas.
prim.AddAppliedSchema("SkelBindingAPI") # Returns: True #
# Apply and get the schema class (preferred usage)
applied_api_schema = UsdGeom.MotionAPI.Apply(prim)
# Remove applied schema (in active layer)
# prim.RemoveAppliedSchema("SkelBindingAPI")
# prim.RemoveAPI("GeomModelAPI")
# For multi-apply schemas, we can feed in our custom name,
# for example for collections it drives the collection name.
prim.ApplyAPI("UsdCollectionAPI", "myCoolCollectionName")
applied_multi_api_schema = Usd.CollectionAPI.Apply(prim, "myCoolCollectionName")
### Non-Applied Schemas ###
# Non-Applied schemas do not have an `Apply` method
# (who would have guessed that?)
non_applied_api_schema = Usd.ModelAPI(prim)

### Low Level ###
# To set applied API schemas via the low level API, we just 
# need to set the `apiSchemas` key to a Token Listeditable Op.
from pxr import Sdf
layer = Sdf.Layer.CreateAnonymous()
prim_path = Sdf.Path("/bicycle")
prim_spec = Sdf.CreatePrimInLayer(layer, prim_path)
schemas = Sdf.TokenListOp.Create(
    prependedItems=["SkelBindingAPI", "UsdGeomModelAPI"]
)
prim_spec.SetInfo("apiSchemas", schemas)
# We don't have nice access the the schema class as in the high level API

Prim Definition

With the prim definition we can inspect what the schemas provide. Basically you are inspecting the class (as to the prim being the instance, if we compare it to OOP paradigms). In production, you won't be using this a lot, it is good to be aware of it though. If you change things here, you are actually on run-time modifying the base class, which might cause some weird issues.

from pxr import Sdf, Tf, Usd, UsdGeom
stage = Usd.Stage.CreateInMemory()
prim_path = Sdf.Path("/bicycle")
prim = stage.DefinePrim(prim_path, "Xform")
prim.ApplyAPI("GeomModelAPI") # Older USD versions: prim.ApplyAPI("UsdGeomModelAPI")
prim_def = prim.GetPrimDefinition()
print(prim_def.GetAppliedAPISchemas()) # Returns: ['GeomModelAPI']
print(prim_def.GetPropertyNames()) 
# Returns: All properties that come from the type name schema and applied schemas
"""
['model:drawModeColor', 'model:cardTextureZPos', 'model:drawMode', 'model:cardTextureZNeg', 
'model:cardTextureYPos', 'model:cardTextureYNeg', 'model:cardTextureXPos', 'model:cardTextur
eXNeg', 'model:cardGeometry', 'model:applyDrawMode', 'proxyPrim', 'visibility', 'xformOpOrde
r', 'purpose']
"""
# You can also bake down the prim definition, this won't flatten custom properties though.
dst_prim = stage.DefinePrim("/flattenedExample")
dst_prim = prim_def.FlattenTo(dst_prim)
# This will also flatten all metadata (docs etc.), this should only be used, if you need to export
# a custom schema to an external vendor. (Not sure if this the "official" way to do it, I'm sure
# there are better ones.)

Prim Type Info

The prim type info holds the composed type info of a prim. You can think of it as as the class that answers Python type() like queries for Usd. It caches the results of type name and applied API schema names, so that prim.IsA(<typeName>) checks can be used to see if the prim matches a given type.

from pxr import Sdf, Tf, Usd, UsdGeom
stage = Usd.Stage.CreateInMemory()
prim_path = Sdf.Path("/bicycle")
prim = stage.DefinePrim(prim_path, "Xform")
prim.ApplyAPI("GeomModelAPI")
print(prim.IsA(UsdGeom.Xform)) # Returns: True
print(prim.IsA(Tf.Type.FindByName('UsdGeomXform'))) # Returns: True
prim_type_info = prim.GetPrimTypeInfo()
print(prim_type_info.GetAppliedAPISchemas()) # Returns: ['GeomModelAPI']
print(prim_type_info.GetSchemaType()) # Returns: Tf.Type.FindByName('UsdGeomXform')
print(prim_type_info.GetSchemaTypeName()) # Returns: Xform

Schema Classes

We can lookup all registered schemas via the plugin registry as well as find out what plugin provided a schema.

Before we do that let's clarify some terminology:

  • Schema Type Name: The name of the schema class, e.g. Cube, Imageable, SkelBindingAPI
  • Tf.Type.typeName registry name: The full registered type name UsdGeomCube, UsdGeomImageable, UsdSkelBindingAPI

We can map from schema type name to Tf.Type.typeName via:

registry = Usd.SchemaRegistry()
registry.GetTypeFromName("Cube").typeName # Returns: "UsdGeomCube"

We can map from Tf.Type.typeName to schema type name via:

registry = Usd.SchemaRegistry()
registry.GetSchemaTypeName("UsdGeomCube") # Returns: "Cube"

Schema Registry

Let's list all the schemas:

from pxr import Plug, Tf, Usd
registry = Plug.Registry()
print(">>>>>", "Typed Schemas")
for type_name in registry.GetAllDerivedTypes(Usd.Typed):
    print(type_name)
print(">>>>>", "API Schemas")
for type_name in registry.GetAllDerivedTypes(Usd.APISchemaBase):
    print(type_name)

# For example to lookup where the "Cube" type is registered from,
# we can run:
print(">>>>>", "Cube Schema Plugin Source")
plugin = registry.GetPluginForType(Tf.Type.FindByName("UsdGeomCube"))
print(plugin.name)
print(plugin.path)
print(plugin.resourcePath)
print(plugin.metadata)

This allows us to also look up the Tf.Type from schema (type) names, which we can then use in IsA() checks.

from pxr import Plug, Sdf, Tf, Usd
registry = Usd.SchemaRegistry()

## Get Tf.Type registry entry (which allows us to get the Python class)
## The result can also be used to run IsA checks for typed schemas.
stage = Usd.Stage.CreateInMemory()
prim_path = Sdf.Path("/bicycleA")
prim = stage.DefinePrim(prim_path, "Cube")
print(prim.IsA(registry.GetTypeFromName("UsdGeomImageable"))) # Returns: True
print(prim.IsA(registry.GetTypeFromName("UsdGeomImageable").pythonClass)) # Returns: True


# GetTypeFromName allows prim type names and the Tf.Type.typeName.
print(registry.GetTypeFromName("UsdGeomCube"))      # Returns: Tf.Type("UsdGeomCube")
print(registry.GetTypeFromName("Cube"))             # Returns: Tf.Type("UsdGeomCube")
# For typed schemas we can also use:
print(registry.GetTypeFromSchemaTypeName("Imageable")) # Returns: Tf.Type('UsdGeomImageable') -> Tf.Type.typeName gives us 'UsdGeomImageable'
print(registry.GetTypeFromSchemaTypeName("Cube"))      # Returns: Tf.Type("UsdGeomCube") -> Tf.Type.typeName gives us 'UsdGeomCube'
print(registry.GetSchemaTypeName("UsdGeomImageable"))  # Returns: "Imageable"
print(registry.GetSchemaTypeName("UsdGeomCube"))       # Returns: "Cube"
# For concrete typed schemas:
print(registry.GetConcreteSchemaTypeName("UsdGeomCube"))  # Returns: "Cube"
print(registry.GetConcreteTypeFromSchemaTypeName("Cube")) # Returns: Tf.Type("UsdGeomCube")
# For API schemas:
print(registry.GetAPISchemaTypeName("UsdSkelBindingAPI"))  # Returns: "SkelBindingAPI"
print(registry.GetAPITypeFromSchemaTypeName("SkelBindingAPI")) # Returns: Tf.Type("UsdSkelBindingAPI")

A practical use case of looking thru the registry, is that we can grab the prim definitions. We can use these to inspect what properties a schema creates. We can use this to for example builds UIs that list all the schema attributes.

from pxr import Usd
registry = Usd.SchemaRegistry()
## Useful inspection lookups ##
# Find API schemas. This uses the `Schema Type Name` syntax:
cube_def = registry.FindConcretePrimDefinition("Cube")
print(cube_def.GetPropertyNames())
# Returns:
"""
['doubleSided', 'extent', 'orientation', 'primvars:displayColor', 
 'primvars:displayOpacity', 'purpose', 'size', 'visibility',
 'xformOpOrder', 'proxyPrim']
"""
skel_bind_def = registry.FindAppliedAPIPrimDefinition("SkelBindingAPI")
print(skel_bind_def.GetPropertyNames())
# Returns:
"""
['primvars:skel:geomBindTransform', 'primvars:skel:jointIndices',
 'primvars:skel:jointWeights', 'skel:blendShapes', 'skel:joints', 
 'skel:animationSource', 'skel:blendShapeTargets', 'skel:skeleton']
"""

Schema Kind

We can also inspect the schema kind. The kind defines (if we look at our inheritance tree in overview) what kind of schema it is.

The kind can be one of:

  • Usd.SchemaKind.AbstractBase
  • Usd.SchemaKind.AbstractTyped
  • Usd.SchemaKind.ConcreteTyped
  • Usd.SchemaKind.NonAppliedAPI
  • Usd.SchemaKind.SingleApplyAPI
  • Usd.SchemaKind.MultipleApplyAPI
from pxr import Plug, Sdf, Tf, Usd
### Check schema types ###
registry = Usd.SchemaRegistry()
## Typed Schemas ##
print(registry.IsTyped(UsdGeom.Cube))         # Returns: True
print(registry.IsTyped(UsdGeom.Imageable))    # Returns: True
print(registry.IsAbstract(UsdGeom.Imageable)) # Returns: True
print(registry.IsAbstract(UsdGeom.Cube))      # Returns: False
print(registry.IsConcrete(UsdGeom.Imageable)) # Returns: False
print(registry.IsConcrete(UsdGeom.Cube))      # Returns: True
# Also works with type name strings
print(registry.IsTyped("UsdGeomImageable"))   # Returns: True
print(registry.IsTyped("UsdGeomCube"))        # Returns: True
## API Schemas ##
print(registry.IsAppliedAPISchema("SkelBindingAPI"))      # Returns: True
print(registry.IsMultipleApplyAPISchema("CollectionAPI")) # Returns: True
## We can also ask by schema type name
print(registry.GetSchemaKind("Cube")) # Returns: pxr.Usd.SchemaKind.ConcreteTyped
print(registry.GetSchemaKind("Imageable")) # Returns: pxr.Usd.SchemaKind.AbstractTyped