đ Exam Guide đ Learning Path OpenUSD đ Dev Guide
Authoring, design with, or debugging composition arcs. A developer needs to know all of the composition arcs, how they work, and when and when it is appropriate to use each. The developer needs to be able to debug complex LIVRPS scenarios
đ More info
Composition arcs are the operators that allow USD (Universal Scene Description) to combine multiple layers of scene description in specific ways.
They define how opinions are discovered, ordered, and resolved across multiple files and layers.
Composition arcs are evaluated according to strength ordering (from weakest â strongest).
| Order | Arc Type | Purpose |
|---|---|---|
| L | Local (Layers) | Local changes |
| I | Inherit | Share properties from a base prim |
| V | VariantSet | Enable configuration switching |
| E | Relocate | Move prim paths |
| R | Reference | Bring in external USD layers |
| P | Payload | Lazy-load external content |
| S | Specialize | Stronger form of inheritance |
Mnemonic: LIVERPS â the strength ordering of composition arcs
Sublayer is a special composition mechanism:
- It does not support prim name changes
- It applies to all sublayers in the root layerâs LayerStack
- Direct opinions from every sublayer are consulted
- Commonly used for shot, sequence, and asset assembly
This makes sublayers ideal for non-destructive layering of work from multiple departments.
The following example shows how a shot composes multiple layers and includes an entire sequence, which itself is composed of additional layers.
Bellow is an example to introduce the concept of Flattering, we are running this script from the script editor in Omniverse. Each of the referenced layers has only an xform in their stages. You can also use the flatten option in the Layer's tab
| flattenShotUSD.py | shot_flattened.usda |
|---|---|
from pxr import Usd
import os
import time
# Folder where your USD files live
BASE_PATH = r"YOUR_PATH"
SHOT_FILENAME = "shot.usd"
def flatten_usd_file(input_path: str):
"""Open a USD file, flatten its composed stage, and save to *_flattened.usd."""
if not os.path.isfile(input_path):
raise RuntimeError(f"Input USD file does not exist: {input_path}")
# Open the stage from the given file
stage = Usd.Stage.Open(input_path)
if not stage:
raise RuntimeError(f"Failed to open stage from: {input_path}")
# Build output path in the same folder
folder = os.path.dirname(input_path)
base, ext = os.path.splitext(os.path.basename(input_path))
output_path = os.path.join(folder, f"{base}_flattened{ext}")
# Measure flattening time
start_time = time.time()
# Flatten the composed stage
flattened_layer = stage.Flatten()
# Export flattened layer
if not flattened_layer.Export(output_path):
raise RuntimeError(f"Failed to export flattened USD: {output_path}")
end_time = time.time()
elapsed_seconds = end_time - start_time
print(f"â
Flattened stage saved to:\n{output_path}")
print(f"â± Flattening time: {elapsed_seconds:.3f} seconds")
return elapsed_seconds
def main():
shot_path = os.path.join(BASE_PATH, SHOT_FILENAME)
print(f"--- Flattening {SHOT_FILENAME} ---")
elapsed = flatten_usd_file(shot_path)
# If you want to use the variable later, it's in `elapsed`
# print(f"Flatten time (seconds): {elapsed:.3f}")
if __name__ == "__main__":
main()
|
In the next example, please note that your FPS will affect the real time.
Layer offsets allow TimeSamples to be shifted and scaled when a layer is brought in via Sublayers or References.
They are commonly used to:
- Retime animation clips
- Reuse animation data non-destructively
- Align animation in time without modifying the source layer
| example.usd | notes |
|---|---|
#usda 1.0
(
subLayers = [
@./someAnimation.usd@ (offset = 10; scale = 0.5)
]
) |
This USD will resolve as next:
A timesample of 30 in the someAnimation will be
resolved here at: 30*0.5 + 10 = 25.
Layer offsets cannot vary themselves over time |
The ordered set of layers resulting from the recursive gathering of all SubLayers of a Layer, plus the layer itself as first and strongest.
đ§ Exercise (SubLayers) - Material
What happens to âoversâ when their underlying prim is moved to a different location in the scenegraph?ï
| Sequence.usd/th> | notes |
|---|---|
#usda 1.0
def Xform "World"
{
def Xform "Sets"
{
def Xform "Bookshelf"
{
def Xform "Book_1"
{
string name = "Toy Story"
}
}
def Xform "Desk"
{
}
}
} |
In this example, the book defined in sequence.usda has the title âToy Storyâ. However, this layer is brought in to shot.usda via a sublayer statement, and the bookâs title is overridden to âWall-E.â The question is: what happens if a user independently working on sequence.usda moves Book_1 to Desk, or if Book_1 is renamed to Video_1? In such a case, the âoverâ in shot.usda would be âorphanedâ and be ignored when composing and evaluating /World/Sets/Desk/Book_1 in shot.usda. It is the responsibility of the user working on sequence.usda to ensure that shot.usda is updated to avoid this problem. |
Inherits is a composition arc that addresses the problem of adding a single, non-destructive edit (override) that can affect a whole class of distinct objects on a stage. Inherits acts as a non-destructive âbroadcastâ operator that applies opinions authored on one prim to every other prim that inherits the âsourceâ prim; not only do property opinions broadcast over inherits arcs - all scene description, hierarchically from the source, inherits.
đ More info
| Trees.usd demonstrating inherits |
Forest.usd demonstrating inherits propagation through references |
Instanceable Trees |
|---|---|---|
#usda 1.0
class Xform "_class_Tree"
{
def Mesh "Trunk"
{
color3f[] :displayColor = [(.8, .8, .2)]
}
def Mesh "Leaves"
{
color3f[] primvars:displayColor = [(0, 1, 0)]
}
}
def "TreeA" (
inherits = </_class_Tree>
)
{
}
def "TreeB" (
inherits = </_class_Tree>
)
{
over "Leaves"
{
color3f[] primvars:displayColor = [(0.8, 1, 0)]
}
}
|
#usda 1.0
# A new prim, of the same name as the original inherits target,
providing new overrides
class "_class_Tree"
{
token size = "small"
# It's autumn in California
over "Leaves"
{
color3f[] primvars:displayColor = [(1.0, .1, .1)]
}
}
# TreeB_1 still inherits from _class_Tree because its
referent does
def "TreeB_1" (
references = @./Trees.usd@</TreeB>
)
{
}
|
#usda 1.0
class Xform "_class_Tree"
{
def Mesh "Trunk"
{
color3f[] primvars:displayColor = [(.8, .8, .2)]
}
def Mesh "Leaves"
{
color3f[] primvars:displayColor = [(0, 1, 0)]
}
}
def "TreeA" (
inherits = </_class_Tree>
instanceable = true
)
{
}
def "TreeB" (
inherits = </_class_Tree>
instanceable = true
)
{
over "Leaves"
{
color3f[] primvars:displayColor = [(0.8, 1, 0)]
}
} |
A prim can inherit from any prim that is neither a descendant nor ancestor of itself, regardless of the primâs specifier or type.
The key difference between references and inherits is that references fully encapsulate their targets, and therefore âdisappearâ when composed through another layer of referencing, whereas the relationship between inheritors and their inherits target remains âliveâ through arbitrary levels of referencing.
Apply the resolved variant selections to all VariantSets that affect the PrimSpec at path in the LayerStack, and iterate through the selected Variants on each VariantSet. For each target, recursively apply LIVERP evaluation on the targeted LayerStack - Note that the âSâ is not present - we ignore Specializes arcs while recursing A VariantSet is a composition arc that allows a content creator to package a discrete set of alternatives, between which a downstream consumer is able to non-destructively switch, or augment.
A variant can contain overriding opinions (for properties, metadata, and more), as well as any arbitrary scene description (entire child prim subtrees, etc). Variants can also include additional composition arcs.
| simpleVariantSet.usd | VariantSet with references |
|---|---|
#usda 1.0
def Xform "Implicits" (
append variantSets = "shapeVariant"
)
{
variantSet "shapeVariant" = {
"Capsule" {
def Capsule "Pill"
{
}
}
"Cone" {
def Cone "PartyHat"
{
}
}
"Cube" {
def Cube "Box"
{
}
}
"Cylinder" {
def Cylinder "Tube"
{
}
}
"Sphere" {
def Sphere "Ball"
{
}
}
}
} |
over "Model" (
prepend variantSets = "referenceVariantSet"
variants = {
string referenceVariantSet = "asset1"
}
)
{
variantSet "referenceVariantSet" = {
"asset1" (
prepend references = @Asset1.usda@
) {
}
"asset2" (
prepend references = @Asset2.usda@
) {
}
}
} |
VariantSets can be nested directly inside each other, on the same prim.
| nestedVariants.py |
|---|
from pxr import Sdf, Usd
stage = Usd.Stage.CreateNew("nestedVariants.usd")
prim = stage.DefinePrim("/Employee")
title = prim.CreateAttribute("title", Sdf.ValueTypeNames.String)
variantSets = prim.GetVariantSets()
critters = [ "Bug", "Bear", "Dragon" ]
jobs = [ "Squasher", "Rider", "Trainer" ]
critterVS = variantSets.AppendVariantSet("critterVariant")
for critter in critters:
critterVS.AppendVariant(critter)
critterVS.SetVariantSelection(critter)
with critterVS.GetVariantEditContext():
# All edits now go "inside" the selected critter variant
jobVS = variantSets.AppendVariantSet("jobVariant")
for job in jobs:
if (job != "Squasher" or critter == "Bug") and \
(job != "Rider" or critter != "Bug") :
jobVS.AppendVariant(job)
jobVS.SetVariantSelection(job)
with jobVS.GetVariantEditContext():
# Now edits *additionally* go inside the selected job variant
title.Set(critter + job)
stage.GetRootLayer().Save() |
Basically we are making a variantSet and attribute depending on other varianSets
đ i.e. (Add VariantSets in Python) đ i.e. (Add Multiple Variants in Python) đ i.e. (Edit VariantSets in Python)
đ More info
Relocates is a composition arc that maps a prim path defined in a remote LayerStack (i.e. across a composition arc) to a new path location in the local namespace (these paths can only be prim paths, not property paths).
| refLayer.usda | main.usda | flattened main.usda |
|---|---|---|
def "PrimA" ()
{
def "PrimAChild" ()
{
uniform string testString = "test"
float childValue = 3.5
}
} |
#usda 1.0
(
relocates = {
</MainPrim/PrimAChild> : </MainPrim/RenamedPrimAChild>
}
)
def "MainPrim" (
prepend references = @refLayer.usda@</PrimA>
)
{
over RenamedPrimAChild
{
float childValue = 5.2
}
} |
def "MainPrim"
{
def "RenamedPrimAChild"
{
float childValue = 5.2
uniform string testString = "test"
}
} |
- You cannot relocate a root prim.
- When a source path is relocated, that original source path is considered no longer valid in the current namespace. Any local opinions authored on a source path will generate a âinvalid option at relocation source pathâ error.
- Relocates that would create invalid or conflicting namespace paths are not allowed
| main.usda with added class and inherits |
flattened main.usda with inherits and relocates applied |
|---|---|
#usda 1.0
(
relocates = {
</MainPrim/PrimAChild> : </MainPrim/RenamedPrimAChild>
}
)
class "WorkClass"
{
float childValue = 20.5
uniform string testString = "from WorkClass"
}
def "MainPrim" (
prepend references = @refLayer.usda@</PrimA>
)
{
def "RenamedPrimAChild"
(
inherits = </WorkClass>
)
{
}
} |
#usda 1.0
(
relocates = {
</MainPrim/PrimAChild> : </MainPrim/RenamedPrimAChild>
}
)
def "MainPrim" (
prepend references = @refLayer.usda@</PrimA>
)
{
over RenamedPrimAChild
{
float childValue = 5.2
}
} |
A relocated prim will still inherit the same opinions it would have had it not been relocated. This can result in some subtle composition behavior.
In the next flattened stage, /Model/Rig/LRig in model.usda has inherited from /ClassA/Rig/LRig even though it was relocated to /Model/Anim/LAnim in that layer, and does not inherit opinions from /ClassA/Anim/LAnim. However, note that /Model_1/Anim/LAnim in the root.usda layer does inherit from the layerâs /ClassA/Anim/LAnim.
| model.usda with ClassA and Model that inherits from ClassA |
root.usda | flattened root.usda/th> |
|---|---|---|
#usda 1.0
(
relocates = {
</Model/Rig/LRig>: </Model/Anim/LAnim>
}
)
class "ClassA"
{
def "Rig"
{
def "LRig"
{
uniform token modelClassALRig = "test"
}
}
def "Anim"
{
def "LAnim"
{
uniform token modelClassALAnim = "test"
}
}
}
def "Model" (
inherits = </ClassA>
)
{
} |
def "Model_1" (
references = @./model.usda@</Model>
)
{
}
class "ClassA"
{
def "Rig"
{
def "LRig"
{
uniform token rootClassALRig = "test"
}
}
def "Anim"
{
def "LAnim"
{
uniform token rootClassALAnim = "test"
}
}
} |
def "Model_1"
{
def "Rig"
{
}
def "Anim"
{
def "LAnim"
{
uniform token modelClassALRig = "test"
uniform token rootClassALAnim = "test"
uniform token rootClassALRig = "test"
}
}
} |
One aspect of relocates and composition is that relocates will ignore all ancestral arcs except variant arcs when we build the PrimIndex for a prim. So, if you had a layer that relocates a prim to be the child of a prim with an ancestral inherits arc:
Next with the relocates for /PrimA/Child to /PrimWithInherits/Child, the ancestral opinions from /ClassA/Child are ignored.
| layer.usd | flattened layer |
|---|---|
#usda 1.0
(
relocates = {
</PrimA/Child>: </PrimWithInherits/Child>
}
)
def "ClassA"
(
)
{
def "Child"
{
uniform token testString = "from ClassA/Child"
uniform token classAChildString = "test"
}
}
def "RefPrim"
(
)
{
def "Child"
{
uniform token testString = "from RefPrim/Child"
uniform token refPrimChildString = "test"
}
}
def "PrimA"
(
prepend references = </RefPrim>
)
{
}
def "PrimWithInherits"
(
inherits = </ClassA>
)
{
} |
def "PrimWithInherits"
{
def "Child"
{
uniform token refPrimChildString = "test"
uniform token testString = "from RefPrim/Child"
}
} |
Next we see as ancestral variant arcs will still compose with relocate
| layer.usd | flattened layer |
|---|---|
#usda 1.0
(
relocates = {
</PrimA/Child>: </PrimWithInherits/Child>
}
)
def "ClassA"
(
)
{
def "Child"
{
uniform token testString = "from ClassA/Child"
uniform token classAChildString = "test"
}
}
def "RefPrim"
(
)
{
def "Child"
{
uniform token testString = "from RefPrim/Child"
uniform token refPrimChildString = "test"
}
}
def "PrimA"
(
prepend references = </RefPrim>
)
{
}
def "PrimWithInherits"
(
# Removed inherits of ClassA
# Added variantSet and selection with authored Child opinions
variants = {
string varSet = "Set1"
}
prepend variantSets = "varSet"
)
{
variantSet "varSet" = {
"Set1" ()
{
def "Child"
{
uniform token testString = "from varSet Child"
uniform token varChildString = "test"
}
}
"Set2" ()
{
}
}
} |
def "PrimWithInherits"
{
def "Child"
{
uniform token refPrimChildString = "test"
uniform token testString = "from varSet Child"
uniform token varChildString = "test"
}
} |
đ More info
Resolve the References affecting the prim at path, and iterate through the resulting targets. For each target, recursively apply LIVERP evaluation on the targeted LayerStack - Note that the âSâ is not present - we ignore Specializes arcs while recursing.
| Marble.usd | MarbleCollection.usd | FlattenedMarbleCollection.usd |
|---|---|---|
#usda 1.0
(
defaultPrim = "Marble"
)
def Xform "Marble" (
kind = "component"
)
{
def Sphere "marble_geom"
{
color3f[] primvars:displayColor = [ (0, 1, 0) ]
}
} |
#usda 1.0
def Xform "MarbleCollection" (
kind = "assembly"
)
{
def "Marble_Green" (
references = @Marble.usd@
)
{
double3 xformOp:translate = (-10, 0, 0)
uniform token[] xformOpOrder = [ "xformOp:translate" ]
}
def "Marble_Red" (
references = @Marble.usd@
)
{
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = [ "xformOp:translate" ]
over "marble_geom"
{
color3f[] primvars:displayColor = [ (1, 0, 0) ]
}
}
} |
#usda 1.0
def Xform "MarbleCollection" (
kind = "assembly"
)
{
def Xform "Marble_Green" (
kind = "component"
)
{
double3 xformOp:translate = (-10, 0, 0)
uniform token[] xformOpOrder = [ "xformOp:translate" ]
def Sphere "marble_geom"
{
color3f[] primvars:displayColor = [ (0, 1, 0) ]
}
}
def Xform "Marble_Red" (
kind = "component"
)
{
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = [ "xformOp:translate" ]
def Sphere "marble_geom"
{
color3f[] primvars:displayColor = [ (1, 0, 0) ]
}
}
} |
References can target any prim in a LayerStack, excepting ancestors of the prim containing the reference
A Relationship is a ânamespace pointerâ that is robust in the face of composition arcs, which means that when you ask USD for a relationshipâs targets, USD will perform all the necessary namespace-manipulations required to translate the authored target value into the scene-level namespace.
| Marble.usd |
|---|
#usda 1.0
(
defaultPrim = "Marble"
)
def Xform "Marble" (
kind = "component"
)
{
def Sphere "marble_geom"
{
rel material:binding = </Marble/GlassMaterial>
color3f[] primvars:displayColor = [ (0, 1, 0) ]
}
def Material "GlassMaterial"
{
# Interface inputs, shading networks, etc.
}
} |
| shot.usda | asset.usda |
|---|---|
#usda 1.0
over "Class"
{
over "B"
{
over "Model"
{
int a = 3
}
}
}
over "A"
{
over "B" (
# variant selection won't be used
variants = {
string type = "b"
}
)
{
}
}
def "ReferencedModel" (
references = @./asset.usda@</A/B/Model>
)
{
}
|
#usda 1.0
class "Class"
{
}
def "A" (
inherits = </Class>
)
{
token purpose = "render"
def "B" (
variantSets = "type"
variants = {
string type = "a"
}
)
{
variantSet "type" = {
"a" {
def "Model"
{
int a = 1
}
}
"b" {
def "Model"
{
int a = 2
}
}
}
}
} |
đ More info
A Payload is a composition arc that is a special kind of a Reference. It is different from references primarily in:
- The targets of References are always consumed greedily by the indexing algorithm that is used to open and build a Stage. When a Stage is opened with UsdStage::InitialLoadSet::LoadNone specified, Payload arcs are recorded, but not traversed. This behavior allows clients to manually construct a âworking setâ that is a subset of the whole scene, by loading just the bits of the scene that they require.
Specializes is a composition arc that allows a specialized prim to be continuously refined from a base prim, through unlimited levels of referencing.
| Robot.usd | RobotScene.usd |
|---|---|
#usda 1.0
def Xform "Robot"
{
def Scope "Materials"
{
def Material "Metal"
{
# Interface inputs drive shader parameters of the encapsulated
# network. We are not showing the connections, nor how we encode
# that the child Shader "Surface" is the primary output for the
# material.
float inputs:diffuseGain = 0
float inputs:specularRoughness = 0
def Shader "Surface"
{
asset info:id = @PxrSurface@
}
}
def Material "CorrodedMetal" (
specializes = </Robot/Materials/Metal>
)
{
# specialize roughness...
float inputs:specularRoughness = 0.2
# Adding a pattern to drive Surface bump
def Shader "Corrosion"
{
asset info:id = @PxrOSL@
vector3f outputs:disp
}
over "Surface"
{
# Override that would connect specularBump to Corrosion
# pattern's "outputs:disp" attribute
}
}
}
} |
#usda 1.0
def Xform "World"
{
def Xform "Characters"
{
def "Rosie" (
references = @./Robot.usd@</Robot>
)
{
over "Materials"
{
over "Metal"
{
float inputs:diffuseGain = 0.3
float inputs:specularRoughness = 0.1
}
}
}
}
} |
In the above example if you examine the flattened RobotScene.usd you will see the effect of specializes on the specialized /World/Characters/Rosie/Materials/CorrodedMetal prim: we overrode both diffuseGain and specularRoughness on the base Metal material, but only the diffuseGain propagates onto /World/Characters/Rosie/Materials/CorrodedMetal, because specularRoughness was already refined on the referenced /Robot/Materials/CorrodedMetal prim. This also demonstrates the difference between specializes and inherits: if you change the specializes arc to inherits in Robot.usd and recompose the scene, you will see that both diffuseGain and specularRoughness propagate onto /World/Characters/Rosie/Materials/CorrodedMetal.
Create modular, reusable components, leverage instancing (native and point) to optimize a scene, and apply different strategies for overriding an instanced asset for efficient, optimized, and collaborative aggregation of assets (models) to build large scenes.
Model kinds are primâlevel metadata that classify a primâs role in the model hierarchy.
The model hierarchy defines a contiguous set of prims descending from a root prim on a stage, all of which are models. Model hierarchy is an index of the scene that is, strictly, a prefix, of the entire scene.
Group, assembly, component all inherit from the base kind âmodelâ Subcomponent is the outlier
Only group models (group or assembly) can contain other models, and a prim can only be a model if its parent is also a group model (except the root).
| Structure |
|---|
model
component
group
assembly
subcomponent |
Group models serve as containers that can have other model children (unlike component models which are leaves), with assemblies being a specific type of group. Whereas the specialized group-model kind assembly generally identifies group models that are published assets, groups tend to be simple âinlinedâ model prims defined inside and as part of assemblies. They are the âglueâ that holds a model hierarchy together.
| Assembly example |
|---|
def Xform "Forest_set" (
kind = "assembly"
)
{
def Xform "Outskirts" (
kind = "group"
)
{
# More deeply nested groups, bottoming out at references to other assemblies and components
}
def Xform "Glade" (
kind = "group"
)
{
# More deeply nested groups, bottoming out at references to other assemblies and components
}
} |
Next exercises requires USDVIEW
đ§ Exercise (Groups) - Material
A component is a reusable, self-contained asset that is complete and referenceable Complete assets like props, characters, or set pieces. They can contain subcomponents but cannot contain other models A component is a âleafâ kind of Model. Components can contain subcomponents, but no other models. Components can reference in other assets that are published as models, but they should override the kind of the referenced prim to âsubcomponentâ.
| overriding the kind of a ânestedâ asset reference |
|---|
def Xform "TreeSpruce" (
kind = "component"
)
{
# Geometry and shading prims that define a Spruce tree...
def "Cone_1" (
kind = "subcomponent"
references = @Cones/PineConeA.usd@
)
{
}
} |
đ More info
Stage traversal is the process of traversing the scenegraph of a stage with the purpose of querying or editing the scene data. We can traverse the scenegraph by iterating through child prims, accessing parent prims, and traversing the hierarchy to find specific prims of interest.
from pxr import Usd
stage: Usd.Stage = Usd.Stage.Open("_assets/stage_traversal.usda")
for prim in stage.Traverse():
# Print the path of each prim
print(prim.GetPath()) |
from pxr import Usd, UsdGeom
stage: Usd.Stage = Usd.Stage.Open("_assets/stage_traversal.usda")
scope_count = 0
xform_count = 0
for prim in stage.Traverse():
if UsdGeom.Scope(prim):
scope_count += 1
print("Scope Type: ", prim.GetName())
elif UsdGeom.Xform(prim):
xform_count +=1
print("Xform Type: ", prim.GetName())
print("Number of Scope prims: ", scope_count)
print("Number of Xform prims: ", xform_count) |
from pxr import Usd
stage: Usd.Stage = Usd.Stage.Open("_assets/stage_traversal.usda")
prim_range = Usd.PrimRange(stage.GetPrimAtPath("/World/Box"))
for prim in prim_range:
print(prim.GetPath()) |
| Python code | USDA |
|---|---|
from pxr import Usd, UsdGeom, Kind, Gf
# Create stage and model root
file_path = "_assets/model_kinds_component.usda"
stage = Usd.Stage.CreateNew(file_path)
world_xform = UsdGeom.Xform.Define(stage, "/World")
stage.SetDefaultPrim(world_xform.GetPrim())
# Make /World a group so children can be models
Usd.ModelAPI(world_xform.GetPrim()).SetKind(Kind.Tokens.group)
# Non-model branch: Markers (utility geometry, no kind)
markers = UsdGeom.Scope.Define(stage, world_xform.GetPath().AppendChild("Markers"))
points = {
"PointA": Gf.Vec3d(-3, 0, -3), "PointB": Gf.Vec3d(-3, 0, 3),
"PointC": Gf.Vec3d(3, 0, -3), "PointD": Gf.Vec3d(3, 0, 3)
}
for name, pos in points.items():
cone = UsdGeom.Cone.Define(stage, markers.GetPath().AppendChild(name))
UsdGeom.XformCommonAPI(cone).SetTranslate(pos)
cone.CreateDisplayColorPrimvar().Set([Gf.Vec3f(1.0, 0.85, 0.2)])
# Model branch: a Component we want to place as a unit
component = UsdGeom.Xform.Define(stage, world_xform.GetPath().AppendChild("Component"))
Usd.ModelAPI(component.GetPrim()).SetKind(Kind.Tokens.component)
body = UsdGeom.Cube.Define(stage, component.GetPath().AppendChild("Body"))
body.CreateDisplayColorPrimvar().Set([(0.25, 0.55, 0.85)])
UsdGeom.XformCommonAPI(body).SetScale((3.0, 1.0, 3.0))
# Model-only traversal: affect models, ignore markers
for prim in Usd.PrimRange(stage.GetPseudoRoot(), predicate=Usd.PrimIsModel):
if prim.IsComponent():
xformable = UsdGeom.Xformable(prim)
if xformable:
UsdGeom.XformCommonAPI(xformable).SetTranslate((0.0, 2.0, 0.0))
# Show which prims were considered models
model_paths = [p.GetPath().pathString for p in Usd.PrimRange(stage.GetPseudoRoot(), predicate=Usd.PrimIsModel)]
print("Model prims seen by traversal:", model_paths)
stage.Save() |
#usda 1.0
(
defaultPrim = "World"
)
def Xform "World" (
kind = "group"
)
{
def Scope "Markers"
{
def Cone "PointA"
{
color3f[] primvars:displayColor = [(1, 0.85, 0.2)]
double3 xformOp:translate = (-3, 0, -3)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cone "PointB"
{
color3f[] primvars:displayColor = [(1, 0.85, 0.2)]
double3 xformOp:translate = (-3, 0, 3)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cone "PointC"
{
color3f[] primvars:displayColor = [(1, 0.85, 0.2)]
double3 xformOp:translate = (3, 0, -3)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cone "PointD"
{
color3f[] primvars:displayColor = [(1, 0.85, 0.2)]
double3 xformOp:translate = (3, 0, 3)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
def Xform "Component" (
kind = "component"
)
{
double3 xformOp:translate = (0, 2, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
def Cube "Body"
{
color3f[] primvars:displayColor = [(0.25, 0.55, 0.85)]
float3 xformOp:scale = (3, 1, 3)
uniform token[] xformOpOrder = ["xformOp:scale"]
}
}
} |
The Four Principles of Scalable Asset Structure
- Legibility ensures your asset structure is easy to understand and interpret.
- Modularity allows for flexibility and reusability.
- Performance ensures your asset structure is efficient and optimized.
- Navigability makes it easy for users to find and access the features and properties they need.
The following example shows a single prim with two collections. The relCollection collection is a relationship-mode collection that includes all objects at the /World/Clothing/Shirts and /World/Clothing/Pants paths. The expCollection collection is a pattern-based collection that matches all objects at the /World/Clothing/Shirts path that start with âRedâ, and any descendants of those objects.
def "CollectionPrim" (
prepend apiSchemas = ["CollectionAPI:relCollection", "CollectionAPI:expCollection"]
)
{
# Specify collection membership using "includes"
rel collection:relCollection:includes = [
</World/Clothing/Shirts>,
</World/Clothing/Pants>,
]
# Specify collection membership using a path expression
pathExpression collection:expCollection:membershipExpression = "/World/Clothing/Shirts/Red*//"
} |
If you want go deeper in collections, in the next link you will find detailed info with examples.
đ More info
Each asset designed to be opened as a stage or added to a scene through referencing has a root layer that serves as its foundation.
Assets should model workstreams into layers.
Asset parameterization enables the reuse of content by allowing certain fields and properties to vary downstream.
Instead of expecting users to know whether a complex asset requires payloading, many assets adopt the âreference-payloadâ pattern. This means their interface file is designed to be referenced, with the payload structure internal to the asset.
We use Lofting to show properties from payloaded assets to the layer from which we are referencing
Instancing in USD is a feature that allows many instances of âthe sameâ object to share the same representation (composed prims) on a UsdStage. Instances can be overridden in stronger layers, so it is possible to âbreakâ an instance when necessary, if it must be uniquified.
Instancing in USD is a feature that allows many instances of âthe sameâ object to share the same representation (composed prims) on a UsdStage. In exchange for this sharing of representation (which provides speed and memory benefits both for the USD core and, generally, for clients processing the UsdStage), we give up the ability to uniquely override opinions on prims beneath the âinstance rootâ, although it is possible to override opinions that will affect all instancesâ views of the data.
In the next example, we create a class _Class_Cube_Red, then three cubes are instances of that class, the first two have modified position, the second has change opinion to a blue material, but is weaker so still will be red, the third is the same but this time is stronger, so the third cube will be blue without modifying the instance status.
#usda 1.0
def Xform "World" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "weakerThanDescendants"
)
class Scope "_Class_Cube_Red"
{
def Mesh "Cube" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [0, 1, 3, 2, 4, 6, 7, 5, 6, 2, 3, 7, 4, 5, 1, 0, 4, 0, 2, 6, 5, 7, 3, 1]
rel material:binding = </World/_Class_Cube_Red/Looks/OmniPBR_Red> (
bindMaterialAs = "weakerThanDescendants"
)
normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0)] (
interpolation = "faceVarying"
)
point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5)]
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1), (1, 0), (1, 1), (0, 1), (0, 0), (0, 1), (0, 0), (1, 0), (1, 1), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (1, 0), (1, 1), (0, 1), (0, 0)] (
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "none"
quatd xformOp:orient = (1, 0, 0, 0)
double3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (0, 1, 1)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Scope "Looks"
{
def Material "OmniPBR_Red"
{
token outputs:mdl:displacement.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.90759075, 0.12366932, 0.01797209)
token outputs:out (
renderType = "material"
)
}
}
}
}
def Xform "Cube_01" (
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (1.6772258843990702, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Xform "Cube_02" (
prepend apiSchemas = ["MaterialBindingAPI"]
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "strongerThanDescendants"
)
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (0, 1.8461749821880287, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Xform "Cube_03" (
prepend apiSchemas = ["MaterialBindingAPI"]
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "weakerThanDescendants"
)
}
def Scope "Looks"
{
def Material "OmniPBR_Yellow"
{
token outputs:mdl:displacement.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.882679, 0.90759075, 0.06889302)
token outputs:out (
renderType = "material"
)
}
}
def Material "OmniPBR_Blue"
{
token outputs:mdl:displacement.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.029408854, 0.08059826, 0.8910891)
token outputs:out (
renderType = "material"
)
}
}
}
} |
def Xform "World"
{
class Scope "_Class_Cube_Red"
{
def Mesh "Cube" (
prepend apiSchemas = ["MaterialBindingAPI"]
variants = {
string ChangeColor = "Variant"
}
prepend variantSets = "ChangeColor"
)
{
float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [0, 1, 3, 2, 4, 6, 7, 5, 6, 2, 3, 7, 4, 5, 1, 0, 4, 0, 2, 6, 5, 7, 3, 1]
normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0)] (
interpolation = "faceVarying"
)
point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5)]
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1), (1, 0), (1, 1), (0, 1), (0, 0), (0, 1), (0, 0), (1, 0), (1, 1), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (1, 0), (1, 1), (0, 1), (0, 0)] (
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "none"
quatd xformOp:orient = (1, 0, 0, 0)
double3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (0, 1, 1)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
variantSet "ChangeColor" = {
"Variant" (
prepend apiSchemas = ["MaterialBindingAPI"]
customData = {
string[] variantPrimPaths = ["."]
}
) {
rel material:binding = </World/Cube_01/Looks/OmniPBR_Yellow> (
bindMaterialAs = "weakerThanDescendants"
)
}
"Variant_1" (
prepend apiSchemas = ["MaterialBindingAPI"]
customData = {
string[] variantPrimPaths = ["."]
}
) {
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "weakerThanDescendants"
)
}
}
}
def Scope "Looks"
{
def Material "OmniPBR_Red"
{
token outputs:mdl:displacement.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Red/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.90759075, 0.12366932, 0.01797209)
token outputs:out (
renderType = "material"
)
}
}
def Material "OmniPBR_Yellow"
{
token outputs:mdl:displacement.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/_Class_Cube_Red/Looks/OmniPBR_Yellow/Shader.outputs:out>
custom uniform bool paused = 0 (
customData = {
bool nonpersistant = 1
}
hidden = true
)
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.882679, 0.90759075, 0.06889302)
token outputs:out (
renderType = "material"
)
}
}
}
}
def Xform "Cube_01" (
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (1.6772258843990702, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Xform "Cube_02" (
prepend apiSchemas = ["MaterialBindingAPI"]
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "strongerThanDescendants"
)
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (0, 1.8461749821880287, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Xform "Cube_03" (
prepend apiSchemas = ["MaterialBindingAPI"]
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
rel material:binding = </World/Looks/OmniPBR_Blue> (
bindMaterialAs = "weakerThanDescendants"
)
}
def Xform "Cube_04" (
inherits = </World/_Class_Cube_Red>
instanceable = true
)
{
color3f[] primvars:displayColor = [(0, 1, 0)] (
interpolation = "constant"
)
uniform token purpose = "default"
token visibility = "inherited"
quatf xformOp:orient = (1, 0, 0, 0)
float3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (1.4336037158412456, 1.6727603814796503, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Cube "Cube_05" (
)
{
float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
color3f[] primvars:displayColor = [(0, 1, 0)] (
interpolation = "constant"
)
double size = 1
quatd xformOp:orient = (1, 0, 0, 0)
double3 xformOp:scale = (1, 1, 1)
double3 xformOp:translate = (3.2250553274445837, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"]
}
def Scope "Looks"
{
def Material "OmniPBR_Yellow"
{
token outputs:mdl:displacement.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/Looks/OmniPBR_Yellow/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.882679, 0.90759075, 0.06889302)
token outputs:out (
renderType = "material"
)
}
}
def Material "OmniPBR_Blue"
{
token outputs:mdl:displacement.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
token outputs:mdl:surface.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
token outputs:mdl:volume.connect = </World/Looks/OmniPBR_Blue/Shader.outputs:out>
def Shader "Shader"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:mdl:sourceAsset = @OmniPBR.mdl@
uniform token info:mdl:sourceAsset:subIdentifier = "OmniPBR"
color3f inputs:diffuse_tint = (0.029408854, 0.08059826, 0.8910891)
token outputs:out (
renderType = "material"
)
}
}
}
} |
đ More info
Scenegraphâor nativeâinstancing is the first implementation of instancing in OpenUSD.
There are four key terms involved in scenegraph instancing that you need to be aware of:
-
Prototype: A unique, shared sub-structure
-
Instance: A repetition of a prototype within a scene
-
Instanceable Prim or Instance Prim: The mutable root of an instance
-
Instance Proxy: A read-only addressable stand-in of the prototype prims of an instance
To enable scenegraph instancing in USD, you need to set one value, the instanceable prim metadata. Setting instanceable = true tells OpenUSD to create one shared version (prototype) of an object that many instances can use as long as they share composition arcs.
Proxies and instance subgraphs are immutable, but the instanceable prim is mutable.
Nested Instancing
Nested instancing is when an instance subgraph has instances within it.
Changes on inherited ancestral properties, like transforms, do not create any new prototypes; they simply leverage inheritance to efficiently introduce variety or control across instances.
You can also use primvars.
#usda 1.0
(
defaultPrim = "Warehouse"
metersPerUnit = 0.01
upAxis = "Z"
)
def Xform "Warehouse"
{
def "RobotArm_01" (
references = @./RobotArm.usd@
instanceable = true
)
{
color3f[] primvars:arm_color = [(0.61, 0.75, 0.24)] (
interpolation = "constant"
)
}
def "RobotArm_02" (
references = @./RobotArm.usd@
instanceable = true
)
{
}
def "RobotArm_03" (
references = @./RobotArm.usd@
instanceable = true
)
{
color3f[] primvars:arm_color = [(0.08, 0.70, 0.52)] (
interpolation = "constant"
)
}
} |
Point instancing is implemented as a schema in OpenUSDâthe PointInstancer prim type.
PointInstancer uses array attributes to describe points in 3D space. Additionally, explicit prototypes represented by prim hierarchies in the stage are specified as relationship targets on the PointInstancer. By combining the points, prototypes, and an index array to map the points to prototypes, OpenUSD is able to handle hundreds of thousands of repetitions very compactly in this vectorized format.
The PointInstancer includes the following mandatory properties:
- prototypes: A relationship targeting the prim hierarchies that the PointInstancer should use as instance prototypes.
- protoIndices Declares an instance of a prototype. Each element in the array maps a point to a prototype id.
- positions The points in local space where each instance is located.
Note: Prototype prims do not exist in scene description â they are generated and managed internally by UsdStage. This allows UsdStage to create and remove prototypes as needed in response to scene description changes. For example, if some of the Car prims in ParkingLot.usd were changed to reference different assets, UsdStage would generate new prototype prims as needed. See Finding and Traversing Prototypes for information on how to use prototype prims.
đ More info đ More info 2
- Model Kinds & Model Hierarchy (Refer to 2.1)
In the OpenUSD ecosystem, a schema is a blueprint that may define a new Prim type or Properties for describing a particular data model. For example, a Geometry schema might specify how to define the vertices and topology of a 3D mesh, while a Shading schema could outline how to describe parameters for materials and textures.
Two types of Schemas:
| Aspect | ISA (Typed) Schemas | API Schemas |
|---|---|---|
| Purpose | Define what a prim is | Define what a prim can do |
| Schema Nature | Concrete or Abstract | Always abstract |
| Prim Type Definition | Define or participate in prim type identity | Do not define a prim type |
| Instantiation | Concrete schemas can be instantiated; abstract schemas cannot | Applied to existing prims |
| Multiplicity per Prim | â Only one typed schema per prim | â Multiple API schemas can be applied |
| Schema Role | Establish core structure, meaning, and inheritance | Add reusable behavior or properties |
| Inheritance Model | Uses ISA (âis-aâ) inheritance | Uses composition via applied APIs |
| Examples (Conceptual) | Base geometry types, specialized prim categories | Physics behavior, shading interfaces, constraints |
Note, all API schemas have API at the end of the name.
| Generic | UsdGeom | UsdLux | UsdPhysics |
|---|---|---|---|
# Retrieve the schema info for a registered schema
Usd.SchemaRegistry.FindSchemaInfo()
# Retrieve the schema typeName
Usd.SchemaRegistry.GetSchemaTypeName()
|
# Import related classes
from pxr import UsdGeom
# Define a sphere in the stage
sphere = UsdGeom.Sphere.Define(stage, "/World/Sphere")
# Get and Set the radius attribute of the sphere
sphere.GetRadiusAttr().Set(10)
|
# Import related classes
from pxr import UsdLux
# Define a disk light in the stage
disk_light = UsdLux.DiskLight.Define(stage, "/World/Lights/DiskLight")
# Get all Attribute names that are a part of the DiskLight schema
dl_attribute_names = disk_light.GetSchemaAttributeNames()
# Get and Set the radius and intensity of the disk light prim
disk_light.GetRadiusAttr().Set(0.4) # from DiskLight typed schema
disk_light.GetIntensityAttr().Set(1000) # from LightAPI
|
# Import related classes
from pxr import UsdPhysics
# Apply a UsdPhysics Rigidbody API on the cube prim
cube_rb_api = UsdPhysics.RigidBodyAPI.Apply(cube.GetPrim())
# Get the Kinematic Enabled Attribute
cube_rb_api.GetKinematicEnabledAttr()
# Create a linear velocity attribute of value 5
cube_rb_api.CreateVelocityAttr(5) |
| class Sphere | ussage |
|---|---|
class Sphere "Sphere" (
inherits = </Gprim>
) {
double radius = 1.0
float3[] extent = [(-1.0, -1.0, -1.0), (1.0, 1.0, 1.0)]
} |
def Sphere "MySphere" {
} |
A schema class is simply a container of a UsdPrim that provides a layer of specific, named API atop the underlying scene graph.
Two types:
| Aspect | IsA schema | API schema |
|---|---|---|
| Main role | Defines what a prim is (its typed schema / prim type). | Adds behaviors or properties to an existing prim type. |
| typeName | Can author a typeName for the prim, so the prim âis aâ that type. :contentReference[oaicite:0]{index=0} |
Cannot author a typeName; never defines the primâs type. :contentReference[oaicite:1]{index=1} |
| Concrete vs abstract | Can be concrete (instantiable typed prim) or non-concrete (abstract base). :contentReference[oaicite:2]{index=2} | Considered non-concrete with respect to prim typing (they donât create a new prim type). :contentReference[oaicite:3]{index=3} |
| Base class | Must derive (directly or indirectly) from UsdTyped. :contentReference[oaicite:4]{index=4} |
Derives from UsdAPISchemaBase (or APISchemaBase in the schema file). :contentReference[oaicite:5]{index=5} |
| How many per prim | A prim can have at most one IsA schema (one typeName). :contentReference[oaicite:6]{index=6} |
A prim can have many API schemas applied at once. :contentReference[oaicite:7]{index=7} |
| How itâs attached to a prim | By setting the primâs typeName to the IsA schemaâs type. |
By applying it via API (e.g. Apply()), recorded in prim metadata (apiSchemas) for applied APIs. :contentReference[oaicite:8]{index=8} |
| Fallback properties & schema registry | Definitions go into the schema registry and can provide fallback values. :contentReference[oaicite:9]{index=9} | Applied API schemas also go into the registry and can add built-ins with fallbacks. :contentReference[oaicite:10]{index=10} |
| Inheritance | Can form inheritance hierarchies (abstract â concrete typed schemas). :contentReference[oaicite:11]{index=11} | Cannot inherit from other API schemas (only from UsdAPISchemaBase / compatible API type). :contentReference[oaicite:12]{index=12} |
| Typical usage | âThis prim is a light, mesh, scope, etc.â | âThis prim has collections, clips, material bindings, render props, etc.â |
| Example from core USD | UsdGeomScope, UsdGeomMesh, UsdGeomImageable (non-concrete). :contentReference[oaicite:13]{index=13} |
UsdModelAPI, UsdClipsAPI, UsdCollectionAPI, UsdGeomModelAPI, UsdGeomMotionAPI. :contentReference[oaicite:14]{index=14} |
Basic Guide To Creating Custom Schemas:
Creating new Prim types can range from simple definitions to complex structures like a Mesh. Hereâs a brief on generating new schema classes:
Configure the Environment: Ensure Python modules like âjinja2â and âargparseâ are installed. Define the Schema Class: A schema class is a container of a UsdPrim. It provides a specific API atop the underlying scene graph. Types of Schema Classes: IsA schema: Imparts a typeName to a Prim, can be concrete or abstract. API schema: Provides an interface but doesnât specify a typeName.
Configure Environment: Watch the tutorial on YouTube
USD provides a code generator script called âusdGenSchemaâ for creating new schema classesThe schema generation script âusdGenSchemaâ is driven by a USD layer.
- 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.
base layer (or starting point) for creating new schema classes
#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"
}
) {
} |
#usda 1.0
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"""
)
} |
#usda 1.0
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"
} |
#usda 1.0
# 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"
)
} |
You can find the previous examples in the USD_ROOT in the next path: .\usd_root\share\usd\examples\plugin\usdSchemaExamples\resources\usdSchemaExamples\schema.usda
đ§ Exercise Generate and use the previouse schema samples
| Step | Description | Command / Output |
|---|---|---|
| 1 | Initialize the schema module (creates build helper files) | .\scripts\usdInitSchema.bat share\usd\examples\plugin\usdSchemaExamples\resources\usdSchemaExamples . |
| 2 | Re-run codegen so everything is in sync (optional but clean) | .\scripts\usdGenSchema.bat share\usd\examples\plugin\usdSchemaExamples\resources\usdSchemaExamples\schema.usda share\usd\examples\plugin\usdSchemaExamples\resources\usdSchemaExamples |
| 3 | Files created by usdInitSchema (if missing) |
- CMakeLists.txt- __init__.py- module.cpp- schemaUserDoc.usda |
đ Full Guide
The kind registry can be extended using the facilities provided by PlugRegistry, by adding a 'Kinds' sub-dictionary to the plugInfo.json file of any module within your "pixar-base aware" build environment. The dictionary entries will look like the following:
SimplePrim.usda
"Info": {
"Kinds": {
"chargroup": {
"baseKind": "assembly",
"description": "A chargroup is an assembly comprised of a single character plus some associated models -- typically hair, garments, and charprops."
},
"charprop": {
"baseKind": "component"
},
"newRootKind": {
}
}
} |
đ Full Guide
đ More Info
đ More Info
In USD, Data Exchange refers to the ability to share, modify, and synchronize scene data across different tools, applications, and teams using a layered, non-destructive composition model, where data from a source application is mapped and translated into a format that the destination application can understand. This mapping ensures that structure, meaning, and intent are preserved across systems, enabling geometry, materials, transforms, variants, and metadata to be exchanged consistently without destructive overwrites or loss of context.
There are four common implementations for adding OpenUSD support for your data format or application:
- Importers
- Exporters
- Standalone converters: a script, executable or microservice dedicated to translating to and from another file format.
- File format plugins. They allow OpenUSD to compose with additional file formats and even non-file-based sources, such as databases and procedurally generated content. They can be used as standalone converters with tools like usdcat.
Two-phase approach: extract and transform. This is loosely inspired by Extract-Transform-Load (ETL)
| Windows | Linux |
|---|---|
|
|
from omni.usd import get_context stage = get_context().get_stage()
đ Full Guide
| From App | From Python env |
|---|---|
# hello_usdex.py
import pathlib
import usdex.core
from pxr import Gf, Usd, UsdGeom
from omni.usd import get_context
stage = get_context().get_stage()
root_layer = stage.GetRootLayer()
identifier = root_layer.identifier
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
UsdGeom.SetStageMetersPerUnit(stage, 0.01)
# Create and Xform and set the transform on it
xform = usdex.core.defineXform(stage, "/Scene")
cube = UsdGeom.Cube.Define(stage, xform.GetPrim().GetPath().AppendChild("Cube"))
usdex.core.setLocalTransform(
prim=cube.GetPrim(),
translation=Gf.Vec3d(10.459, 49.592, 17.792),
pivot=Gf.Vec3d(0.0),
rotation=Gf.Vec3f(-0.379, 56.203, 0.565),
rotationOrder=usdex.core.RotationOrder.eXyz,
scale=Gf.Vec3f(1),
)
# Save the stage
stage.Save()
print(f"Created USD stage '{pathlib.Path('./'+identifier).absolute()}' using:")
print(f"\tOpenUSD version: {Usd.GetVersion()}")
print(f"\tOpenUSD Exchange SDK version: {usdex.core.version()}") |
# hello_usdex.py
import pathlib
import usdex.core
from pxr import Gf, Usd, UsdGeom
# Create a new USD stage
identifier = "hello_world.usda"
stage = usdex.core.createStage(identifier, "Scene", UsdGeom.Tokens.y, 0.01, "OpenUSD Exchange SDK Example")
# Create and Xform and set the transform on it
xform = usdex.core.defineXform(stage, "/Scene")
cube = UsdGeom.Cube.Define(stage, xform.GetPrim().GetPath().AppendChild("Cube"))
usdex.core.setLocalTransform(
prim=cube.GetPrim(),
translation=Gf.Vec3d(10.459, 49.592, 17.792),
pivot=Gf.Vec3d(0.0),
rotation=Gf.Vec3f(-0.379, 56.203, 0.565),
rotationOrder=usdex.core.RotationOrder.eXyz,
scale=Gf.Vec3f(1),
)
# Save the stage
stage.Save()
print(f"Created USD stage '{pathlib.Path('./'+identifier).absolute()}' using:")
print(f"\tOpenUSD version: {Usd.GetVersion()}")
print(f"\tOpenUSD Exchange SDK version: {usdex.core.version()}") |
When authoring UsdStages it is important to configure certain metrics & metadata on the root SdfLayer of the stage. Available Functions: configureStage, createStage, saveStage. Detailed in the next link.
đ Review Functions
When authoring UsdPrims to a Stage, you will need to specify an SdfPath that identifies a unique location for the Prim. The nature of OpenUSDâs composition algorithm (know as âLIVERPSâ) makes it fairly complex to determine whether your chosen location is valid for authoring.
đ Review Functions
Introspection of USD layers
đ Review Functions
To achieve a direct and faithful extraction between two data formats, conceptual data mapping is crucial. This process involves analyzing how to map data models from one format to another.
đ More Info
| Final result from previous exercises |
|---|
import argparse
import logging
import math
from enum import Enum
from pathlib import Path
import assimp_py
from pxr import Gf, Sdf, Tf, Usd, UsdGeom, UsdShade
logger = logging.getLogger("obj2usd")
class UpAxis(Enum):
Y = UsdGeom.Tokens.y
Z = UsdGeom.Tokens.z
def __str__(self):
return self.value
# ADD CODE BELOW HERE
# vvvvvvvvvvvvvvvvvvv
def extract(input_file: Path, output_file: Path) -> Usd.Stage:
logger.info("Executing extraction phase...")
process_flags = 0
# Load the obj using Assimp
scene = assimp_py.ImportFile(str(input_file), process_flags)
# Define the stage where the output will go
stage: Usd.Stage = Usd.Stage.CreateNew(str(output_file))
for mesh in scene.meshes:
# Replace any invalid characters with underscores.
sanitized_mesh_name = Tf.MakeValidIdentifier(mesh.name)
usd_mesh = UsdGeom.Mesh.Define(stage, f"/{sanitized_mesh_name}")
# You can use the Vt APIs here instead of Python lists.
# Especially keep this in mind for C++ implementations.
face_vertex_counts = []
face_vertex_indices = []
for indices in mesh.indices:
# Convert the indices to a flat list
face_vertex_indices.extend(indices)
# Append the number of vertices for each face
face_vertex_counts.append(len(indices))
usd_mesh.CreatePointsAttr(mesh.vertices)
usd_mesh.CreateFaceVertexCountsAttr().Set(face_vertex_counts)
usd_mesh.CreateFaceVertexIndicesAttr().Set(face_vertex_indices)
# Treat the mesh as a polygonal mesh and not a subdivision surface.
# Respect the normals or lack of normals from OBJ.
usd_mesh.CreateSubdivisionSchemeAttr(UsdGeom.Tokens.none)
if mesh.normals:
usd_mesh.CreateNormalsAttr(mesh.normals)
# Get the mesh's material by index
# scene.materials is a dictionary consisting of assimp material properties
mtl = scene.materials[mesh.material_index]
if not mtl:
continue
sanitized_mat_name = Tf.MakeValidIdentifier(mtl["NAME"])
material_path = Sdf.Path(f"/{sanitized_mat_name}")
# Create the material prim
material: UsdShade.Material = UsdShade.Material.Define(stage, material_path)
# Create a UsdPreviewSurface Shader prim.
shader: UsdShade.Shader = UsdShade.Shader.Define(stage, material_path.AppendChild("Shader"))
shader.CreateIdAttr("UsdPreviewSurface")
# Connect shader surface output as an output for the material graph.
material.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), UsdShade.Tokens.surface)
# Get colors
diffuse_color = mtl["COLOR_DIFFUSE"]
emissive_color = mtl["COLOR_EMISSIVE"]
specular_color = mtl["COLOR_SPECULAR"]
# Convert specular shininess to roughness.
roughness = 1 - math.sqrt(mtl["SHININESS"] / 1000.0)
shader.CreateInput("useSpecularWorkflow", Sdf.ValueTypeNames.Int).Set(1)
shader.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(diffuse_color))
shader.CreateInput("emissiveColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(emissive_color))
shader.CreateInput("specularColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(specular_color))
shader.CreateInput("roughness", Sdf.ValueTypeNames.Float).Set(roughness)
binding_api = UsdShade.MaterialBindingAPI.Apply(usd_mesh.GetPrim())
binding_api.Bind(material)
return stage
def transform(stage: Usd.Stage, args: argparse.Namespace):
logger.info("Executing transformation phase...")
def main(args: argparse.Namespace):
# Extract the .obj
stage: Usd.Stage = extract(args.input, args.output)
# Transformations to be applied to the scene hierarchy
transform(stage, args)
# Save the Stage after editing
stage.Save()
# ^^^^^^^^^^^^^^^^^^^^
# ADD CODE ABOVE HERE
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
parser = argparse.ArgumentParser(
"obj2usd", description="An OBJ to USD converter script."
)
parser.add_argument("input", help="Input OBJ file", type=Path)
parser.add_argument("-o", "--output", help="Specify an output USD file", type=Path)
export_opts = parser.add_argument_group("Export Options")
export_opts.add_argument(
"-u",
"--up-axis",
help="Specify the up axis for the exported USD stage.",
type=UpAxis,
choices=list(UpAxis),
default=UpAxis.Y,
)
args = parser.parse_args()
if args.output is None:
args.output = args.input.parent / f"{args.input.stem}.usda"
logger.info(f"Converting {args.input}...")
main(args)
logger.info(f"Converted results output as: {args.output}.")
logger.info(f"Done.") |
Omniverse Asset Validator is based on usdchecker and provides a GUI for asset validation. The asset validator includes:
- User interface
- Command line interface
- Python API
- Automatic fixes for failed validations
- Ability to add new rules
Key aspects of data transformation include:
- Export options
- Content re-structuring
- Optimizations, e.g., mesh merging
A transformation step doesnât need to happen in the same process as extraction or other transformation steps. It could be a separate process or a cloud service. You can even consider transformations that may occur as post-processes after a stage has been exported or converted.
File formats:
| Order | Arc Type |
|---|---|
| .usda | Human-readable UTF-8 text |
| .usdc | Random-access âCrateâ binary |
| .usd | Either of the above |
usdcat can be used to convert files between file formats. With plain, .usd files, you can change them using the --usdFormat option.
$ usdcat file.usd --out file.usd --usdFormat usda
đ More Info
đ More Info
Converting between .usda and .usdc Files
A .usd file can be either a text or binary format file. When USD opens a .usd file, it detects the underlying format and handles the file appropriately. You can convert any .usda or .usdc file to a .usd file simply by renaming it.
Consider Sphere.usda To convert this file to binary, we can specify an output filename with the .usdc extension: $ usdcat -o NewSphere.usdc Sphere.usda This produces a binary file named Sphere.usdc in the current directory containing the same content as Sphere.usda. We can verify this using usddiff: $ usddiff Sphere.usda NewSphere.usdc
Same goes for the other way around
đ Full details
Usdz File Format Specification
A single object, from marshaling and transmission perspectives Potentially streamable Usable without unpacking to a filesystem
A usdz package is an uncompressed zip archive that is allowed to contain the following file types:
| Kind | Allowed File Types |
|---|---|
| USD | usda, usdc, usd |
| Image | png, jpeg, exr, avif |
| Audio | M4A, MP3, WAV |
- A usdz package is a zero compression, unencrypted zip archive.
- it is possible to reference individual files within a package from outside the package
- packages can themselves contain other packages
- A usdz file is read-only - editing its contents requires first unpacking the package and editing its constituent parts using appropriate tools. Since usdz is a âcoreâ USD file format, one can use usdcat and usdedit on packages:
đ Full details
For files that contain more than a few small definitions or overrides, the binary âusdcâ format will open faster and consume substantially much less memory while held open (a UsdStage keeps open all the layers that participate in a composition). You should not need to exert any extra effort to get this behavior since creating a new layer or stage with a filename someFile.usd will, by default, create a usdc file.
âą A prim path describes where a prim lives in the scene hierarchy. Prims are objects such as geometry, lights, transforms, etc.
âą Extending a prim path with a . and a property name gives you a property path. Properties include attributes (data values) and relationships (connections between prims).
âą USD paths can include variant selections, using curly braces to specify which variant of a prim is being referenced.
At its core, an OpenUSD stage presents the scenegraph, which dictates what is in our scene.
| first_stage.usda |
|---|
# Import the `Usd` module from the `pxr` package:
from pxr import Usd
# Define a file path name:
file_path = "_assets/first_stage.usda"
# Create a stage at the given `file_path`:
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
print(stage.ExportToString(addSourceFileComment=False)) |
| first_stage.usda |
|---|
# Import the `Usd` module from the `pxr` package:
from pxr import Usd
# Define a file path name:
file_path = "_assets/first_stage.usda"
# Create a stage at the given `file_path`:
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
print(stage.ExportToString(addSourceFileComment=False)) |
| first_stage.usda |
|---|
from pxr import Usd
# Open an existing USD stage from disk:
stage: Usd.Stage = Usd.Stage.Open("_assets/first_stage.usda")
# Add a simple prim so we can see a change in the saved file:
stage.DefinePrim("/World", "Xform")
# Save the stage back to disk:
stage.Save()
# Print the stage as text so we can inspect the result:
print(stage.ExportToString(addSourceFileComment=False)) |
Sometimes you may want to create a stage without immediately writing it to disk. This is useful when generating temporary data, running tests, or building a stage that you only want to save after validating its contents.
| in_memory_stage.usda |
|---|
from pxr import Usd
# Create a new stage stored only in memory:
stage: Usd.Stage = Usd.Stage.CreateInMemory()
# Add a prim so the stage contains some data:
stage.DefinePrim("/World", "Xform")
# Print the stage's contents:
print("In-memory stage:")
print(stage.ExportToString(addSourceFileComment=False))
# Export the stage to disk if needed:
stage.Export("_assets/in_memory_stage.usda") |
When you create a stage with CreateNew(), the file you pass becomes its root layer.
| root_layer_example.usda |
|---|
from pxr import Usd, Sdf
import os
# Create a new stage:
stage: Usd.Stage = Usd.Stage.CreateNew("_assets/root_layer_example.usda")
# Get the root layer object:
root_layer: Sdf.Layer = stage.GetRootLayer()
# Use relpath to avoid printing build machine filesystem info.
print("Root layer identifier:", os.path.relpath(root_layer.identifier))
# Add a simple prim so the stage is not empty:
stage.DefinePrim("/World", "Xform")
# Create an additional layer (in a different format) and add it as a sublayer:
extra_layer: Sdf.Layer = Sdf.Layer.CreateNew("_assets/extra_layer.usdc")
# Anchor the path relative to the root layer for better portability
rel_path = "./" + os.path.basename(extra_layer.identifier)
root_layer.subLayerPaths.append(rel_path)
# Save both layers:
stage.Save()
extra_layer.Save()
# Print the contents of the root layer:
print("Root layer contents:")
print(root_layer.ExportToString()) |
đ Full details
If you followed the previous subjects you have already worked a lot with prims. A prim is the primary container object in USD. It can contain other prims and properties holding data.
DefinePrim() API provides a generic way to create any type of prim:
| Python snippet | prims.usda |
|---|---|
# Import the `Usd` module from the `pxr` package:
from pxr import Usd
# Create a new USD stage with root layer named "prims.usda":
stage: Usd.Stage = Usd.Stage.CreateNew("_assets/prims.usda")
# Define a new primitive at the path "/hello" on the current stage:
stage.DefinePrim("/hello")
# Define a new primitive at the path "/world" on the current stage with the prim type, Sphere.
stage.DefinePrim("/world", "Sphere")
stage.Save() |
#usda 1.0
def "hello"
{
}
def Sphere "world"
{
} |
Using a specific API to create a prim
| Python snippet | sphere_prim.usda |
|---|---|
from pxr import Usd, UsdGeom
file_path = "_assets/sphere_prim.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
# Define a prim of type `Sphere` at path `/hello`:
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, "/hello")
sphere.CreateRadiusAttr().Set(2)
# Save the stage:
stage.Save()
|
#usda 1.0
def Sphere "hello"
{
double radius = 2
}
|
| python | |
|---|---|
from pxr import Usd, UsdGeom
file_path = "_assets/prim_hierarchy.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
# Define a Scope prim in stage at `/Geometry`
geom_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, "/Geometry")
# Define an Xform prim in the stage as a child of /Geometry called GroupTransform
xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, geom_scope.GetPath().AppendPath("GroupTransform"))
# Define a Cube in the stage as a child of /Geometry/GroupTransform, called Box
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, xform.GetPath().AppendPath("Box"))
stage.Save()
|
#usda 1.0
def Scope "Geometry"
{
def Xform "GroupTransform"
{
def Cube "Box"
{
}
}
}
|
| python | prim_hierarchy.usda |
|---|---|
from pxr import Usd
file_path = "_assets/prim_hierarchy.usda"
stage: Usd.Stage = Usd.Stage.Open(file_path)
prim: Usd.Prim = stage.GetPrimAtPath("/Geometry")
child_prim: Usd.Prim
if child_prim := prim.GetChild("Box"):
print("Child prim exists")
else:
print("Child prim DOES NOT exist")
|
#usda 1.0
def Scope "Geometry"
{
def Xform "GroupTransform"
{
def Cube "Box"
{
}
}
}
|
Prims can have two types of properties: attributes and relationships. Attributes are the most common type of property authored in most USD scenes. An attribute can take on exactly one of the legal attribute typeNames USD provides, and can take on both a default value and an animated value. Resolving an attribute at any given timeCode will yield either a single value or no value. Attributes resolve according to âstrongest winsâ rules, so all values for any given attribute will be fetched from the strongest PrimSpec that provides either a default value or an animated value. Note that this simple rule is somewhat more complicated in the presence of authored value clips. One interacts with attributes through the UsdAttribute API.
- Attributes
Each attribute can have a default value, and it can also have different values at different points in time, called time samples.
| Code | attributes_ex1.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/attributes_ex1.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
# Define a sphere under the World xForm:
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world_xform.GetPath().AppendPath("Sphere"))
# Define a cube under the World xForm and set it to be 5 units away from the sphere:
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube"))
UsdGeom.XformCommonAPI(cube).SetTranslate(Gf.Vec3d(5, 0, 0))
# Get the property names of the cube prim:
cube_prop_names = cube.GetPrim().GetPropertyNames()
# Print the property names:
for prop_name in cube_prop_names:
print(prop_name)
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Sphere "Sphere"
{
}
def Cube "Cube"
{
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
|
| Code | attributes_ex2.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/attributes_ex2.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world_xform.GetPath().AppendPath("Sphere"))
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube"))
UsdGeom.XformCommonAPI(cube).SetTranslate(Gf.Vec3d(5, 0, 0))
# Get the attributes of the cube prim
cube_attrs = cube.GetPrim().GetAttributes()
for attr in cube_attrs:
print(attr)
# Get the size, display color, and extent attributes of the cube
cube_size: Usd.Attribute = cube.GetSizeAttr()
cube_displaycolor: Usd.Attribute = cube.GetDisplayColorAttr()
cube_extent: Usd.Attribute = cube.GetExtentAttr()
print(f"Size: {cube_size.Get()}")
print(f"Display Color: {cube_displaycolor.Get()}")
print(f"Extent: {cube_extent.Get()}")
stage.Save()
|
def Xform "World"
{
def Sphere "Sphere"
{
}
def Cube "Cube"
{
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
|
| Code | attributes_ex3.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/attributes_ex3.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world_xform.GetPath().AppendPath("Sphere"))
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube"))
UsdGeom.XformCommonAPI(cube).SetTranslate(Gf.Vec3d(5,0,0))
# Get the size, display color, and extent attributes of the cube
cube_size: Usd.Attribute = cube.GetSizeAttr()
cube_displaycolor: Usd.Attribute = cube.GetDisplayColorAttr()
cube_extent: Usd.Attribute = cube.GetExtentAttr()
# Modify the size, extent, and display color attributes:
cube_size.Set(cube_size.Get() * 2)
cube_extent.Set(cube_extent.Get() * 2)
cube_displaycolor.Set([(0.0, 1.0, 0.0)])
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Sphere "Sphere"
{
}
def Cube "Cube"
{
float3[] extent = [(-2, -2, -2), (2, 2, 2)]
color3f[] primvars:displayColor = [(0, 1, 0)]
double size = 4
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
|
Similarly to how prims can be deactivated through composing overriding opinions, the value that an attribute produces can be blocked by an overriding opinion of None, which can be authored using UsdAttribute::Block().
In the next example, Usd.Attribute.Get(t) will return always none, because we cannot override in timeSamples, we could if we dont define
| attributeBlock.usda |
|---|
def Sphere "BigBall"
{
double radius = 100
double radius.timeSamples = {
1: 100,
24: 500,
}
}
def "DefaultBall" (
references = </BigBall>
)
{
double radius = None
}
|
đ Full examples
đ More Info
- Relationships
Relationships establish connections between prims, acting as pointers or links between objects in the scene hierarchy. A relationship allows a prim to target or refer to other prims, attributes, or even other relationships. This establishes dependencies between scenegraph objects.
| Code | relationships_ex1.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/relationships_ex1.usda"
stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
# Define a sphere under the World Xform:
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world_xform.GetPath().AppendPath("Sphere"))
# Define a cube under the World Xform and set it to be 5 units away from the sphere:
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube"))
UsdGeom.XformCommonAPI(cube).SetTranslate(Gf.Vec3d(5, 0, 0))
# Create typeless container for the group
group = stage.DefinePrim("/World/Group")
# Define the relationship
group.CreateRelationship("members", custom=True).SetTargets(
[sphere.GetPath(), cube.GetPath()]
)
# List relationship targets
members_rel = group.GetRelationship("members")
print("Group members:", [str(p) for p in members_rel.GetTargets()])
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Sphere "Sphere"
{
}
def Cube "Cube"
{
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def "Group"
{
custom rel members = [
</World/Sphere>,
</World/Cube>,
]
}
} |
| Code | relationships_ex2.usda |
|---|---|
from pxr import Usd, UsdGeom
file_path = "_assets/relationships_ex2.usda"
stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
# Define a "high cost" Sphere Prim under the World Xform:
high: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world_xform.GetPath().AppendPath("HiRes"))
# Define a "low cost" Cube Prim under World Xfrom
low: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Proxy"))
UsdGeom.Imageable(high).GetPurposeAttr().Set("render")
UsdGeom.Imageable(low).GetPurposeAttr().Set("proxy")
# Author the proxy link on the render Prim
UsdGeom.Imageable(high).GetProxyPrimRel().SetTargets([low.GetPath()])
# Tools that honor proxyPrim should draw the proxy in preview
draw_prim = UsdGeom.Imageable(high).ComputeProxyPrim() # returns Usd.Prim
print("Preview should draw:", str(draw_prim[0].GetPath() if draw_prim else high.GetPath()))
stage.Save() |
#usda 1.0
def Xform "World"
{
def Sphere "HiRes"
{
rel proxyPrim = </World/Proxy>
uniform token purpose = "render"
}
def Cube "Proxy"
{
uniform token purpose = "proxy"
}
} |
| Code | relationships_ex3.usda |
|---|---|
from pxr import Usd, UsdGeom, UsdShade, Gf, Sdf
file_path = "_assets/relationships_ex3.usda"
stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
cube_1: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube_1"))
cube_2: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube_2"))
UsdGeom.XformCommonAPI(cube_2).SetTranslate(Gf.Vec3d(5, 0, 0))
cube_3: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world_xform.GetPath().AppendPath("Cube_3"))
UsdGeom.XformCommonAPI(cube_3).SetTranslate(Gf.Vec3d(10, 0, 0))
# Create typeless container for the materials
looks = stage.DefinePrim("/World/Looks")
# Create simple green material for preview
green: UsdShade.Material = UsdShade.Material.Define(stage, looks.GetPath().AppendPath("GreenMat"))
green_ps = UsdShade.Shader.Define(stage, green.GetPath().AppendPath("PreviewSurface"))
green_ps.CreateIdAttr("UsdPreviewSurface")
green_ps.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(0.0, 1.0, 0.0))
green.CreateSurfaceOutput().ConnectToSource(green_ps.ConnectableAPI(), "surface")
# Create simple red material for preview
red: UsdShade.Material = UsdShade.Material.Define(stage, looks.GetPath().AppendPath("RedMat"))
red_ps = UsdShade.Shader.Define(stage, red.GetPath().AppendPath("PreviewSurface"))
red_ps.CreateIdAttr("UsdPreviewSurface")
red_ps.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(1.0, 0.0, 0.0))
red.CreateSurfaceOutput().ConnectToSource(red_ps.ConnectableAPI(), "surface")
# Bind materials to Prims
UsdShade.MaterialBindingAPI.Apply(cube_1.GetPrim()).Bind(green)
UsdShade.MaterialBindingAPI.Apply(cube_2.GetPrim()).Bind(green)
UsdShade.MaterialBindingAPI.Apply(cube_3.GetPrim()).Bind(red)
# Verify by reading the direct binding
for prim in [cube_1, cube_2, cube_3]:
mat = UsdShade.MaterialBindingAPI(prim).GetDirectBinding().GetMaterial()
print(f"{prim.GetPath()} -> {mat.GetPath() if mat else 'None'}")
stage.Save() |
#usda 1.0
def Xform "World"
{
def Cube "Cube_1" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Looks/GreenMat>
}
def Cube "Cube_2" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Looks/GreenMat>
double3 xformOp:translate = (5, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def Cube "Cube_3" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </World/Looks/RedMat>
double3 xformOp:translate = (10, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
def "Looks"
{
def Material "GreenMat"
{
token outputs:surface.connect = </World/Looks/GreenMat/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (0, 1, 0)
token outputs:surface
}
}
def Material "RedMat"
{
token outputs:surface.connect = </World/Looks/RedMat/PreviewSurface.outputs:surface>
def Shader "PreviewSurface"
{
uniform token info:id = "UsdPreviewSurface"
color3f inputs:diffuseColor = (1, 0, 0)
token outputs:surface
}
}
}
} |
- Connection
Connections are quite similar to relationships in that they are list-edited âscene pointersâ that robustly identify other scene objects. The key difference is that while relationships are typeless, independent properties, connections are instead a sub-aspect of USD attributes.
We have already show a few examples of this, let's see one more example.
| Code 1 - Create timecode_sample.usda | Code 2 - Create animation | timecode_ex.usda |
|---|---|---|
# Import the necessary modules from the `pxr` library:
from pxr import Usd, UsdGeom, Gf
# Create a new USD stage file named "timecode_ex.usda":
file_path = "_assets/timecode_sample.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
# Define a transform ("Xform") primitive at the "/World" path:
world: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
# Define a Sphere primitive as a child of the transform at "/World/Sphere" path:
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Define(stage, world.GetPath().AppendPath("Sphere"))
# Define a blue Cube as a background prim:
box: UsdGeom.Cube = UsdGeom.Cube.Define(stage, world.GetPath().AppendPath("Backdrop"))
box.GetDisplayColorAttr().Set([(0.0, 0.0, 1.0)])
cube_xform_api = UsdGeom.XformCommonAPI(box)
cube_xform_api.SetScale(Gf.Vec3f(5, 5, 0.1))
cube_xform_api.SetTranslate(Gf.Vec3d(0, 0, -2))
# Save the stage to the file:
stage.Save() |
from pxr import Usd, UsdGeom, Gf
# Open stage from example 2a
stage: Usd.Stage = Usd.Stage.Open("_assets/timecode_ex.usda")
sphere: UsdGeom.Sphere = UsdGeom.Sphere.Get(stage, "/World/Sphere")
if scale_attr := sphere.GetScaleOp().GetAttr():
scale_attr.Clear()
sphere_xform_api = UsdGeom.XformCommonAPI(sphere)
# Set scale of the sphere at time 1
sphere_xform_api.SetScale(Gf.Vec3f(1.00, 1.00, 1.00), time=1)
# Set scale of the sphere at time 30
sphere_xform_api.SetScale(Gf.Vec3f(1.00, 1.00, 1.00), time=30)
# Set scale of the sphere at time 45
sphere_xform_api.SetScale(Gf.Vec3f(1.00, 0.20, 1.25), time=45)
# Set scale of the sphere at time 50
sphere_xform_api.SetScale(Gf.Vec3f(0.75, 2.00, 0.75), time=50)
# Set scale of the sphere at time 60
sphere_xform_api.SetScale(Gf.Vec3f(1.00, 1.00, 1.00), time=60)
# Export to a new flattened layer for this example.
stage.Export("_assets/timecode_ex2b.usda", addSourceFileComment=False) |
#usda 1.0
(
endTimeCode = 60
startTimeCode = 1
)
def Xform "World"
{
def Sphere "Sphere"
{
float3 xformOp:scale.timeSamples = {
1: (1, 1, 1),
30: (1, 1, 1),
45: (1, 0.2, 1.25),
50: (0.75, 2, 0.75),
60: (1, 1, 1),
}
double3 xformOp:translate.timeSamples = {
1: (0, 5.5, 0),
30: (0, -4.5, 0),
45: (0, -5, 0),
50: (0, -3.25, 0),
60: (0, 5.5, 0),
}
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
def Cube "Backdrop"
{
color3f[] primvars:displayColor = [(0, 0, 1)]
float3 xformOp:scale = (5, 5, 0.1)
double3 xformOp:translate = (0, 0, -2)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
} |
| example.usd | notes |
|---|---|
#usda 1.0
(
timeCodesPerSecond = 24
framesPerSecond = 12
endTimeCode = 240
startTimeCode = 1
)
def Xform "Asset"
{
def Sphere "Sphere"
{
double3 xformOp:translate.timeSamples = {
1: (0, 5.0, 0),
240: (0, -5.0, 0),
}
uniform token[] xformOpOrder = ["xformOp:translate"]
}
} |
TimeCode 240 corresponds to 10 seconds of real time
If the left example layer was referenced into another
layer that specified a timeCodesPerSecond value of 48
the TimeSample at TimeCode 240 would be scaled to TimeCode
480 to ensure that the translation still occurs at 10
seconds of real time.
If the left example layer specified a framesPerSecond
of 12, this would not change the scaling of the TimeSample
at TimeCode 240, and instead change the playback rate in a
playback device to march forward by two TimeCodes for each
consecutive rendered frame, which will be held for 1/12 of
a second. |
In OpenUSD, a path is a string that uniquely identifies the location of a prim or property within a USD scenegraph hierarchy, similar to a filesystem path. The root of the scene is represented by /, and each nested prim is separated by /. For example:
/World/Geometry/Box
Primary Uses of Paths Paths are fundamental in USD for:
- Uniquely identifying prims and their properties within a scene.
- Navigating the scene hierarchy programmatically or via tools.
- Authoringâspecifying where to add or modify prims and properties on a stage.
- Querying and filtering prims, such as with expressions that match certain path patterns.
| Code | paths.usda |
|---|---|
from pxr import Usd
stage: Usd.Stage = Usd.Stage.CreateNew("_assets/paths.usda")
stage.DefinePrim("/hello")
stage.DefinePrim("/hello/world")
# Get the primitive at the path "/hello" from the current stage
hello_prim: Usd.Prim = stage.GetPrimAtPath("/hello")
# Get the primitive at the path "/hello/world" from the current stage
hello_world_prim: Usd.Prim = stage.GetPrimAtPath("/hello/world")
# Get the primitive at the path "/world" from the current stage
# Note: This will return an invalid prim because "/world" does not exist, but if changed to "/hello/world" it will return a valid prim
world_prim: Usd.Prim = stage.GetPrimAtPath("/world")
# Print whether the primitive is valid
print("Is /hello a valid prim? ", hello_prim.IsValid())
print("Is /hello/world a valid prim? ", hello_world_prim.IsValid())
print("Is /world a valid prim? ", world_prim.IsValid())
stage.Save()
|
def "hello"
{
def "world"
{
}
}
|
| Code | paths_build_and_nav.usda |
|---|---|
from pxr import Usd, UsdGeom, Sdf
stage = Usd.Stage.CreateNew("_assets/paths_build_and_nav.usda")
# Build prim paths via Sdf.Path
world_path = Sdf.Path("/World")
geometry_path = world_path.AppendChild("Geometry") # /World/Geometry
sphere_path = geometry_path.AppendChild("Sphere") # /World/Geometry/Sphere
looks_path = world_path.AppendChild("Looks") # /World/Looks
material_path = looks_path.AppendChild("Material") # /World/Looks/Material
# Define prims at those paths
stage.DefinePrim(world_path)
stage.DefinePrim(geometry_path)
UsdGeom.Sphere.Define(stage, sphere_path)
stage.DefinePrim(looks_path)
stage.DefinePrim(material_path)
# Path checks and basic navigation
print("sphere_path IsPrimPath:", sphere_path.IsPrimPath())
print("sphere_path parent:", sphere_path.GetParentPath())
print("Geometry prim valid:", stage.GetPrimAtPath(geometry_path).IsValid())
print("\nmaterial_path IsPrimPath:", material_path.IsPrimPath())
print("material_path parent:", material_path.GetParentPath())
print("Looks prim valid:", stage.GetPrimAtPath(looks_path).IsValid())
stage.Save()
|
#usda 1.0
def "World"
{
def "Geometry"
{
def Sphere "Sphere"
{
}
}
def "Looks"
{
def "Material"
{
}
}
}
|
| Code | paths_property_authoring.usda |
|---|---|
from pxr import Usd, UsdGeom, Sdf
stage = Usd.Stage.CreateNew("_assets/paths_property_authoring.usda")
# A prim to work with
sphere = UsdGeom.Sphere.Define(stage, "/World/Geom/Sphere")
# Create a property path for the attribute /World/Geom/Sphere.userProperties:tag
attr_property_path = sphere.GetPath().AppendProperty("userProperties:tag")
# Working with the property path for the attribute
owner_prim = stage.GetPrimAtPath(attr_property_path.GetPrimPath())
attr_name = stage.GetPropertyAtPath(attr_property_path).GetPath().name # "userProperties:tag"
print(f"Attribute property '{attr_name}' has been defined on {owner_prim.GetPath()} after AppendProperty: {owner_prim.GetAttribute(attr_name).IsDefined()}")
# Define the attribute on the owner prim
attr = owner_prim.CreateAttribute(attr_name, Sdf.ValueTypeNames.String)
print(f"\nAttribute property '{attr_name}' has been defined on {owner_prim.GetPath()} after CreateAttribute: {owner_prim.GetAttribute(attr_name).IsDefined()}")
attr.Set("surveyed")
print(f"Attribute value after Set: {stage.GetAttributeAtPath(attr_property_path).Get()}")
# Create a relationship from a property path
marker = UsdGeom.Xform.Define(stage, "/World/Markers/MarkerA")
# Create a property path for the relationship
rel_property_path = sphere.GetPath().AppendProperty("my:ref") # /World/Geom/Sphere.my:ref
# Working with the property path for the relationship
owner_prim = stage.GetPrimAtPath(rel_property_path.GetPrimPath())
rel_name = stage.GetPropertyAtPath(rel_property_path).GetPath().name # "my:ref"
print(f"\nRelationship property '{rel_name}' has been defined on {owner_prim.GetPath()} after AppendProperty: {owner_prim.GetRelationship(rel_name).IsDefined()}")
# Define the relationship on the owner prim
rel = owner_prim.CreateRelationship(rel_name)
print(f"\nRelationship property '{rel_name}' has been defined on {owner_prim.GetPath()} after CreateRelationship: {owner_prim.GetRelationship(rel_name).IsDefined()}")
rel.AddTarget(marker.GetPath())
print(f"Relationship targets after AddTarget: {[str(p) for p in stage.GetRelationshipAtPath(rel_property_path).GetTargets()]}")
stage.Save()
|
#usda 1.0
def "World"
{
def "Geom"
{
def Sphere "Sphere"
{
custom rel my:ref
prepend rel my:ref = </World/Markers/MarkerA>
custom string userProperties:tag = "surveyed"
}
}
def "Markers"
{
def Xform "MarkerA"
{
}
}
}
|
OpenUSD modules are organized libraries within the USD codebase that expose specific functionality for building, querying, or extending USD scenes. They help structure USDâs capabilities into cohesive APIs for different purposes such as scene authoring, composition, geometry, math utilities, and more.
Core USD Packages
| Name | Type | Description |
|---|---|---|
| base | Core Package | Foundational utilities and shared low-level functionality used across all USD modules |
| usd | Core Package | Core APIs for authoring, composing, and reading USD scene data |
| imaging | Optional Package | Rendering and visualization support for USD scenes |
| usdImaging | Optional Package | USD-specific imaging adapters and renderer integration |
Commonly Used USD Modules
| Name | Type | Description |
|---|---|---|
| Usd | Core Module | High-level API for stages, prims, properties, metadata, and composition arcs |
| Sdf | Foundation Module | Scene description foundation for layers, paths, serialization, and data structures |
| Gf | Utility Module | Math and geometry types such as vectors, matrices, and quaternions |
Schema Modules
| Name | Type | Description |
|---|---|---|
| UsdGeom | Schema Module | Geometry primitives, transforms, and spatial data |
| UsdShade | Schema Module | Materials, shaders, and shading networks |
| UsdPhysics | Schema Module | Physical properties, simulation, and physics metadata |
Metadata is âdata about the dataâ in a USD scene: extra information that guides behavior, organization, and pipeline decisions but is not the primary scene content (like geometry values).
Metadata commonly stores things like:
-
author / creation notes, annotations
-
project- or pipeline-specific tags
-
render / workflow hints or flags
Metadata can be authored at different scopes (for example on a stage, prim, or property), enabling both global and localized control.
| Aspect | Metadata | Attributes |
|---|---|---|
| Schema Relationship | Separate from the core schema data model | Defined explicitly by schemas |
| Intended Use | Supplementary and contextual information | Actual authored data that defines an object |
| Role in Scene | Guides behavior, organization, or pipeline logic | Describes concrete properties and state |
| Time-Varying | â Cannot be time-sampled | â Can be time-sampled |
| Performance Implication | Often cheaper to store and evaluate | Time-sampling may increase evaluation cost |
| Examples (Conceptual) | Tags, annotations, pipeline hints, ownership, workflow flags | Transform values, visibility, material parameters, animation data |
| Code |
|---|
# Retrieve the metadata value associated with the given key for a USD Object
usdobject.GetMetadata('key')
# Set the metadata value for the given key on a USD Object
usdobject.SetMetadata('key', value)
# Retrieve the metadata value associated with the given key for the stage
stage.GetMetadata('key')
# Set the metadata value for the given key on the stage
stage.SetMetadata('key', value)
# Use for better performance if accessing a single value and not all the metadata within a key
GetMetadataByDictKey()
|
The folders in USD. It does not represent any geometry or renderable content itself but acts as a container for organizing other prims
| Code | scope.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/scope.usda"
stage = Usd.Stage.CreateNew(file_path)
# World container (transformable)
world = UsdGeom.Xform.Define(stage, "/World")
num_a_prims = 2
num_b_prims = 2
# Two organizational Scopes (non-transformable grouping prims)
a_scope = UsdGeom.Scope.Define(stage, world.GetPath().AppendPath("A_Scope"))
b_scope = UsdGeom.Scope.Define(stage, world.GetPath().AppendPath("B_Scope"))
# Populate the scopes with some geometry
for a in range(num_a_prims):
sphere = UsdGeom.Sphere.Define(stage, a_scope.GetPath().AppendPath(f"A_Sphere_{a}"))
UsdGeom.XformCommonAPI(sphere).SetTranslate(Gf.Vec3d(a*2.5, 0, 0))
for b in range(num_b_prims):
cube = UsdGeom.Cube.Define(stage, b_scope.GetPath().AppendPath(f"B_Cube_{b}"))
UsdGeom.XformCommonAPI(cube).SetTranslate(Gf.Vec3d(b*2.5, -2.5, 0))
# Deactivate the A_Scope
a_scope.GetPrim().SetActive(False)
stage.Save()
|
|
In OpenUSD, an Xform is a type of prim that stores transformation data, such as translation, rotation, and scaling, which apply to its child prims.
| Code | xform_prim.usda |
|---|---|
# Import the necessary modules from the pxr package:
from pxr import Usd, UsdGeom
# Create a new USD stage with root layer named "xform_prim.usda":
file_path = "_assets/xform_prim.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
# Define a new Xform primitive at the path "/World" on the current stage:
world: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
# Save changes to the current stage to its root layer:
stage.Save()
print(stage.ExportToString(addSourceFileComment=False))
|
#usda 1.0
def Xform "World"
{
}
|
This API facilitates the authoring and retrieval of a common set of operations with a single translation, rotation, scale and pivot that is generally compatible with import and export into many tools. Itâs designed to simplify the interchange of these transformations.
| Code | xformcommonapi.usda |
|---|---|
from pxr import Usd, UsdGeom, Gf
file_path = "_assets/xformcommonapi.usda"
stage = Usd.Stage.CreateNew(file_path)
# A root transform group we will move and rotate
world = UsdGeom.Xform.Define(stage, "/World")
parent = UsdGeom.Xform.Define(stage, world.GetPath().AppendPath("Parent_Prim"))
# Parent Translate, Rotate, Scale using XformCommonAPI
parent_xform_api = UsdGeom.XformCommonAPI(parent)
parent_xform_api.SetTranslate(Gf.Vec3d(5, 0, 3))
parent_xform_api.SetRotate(Gf.Vec3f(90, 0, 0))
parent_xform_api.SetScale(Gf.Vec3f(3.0, 3.0, 3.0))
child_translation = Gf.Vec3d(2, 0, 0)
# Child A - inherits parent transforms
child_a_cone = UsdGeom.Cone.Define(stage, parent.GetPath().AppendChild("Child_A"))
child_a_xform_api = UsdGeom.XformCommonAPI(child_a_cone)
child_a_xform_api.SetTranslate(child_translation) # Parent_Prim transform + local placement
# Child B - "/World/Alt_Parent/Child_B" does NOT inherit Parent_Prim transforms
alt_parent = UsdGeom.Xform.Define(stage, world.GetPath().AppendChild("Alt_Parent"))
child_b_cone = UsdGeom.Cone.Define(stage, alt_parent.GetPath().AppendChild("Child_B"))
child_b_xform_api = UsdGeom.XformCommonAPI(child_b_cone)
child_b_xform_api.SetTranslate(child_translation) # local placement only
# Inspect the authored Xform Operation Order
print("Parent xformOpOrder:", UsdGeom.Xformable(parent).GetXformOpOrderAttr().Get())
print("Alt_Parent xformOpOrder:", UsdGeom.Xformable(alt_parent).GetXformOpOrderAttr().Get())
print("Child A xformOpOrder:", UsdGeom.Xformable(child_a_cone).GetXformOpOrderAttr().Get())
print("Child B xformOpOrder:", UsdGeom.Xformable(child_b_cone).GetXformOpOrderAttr().Get())
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Xform "Parent_Prim"
{
float3 xformOp:rotateXYZ = (90, 0, 0)
float3 xformOp:scale = (3, 3, 3)
double3 xformOp:translate = (5, 0, 3)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
def Cone "Child_A"
{
double3 xformOp:translate = (2, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
def Xform "Alt_Parent"
{
def Cone "Child_B"
{
double3 xformOp:translate = (2, 0, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
}
|
UsdLux is the schema domain that includes a set of light types and light-related schemas. It provides a standardized way to represent various types of lights, such as:
- Directional lights (UsdLuxDistantLight)
- Area lights, including
- Cylinder lights (UsdLuxCylinderLight)
- Rectangular area lights (UsdLuxRectLight)
- Disk lights (UsdLuxDiskLight)
- Sphere lights (UsdLuxSphereLight)
- Dome lights (UsdLuxDomeLight)
- Portal lights (UsdLuxPortalLight)
| Code | distant_light.usda |
|---|---|
from pxr import Usd, UsdGeom, UsdLux
file_path = "_assets/distant_light.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
world: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
geo_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, world.GetPath().AppendPath("Geometry"))
box_geo: UsdGeom.Cube = UsdGeom.Cube.Define(stage, geo_scope.GetPath().AppendPath("Cube"))
# Define a new Scope primitive at the path "/World/Lights" on the current stage:
lights_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, world.GetPath().AppendPath("Lights"))
# Define a new DistantLight primitive at the path "/World/Lights/SunLight" on the current stage:
distant_light: UsdLux.DistantLight = UsdLux.DistantLight.Define(stage, lights_scope.GetPath().AppendPath("SunLight"))
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Scope "Geometry"
{
def Cube "Cube"
{
}
}
def Scope "Lights"
{
def DistantLight "SunLight"
{
}
}
}
|
| Code | light_props.usda |
|---|---|
from pxr import Gf, Usd, UsdGeom, UsdLux
file_path = "_assets/light_props.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
geom_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, "/Geometry")
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, geom_scope.GetPath().AppendPath("Box"))
# Define a `Scope` Prim in stage at `/Lights`:
lights_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, "/Lights")
# Define a `Sun` prim in stage as a child of `lights_scope`, called `Sun`:
distant_light = UsdLux.DistantLight.Define(stage, lights_scope.GetPath().AppendPath("Sun"))
# Define a `SphereLight` prim in stage as a child of lights_scope called `SphereLight`:
sphere_light = UsdLux.SphereLight.Define(stage, lights_scope.GetPath().AppendPath("SphereLight"))
# Configure the distant light's emissive attributes:
distant_light.GetColorAttr().Set(Gf.Vec3f(1.0, 0.0, 0.0)) # Light color (red)
distant_light.GetIntensityAttr().Set(120.0) # Light intensity
# Lights are Xformable
if not (distant_light_xform_api := UsdGeom.XformCommonAPI(distant_light)):
raise Exception("Prim not compatible with XformCommonAPI")
distant_light_xform_api.SetRotate((45.0, 0.0, 0.0))
# Configure the sphere light's emissive attributes:
sphere_light.GetColorAttr().Set(Gf.Vec3f(0.0, 0.0, 1.0)) # Light color (blue)
sphere_light.GetIntensityAttr().Set(50000.0) # Light intensity
# Lights are Xformable
if not (sphere_light_xform_api := UsdGeom.XformCommonAPI(sphere_light)):
raise Exception("Prim not compatible with XformCommonAPI")
sphere_light_xform_api.SetTranslate((5.0, 10.0, 0.0))
stage.Save()
|
#usda 1.0
def Scope "Geometry"
{
def Cube "Box"
{
}
}
def Scope "Lights"
{
def DistantLight "Sun"
{
color3f inputs:color = (1, 0, 0)
float inputs:intensity = 120
float3 xformOp:rotateXYZ = (45, 0, 0)
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
}
def SphereLight "SphereLight"
{
color3f inputs:color = (0, 0, 1)
float inputs:intensity = 50000
double3 xformOp:translate = (5, 10, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}
}
|
A primvar (primitive variable) is a special kind of attribute that can vary and interpolate across a geometric primitive. You work with primvars through UsdGeomImageable and UsdGeomPrimvar. Review its class. See Primvar Data Management
Primvars support several interpolation modes, based on interpolation modes of the primvar equivalent Primitive Variable from RenderMan. These are:
- constant: One value remains constant over the entire surface prim. Note that only constant interpolation primvars will inherit down the namespace.
- uniform: One value remains constant for each UV patch segment of the surface prim (which is a face for meshes).
- varying: Four values are interpolated over each UV patch segment of the surface. Bilinear interpolation is used for interpolation between the four values.
- vertex: Values are interpolated between each vertex in the surface prim. The basis function of the surface is used for interpolation between vertices.
- faceVarying: For polygons and subdivision surfaces, four values are interpolated over each face of the mesh. Bilinear interpolation is used for interpolation between the four values.
Note: If youâre working with polygonal mesh data that is explicitly not intended to be subdivided, you should use varying interpolation in addition to setting the mesh subdivision scheme to none. This will ensure that the mesh renders identically whether a renderer supports subdivision or not.
Primvars have a characteristic different from most USD attributes in that primvars will get âinheritedâ down the scene namespace. Regular USD attributes only apply to the prim on which they are specified, but primvars implicitly also apply to all child imageable prims (unless the child prims have their own opinions about those primvars).
Primvar Element Size: sets how many consecutive values in the primvar array should be treated as a single element to be interpolated over a Gprim
| elementsize.usda | Flattened result |
|---|---|
#usda 1.0
(
)
def Cube "TestPrim"
{
string[] primvars:testPrimvar = ["element1-partA", "element1-partB", "element2-partA", "element2-partB"] (
elementSize = 2
)
int[] primvars:testPrimvar:indices = [0, 1, 0]
} |
[element1-partA, element1-partB, element2-partA, element2-partB, element1-partA, element1-partB]
|
Notice how the primvar indices refer to pairs of values in the value array â the â1â index refers to the third and fourth items in the values array.
Generally, the default element size of 1 will work for most use-cases. However you may encounter use-cases where you need to communicate an aggregate element to a renderer (e.g. representing spherical harmonics using nine floating-point coefficients) using larger element sizes.
If you want to dig much more in primvars and materials, follow the next doc.
đ More info
Value resolution is how OpenUSD figures out the final value of a property or piece of metadata by looking at all the different sources that might have information about it.
Key Differences and Main Points: Value Resolution vs Composition in OpenUSD
| Aspect | Value Resolution | Composition |
|---|---|---|
| Definition | Determines the final value of a property or metadata by combining multiple sources. | Determines where data comes from and creates an index of sources for each prim. |
| Caching | Not cached; values are calculated on demand for efficiency and memory optimisation. | Cached at the prim level for quick access. |
| Rules | Vary by data type (e.g., metadata, relationships, attributes). | Vary by composition arc and strength ordering. |
| Metadata Resolution | Strongest opinion wins; some metadata (e.g., dictionaries) combine element by element. | Strongest opinion wins based on composition arcs and strength ordering. |
| Relationship Resolution | Combines multiple targets using list editing semantics (e.g., merging lists). | Determines the hierarchy and structure of the scene. |
| Attribute Resolution | Considers value clips, time samples, and default values, with time scaling and interpolation. | Focuses on organising and layering data sources. |
| Performance | Optimised for runtime efficiency and low memory usage. | Pre-calculates composition logic for faster access. |
| Key Use Case | Combines data from multiple sources seamlessly in a non-destructive workflow. | Organises and indexes data sources for efficient scene management. |
| Python Tools | Use UsdAttributeQuery to cache attribute values for repeated access. |
No specific tools mentioned for caching. |
| Code | light_props.usda |
|---|---|
from pxr import Gf, Usd, UsdGeom, UsdLux
file_path = "_assets/light_props.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
geom_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, "/Geometry")
cube: UsdGeom.Cube = UsdGeom.Cube.Define(stage, geom_scope.GetPath().AppendPath("Box"))
# Define a `Scope` Prim in stage at `/Lights`:
lights_scope: UsdGeom.Scope = UsdGeom.Scope.Define(stage, "/Lights")
# Define a `Sun` prim in stage as a child of `lights_scope`, called `Sun`:
distant_light = UsdLux.DistantLight.Define(stage, lights_scope.GetPath().AppendPath("Sun"))
# Define a `SphereLight` prim in stage as a child of lights_scope called `SphereLight`:
sphere_light = UsdLux.SphereLight.Define(stage, lights_scope.GetPath().AppendPath("SphereLight"))
# Configure the distant light's emissive attributes:
distant_light.GetColorAttr().Set(Gf.Vec3f(1.0, 0.0, 0.0)) # Light color (red)
distant_light.GetIntensityAttr().Set(120.0) # Light intensity
# Lights are Xformable
if not (distant_light_xform_api := UsdGeom.XformCommonAPI(distant_light)):
raise Exception("Prim not compatible with XformCommonAPI")
distant_light_xform_api.SetRotate((45.0, 0.0, 0.0))
# Configure the sphere light's emissive attributes:
sphere_light.GetColorAttr().Set(Gf.Vec3f(0.0, 0.0, 1.0)) # Light color (blue)
sphere_light.GetIntensityAttr().Set(50000.0) # Light intensity
# Lights are Xformable
if not (sphere_light_xform_api := UsdGeom.XformCommonAPI(sphere_light)):
raise Exception("Prim not compatible with XformCommonAPI")
sphere_light_xform_api.SetTranslate((5.0, 10.0, 0.0))
stage.Save()
|
#usda 1.0
(
defaultPrim = "World"
endTimeCode = 120
startTimeCode = 1
timeCodesPerSecond = 30
)
def Xform "World"
{
float3 xformOp:rotateXYZ = (-75, 0, 0)
uniform token[] xformOpOrder = ["xformOp:rotateXYZ"]
def Cube "Ground"
{
float3 xformOp:scale = (10, 5, 0.1)
double3 xformOp:translate = (0, 0, -0.1)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
def Cube "StaticDefaultCube"
{
color3f[] primvars:displayColor = [(0.2, 0.2, 0.8)]
float3 xformOp:scale
double3 xformOp:translate = (8, 0, 1)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
def Cube "StaticCube"
{
color3f[] primvars:displayColor = [(0.8, 0.2, 0.2)]
float3 xformOp:scale = (1.5, 1.5, 1.5)
double3 xformOp:translate = (-8, 0, 1.5)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
def Cube "AnimCube"
{
color3f[] primvars:displayColor = [(0.2, 0.8, 0.2)]
float3 xformOp:scale = (1.5, 1.5, 1.5)
float3 xformOp:scale.timeSamples = {
60: (2.5, 2.5, 2.5),
120: (5, 5, 5),
}
double3 xformOp:translate = (0, 0, 1.5)
double3 xformOp:translate.timeSamples = {
60: (0, 0, 2.5),
120: (0, 0, 5),
}
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
}
}
|
| Code | value_resolution_composed.usda |
|---|---|
from pxr import Usd, UsdGeom
import os
# --- Layer 1 (weaker)
layer_1_path = "_assets/value_resolution_layer_1.usda"
layer_1_stage = Usd.Stage.CreateNew(layer_1_path)
layer_1_xform = UsdGeom.Xform.Define(layer_1_stage, "/World/XformPrim")
layer_1_xform_prim = layer_1_xform.GetPrim()
# "/World/XformPrim" customData
layer_1_xform_prim.SetCustomDataByKey("source", "layer_1")
layer_1_xform_prim.SetCustomDataByKey("opinion", "weak")
layer_1_xform_prim.SetCustomDataByKey("unique_layer_value", "layer_1_unique_value") # only authored in layer_1
# Relationship contribution from base
look_a = UsdGeom.Xform.Define(layer_1_stage, "/World/Looks/LookA")
layer_1_xform_prim.CreateRelationship("look:targets").AddTarget(look_a.GetPath())
layer_1_stage.Save()
# --- Layer 2 (stronger)
layer_2_path = "_assets/value_resolution_layer_2.usda"
layer_2_stage = Usd.Stage.CreateNew(layer_2_path)
layer_2_xform = UsdGeom.Xform.Define(layer_2_stage, "/World/XformPrim")
layer_2_xform_prim = layer_2_xform.GetPrim()
# "/World/XformPrim" customData
layer_2_xform_prim.SetCustomDataByKey("source", "layer_2")
layer_2_xform_prim.SetCustomDataByKey("opinion", "strong")
# Relationship contribution from override
look_b = UsdGeom.Xform.Define(layer_2_stage, "/World/Looks/LookB")
layer_2_xform_prim.CreateRelationship("look:targets").AddTarget(look_b.GetPath())
layer_2_stage.Save()
# --- Composed stage. First sublayer listed (layer_2) is strongest
composed_path = "_assets/value_resolution_composed.usda"
composed_stage = Usd.Stage.CreateNew(composed_path)
composed_stage.GetRootLayer().subLayerPaths = [os.path.basename(layer_2_path), os.path.basename(layer_1_path)]
xform_prim = composed_stage.GetPrimAtPath("/World/XformPrim")
resolved_custom_data = xform_prim.GetCustomData()
# resolved custom data:
print("Resolved CustomData:")
for key, value in resolved_custom_data.items():
print(f"- '{key}': '{value}'")
# resolved relationship targets:
targets = xform_prim.GetRelationship("look:targets").GetTargets()
print(f"\nResolved relationship targets: {[str(t) for t in targets]}") # both LookA and LookB
composed_stage.Save()
# Write out the composed stage to a single file for inspection
explicit_composed_path = '_assets/value_resolution_composed_explicit.usda'
txt = composed_stage.ExportToString(addSourceFileComment=False)
with open(explicit_composed_path, "w") as f:
f.write(txt)
|
#usda 1.0
def "World"
{
def Xform "XformPrim" (
customData = {
string opinion = "strong"
string source = "layer_2"
string unique_layer_value = "layer_1_unique_value"
}
)
{
custom rel look:targets = [
</World/Looks/LookB>,
</World/Looks/LookA>,
]
}
def "Looks"
{
def Xform "LookA"
{
}
def Xform "LookB"
{
}
}
}
|
List Editing
List editing is a USD feature that allows array-valued elements to be non-destructively and sparsely modified across composition layers, instead of being fully overridden. This enables layered workflows where stronger layers adjust lists defined in weaker layers without copying or rewriting them.
Supported List Operations:
| Operation | Description | Behavior |
|---|---|---|
| append | Add value(s) to the end of the resolved list | Existing values are reshuffled to the back |
| prepend | Add value(s) to the front of the resolved list | Existing values are reshuffled to the front |
| delete | Remove value(s) from the resolved list | Safe and speculative; no error if value doesnât exist |
| explicit (reset) | Replace the entire resolved list | Ignores all weaker-layer list operations |
| Rule | Explanation |
|---|---|
| Non-destructive | Weaker layers remain intact |
| Sparse | Only changes are authored |
| Order-aware | Prepend and append affect ordering |
| Layer-dependent strength | Prepending in weaker layers can override appends in stronger layers |
| No duplication | Resolved lists behave like sets |
| No repetition in ops | Same value cannot appear twice in one operation |
| Syntax | Meaning |
|---|---|
references = @file.usd@ |
Explicit reset |
references += @file.usd@ |
Append |
references -= @file.usd@ |
Delete |
| Single value allowed | Brackets optional for single entries |
| Multiple values | Use [ ... ] list syntax |
Here are a few ways we can use custom properties to enhance our OpenUSD workflows:
- Metadata storage : Storing additional information about a prim, such as author names, creation dates, or custom tags.
- Animation data : Defining custom animation curves or parameters that are not covered by standard schema properties.
- Simulation parameters : Storing parameters for physics simulations or other procedural generation processes.
- Arbitrary end user data : Because they can be easily defined at run time, custom properties are the best way to allow end users to define arbitrary custom data.
| Code | custom_attributes.usda |
|---|---|
from pxr import Usd, UsdGeom, Sdf
file_path = "_assets/custom_attributes.usda"
stage: Usd.Stage = Usd.Stage.CreateNew(file_path)
world_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World")
geometry_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, world_xform.GetPath().AppendPath("Packages"))
box_xform: UsdGeom.Xform = UsdGeom.Xform.Define(stage, geometry_xform.GetPath().AppendPath("Box"))
box_prim: Usd.Prim = box_xform.GetPrim()
box_prim.GetReferences().AddReference("./cubebox_a02/cubebox_a02.usd")
# Create additional attributes for the box prim
weight = box_prim.CreateAttribute("acme:weight", Sdf.ValueTypeNames.Float, custom=True)
category = box_prim.CreateAttribute("acme:category", Sdf.ValueTypeNames.String, custom=True)
hazard = box_prim.CreateAttribute("acme:hazardous_material", Sdf.ValueTypeNames.Bool, custom=True)
# Optionally document your custom property
weight.SetDocumentation("The weight of the package in kilograms.")
category.SetDocumentation("The shopping category for the products this package contains.")
hazard.SetDocumentation("Whether this package contains hazard materials.")
# Set values for the attributes
weight.Set(5.5)
category.Set("Cosmetics")
hazard.Set(False)
# Save the stage
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Xform "Packages"
{
def Xform "Box" (
prepend references = @./cubebox_a02/cubebox_a02.usd@
)
{
custom string acme:category = "Cosmetics" (
doc = "The shopping category for the products this package contains."
)
custom bool acme:hazardous_material = 0 (
doc = "Whether this package contains hazard materials."
)
custom float acme:weight = 5.5 (
doc = "The weight of the package in kilograms."
)
}
}
}
|
| Code | custom_attributes.usda |
|---|---|
from pxr import Usd
file_path = "_assets/custom_attributes.usda"
stage: Usd.Stage = Usd.Stage.Open(file_path)
box_prim = stage.GetPrimAtPath("/World/Packages/Box")
# Get the weight attribute
weight_attr: Usd.Attribute = box_prim.GetAttribute("acme:weight")
# Set the value of the weight attribute
weight_attr.Set(4.25)
# Print the weight of the box
print("Weight of Box:", weight_attr.Get())
stage.Save()
|
#usda 1.0
def Xform "World"
{
def Xform "Packages"
{
def Xform "Box" (
prepend references = @./cubebox_a02/cubebox_a02.usd@
)
{
custom string acme:category = "Cosmetics" (
doc = "The shopping category for the products this package contains."
)
custom bool acme:hazardous_material = 0 (
doc = "Whether this package contains hazard materials."
)
custom float acme:weight = 4.25 (
doc = "The weight of the package in kilograms."
)
}
}
}
|
đ More info
Deactivating a prim provides a way to temporarily remove, or prune, prims and their descendants from being composed and processed on the stage, which can make traversals more efficient.
| Code | Result |
|---|---|
from pxr import Usd
# Open the USD stage from the specified file
file_path = "_assets/active-inactive.usda"
stage = Usd.Stage.Open(file_path)
# Iterate through all the prims on the stage
# Print the state of the stage before deactivation
print("Stage contents BEFORE deactivating:")
for prim in stage.Traverse():
print(prim.GetPath())
# Get the "/World/Box" prim and deactivate it
box = stage.GetPrimAtPath("/World/Box")
# Passing in False to SetActive() will set the prim as Inactive and passing in True will set the prim as active
box.SetActive(False)
print("\n\nStage contents AFTER deactivating:")
for prim in stage.Traverse():
print(prim.GetPath())
|
Stage contents BEFORE deactivating:
/World
/World/Box
/World/Box/Geometry
/World/Box/Geometry/Cube
/World/Box/Materials
/World/Box/Materials/BoxMat
/World/Environment
/World/Environment/SkyLight
Stage contents AFTER deactivating:
/World
/World/Environment
/World/Environment/SkyLight
|
Hydra is a rendering architecture within OpenUSD that provides a high-performance, scalable, and extensible solution for rendering large 3D scenes. It serves as a bridge between the scene description data, such as USD, and the rendering backend, such as OpenGL or DirectX. Hydra is a rendering architecture within OpenUSD that bridges scene data and rendering backends. It operates on a scene delegate and enables render delegate plugins to generate rendering instructions for specific backends. Hydra supports various rendering backends and techniques, including rasterization and ray tracing. It provides extensibility through plugins and custom rendering backends.
đ More info
- Traversing a Stage -> (go to 2.2)
- LIVERPS -> (go to 1.7)
- References -> (go to 1.2.5)
- ISA Schemas -> (go to 3.1)
- Inherits -> (go to 1.2.2)
- USDCAT -> (go to 4.5)
| Code | .usda |
|---|---|
# To run From Script Editor in Omniverse
from pxr import Usd, UsdGeom, Sdf
from omni.usd import get_context
stage = get_context().get_stage()
world_prim = stage.GetPrimAtPath("/World")
if not world_prim.IsValid():
world_xform = UsdGeom.Xform.Define(stage, "/World")
else:
world_xform = UsdGeom.Xform(world_prim)
geometry_xform = UsdGeom.Sphere.Define(stage, "/World/Sphere")
from pxr import Usd, UsdGeom, Sdf
from omni.usd import get_context
stage = get_context().get_stage()
world_prim = stage.GetPrimAtPath("/World")
if not world_prim.IsValid():
world_xform = UsdGeom.Xform.Define(stage, "/World")
else:
world_xform = UsdGeom.Xform(world_prim)
geometry_xform = UsdGeom.Sphere.Define(stage, "/World/Sphere")
|
#usda 1.0
(
defaultPrim = "World"
endTimeCode = 1000000
metersPerUnit = 1
startTimeCode = 0
timeCodesPerSecond = 60
upAxis = "Z"
)
def Xform "World"
{
def Sphere "Sphere"
{
float3[] extent = [(-2, -2, -2), (2, 2, 2)]
color3f[] primvars:displayColor = [(0, 0, 1)]
double radius = 2
}
}
|
đ§ [Tutorial] (https://openusd.org/release/tut_inspect_and_author_props.html) - [Material] (https://github.com/DreamCodes4Life/OpenUSDFundamentals/tree/main/tutorials/authoringProperties)
Visibility is a builtin attribute of the UsdGeomImageable base schema, and models the simplest form of âpruningâ invisibility that is supported by most DCC apps. Visibility can take on two possible token string values:
inherited (the fallback value if the attribute is not authored)
invisible
If the resolved value is invisible, then neither the prim itself nor any prims in the subtree rooted at the prim should be rendered - this is what we mean by âpruning invisibilityâ, since invisible subtrees are definitively pruned in their entirety. If the resolve value is inherited, it means that the computed visibility (as provided by UsdGeomImageable::ComputeVisibility()) of the prim will be whatever the computed value of the primâs namespace parent is.
Visibility may be animated, allowing a sub-tree of geometry to be renderable for some segment of a shot, and absent from others; unlike the action of deactivating geometry prims, invisible geometry is still available for inspection, for positioning, for defining volumes, etc.
OpenUSD Performance Cheat Sheet
Practical notes distilled from the official âMaximizing Performanceâ docs.
Youâre likely hitting avoidable performance bottlenecks if you notice any of these:
- Stage open time grows much faster than linearly with scene size.
- Hydra / viewport feels sluggish when loading or switching shots.
- Tools that make many USD edits (imports, retimes, mass metadata updates) get slow as scenes grow.
- Assets are made up of many tiny layers or very large
.usdafiles.
| Topic | When It Matters (Symptoms) | What To Do |
|---|---|---|
| Use a multithreaded allocator | CPU usage looks high but scaling across cores is poor; loading big stages or imaging is slower than expected on many-core machines. | Link USD (and your DCC host if possible) against a multithread-friendly allocator such as jemalloc. This often gives ~2Ă speed-ups for multi-core workloads. On Linux, you can experiment by LD_PRELOAD-ing jemalloc when launching your app. |
Prefer binary .usd (usdc) for heavy data |
Large text .usda files take a long time to open; memory usage is high; Alembic-to-USD conversions feel heavy. |
Store geometry and animation caches in binary crate files (.usd / usdc). Reserve .usda mainly for small, human-readable âinterfaceâ layers (asset info, variants, payload wiring). Convert heavy .usda or Alembic to .usd where possible. |
| Package assets with payloads | Opening a full shot composes all geometry and shading at once, making first-open very slow; even just inspecting the scene graph is expensive. | Use payloads to separate a lightweight âinterfaceâ layer (model prim, asset info, variants, bbox) from heavier detail (geo, materials, animation). Open stages unloaded and load only the prims you actually need. |
| Keep layer counts reasonable | Each asset is made of many small layers (e.g., 10+), multiplied across all references and instances, causing lots of I/O and composition work. | Treat âlots of layers per assetâ as a smell. Flatten or stitch authoring/workflow layers into a smaller set at publish time. Use tools like usdcat --flatten and friends, and keep an eye on UsdStage::GetUsedLayers(). |
| Minimize prim count (vs. property count) | Stage open time grows sharply with the number of prims, even when you donât add much more data per prim. | Spend your âbudgetâ on fewer prims with more properties, not more prims. Avoid extra hierarchy prims that exist only for organization; use namespaced properties instead where possible. |
| Avoid unnecessary Xform+gprim pairs | Lots of tiny Xform + Mesh pairs everywhere; prim count goes through the roof for complex scenes. |
Use transformable gprims: in many cases you can transform the gprim itself instead of adding an extra Xform parent. This can significantly cut prim count (often 40â50% in dense scenes). |
| Use instancing at the right level | You have many repeated objects, but performance is still bad despite âinstancingâ everything at the gprim level. | Prefer instancing at asset / collection level instead of per-gprim. Fine-grained instancing alone doesnât reduce prim count and can add composition overhead; coarser instancing reduces both memory and composition work. |
When doing many edits to layers or specs in a loop (C++ or Python), change notifications and cache invalidation can dominate cost.
Use SdfChangeBlock when:
- Youâre importing, retiming, or mass-editing thousands of specs.
- You donât need observers (like a UI) to see each individual intermediate change.
- You conceptually care about the final state of the edits, not each step.
def means âEven though I may not be declaring a typed prim in this layer, by the time the UsdStage has finished composing, I expect a type to have been provided by referenced/layered scene descriptionâ (in the example, presumably the type would be provided by the layer targeted by the payload arc.
over means âIf some referenced layer happens to define this prim, then layer the information I contain onto it; but if not, just consider this information ancillaryâ
UsdGeomModelAPI adds draw modes that let you replace heavy geometry with simple stand-ins in the viewport for prims of kind = model. :contentReference[oaicite:0]{index=0}
Two key attributes control this:
model:drawMode(inheritable) â says what kind of stand-in to use (origin, bounds, cards, etc.). :contentReference[oaicite:1]{index=1}model:applyDrawMode(not inherited) â a boolean that says whether to actually use the resolved draw mode on this prim. :contentReference[oaicite:2]{index=2}
USD can then stop traversing the subtree at that prim and draw proxy geometry instead (axes, bounding box, or textured âcardsâ). This is mainly for performance + clarity when looking at big scenes. :contentReference[oaicite:3]{index=3}
For kind = component models, if you do not author model:applyDrawMode, they behave as if it were true when model:drawMode resolves to a non-default value. If you want to opt-out, apply UsdGeomModelAPI and explicitly set model:applyDrawMode = false. :contentReference[oaicite:4]{index=4}
cards mode uses flat quads around the model, with textures and layout controlled by model:cardGeometry and per-axis texture attributes (X/Y/Z ±). :contentReference[oaicite:5]{index=5}
| Attribute | Type / Scope | What It Does | Common Values / Notes |
|---|---|---|---|
model:drawMode |
token, inherited | Chooses the type of proxy geometry when draw mode is applied. | origin â draw basis vectors at the primâs origin. bounds â draw the model-space bounding box. cards â draw textured quads as a stand-in. default â draw the full USD subtree normally. inherited â defer to parentâs drawMode; if nothing is set anywhere, result is default. :contentReference[oaicite:6]{index=6} |
model:applyDrawMode |
bool, not inherited | Controls whether this prim actually uses the resolved model:drawMode. |
If true and resolved model:drawMode is not default, USD stops traversal here and draws proxy geometry. For kind = component, âno opinion authoredâ is treated as true by default. Set to false to force full geometry even when a non-default drawMode is inherited. :contentReference[oaicite:7]{index=7} |
model:drawModeColor |
color3f, not inherited | Base color for generated proxy geometry (axes, bounds lines, or untextured cards). | Default is a mid-gray; used when no texture exists or when a card face has an invalid texture path. :contentReference[oaicite:8]{index=8} |
model:cardGeometry |
token, not inherited | Controls the shape of the quads in cards mode. |
cross â quads that bisect the model along ±X, ±Y, ±Z. box â quads on the faces of the bounds box. fromTexture â quads and placement taken from texture metadata (world-to-screen matrix). :contentReference[oaicite:9]{index=9} |
model:cardTextureXPosmodel:cardTextureXNegmodel:cardTextureYPos / YNegmodel:cardTextureZPos / ZNeg |
asset paths, not inherited | Textures used on each axis-aligned card face in cards mode. |
If both + and â textures exist on an axis, each side uses its own. If only one is set, itâs mirrored (flipped in s) to the opposite side. Faces with missing/invalid textures use model:drawModeColor. In fromTexture mode, only faces with valid textures are drawn. :contentReference[oaicite:10]{index=10} |
- Set
model:drawModesomewhere up the hierarchy (origin,bounds, orcards). - On the prim where you want the proxy to actually be used, ensure
model:applyDrawMode = true(or rely on the component default). - Optionally: adjust
model:drawModeColor,model:cardGeometry, and the card textures for nicer impostors. :contentReference[oaicite:11]{index=11}
Thatâs enough to get simple, efficient stand-in rendering for your models using UsdGeomModelAPIâs draw modes.
|
|
|
https://docs.nvidia.com/learn-openusd/latest/stage-setting/properties/attributes.html





