Update for changes in Blender's API
[blender-addons.git] / rigify / rot_mode.py
blob22def2246fb6742d735b01e957b78037ce54bdc8
1 '''
2 Quat/Euler Rotation Mode Converter v0.1
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
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 - ...
19 GitHub: https://github.com/MarioMey/rotation_mode_addon/
20 BlenderArtist thread: http://blenderartists.org/forum/showthread.php?388197-Quat-Euler-Rotation-Mode-Converter
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
26 '''
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"}
40 import bpy
42 order_list = ['QUATERNION', 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']
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
51 fc = action.fcurves.new(data_path, array_index)
52 fc.group = group
53 return fc
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()
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()
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
79 # Converts only one group/bone in one action - Quat to euler
80 def group_qe(self, obj, action, bone, bone_prefix, order):
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]
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)
94 self.add_keyframe_euler(action, euler, fr, bone_prefix, group)
95 bone.rotation_mode = order
97 # Converts only one group/bone in one action - Euler to Quat
98 def group_eq(self, obj, action, bone, bone_prefix, order):
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]
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()
112 self.add_keyframe_quat(action, quat, fr, bone_prefix, group)
113 bone.rotation_mode = order
115 # One Action - One Bone
116 def one_act_one_bon(self, obj, action, bone, order):
117 do = False
118 bone_prefix = ''
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'
128 order_euler = cond1 or cond2 or cond3 or cond4 or cond5 or cond6
129 order_quat = order == 'QUATERNION'
131 for fcurve in action.fcurves:
132 if fcurve.group.name == bone.name:
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
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
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)
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)
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)
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)
168 # Changes rotation mode to new one
169 bone.rotation_mode = order
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)
176 # One action, all Bones (in Action)
177 def one_act_every_bon(self, obj, action, order):
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')
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')
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)
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)
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)
219 convert = convert()
222 # def initSceneProperties(scn):
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")
235 # scn['order_list'] = 0
237 # return
239 # initSceneProperties(bpy.context.scene)
242 # GUI (Panel)
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'
251 # draw the gui
252 def draw(self, context):
253 layout = self.layout
254 scn = context.scene
255 # ~ toolsettings = context.tool_settings
257 col = layout.column(align=True)
258 row = col.row(align=True)
259 id_store = context.window_manager
261 layout.prop(scn, 'order_list')
263 if id_store.rigify_convert_only_selected:
264 icon = 'OUTLINER_DATA_ARMATURE'
265 else:
266 icon = 'ARMATURE_DATA'
268 layout.prop(id_store, 'rigify_convert_only_selected', toggle=True, icon=icon)
270 col = layout.column(align=True)
271 row = col.row(align=True)
273 row.operator('rigify_quat2eu.current', icon='ACTION')
274 row = col.row(align=True)
275 row.operator('rigify_quat2eu.all', icon='NLA')
278 class CONVERT_OT_quat2eu_current_action(bpy.types.Operator):
279 bl_label = 'Convert Current Action'
280 bl_idname = 'rigify_quat2eu.current'
281 bl_description = 'Converts bones in current Action'
282 bl_options = {'REGISTER', 'UNDO'}
284 # on mouse up:
285 def invoke(self, context, event):
286 self.execute(context)
287 return {'FINISHED'}
289 def execute(op, context):
290 obj = bpy.context.active_object
291 pose_bones = bpy.context.selected_pose_bones
292 action = obj.animation_data.action
293 order = order_list[bpy.context.scene['order_list']]
294 id_store = context.window_manager
296 if id_store.rigify_convert_only_selected:
297 convert.one_act_sel_bon(obj, action, pose_bones, order)
298 else:
299 convert.one_act_every_bon(obj, action, order)
301 return {'FINISHED'}
304 class CONVERT_OT_quat2eu_all_actions(bpy.types.Operator):
305 bl_label = 'Convert All Actions'
306 bl_idname = 'rigify_quat2eu.all'
307 bl_description = 'Converts bones in every Action'
308 bl_options = {'REGISTER', 'UNDO'}
310 # on mouse up:
311 def invoke(self, context, event):
312 self.execute(context)
313 return {'FINISHED'}
315 def execute(op, context):
316 obj = bpy.context.active_object
317 pose_bones = bpy.context.selected_pose_bones
318 order = order_list[bpy.context.scene['order_list']]
319 id_store = context.window_manager
321 if id_store.rigify_convert_only_selected:
322 convert.all_act_sel_bon(obj, pose_bones, order)
323 else:
324 convert.all_act_every_bon(obj, order)
326 return {'FINISHED'}
329 def register():
330 IDStore = bpy.types.WindowManager
332 items = [('QUATERNION', 'QUATERNION', 'QUATERNION'),
333 ('XYZ', 'XYZ', 'XYZ'),
334 ('XZY', 'XZY', 'XZY'),
335 ('YXZ', 'YXZ', 'YXZ'),
336 ('YZX', 'YZX', 'YZX'),
337 ('ZXY', 'ZXY', 'ZXY'),
338 ('ZYX', 'ZYX', 'ZYX')]
340 bpy.types.Scene.order_list = bpy.props.EnumProperty(items=items, name='Convert to',
341 description="The target rotation mode", default='QUATERNION')
343 IDStore.rigify_convert_only_selected = bpy.props.BoolProperty(
344 name="Convert Only Selected", description="Convert selected bones only", default=True)
346 bpy.utils.register_class(ToolsPanel)
347 bpy.utils.register_class(CONVERT_OT_quat2eu_current_action)
348 bpy.utils.register_class(CONVERT_OT_quat2eu_all_actions)
350 def unregister():
351 IDStore = bpy.types.WindowManager
353 bpy.utils.unregister_class(ToolsPanel)
354 bpy.utils.unregister_class(CONVERT_OT_quat2eu_current_action)
355 bpy.utils.unregister_class(CONVERT_OT_quat2eu_all_actions)
357 del IDStore.rigify_convert_only_selected
359 # bpy.utils.register_module(__name__)