My guess is that you forgot to also transform the mesh points from a Z up scene to a Y up stage.
What you are doing is a change of base, and you’ll need a “sandwich product” (formulas seem inverted in those two links, but that is because the first one is row-major, while the second is column-major) to achieve it.
Given C
the change-of-base transform (in your case, “the transform that make a Z-Up scene becomes a Y-Up scene”) and Cinv
the inverse of this transform, for a given transform M
, the M’
in the new base is
M' = Cinv * M * C
So given the global position Pg
of a vertex of your mesh, it’s position Pg’
in Y Up is
Pg' = Cinv * Pg * C
But that’s not the whole story: the vertex global position is made of two transforms:
- its local position
P
defined in the pointf3f[] points
attribute of your mesh
- the local transform
T
of its prim
Pg = M * P
So, going back to the change of base:
Pg' = Cinv * Pg * C
Pg' = Cinv * M * P * C
We now need to extract from this equation two transforms:
- one for the Prim transform in Y up
- another one for point transform in Y up
Let’s insert an identity matrix I
in the equation (a matrix that don’t have an effect, like multiplying a number by 1 or adding 0 to a sum).
Pg' = Cinv * M * I * P * C
A property of an indentity matrix is that if you multiply a matrix by its inverse, you get an identity:
I = M * Minv = Minv * M
With this property in mind, let’s decompose the I
matrix:
Pg' = Cinv * M * (C * Cinv) * P * C
Or, if we drop the parenthesis:
Pg' = Cinv * M * C * Cinv * P * C
Pg' = M' * P'
M’
being the prim transform in Y-Up and P’
the point transform in Y up. In another term: "you need to change the base of all intermediate transforms, being prim transforms or point locations”.
Here a sample below that transform an Z Up USD layer into a Y Up USD layer:
Z-Up stage-z-up.usda
:
#usda 1.0
(
defaultPrim = "Tri"
metersPerUnit = 1
upAxis = "Z"
)
def Mesh "Tri"
{
# Rotation +90 degrees around X
# Translation 1 unit toward Y
matrix4d xformOp:transform = (
(1, 0, 0, 0),
(0, 0, 1, 0),
(0, -1, 0, 0),
(0, 1, 0, 1)
)
uniform token[] xformOpOrder = ["xformOp:transform"]
int[] faceVertexCounts = [3]
int[] faceVertexIndices = [0, 1, 2]
point3f[] points = [(0, 0, 0), (0, 1, 0), (1, 0, 0)]
}
Y-Up stage-y-up.usda
:
#usda 1.0
(
metersPerUnit = 1
subLayers = [
@stage-z-up.usda@
]
upAxis = "Y"
)
over "Tri"
{
matrix4d xformOp:transform = (
(1, 0, 0, 0),
(0, 0, 1, 0),
(0, -1, 0, 0),
(0, 0, -1, 1)
)
uniform token[] xformOpOrder = ["xformOp:transform"]
point3f[] points = [(0, 0, 0), (0, 0, -1), (1, 0, 0)]
}
See the differences ?
- the Translation part of the Prim transform (the last row) was “a translation 1 unit toward Y (0, 1, 0) in Z-Up” is now “a translation of -1 unit along Z in Y-Up“
- the points coordinates have also changed
Here is the script I used to do the change of base:
from pxr import Gf, Usd, UsdGeom
# We create a new scene that sublayers the original,
# Z up layer. All edits will be overrides in our Y up scene
stage = Usd.Stage.CreateNew("stage-y-up.usda")
stage.GetRootLayer().subLayerPaths = ["stage-z-up.usda"]
# We set the new Y up layer axis metadata
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
UsdGeom.SetStageMetersPerUnit(stage, 1)
prim = stage.GetPrimAtPath("/Tri")
# This is the matrix used for the change of Z up to Y up
# A point "up" in Z (0, 0, 1) becomes a point "up" in Y (0, 1, 0)
# In another term, we need to apply a clockwise 90 degrees
# rotation around the X axis
transform_z_up_to_y_up = Gf.Matrix4d()
transform_z_up_to_y_up.SetRotate(Gf.Rotation([1, 0, 0], -90))
# First, we transform the UsdGeom.Xformable prim itself
xformable = UsdGeom.Xformable(prim)
op = xformable.MakeMatrixXform()
prim_transform_z_up = op.Get()
prim_transform_y_up = transform_z_up_to_y_up.GetInverse() * prim_transform_z_up * transform_z_up_to_y_up
op.Set(prim_transform_y_up)
# Then, we transform each point coordinates of the mesh
points_attr = UsdGeom.Mesh(prim).GetPointsAttr()
origin = Gf.Vec3f()
points_y_up = []
for point_z_up in points_attr.Get():
point_z_up_transform = Gf.Matrix4d()
point_z_up_transform.SetTranslate(Gf.Vec3d(point_z_up))
point_y_up_transform = transform_z_up_to_y_up.GetInverse() * point_z_up_transform * transform_z_up_to_y_up
point_y_up = point_y_up_transform.Transform(origin)
points_y_up.append(Gf.Vec3f(point_y_up))
points_attr.Set(points_y_up)
# And we save the scene
stage.Save()
What if you also wanted to change from meters to centimeters ? The code would stay very much the same, you’d only scale transform_z_up_to_y_up
by 100 (“1 meter becomes 100 centimeters”) and UsdGeom.SetStageMetersPerUnit(stage, 0.01)
.
As a note: point positions is not the only attribute you’ll need to take care of, you’ll certainly want to rotate the mesh normals as well.
That should be all that you want to become an ace of base.