Transformations
Transformations in USD are represented with the UsdGeomXformOp class, e.g. xformOp:translate
, xformOp:scale
, xformOp:orient
(to represent a rotation with a quaternion) or xformOp:rotateYXZ
(to represent a rotation with euler angles applied in this order: Y
, X
and then finally Z
). Note the property namespace xformOp
where all operations are defined.
The transformations are stored along a prim and can be overridden in stronger layers and/or composed in the usual USD fashion. The class responsible for managing transformation on a prim is called UsdGeomXformable
.
As we’ve already seen USD uses inheritance to model which functionalities a prim owns: in the following graph the green boxes represent classes from the UsdGeomImageable
base class that provides attributes and functions for all prims that require rendering or visualization, to UsdGeomXformable
which provides a stack of operations (translate/rotate/etc.) that will be applied to the prim before visualizing it, to UsdGeomGprim
which is the base class for all geometric primitives (and encodes stuff like doubleSided
or orientation
and where the displayColor
primvar also resides) to the UsdGeomCube
class which we’ve already seen many times to create a Cube
prim.
Let’s take a look at a simple example of an Xform
with a child Cube
prim: the Xform
will have a scale and a translation transformations in its local stack and therefore those transformations will be inherited by the Cube
as well.
from pxr import Sdf, UsdGeom, Usd, UsdLux, Gf, Kind
import omni.usd
import carb
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)
# Create an Xform and a Cube as child of the Xform
xform : UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World/Xform")
cube : UsdGeom.Cube = UsdGeom.Cube.Define(stage, "/World/Xform/Cube")
if not xform.GetPrim().IsA(UsdGeom.Xformable):
carb.log_error("Unexpected! A Xform derives from Xformable, as gprims do!")
# Define a scaling operation and a translation on the Xform
# These will be inherited by the cube's transformation
scale = Gf.Vec3f(50, 50, 50)
# Try to get the scale attribute or create it in case it doesn't exist yet
dstOp = UsdGeom.XformOp(xform.GetPrim().GetAttribute("xformOp:scale"))
if not dstOp:
# Create this scale op
xformable : UsdGeom.Xformable = UsdGeom.Xformable(xform)
dstOp = xformable.AddXformOp(UsdGeom.XformOp.TypeScale, UsdGeom.XformOp.PrecisionFloat)
dstOp.Set(Gf.Vec3f(scale))
translate = Gf.Vec3f(20, 0, 0)
dstOp = UsdGeom.XformOp(xform.GetPrim().GetAttribute("xformOp:translate"))
if not dstOp:
# Create this translate op
xformable : UsdGeom.Xformable = UsdGeom.Xformable(xform)
dstOp = xformable.AddXformOp(UsdGeom.XformOp.TypeTranslate, UsdGeom.XformOp.PrecisionFloat)
dstOp.Set(Gf.Vec3f(translate))
stage.GetRootLayer().Export(BASE_DIRECTORY + "/RootLayer.usda")
omni.usd.get_context().open_stage(BASE_DIRECTORY + "/RootLayer.usda")
If you inspect the generated .usda
, you’ll find that the order of the XformOp
s as we added them was also stored:
$ cat /tmp/RootLayer.usda
#usda 1.0
def Xform "World"
{
def Xform "Environment"
{
def DomeLight "DomeLight"
{
float inputs:intensity = 1000
}
}
def Xform "Xform"
{
float3 xformOp:scale = (50, 50, 50)
float3 xformOp:translate = (20, 0, 0)
uniform token[] xformOpOrder = ["xformOp:scale", "xformOp:translate"] # Order is stored
def Cube "Cube"
{
}
}
}
In case we wanted to apply first the translate operation and then the scale operation, the order could also have been changed by re-assigning those operations to the Xform
local stack with SetXformOpOrder
.
Reset Xform Stack
There is one particular operation called !resetXformOp!
which acts like a boolean flag: if added to a prim
- it is always added as first op in the
xformOpOrder
- it causes the prim not to inherit its parent’s transformation
Let’s see the same example as before but with a !resetXformOp!
applied to the Cube
(not to the Xform
- it would mean that we don’t want the Xform
to inherit transformations by /World
, but that’s not what we’re trying to do here):
from pxr import Sdf, UsdGeom, Usd, UsdLux, Gf, Kind
import omni.usd
import carb
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)
# Create an Xform and a Cube as child of the Xform
xform : UsdGeom.Xform = UsdGeom.Xform.Define(stage, "/World/Xform")
cube : UsdGeom.Cube = UsdGeom.Cube.Define(stage, "/World/Xform/Cube")
if not xform.GetPrim().IsA(UsdGeom.Xformable):
carb.log_error("Unexpected! A Xform derives from Xformable, as gprims do!")
# Define a scaling operation and a translation on the Xform. But also set a
# resetXformStack op: the transformations will *NOT* be inherited by the cube
scale = Gf.Vec3f(50, 50, 50)
# Try to get the scale attribute or create it in case it doesn't exist yet
dstOp = UsdGeom.XformOp(xform.GetPrim().GetAttribute("xformOp:scale"))
if not dstOp:
# Create this scale op
xformable : UsdGeom.Xformable = UsdGeom.Xformable(xform)
dstOp = xformable.AddXformOp(UsdGeom.XformOp.TypeScale, UsdGeom.XformOp.PrecisionFloat)
dstOp.Set(Gf.Vec3f(scale))
translate = Gf.Vec3f(20, 0, 0)
dstOp = UsdGeom.XformOp(xform.GetPrim().GetAttribute("xformOp:translate"))
if not dstOp:
# Create this translate op
xformable : UsdGeom.Xformable = UsdGeom.Xformable(xform)
dstOp = xformable.AddXformOp(UsdGeom.XformOp.TypeTranslate, UsdGeom.XformOp.PrecisionFloat)
dstOp.Set(Gf.Vec3f(translate))
# Set the resetXformStack on the cube (NOT to the Xform.. otherwise the cube would have been affected)
xformable : UsdGeom.Xformable = UsdGeom.Xformable(cube.GetPrim())
xformable.SetResetXformStack(True)
stage.GetRootLayer().Export(BASE_DIRECTORY + "/RootLayer.usda")
omni.usd.get_context().open_stage(BASE_DIRECTORY + "/RootLayer.usda")
$ cat /tmp/RootLayer.usda
#usda 1.0
def Xform "World"
{
def Xform "Environment"
{
def DomeLight "DomeLight"
{
float inputs:intensity = 1000
}
}
def Xform "Xform"
{
float3 xformOp:scale = (50, 50, 50)
float3 xformOp:translate = (20, 0, 0)
uniform token[] xformOpOrder = ["xformOp:scale", "xformOp:translate"]
def Cube "Cube"
{
uniform token[] xformOpOrder = ["!resetXformStack!"] # Note the resetXformStack
}
}
}
Note how the !resetXformStack!
was declared as a uniform
token: tokens are types of attributes in USD which can encode string values, paths or - as in this case - concepts that do not vary over time or across different instances of a prim (think of uniform
pretty much like a GLSL shader’s uniform
).
In this second example the cube will not inherit any scaling nor translation from its parent’s hierarchy and therefore remain with its default dimensions at origin (0;0;0)
.