Generating New Schema Classes

Generating New Schema Classes


VERIFIED ON USD VERSION 0.8.0

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.

UsdModel, 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:

  • 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. We are in-process as of 5/15 on establishing a convention that the C++/python class name for API schemas ends in "API".  In core USD, UsdModel is an (not-yet-conforming) example of an API schema; UsdRiLookAPI is an example from our RenderMan schema module, which adds/extracts RenderMan-specific shading information from a generic UsdShadeLook-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, UsdRiLookAPI::CreateBxdfRel() will create a relationship named riLook:bxdf.
  • 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.

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. Because the definition registry is keyed by prim typeName, a prim can "be" at most (or IsA) a single type, whereas a prim can host data for any number of API schemas, which cannot provide fallback values.

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 = [
        # You may have to adjust this path depending on where you have this file.
        # It needs to point to you installation's pxr/usd/lib/usd/schema.usda, or 
        # Another schema.usda which your schema is extending.  If you were adding
        # sub-classes of UsdGeom schema types, you would refer to 
        # pxr/usd/lib/usdGeom/schema.usda instead
        @../../../../pxr/usd/lib/usd/schema.usda@
    ]
)

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

Example Untyped (Non-Concrete) IsA Schema

A simple untyped IsA schema prim with one attribute and one relationship would look as follows:

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
    # amber/lib/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 Typed IsA Schema

The following is an example of a typed IsA schema class. It derives from the untyped </SimplePrim> defined above, specifies a 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 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 API schemas are not allowed to specify a fallback value for the attributes.

# API schemas only provide an interface to the prim's qualities.
# They are not allowed to specify a typeName.
class "ParamsAPI"
{
    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 . -t USD/pxr/usd/lib/usd/codegenTemplates
 
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
            wrote extras/usd/examples/usdSchemaExamples/paramsAPI.h
            wrote extras/usd/examples/usdSchemaExamples/paramsAPI.cpp
            wrote extras/usd/examples/usdSchemaExamples/wrapParamsAPI.cpp
            wrote extras/usd/examples/usdSchemaExamples/plugInfo.json
Generating Schematics:
            wrote extras/usd/examples/usdSchemaExamples/generatedSchema.usda

Compiling the Schema Prims

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

Build plugin using cmake
make -j <NUM_CORES> install

That should be it. To test that the plugin was installed correctly, create a usd file named Test.usda with the following content:

testenv/Test.usda
#usda 1.0

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

def Xform "Object"
{
    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.

Using the Schema Classes

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(obj);
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(obj);
print 'mass: %s' % paramsAPI.GetMassAttr().Get()
print 'velocity: %s' % paramsAPI.GetVelocityAttr().Get()
print 'volume: %s' % paramsAPI.GetVolumeAttr().Get()

Graphics Home