Euler angles from quaternion do not match Transform UI input (Isaac Sim 4.5.0)

Hi, I’m using Omniverse Isaac Sim 4.5.0, and I’m trying to retrieve the Euler angles of an object through Python API. Here’s what I’m facing:

  1. In the Transform UI, I manually input Euler angles like (200°, 300°, 300°).
  2. However, when I query the object’s rotation using xformOp:orient, I get a quaternion.
  3. I tried converting this quaternion back to Euler angles using:
  • quat_to_euler_angles() from the official API,
  • scipy.spatial.transform.Rotation.from_quat(),
  • Custom math function (XYZ or ZYX decomposition).
  1. But all approaches give me Euler angles like (-160°, -60°, -60°) instead of my original input (200°, 300°, 300°).

🧩 My goal:

I want to retrieve the Euler angles that match exactly what I input into the Transform UI, so I can do accurate comparisons or validation in scripts. But due to angle wrapping or internal conversion behavior, it’s hard to determine if the actual rotation is the same as what I set.

🧠 What I understand:

  • The Transform UI accepts degrees and converts them internally to quaternions (xformOp:orient).
  • Euler angles are not stored directly.
  • Due to the non-uniqueness of Euler angles (especially near gimbal lock), the same rotation can produce multiple Euler representations.

Questions:

  • Is there a way to retrieve the Euler angles as they were entered into the UI (before conversion)?
  • Or is there a canonicalized way in Omniverse to normalize or compare Euler input and quaternion-converted Euler output?
  • Is the UI internally using a specific rotation order (e.g., XYZ or ZYX)? Can I enforce that when using quat_to_euler_angles()?

@minho.lee2 do you think this thread is relevant?

Get Euler angles rotation of a prim - Omniverse / Isaac Sim - NVIDIA Developer Forums

Thank you for the response!

To clarify the rotation order:
Yes, I am using ZYX rotation order when converting from Euler degrees to quaternion. Here’s the function I use in the script:

def euler_xyz_to_quatd(euler_deg):
    x_rad = math.radians(euler_deg[0])
    y_rad = math.radians(euler_deg[1])
    z_rad = math.radians(euler_deg[2])

    qx = Gf.Quatd(math.cos(x_rad / 2), Gf.Vec3d(1, 0, 0) * math.sin(x_rad / 2))
    qy = Gf.Quatd(math.cos(y_rad / 2), Gf.Vec3d(0, 1, 0) * math.sin(y_rad / 2))
    qz = Gf.Quatd(math.cos(z_rad / 2), Gf.Vec3d(0, 0, 1) * math.sin(z_rad / 2))

    return qx * qy * qz  # Applied in XYZ order: x → y → z

After that, to get the Euler angles back from the quaternion stored in xformOp:orient, I use:

prim_ori_path = prim.GetAttribute("xformOp:orient")   
quat_x = prim_ori_path.Get()
x, y, z = quat_x.GetImaginary()
w = quat_x.GetReal()
        
quat_np = np.array([w, x, y, z], dtype=np.float64)    
euler_from_quat_deg = quat_to_euler_angles(quat_np, degrees=True, extrinsic=False)

What works:

In many simple cases, the conversion back gives me the original values (e.g., (10°, 20°, 30°) → back to (10°, 20°, 30°)).


But here’s the issue:

When I input something like (200°, 300°, 300°) into the Transform UI,

  • the UI automatically rewrites it as (-160°, -60°, -60°),
  • and the quaternion → Euler conversion matches (-160°, -60°, -60°), not what I originally typed.

This seems to be related to:

  • angle wrapping (values outside ±180°)
  • non-uniqueness of Euler representations
  • gimbal lock (especially near ±90°)

My question:

Is there an official way to verify that a quaternion corresponds to an originally intended Euler input, such as (200, 300, 300), even when the recovered Euler angles differ numerically?

In other words:

  • Can I confirm that (200°, 300°, 300°) and (-160°, -60°, -60°) are the same rotation, even though they look different?
  • Or is there a standard normalization method or comparison function in Omniverse to check rotation equivalence (like comparing matrices or dot products of quaternions)?

Any guidance or best practices for validation would be greatly appreciated.