Skip to article frontmatterSkip to article content

Chapter 6

6.1 Revisiting Xforms

Create a stage

from pxr import Usd, UsdGeom, Gf, Sdf
stage = Usd.Stage.CreateNew("xform.usd")
xform = UsdGeom.Xform.Define(stage, "/World/Xform")

A function to create a Prim by type

def create_prim(stage: Usd.Stage, prim_path: str, prim_type: str):    
    return stage.DefinePrim(prim_path, prim_type)    

Use the function to create an Xform

create_prim(stage, "/World/Xform", "Xform")

Capture the return value of the create_prim function as a variable

light_prim = create_prim(stage, "/Lights/DistantLight", "DistantLight") 

Accesses the Xform of the light_prim

xform_light = UsdGeom.Xformable(light_prim)

Rotate the distant light’s Xform by 50° around the y axis

rotation_op = xform_light.AddRotateXYZOp()   
rotation_op.Set(Gf.Vec3d(0, 50, 0))    

6.1.1 Understanding Transform Order

Using different transform orders

cube1 = UsdGeom.Cube.Define(stage, "/World/Cube1") 
cube1.AddRotateXYZOp().Set(Gf.Vec3d(45, 0, 0)) 
cube1.AddTranslateOp().Set(Gf.Vec3d(0, 10, 0))

cube2 = UsdGeom.Cube.Define(stage, "/World/Cube2")
cube2.AddTranslateOp().Set(Gf.Vec3d(0, 10, 0))
cube2.AddRotateXYZOp().Set(Gf.Vec3d(45, 0, 0))

6.1.2 Applying Transform Order

Clear the transform order of an Xform

xform.ClearXformOpOrder()

Use ‘Quaternion’ rotation

xform.AddTranslateOp()
xform.AddOrientOp()   
xform.AddScaleOp()    

Check the transform order

xform_ops = xform.GetOrderedXformOps()    
for op in xform_ops:
    print(op.GetOpName())    

Alter a transform based on the order

xform_ops[0].Set(Gf.Vec3d(0,0,0))   
xform_ops[1].Set(Gf.Quatf(1,0,0,0)) 
xform_ops[2].Set(Gf.Vec3d(1,1,1))   

Remove the existing order in cube1

cube1 = UsdGeom.Xform.Get(stage, '/World/Cube1')    
cube1.ClearXformOpOrder()    

Add new transform order to cube1

cube1.AddTranslateOp()
cube1.AddOrientOp()
cube1.AddScaleOp()

Check the transform order

xform_ops = cube1.GetOrderedXformOps()   
for op in xform_ops:
    print(op.GetOpName())   

Assign new values to cube1’s transform

xform_ops[0].Set(Gf.Vec3d(0,5,0))    
xform_ops[1].Set(Gf.Quatf(0.92, 0.38, 0, 0))    
xform_ops[2].Set(Gf.Vec3d(2,2,2))    

6.2.1 Euler Angles

Using Euler angles to represent a rotation

xform.ClearXformOpOrder()    
xform.AddRotateXYZOp().Set(Gf.Vec3d(45, 0, 0))    

6.2.2 Quaternions

Use a quaternion to represent the orientation of an Xform

xform.ClearXformOpOrder()     
w, x, y, z = (0.92, 0.38, 0, 0)   
xform.AddOrientOp().Set(Gf.Quatf(w, x, y, z))    

6.2.3 Transform and Rotation Matrices

Add a transform matrix

matrix = Gf.Matrix4d(0.707, -0.707, 0, 0,
    0.707, 0.707, 0, 0,
    0, 0, 1, 0,
    100,200,0,1)    
xform.MakeMatrixXform().Set(matrix)     

Obtaining World & Local Transforms

Lising 5.2 Get the World Transform Matrix

def get_world_transform(stage: Usd.Stage, prim_path: str):   
    prim = stage.GetPrimAtPath(prim_path)
    xform = UsdGeom.Xformable(prim)    
    time_code = Usd.TimeCode.Default()    
    world_transform: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time_code)    
    return world_transform    

Listing 5.3 Get the Local Transform Matrix

def get_local_transform(stage: Usd.Stage, prim_path: str):
    prim = stage.GetPrimAtPath(prim_path)
    xform = UsdGeom.Xformable(prim)    
    time_code = Usd.TimeCode.Default()
    local_transformation: Gf.Matrix4d = xform.GetLocalTransformation()    
    return local_transformation    

Get the world transform of the prim ‘Cube1’

world_transform = get_world_transform(stage, "/World/Cube1")

Extract the translation

translation: Gf.Vec3d = world_transform.ExtractTranslation() 

Extract the rotation

rotation: Gf.Rotation = world_transform.ExtractRotation() 

Extract the quaternion rotation

q: Gf.Quatf = world_transform.ExtractRotationQuat()

Extract the scale

rotation_matrix = world_transform.ExtractRotationMatrix()    
scale: Gf.Vec3d = Gf.Vec3d([v.GetLength() for v in rotation_matrix])  

Print transform info

print(f"Translation: {translation}\n"  
    f"Rotation (Quaternion): {q}\n"    
    f"Rotation Matrix: {rotation_matrix}\n"
    f"Scale: {scale}")

Extract local transformation data

local_transformation = get_local_transform(stage, "/World/Cube1")    
translation_local: Gf.Vec3d = local_transformation.ExtractTranslation()    

6.3.2 Compute the Bounding Box

Listing 5.4 Compute the Bounding Box

def compute_bounding_box(stage, prim_path):    
    prim = stage.GetPrimAtPath(prim_path)
    purposes = [UsdGeom.Tokens.default_]    
    bboxcache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), purposes)    
    bboxes = bboxcache.ComputeWorldBound(prim)    
    min_point = bboxes.ComputeAlignedRange().GetMin()
    max_point = bboxes.ComputeAlignedRange().GetMax()    
    return min_point, max_point    

Apply the function on ‘Cube1’

prim_path = "/World/Cube1" 
min_point, max_point = compute_bounding_box(stage, prim_path)   

6.3.3 An Example Stage

Create a new stage and reference to the dice

from pxr import Usd, UsdGeom, Gf
stage = Usd.Stage.CreateNew("dice_scene.usd")
dice1 =  UsdGeom.Xform.Define(stage, '/World/Dice1')
dice1.GetPrim().GetReferences().AddReference('./Assets/Dice.usd')  

Apply new transformations

dice1.ClearXformOpOrder()    
dice1.AddTranslateOp().Set(Gf.Vec3d(220, 100, 100))
dice1.AddRotateXYZOp().Set(Gf.Vec3d(-180, 0, 90)) 
dice1.AddScaleOp().Set(Gf.Vec3d(1, 1, 1))    

Add a new dice through internal reference

dice2=  UsdGeom.Xform.Define(stage, '/World/Dice2') 
dice2.GetPrim().GetReferences().AddInternalReference("/World/Dice1") 

‘get_world_transform’ function (same as Lising 5.2)

def get_world_transform(stage: Usd.Stage, prim_path: str):
    prim = stage.GetPrimAtPath(prim_path)
    xform = UsdGeom.Xformable(prim)
    time_code = Usd.TimeCode.Default()
    world_transform: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time_code)
    return world_transform

‘compute_bounding_box’ function (same as Lising 5.4)

def compute_bounding_box(stage, prim_path):
    prim = stage.GetPrimAtPath(prim_path)
    purposes = [UsdGeom.Tokens.default_]
    bboxcache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), purposes)
    bboxes = bboxcache.ComputeWorldBound(prim)
    min_point = bboxes.ComputeAlignedRange().GetMin() 
    max_point = bboxes.ComputeAlignedRange().GetMax()
    return min_point, max_point

Retrieve the translation and rotation of ‘Dice1’

dice1_transform_matrix = get_world_transform(stage, "/World/Dice1") 
dice1_translation =  dice1_transform_matrix.ExtractTranslation()
dice1_rotation = dice1_transform_matrix.ExtractRotationQuat().GetNormalized()

Calculate the bounding box for ‘Dice1’

box_min, box_max = compute_bounding_box(stage, "/World/Dice1") 
dice_size = box_max[0] - box_min[0]   

Get translations to position ‘Dice2’ relative to ‘Dice1’

dice2_translation = dice1_translation - Gf.Vec3d(dice_size, 0, 0)

Add rotation to ‘Dice2’ to have the correct face facing forwards

further_rotation = Gf.Rotation(Gf.Vec3d(1, 0, 0), 90).GetQuat()
dice2_rotation = further_rotation * dice1_rotation  

Apply all the new transforms to ‘Dice2’

dice2.ClearXformOpOrder() 
dice2.AddTranslateOp().Set(dice2_translation) 
dice2.AddOrientOp().Set(Gf.Quatf(dice2_rotation))
dice2.AddScaleOp().Set(Gf.Vec3d(1, 1, 1))

Repeat the previous process to create a new dice and form “USD”

dice3 = UsdGeom.Xform.Define(stage, '/World/Dice3')
dice3.GetPrim().GetReferences().AddInternalReference("/World/Dice1")
dice3_translation = dice2_translation - Gf.Vec3d(dice_size, 0, 0)
dice3_rotation = Gf.Rotation(Gf.Vec3d(0, 1, 0), 90).GetQuat() * dice2_rotation 
dice3.ClearXformOpOrder() 
dice3.AddTranslateOp().Set(dice3_translation) 
dice3.AddOrientOp().Set(Gf.Quatf(dice3_rotation) )
dice3.AddScaleOp().Set(Gf.Vec3d(1, 1, 1))

6.3.4 Enhancing the Look of Your Stage

Add a simple background

backdrop =  UsdGeom.Xform.Define(stage, '/World/Backdrop')
backdrop.GetPrim().GetReferences().AddReference('./Assets/Backdrop.usd')   

Design thoughtful lighting

from pxr import UsdLux
dome_light = UsdLux.DomeLight.Define(stage, "/Environment/Sky")
dome_light.CreateIntensityAttr(500)
distant_light = UsdLux.DistantLight.Define(stage, "/World/Lights/DistantLight")
distant_light.AddRotateXYZOp().Set(Gf.Vec3d(-51.3, 0, -46.1))
distant_light.CreateIntensityAttr(750)

Vary colors and materials

from pxr import UsdShade
shader_path = "/World/Dice2/materials/Dice_Color/preview_Principled_BSDF" 
shader = UsdShade.Shader(stage.GetPrimAtPath(shader_path))   
shader.GetInput("diffuseColor").Set(Gf.Vec3f(0.8, 0, 0))