[QUOTE=Theodox;27078]Alas, the quaternion to euler conversion is not deterministic: each quaternion produces an infinite number of valid euler rotations and vice-versa. A given eulerization is based on convention and on rotation order; it sounds like you’ve got the rotation order but Maya controls the conventions. The sad truth is that there are many equally valid ways of reporting the same orientation in both systems, so you can’t get a reliable mapping (“Euler Filter”, anyone?)
You might find it easier to just use API quaternions and convert them to Eulers that way. The new api makes that much less painful:
import maya.api.OpenMaya as api
import math
q = api.MQuaternion(.707, 0, 0 , .707)
e = q.asEulerRotation()
print map(math.degree, e)
[-180, -89.999999, 0.0]
The only wrinkle is that the MEulerRotation value will be in radians, so you need to convert it to degrees as above.[/QUOTE]
Yes I’m aware of the issues with conversions. I was considering maybe using the python in max/blender in future so less reliance on the api for each app might be a good thing? I tried your example code, it errored, I changed math.degree to math.degrees, and that worked but my result was [90.0, -0.0, 0.0], could be due to me having Z up axis instead of Y?
After 2 days looking into the conversion math, I sourced/ported several implementations and mix’n’matched what I could make sense of. The following code works for my use case perfectly:
import math
from collections import namedtuple
Quaternion = namedtuple('Quaternion', 'w x y z')
Euler = namedtuple('Euler', 'x y z')
def crop_rotation(angle):
if angle > 180:
return angle-360
elif angle < -180:
return angle+360
else:
return angle
def convert_to_radians(degrees):
return (degrees/180) * math.pi
def convert_to_degrees(radians):
return (radians*180) / math.pi
def quaternion_to_euler(q):
sqw = q.w * q.w
sqx = q.x * q.x
sqy = q.y * q.y
sqz = q.z * q.z
normal = math.sqrt(sqw + sqx + sqy + sqz)
pole_result = (q.x * q.z) + (q.y * q.w)
if (pole_result > (0.5 * normal)): # singularity at north pole
ry = math.pi/2 #heading/yaw?
rz = 0 #attitude/roll?
rx = 2 * math.atan2(q.x, q.w) #bank/pitch?
return Euler(rx, ry, rz)
if (pole_result < (-0.5 * normal)): # singularity at south pole
ry = -math.pi/2
rz = 0
rx = -2 * math.atan2(q.x, q.w)
return Euler(rx, ry, rz)
r11 = 2*(q.x*q.y + q.w*q.z)
r12 = sqw + sqx - sqy - sqz
r21 = -2*(q.x*q.z - q.w*q.y)
r31 = 2*(q.y*q.z + q.w*q.x)
r32 = sqw - sqx - sqy + sqz
rx = math.atan2( r31, r32 )
ry = math.asin ( r21 )
rz = math.atan2( r11, r12 )
return Euler(rx, ry, rz)
def euler_to_quaternion(angle_x, angle_y, angle_z):
heading = convert_to_radians(angle_y)
attitude = convert_to_radians(angle_z)
bank = convert_to_radians(angle_x)
c1 = math.cos(heading/2)
c2 = math.cos(attitude/2)
c3 = math.cos(bank/2)
s1 = math.sin(heading/2)
s2 = math.sin(attitude/2)
s3 = math.sin(bank/2)
w = (c1 * c2 * c3) - (s1 * s2 * s3)
x = (s1 * s2 * c3) + (c1 * c2 * s3)
y = (s1 * c2 * c3) + (c1 * s2 * s3)
z = (c1 * s2 * c3) - (s1 * c2 * s3)
return Quaternion(w, x, y, z)
def test_q2e(q):
rotations = quaternion_to_euler(q)
rx = crop_rotation( convert_to_degrees( rotations.x ) *-1 )
ry = crop_rotation( convert_to_degrees( rotations.y ) )
rz = crop_rotation( convert_to_degrees( rotations.z ) )
e = Euler(rx, ry, rz)
print e
return e
def test_e2q(e):
angleX = crop_rotation(e.x*-1)
angleY = crop_rotation(e.y*-1)
angleZ = crop_rotation(e.z)
q = euler_to_quaternion(angleX, angleY, angleZ)
print q
return q
test1 = Quaternion(-0.5, -0.5, 0.5, 0.5)
test2 = Quaternion(-0.707107, 0, -0.707107, 0)
test3 = Quaternion(-3.09086e-08, 0.707107, 3.09086e-08, 0.707107)
print str(test1)+" #Original Quaternion
"+str(test1 == test_e2q( test_q2e( test1 ) ))+"
"
print str(test2)+" #Original Quaternion
"+str(test2 == test_e2q( test_q2e( test2 ) ))+"
"
print str(test3)+" #Original Quaternion
"+str(test3 == test_e2q( test_q2e( test3 ) ))+"
"
The conversion from euler to quaternion is slightly imprecise, at least in my case I don’t think that will cause any major issues. I don’t think you can really round it based on the differences from the 3 tests.