Fix T71100: Node Wrangler creates nodes on linked node trees
[blender-addons.git] / rigify / rot_mode.py
bloba395ec6e006bde59badc2590e7f0604edcf28628
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 '''
4 Quat/Euler Rotation Mode Converter v0.1
6 This script/addon:
7 - Changes (pose) bone rotation mode
8 - Converts keyframes from one rotation mode to another
9 - Creates fcurves/keyframes in target rotation mode
10 - Deletes previous fcurves/keyframes.
11 - Converts multiple bones
12 - Converts multiple Actions
14 TO-DO:
15 - To convert object's rotation mode (already done in Mutant Bob script,
16 but not done in this one.
17 - To understand "EnumProperty" and write it well.
18 - Code clean
19 - ...
21 GitHub: https://github.com/MarioMey/rotation_mode_addon/
22 BlenderArtist thread: http://blenderartists.org/forum/showthread.php?388197-Quat-Euler-Rotation-Mode-Converter
24 Mutant Bob did the "hard code" of this script. Thanks him!
25 blender.stackexchange.com/questions/40711/how-to-convert-quaternions-keyframes-to-euler-ones-in-several-actions
28 '''
30 # bl_info = {
31 # "name": "Rotation Mode Converter",
32 # "author": "Mario Mey / Mutant Bob",
33 # "version": (0, 2),
34 # "blender": (2, 91, 0),
35 # 'location': 'Pose Mode -> Header -> Pose -> Convert Rotation Modes',
36 # "description": "Converts Animation between different rotation orders",
37 # "warning": "",
38 # "doc_url": "",
39 # "tracker_url": "https://github.com/MarioMey/rotation_mode_addon/",
40 # "category": "Animation",
41 # }
43 import bpy
44 from bpy.props import (
45 EnumProperty,
46 StringProperty,
50 def get_or_create_fcurve(action, data_path, array_index=-1, group=None):
51 for fc in action.fcurves:
52 if fc.data_path == data_path and (array_index < 0 or fc.array_index == array_index):
53 return fc
55 fc = action.fcurves.new(data_path, index=array_index)
56 fc.group = group
57 return fc
59 def add_keyframe_quat(action, quat, frame, bone_prefix, group):
60 for i in range(len(quat)):
61 fc = get_or_create_fcurve(action, bone_prefix + "rotation_quaternion", i, group)
62 pos = len(fc.keyframe_points)
63 fc.keyframe_points.add(1)
64 fc.keyframe_points[pos].co = [frame, quat[i]]
65 fc.update()
67 def add_keyframe_euler(action, euler, frame, bone_prefix, group):
68 for i in range(len(euler)):
69 fc = get_or_create_fcurve(action, bone_prefix + "rotation_euler", i, group)
70 pos = len(fc.keyframe_points)
71 fc.keyframe_points.add(1)
72 fc.keyframe_points[pos].co = [frame, euler[i]]
73 fc.update()
75 def frames_matching(action, data_path):
76 frames = set()
77 for fc in action.fcurves:
78 if fc.data_path == data_path:
79 fri = [kp.co[0] for kp in fc.keyframe_points]
80 frames.update(fri)
81 return frames
83 def group_qe(obj, action, bone, bone_prefix, order):
84 """Converts only one group/bone in one action - Quat to euler."""
85 pose_bone = bone
86 data_path = bone_prefix + "rotation_quaternion"
87 frames = frames_matching(action, data_path)
88 group = action.groups[bone.name]
90 for fr in frames:
91 quat = bone.rotation_quaternion.copy()
92 for fc in action.fcurves:
93 if fc.data_path == data_path:
94 quat[fc.array_index] = fc.evaluate(fr)
95 euler = quat.to_euler(order)
97 add_keyframe_euler(action, euler, fr, bone_prefix, group)
98 bone.rotation_mode = order
100 def group_eq(obj, action, bone, bone_prefix, order):
101 """Converts only one group/bone in one action - Euler to Quat."""
102 pose_bone = bone
103 data_path = bone_prefix + "rotation_euler"
104 frames = frames_matching(action, data_path)
105 group = action.groups[bone.name]
107 for fr in frames:
108 euler = bone.rotation_euler.copy()
109 for fc in action.fcurves:
110 if fc.data_path == data_path:
111 euler[fc.array_index] = fc.evaluate(fr)
112 quat = euler.to_quaternion()
114 add_keyframe_quat(action, quat, fr, bone_prefix, group)
115 bone.rotation_mode = order
117 def convert_curves_of_bone_in_action(obj, action, bone, order):
118 """Convert given bone's curves in given action to given rotation order."""
119 to_euler = False
120 bone_prefix = ''
122 for fcurve in action.fcurves:
123 if fcurve.group.name == bone.name:
125 # If To-Euler conversion
126 if order != 'QUATERNION':
127 if fcurve.data_path.endswith('rotation_quaternion'):
128 to_euler = True
129 bone_prefix = fcurve.data_path[:-len('rotation_quaternion')]
130 break
132 # If To-Quat conversion
133 else:
134 if fcurve.data_path.endswith('rotation_euler'):
135 to_euler = True
136 bone_prefix = fcurve.data_path[:-len('rotation_euler')]
137 break
139 # If To-Euler conversion
140 if to_euler and order != 'QUATERNION':
141 # Converts the group/bone from Quat to Euler
142 group_qe(obj, action, bone, bone_prefix, order)
144 # Removes quaternion fcurves
145 for key in action.fcurves:
146 if key.data_path == 'pose.bones["' + bone.name + '"].rotation_quaternion':
147 action.fcurves.remove(key)
149 # If To-Quat conversion
150 elif to_euler:
151 # Converts the group/bone from Euler to Quat
152 group_eq(obj, action, bone, bone_prefix, order)
154 # Removes euler fcurves
155 for key in action.fcurves:
156 if key.data_path == 'pose.bones["' + bone.name + '"].rotation_euler':
157 action.fcurves.remove(key)
159 # Changes rotation mode to new one
160 bone.rotation_mode = order
162 class POSE_OT_convert_rotation(bpy.types.Operator):
163 bl_label = 'Convert Rotation Modes'
164 bl_idname = 'pose.convert_rotation'
165 bl_description = 'Convert animation from any rotation mode to any other'
166 bl_options = {'REGISTER', 'UNDO'}
168 # Properties.
169 target_rotation_mode: EnumProperty(
170 items=[
171 ('QUATERNION', 'Quaternion', 'Quaternion'),
172 ('XYZ', 'XYZ', 'XYZ Euler'),
173 ('XZY', 'XZY', 'XZY Euler'),
174 ('YXZ', 'YXZ', 'YXZ Euler'),
175 ('YZX', 'YZX', 'YZX Euler'),
176 ('ZXY', 'ZXY', 'ZXY Euler'),
177 ('ZYX', 'ZYX', 'ZYX Euler')
179 name='Convert To',
180 description="The target rotation mode",
181 default='QUATERNION',
183 affected_bones: EnumProperty(
184 name="Affected Bones",
185 items=[
186 ('SELECT', 'Selected', 'Selected'),
187 ('ALL', 'All', 'All'),
189 description="Which bones to affect",
190 default='SELECT',
192 affected_actions: EnumProperty(
193 name="Affected Action",
194 items=[
195 ('SINGLE', 'Single', 'Single'),
196 ('ALL', 'All', 'All'),
198 description="Which Actions to affect",
199 default='SINGLE',
201 selected_action: StringProperty(name="Action")
203 def invoke(self, context, event):
204 ob = context.object
205 if ob and ob.type=='ARMATURE' and ob.animation_data and ob.animation_data.action:
206 self.selected_action = context.object.animation_data.action.name
207 else:
208 self.affected_actions = 'ALL'
210 wm = context.window_manager
211 return wm.invoke_props_dialog(self)
213 def draw(self, context):
214 layout = self.layout
215 layout.use_property_split = True
216 layout.use_property_decorate = False
218 layout.row().prop(self, 'affected_bones', expand=True)
219 layout.row().prop(self, 'affected_actions', expand=True)
220 if self.affected_actions=='SINGLE':
221 layout.prop_search(self, 'selected_action', bpy.data, 'actions')
222 layout.prop(self, 'target_rotation_mode')
224 def execute(self, context):
225 obj = context.active_object
227 actions = [bpy.data.actions.get(self.selected_action)]
228 pose_bones = context.selected_pose_bones
229 if self.affected_bones=='ALL':
230 pose_bones = obj.pose.bones
231 if self.affected_actions=='ALL':
232 actions = bpy.data.actions
234 for action in actions:
235 for pb in pose_bones:
236 convert_curves_of_bone_in_action(obj, action, pb, self.target_rotation_mode)
238 return {'FINISHED'}
240 def draw_convert_rotation(self, context):
241 self.layout.separator()
242 self.layout.operator(POSE_OT_convert_rotation.bl_idname)
244 classes = [
245 POSE_OT_convert_rotation
248 def register():
249 from bpy.utils import register_class
251 # Classes.
252 for cls in classes:
253 register_class(cls)
255 bpy.types.VIEW3D_MT_pose.append(draw_convert_rotation)
257 def unregister():
258 from bpy.utils import unregister_class
260 # Classes.
261 for cls in classes:
262 unregister_class(cls)
264 bpy.types.VIEW3D_MT_pose.remove(draw_convert_rotation)