Traversing

Traversing means ‘scanning’ all (or a subset of) the prims on a stage.

The simplest form of traversal is:

def traverse_and_print_prim_paths(stage):
    for prim in stage.Traverse():
        print(prim.GetPath())

traverse_and_print_prim_paths(stage)

You can test this out by generating a random scene hierarchy of prims (this is mostly boilerplate code)

from pxr import Sdf, UsdGeom, Usd, UsdLux, Gf
import omni.usd
import carb
import random
import string

# Function to generate a random identifier and make the sibling prim names unique
def generate_random_identifier(length=5):
    return ''.join(random.choice(string.ascii_letters) for _ in range(length))

BASE_DIRECTORY = "/tmp"  # This is where the .usda files will be saved

stage : Usd.Stage = Usd.Stage.CreateInMemory("RootLayer.usda")

xform : UsdGeom.Xform = UsdGeom.Xform.Define(stage, Sdf.Path("/World"))
environment_xform = UsdGeom.Xform.Define(stage, "/World/Environment")
dome_light = UsdLux.DomeLight.Define(stage, "/World/Environment/DomeLight")
dome_light.CreateIntensityAttr(1000)


# Function to generate a random scene hierarchy
def generate_random_hierarchy(parent_xform, depth):
    if depth <= 0:
        return

    for _ in range(random.randint(1, 4)):  # Randomly choose 1 to 4 children
        child_name = "Child_" + generate_random_identifier()  # Add a random identifier
        child_xform = UsdGeom.Xform.Define(stage, parent_xform.GetPath().AppendChild(child_name))
        if random.choice([True, False]):  # Randomly choose to add cube or sphere
            shape_name = "Cube_" + generate_random_identifier()  # Add a random identifier
            cube = UsdGeom.Cube.Define(stage, child_xform.GetPath().AppendChild(shape_name))
            extent = [(random.uniform(-50, 50), random.uniform(-50, 50), random.uniform(-50, 50)),
                      (random.uniform(-50, 50), random.uniform(-50, 50), random.uniform(-50, 50))]
            cube.GetExtentAttr().Set(extent)
            cube.GetSizeAttr().Set(random.uniform(10, 100))

            # Add random small offsets from the center
            offset = Gf.Vec3d(random.uniform(-100, 100), random.uniform(-100, 100), random.uniform(-100, 100))
            cube_center = Gf.Vec3d(0, 0, 0)  # Center of the scene
            cube_center += offset
            UsdGeom.Xformable(cube.GetPrim()).AddTranslateOp().Set(cube_center)

        else:
            shape_name = "Sphere_" + generate_random_identifier()  # Add a random identifier
            sphere = UsdGeom.Sphere.Define(stage, child_xform.GetPath().AppendChild(shape_name))
            extent = [(random.uniform(-50, 50), random.uniform(-50, 50), random.uniform(-50, 50)),
                      (random.uniform(-50, 50), random.uniform(-50, 50), random.uniform(-50, 50))]
            sphere.GetExtentAttr().Set(extent)

            # Add random small offsets from the center
            offset = Gf.Vec3d(random.uniform(-100, 100), random.uniform(-100, 100), random.uniform(-100, 100))
            sphere_center = Gf.Vec3d(0, 0, 0)  # Center of the scene
            sphere_center += offset
            UsdGeom.Xformable(sphere.GetPrim()).AddTranslateOp().Set(sphere_center)

            UsdGeom.Xformable(sphere.GetPrim()).AddScaleOp().Set(Gf.Vec3d(random.uniform(10, 100),
                                                                          random.uniform(10, 100),
                                                                          random.uniform(10, 100)))

        generate_random_hierarchy(child_xform, depth - 1)

# Generate the random hierarchy with a maximum depth of 5-6
generate_random_hierarchy(xform, random.randint(5, 6))


# Traversal!
####################################
def traverse_and_print_prim_paths(stage):
    for prim in stage.Traverse():
        print(prim.GetPath())

traverse_and_print_prim_paths(stage)
####################################


stage.GetRootLayer().Export(BASE_DIRECTORY + "/RootLayer.usda")
omni.usd.get_context().open_stage(BASE_DIRECTORY + "/RootLayer.usda")

Other common ways of scanning prims is by searching for specific types of prims (e.g. of Sphere type)

from typing import List, Type

def find_prims_by_type(stage: Usd.Stage, prim_type: Type[Usd.Typed]) -> List[Usd.Prim]:
    found_prims = [x for x in stage.Traverse() if x.IsA(prim_type)]
    return found_prims

prims: List[Usd.Prim] = find_prims_by_type(stage, UsdGeom.Sphere)
print(prims)

There are also UsdPrim::GetChild functions and UsdPrim::GetChildren() and UsdPrim::GetAllChildren() (the former returns the active, loaded, defined and non-abstract children of a prim, the latter returns any child).

Instance traversals

Recall that normal traversals don’t work with instances, in case you need to traverse instance proxies, you should use Usd.TraverseInstanceProxies

from pxr import Sdf, UsdGeom, Usd, UsdLux, Gf
import omni.usd
import carb

BASE_DIRECTORY = "/tmp"  # This is where the .usda files will be saved

parking_lot_stage : Usd.Stage = Usd.Stage.CreateInMemory("ParkingLot.usda")
car_stage : Usd.Stage = Usd.Stage.CreateInMemory("Car.usda")

# Car stage
xform : Usd.Prim = car_stage.DefinePrim(Sdf.Path("/Car"))
xform.GetPrim().CreateAttribute("color", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(0, 0, 0))
body : UsdGeom.Mesh = UsdGeom.Mesh.Define(car_stage, Sdf.Path("/Car/Body"))
body.GetPrim().CreateAttribute("color", Sdf.ValueTypeNames.Color3f).Set(Gf.Vec3f(0, 0, 0))
Door : UsdGeom.Mesh = UsdGeom.Mesh.Define(car_stage, Sdf.Path("/Car/Door"))
car_stage.GetRootLayer().Export(BASE_DIRECTORY + "/Car.usda")

# Parking Lot stage
xform : Usd.Prim = parking_lot_stage.DefinePrim(Sdf.Path("/ParkingLot"))
car1_prim : Usd.Prim = parking_lot_stage.DefinePrim("/ParkingLot/Car_1")
loaded_layer = Sdf.Layer.FindOrOpen(BASE_DIRECTORY + "/Car.usda")
car1_prim.GetReferences().AddReference(loaded_layer.identifier, "/Car")
car2_prim : Usd.Prim = parking_lot_stage.DefinePrim("/ParkingLot/Car_2")
loaded_layer = Sdf.Layer.FindOrOpen(BASE_DIRECTORY + "/Car.usda")
car2_prim.GetReferences().AddReference(loaded_layer.identifier, "/Car")
car3_prim : Usd.Prim = parking_lot_stage.DefinePrim("/ParkingLot/Car_3")
loaded_layer = Sdf.Layer.FindOrOpen(BASE_DIRECTORY + "/Car.usda")
car3_prim.GetReferences().AddReference(loaded_layer.identifier, "/Car")

# Mark with metadata Car_1 and Car_2 as instanceable, i.e. "these reference prims can be reused"
# while Car_3 is not marked
car1_prim.SetInstanceable(True)
car2_prim.SetInstanceable(True)


# Traversal!
####################################

# This will only return non-instance prims
# def traverse_and_print_prim_paths(stage):
#     for prim in stage.Traverse():
#         print(prim.GetPath())
# traverse_and_print_prim_paths(parking_lot_stage)

# This will allow you to traverse instanced prims thanks to instance proxies!
def traverse_and_print_all_prim_paths(stage):
    for prim in stage.Traverse(Usd.TraverseInstanceProxies()):
        print(prim.GetPath())
traverse_and_print_all_prim_paths(parking_lot_stage)

####################################

# Export root layer to file
parking_lot_stage.GetRootLayer().Export(BASE_DIRECTORY + "/ParkingLot.usda")
omni.usd.get_context().open_stage(BASE_DIRECTORY + "/ParkingLot.usda")