Advanced topics that separate USD practitioners โ extending USD for studio needs and diagnosing complex composition failures before they reach production.
Customizing USD (6%) and Debugging & Troubleshooting (11%) together represent ~17% of the NCP-OUSD exam. These questions are the most scenario-driven โ you'll be asked what tool or API call to use when something goes wrong, or which mechanism to choose when extending USD for a custom pipeline. Knowing the vocabulary (IsA vs API schema, plugInfo.json, GetPrimStack vs GetPropertyStack) is often enough to differentiate every answer choice.
Extend USD's type system with new IsA schemas (new prim types) and API schemas (add behavior to existing prims). Defined in .usda schema files and compiled with usdGenSchema to generate C++ headers and Python bindings.
Teach USD to read/write new file formats (e.g., .abc for Alembic, custom binary formats) by implementing a SdfFileFormat subclass in C++. USD transparently opens files by matching extension to your registered plugin.
API methods โ GetPrimStack, GetPropertyStack, GetResolveInfo โ let you diagnose what is composing and why unexpected values appear. These are the primary debugging tools on the exam.
Reduce load and render time via payload pruning, instancing, layer muting, prim population masks, and sparse traversal. Use Usd.Stage.OpenMasked() to load only the prim subtrees you need.
All USD extensions are plugins registered via plugInfo.json. Every custom schema, file format plugin, ArResolver, and Kind registry entry must have a corresponding plugInfo.json in a directory that USD can discover.
# plugInfo.json structure
{
"Plugins": [
{
"Type": "resource",
"Name": "myStudioPlugin",
"Root": ".",
"Info": {
"Types": {
"MyAsset": {
"bases": ["UsdTyped"],
"alias": { "UsdSchemaBase": "MyAsset" }
}
}
}
}
]
}
# Shell: point USD to your plugin directory
export PXR_PLUGINPATH_NAME=/studio/plugins/myStudioPlugin
An IsA schema defines a new prim type โ like UsdGeomMesh, but for your studio's custom data. Prims of this type appear in the USD type registry and can be queried via prim.GetTypeName().
# schema.usda โ defining a custom IsA schema
class MyAsset "MyAsset" (
inherits = </Typed>
doc = "A custom studio asset type."
) {
string asset:category = "" (doc = "Asset category, e.g. hero_prop")
float asset:lodBias = 1.0
}
# Python: create a prim of your custom type
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/World/Hero", "MyAsset")
print(prim.GetTypeName()) # "MyAsset"
# Access generated attribute helpers (after usdGenSchema)
myAsset = MyAsset(prim)
myAsset.GetAssetCategoryAttr().Set("hero_prop")
An API schema adds behavior to any prim without changing its type. Multiple API schemas can be applied to a single prim simultaneously โ something IsA schemas cannot do.
# schema.usda โ defining a custom applied API schema
class "RenderTagAPI" (
inherits = </APISchemaBase>
customData = { token[] apiSchemaType = "singleApply" }
) {
token render:tag = "default"
}
# Python: apply the API schema to any prim
from pxr import Usd
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/World/Cube", "Cube")
RenderTagAPI.Apply(prim)
api = RenderTagAPI(prim)
api.CreateRenderTagAttr().Set("proxy")
print(prim.GetAppliedSchemas()) # ["RenderTagAPI"]
File format plugins teach USD to open and save files in formats it doesn't natively support. Unlike schema plugins, file format plugins must be written in C++ only โ Python is not supported for this plugin type.
// C++ โ minimal SdfFileFormat subclass (header sketch)
class MyJsonFileFormat : public SdfFileFormat {
public:
MY_API bool Read(
SdfLayer* layer,
const std::string& resolvedPath,
bool metadataOnly) const override;
MY_API bool WriteToString(
const SdfLayer& layer,
std::string* str,
const std::string& comment = std::string()) const override;
};
// plugInfo.json entry for the file format
{
"Plugins": [{
"Type": "resource",
"Name": "myJsonFormat",
"Info": {
"SdfFileFormat": {
"Extensions": ["myjson"],
"Target": "usd",
"FormatId": "myJson"
}
}
}]
}
USD has built-in kinds: component, assembly, group, subcomponent. Studios can register custom kinds that extend these for pipeline validation.
# plugInfo.json โ registering a custom kind
{
"Plugins": [{
"Type": "resource",
"Name": "studioKinds",
"Info": {
"Kinds": {
"hero_prop": { "baseKind": "component" },
"env_piece": { "baseKind": "assembly" }
}
}
}]
}
# Python โ reading and setting kinds
from pxr import Usd, Kind
prim = stage.GetPrimAtPath("/World/Hero")
modelApi = Usd.ModelAPI(prim)
modelApi.SetKind("hero_prop")
print(modelApi.GetKind()) # "hero_prop"
print(Kind.Registry.IsA("hero_prop", "component")) # True
When no explicit variant selection is authored in any layer, USD uses variant fallback selections to provide default behavior. This prevents "empty" variant sets from leaving geometry incomplete.
# Python โ setting stage-wide variant fallback selections
from pxr import Usd
stage = Usd.Stage.Open("shot.usd")
# Stage-wide default: any unselected "lodVariant" defaults to "lo"
stage.SetMetadata("variantSelections", {"lodVariant": "lo"})
# Verify a prim's active variant selection
prim = stage.GetPrimAtPath("/World/Hero")
vs = prim.GetVariantSets()
sel = vs.GetVariantSelection("lodVariant")
print(sel) # "lo" (from fallback if not explicitly authored)
USD provides a full introspection API to diagnose composition. These methods are the first tools to reach for when attribute values are unexpected or prims are missing.
| Method | Returns | Use for |
|---|---|---|
prim.GetPrimStack() | List of Sdf.PrimSpec (strength order) | See all contributing specs for a prim |
attr.GetPropertyStack() | List of Sdf.PropertySpec | See all attribute opinions, strongest first |
attr.GetResolveInfo(time) | UsdResolveInfo object | Which layer/arc won for this attribute |
attr.GetResolveInfo().GetSource() | UsdResolveInfoSource enum | Value source: Default, TimeSamples, Fallback, NoValue |
prim.IsActive() | bool | Is prim active (visible to traversal)? |
prim.IsLoaded() | bool | Is prim's payload loaded? |
prim.IsDefined() | bool | Does a defining spec exist? |
prim.IsInstance() | bool | Is this prim a point instance? |
from pxr import Usd
stage = Usd.Stage.Open("complex_shot.usd")
prim = stage.GetPrimAtPath("/World/Hero")
# 1. X-ray the prim's composition
for spec in prim.GetPrimStack():
print(spec.layer.identifier, "->", spec.path)
# 2. Diagnose a surprising attribute value
attr = prim.GetAttribute("displayColor")
for prop_spec in attr.GetPropertyStack():
print(prop_spec.layer.identifier, "value:", prop_spec.default)
# 3. Find which layer's opinion won
resolve_info = attr.GetResolveInfo()
print("Source:", resolve_info.GetSource())
# Usd.ResolveInfoSourceDefault, TimeSamples, Fallback, NoValue
# 4. Check prim state
print("Active:", prim.IsActive())
print("Loaded:", prim.IsLoaded())
print("Has payloads:", prim.HasAuthoredPayloads())
Most debugging scenarios on the exam describe a symptom โ a wrong value, a missing prim, a wrong variant โ and ask what to do first. Match symptom to the right introspection tool.
# Debugging workflow โ unexpected variant behavior
prim = stage.GetPrimAtPath("/World/Hero")
# Step 1: which variant is actually active?
vs = prim.GetVariantSets()
active = vs.GetVariantSelection("lodVariant")
print("Active variant:", active)
# Step 2: look at all opinions on a suspicious attribute
attr = prim.GetAttribute("points")
stack = attr.GetPropertyStack()
for spec in stack:
print(f" [{spec.layer.identifier}] {spec.path} = {spec.default}")
# Step 3: is there a local override beating everything?
for spec in prim.GetPrimStack():
if spec.HasInfo("active") or spec.HasInfo("specifier"):
print(f" Defining spec in: {spec.layer.identifier}")
Large USD scenes can be slow to load and traverse. These techniques are tested in both the Debugging and Customizing sections of the exam.
from pxr import Usd
# Technique 1: Population mask โ load only /World/Hero subtree
mask = Usd.StagePopulationMask(["/World/Hero"])
stage = Usd.Stage.OpenMasked("bigScene.usd", mask)
# Everything outside /World/Hero is excluded from composition
# Technique 2: Load rules โ fine-grained payload control
rules = Usd.StageLoadRules()
rules.LoadAll()
rules.Unload("/World/Crowd") # exclude crowd from loading
stage.SetLoadRules(rules)
# Technique 3: Mute a layer
layer_id = "debugging_overrides.usda"
stage.MuteLayer(layer_id)
print(stage.IsLayerMuted(layer_id)) # True
# Technique 4: Sparse traversal โ skip inactive and unloaded prims
it = iter(stage.TraverseAll())
for prim in it:
if not prim.IsActive():
it.PruneChildren()
continue
process(prim)
These tools are frequently tested โ know what each one does and when to reach for it.
| Tool | What it does | When to use |
|---|---|---|
usdcat file.usda | Print ASCII representation of any USD file (incl. binary .usdc) | Quick composed-value inspection, no GUI needed |
usdview file.usd | Interactive viewer with composition inspector and attribute browser | Visual debugging, animated scenes, layer stack inspection |
usdchecker file.usd | Run compliance checks; --argsDict for custom checks | Authoring validation before delivery; automated pipelines |
usdresolve "asset:myAsset?v=3" | Test resolver output for a given asset path | Diagnosing asset resolution failures |
usdtree file.usd | Print prim hierarchy tree | Quick structural overview of a scene |
# Terminal examples
# Inspect a binary .usdc as if it were ASCII
usdcat hero_asset.usdc
# Run compliance checks
usdchecker shot_v03.usd
usdchecker --argsDict '{"rootLayerOnly": True}' shot_v03.usd
# Inspect prim hierarchy
usdtree complex_stage.usd
# Test asset resolver
usdresolve "omniverse://server/assets/hero.usd?v=latest"
# Open in interactive viewer
usdview shot_v03.usd
Every extension โ schema, file format, resolver, kind โ is registered here. PXR_PLUGINPATH_NAME env var points USD to your plugin directories. Plugins load at startup; restarting the process is required after adding a plugin.
IsA schema defines a new prim type (inherits from Typed). API schema adds behavior to any prim without changing its type โ Apply() it, then use the generated helper methods. Multiple API schemas can stack; only one IsA type per prim.
Shows every Sdf.PrimSpec contributing to a prim, strongest to weakest. When a value surprises you, call GetPrimStack() first. attr.GetPropertyStack() gives the same for individual attribute opinions.
Unloaded payloads exclude their entire subtree from composition โ the prim exists but has no geometry or children. Use population masks + load rules for camera-frustum culling in large scenes. prim.IsLoaded() checks the fence.
usdcat to inspect composed values without Python โ works on binary .usdc too. usdview for interactive inspection with the composition tree UI. usdchecker for automated compliance before delivery.
Unlike schema and resolver plugins (which can have Python bindings), file format plugins must be written entirely in C++. They implement SdfFileFormat::Read / Write and teach USD to open new file extensions natively.
Debugging questions (11%) are scenario-based: "a value is wrong, what do you call first?" Know GetPrimStack, GetPropertyStack, GetResolveInfo cold. Customizing USD (6%) tests schema vocabulary โ IsA vs API, plugInfo.json, file format plugin language requirement. A single wrong assumption on "C++ only" can cost you those questions.
Customizing USD builds directly on Data Modeling (13%) โ you must understand USD's type system before you can extend it. Debugging connects to Composition (23%) โ most debugging scenarios are composition failures traced via GetPrimStack. File format plugins also touch Asset Resolution (11%) โ resolvers and file formats work together to locate and parse files.