xform-common-api-usage#
Code |
HI.005 |
---|---|
Validator |
|
Compatibility |
openusd |
Tags |
✅ |
Summary#
Transformations on prims that are intended to be translated, rotated or scaled by users should conform to the UsdGeomXformCommonAPI.
Description#
UsdGeomXformCommonAPI
provides a standardized interface for authoring common transformation operations. Developers are encouraged to avoid direct manipulation of the xformOpOrder and individual xformOp attributes unless advanced control is explicitly required.
If a prim is not intended to be translated, rotated, or scaled by users, the recommended alternatives are:
UsdGeomXformCommonAPI (e.g. [“xformOp:translate”, “xformOp:translate:pivot”, “xformOp:rotateXYZ”, “xformOp:scale”, “!invert!xformOp:translate:pivot”])
Transform Matrix (e.g. [“xformOp:transform”])
Translate + Quaternion (e.g. [“xformOp:translate”, “xformOp:orient”])
Why is it required?#
Avoids the need to manually decompose the transform matrix into translation, rotation and scale components when human readable values are required.
Provides a consistent transformation description when composing transforms from multiple USD layers.
Examples#
#usda 1.0
# Recommended: Using UsdGeomXformCommonAPI
def Xform "Door" ()
{
def Scope "Geometry" {
def Xform "Panel" {
float3 xformOp:translate = (0.0, 0.0, 0.0)
float3 xformOp:rotateXYZ = (0.0, 0.0, -45.0)
float3 xformOp:scale = (1.0, 1.0, 1.0)
uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
}
}
}
# Not recommended: Usage of transform xformOp for prims that are not intended to be translated, rotated or scaled by users
def Xform "Door_NonRecommended" ()
{
def Scope "Geometry" {
def Xform "Panel" {
matrix4d xformOp:transform = ((0.70710678, -0.70710678, 0, 0), (0.70710678, 0.70710678, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
uniform token[] xformOpOrder = ["xformOp:transform"]
}
}
}
How to comply#
Use the UsdGeomXformCommonAPI to author transformations on prims that are intended to be translated, rotated or scaled by users (e.g. the root prim of an asset)
Use the standard translate, rotate, scale, pivot and inverse pivot attributes provided by the API
Avoid direct manipulation of xformOpOrder unless advanced control is needed
For more information#
Utility Script#
The script below will convert xformOp:transform to UsdGeomXformCommonAPI xform Ops.
Note: The xformOp:transform attributes should be defined in the current edit target to that the script can remove them.
"""
OpenUSD Script: Convert Transform Ops to TRS (Translate, Rotate, Scale) Ops
Using UsdGeomXformCommonAPI
This script converts transform xform ops (matrix4d) on USD prims to the standard
TRS format supported by XformCommonAPI using the built-in GetXformVectors() and
SetXformVectors() methods that handle all the matrix decomposition automatically.
"""
from pxr import Usd, UsdGeom, Gf, Sdf
import omni.usd
def convert_prim_to_trs(prim, time_samples=None, rotation_order=UsdGeom.XformCommonAPI.RotationOrderXYZ):
"""
Convert a prim's transform operations to TRS format using XformCommonAPI.
Args:
prim: UsdPrim to convert
time_samples: List of time codes to sample, or None for default time
rotation_order: UsdGeom.XformCommonAPI.RotationOrder enum value
Returns:
bool: True if conversion was successful, False otherwise
"""
if not prim.IsValid():
print(f"Error: Invalid prim {prim.GetPath()}")
return False
# Check if prim is transformable
if not UsdGeom.Xformable(prim):
print(f"Warning: Prim {prim.GetPath()} is not transformable, skipping")
return True
xformable = UsdGeom.Xformable(prim)
# Create XformCommonAPI
xform_api = UsdGeom.XformCommonAPI(prim)
# Check if prim has any matrix transform ops that need conversion
existing_ops = xformable.GetOrderedXformOps()
has_matrix_op = any(op.GetOpType() == UsdGeom.XformOp.TypeTransform for op in existing_ops)
if not has_matrix_op:
print(f"Info: Prim {prim.GetPath()} has no matrix transform ops, skipping")
return True
# Get time codes to process
time_codes = time_samples if time_samples else [Usd.TimeCode.Default()]
print(f"Converting prim {prim.GetPath()} to TRS format...")
# First, read all the transform data for each time sample
transform_data = {}
# Process each time sample to get the data
for time_code in time_codes:
# Use GetXformVectors to extract TRS components - this handles all the matrix decomposition!
# The function returns a tuple of (translation, rotation, scale, pivot, rotation_order)
try:
result = xform_api.GetXformVectors(time_code)
if not result:
print(f"Warning: Failed to get xform vectors for {prim.GetPath()} at time {time_code}")
continue
translation, rotation, scale, pivot, rotation_order_out = result
transform_data[time_code] = (translation, rotation, scale, pivot, rotation_order_out)
except Exception as e:
print(f"Warning: Exception getting xform vectors for {prim.GetPath()} at time {time_code}: {e}")
continue
# Now clear existing transform ops and delete the old matrix transform ops
existing_ops = xformable.GetOrderedXformOps()
for op in existing_ops:
print(op)
if op.GetOpType() == UsdGeom.XformOp.TypeTransform:
# Remove the transform op attribute entirely
prim.RemoveProperty(op.GetAttr().GetName())
xformable.ClearXformOpOrder()
# Set the new TRS ops for each time sample
for time_code, (translation, rotation, scale, pivot, rotation_order_out) in transform_data.items():
# Clear existing xform ops and set new TRS ops
# SetXformVectors will create the proper TRS op structure
success = xform_api.SetXformVectors(
translation, rotation, scale, pivot, rotation_order, time_code
)
if not success:
print(f"Warning: Failed to set xform vectors for {prim.GetPath()} at time {time_code}")
continue
print(f"Successfully converted {prim.GetPath()} to TRS format")
return True
def convert_stage_to_trs(stage, prim_paths=None, time_samples=None, rotation_order=UsdGeom.XformCommonAPI.RotationOrderXYZ):
"""
Convert all or specified prims in a stage to TRS format.
Args:
stage: UsdStage to process
prim_paths: List of prim paths to convert, or None for all transformable prims
time_samples: List of time codes to sample
rotation_order: Rotation order to use for conversions
Returns:
int: Number of prims successfully converted
"""
if not stage:
print("Error: Invalid stage")
return 0
converted_count = 0
if prim_paths:
# Convert specific prims
for path_str in prim_paths:
prim_path = Sdf.Path(path_str)
prim = stage.GetPrimAtPath(prim_path)
if convert_prim_to_trs(prim, time_samples, rotation_order):
converted_count += 1
else:
# Convert all prims with matrix transform ops
for prim in stage.Traverse():
if UsdGeom.Xformable(prim):
xformable = UsdGeom.Xformable(prim)
existing_ops = xformable.GetOrderedXformOps()
has_matrix_op = any(op.GetOpType() == UsdGeom.XformOp.TypeTransform for op in existing_ops)
if has_matrix_op and convert_prim_to_trs(prim, time_samples, rotation_order):
converted_count += 1
return converted_count
# Example usage in Omniverse Script Editor:
# Get the current stage
stage = omni.usd.get_context().get_stage()
# Convert all prims with matrix transform ops to TRS format
if stage:
print("Converting all prims with matrix transform ops to TRS format...")
converted_count = convert_stage_to_trs(stage)
print(f"Successfully converted {converted_count} prims to TRS format")
else:
print("Error: No stage available")
# Or convert specific prims:
# convert_prim_to_trs(stage.GetPrimAtPath("/World/Cube"))
# Or convert with specific rotation order:
# convert_stage_to_trs(stage, rotation_order=UsdGeom.XformCommonAPI.RotationOrderZXY)