2 Quat/Euler Rotation Mode Converter v0.1
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
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.
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
29 # "name": "Quat/Euler Rotation Mode Converter",
30 # "author": "Mario Mey / Mutant Bob",
32 # "blender": (2, 76, 0),
34 # "description": "Converts bones rotation mode",
37 # "tracker_url": "https://github.com/MarioMey/rotation_mode_addon/",
38 # "category": "Animation"}
42 order_list
= ['QUATERNION', 'XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']
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
):
51 fc
= action
.fcurves
.new(data_path
, array_index
)
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
]]
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
]]
71 def frames_matching(self
, action
, data_path
):
73 for fc
in action
.fcurves
:
74 if fc
.data_path
== data_path
:
75 fri
= [kp
.co
[0] for kp
in fc
.keyframe_points
]
79 # Converts only one group/bone in one action - Quat to euler
80 def group_qe(self
, obj
, action
, bone
, bone_prefix
, order
):
83 data_path
= bone_prefix
+ "rotation_quaternion"
84 frames
= self
.frames_matching(action
, data_path
)
85 group
= action
.groups
[bone
.name
]
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
):
101 data_path
= bone_prefix
+ "rotation_euler"
102 frames
= self
.frames_matching(action
, data_path
)
103 group
= action
.groups
[bone
.name
]
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
):
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'):
138 bone_prefix
= fcurve
.data_path
[:-len('rotation_quaternion')]
141 # If To-Quat conversion
143 if fcurve
.data_path
.endswith('rotation_euler'):
145 bone_prefix
= fcurve
.data_path
[:-len('rotation_euler')]
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
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
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
])
191 print(fcurve
.group
.name
, 'does not exist in Armature. Fcurve-group is not affected')
193 # Look for the ones that has rotation_quaternion
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
])
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
)
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' ) ],
233 # description = "The target rotation mode")
235 # scn['order_list'] = 0
239 # initSceneProperties(bpy.context.scene)
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'
252 def draw(self
, context
):
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'
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'}
285 def invoke(self
, context
, event
):
286 self
.execute(context
)
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
)
299 convert
.one_act_every_bon(obj
, action
, order
)
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'}
311 def invoke(self
, context
, event
):
312 self
.execute(context
)
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
)
324 convert
.all_act_every_bon(obj
, order
)
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
)
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__)