Generating New Schema Classes

Generating New Schema Classes


VERIFIED ON USD VERSION 21.08

Configure Environment

There are a couple of prerequisites to dealing with schema generation in USD.

  • usdGenSchema, our tool for generating C++ classes from a schema.usda file requires the jinja2 template substitution module and argpase modules be installed and available in your python syspath.
  • Ensure that you have setup your python environment properly, so it can find USD python modules. We'll refer to the install location of your USD build with USD_INSTALL_ROOT, this is determined in the build with the cmake flag, -DCMAKE_INSTALL_PREFIX.
Environment Variable Meaning Value
PYTHONPATH This is a path list which Python uses to find modules. $PYTHONPATH:USD_INSTALL_ROOT/lib/python/

For more information see our page on Advanced Build Configuration.


The files used in this tutorial are available in USD/extras/usd/examples/usdSchemaExamples/. The objective of this tutorial is to introduce the user to various types of schema classes and to provide instructions for generating, compiling and using them. For a detailed description of schema classes and the various options that usdGenSchema provides for customizing them, see API documentation for generating schemas

What is a Schema Class?

A schema class is simply a container of a UsdPrim that provides a layer of specific, named API atop the underlying scene graph. USD provides a code generator script called 'usdGenSchema' for creating new schema classes. For more info on the script and all of the options it provides see the references section below.

UsdModelAPI, UsdGeomImageable, UsdGeomMesh etc. are examples of schema classes generated using the script. There are other schemas in USD/pxr/usd/lib/usd under usdGeom, usdRi, usdShade and more!

Types of Schema Classes

copied from the API documentation...

Schema classes are classified into the following two types:

  • IsA schema - An IsA schema can impart a typeName to a prim in addition to providing an interface to a prim's qualities. Every IsA schema must derive from the core class UsdTyped, which is the base class for all typed schemas. Furthermore, an IsA schema can be concrete or non-concrete. An IsA schema will be concrete (or instantiable) if its schema declaration provides both a name for the schema (in quotes) and a typeName in the schema.usda file in which it is defined. A non-concrete (abstract) IsA schema provides only a name for the schema, and hence cannot be instantiated; non-concrete schemas exist to serve as super-classes for related sets of concrete IsA schemas. UsdGeomImageable is an example of a non-concrete IsA schema. UsdGeomScope is an example of a concrete, typed IsA schema.
  • API schema - An API schema provides an interface to a prim's qualities, but does not specify a typeName for the underlying prim. The prim's qualities include its inheritance structure, attributes, relationships etc. Since it cannot provide a typeName, an API schema is considered to be non-concrete. As a convention, the C++/python class name for an API schema must end with "API".  In core USD, UsdModelAPI is an example of an API schema; UsdRiMaterialAPI is an example from our RenderMan schema module, which adds/extracts RenderMan-specific shading information from a generic UsdShadeMaterial-typed prim.  Also by convention (with which usdGenSchema can help), the properties that "belong" to an API schema are typically namespaced with the base-name of the schema, camelCased.  For example, UsdRiMaterialAPI::CreateSurfaceAttr() will create an attribute named outputs:ri:surface. API schemas are classified into the following two types: 
    • Non-applied API schemas - If an API schema only provides an interface to certain core bits of metadata (like UsdModelAPI, which sets model kind and UsdClipsAPI, which sets value-clips related metadata) or if there is no use of recording the application of the API schema on a prim (for the purpose of interchange), we make it a non-applied API schema. Examples of non-applied API schemas include UsdModelAPI, UsdClipsAPI, UsdShadeConnectableAPI and UsdGeomPrimvarsAPI. Typically, non-applied API schemas can apply to any prim-type (eg, UsdClipsAPI) or to a known fixed set of prim types, like in the case of UsdShadeConnectableAPI which is only applicable to Shaders and NodeGraphs.
    • Applied API Schemas - If there is a need to record and discover whether an API schema has been applied to a prim, we make it an applied API schema. In general, API schemas that add one or more properties to a prim should be tagged as applied API schemas. A public Apply() method is auto-generated for applied API schemas. Once an API schema has been applied to a prim, prim.HasAPI<APISchemaType>() will return true. An applied API schema must be applied to a prim via a call to this method, for the schema object to evaluate to true when converted to a bool using the explicit bool conversion operator, and for the API schema's properties to become builtin properties for the prim. Examples of applied API schemas include UsdCollectionAPI (a multiple-apply schema), UsdGeomModelAPI and UsdGeomMotionAPI.  
      Applied API schemas are further classified into the following two sub-categories:
      • Single-Apply API Schemas - Applied API schemas that can only be applied once to a prim. For example: UsdGeomModelAPI and UsdGeomMotionAPI. 
      • Multiple-Apply API Schemas - Applied API schemas that can be applied multiple times on the same prim with different instance names. For example, UsdCollectionAPI, which has to be applied once per-collection owned by a prim. 

The definition of an IsA schema is published, at runtime, into an introspectable "schema definition registry", which is consulted by core Usd when performing property value resolution (i.e. retrieving a property's value at a given UsdTime).  This allows IsA schemas to provide fallback values for their properties, i.e., a value that the property will possess, even when none has been authored. 

The author of an API schema must decide on the type of API schema at the time of its creation by setting token-valued customData entry 'apiSchemaType' in the schema definition. It can be set to one of 'nonApplied', 'singleApply' or 'multipleApply'. When unspecified, the fallback apiSchemaType for an API schema is 'singleApply'. An API schema can only inherit from another compatible API schema with matching customData["apiSchemaType"] or from "/APISchemaBase" directly. This is enforced by the schema code generation script 'usdGenSchema'.   API schemas that are singleApply or multipleApply also populate the UsdSchemaRegistry at runtime, and when properly applied to a prim, are taken together with the prim's IsA type to produce the prim's UsdPrimTypeInfo, which is a complete "type signature", used as a key to retrieve the prim's UsdPrimDefinition, which is what USD actually uses to discover fallback values (among other things) during property value resolution, thus by applying an API schema, one is able to add builtin properties-with-fallbacks to any prim.

Although we will not cover it in this tutorial, you can add (and we have for many of the core schemas) custom methods to any generated IsA or API schema that will be preserved if/when you need to re-run usdGenSchema.  Custom methods are handy for providing computations, or authoring operations that require coordinated authoring of more than a single value on a property at once.

Schema Generation Prerequisites

The schema generation script 'usdGenSchema' is driven by a USD layer (typically named schema.usda). Every schema.usda layer must meet the following requirements in order for generated code to compile and work with USD core successfully.

  • Must specify the libraryName as layer metadata.
  • usd/schema.usda must exist in the layer stack, not necessarily as a direct subLayer.
  • Schema typenames must be unique across all libraries.
  • Attribute names and tokens must be camelCased valid identifiers.

In our examples, we'll use the following as the base layer (or starting point) for creating new schema classes in order to satisfy the first two requirements above.

#usda 1.0
(
    """ This file describes an example schema for code generation using
        usdGenSchema.
    """
    subLayers = [
        # To refer to schema types defined in schema.usda files from other libraries,
        # simply add comma-separated lines of the form @<library name>/schema.usda@.
        # In this example, we're referring to schema types from 'usd'. If you were 
        # adding sub-classes of UsdGeom schema types, you would use usdGeom/schema.usda 
        # instead
        @usd/schema.usda@
    ]
)

over "GLOBAL" (
    customData = {
        string libraryName       = "usdSchemaExamples"
        string libraryPath       = "./"
        string libraryPrefix     = "UsdSchemaExamples"
    }
) { 
} 

Example Typed, Non-Concrete, IsA Schema

A simple non-concrete IsA schema prim with one attribute and one relationship would look as follows.  The schema is Typed, but a "SimplePrim" cannot be instantiated on a UsdStage - it is likely a base-class for other, concrete schemas.

class "SimplePrim" (
    doc = """An example of an untyped schema prim. Note that it does not 
             specify a typeName"""
    # IsA schemas should derive from </Typed>, which is defined in the sublayer
    # usd/schema.usda.
    inherits = </Typed>
    customData = {
        # Provide a different class name for the C++ and python schema classes.
        # This will be prefixed with libraryPrefix.
        # In this case, the class name becomes UsdSchemaExamplesSimple.
        string className = "Simple"
    }
)  {
    int intAttr = 0 (
        doc = "An integer attribute with fallback value of 0."
    )
    rel target (
        doc = """A relationship called target that could point to another prim
                 or a property"""
    )
}

Example Concrete IsA Schema

The following is an example of a typed IsA schema class. It derives from the non-concrete </SimplePrim> defined above, specifies a concrete typeName 'ComplexPrim' and adds a string attribute with a fallback value.

class ComplexPrim "ComplexPrim" (
    doc = """An example of a untyped IsA schema prim"""
    # Inherits from </SimplePrim> defined in simple.usda.
    inherits = </SimplePrim>
    customData = {
        string className = "Complex"
    }
)  {
    string complexString = "somethingComplex"
}

Example Applied API Schema

The following is a simple example of an API schema that provides API for manipulating three custom double valued attributes. Note again that its encouraged to provide fallback values for the attributes in API schemas but currently these are not used for value resolution in USD.  Note also that API schemas are distinguished from Typed schemas in that they must derive from APISchemaBase.

# API schemas only provide an interface to the prim's qualities.
# They are not allowed to specify a typeName.
class "ParamsAPI" (
    # IsA schemas should derive from </APISchemaBase>, which is defined in the sublayer
    # usd/schema.usda.
    inherits = </APISchemaBase>
    customData = {
        token apiSchemaType = "singleApply"
    }
)
{
    double params:mass (
        # Informs schema generator to create GetMassAttr() instead of GetParamsMassAttr() method
        customData = {
            string apiName = "mass"
        }
        doc = "Double value denoting mass"
    )
    double params:velocity (
        customData = {
            string apiName = "velocity"
        }
        doc = "Double value denoting velocity"
    )
    double params:volume (
        customData = {
            string apiName = "volume"
        }
        doc = "Double value denoting volume"
    )
}

All of the above schema classes are available in the schema.usda file in extras/usd/examples/usdSchemaExamples/. You can run usdGenSchema to generate all the necessary files. See below for the list of files generated (or edited if they already exist).

usdGenSchema output
$ usdGenSchema schema.usda .
 
Processing schema classes:
SimplePrim, ComplexPrim, ParamsAPI
Loading Templates
Writing Schema Tokens:
        unchanged extras/usd/examples/usdSchemaExamples/tokens.h
        unchanged extras/usd/examples/usdSchemaExamples/tokens.cpp
        unchanged extras/usd/examples/usdSchemaExamples/wrapTokens.cpp
Generating Classes:
        unchanged extras/usd/examples/usdSchemaExamples/simple.h
        unchanged extras/usd/examples/usdSchemaExamples/simple.cpp
        unchanged extras/usd/examples/usdSchemaExamples/wrapSimple.cpp
        unchanged extras/usd/examples/usdSchemaExamples/complex.h
        unchanged extras/usd/examples/usdSchemaExamples/complex.cpp
        unchanged extras/usd/examples/usdSchemaExamples/wrapComplex.cpp
        unchanged extras/usd/examples/usdSchemaExamples/paramsAPI.h
        unchanged extras/usd/examples/usdSchemaExamples/paramsAPI.cpp
        unchanged extras/usd/examples/usdSchemaExamples/wrapParamsAPI.cpp
        unchanged extras/usd/examples/usdSchemaExamples/plugInfo.json
Generating Schematics:
        unchanged extras/usd/examples/usdSchemaExamples/generatedSchema.usda

Building the Schema

To rebuild the plugin, simply go to the root of your build directory and run.

Build plugin using cmake
cmake --build . --target install --config Release

Using the Schema Classes

Because this schema is an external plugin, the USD build must be told where to find it before it can be used. This can be done by either:

  • Setting the PXR_PLUGINPATH_NAME environment variable to the location of the plugin's resources directory.
    For example, if you are building the usdSchemaExamples plugin out of the USD source tree, this location will be <prefix>/share/usd/examples/plugin/usdSchemaExamples/resources
  • Copying usdSchemaExamples.so (on Windows, usdSchemaExamples.dll and .lib) and the usdSchemaExamples directory to <prefix>/plugin/usd

Create a usd file named Test.usda with the following content:

Test.usda
#usda 1.0

def ComplexPrim "Complex"
{
    string complexString = "a really complex string"
	int intAttr = 10
	add rel target = </Object>
}

def Xform "Object" (
    prepend apiSchemas = ["ParamsAPI"]
)
{
    custom double params:mass = 1.0;
    custom double params:velocity = 10.0;
    custom double params:volume = 4.0;
}

You should be able to load the above usda file in usdview without warnings or errors.

C++ Example

The following C++ code loads the above test scene, constructs schema prims and uses the API provided by schema code generation.

Schema Prim Usage C++ Example
UsdStageRefPtr stage = UsdStage::Open("/path/to/testenv/Test.usda");

UsdPrim cp = stage->GetPrimAtPath("/Complex")

UsdSchemaExamplesSimple simple(cp);
UsdRelationship target = simple.GetTargetRel();
UsdAttribute intAttr = simple.GetIntAttrAttr();

UsdSchemaExamplesComplex complex(cp);
std::cout << complex.GetComplexStringAttr().Get<string>() << std::endl;

UsdPrim obj = stage->GetPrimAtPath("/Object")
UsdSchemaExamplesParamsAPI paramsAPI = UsdSchemaExamplesParamsAPI::Apply(obj);
assert(obj.HasAPI<UsdSchemaExamplesParamsAPI>());
std::cout << "mass: " << paramsAPI.GetMassAttr().Get<double>() << std::endl;
std::cout << "velocity: " << paramsAPI.GetVelocityAttr().Get<double>() << std::endl;
std::cout << "volume: " << paramsAPI.GetVolumeAttr().Get<double>() << std::endl;

Python Example

The following python code loads the above test scene, constructs schema prims and uses the API provided by schema code generation.

Python Example
from pxr import Usd, UsdSchemaExamples
stage = Usd.Stage.Open("Test.usda")
cp = stage.GetPrimAtPath("/Complex")
simple = UsdSchemaExamples.Simple(cp)
target = simple.GetTargetRel()
intAttr = simple.GetIntAttrAttr()
complex = UsdSchemaExamples.Complex(cp)
print 'complexString: %s' % complex.GetComplexStringAttr().Get()
obj = stage.GetPrimAtPath("/Object")
paramsAPI = UsdSchemaExamples.ParamsAPI.Apply(obj);
assert obj.HasAPI(UsdSchemaExamples.ParamsAPI)
print 'mass: %s' % paramsAPI.GetMassAttr().Get()
print 'velocity: %s' % paramsAPI.GetVelocityAttr().Get()
print 'volume: %s' % paramsAPI.GetVolumeAttr().Get()

Graphics Home