Productivity toolset addon that I've been working on for some months and got good...
[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, 6),
23     "blender": (2, 68),
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
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="%s Start Frame" % 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: Node Templates
295 class NODE_OT_AddTemplateVignette(Operator):
296     bl_idname = "node.template_add_vignette"
297     bl_label = "Add Vignette"
298     bl_description = "Add a vignette effect"
299     bl_options = {'REGISTER', 'UNDO'}
300
301     @classmethod
302     def poll(cls, context):
303         space = context.space_data
304         return space.type == 'NODE_EDITOR' \
305                 and space.node_tree is not None \
306                 and space.tree_type == 'CompositorNodeTree'
307
308     # used as reference the setup scene script from master nazgul
309     def _setupNodes(self, context):
310         scene = context.scene
311         space = context.space_data
312         tree = scene.node_tree
313
314         bpy.ops.node.select_all(action='DESELECT')
315
316         ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
317         ellipse.width = 0.8
318         ellipse.height = 0.4
319         blur = tree.nodes.new(type='CompositorNodeBlur')
320         blur.use_relative = True
321         blur.factor_x = 30
322         blur.factor_y = 50
323         ramp = tree.nodes.new(type='CompositorNodeValToRGB')
324         ramp.color_ramp.interpolation = 'B_SPLINE'
325         ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
326
327         overlay = tree.nodes.new(type='CompositorNodeMixRGB')
328         overlay.blend_type = 'OVERLAY'
329         overlay.inputs[0].default_value = 0.8
330         overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
331
332         tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
333         tree.links.new(blur.outputs["Image"],ramp.inputs[0])
334         tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
335
336         if tree.nodes.active:
337             blur.location = tree.nodes.active.location
338             blur.location += Vector((330.0, -250.0))
339         else:
340             blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
341
342         ellipse.location = blur.location
343         ellipse.location += Vector((-300.0, 0))
344
345         ramp.location = blur.location
346         ramp.location += Vector((175.0, 0))
347
348         overlay.location = ramp.location
349         overlay.location += Vector((240.0, 275.0))
350
351         for node in {ellipse, blur, ramp, overlay}:
352             node.select = True
353             node.show_preview = False
354
355         bpy.ops.node.join()
356
357         frame = ellipse.parent
358         frame.label = 'Vignette'
359         frame.use_custom_color = True
360         frame.color = (0.783538, 0.0241576, 0.0802198)
361         
362         overlay.parent = None
363         overlay.label = 'Vignette Overlay'
364
365     def execute(self, context):
366         self._setupNodes(context)
367
368         return {'FINISHED'}
369
370 # Node Templates Menu
371 class NODE_MT_amaranth_templates(bpy.types.Menu):
372     bl_idname = 'NODE_MT_amaranth_templates'
373     bl_space_type = 'NODE_EDITOR'
374     bl_label = "Templates"
375     bl_description = "List of Amaranth Templates"
376
377     def draw(self, context):
378         layout = self.layout
379         layout.operator(
380             NODE_OT_AddTemplateVignette.bl_idname,
381             text="Vignette",
382             icon='COLOR')
383
384 def node_templates_pulldown(self, context):
385     layout = self.layout
386     row = layout.row(align=True)
387     row.scale_x = 1.3
388     row.menu("NODE_MT_amaranth_templates",
389         icon="RADIO")
390 # // FEATURE: Node Templates
391
392 def node_stats(self,context):
393     if context.scene.node_tree:
394         tree_type = context.space_data.tree_type
395         nodes = context.scene.node_tree.nodes
396         nodes_total = len(nodes.keys())
397         nodes_selected = 0
398         for n in nodes:
399             if n.select:
400                 nodes_selected = nodes_selected + 1
401
402         if tree_type == 'CompositorNodeTree':
403             layout = self.layout
404             row = layout.row(align=True)
405             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
406
407 # FEATURE: Simplify Compo Nodes
408 class NODE_PT_simplify(bpy.types.Panel):
409     '''Simplify Compositor Panel'''
410     bl_space_type = 'NODE_EDITOR'
411     bl_region_type = 'UI'
412     bl_label = 'Simplify'
413 #    bl_options = {'DEFAULT_CLOSED'}
414
415     def draw(self, context):
416         layout = self.layout
417         node_tree = context.scene.node_tree
418
419         if node_tree is not None:
420             layout.prop(node_tree, 'types')
421             layout.operator(NODE_OT_toggle_mute.bl_idname,
422                 text="Turn On" if node_tree.toggle_mute else "Turn Off",
423                 icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
424         
425             if node_tree.types == 'VECBLUR':
426                 layout.label(text="This will also toggle the Vector pass {}".format(
427                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
428
429 class NODE_OT_toggle_mute(Operator):
430     """"""
431     bl_idname = "node.toggle_mute"
432     bl_label = "Toggle Mute"
433
434     def execute(self, context):
435         scene = context.scene
436         node_tree = scene.node_tree
437         node_type = node_tree.types
438         rlayers = scene.render
439         
440         if not 'amaranth_pass_vector' in scene.keys():
441             scene['amaranth_pass_vector'] = []
442         
443         #can't extend() the list, so make a dummy one
444         pass_vector = scene['amaranth_pass_vector']
445
446         if not pass_vector:
447             pass_vector = []
448
449         if node_tree.toggle_mute:
450             for node in node_tree.nodes:
451                 if node_type == 'ALL':
452                     node.mute = node.status
453                 if node.type == node_type:
454                     node.mute = node.status
455                 if node_type == 'VECBLUR':
456                     for layer in rlayers.layers:
457                         if layer.name in pass_vector:
458                             layer.use_pass_vector = True
459                             pass_vector.remove(layer.name)
460
461                 node_tree.toggle_mute = False
462
463         else:
464             for node in node_tree.nodes:
465                 if node_type == 'ALL':
466                     node.mute = True
467                 if node.type == node_type:
468                     node.status = node.mute
469                     node.mute = True
470                 if node_type == 'VECBLUR':
471                     for layer in rlayers.layers:
472                         if layer.use_pass_vector:
473                             pass_vector.append(layer.name)
474                             layer.use_pass_vector = False
475                             pass
476
477                 node_tree.toggle_mute = True
478
479         # Write back to the custom prop
480         pass_vector = sorted(set(pass_vector))
481         scene['amaranth_pass_vector'] = pass_vector
482
483         return {'FINISHED'}
484         
485
486 # FEATURE: OB/MA ID panel in Node Editor
487 class NODE_PT_indices(bpy.types.Panel):
488     '''Object / Material Indices Panel'''
489     bl_space_type = 'NODE_EDITOR'
490     bl_region_type = 'UI'
491     bl_label = 'Object / Material Indices'
492     bl_options = {'DEFAULT_CLOSED'}
493
494     @classmethod
495     def poll(cls, context):
496         node = context.active_node
497         return node and node.type == 'ID_MASK'
498
499     def draw(self, context):
500         layout = self.layout
501
502         objects = bpy.data.objects
503         materials = bpy.data.materials
504         node = context.active_node
505
506         show_ob_id = False
507         show_ma_id = False
508         matching_ids = False
509
510         if context.active_object:
511             ob_act = context.active_object
512         else:
513             ob_act = False
514
515         for ob in objects:
516             if ob and ob.pass_index > 0:
517                 show_ob_id = True
518         for ma in materials:
519             if ma and ma.pass_index > 0:
520                 show_ma_id = True
521         row = layout.row(align=True)  
522         row.prop(node, 'index', text="Mask Index")
523         row.prop(node, 'use_matching_indices', text="Only Matching IDs")
524         
525         layout.separator()
526
527         if not show_ob_id and not show_ma_id:
528             layout.label(text="No objects or materials indices so far.", icon="INFO")
529
530         if show_ob_id:
531             split = layout.split()
532             col = split.column()
533             col.label(text="Object Name")
534             split.label(text="ID Number")
535             row = layout.row()
536             for ob in objects:
537                 icon = "OUTLINER_DATA_" + ob.type
538                 if ob.library:
539                     icon = "LIBRARY_DATA_DIRECT"
540                 elif ob.is_library_indirect:
541                     icon = "LIBRARY_DATA_INDIRECT"
542
543                 if ob and node.use_matching_indices \
544                       and ob.pass_index == node.index \
545                       and ob.pass_index != 0:
546                     matching_ids = True
547                     row.label(
548                       text="[{}]".format(ob.name)
549                           if ob_act and ob.name == ob_act.name else ob.name,
550                       icon=icon)
551                     row.label(text="%s" % ob.pass_index)
552                     row = layout.row()
553
554                 elif ob and not node.use_matching_indices \
555                         and ob.pass_index > 0:
556
557                     matching_ids = True
558                     row.label(
559                       text="[{}]".format(ob.name)
560                           if ob_act and ob.name == ob_act.name else ob.name,
561                       icon=icon)
562                     row.label(text="%s" % ob.pass_index)
563                     row = layout.row()
564
565             if node.use_matching_indices and not matching_ids:
566                 row.label(text="No objects with ID %s" % node.index, icon="INFO")
567
568             layout.separator()
569
570         if show_ma_id:
571             split = layout.split()
572             col = split.column()
573             col.label(text="Material Name")
574             split.label(text="ID Number")
575             row = layout.row()
576
577             for ma in materials:
578                 icon = "BLANK1"
579                 if ma.use_nodes:
580                     icon = "NODETREE"
581                 elif ma.library:
582                     icon = "LIBRARY_DATA_DIRECT"
583                     if ma.is_library_indirect:
584                         icon = "LIBRARY_DATA_INDIRECT"
585
586                 if ma and node.use_matching_indices \
587                       and ma.pass_index == node.index \
588                       and ma.pass_index != 0:
589                     matching_ids = True
590                     row.label(text="%s" % ma.name, icon=icon)
591                     row.label(text="%s" % ma.pass_index)
592                     row = layout.row()
593
594                 elif ma and not node.use_matching_indices \
595                         and ma.pass_index > 0:
596
597                     matching_ids = True
598                     row.label(text="%s" % ma.name, icon=icon)
599                     row.label(text="%s" % ma.pass_index)
600                     row = layout.row()
601
602             if node.use_matching_indices and not matching_ids:
603                 row.label(text="No materials with ID %s" % node.index, icon="INFO")
604
605
606 # // FEATURE: OB/MA ID panel in Node Editor
607
608 # FEATURE: Unsimplify on render
609 @persistent
610 def unsimplify_render_pre(scene):
611     render = scene.render
612     scene.simplify_status = render.use_simplify
613
614     if scene.use_unsimplify_render:
615         render.use_simplify = False
616
617 @persistent
618 def unsimplify_render_post(scene):
619     render = scene.render
620     render.use_simplify = scene.simplify_status
621
622 def unsimplify_ui(self,context):
623     scene = bpy.context.scene
624     self.layout.prop(scene, 'use_unsimplify_render')
625 # //FEATURE: Unsimplify on render
626
627 # FEATURE: Extra Info Stats
628 def stats_scene(self, context):
629
630     preferences = context.user_preferences.addons[__name__].preferences
631
632     if preferences.use_scene_stats:
633         scenes_count = str(len(bpy.data.scenes))
634         cameras_count = str(len(bpy.data.cameras))
635         cameras_selected = 0
636         meshlights = 0
637         meshlights_visible = 0
638     
639         for ob in context.scene.objects:
640             if ob.material_slots:
641                 for ma in ob.material_slots:
642                     if ma.material:
643                         if ma.material.node_tree:
644                             for no in ma.material.node_tree.nodes:
645                                 if no.type == 'EMISSION':
646                                     meshlights = meshlights + 1
647                                     if ob in context.visible_objects:
648                                         meshlights_visible = meshlights_visible + 1
649                                     break
650             if ob in context.selected_objects:
651                 if ob.type == 'CAMERA':
652                     cameras_selected = cameras_selected + 1
653     
654         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
655     
656         row = self.layout.row(align=True)
657         row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
658                    scenes_count, cameras_selected, cameras_count,
659                    meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
660
661 # //FEATURE: Extra Info Stats
662
663 # FEATURE: Camera Bounds as Render Border
664 class VIEW3D_OT_render_border_camera(Operator):
665     """Set camera bounds as render border"""
666     bl_idname = "view3d.render_border_camera"
667     bl_label = "Camera as Render Border"
668
669     @classmethod
670     def poll(cls, context):
671         return context.space_data.region_3d.view_perspective == 'CAMERA'
672
673     def execute(self, context):
674         render = context.scene.render
675         render.use_border = True
676         render.border_min_x = 0
677         render.border_min_y = 0
678         render.border_max_x = 1
679         render.border_max_y = 1
680
681         return {'FINISHED'}
682
683 def button_render_border_camera(self, context):
684
685     view3d = context.space_data.region_3d
686     
687     if view3d.view_perspective == 'CAMERA':
688         layout = self.layout
689         layout.separator()
690         layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
691                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
692
693 # //FEATURE: Camera Bounds as Render Border
694
695 # FEATURE: Passepartout options on W menu
696 def button_camera_passepartout(self, context):
697
698     view3d = context.space_data.region_3d
699     cam = context.scene.camera.data
700     
701     if view3d.view_perspective == 'CAMERA':
702         layout = self.layout
703         if cam.show_passepartout:
704             layout.prop(cam, "passepartout_alpha", text="Passepartout")
705         else:
706             layout.prop(cam, "show_passepartout")
707
708 # FEATURE: Show Only Render with Alt+Shift+Z
709 class VIEW3D_OT_show_only_render(Operator):
710     bl_idname = "view3d.show_only_render"
711     bl_label = "Show Only Render"
712
713     def execute(self, context):
714         space = bpy.context.space_data
715         
716         if space.show_only_render:
717             space.show_only_render = False
718         else:
719             space.show_only_render = True
720         return {'FINISHED'}
721
722
723 # FEATURE: Display Active Image Node on Image Editor
724 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
725 image_nodes = {"CompositorNodeImage",
726                "ShaderNodeTexImage",
727                "ShaderNodeTexEnvironment"}
728
729 class NODE_OT_show_active_node_image(Operator):
730     """Show active image node image in the image editor"""
731     bl_idname = "node.show_active_node_image"
732     bl_label = "Show Active Node Node"
733     bl_options = {'UNDO'}
734
735     def execute(self, context):
736         preferences = context.user_preferences.addons[__name__].preferences
737         if preferences.use_image_node_display:
738             if context.active_node:
739                 active_node = context.active_node
740                 if active_node.bl_idname in image_nodes and active_node.image:
741                     for area in context.screen.areas:
742                         if area.type == "IMAGE_EDITOR":
743                             for space in area.spaces:
744                                 if space.type == "IMAGE_EDITOR":
745                                     space.image = active_node.image
746                             break
747     
748         return {'FINISHED'}
749 # // FEATURE: Display Active Image Node on Image Editor
750
751 # FEATURE: Select Meshlights
752 class OBJECT_OT_select_meshlights(Operator):
753     """Select light emitting meshes"""
754     bl_idname = "object.select_meshlights"
755     bl_label = "Select Meshlights"
756     bl_options = {'UNDO'}
757
758     @classmethod
759     def poll(cls, context):
760         return context.scene.render.engine == 'CYCLES'
761
762     def execute(self, context):
763         # Deselect everything first
764         bpy.ops.object.select_all(action='DESELECT')
765
766         for ob in context.scene.objects:
767             if ob.material_slots:
768                 for ma in ob.material_slots:
769                     if ma.material:
770                         if ma.material.node_tree:
771                             for no in ma.material.node_tree.nodes:
772                                 if no.type == 'EMISSION':
773                                     ob.select = True
774                                     context.scene.objects.active = ob
775
776         if not context.selected_objects and not context.scene.objects.active:
777             self.report({'INFO'}, "No meshlights to select")
778
779         return {'FINISHED'}
780
781 def button_select_meshlights(self, context):
782     
783     if context.scene.render.engine == 'CYCLES':
784         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
785 # // FEATURE: Select Meshlights
786
787 # FEATURE: Cycles Viewport Extra Settings
788 def material_cycles_settings_extra(self, context):
789     
790     layout = self.layout
791     col = layout.column()
792     row = col.row(align=True)
793     
794     obj = context.object
795     mat = context.material
796     if obj.type == 'MESH':
797         row.prop(obj, "show_transparent", text="Viewport Alpha")
798         row.active = obj.show_transparent
799         row.prop(mat, "alpha", text="Alpha")
800
801 classes = (SCENE_OT_refresh,
802            WM_OT_save_reload,
803            NODE_OT_AddTemplateVignette,
804            NODE_MT_amaranth_templates,
805            FILE_OT_directory_current_blend,
806            NODE_PT_indices,
807            NODE_PT_simplify,
808            NODE_OT_toggle_mute,
809            NODE_OT_show_active_node_image,
810            VIEW3D_OT_render_border_camera,
811            VIEW3D_OT_show_only_render,
812            OBJECT_OT_select_meshlights)
813
814 addon_keymaps = []
815
816 kmi_defs = (
817     ('wm.call_menu', 'W', False, False, False, (('name', NODE_MT_amaranth_templates.bl_idname),)),
818 )
819
820 def register():
821
822     bpy.utils.register_class(AmaranthToolsetPreferences)
823
824     # UI: Register the panel
825     init_properties()
826     for c in classes:
827         bpy.utils.register_class(c)
828
829     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
830     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
831     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
832
833     bpy.types.INFO_MT_file.append(button_save_reload)
834     bpy.types.INFO_HT_header.append(stats_scene)
835
836     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
837     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
838     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
839
840     bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
841
842     bpy.types.NODE_HT_header.append(node_templates_pulldown)
843     bpy.types.NODE_HT_header.append(node_stats)
844
845     bpy.types.CyclesMaterial_PT_settings.append(material_cycles_settings_extra)
846
847     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
848
849     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
850     bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
851
852     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
853     bpy.app.handlers.render_post.append(unsimplify_render_post)
854
855     wm = bpy.context.window_manager
856     kc = wm.keyconfigs.addon
857     if kc:
858         km = kc.keymaps.new(name='Window')
859         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
860         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
861
862         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
863         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
864         kmi = km.keymap_items.new('wm.context_toggle_enum', 'Z', 'PRESS', shift=True, alt=False)
865         kmi.properties.data_path = 'space_data.viewport_shade'
866         kmi.properties.value_1 = 'SOLID'
867         kmi.properties.value_2 = 'RENDERED'
868
869         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
870         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
871         km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
872
873         addon_keymaps.append((km, kmi))
874
875         # copypasted from the awesome node efficiency tools, future hotkeys proof!
876         km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
877         for (identifier, key, CTRL, SHIFT, ALT, props) in kmi_defs:
878             kmi = km.keymap_items.new(identifier, key, 'PRESS', ctrl=CTRL, shift=SHIFT, alt=ALT)
879             if props:
880                 for prop, value in props:
881                     setattr(kmi.properties, prop, value)
882             addon_keymaps.append((km, kmi))
883
884 def unregister():
885
886     bpy.utils.unregister_class(AmaranthToolsetPreferences)
887
888     for c in classes:
889         bpy.utils.unregister_class(c)
890
891     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
892     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
893     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
894
895     bpy.types.INFO_MT_file.remove(button_save_reload)
896     bpy.types.INFO_HT_header.remove(stats_scene)
897
898     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
899     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
900     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
901
902     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
903
904     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
905     bpy.types.NODE_HT_header.remove(node_stats)
906
907     bpy.types.CyclesMaterial_PT_settings.remove(material_cycles_settings_extra)
908
909     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
910
911     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
912     bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
913
914     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
915     bpy.app.handlers.render_post.remove(unsimplify_render_post)
916     
917     for km, kmi in addon_keymaps:
918         km.keymap_items.remove(kmi)
919     addon_keymaps.clear()
920     
921     clear_properties()
922
923 if __name__ == "__main__":
924     register()