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