| __author__ = 'thoth'
#
# demonstrate the "instability" of matrix decomposition into quaternions.
#
"""
http://blender.stackexchange.com/questions/28438/how-do-i-ensure-a-sequence-of-quaternions-from-matrix-decompose-is-continuous
It seems that blender's Matrix decompose() function can exhibit some numerical "instability".  What I mean is that orientation matrices that are relatively close can have significantly different elements in their quaternion matrix that makes interpolated keyframing unusable.
The following simple python script will calculate a sequence of matrices representing rotations around the Z axis in a smooth manner.  It then decomposes the matrix into a quaternion, and inserts that as a keyframe.  When you view the resulting fcurves in blender you can see that the Z value jumps from -1 to 1 about halfway through the animation.
Is there a technique for blender's python API to get a sequence of quaternions that can be keyframed and interpolated without discontinuity?  For bonus points: link to an article that explains this mathematical oddity and why decomposition works this way.
This technique should be usable with arbitrary orientation matrices, because they are calculated from something a little more complex than this simple Z rotation (in my specific case I'm flying along a bezier curve).
"""
import bpy
from math import *
from mathutils import *
def matrix_for_time(t):
    theta = 2 * pi * t
    mat = Matrix([[cos(theta), sin(theta), 0],
                  [-sin(theta), cos(theta), 0],
                  [0, 0, 1]]).to_4x4()
    # for illustration we use rotation about Z axis,
    # but in the arbitrary case, the orientation could be for a pine cone bouncing down a hill.
    return mat
def mission(obj):
    res = 36
    qs = QuaternionStabilizer()
    for z in range(res):
        mat = matrix_for_time(z/res)
        (loc,quat,scale) = mat.decompose()
        obj.rotation_quaternion = qs.stabilize(quat)
        for ai in range(len(quat)):
            obj.keyframe_insert(frame=z*5, data_path="rotation_quaternion", index=ai)
class QuaternionStabilizer:
    def __init__(self):
        self.old=None
    def stabilize(self, q):
        if self.old is None:
            rval = q
        else:
            # compute the distance between old and q
            d1 = (self.old-q).magnitude
            # compute the distance between old and -q
            d2 = (self.old+q).magnitude
            if (d1<d2):
                # q is closer to old
                rval = q
            else:
                # -q is closer to old
                rval = -q
        self.old = rval
        return rval
mission(bpy.context.active_object)
 | 
Blender python API quick-start
Syntax highlighting by Pygments.