re-commit temp workaround [#35920], this still fails for OSX retina display,
[blender-staging.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,
29                        BoolProperty,
30                        EnumProperty,
31                        StringProperty,
32                        )
33
34
35 class ANIM_OT_keying_set_export(Operator):
36     "Export Keying Set to a python script"
37     bl_idname = "anim.keying_set_export"
38     bl_label = "Export Keying Set..."
39
40     filepath = StringProperty(
41             subtype='FILE_PATH',
42             )
43     filter_folder = BoolProperty(
44             name="Filter folders",
45             default=True,
46             options={'HIDDEN'},
47             )
48     filter_text = BoolProperty(
49             name="Filter text",
50             default=True,
51             options={'HIDDEN'},
52             )
53     filter_python = BoolProperty(
54             name="Filter python",
55             default=True,
56             options={'HIDDEN'},
57             )
58
59     def execute(self, context):
60         if not self.filepath:
61             raise Exception("Filepath not set")
62
63         f = open(self.filepath, "w")
64         if not f:
65             raise Exception("Could not open file")
66
67         scene = context.scene
68         ks = scene.keying_sets.active
69
70         f.write("# Keying Set: %s\n" % ks.bl_idname)
71
72         f.write("import bpy\n\n")
73         f.write("scene = bpy.context.scene\n\n")
74
75         # Add KeyingSet and set general settings
76         f.write("# Keying Set Level declarations\n")
77         f.write("ks = scene.keying_sets.new(idname=\"%s\", name=\"%s\")\n"
78                 "" % (ks.bl_idname, ks.bl_label))
79         f.write("ks.bl_description = \"%s\"\n" % ks.bl_description)
80
81         if not ks.is_path_absolute:
82             f.write("ks.is_path_absolute = False\n")
83         f.write("\n")
84
85         f.write("ks.bl_options = %r\n" % ks.bl_options)
86         f.write("\n")
87
88         # --------------------------------------------------------
89         # generate and write set of lookups for id's used in paths
90
91         # cache for syncing ID-blocks to bpy paths + shorthand's
92         id_to_paths_cache = {}
93
94         for ksp in ks.paths:
95             if ksp.id is None:
96                 continue
97             if ksp.id in id_to_paths_cache:
98                 continue
99
100             """
101             - idtype_list is used to get the list of id-datablocks from
102               bpy.data.* since this info isn't available elsewhere
103             - id.bl_rna.name gives a name suitable for UI,
104               with a capitalised first letter, but we need
105               the plural form that's all lower case
106             """
107
108             idtype_list = ksp.id.bl_rna.name.lower() + "s"
109             id_bpy_path = "bpy.data.%s[\"%s\"]" % (idtype_list, ksp.id.name)
110
111             # shorthand ID for the ID-block (as used in the script)
112             short_id = "id_%d" % len(id_to_paths_cache)
113
114             # store this in the cache now
115             id_to_paths_cache[ksp.id] = [short_id, id_bpy_path]
116
117         f.write("# ID's that are commonly used\n")
118         for id_pair in id_to_paths_cache.values():
119             f.write("%s = %s\n" % (id_pair[0], id_pair[1]))
120         f.write("\n")
121
122         # write paths
123         f.write("# Path Definitions\n")
124         for ksp in ks.paths:
125             f.write("ksp = ks.paths.add(")
126
127             # id-block + data_path
128             if ksp.id:
129                 # find the relevant shorthand from the cache
130                 id_bpy_path = id_to_paths_cache[ksp.id][0]
131             else:
132                 id_bpy_path = "None"  # XXX...
133             f.write("%s, '%s'" % (id_bpy_path, ksp.data_path))
134
135             # array index settings (if applicable)
136             if ksp.use_entire_array:
137                 f.write(", index=-1")
138             else:
139                 f.write(", index=%d" % ksp.array_index)
140
141             # grouping settings (if applicable)
142             # NOTE: the current default is KEYINGSET, but if this changes,
143             # change this code too
144             if ksp.group_method == 'NAMED':
145                 f.write(", group_method='%s', group_name=\"%s\"" %
146                         (ksp.group_method, ksp.group))
147             elif ksp.group_method != 'KEYINGSET':
148                 f.write(", group_method='%s'" % ksp.group_method)
149
150             # finish off
151             f.write(")\n")
152
153         f.write("\n")
154         f.close()
155
156         return {'FINISHED'}
157
158     def invoke(self, context, event):
159         wm = context.window_manager
160         wm.fileselect_add(self)
161         return {'RUNNING_MODAL'}
162
163
164 class BakeAction(Operator):
165     """Bake object/pose loc/scale/rotation animation to a new action"""
166     bl_idname = "nla.bake"
167     bl_label = "Bake Action"
168     bl_options = {'REGISTER', 'UNDO'}
169
170     frame_start = IntProperty(
171             name="Start Frame",
172             description="Start frame for baking",
173             min=0, max=300000,
174             default=1,
175             )
176     frame_end = IntProperty(
177             name="End Frame",
178             description="End frame for baking",
179             min=1, max=300000,
180             default=250,
181             )
182     step = IntProperty(
183             name="Frame Step",
184             description="Frame Step",
185             min=1, max=120,
186             default=1,
187             )
188     only_selected = BoolProperty(
189             name="Only Selected",
190             description="Only key selected object/bones",
191             default=True,
192             )
193     visual_keying = BoolProperty(
194             name="Visual Keying",
195             description="Keyframe from the final transformations (with constraints applied)",
196             default=False,
197             )
198     clear_constraints = BoolProperty(
199             name="Clear Constraints",
200             description="Remove all constraints from keyed object/bones, and do 'visual' keying",
201             default=False,
202             )
203     clear_parents = BoolProperty(
204             name="Clear Parents",
205             description="Bake animation onto the object then clear parents (objects only)",
206             default=False,
207             )
208     bake_types = EnumProperty(
209             name="Bake Data",
210             description="Which data's transformations to bake",
211             options={'ENUM_FLAG'},
212             items=(('POSE', "Pose", "Bake bones transformations"),
213                    ('OBJECT', "Object", "Bake object transformations"),
214                    ),
215             default={'POSE'},
216             )
217
218     def execute(self, context):
219
220         from bpy_extras import anim_utils
221
222         action = anim_utils.bake_action(self.frame_start,
223                                         self.frame_end,
224                                         frame_step=self.step,
225                                         only_selected=self.only_selected,
226                                         do_pose='POSE' in self.bake_types,
227                                         do_object='OBJECT' in self.bake_types,
228                                         do_visual_keying=self.visual_keying,
229                                         do_constraint_clear=self.clear_constraints,
230                                         do_parents_clear=self.clear_parents,
231                                         do_clean=True,
232                                         )
233
234         if action is None:
235             self.report({'INFO'}, "Nothing to bake")
236             return {'CANCELLED'}
237
238         return {'FINISHED'}
239
240     def invoke(self, context, event):
241         scene = context.scene
242         self.frame_start = scene.frame_start
243         self.frame_end = scene.frame_end
244         self.bake_types = {'POSE'} if context.mode == 'POSE' else {'OBJECT'}
245
246         wm = context.window_manager
247         return wm.invoke_props_dialog(self)
248
249
250 class ClearUselessActions(Operator):
251     """Mark actions with no F-Curves for deletion after save & reload of """ \
252     """file preserving \"action libraries\""""
253     bl_idname = "anim.clear_useless_actions"
254     bl_label = "Clear Useless Actions"
255     bl_options = {'REGISTER', 'UNDO'}
256
257     only_unused = BoolProperty(name="Only Unused",
258             description="Only unused (Fake User only) actions get considered",
259             default=True)
260
261     @classmethod
262     def poll(cls, context):
263         return bool(bpy.data.actions)
264
265     def execute(self, context):
266         removed = 0
267
268         for action in bpy.data.actions:
269             # if only user is "fake" user...
270             if ((self.only_unused is False) or
271                 (action.use_fake_user and action.users == 1)):
272
273                 # if it has F-Curves, then it's a "action library"
274                 # (i.e. walk, wave, jump, etc.)
275                 # and should be left alone as that's what fake users are for!
276                 if not action.fcurves:
277                     # mark action for deletion
278                     action.user_clear()
279                     removed += 1
280
281         self.report({'INFO'}, "Removed %d empty and/or fake-user only Actions"
282                               % removed)
283         return {'FINISHED'}
284
285
286 class UpdateAnimData(Operator):
287     """Update data paths from 2.56 and previous versions, """ \
288     """modifying data paths of drivers and fcurves"""
289     bl_idname = "anim.update_data_paths"
290     bl_label = "Update Animation Data"
291
292     def execute(self, context):
293         import animsys_refactor
294         animsys_refactor.update_data_paths(animsys_refactor.data_2_56_to_2_59)
295         return {'FINISHED'}