15
$\begingroup$

I created a character for a game. It has IK legs. I animated its foot and didn't realize that they were Quaterinon. I made lot of actions with it.

Now... as I will make more actions with this character, I would like to convert every Action with its rotation data to Euler. So, after changing its rotation mode, the bone would have the same visual rotation as it had with Quaternions.

There are some ways to do it by hand. In my opinion, the easiest is:

  • Select one Action.
  • Select the bone I want to convert.
  • Enter in Edit mode.
  • Duplicate it.
  • Go to pose mode.
  • Insert a keyframe LocRotScale (now, it appear as a channel in the Action).
  • Copy every frame from the first bone and paste them in the duplicated one (select keyframes, Ctrl-C, select channel, Ctrl-V)
  • Change Rotation mode to XYZ-Euler to the duplicated bone (this step has to be here. If not, it wouldn't have the correct keyframes).
  • Add a CopyRot constraint to the duplicated bone, copying the visual rotation from the first one.
  • In the first keyframe, insert VisualRotation Keyframe (selecting VisualRot in keying set).
  • Press Up key, press I key. Again. Again. Again till the last keyframe.
  • Select first bone.
  • Convert to Euler.
  • Insert a keyframe (to aquire Euler channels).
  • Copy every keyframe from the second bone and paste to the first.
  • Delete the second bone and its channel in the Action.

As you can see, it is a big deal to do this with every bone and every Action.

I think, in total, there are 3 ways to do this:

  • The one I said, by hand.
  • To make a script that make all those steps, but automatic.
  • To make a script that include advanced math operation to convert those Quaterion keyframes to Euler ones.
  • To use "Rotation mode keyframes" in every Action, to respect the correct mode. I mean, to put a Quaternion keyframe in every already done Action and put a Euler keyframe in every new action. (I didn't try this technique, I don't know if it works in BGE)

... any other?

$\endgroup$

4 Answers 4

10
$\begingroup$

I cobbled together the following python script: http://web.purplefrog.com/~thoth/blender/python-cookbook/convert-quaternion-keyframes-to-euler.html

Here is another update that supports both bone fcurves, and the fcurve groups.

import bpy

def get_or_create_fcurve(action, data_path, array_index=-1, group=None):
    for fc in action.fcurves:
        if fc.data_path == data_path and (array_index<0 or fc.array_index == array_index):
            return fc

    fc = action.fcurves.new(data_path, index=array_index)
    fc.group = group
    return fc

def add_keyframe_euler(action, euler, frame, bone_prefix, group):
    for i in range(len(euler)):
        fc = get_or_create_fcurve(action, bone_prefix+"rotation_euler", i, group)
        pos = len(fc.keyframe_points)
        fc.keyframe_points.add(1)
        fc.keyframe_points[pos].co = [frame, euler[i]]
        fc.update()


def frames_matching(action, data_path):
    frames = set()
    for fc in action.fcurves:
        if fc.data_path == data_path:
            fri = [kp.co[0] for kp in fc.keyframe_points]
            frames.update(fri)
    return frames


def fcurves_group(action, data_path):
    for fc in action.fcurves:
        if fc.data_path == data_path and fc.group is not None:
            return fc.group
    return None


def convert_quaternion_to_euler(action, obj, order):

    bone_prefixes = set()
    for fc in action.fcurves:
        if fc.data_path == "rotation_quaternion" or fc.data_path[-20:]==".rotation_quaternion":
            bone_prefixes.add(fc.data_path[:-19])


    for bone_prefix in bone_prefixes:
        if (bone_prefix == ""):
            bone = obj
        else:
            bone = eval("obj."+bone_prefix[:-1]) # I wish I knew a better way to do this

        data_path = bone_prefix + "rotation_quaternion"
        frames = frames_matching(action, data_path)
        group = fcurves_group(action, data_path)

        for fr in frames:
            quat = bone.rotation_quaternion.copy()
            for fc in action.fcurves:
                if fc.data_path == data_path:
                    quat[fc.array_index] = fc.evaluate(fr)
            euler = quat.to_euler(order)

            add_keyframe_euler(action, euler, fr, bone_prefix, group)
            bone.rotation_mode = order
#
#

scn = bpy.context.scene
obj = bpy.context.active_object
order='XYZ'
convert_quaternion_to_euler(obj.animation_data.action, obj, order)
$\endgroup$
11
  • $\begingroup$ Thanks for this script! It does the trick, but for an object, not for bones as I need. Also, it creates Euler channels outside the object group. I think by adding some code, they could be inside the channel. And this could be important if it is modified to use it for several bones. $\endgroup$
    – Mario Mey
    Commented Oct 29, 2015 at 16:07
  • $\begingroup$ aw nuts. I overlooked the bone part of the question. This will take a little more work as I create a new test bed. $\endgroup$
    – Mutant Bob
    Commented Oct 29, 2015 at 16:54
  • $\begingroup$ The scripts seems to do what it has to do... but there is only one thing that I told you in last comment: all the new Euler channels are not in its respective group. Below all the bone groups, there are all the "X Euler Rotation (body)", "Y Euler Rotation (body)", "Z Euler Rotation (body)", "X Euler Rotation (hand.l)"... and so. Only that and it's perfect! $\endgroup$
    – Mario Mey
    Commented Oct 30, 2015 at 22:51
  • 3
    $\begingroup$ For those who are interested in this script, I modified it and converted to an addon. Now, it converts selected/all Bones from one/all Actions, in Tools Panel. Here. Thanks again, Mutant Bob! $\endgroup$
    – Mario Mey
    Commented Dec 15, 2015 at 19:52
  • 1
    $\begingroup$ I know it is not proper etiquette to necro a dead discussion like this, but I really want to express my thanks for this script. I've been pulling my hair out trying to find an answer for converting quats animations to euler and this script is working for me in Blender 2.92. This in actual fact is still a problem and it is not a dead topic or discussion, even 6 years later. So, Thank you so much for this!!! $\endgroup$
    – PteJack
    Commented May 13, 2021 at 19:55
5
$\begingroup$

With Rigify you can also convert rotation keyframes from any rotation mode to an other. At least it worked for me good so far. Just enable the Addon in preferences, then go to Pose > Convert Rotation Modes

I hope it's helpful.

enter image description here

enter image description here

$\endgroup$
3
  • 3
    $\begingroup$ Just wow! JUST WOW. I realized that it is my and Mutant Bob's code! Look inside this file in Blender folder: 2.93/scripts/addons/rigify/rot_mode.py. There is my name! In other words... that's our code. I'm happy! $\endgroup$
    – Mario Mey
    Commented Oct 5, 2021 at 18:53
  • $\begingroup$ Here is my GUI: imgur.com/a/YEvdFgA and here is Rigify's: imgur.com/a/uzeQcVJ. You can add this last capture to your answer 😉. $\endgroup$
    – Mario Mey
    Commented Oct 5, 2021 at 18:57
  • 1
    $\begingroup$ Nice, then i would say you did a top-of-the-line-job there :) For me it was really helpful for converting old quarternion animation data. Thanks :) $\endgroup$
    – Atti
    Commented Nov 2, 2021 at 12:52
1
$\begingroup$

Here's what works for me (v. 2.78). While in Pose mode select all bones (A), press Ctrl+R, Set Rotation Mode context menu will pop up, select whatever rotation mode you want.

$\endgroup$
1
  • $\begingroup$ That only sets the rotation mode. It doesn't convert the f-curve and its actions. $\endgroup$
    – user27640
    Commented Jul 7, 2017 at 0:01
0
$\begingroup$

use this script for bone rotation conversion. simple and easy to read just replace the name of your action and bone

import bpy

action = bpy.data.actions["Hand.RAction"]
eulerDataPath = 'pose.bones["CATRigRArmPalm"].rotation_euler'
quatPath = 'pose.bones["CATRigRArmPalm"].rotation_quaternion'
obj = bpy.context.object
bone = obj.pose.bones["CATRigRArmPalm"]
bone.rotation_mode = 'XYZ'
for fc in action.fcurves:
    for keyframe in fc.keyframe_points:
        if fc.data_path == eulerDataPath:
            ftime = int(keyframe.co.x)
            bpy.context.scene.frame_set(ftime)
            bone.rotation_mode = 'XYZ'
            obj.keyframe_insert(data_path=eulerDataPath,frame = ftime)
            bone.rotation_mode = 'QUATERNION'
            continue

bone.rotation_mode = 'XYZ'
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .