946543cc864f75dd660ca7cbc7d7af214dec69b8
[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-80 compliant>
20
21 import bpy
22 from bpy.types import Menu, Operator
23
24
25 class AddPresetBase():
26     '''Base preset class, only for subclassing
27     subclasses must define
28      - preset_values
29      - preset_subdir '''
30     # bl_idname = "script.preset_base_add"
31     # bl_label = "Add a Python Preset"
32     bl_options = {'REGISTER'}  # only because invoke_props_popup requires.
33
34     name = bpy.props.StringProperty(
35             name="Name",
36             description="Name of the preset, used to make the path name",
37             maxlen=64,
38             )
39     remove_active = bpy.props.BoolProperty(
40             default=False,
41             options={'HIDDEN'},
42             )
43
44     @staticmethod
45     def as_filename(name):  # could reuse for other presets
46         for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
47             name = name.replace(char, '_')
48         return name.lower().strip()
49
50     def execute(self, context):
51         import os
52
53         if hasattr(self, "pre_cb"):
54             self.pre_cb(context)
55
56         preset_menu_class = getattr(bpy.types, self.preset_menu)
57
58         is_xml = getattr(preset_menu_class, "preset_type", None) == 'XML'
59
60         if is_xml:
61             ext = ".xml"
62         else:
63             ext = ".py"
64
65         if not self.remove_active:
66             name = self.name.strip()
67             if not name:
68                 return {'FINISHED'}
69
70             filename = self.as_filename(name)
71
72             target_path = os.path.join("presets", self.preset_subdir)
73             target_path = bpy.utils.user_resource('SCRIPTS',
74                                                   target_path,
75                                                   create=True)
76
77             if not target_path:
78                 self.report({'WARNING'}, "Failed to create presets path")
79                 return {'CANCELLED'}
80
81             filepath = os.path.join(target_path, filename) + ext
82
83             if hasattr(self, "add"):
84                 self.add(context, filepath)
85             else:
86                 print("Writing Preset: %r" % filepath)
87
88                 if is_xml:
89                     import rna_xml
90                     rna_xml.xml_file_write(context,
91                                            filepath,
92                                            preset_menu_class.preset_xml_map)
93                 else:
94                     file_preset = open(filepath, 'w')
95                     file_preset.write("import bpy\n")
96
97                     if hasattr(self, "preset_defines"):
98                         for rna_path in self.preset_defines:
99                             exec(rna_path)
100                             file_preset.write("%s\n" % rna_path)
101                         file_preset.write("\n")
102
103                     for rna_path in self.preset_values:
104                         value = eval(rna_path)
105                         # convert thin wrapped sequences
106                         # to simple lists to repr()
107                         try:
108                             value = value[:]
109                         except:
110                             pass
111
112                         file_preset.write("%s = %r\n" % (rna_path, value))
113
114                     file_preset.close()
115
116             preset_menu_class.bl_label = bpy.path.display_name(filename)
117
118         else:
119             preset_active = preset_menu_class.bl_label
120
121             # fairly sloppy but convenient.
122             filepath = bpy.utils.preset_find(preset_active,
123                                              self.preset_subdir,
124                                              ext=ext)
125
126             if not filepath:
127                 filepath = bpy.utils.preset_find(preset_active,
128                                                  self.preset_subdir,
129                                                  display_name=True,
130                                                  ext=ext)
131
132             if not filepath:
133                 return {'CANCELLED'}
134
135             if hasattr(self, "remove"):
136                 self.remove(context, filepath)
137             else:
138                 try:
139                     os.remove(filepath)
140                 except:
141                     import traceback
142                     traceback.print_exc()
143
144             # XXX, stupid!
145             preset_menu_class.bl_label = "Presets"
146
147         if hasattr(self, "post_cb"):
148             self.post_cb(context)
149
150         return {'FINISHED'}
151
152     def check(self, context):
153         self.name = self.as_filename(self.name.strip())
154
155     def invoke(self, context, event):
156         if not self.remove_active:
157             wm = context.window_manager
158             return wm.invoke_props_dialog(self)
159         else:
160             return self.execute(context)
161
162
163 class ExecutePreset(Operator):
164     '''Execute a preset'''
165     bl_idname = "script.execute_preset"
166     bl_label = "Execute a Python Preset"
167
168     filepath = bpy.props.StringProperty(
169             name="Path",
170             description="Path of the Python file to execute",
171             maxlen=512,
172             )
173     menu_idname = bpy.props.StringProperty(
174             name="Menu ID Name",
175             description="ID name of the menu this was called from",
176             )
177
178     def execute(self, context):
179         from os.path import basename, splitext
180         filepath = self.filepath
181
182         # change the menu title to the most recently chosen option
183         preset_class = getattr(bpy.types, self.menu_idname)
184         preset_class.bl_label = bpy.path.display_name(basename(filepath))
185
186         ext = splitext(filepath)[1].lower()
187
188         # execute the preset using script.python_file_run
189         if ext == ".py":
190             bpy.ops.script.python_file_run(filepath=filepath)
191         elif ext == ".xml":
192             import rna_xml
193             rna_xml.xml_file_run(context,
194                                  filepath,
195                                  preset_class.preset_xml_map)
196         else:
197             self.report({'ERROR'}, "unknown filetype: %r" % ext)
198             return {'CANCELLED '}
199
200         return {'FINISHED'}
201
202
203 class AddPresetRender(AddPresetBase, Operator):
204     '''Add a Render Preset'''
205     bl_idname = "render.preset_add"
206     bl_label = "Add Render Preset"
207     preset_menu = "RENDER_MT_presets"
208
209     preset_defines = [
210         "scene = bpy.context.scene"
211     ]
212
213     preset_values = [
214         "scene.render.field_order",
215         "scene.render.fps",
216         "scene.render.fps_base",
217         "scene.render.pixel_aspect_x",
218         "scene.render.pixel_aspect_y",
219         "scene.render.resolution_percentage",
220         "scene.render.resolution_x",
221         "scene.render.resolution_y",
222         "scene.render.use_fields",
223         "scene.render.use_fields_still",
224     ]
225
226     preset_subdir = "render"
227
228
229 class AddPresetCamera(AddPresetBase, Operator):
230     '''Add a Camera Preset'''
231     bl_idname = "camera.preset_add"
232     bl_label = "Add Camera Preset"
233     preset_menu = "CAMERA_MT_presets"
234
235     preset_defines = [
236         "cam = bpy.context.object.data"
237     ]
238
239     preset_values = [
240         "cam.sensor_width",
241         "cam.sensor_height",
242         "cam.sensor_fit"
243     ]
244
245     preset_subdir = "camera"
246
247
248 class AddPresetSSS(AddPresetBase, Operator):
249     '''Add a Subsurface Scattering Preset'''
250     bl_idname = "material.sss_preset_add"
251     bl_label = "Add SSS Preset"
252     preset_menu = "MATERIAL_MT_sss_presets"
253
254     preset_defines = [
255         ("material = "
256          "bpy.context.material.active_node_material "
257          "if bpy.context.material.active_node_material "
258          "else bpy.context.material")
259     ]
260
261     preset_values = [
262         "material.subsurface_scattering.back",
263         "material.subsurface_scattering.color",
264         "material.subsurface_scattering.color_factor",
265         "material.subsurface_scattering.error_threshold",
266         "material.subsurface_scattering.front",
267         "material.subsurface_scattering.ior",
268         "material.subsurface_scattering.radius",
269         "material.subsurface_scattering.scale",
270         "material.subsurface_scattering.texture_factor",
271     ]
272
273     preset_subdir = "sss"
274
275
276 class AddPresetCloth(AddPresetBase, Operator):
277     '''Add a Cloth Preset'''
278     bl_idname = "cloth.preset_add"
279     bl_label = "Add Cloth Preset"
280     preset_menu = "CLOTH_MT_presets"
281
282     preset_defines = [
283         "cloth = bpy.context.cloth"
284     ]
285
286     preset_values = [
287         "cloth.settings.air_damping",
288         "cloth.settings.bending_stiffness",
289         "cloth.settings.mass",
290         "cloth.settings.quality",
291         "cloth.settings.spring_damping",
292         "cloth.settings.structural_stiffness",
293     ]
294
295     preset_subdir = "cloth"
296
297
298 class AddPresetSunSky(AddPresetBase, Operator):
299     '''Add a Sky & Atmosphere Preset'''
300     bl_idname = "lamp.sunsky_preset_add"
301     bl_label = "Add Sunsky Preset"
302     preset_menu = "LAMP_MT_sunsky_presets"
303
304     preset_defines = [
305         "sky = bpy.context.object.data.sky"
306     ]
307
308     preset_values = [
309         "sky.atmosphere_extinction",
310         "sky.atmosphere_inscattering",
311         "sky.atmosphere_turbidity",
312         "sky.backscattered_light",
313         "sky.horizon_brightness",
314         "sky.spread",
315         "sky.sun_brightness",
316         "sky.sun_intensity",
317         "sky.sun_size",
318         "sky.sky_blend",
319         "sky.sky_blend_type",
320         "sky.sky_color_space",
321         "sky.sky_exposure",
322     ]
323
324     preset_subdir = "sunsky"
325
326
327 class AddPresetInteraction(AddPresetBase, Operator):
328     '''Add an Application Interaction Preset'''
329     bl_idname = "wm.interaction_preset_add"
330     bl_label = "Add Interaction Preset"
331     preset_menu = "USERPREF_MT_interaction_presets"
332
333     preset_defines = [
334         "user_preferences = bpy.context.user_preferences"
335     ]
336
337     preset_values = [
338         "user_preferences.edit.use_drag_immediately",
339         "user_preferences.edit.use_insertkey_xyz_to_rgb",
340         "user_preferences.inputs.invert_mouse_zoom",
341         "user_preferences.inputs.select_mouse",
342         "user_preferences.inputs.use_emulate_numpad",
343         "user_preferences.inputs.use_mouse_continuous",
344         "user_preferences.inputs.use_mouse_emulate_3_button",
345         "user_preferences.inputs.view_rotate_method",
346         "user_preferences.inputs.view_zoom_axis",
347         "user_preferences.inputs.view_zoom_method",
348     ]
349
350     preset_subdir = "interaction"
351
352
353 class AddPresetTrackingCamera(AddPresetBase, Operator):
354     '''Add a Tracking Camera Intrinsics  Preset'''
355     bl_idname = "clip.camera_preset_add"
356     bl_label = "Add Camera Preset"
357     preset_menu = "CLIP_MT_camera_presets"
358
359     preset_defines = [
360         "camera = bpy.context.edit_movieclip.tracking.camera"
361     ]
362
363     preset_values = [
364         "camera.sensor_width",
365         "camera.units",
366         "camera.focal_length",
367         "camera.pixel_aspect",
368         "camera.k1",
369         "camera.k2",
370         "camera.k3"
371     ]
372
373     preset_subdir = "tracking_camera"
374
375
376 class AddPresetTrackingTrackColor(AddPresetBase, Operator):
377     '''Add a Clip Track Color Preset'''
378     bl_idname = "clip.track_color_preset_add"
379     bl_label = "Add Track Color Preset"
380     preset_menu = "CLIP_MT_track_color_presets"
381
382     preset_defines = [
383         "track = bpy.context.edit_movieclip.tracking.tracks.active"
384     ]
385
386     preset_values = [
387         "track.color",
388         "track.use_custom_color"
389     ]
390
391     preset_subdir = "tracking_track_color"
392
393
394 class AddPresetTrackingSettings(AddPresetBase, Operator):
395     '''Add a motion tracking settings preset'''
396     bl_idname = "clip.tracking_settings_preset_add"
397     bl_label = "Add Tracking Settings Preset"
398     preset_menu = "CLIP_MT_tracking_settings_presets"
399
400     preset_defines = [
401         "settings = bpy.context.edit_movieclip.tracking.settings"
402     ]
403
404     preset_values = [
405         "settings.default_tracker",
406         "settings.default_pyramid_levels",
407         "settings.default_correlation_min",
408         "settings.default_pattern_size",
409         "settings.default_search_size",
410         "settings.default_frames_limit",
411         "settings.default_pattern_match",
412         "settings.default_margin",
413         "settings.use_default_red_channel",
414         "settings.use_default_green_channel",
415         "settings.use_default_blue_channel"
416     ]
417
418     preset_subdir = "tracking_settings"
419
420
421 class AddPresetInterfaceTheme(AddPresetBase, Operator):
422     '''Add a theme preset'''
423     bl_idname = "wm.interface_theme_preset_add"
424     bl_label = "Add Tracking Settings Preset"
425     preset_menu = "USERPREF_MT_interface_theme_presets"
426     preset_subdir = "interface_theme"
427
428
429 class AddPresetKeyconfig(AddPresetBase, Operator):
430     '''Add a Key-config Preset'''
431     bl_idname = "wm.keyconfig_preset_add"
432     bl_label = "Add Keyconfig Preset"
433     preset_menu = "USERPREF_MT_keyconfigs"
434     preset_subdir = "keyconfig"
435
436     def add(self, context, filepath):
437         bpy.ops.wm.keyconfig_export(filepath=filepath)
438         bpy.utils.keyconfig_set(filepath)
439
440     def pre_cb(self, context):
441         keyconfigs = bpy.context.window_manager.keyconfigs
442         if self.remove_active:
443             preset_menu_class = getattr(bpy.types, self.preset_menu)
444             preset_menu_class.bl_label = keyconfigs.active.name
445
446     def post_cb(self, context):
447         keyconfigs = bpy.context.window_manager.keyconfigs
448         if self.remove_active:
449             keyconfigs.remove(keyconfigs.active)
450
451
452 class AddPresetOperator(AddPresetBase, Operator):
453     '''Add an Application Interaction Preset'''
454     bl_idname = "wm.operator_preset_add"
455     bl_label = "Operator Preset"
456     preset_menu = "WM_MT_operator_presets"
457
458     operator = bpy.props.StringProperty(
459             name="Operator",
460             maxlen=64,
461             options={'HIDDEN'},
462             )
463
464     preset_defines = [
465         "op = bpy.context.active_operator",
466     ]
467
468     @property
469     def preset_subdir(self):
470         return AddPresetOperator.operator_path(self.operator)
471
472     @property
473     def preset_values(self):
474         properties_blacklist = Operator.bl_rna.properties.keys()
475
476         prefix, suffix = self.operator.split("_OT_", 1)
477         op = getattr(getattr(bpy.ops, prefix.lower()), suffix)
478         operator_rna = op.get_rna().bl_rna
479         del op
480
481         ret = []
482         for prop_id, prop in operator_rna.properties.items():
483             if not (prop.is_hidden or prop.is_skip_save):
484                 if prop_id not in properties_blacklist:
485                     ret.append("op.%s" % prop_id)
486
487         return ret
488
489     @staticmethod
490     def operator_path(operator):
491         import os
492         prefix, suffix = operator.split("_OT_", 1)
493         return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix))
494
495
496 class WM_MT_operator_presets(Menu):
497     bl_label = "Operator Presets"
498
499     def draw(self, context):
500         self.operator = context.active_operator.bl_idname
501         Menu.draw_preset(self, context)
502
503     @property
504     def preset_subdir(self):
505         return AddPresetOperator.operator_path(self.operator)
506
507     preset_operator = "script.execute_preset"