Merging r38419 through r38432 from trunk into soc-2011-tomato
[blender.git] / release / scripts / startup / bl_operators / presets.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 import bpy
22
23
24 class AddPresetBase():
25     '''Base preset class, only for subclassing
26     subclasses must define
27      - preset_values
28      - preset_subdir '''
29     # bl_idname = "script.preset_base_add"
30     # bl_label = "Add a Python Preset"
31     bl_options = {'REGISTER'}  # only because invoke_props_popup requires.
32
33     name = bpy.props.StringProperty(name="Name", description="Name of the preset, used to make the path name", maxlen=64, default="")
34     remove_active = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
35
36     @staticmethod
37     def as_filename(name):  # could reuse for other presets
38         for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
39             name = name.replace(char, '_')
40         return name.lower().strip()
41
42     def execute(self, context):
43         import os
44
45         if hasattr(self, "pre_cb"):
46             self.pre_cb(context)
47
48         preset_menu_class = getattr(bpy.types, self.preset_menu)
49
50         if not self.remove_active:
51             name = self.name.strip()
52             if not name:
53                 return {'FINISHED'}
54
55             filename = self.as_filename(name)
56
57             target_path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", self.preset_subdir), create=True)
58
59             if not target_path:
60                 self.report({'WARNING'}, "Failed to create presets path")
61                 return {'CANCELLED'}
62
63             filepath = os.path.join(target_path, filename) + ".py"
64
65             if hasattr(self, "add"):
66                 self.add(context, filepath)
67             else:
68                 file_preset = open(filepath, 'w')
69                 file_preset.write("import bpy\n")
70
71                 if hasattr(self, "preset_defines"):
72                     for rna_path in self.preset_defines:
73                         exec(rna_path)
74                         file_preset.write("%s\n" % rna_path)
75                     file_preset.write("\n")
76
77                 for rna_path in self.preset_values:
78                     value = eval(rna_path)
79                     # convert thin wrapped sequences to simple lists to repr()
80                     try:
81                         value = value[:]
82                     except:
83                         pass
84
85                     file_preset.write("%s = %r\n" % (rna_path, value))
86
87                 file_preset.close()
88
89             preset_menu_class.bl_label = bpy.path.display_name(filename)
90
91         else:
92             preset_active = preset_menu_class.bl_label
93
94             # fairly sloppy but convenient.
95             filepath = bpy.utils.preset_find(preset_active, self.preset_subdir)
96
97             if not filepath:
98                 filepath = bpy.utils.preset_find(preset_active, self.preset_subdir, display_name=True)
99
100             if not filepath:
101                 return {'CANCELLED'}
102
103             if hasattr(self, "remove"):
104                 self.remove(context, filepath)
105             else:
106                 try:
107                     os.remove(filepath)
108                 except:
109                     import traceback
110                     traceback.print_exc()
111
112             # XXX, stupid!
113             preset_menu_class.bl_label = "Presets"
114
115         if hasattr(self, "post_cb"):
116             self.post_cb(context)
117
118         return {'FINISHED'}
119
120     def check(self, context):
121         self.name = self.as_filename(self.name.strip())
122
123     def invoke(self, context, event):
124         if not self.remove_active:
125             wm = context.window_manager
126             return wm.invoke_props_dialog(self)
127         else:
128             return self.execute(context)
129
130
131 class ExecutePreset(bpy.types.Operator):
132     ''' Executes a preset '''
133     bl_idname = "script.execute_preset"
134     bl_label = "Execute a Python Preset"
135
136     filepath = bpy.props.StringProperty(name="Path", description="Path of the Python file to execute", maxlen=512, default="")
137     menu_idname = bpy.props.StringProperty(name="Menu ID Name", description="ID name of the menu this was called from", default="")
138
139     def execute(self, context):
140         from os.path import basename
141         filepath = self.filepath
142
143         # change the menu title to the most recently chosen option
144         preset_class = getattr(bpy.types, self.menu_idname)
145         preset_class.bl_label = bpy.path.display_name(basename(filepath))
146
147         # execute the preset using script.python_file_run
148         bpy.ops.script.python_file_run(filepath=filepath)
149         return {'FINISHED'}
150
151
152 class AddPresetRender(AddPresetBase, bpy.types.Operator):
153     '''Add a Render Preset'''
154     bl_idname = "render.preset_add"
155     bl_label = "Add Render Preset"
156     preset_menu = "RENDER_MT_presets"
157
158     preset_defines = [
159         "scene = bpy.context.scene"
160     ]
161
162     preset_values = [
163         "scene.render.field_order",
164         "scene.render.fps",
165         "scene.render.fps_base",
166         "scene.render.pixel_aspect_x",
167         "scene.render.pixel_aspect_y",
168         "scene.render.resolution_percentage",
169         "scene.render.resolution_x",
170         "scene.render.resolution_y",
171         "scene.render.use_fields",
172         "scene.render.use_fields_still",
173     ]
174
175     preset_subdir = "render"
176
177
178 class AddPresetCamera(AddPresetBase, bpy.types.Operator):
179     '''Add a Camera Preset'''
180     bl_idname = "camera.preset_add"
181     bl_label = "Add Camera Preset"
182     preset_menu = "CAMERA_MT_presets"
183
184     preset_defines = [
185         "cam = bpy.context.object.data"
186     ]
187
188     preset_values = [
189         "cam.sensor_x",
190         "cam.sensor_y",
191     ]
192
193     preset_subdir = "camera"
194
195
196 class AddPresetSSS(AddPresetBase, bpy.types.Operator):
197     '''Add a Subsurface Scattering Preset'''
198     bl_idname = "material.sss_preset_add"
199     bl_label = "Add SSS Preset"
200     preset_menu = "MATERIAL_MT_sss_presets"
201
202     preset_defines = [
203         "material = (bpy.context.material.active_node_material if bpy.context.material.active_node_material else bpy.context.material)"
204     ]
205
206     preset_values = [
207         "material.subsurface_scattering.back",
208         "material.subsurface_scattering.color",
209         "material.subsurface_scattering.color_factor",
210         "material.subsurface_scattering.error_threshold",
211         "material.subsurface_scattering.front",
212         "material.subsurface_scattering.ior",
213         "material.subsurface_scattering.radius",
214         "material.subsurface_scattering.scale",
215         "material.subsurface_scattering.texture_factor",
216     ]
217
218     preset_subdir = "sss"
219
220
221 class AddPresetCloth(AddPresetBase, bpy.types.Operator):
222     '''Add a Cloth Preset'''
223     bl_idname = "cloth.preset_add"
224     bl_label = "Add Cloth Preset"
225     preset_menu = "CLOTH_MT_presets"
226
227     preset_defines = [
228         "cloth = bpy.context.cloth"
229     ]
230
231     preset_values = [
232         "cloth.settings.air_damping",
233         "cloth.settings.bending_stiffness",
234         "cloth.settings.mass",
235         "cloth.settings.quality",
236         "cloth.settings.spring_damping",
237         "cloth.settings.structural_stiffness",
238     ]
239
240     preset_subdir = "cloth"
241
242
243 class AddPresetSunSky(AddPresetBase, bpy.types.Operator):
244     '''Add a Sky & Atmosphere Preset'''
245     bl_idname = "lamp.sunsky_preset_add"
246     bl_label = "Add Sunsky Preset"
247     preset_menu = "LAMP_MT_sunsky_presets"
248
249     preset_defines = [
250         "sky = bpy.context.object.data.sky"
251     ]
252
253     preset_values = [
254         "sky.atmosphere_extinction",
255         "sky.atmosphere_inscattering",
256         "sky.atmosphere_turbidity",
257         "sky.backscattered_light",
258         "sky.horizon_brightness",
259         "sky.spread",
260         "sky.sun_brightness",
261         "sky.sun_intensity",
262         "sky.sun_size",
263         "sky.sky_blend",
264         "sky.sky_blend_type",
265         "sky.sky_color_space",
266         "sky.sky_exposure",
267     ]
268
269     preset_subdir = "sunsky"
270
271
272 class AddPresetInteraction(AddPresetBase, bpy.types.Operator):
273     '''Add an Application Interaction Preset'''
274     bl_idname = "wm.interaction_preset_add"
275     bl_label = "Add Interaction Preset"
276     preset_menu = "USERPREF_MT_interaction_presets"
277
278     preset_defines = [
279         "user_preferences = bpy.context.user_preferences"
280     ]
281
282     preset_values = [
283         "user_preferences.edit.use_drag_immediately",
284         "user_preferences.edit.use_insertkey_xyz_to_rgb",
285         "user_preferences.inputs.invert_mouse_zoom",
286         "user_preferences.inputs.select_mouse",
287         "user_preferences.inputs.use_emulate_numpad",
288         "user_preferences.inputs.use_mouse_continuous",
289         "user_preferences.inputs.use_mouse_emulate_3_button",
290         "user_preferences.inputs.view_rotate_method",
291         "user_preferences.inputs.view_zoom_axis",
292         "user_preferences.inputs.view_zoom_method",
293     ]
294
295     preset_subdir = "interaction"
296
297
298 class AddPresetTrackingCamera(AddPresetBase, bpy.types.Operator):
299     '''Add a Tracking Camera Intrinsics  Preset'''
300     bl_idname = "clip.camera_preset_add"
301     bl_label = "Add Camera Preset"
302     preset_menu = "CLIP_MT_camera_presets"
303
304     preset_defines = [
305         "camera = bpy.context.edit_movieclip.tracking.camera"
306     ]
307
308     preset_values = [
309         "camera.sensor_width",
310         "camera.sensor_height",
311         "camera.units",
312         "camera.focal_length",
313         "camera.k1",
314         "camera.k2",
315         "camera.k3"
316     ]
317
318     preset_subdir = "tracking_camera"
319
320
321 class AddPresetKeyconfig(AddPresetBase, bpy.types.Operator):
322     '''Add a Keyconfig Preset'''
323     bl_idname = "wm.keyconfig_preset_add"
324     bl_label = "Add Keyconfig Preset"
325     preset_menu = "USERPREF_MT_keyconfigs"
326     preset_subdir = "keyconfig"
327
328     def add(self, context, filepath):
329         bpy.ops.wm.keyconfig_export(filepath=filepath)
330         bpy.utils.keyconfig_set(filepath)
331
332     def pre_cb(self, context):
333         keyconfigs = bpy.context.window_manager.keyconfigs
334         if self.remove_active:
335             preset_menu_class = getattr(bpy.types, self.preset_menu)
336             preset_menu_class.bl_label = keyconfigs.active.name
337
338     def post_cb(self, context):
339         keyconfigs = bpy.context.window_manager.keyconfigs
340         if self.remove_active:
341             keyconfigs.remove(keyconfigs.active)
342
343
344 class AddPresetOperator(AddPresetBase, bpy.types.Operator):
345     '''Add an Application Interaction Preset'''
346     bl_idname = "wm.operator_preset_add"
347     bl_label = "Operator Preset"
348     preset_menu = "WM_MT_operator_presets"
349
350     operator = bpy.props.StringProperty(name="Operator", maxlen=64, options={'HIDDEN'})
351
352     # XXX, not ideal
353     preset_defines = [
354         "op = bpy.context.space_data.operator",
355     ]
356
357     @property
358     def preset_subdir(self):
359         return AddPresetOperator.operator_path(self.operator)
360
361     @property
362     def preset_values(self):
363         properties_blacklist = bpy.types.Operator.bl_rna.properties.keys()
364
365         prefix, suffix = self.operator.split("_OT_", 1)
366         operator_rna = getattr(getattr(bpy.ops, prefix.lower()), suffix).get_rna().bl_rna
367
368         ret = []
369         for prop_id, prop in operator_rna.properties.items():
370             if (not (prop.is_hidden or prop.is_skip_save)) and prop_id not in properties_blacklist:
371                 ret.append("op.%s" % prop_id)
372
373         return ret
374
375     @staticmethod
376     def operator_path(operator):
377         import os
378         prefix, suffix = operator.split("_OT_", 1)
379         return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix))
380
381
382 class WM_MT_operator_presets(bpy.types.Menu):
383     bl_label = "Operator Presets"
384
385     def draw(self, context):
386         self.operator = context.space_data.operator.bl_idname
387         bpy.types.Menu.draw_preset(self, context)
388
389     @property
390     def preset_subdir(self):
391         return AddPresetOperator.operator_path(self.operator)
392
393     preset_operator = "script.execute_preset"