Skip to content

Commit b787721

Browse files
committed
added: quat2euler feature set
1 parent 9ef7dc1 commit b787721

File tree

2 files changed

+397
-3
lines changed

2 files changed

+397
-3
lines changed

rot_mode.py

Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
'''
2+
Quat/Euler Rotation Mode Converter v0.1
3+
4+
This script/addon:
5+
- Changes (pose) bone rotation mode
6+
- Converts keyframes from one rotation mode to another
7+
- Creates fcurves/keyframes in target rotation mode
8+
- Deletes previous fcurves/keyframes.
9+
- Converts multiple bones
10+
- Converts multiple Actions
11+
12+
TO-DO:
13+
- To convert object's rotation mode (alrady done in Mutant Bob script,
14+
but not done in this one.
15+
- To understand "EnumProperty" and write it well.
16+
- Code clean
17+
- ...
18+
19+
GitHub: https://github.com/MarioMey/rotation_mode_addon/
20+
BlenderArtist thread: http://blenderartists.org/forum/showthread.php?388197-Quat-Euler-Rotation-Mode-Converter
21+
22+
Mutant Bob did the "hard code" of this script. Thanks him!
23+
blender.stackexchange.com/questions/40711/how-to-convert-quaternions-keyframes-to-euler-ones-in-several-actions
24+
25+
26+
'''
27+
28+
# bl_info = {
29+
# "name": "Quat/Euler Rotation Mode Converter",
30+
# "author": "Mario Mey / Mutant Bob",
31+
# "version": (0, 1),
32+
# "blender": (2, 76, 0),
33+
# 'location': '',
34+
# "description": "Converts bones rotation mode",
35+
# "warning": "",
36+
# "wiki_url": "",
37+
# "tracker_url": "https://github.com/MarioMey/rotation_mode_addon/",
38+
# "category": "Animation"}
39+
40+
import bpy
41+
42+
order_list = ['QUATERNION', 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']
43+
44+
45+
class convert():
46+
def get_or_create_fcurve(self, action, data_path, array_index=-1, group=None):
47+
for fc in action.fcurves:
48+
if fc.data_path == data_path and (array_index < 0 or fc.array_index == array_index):
49+
return fc
50+
51+
fc = action.fcurves.new(data_path, array_index)
52+
fc.group = group
53+
return fc
54+
55+
def add_keyframe_quat(self, action, quat, frame, bone_prefix, group):
56+
for i in range(len(quat)):
57+
fc = self.get_or_create_fcurve(action, bone_prefix + "rotation_quaternion", i, group)
58+
pos = len(fc.keyframe_points)
59+
fc.keyframe_points.add(1)
60+
fc.keyframe_points[pos].co = [frame, quat[i]]
61+
fc.update()
62+
63+
def add_keyframe_euler(self, action, euler, frame, bone_prefix, group):
64+
for i in range(len(euler)):
65+
fc = self.get_or_create_fcurve(action, bone_prefix + "rotation_euler", i, group)
66+
pos = len(fc.keyframe_points)
67+
fc.keyframe_points.add(1)
68+
fc.keyframe_points[pos].co = [frame, euler[i]]
69+
fc.update()
70+
71+
def frames_matching(self, action, data_path):
72+
frames = set()
73+
for fc in action.fcurves:
74+
if fc.data_path == data_path:
75+
fri = [kp.co[0] for kp in fc.keyframe_points]
76+
frames.update(fri)
77+
return frames
78+
79+
# Converts only one group/bone in one action - Quat to euler
80+
def group_qe(self, obj, action, bone, bone_prefix, order):
81+
82+
pose_bone = bone
83+
data_path = bone_prefix + "rotation_quaternion"
84+
frames = self.frames_matching(action, data_path)
85+
group = action.groups[bone.name]
86+
87+
for fr in frames:
88+
quat = bone.rotation_quaternion.copy()
89+
for fc in action.fcurves:
90+
if fc.data_path == data_path:
91+
quat[fc.array_index] = fc.evaluate(fr)
92+
euler = quat.to_euler(order)
93+
94+
self.add_keyframe_euler(action, euler, fr, bone_prefix, group)
95+
bone.rotation_mode = order
96+
97+
# Converts only one group/bone in one action - Euler to Quat
98+
def group_eq(self, obj, action, bone, bone_prefix, order):
99+
100+
pose_bone = bone
101+
data_path = bone_prefix + "rotation_euler"
102+
frames = self.frames_matching(action, data_path)
103+
group = action.groups[bone.name]
104+
105+
for fr in frames:
106+
euler = bone.rotation_euler.copy()
107+
for fc in action.fcurves:
108+
if fc.data_path == data_path:
109+
euler[fc.array_index] = fc.evaluate(fr)
110+
quat = euler.to_quaternion()
111+
112+
self.add_keyframe_quat(action, quat, fr, bone_prefix, group)
113+
bone.rotation_mode = order
114+
115+
# One Action - One Bone
116+
def one_act_one_bon(self, obj, action, bone, order):
117+
do = False
118+
bone_prefix = ''
119+
120+
# What kind of conversion
121+
cond1 = order == 'XYZ'
122+
cond2 = order == 'XZY'
123+
cond3 = order == 'YZX'
124+
cond4 = order == 'YXZ'
125+
cond5 = order == 'ZXY'
126+
cond6 = order == 'ZYX'
127+
128+
order_euler = cond1 or cond2 or cond3 or cond4 or cond5 or cond6
129+
order_quat = order == 'QUATERNION'
130+
131+
for fcurve in action.fcurves:
132+
if fcurve.group.name == bone.name:
133+
134+
# If To-Euler conversion
135+
if order != 'QUATERNION':
136+
if fcurve.data_path.endswith('rotation_quaternion'):
137+
do = True
138+
bone_prefix = fcurve.data_path[:-len('rotation_quaternion')]
139+
break
140+
141+
# If To-Quat conversion
142+
else:
143+
if fcurve.data_path.endswith('rotation_euler'):
144+
do = True
145+
bone_prefix = fcurve.data_path[:-len('rotation_euler')]
146+
break
147+
148+
# If To-Euler conversion
149+
if do and order != 'QUATERNION':
150+
# Converts the group/bone from Quat to Euler
151+
self.group_qe(obj, action, bone, bone_prefix, order)
152+
153+
# Removes quaternion fcurves
154+
for key in action.fcurves:
155+
if key.data_path == 'pose.bones["' + bone.name + '"].rotation_quaternion':
156+
action.fcurves.remove(key)
157+
158+
# If To-Quat conversion
159+
elif do:
160+
# Converts the group/bone from Euler to Quat
161+
self.group_eq(obj, action, bone, bone_prefix, order)
162+
163+
# Removes euler fcurves
164+
for key in action.fcurves:
165+
if key.data_path == 'pose.bones["' + bone.name + '"].rotation_euler':
166+
action.fcurves.remove(key)
167+
168+
# Changes rotation mode to new one
169+
bone.rotation_mode = order
170+
171+
# One Action, selected bones
172+
def one_act_sel_bon(self, obj, action, pose_bones, order):
173+
for bone in pose_bones:
174+
self.one_act_one_bon(obj, action, bone, order)
175+
176+
# One action, all Bones (in Action)
177+
def one_act_every_bon(self, obj, action, order):
178+
179+
# Collects pose_bones that are in the action
180+
pose_bones = set()
181+
# Checks all fcurves
182+
for fcurve in action.fcurves:
183+
# Look for the ones that has rotation_euler
184+
if order == 'QUATERNION':
185+
if fcurve.data_path.endswith('rotation_euler'):
186+
# If the bone from action really exists
187+
if fcurve.group.name in obj.pose.bones:
188+
if obj.pose.bones[fcurve.group.name] not in pose_bones:
189+
pose_bones.add(obj.pose.bones[fcurve.group.name])
190+
else:
191+
print(fcurve.group.name, 'does not exist in Armature. Fcurve-group is not affected')
192+
193+
# Look for the ones that has rotation_quaternion
194+
else:
195+
if fcurve.data_path.endswith('rotation_quaternion'):
196+
# If the bone from action really exists
197+
if fcurve.group.name in obj.pose.bones:
198+
if obj.pose.bones[fcurve.group.name] not in pose_bones:
199+
pose_bones.add(obj.pose.bones[fcurve.group.name])
200+
else:
201+
print(fcurve.group.name, 'does not exist in Armature. Fcurve-group is not affected')
202+
203+
# Convert current action and pose_bones that are in each action
204+
for bone in pose_bones:
205+
self.one_act_one_bon(obj, action, bone, order)
206+
207+
# All Actions, selected bones
208+
def all_act_sel_bon(self, obj, pose_bones, order):
209+
for action in bpy.data.actions:
210+
for bone in pose_bones:
211+
self.one_act_one_bon(obj, action, bone, order)
212+
213+
# All actions, All Bones (in each Action)
214+
def all_act_every_bon(self, obj, order):
215+
for action in bpy.data.actions:
216+
self.one_act_every_bon(obj, action, order)
217+
218+
219+
convert = convert()
220+
221+
222+
# def initSceneProperties(scn):
223+
#
224+
# bpy.types.Scene.order_list = bpy.props.EnumProperty(
225+
# items = [('QUATERNION', 'QUATERNION', 'QUATERNION' ),
226+
# ('XYZ', 'XYZ', 'XYZ' ),
227+
# ('XZY', 'XZY', 'XZY' ),
228+
# ('YXZ', 'YXZ', 'YXZ' ),
229+
# ('YZX', 'YZX', 'YZX' ),
230+
# ('ZXY', 'ZXY', 'ZXY' ),
231+
# ('ZYX', 'ZYX', 'ZYX' ) ],
232+
# name = "Order",
233+
# description = "The target rotation mode")
234+
#
235+
# scn['order_list'] = 0
236+
#
237+
# return
238+
#
239+
# initSceneProperties(bpy.context.scene)
240+
241+
242+
# GUI (Panel)
243+
#
244+
class ToolsPanel(bpy.types.Panel):
245+
bl_space_type = 'VIEW_3D'
246+
bl_region_type = 'TOOLS'
247+
bl_category = "Tools"
248+
bl_context = "posemode"
249+
bl_label = 'Rigify Quat/Euler Converter'
250+
251+
# draw the gui
252+
def draw(self, context):
253+
layout = self.layout
254+
scn = context.scene
255+
# ~ toolsettings = context.tool_settings
256+
257+
col = layout.column(align=True)
258+
row = col.row(align=True)
259+
260+
layout.prop(scn, 'order_list')
261+
262+
col = layout.column(align=True)
263+
row = col.row(align=True)
264+
265+
col.label(text="Current Action:")
266+
col.operator('current.selected')
267+
col.operator('current.every')
268+
269+
row = col.row(align=True)
270+
col = layout.column(align=True)
271+
272+
col.label(text="All Actions:")
273+
col.operator('all.selected')
274+
col.operator('all.every')
275+
276+
277+
class CONVERT_OT_current_action_selected_bones(bpy.types.Operator):
278+
bl_label = 'Selected Bones'
279+
bl_idname = 'current.selected'
280+
bl_description = 'Converts selected bones in current Action'
281+
bl_options = {'REGISTER', 'UNDO'}
282+
283+
# on mouse up:
284+
def invoke(self, context, event):
285+
self.execute(context)
286+
return {'FINISHED'}
287+
288+
def execute(op, context):
289+
obj = bpy.context.active_object
290+
pose_bones = bpy.context.selected_pose_bones
291+
action = obj.animation_data.action
292+
order = order_list[bpy.context.scene['order_list']]
293+
294+
convert.one_act_sel_bon(obj, action, pose_bones, order)
295+
296+
return {'FINISHED'}
297+
298+
299+
class CONVERT_OT_current_action_every_bones(bpy.types.Operator):
300+
bl_label = 'All Bones'
301+
bl_idname = 'current.every'
302+
bl_description = 'Converts every bone in current Action'
303+
bl_options = {'REGISTER', 'UNDO'}
304+
305+
# on mouse up:
306+
def invoke(self, context, event):
307+
self.execute(context)
308+
return {'FINISHED'}
309+
310+
def execute(op, context):
311+
obj = bpy.context.active_object
312+
pose_bones = bpy.context.selected_pose_bones
313+
action = obj.animation_data.action
314+
order = order_list[bpy.context.scene['order_list']]
315+
316+
convert.one_act_every_bon(obj, action, order)
317+
318+
return {'FINISHED'}
319+
320+
321+
class CONVERT_OT_all_actions_selected_bones(bpy.types.Operator):
322+
bl_label = 'Selected Bone'
323+
bl_idname = 'all.selected'
324+
bl_description = 'Converts selected bones in every Action'
325+
bl_options = {'REGISTER', 'UNDO'}
326+
327+
# on mouse up:
328+
def invoke(self, context, event):
329+
self.execute(context)
330+
return {'FINISHED'}
331+
332+
def execute(op, context):
333+
obj = bpy.context.active_object
334+
pose_bones = bpy.context.selected_pose_bones
335+
order = order_list[bpy.context.scene['order_list']]
336+
337+
convert.all_act_sel_bon(obj, pose_bones, order)
338+
339+
return {'FINISHED'}
340+
341+
342+
class CONVERT_OT_all_action_every_bones(bpy.types.Operator):
343+
bl_label = 'All Bone'
344+
bl_idname = 'all.every'
345+
bl_description = 'Converts every bone in every Action'
346+
bl_options = {'REGISTER', 'UNDO'}
347+
348+
# on mouse up:
349+
def invoke(self, context, event):
350+
self.execute(context)
351+
return {'FINISHED'}
352+
353+
def execute(op, context):
354+
obj = bpy.context.active_object
355+
order = order_list[bpy.context.scene['order_list']]
356+
357+
convert.all_act_every_bon(obj, order)
358+
return {'FINISHED'}
359+
360+
361+
def register():
362+
items = [('QUATERNION', 'QUATERNION', 'QUATERNION'),
363+
('XYZ', 'XYZ', 'XYZ'),
364+
('XZY', 'XZY', 'XZY'),
365+
('YXZ', 'YXZ', 'YXZ'),
366+
('YZX', 'YZX', 'YZX'),
367+
('ZXY', 'ZXY', 'ZXY'),
368+
('ZYX', 'ZYX', 'ZYX')]
369+
370+
bpy.types.Scene.order_list = bpy.props.EnumProperty(items=items, name='Order',
371+
description="The target rotation mode", default='QUATERNION')
372+
373+
bpy.utils.register_class(ToolsPanel)
374+
bpy.utils.register_class(CONVERT_OT_all_action_every_bones)
375+
bpy.utils.register_class(CONVERT_OT_all_actions_selected_bones)
376+
bpy.utils.register_class(CONVERT_OT_current_action_every_bones)
377+
bpy.utils.register_class(CONVERT_OT_current_action_selected_bones)
378+
379+
380+
def unregister():
381+
bpy.utils.unregister_class(ToolsPanel)
382+
bpy.utils.unregister_class(CONVERT_OT_all_action_every_bones)
383+
bpy.utils.unregister_class(CONVERT_OT_all_actions_selected_bones)
384+
bpy.utils.unregister_class(CONVERT_OT_current_action_every_bones)
385+
bpy.utils.unregister_class(CONVERT_OT_current_action_selected_bones)
386+
387+
# bpy.utils.register_module(__name__)

0 commit comments

Comments
 (0)