remove bl_operators/nla.py, move bake_action function into bpy_extras.anim_utils...
[blender.git] / release / scripts / startup / bl_operators / anim.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 if "bpy" in locals():
22     import imp
23     if "anim_utils" in locals():
24         imp.reload(anim_utils)
25
26 import bpy
27 from bpy.types import Operator
28 from bpy.props import IntProperty, BoolProperty, EnumProperty
29
30
31 class ANIM_OT_keying_set_export(Operator):
32     "Export Keying Set to a python script"
33     bl_idname = "anim.keying_set_export"
34     bl_label = "Export Keying Set..."
35
36     filepath = bpy.props.StringProperty(name="File Path", description="Filepath to write file to")
37     filter_folder = bpy.props.BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'})
38     filter_text = bpy.props.BoolProperty(name="Filter text", description="", default=True, options={'HIDDEN'})
39     filter_python = bpy.props.BoolProperty(name="Filter python", description="", default=True, options={'HIDDEN'})
40
41     def execute(self, context):
42         if not self.filepath:
43             raise Exception("Filepath not set")
44
45         f = open(self.filepath, "w")
46         if not f:
47             raise Exception("Could not open file")
48
49         scene = context.scene
50         ks = scene.keying_sets.active
51
52         f.write("# Keying Set: %s\n" % ks.name)
53
54         f.write("import bpy\n\n")
55         f.write("scene= bpy.data.scenes[0]\n\n")  # XXX, why not use the current scene?
56
57         # Add KeyingSet and set general settings
58         f.write("# Keying Set Level declarations\n")
59         f.write("ks= scene.keying_sets.new(name=\"%s\")\n" % ks.name)
60
61         if not ks.is_path_absolute:
62             f.write("ks.is_path_absolute = False\n")
63         f.write("\n")
64
65         f.write("ks.bl_options = %r\n" % ks.bl_options)
66         f.write("\n")
67
68         # generate and write set of lookups for id's used in paths
69         id_to_paths_cache = {}  # cache for syncing ID-blocks to bpy paths + shorthands
70
71         for ksp in ks.paths:
72             if ksp.id is None:
73                 continue
74             if ksp.id in id_to_paths_cache:
75                 continue
76
77             # - idtype_list is used to get the list of id-datablocks from bpy.data.*
78             #   since this info isn't available elsewhere
79             # - id.bl_rna.name gives a name suitable for UI,
80             #   with a capitalised first letter, but we need
81             #   the plural form that's all lower case
82             idtype_list = ksp.id.bl_rna.name.lower() + "s"
83             id_bpy_path = "bpy.data.%s[\"%s\"]" % (idtype_list, ksp.id.name)
84
85             # shorthand ID for the ID-block (as used in the script)
86             short_id = "id_%d" % len(id_to_paths_cache)
87
88             # store this in the cache now
89             id_to_paths_cache[ksp.id] = [short_id, id_bpy_path]
90
91         f.write("# ID's that are commonly used\n")
92         for id_pair in id_to_paths_cache.values():
93             f.write("%s = %s\n" % (id_pair[0], id_pair[1]))
94         f.write("\n")
95
96         # write paths
97         f.write("# Path Definitions\n")
98         for ksp in ks.paths:
99             f.write("ksp = ks.paths.add(")
100
101             # id-block + data_path
102             if ksp.id:
103                 # find the relevant shorthand from the cache
104                 id_bpy_path = id_to_paths_cache[ksp.id][0]
105             else:
106                 id_bpy_path = "None"  # XXX...
107             f.write("%s, '%s'" % (id_bpy_path, ksp.data_path))
108
109             # array index settings (if applicable)
110             if ksp.use_entire_array:
111                 f.write(", index=-1")
112             else:
113                 f.write(", index=%d" % ksp.array_index)
114
115             # grouping settings (if applicable)
116             # NOTE: the current default is KEYINGSET, but if this changes, change this code too
117             if ksp.group_method == 'NAMED':
118                 f.write(", group_method='%s', group_name=\"%s\"" % (ksp.group_method, ksp.group))
119             elif ksp.group_method != 'KEYINGSET':
120                 f.write(", group_method='%s'" % ksp.group_method)
121
122             # finish off
123             f.write(")\n")
124
125         f.write("\n")
126         f.close()
127
128         return {'FINISHED'}
129
130     def invoke(self, context, event):
131         wm = context.window_manager
132         wm.fileselect_add(self)
133         return {'RUNNING_MODAL'}
134
135
136 class BakeAction(Operator):
137     '''Bake animation to an Action'''
138     bl_idname = "nla.bake"
139     bl_label = "Bake Action"
140     bl_options = {'REGISTER', 'UNDO'}
141
142     frame_start = IntProperty(
143             name="Start Frame",
144             description="Start frame for baking",
145             min=0, max=300000,
146             default=1,
147             )
148     frame_end = IntProperty(
149             name="End Frame",
150             description="End frame for baking",
151             min=1, max=300000,
152             default=250,
153             )
154     step = IntProperty(
155             name="Frame Step",
156             description="Frame Step",
157             min=1, max=120,
158             default=1,
159             )
160     only_selected = BoolProperty(
161             name="Only Selected",
162             default=True,
163             )
164     clear_consraints = BoolProperty(
165             name="Clear Constraints",
166             default=False,
167             )
168     bake_types = EnumProperty(
169             name="Bake Data",
170             options={'ENUM_FLAG'},
171             items=(('POSE', "Pose", ""),
172                    ('OBJECT', "Object", ""),
173                    ),
174             default={'POSE'},
175             )
176
177     def execute(self, context):
178
179         from bpy_extras import anim_utils
180
181         action = anim_utils.bake_action(self.frame_start,
182                                         self.frame_end,
183                                         self.step,
184                                         self.only_selected,
185                                         'POSE' in self.bake_types,
186                                         'OBJECT' in self.bake_types,
187                                         self.clear_consraints,
188                                         True,
189                                  )
190
191         if action is None:
192             self.report({'INFO'}, "Nothing to bake")
193             return {'CANCELLED'}
194
195         return {'FINISHED'}
196
197     def invoke(self, context, event):
198         wm = context.window_manager
199         return wm.invoke_props_dialog(self)
200
201
202 class ClearUselessActions(Operator):
203     '''Mark actions with no F-Curves for deletion after save+reload of ''' \
204     '''file preserving "action libraries"'''
205     bl_idname = "anim.clear_useless_actions"
206     bl_label = "Clear Useless Actions"
207     bl_options = {'REGISTER', 'UNDO'}
208
209     only_unused = BoolProperty(name="Only Unused",
210             description="Only unused (Fake User only) actions get considered",
211             default=True)
212
213     @classmethod
214     def poll(cls, context):
215         return len(bpy.data.actions) != 0
216
217     def execute(self, context):
218         removed = 0
219
220         for action in bpy.data.actions:
221             # if only user is "fake" user...
222             if ((self.only_unused is False) or
223                 (action.use_fake_user and action.users == 1)):
224
225                 # if it has F-Curves, then it's a "action library"
226                 # (i.e. walk, wave, jump, etc.)
227                 # and should be left alone as that's what fake users are for!
228                 if not action.fcurves:
229                     # mark action for deletion
230                     action.user_clear()
231                     removed += 1
232
233         self.report({'INFO'}, "Removed %d empty and/or fake-user only Actions"
234                               % removed)
235         return {'FINISHED'}