svn merge -r39781:39792 https://svn.blender.org/svnroot/bf-blender/trunk/blender...
[blender-staging.git] / release / scripts / startup / bl_operators / nla.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 import bpy
22 from bpy.types import Operator
23
24
25 def pose_frame_info(obj):
26     from mathutils import Matrix
27
28     info = {}
29
30     pose = obj.pose
31
32     pose_items = pose.bones.items()
33
34     for name, pbone in pose_items:
35         binfo = {}
36         bone = pbone.bone
37
38         binfo["parent"] = getattr(bone.parent, "name", None)
39         binfo["bone"] = bone
40         binfo["pbone"] = pbone
41         binfo["matrix_local"] = bone.matrix_local.copy()
42         try:
43             binfo["matrix_local_inv"] = binfo["matrix_local"].inverted()
44         except:
45             binfo["matrix_local_inv"] = Matrix()
46
47         binfo["matrix"] = bone.matrix.copy()
48         binfo["matrix_pose"] = pbone.matrix.copy()
49         try:
50             binfo["matrix_pose_inv"] = binfo["matrix_pose"].inverted()
51         except:
52             binfo["matrix_pose_inv"] = Matrix()
53
54         info[name] = binfo
55
56     for name, pbone in pose_items:
57         binfo = info[name]
58         binfo_parent = binfo.get("parent", None)
59         if binfo_parent:
60             binfo_parent = info[binfo_parent]
61
62         matrix = binfo["matrix_pose"]
63         rest_matrix = binfo["matrix_local"]
64
65         if binfo_parent:
66             matrix = binfo_parent["matrix_pose_inv"] * matrix
67             rest_matrix = binfo_parent["matrix_local_inv"] * rest_matrix
68
69         binfo["matrix_key"] = rest_matrix.inverted() * matrix
70
71     return info
72
73
74 def obj_frame_info(obj):
75     info = {}
76     # parent = obj.parent
77     info["matrix_key"] = obj.matrix_local.copy()
78     return info
79
80
81 def bake(frame_start,
82          frame_end, step=1,
83          only_selected=False,
84          do_pose=True,
85          do_object=True,
86          do_constraint_clear=False,
87          action=None):
88
89     scene = bpy.context.scene
90     obj = bpy.context.object
91     pose = obj.pose
92     frame_back = scene.frame_current
93
94     if pose is None:
95         do_pose = False
96
97     if do_pose is None and do_object is None:
98         return None
99
100     pose_info = []
101     obj_info = []
102
103     frame_range = range(frame_start, frame_end + 1, step)
104
105     # -------------------------------------------------------------------------
106     # Collect transformations
107
108     # could speed this up by applying steps here too...
109     for f in frame_range:
110         scene.frame_set(f)
111
112         if do_pose:
113             pose_info.append(pose_frame_info(obj))
114         if do_object:
115             obj_info.append(obj_frame_info(obj))
116
117         f += 1
118
119     # -------------------------------------------------------------------------
120     # Create action
121
122     # incase animation data hassnt been created
123     atd = obj.animation_data_create()
124     if action is None:
125         action = bpy.data.actions.new("Action")
126     atd.action = action
127
128     if do_pose:
129         pose_items = pose.bones.items()
130     else:
131         pose_items = []  # skip
132
133     # -------------------------------------------------------------------------
134     # Apply transformations to action
135
136     # pose
137     for name, pbone in (pose_items if do_pose else ()):
138         if only_selected and not pbone.bone.select:
139             continue
140
141         if do_constraint_clear:
142             while pbone.constraints:
143                 pbone.constraints.remove(pbone.constraints[0])
144
145         for f in frame_range:
146             matrix = pose_info[(f - frame_start) // step][name]["matrix_key"]
147
148             # pbone.location = matrix.to_translation()
149             # pbone.rotation_quaternion = matrix.to_quaternion()
150             pbone.matrix_basis = matrix
151
152             pbone.keyframe_insert("location", -1, f, name)
153
154             rotation_mode = pbone.rotation_mode
155
156             if rotation_mode == 'QUATERNION':
157                 pbone.keyframe_insert("rotation_quaternion", -1, f, name)
158             elif rotation_mode == 'AXIS_ANGLE':
159                 pbone.keyframe_insert("rotation_axis_angle", -1, f, name)
160             else:  # euler, XYZ, ZXY etc
161                 pbone.keyframe_insert("rotation_euler", -1, f, name)
162
163             pbone.keyframe_insert("scale", -1, f, name)
164
165     # object. TODO. multiple objects
166     if do_object:
167         if do_constraint_clear:
168             while obj.constraints:
169                 obj.constraints.remove(obj.constraints[0])
170
171         for f in frame_range:
172             matrix = obj_info[(f - frame_start) // step]["matrix_key"]
173             obj.matrix_local = matrix
174
175             obj.keyframe_insert("location", -1, f)
176
177             rotation_mode = obj.rotation_mode
178
179             if rotation_mode == 'QUATERNION':
180                 obj.keyframe_insert("rotation_quaternion", -1, f)
181             elif rotation_mode == 'AXIS_ANGLE':
182                 obj.keyframe_insert("rotation_axis_angle", -1, f)
183             else:  # euler, XYZ, ZXY etc
184                 obj.keyframe_insert("rotation_euler", -1, f)
185
186             obj.keyframe_insert("scale", -1, f)
187
188     scene.frame_set(frame_back)
189
190     return action
191
192
193 from bpy.props import IntProperty, BoolProperty, EnumProperty
194
195
196 class BakeAction(Operator):
197     '''Bake animation to an Action'''
198     bl_idname = "nla.bake"
199     bl_label = "Bake Action"
200     bl_options = {'REGISTER', 'UNDO'}
201
202     frame_start = IntProperty(
203             name="Start Frame",
204             description="Start frame for baking",
205             min=0, max=300000,
206             default=1,
207             )
208     frame_end = IntProperty(
209             name="End Frame",
210             description="End frame for baking",
211             min=1, max=300000,
212             default=250,
213             )
214     step = IntProperty(
215             name="Frame Step",
216             description="Frame Step",
217             min=1, max=120,
218             default=1,
219             )
220     only_selected = BoolProperty(
221             name="Only Selected",
222             default=True,
223             )
224     clear_consraints = BoolProperty(
225             name="Clear Constraints",
226             default=False,
227             )
228     bake_types = EnumProperty(
229             name="Bake Data",
230             options={'ENUM_FLAG'},
231             items=(('POSE', "Pose", ""),
232                    ('OBJECT', "Object", ""),
233                    ),
234             default={'POSE'},
235             )
236
237     def execute(self, context):
238
239         action = bake(self.frame_start,
240                       self.frame_end,
241                       self.step,
242                       self.only_selected,
243                       'POSE' in self.bake_types,
244                       'OBJECT' in self.bake_types,
245                       self.clear_consraints,
246                       )
247
248         if action is None:
249             self.report({'INFO'}, "Nothing to bake")
250             return {'CANCELLED'}
251
252         # basic cleanup, could move elsewhere
253         for fcu in action.fcurves:
254             keyframe_points = fcu.keyframe_points
255             i = 1
256             while i < len(fcu.keyframe_points) - 1:
257                 val_prev = keyframe_points[i - 1].co[1]
258                 val_next = keyframe_points[i + 1].co[1]
259                 val = keyframe_points[i].co[1]
260
261                 if abs(val - val_prev) + abs(val - val_next) < 0.0001:
262                     keyframe_points.remove(keyframe_points[i])
263                 else:
264                     i += 1
265
266         return {'FINISHED'}
267
268     def invoke(self, context, event):
269         wm = context.window_manager
270         return wm.invoke_props_dialog(self)
271
272 #################################
273
274
275 class ClearUselessActions(bpy.types.Operator):
276     '''Mark actions with no F-Curves for deletion after save+reload of file preserving "action libraries"'''
277     bl_idname = "anim.clear_useless_actions"
278     bl_label = "Clear Useless Actions"
279     bl_options = {'REGISTER', 'UNDO'}
280
281     only_unused = BoolProperty(name="Only Unused",
282             description="Only unused (Fake User only) actions get considered",
283             default=True)
284
285     @classmethod
286     def poll(cls, context):
287         return len(bpy.data.actions) != 0
288
289     def execute(self, context):
290         removed = 0
291
292         for action in bpy.data.actions:
293             # if only user is "fake" user...
294             if ((self.only_unused is False) or
295                 (action.use_fake_user and action.users == 1)):
296
297                 # if it has F-Curves, then it's a "action library" (i.e. walk, wave, jump, etc.)
298                 # and should be left alone as that's what fake users are for!
299                 if not action.fcurves:
300                     # mark action for deletion
301                     action.user_clear()
302                     removed += 1
303
304         self.report({'INFO'}, "Removed %d empty and/or fake-user only Actions" % (removed))
305         return {'FINISHED'}