ee270c6e8c7bdb12b1b060248538a00e68392694
[blender-staging.git] / release / scripts / modules / bpy_extras / anim_utils.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 compliant>
20
21 __all__ = (
22     "bake_action",
23     )
24
25 import bpy
26
27
28 # XXX visual keying is actually always considered as True in this code...
29 def bake_action(
30         obj,
31         frame_start,
32         frame_end,
33         frame_step=1,
34         only_selected=False,
35         do_pose=True,
36         do_object=True,
37         do_visual_keying=True,
38         do_constraint_clear=False,
39         do_parents_clear=False,
40         do_clean=False,
41         action=None,
42 ):
43
44     """
45     Return an image from the file path with options to search multiple paths
46     and return a placeholder if its not found.
47
48     :arg obj: Object to bake.
49     :type obj: :class:`bpy.types.Object`
50     :arg frame_start: First frame to bake.
51     :type frame_start: int
52     :arg frame_end: Last frame to bake.
53     :type frame_end: int
54     :arg frame_step: Frame step.
55     :type frame_step: int
56     :arg only_selected: Only bake selected bones.
57     :type only_selected: bool
58     :arg do_pose: Bake pose channels.
59     :type do_pose: bool
60     :arg do_object: Bake objects.
61     :type do_object: bool
62     :arg do_visual_keying: Use the final transformations for baking ('visual keying')
63     :type do_visual_keying: bool
64     :arg do_constraint_clear: Remove constraints after baking.
65     :type do_constraint_clear: bool
66     :arg do_parents_clear: Unparent after baking objects.
67     :type do_parents_clear: bool
68     :arg do_clean: Remove redundant keyframes after baking.
69     :type do_clean: bool
70     :arg action: An action to bake the data into, or None for a new action
71        to be created.
72     :type action: :class:`bpy.types.Action` or None
73
74     :return: an action or None
75     :rtype: :class:`bpy.types.Action`
76     """
77
78     # -------------------------------------------------------------------------
79     # Helper Functions and vars
80
81     def pose_frame_info(obj):
82         matrix = {}
83         for name, pbone in obj.pose.bones.items():
84             if do_visual_keying:
85                 # Get the final transform of the bone in its own local space...
86                 matrix[name] = obj.convert_space(pbone, pbone.matrix, 'POSE', 'LOCAL')
87             else:
88                 matrix[name] = pbone.matrix_basis.copy()
89         return matrix
90
91     if do_parents_clear:
92         if do_visual_keying:
93             def obj_frame_info(obj):
94                 return obj.matrix_world.copy()
95         else:
96             def obj_frame_info(obj):
97                 parent = obj.parent
98                 matrix = obj.matrix_basis
99                 if parent:
100                     return parent.matrix_world * matrix
101                 else:
102                     return matrix.copy()
103     else:
104         if do_visual_keying:
105             def obj_frame_info(obj):
106                 parent = obj.parent
107                 matrix = obj.matrix_world
108                 if parent:
109                     return parent.matrix_world.inverted_safe() * matrix
110                 else:
111                     return matrix.copy()
112         else:
113             def obj_frame_info(obj):
114                 return obj.matrix_basis.copy()
115
116     # -------------------------------------------------------------------------
117     # Setup the Context
118
119     # TODO, pass data rather then grabbing from the context!
120     scene = bpy.context.scene
121     frame_back = scene.frame_current
122
123     if obj.pose is None:
124         do_pose = False
125
126     if not (do_pose or do_object):
127         return None
128
129     pose_info = []
130     obj_info = []
131
132     options = {'INSERTKEY_NEEDED'}
133
134     frame_range = range(frame_start, frame_end + 1, frame_step)
135
136     # -------------------------------------------------------------------------
137     # Collect transformations
138
139     for f in frame_range:
140         scene.frame_set(f)
141         scene.update()
142         if do_pose:
143             pose_info.append(pose_frame_info(obj))
144         if do_object:
145             obj_info.append(obj_frame_info(obj))
146
147     # -------------------------------------------------------------------------
148     # Clean (store initial data)
149     if do_clean and action is not None:
150         clean_orig_data = {fcu: {p.co[1] for p in fcu.keyframe_points} for fcu in action.fcurves}
151     else:
152         clean_orig_data = {}
153
154     # -------------------------------------------------------------------------
155     # Create action
156
157     # in case animation data hasn't been created
158     atd = obj.animation_data_create()
159     if action is None:
160         action = bpy.data.actions.new("Action")
161
162     # Leave tweak mode before trying to modify the action (T48397)
163     if atd.use_tweak_mode:
164         atd.use_tweak_mode = False
165
166     atd.action = action
167
168     # -------------------------------------------------------------------------
169     # Apply transformations to action
170
171     # pose
172     if do_pose:
173         for name, pbone in obj.pose.bones.items():
174             if only_selected and not pbone.bone.select:
175                 continue
176
177             if do_constraint_clear:
178                 while pbone.constraints:
179                     pbone.constraints.remove(pbone.constraints[0])
180
181             # create compatible eulers
182             euler_prev = None
183
184             for (f, matrix) in zip(frame_range, pose_info):
185                 pbone.matrix_basis = matrix[name].copy()
186
187                 pbone.keyframe_insert("location", -1, f, name, options)
188
189                 rotation_mode = pbone.rotation_mode
190                 if rotation_mode == 'QUATERNION':
191                     pbone.keyframe_insert("rotation_quaternion", -1, f, name, options)
192                 elif rotation_mode == 'AXIS_ANGLE':
193                     pbone.keyframe_insert("rotation_axis_angle", -1, f, name, options)
194                 else:  # euler, XYZ, ZXY etc
195                     if euler_prev is not None:
196                         euler = pbone.rotation_euler.copy()
197                         euler.make_compatible(euler_prev)
198                         pbone.rotation_euler = euler
199                         euler_prev = euler
200                         del euler
201                     else:
202                         euler_prev = pbone.rotation_euler.copy()
203                     pbone.keyframe_insert("rotation_euler", -1, f, name, options)
204
205                 pbone.keyframe_insert("scale", -1, f, name, options)
206
207     # object. TODO. multiple objects
208     if do_object:
209         if do_constraint_clear:
210             while obj.constraints:
211                 obj.constraints.remove(obj.constraints[0])
212
213         # create compatible eulers
214         euler_prev = None
215
216         for (f, matrix) in zip(frame_range, obj_info):
217             name = "Action Bake"  # XXX: placeholder
218             obj.matrix_basis = matrix
219
220             obj.keyframe_insert("location", -1, f, name, options)
221
222             rotation_mode = obj.rotation_mode
223             if rotation_mode == 'QUATERNION':
224                 obj.keyframe_insert("rotation_quaternion", -1, f, name, options)
225             elif rotation_mode == 'AXIS_ANGLE':
226                 obj.keyframe_insert("rotation_axis_angle", -1, f, name, options)
227             else:  # euler, XYZ, ZXY etc
228                 if euler_prev is not None:
229                     euler = obj.rotation_euler.copy()
230                     euler.make_compatible(euler_prev)
231                     obj.rotation_euler = euler
232                     euler_prev = euler
233                     del euler
234                 else:
235                     euler_prev = obj.rotation_euler.copy()
236                 obj.keyframe_insert("rotation_euler", -1, f, name, options)
237
238             obj.keyframe_insert("scale", -1, f, name, options)
239
240         if do_parents_clear:
241             obj.parent = None
242
243     # -------------------------------------------------------------------------
244     # Clean
245
246     if do_clean:
247         for fcu in action.fcurves:
248             fcu_orig_data = clean_orig_data.get(fcu, set())
249
250             keyframe_points = fcu.keyframe_points
251             i = 1
252             while i < len(keyframe_points) - 1:
253                 val = keyframe_points[i].co[1]
254
255                 if val in fcu_orig_data:
256                     i += 1
257                     continue
258
259                 val_prev = keyframe_points[i - 1].co[1]
260                 val_next = keyframe_points[i + 1].co[1]
261
262                 if abs(val - val_prev) + abs(val - val_next) < 0.0001:
263                     keyframe_points.remove(keyframe_points[i])
264                 else:
265                     i += 1
266
267     scene.frame_set(frame_back)
268
269     return action