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