Clear executable flag from addon scripts
[blender-addons-contrib.git] / scene_amaranth_toolset.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 bl_info = {
20     "name": "Amaranth Toolset",
21     "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin",
22     "version": (0, 7, 3),
23     "blender": (2, 69, 0),
24     "location": "Scene Properties > Amaranth Toolset Panel",
25     "description": "A collection of tools and settings to improve productivity",
26     "warning": "",
27     "wiki_url": "http://pablovazquez.org/amaranth",
28     "tracker_url": "",
29     "category": "Scene"}
30
31
32 import bpy
33 import bmesh
34 from bpy.types import Operator, AddonPreferences, Panel
35 from bpy.props import BoolProperty
36 from mathutils import Vector
37 from bpy.app.handlers import persistent
38
39 # Preferences
40 class AmaranthToolsetPreferences(AddonPreferences):
41     bl_idname = __name__
42     use_frame_current = BoolProperty(
43             name="Current Frame Slider",
44             description="Set the current frame from the Specials menu in the 3D View",
45             default=True,
46             )
47     use_file_save_reload = BoolProperty(
48             name="Save & Reload File",
49             description="File menu > Save & Reload, or Ctrl + Shift + W",
50             default=True,
51             )
52
53     use_scene_refresh = BoolProperty(
54             name="Refresh Scene",
55             description="Specials Menu [W], or hit F5",
56             default=True,
57             )
58     use_timeline_extra_info = BoolProperty(
59             name="Timeline Extra Info",
60             description="Timeline Header",
61             default=True,
62             )
63     use_image_node_display = BoolProperty(
64             name="Active Image Node in Editor",
65             description="Display active node image in image editor",
66             default=True,
67             )
68     use_scene_stats = BoolProperty(
69             name="Extra Scene Statistics",
70             description="Display extra scene statistics in Info editor's header",
71             default=True,
72             )
73
74
75     def draw(self, context):
76         layout = self.layout
77
78         layout.label(
79             text="Here you can enable or disable specific tools, "
80                  "in case they interfere with others or are just plain annoying")
81
82         split = layout.split(percentage=0.25)
83
84         col = split.column()
85         sub = col.column(align=True)
86         sub.label(text="3D View", icon="VIEW3D")
87         sub.prop(self, "use_frame_current")
88         sub.prop(self, "use_scene_refresh")
89
90         sub.separator()
91
92         sub.label(text="General", icon="SCENE_DATA")
93         sub.prop(self, "use_file_save_reload")
94         sub.prop(self, "use_timeline_extra_info")
95         sub.prop(self, "use_scene_stats")
96
97         sub.separator()
98
99         sub.label(text="Nodes Editor", icon="NODETREE")
100         sub.prop(self, "use_image_node_display")
101
102         col = split.column()
103         sub = col.column(align=True)
104         sub.label(text="")
105         sub.label(
106             text="Set the current frame from the Specials menu in the 3D View [W]")
107         sub.label(
108             text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
109
110         sub.separator()
111         sub.label(text="") # General
112         sub.label(
113             text="Quickly save and reload the current file (no warning!). "
114                  "File menu or Ctrl+Shift+W")
115         sub.label(
116             text="SMPTE Timecode and frames left/ahead on Timeline's header")
117         sub.label(
118             text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
119
120         sub.separator()
121         sub.label(text="") # Nodes
122         sub.label(
123             text="When selecting an Image node, display it on the Image editor "
124                  "(if any)")
125
126 # Properties
127 def init_properties():
128
129     scene = bpy.types.Scene
130     node = bpy.types.Node
131     nodes_compo = bpy.types.CompositorNodeTree
132
133     scene.use_unsimplify_render = bpy.props.BoolProperty(
134         default=False,
135         name="Unsimplify Render",
136         description="Disable Simplify during render")
137     scene.simplify_status = bpy.props.BoolProperty(default=False)
138
139     node.use_matching_indices = bpy.props.BoolProperty(
140         default=True,
141         description="If disabled, display all available indices")
142
143     test_items = [
144         ("ALL", "All Types", "", 0),
145         ("BLUR", "Blur", "", 1),
146         ("BOKEHBLUR", "Bokeh Blur", "", 2),
147         ("VECBLUR", "Vector Blur", "", 3),
148         ("DEFOCUS", "Defocus", "", 4),
149         ("R_LAYERS", "Render Layer", "", 5)
150         ]
151
152     nodes_compo.types = bpy.props.EnumProperty(
153         items=test_items, name = "Types")
154
155     nodes_compo.toggle_mute = bpy.props.BoolProperty(default=False)
156     node.status = bpy.props.BoolProperty(default=False)
157
158
159 def clear_properties():
160     props = (
161         "use_unsimplify_render",
162         "simplify_status",
163         "use_matching_indices",
164         "use_simplify_nodes_vector",
165         "status"
166     )
167
168     wm = bpy.context.window_manager
169     for p in props:
170         if p in wm:
171             del wm[p]
172
173 # FEATURE: Refresh Scene!
174 class SCENE_OT_refresh(Operator):
175     """Refresh the current scene"""
176     bl_idname = "scene.refresh"
177     bl_label = "Refresh!"
178
179     def execute(self, context):
180         preferences = context.user_preferences.addons[__name__].preferences
181         scene = context.scene
182
183         if preferences.use_scene_refresh:
184             # Changing the frame is usually the best way to go
185             scene.frame_current = scene.frame_current
186             self.report({"INFO"}, "Scene Refreshed!")
187
188         return {'FINISHED'}
189
190 def button_refresh(self, context):
191
192     preferences = context.user_preferences.addons[__name__].preferences
193
194     if preferences.use_scene_refresh:
195         self.layout.separator()
196         self.layout.operator(
197             SCENE_OT_refresh.bl_idname,
198             text="Refresh!",
199             icon='FILE_REFRESH')
200 # // FEATURE: Refresh Scene!
201
202 # FEATURE: Save & Reload
203 def save_reload(self, context, path):
204
205     if path:
206         bpy.ops.wm.save_mainfile()
207         self.report({'INFO'}, "Saved & Reloaded")
208         bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
209     else:
210         bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
211
212 class WM_OT_save_reload(Operator):
213     """Save and Reload the current blend file"""
214     bl_idname = "wm.save_reload"
215     bl_label = "Save & Reload"
216
217     def execute(self, context):
218
219         path = bpy.data.filepath
220         save_reload(self, context, path)
221         return {'FINISHED'}
222
223 def button_save_reload(self, context):
224
225     preferences = context.user_preferences.addons[__name__].preferences
226
227     if preferences.use_file_save_reload:
228         self.layout.separator()
229         self.layout.operator(
230             WM_OT_save_reload.bl_idname,
231             text="Save & Reload",
232             icon='FILE_REFRESH')
233 # // FEATURE: Save & Reload
234
235 # FEATURE: Current Frame
236 def button_frame_current(self, context):
237
238     preferences = context.user_preferences.addons[__name__].preferences
239     scene = context.scene
240
241     if preferences.use_frame_current:
242         self.layout.separator()
243         self.layout.prop(
244             scene, "frame_current",
245             text="Set Current Frame")
246 # // FEATURE: Current Frame
247
248 # FEATURE: Timeline Time + Frames Left
249 def label_timeline_extra_info(self, context):
250
251     preferences = context.user_preferences.addons[__name__].preferences
252     layout = self.layout
253     scene = context.scene
254
255     if preferences.use_timeline_extra_info:
256         row = layout.row(align=True)
257
258         # Check for preview range
259         frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
260         frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
261
262         row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
263                         bpy.utils.smpte_from_frame(frame_end - frame_start)))
264
265         if (scene.frame_current > frame_end):
266             row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
267         elif (scene.frame_current == frame_start):
268             row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
269         elif (scene.frame_current == frame_end):
270             row.label(text="%s End Frame" % scene.frame_current)
271         else:
272             row.label(text="%s Frames Left" % (frame_end - scene.frame_current))
273
274 # // FEATURE: Timeline Time + Frames Left
275
276 # FEATURE: Directory Current Blend
277 class FILE_OT_directory_current_blend(Operator):
278     """Go to the directory of the currently open blend file"""
279     bl_idname = "file.directory_current_blend"
280     bl_label = "Current Blend's Folder"
281
282     def execute(self, context):
283         bpy.ops.file.select_bookmark(dir='//')
284         return {'FINISHED'}
285
286 def button_directory_current_blend(self, context):
287
288     if bpy.data.filepath:
289         self.layout.operator(
290             FILE_OT_directory_current_blend.bl_idname,
291             text="Current Blend's Folder",
292             icon='APPEND_BLEND')
293 # // FEATURE: Directory Current Blend
294
295 # FEATURE: Libraries panel on file browser
296 class FILE_PT_libraries(Panel):
297     bl_space_type = 'FILE_BROWSER'
298     bl_region_type = 'CHANNELS'
299     bl_label = "Libraries"
300
301     def draw(self, context):
302         layout = self.layout
303
304         libs = bpy.data.libraries
305         libslist = []
306
307         # Build the list of folders from libraries
308         import os
309
310         for lib in libs:
311             directory_name = os.path.dirname(lib.filepath)
312             libslist.append(directory_name)
313
314         # Remove duplicates and sort by name
315         libslist = set(libslist)
316         libslist = sorted(libslist)
317
318         # Draw the box with libs
319
320         row = layout.row()
321         box = row.box()
322
323         if libslist:
324             for filepath in libslist:
325                 if filepath != '//':
326                     split = box.split(percentage=0.85)
327                     col = split.column()
328                     sub = col.column(align=True)
329                     sub.label(text=filepath)
330
331                     col = split.column()
332                     sub = col.column(align=True)
333                     props = sub.operator(
334                         FILE_OT_directory_go_to.bl_idname,
335                         text="", icon="BOOKMARKS")
336                     props.filepath = filepath
337         else:
338             box.label(text='No libraries loaded')
339
340 class FILE_OT_directory_go_to(Operator):
341     """Go to this library's directory"""
342     bl_idname = "file.directory_go_to"
343     bl_label = "Go To"
344
345     filepath = bpy.props.StringProperty(subtype="FILE_PATH")
346
347     def execute(self, context):
348
349         bpy.ops.file.select_bookmark(dir=self.filepath)
350         return {'FINISHED'}
351
352 # FEATURE: Node Templates
353 class NODE_OT_AddTemplateVignette(Operator):
354     bl_idname = "node.template_add_vignette"
355     bl_label = "Add Vignette"
356     bl_description = "Add a vignette effect"
357     bl_options = {'REGISTER', 'UNDO'}
358
359     @classmethod
360     def poll(cls, context):
361         space = context.space_data
362         return space.type == 'NODE_EDITOR' \
363                 and space.node_tree is not None \
364                 and space.tree_type == 'CompositorNodeTree'
365
366     # used as reference the setup scene script from master nazgul
367     def _setupNodes(self, context):
368         scene = context.scene
369         space = context.space_data
370         tree = scene.node_tree
371
372         bpy.ops.node.select_all(action='DESELECT')
373
374         ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
375         ellipse.width = 0.8
376         ellipse.height = 0.4
377         blur = tree.nodes.new(type='CompositorNodeBlur')
378         blur.use_relative = True
379         blur.factor_x = 30
380         blur.factor_y = 50
381         ramp = tree.nodes.new(type='CompositorNodeValToRGB')
382         ramp.color_ramp.interpolation = 'B_SPLINE'
383         ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
384
385         overlay = tree.nodes.new(type='CompositorNodeMixRGB')
386         overlay.blend_type = 'OVERLAY'
387         overlay.inputs[0].default_value = 0.8
388         overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
389
390         tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
391         tree.links.new(blur.outputs["Image"],ramp.inputs[0])
392         tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
393
394         if tree.nodes.active:
395             blur.location = tree.nodes.active.location
396             blur.location += Vector((330.0, -250.0))
397         else:
398             blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
399
400         ellipse.location = blur.location
401         ellipse.location += Vector((-300.0, 0))
402
403         ramp.location = blur.location
404         ramp.location += Vector((175.0, 0))
405
406         overlay.location = ramp.location
407         overlay.location += Vector((240.0, 275.0))
408
409         for node in {ellipse, blur, ramp, overlay}:
410             node.select = True
411             node.show_preview = False
412
413         bpy.ops.node.join()
414
415         frame = ellipse.parent
416         frame.label = 'Vignette'
417         frame.use_custom_color = True
418         frame.color = (0.783538, 0.0241576, 0.0802198)
419
420         overlay.parent = None
421         overlay.label = 'Vignette Overlay'
422
423     def execute(self, context):
424         self._setupNodes(context)
425
426         return {'FINISHED'}
427
428 # Node Templates Menu
429 class NODE_MT_amaranth_templates(bpy.types.Menu):
430     bl_idname = 'NODE_MT_amaranth_templates'
431     bl_space_type = 'NODE_EDITOR'
432     bl_label = "Templates"
433     bl_description = "List of Amaranth Templates"
434
435     def draw(self, context):
436         layout = self.layout
437         layout.operator(
438             NODE_OT_AddTemplateVignette.bl_idname,
439             text="Vignette",
440             icon='COLOR')
441
442 def node_templates_pulldown(self, context):
443
444     if context.space_data.tree_type == 'CompositorNodeTree':
445         layout = self.layout
446         row = layout.row(align=True)
447         row.scale_x = 1.3
448         row.menu("NODE_MT_amaranth_templates",
449             icon="RADIO")
450 # // FEATURE: Node Templates
451
452 def node_stats(self,context):
453     if context.scene.node_tree:
454         tree_type = context.space_data.tree_type
455         nodes = context.scene.node_tree.nodes
456         nodes_total = len(nodes.keys())
457         nodes_selected = 0
458         for n in nodes:
459             if n.select:
460                 nodes_selected = nodes_selected + 1
461
462         if tree_type == 'CompositorNodeTree':
463             layout = self.layout
464             row = layout.row(align=True)
465             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
466
467 # FEATURE: Simplify Compo Nodes
468 class NODE_PT_simplify(bpy.types.Panel):
469     '''Simplify Compositor Panel'''
470     bl_space_type = 'NODE_EDITOR'
471     bl_region_type = 'UI'
472     bl_label = 'Simplify'
473     bl_options = {'DEFAULT_CLOSED'}
474
475     @classmethod
476     def poll(cls, context):
477         space = context.space_data
478         return space.type == 'NODE_EDITOR' \
479                 and space.node_tree is not None \
480                 and space.tree_type == 'CompositorNodeTree'
481
482     def draw(self, context):
483         layout = self.layout
484         node_tree = context.scene.node_tree
485
486         if node_tree is not None:
487             layout.prop(node_tree, 'types')
488             layout.operator(NODE_OT_toggle_mute.bl_idname,
489                 text="Turn On" if node_tree.toggle_mute else "Turn Off",
490                 icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
491
492             if node_tree.types == 'VECBLUR':
493                 layout.label(text="This will also toggle the Vector pass {}".format(
494                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
495
496 class NODE_OT_toggle_mute(Operator):
497     """"""
498     bl_idname = "node.toggle_mute"
499     bl_label = "Toggle Mute"
500
501     def execute(self, context):
502         scene = context.scene
503         node_tree = scene.node_tree
504         node_type = node_tree.types
505         rlayers = scene.render
506
507         if not 'amaranth_pass_vector' in scene.keys():
508             scene['amaranth_pass_vector'] = []
509
510         #can't extend() the list, so make a dummy one
511         pass_vector = scene['amaranth_pass_vector']
512
513         if not pass_vector:
514             pass_vector = []
515
516         if node_tree.toggle_mute:
517             for node in node_tree.nodes:
518                 if node_type == 'ALL':
519                     node.mute = node.status
520                 if node.type == node_type:
521                     node.mute = node.status
522                 if node_type == 'VECBLUR':
523                     for layer in rlayers.layers:
524                         if layer.name in pass_vector:
525                             layer.use_pass_vector = True
526                             pass_vector.remove(layer.name)
527
528                 node_tree.toggle_mute = False
529
530         else:
531             for node in node_tree.nodes:
532                 if node_type == 'ALL':
533                     node.mute = True
534                 if node.type == node_type:
535                     node.status = node.mute
536                     node.mute = True
537                 if node_type == 'VECBLUR':
538                     for layer in rlayers.layers:
539                         if layer.use_pass_vector:
540                             pass_vector.append(layer.name)
541                             layer.use_pass_vector = False
542                             pass
543
544                 node_tree.toggle_mute = True
545
546         # Write back to the custom prop
547         pass_vector = sorted(set(pass_vector))
548         scene['amaranth_pass_vector'] = pass_vector
549
550         return {'FINISHED'}
551
552
553 # FEATURE: OB/MA ID panel in Node Editor
554 class NODE_PT_indices(bpy.types.Panel):
555     '''Object / Material Indices Panel'''
556     bl_space_type = 'NODE_EDITOR'
557     bl_region_type = 'UI'
558     bl_label = 'Object / Material Indices'
559     bl_options = {'DEFAULT_CLOSED'}
560
561     @classmethod
562     def poll(cls, context):
563         node = context.active_node
564         return node and node.type == 'ID_MASK'
565
566     def draw(self, context):
567         layout = self.layout
568
569         objects = bpy.data.objects
570         materials = bpy.data.materials
571         node = context.active_node
572
573         show_ob_id = False
574         show_ma_id = False
575         matching_ids = False
576
577         if context.active_object:
578             ob_act = context.active_object
579         else:
580             ob_act = False
581
582         for ob in objects:
583             if ob and ob.pass_index > 0:
584                 show_ob_id = True
585         for ma in materials:
586             if ma and ma.pass_index > 0:
587                 show_ma_id = True
588         row = layout.row(align=True)
589         row.prop(node, 'index', text="Mask Index")
590         row.prop(node, 'use_matching_indices', text="Only Matching IDs")
591
592         layout.separator()
593
594         if not show_ob_id and not show_ma_id:
595             layout.label(text="No objects or materials indices so far.", icon="INFO")
596
597         if show_ob_id:
598             split = layout.split()
599             col = split.column()
600             col.label(text="Object Name")
601             split.label(text="ID Number")
602             row = layout.row()
603             for ob in objects:
604                 icon = "OUTLINER_DATA_" + ob.type
605                 if ob.library:
606                     icon = "LIBRARY_DATA_DIRECT"
607                 elif ob.is_library_indirect:
608                     icon = "LIBRARY_DATA_INDIRECT"
609
610                 if ob and node.use_matching_indices \
611                       and ob.pass_index == node.index \
612                       and ob.pass_index != 0:
613                     matching_ids = True
614                     row.label(
615                       text="[{}]".format(ob.name)
616                           if ob_act and ob.name == ob_act.name else ob.name,
617                       icon=icon)
618                     row.label(text="%s" % ob.pass_index)
619                     row = layout.row()
620
621                 elif ob and not node.use_matching_indices \
622                         and ob.pass_index > 0:
623
624                     matching_ids = True
625                     row.label(
626                       text="[{}]".format(ob.name)
627                           if ob_act and ob.name == ob_act.name else ob.name,
628                       icon=icon)
629                     row.label(text="%s" % ob.pass_index)
630                     row = layout.row()
631
632             if node.use_matching_indices and not matching_ids:
633                 row.label(text="No objects with ID %s" % node.index, icon="INFO")
634
635             layout.separator()
636
637         if show_ma_id:
638             split = layout.split()
639             col = split.column()
640             col.label(text="Material Name")
641             split.label(text="ID Number")
642             row = layout.row()
643
644             for ma in materials:
645                 icon = "BLANK1"
646                 if ma.use_nodes:
647                     icon = "NODETREE"
648                 elif ma.library:
649                     icon = "LIBRARY_DATA_DIRECT"
650                     if ma.is_library_indirect:
651                         icon = "LIBRARY_DATA_INDIRECT"
652
653                 if ma and node.use_matching_indices \
654                       and ma.pass_index == node.index \
655                       and ma.pass_index != 0:
656                     matching_ids = True
657                     row.label(text="%s" % ma.name, icon=icon)
658                     row.label(text="%s" % ma.pass_index)
659                     row = layout.row()
660
661                 elif ma and not node.use_matching_indices \
662                         and ma.pass_index > 0:
663
664                     matching_ids = True
665                     row.label(text="%s" % ma.name, icon=icon)
666                     row.label(text="%s" % ma.pass_index)
667                     row = layout.row()
668
669             if node.use_matching_indices and not matching_ids:
670                 row.label(text="No materials with ID %s" % node.index, icon="INFO")
671
672
673 # // FEATURE: OB/MA ID panel in Node Editor
674
675 # FEATURE: Unsimplify on render
676 @persistent
677 def unsimplify_render_pre(scene):
678     render = scene.render
679     scene.simplify_status = render.use_simplify
680
681     if scene.use_unsimplify_render:
682         render.use_simplify = False
683
684 @persistent
685 def unsimplify_render_post(scene):
686     render = scene.render
687     render.use_simplify = scene.simplify_status
688
689 def unsimplify_ui(self,context):
690     scene = bpy.context.scene
691     self.layout.prop(scene, 'use_unsimplify_render')
692 # //FEATURE: Unsimplify on render
693
694 # FEATURE: Extra Info Stats
695 def stats_scene(self, context):
696
697     preferences = context.user_preferences.addons[__name__].preferences
698
699     if preferences.use_scene_stats:
700         scenes_count = str(len(bpy.data.scenes))
701         cameras_count = str(len(bpy.data.cameras))
702         cameras_selected = 0
703         meshlights = 0
704         meshlights_visible = 0
705
706         for ob in context.scene.objects:
707             if ob.material_slots:
708                 for ma in ob.material_slots:
709                     if ma.material:
710                         if ma.material.node_tree:
711                             for no in ma.material.node_tree.nodes:
712                                 if no.type == 'EMISSION':
713                                     meshlights = meshlights + 1
714                                     if ob in context.visible_objects:
715                                         meshlights_visible = meshlights_visible + 1
716                                     break
717             if ob in context.selected_objects:
718                 if ob.type == 'CAMERA':
719                     cameras_selected = cameras_selected + 1
720
721         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
722
723         row = self.layout.row(align=True)
724         row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
725                    scenes_count, cameras_selected, cameras_count,
726                    meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
727
728 # //FEATURE: Extra Info Stats
729
730 # FEATURE: Camera Bounds as Render Border
731 class VIEW3D_OT_render_border_camera(Operator):
732     """Set camera bounds as render border"""
733     bl_idname = "view3d.render_border_camera"
734     bl_label = "Camera as Render Border"
735
736     @classmethod
737     def poll(cls, context):
738         return context.space_data.region_3d.view_perspective == 'CAMERA'
739
740     def execute(self, context):
741         render = context.scene.render
742         render.use_border = True
743         render.border_min_x = 0
744         render.border_min_y = 0
745         render.border_max_x = 1
746         render.border_max_y = 1
747
748         return {'FINISHED'}
749
750 def button_render_border_camera(self, context):
751
752     view3d = context.space_data.region_3d
753
754     if view3d.view_perspective == 'CAMERA':
755         layout = self.layout
756         layout.separator()
757         layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
758                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
759
760 # //FEATURE: Camera Bounds as Render Border
761
762 # FEATURE: Passepartout options on W menu
763 def button_camera_passepartout(self, context):
764
765     view3d = context.space_data.region_3d
766     cam = context.scene.camera.data
767
768     if view3d.view_perspective == 'CAMERA':
769         layout = self.layout
770         if cam.show_passepartout:
771             layout.prop(cam, "passepartout_alpha", text="Passepartout")
772         else:
773             layout.prop(cam, "show_passepartout")
774
775 # FEATURE: Show Only Render with Alt+Shift+Z
776 class VIEW3D_OT_show_only_render(Operator):
777     bl_idname = "view3d.show_only_render"
778     bl_label = "Show Only Render"
779
780     def execute(self, context):
781         space = bpy.context.space_data
782
783         if space.show_only_render:
784             space.show_only_render = False
785         else:
786             space.show_only_render = True
787         return {'FINISHED'}
788
789
790 # FEATURE: Display Active Image Node on Image Editor
791 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
792 image_nodes = {"CompositorNodeImage",
793                "ShaderNodeTexImage",
794                "ShaderNodeTexEnvironment"}
795
796 class NODE_OT_show_active_node_image(Operator):
797     """Show active image node image in the image editor"""
798     bl_idname = "node.show_active_node_image"
799     bl_label = "Show Active Node Node"
800     bl_options = {'UNDO'}
801
802     def execute(self, context):
803         preferences = context.user_preferences.addons[__name__].preferences
804         if preferences.use_image_node_display:
805             if context.active_node:
806                 active_node = context.active_node
807                 if active_node.bl_idname in image_nodes and active_node.image:
808                     for area in context.screen.areas:
809                         if area.type == "IMAGE_EDITOR":
810                             for space in area.spaces:
811                                 if space.type == "IMAGE_EDITOR":
812                                     space.image = active_node.image
813                             break
814
815         return {'FINISHED'}
816 # // FEATURE: Display Active Image Node on Image Editor
817
818 # FEATURE: Select Meshlights
819 class OBJECT_OT_select_meshlights(Operator):
820     """Select light emitting meshes"""
821     bl_idname = "object.select_meshlights"
822     bl_label = "Select Meshlights"
823     bl_options = {'UNDO'}
824
825     @classmethod
826     def poll(cls, context):
827         return context.scene.render.engine == 'CYCLES'
828
829     def execute(self, context):
830         # Deselect everything first
831         bpy.ops.object.select_all(action='DESELECT')
832
833         for ob in context.scene.objects:
834             if ob.material_slots:
835                 for ma in ob.material_slots:
836                     if ma.material:
837                         if ma.material.node_tree:
838                             for no in ma.material.node_tree.nodes:
839                                 if no.type == 'EMISSION':
840                                     ob.select = True
841                                     context.scene.objects.active = ob
842
843         if not context.selected_objects and not context.scene.objects.active:
844             self.report({'INFO'}, "No meshlights to select")
845
846         return {'FINISHED'}
847
848 def button_select_meshlights(self, context):
849
850     if context.scene.render.engine == 'CYCLES':
851         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
852 # // FEATURE: Select Meshlights
853
854 # FEATURE: Cycles Viewport Extra Settings
855 def material_cycles_settings_extra(self, context):
856
857     layout = self.layout
858     col = layout.column()
859     row = col.row(align=True)
860
861     obj = context.object
862     mat = context.material
863     if obj.type == 'MESH':
864         row.prop(obj, "show_transparent", text="Viewport Alpha")
865         row.active = obj.show_transparent
866         row.prop(mat, "alpha", text="Alpha")
867 # // FEATURE: Cycles Viewport Extra Settings
868
869 # FEATURE: Particles Material indicator
870 def particles_material_info(self, context):
871
872     layout = self.layout
873
874     ob = context.object
875     psys = context.particle_system
876
877     mats = len(ob.material_slots)
878
879
880     if ob.material_slots:
881         if psys.settings.material <= len(ob.material_slots) \
882         and ob.material_slots[psys.settings.material-1].name == "":
883             layout.label(text="No material on this slot", icon="MATSPHERE")
884         else:
885             layout.label(
886                 text="%s" % ob.material_slots[psys.settings.material-1].name \
887                     if psys.settings.material <= mats \
888                     else "No material with this index{}".format( \
889                         ". Using %s" % ob.material_slots[mats-1].name \
890                         if ob.material_slots[mats-1].name != "" else ""),
891                 icon="MATERIAL_DATA")
892 # // FEATURE: Particles Material indicator
893
894 # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
895 class MESH_OT_find_asymmetric(Operator):
896     """
897     Find asymmetric vertices
898     """
899
900     bl_idname = "mesh.find_asymmetric"
901     bl_label = "Find Asymmetric"
902     bl_options = {'UNDO', 'REGISTER'}
903
904     @classmethod
905     def poll(cls, context):
906         object = context.object
907         if object:
908             return object.mode == 'EDIT' and object.type == 'MESH'
909         return False
910
911     def execute(self, context):
912         threshold = 1e-6
913
914         object = context.object
915         bm = bmesh.from_edit_mesh(object.data)
916
917         # Deselect all the vertices
918         for v in bm.verts:
919             v.select = False
920
921         for v1 in bm.verts:
922             if abs(v1.co[0]) < threshold:
923                 continue
924
925             mirror_found = False
926             for v2 in bm.verts:
927                 if v1 == v2:
928                     continue
929                 if v1.co[0] * v2.co[0] > 0.0:
930                     continue
931
932                 mirror_coord = Vector(v2.co)
933                 mirror_coord[0] *= -1
934                 if (mirror_coord - v1.co).length_squared < threshold:
935                     mirror_found = True
936                     break
937             if not mirror_found:
938                 v1.select = True
939
940         bm.select_flush_mode()
941
942         bmesh.update_edit_mesh(object.data)
943
944         return {'FINISHED'}
945
946 class MESH_OT_make_symmetric(Operator):
947     """
948     Make symmetric
949     """
950
951     bl_idname = "mesh.make_symmetric"
952     bl_label = "Make Symmetric"
953     bl_options = {'UNDO', 'REGISTER'}
954
955     @classmethod
956     def poll(cls, context):
957         object = context.object
958         if object:
959             return object.mode == 'EDIT' and object.type == 'MESH'
960         return False
961
962     def execute(self, context):
963         threshold = 1e-6
964
965         object = context.object
966         bm = bmesh.from_edit_mesh(object.data)
967
968         for v1 in bm.verts:
969             if v1.co[0] < threshold:
970                 continue
971             if not v1.select:
972                 continue
973
974             closest_vert = None
975             closest_distance = -1
976             for v2 in bm.verts:
977                 if v1 == v2:
978                     continue
979                 if v2.co[0] > threshold:
980                     continue
981                 if not v2.select:
982                     continue
983
984                 mirror_coord = Vector(v2.co)
985                 mirror_coord[0] *= -1
986                 distance = (mirror_coord - v1.co).length_squared
987                 if closest_vert is None or distance < closest_distance:
988                     closest_distance = distance
989                     closest_vert = v2
990
991             if closest_vert:
992                 closest_vert.select = False
993                 closest_vert.co = Vector(v1.co)
994                 closest_vert.co[0] *= -1
995             v1.select = False
996
997         for v1 in bm.verts:
998             if v1.select:
999                 closest_vert = None
1000                 closest_distance = -1
1001                 for v2 in bm.verts:
1002                     if v1 != v2:
1003                         mirror_coord = Vector(v2.co)
1004                         mirror_coord[0] *= -1
1005                         distance = (mirror_coord - v1.co).length_squared
1006                         if closest_vert is None or distance < closest_distance:
1007                             closest_distance = distance
1008                             closest_vert = v2
1009                 if closest_vert:
1010                     v1.select = False
1011                     v1.co = Vector(closest_vert.co)
1012                     v1.co[0] *= -1
1013
1014         bm.select_flush_mode()
1015         bmesh.update_edit_mesh(object.data)
1016
1017         return {'FINISHED'}
1018 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1019
1020 # FEATURE: Cycles Render Samples per Scene
1021 def render_cycles_scene_samples(self, context):
1022
1023     layout = self.layout
1024
1025     scenes = bpy.data.scenes
1026     scene = context.scene
1027     cscene = scene.cycles
1028
1029     if (len(bpy.data.scenes) > 1):
1030         layout.separator()
1031
1032         layout.label(text="Samples Per Scene:")
1033
1034         if cscene.progressive == 'PATH':
1035             for s in bpy.data.scenes:
1036                 if s != scene and s.render.engine == 'CYCLES':
1037                     cscene = s.cycles
1038
1039                     split = layout.split()
1040                     col = split.column()
1041                     sub = col.column(align=True)
1042
1043                     sub.label(text="%s" % s.name)
1044
1045                     col = split.column()
1046                     sub = col.column(align=True)
1047                     sub.prop(cscene, "samples", text="Render")
1048         else:
1049             for s in bpy.data.scenes:
1050                 if s != scene and s.render.engine == 'CYCLES':
1051                     cscene = s.cycles
1052
1053                     split = layout.split()
1054                     col = split.column()
1055                     sub = col.column(align=True)
1056
1057                     sub.label(text="%s" % s.name)
1058
1059                     col = split.column()
1060                     sub = col.column(align=True)
1061                     sub.prop(cscene, "aa_samples", text="Render")
1062 # // FEATURE: Cycles Render Samples per Scene
1063
1064 classes = (SCENE_OT_refresh,
1065            WM_OT_save_reload,
1066            MESH_OT_find_asymmetric,
1067            MESH_OT_make_symmetric,
1068            NODE_OT_AddTemplateVignette,
1069            NODE_MT_amaranth_templates,
1070            FILE_OT_directory_current_blend,
1071            FILE_OT_directory_go_to,
1072            NODE_PT_indices,
1073            NODE_PT_simplify,
1074            NODE_OT_toggle_mute,
1075            NODE_OT_show_active_node_image,
1076            VIEW3D_OT_render_border_camera,
1077            VIEW3D_OT_show_only_render,
1078            OBJECT_OT_select_meshlights,
1079            FILE_PT_libraries)
1080
1081 addon_keymaps = []
1082
1083 kmi_defs = (
1084     ('wm.call_menu', 'W', False, False, False, (('name', NODE_MT_amaranth_templates.bl_idname),)),
1085 )
1086
1087 def register():
1088     import sys
1089     have_cycles = True if "_cycles" in sys.modules else False
1090
1091     bpy.utils.register_class(AmaranthToolsetPreferences)
1092
1093     # UI: Register the panel
1094     init_properties()
1095     for c in classes:
1096         bpy.utils.register_class(c)
1097
1098     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
1099     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
1100     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
1101
1102     bpy.types.INFO_MT_file.append(button_save_reload)
1103     bpy.types.INFO_HT_header.append(stats_scene)
1104
1105     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
1106     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
1107     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
1108
1109     bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
1110
1111     bpy.types.NODE_HT_header.append(node_templates_pulldown)
1112     bpy.types.NODE_HT_header.append(node_stats)
1113
1114     if have_cycles:
1115         bpy.types.CyclesMaterial_PT_settings.append(material_cycles_settings_extra)
1116         bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
1117
1118     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
1119
1120     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
1121     if have_cycles:
1122         bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
1123
1124     bpy.types.PARTICLE_PT_render.prepend(particles_material_info)
1125
1126     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
1127     bpy.app.handlers.render_post.append(unsimplify_render_post)
1128
1129     wm = bpy.context.window_manager
1130     kc = wm.keyconfigs.addon
1131     if kc:
1132         km = kc.keymaps.new(name='Window')
1133         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
1134         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
1135
1136         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
1137         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
1138         kmi = km.keymap_items.new('wm.context_toggle_enum', 'Z', 'PRESS', shift=True, alt=False)
1139         kmi.properties.data_path = 'space_data.viewport_shade'
1140         kmi.properties.value_1 = 'SOLID'
1141         kmi.properties.value_2 = 'RENDERED'
1142
1143         km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
1144         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
1145         kmi.properties.data_path = 'area.type'
1146         kmi.properties.value = 'DOPESHEET_EDITOR'
1147
1148         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
1149         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
1150         kmi.properties.data_path = 'area.type'
1151         kmi.properties.value = 'GRAPH_EDITOR'
1152
1153         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
1154         kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
1155         kmi.properties.data_path = 'space_data.mode'
1156         kmi.properties.value_1 = 'ACTION'
1157         kmi.properties.value_2 = 'DOPESHEET'
1158
1159         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
1160         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
1161         km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
1162
1163         addon_keymaps.append((km, kmi))
1164
1165         # copypasted from the awesome node efficiency tools, future hotkeys proof!
1166         km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
1167         for (identifier, key, CTRL, SHIFT, ALT, props) in kmi_defs:
1168             kmi = km.keymap_items.new(identifier, key, 'PRESS', ctrl=CTRL, shift=SHIFT, alt=ALT)
1169             if props:
1170                 for prop, value in props:
1171                     setattr(kmi.properties, prop, value)
1172             addon_keymaps.append((km, kmi))
1173
1174 def unregister():
1175
1176     bpy.utils.unregister_class(AmaranthToolsetPreferences)
1177
1178     for c in classes:
1179         bpy.utils.unregister_class(c)
1180
1181     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
1182     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
1183     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
1184
1185     bpy.types.INFO_MT_file.remove(button_save_reload)
1186     bpy.types.INFO_HT_header.remove(stats_scene)
1187
1188     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
1189     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
1190     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
1191
1192     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
1193
1194     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
1195     bpy.types.NODE_HT_header.remove(node_stats)
1196
1197     bpy.types.CyclesMaterial_PT_settings.remove(material_cycles_settings_extra)
1198     bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
1199
1200     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
1201
1202     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
1203     bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
1204
1205     bpy.types.PARTICLE_PT_render.remove(particles_material_info)
1206
1207     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
1208     bpy.app.handlers.render_post.remove(unsimplify_render_post)
1209
1210     for km, kmi in addon_keymaps:
1211         km.keymap_items.remove(kmi)
1212     addon_keymaps.clear()
1213
1214     clear_properties()
1215
1216 if __name__ == "__main__":
1217     register()