Amaranth Addon
[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 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     layout = self.layout
444     row = layout.row(align=True)
445     row.scale_x = 1.3
446     row.menu("NODE_MT_amaranth_templates",
447         icon="RADIO")
448 # // FEATURE: Node Templates
449
450 def node_stats(self,context):
451     if context.scene.node_tree:
452         tree_type = context.space_data.tree_type
453         nodes = context.scene.node_tree.nodes
454         nodes_total = len(nodes.keys())
455         nodes_selected = 0
456         for n in nodes:
457             if n.select:
458                 nodes_selected = nodes_selected + 1
459
460         if tree_type == 'CompositorNodeTree':
461             layout = self.layout
462             row = layout.row(align=True)
463             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
464
465 # FEATURE: Simplify Compo Nodes
466 class NODE_PT_simplify(bpy.types.Panel):
467     '''Simplify Compositor Panel'''
468     bl_space_type = 'NODE_EDITOR'
469     bl_region_type = 'UI'
470     bl_label = 'Simplify'
471 #    bl_options = {'DEFAULT_CLOSED'}
472
473     def draw(self, context):
474         layout = self.layout
475         node_tree = context.scene.node_tree
476
477         if node_tree is not None:
478             layout.prop(node_tree, 'types')
479             layout.operator(NODE_OT_toggle_mute.bl_idname,
480                 text="Turn On" if node_tree.toggle_mute else "Turn Off",
481                 icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
482         
483             if node_tree.types == 'VECBLUR':
484                 layout.label(text="This will also toggle the Vector pass {}".format(
485                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
486
487 class NODE_OT_toggle_mute(Operator):
488     """"""
489     bl_idname = "node.toggle_mute"
490     bl_label = "Toggle Mute"
491
492     def execute(self, context):
493         scene = context.scene
494         node_tree = scene.node_tree
495         node_type = node_tree.types
496         rlayers = scene.render
497         
498         if not 'amaranth_pass_vector' in scene.keys():
499             scene['amaranth_pass_vector'] = []
500         
501         #can't extend() the list, so make a dummy one
502         pass_vector = scene['amaranth_pass_vector']
503
504         if not pass_vector:
505             pass_vector = []
506
507         if node_tree.toggle_mute:
508             for node in node_tree.nodes:
509                 if node_type == 'ALL':
510                     node.mute = node.status
511                 if node.type == node_type:
512                     node.mute = node.status
513                 if node_type == 'VECBLUR':
514                     for layer in rlayers.layers:
515                         if layer.name in pass_vector:
516                             layer.use_pass_vector = True
517                             pass_vector.remove(layer.name)
518
519                 node_tree.toggle_mute = False
520
521         else:
522             for node in node_tree.nodes:
523                 if node_type == 'ALL':
524                     node.mute = True
525                 if node.type == node_type:
526                     node.status = node.mute
527                     node.mute = True
528                 if node_type == 'VECBLUR':
529                     for layer in rlayers.layers:
530                         if layer.use_pass_vector:
531                             pass_vector.append(layer.name)
532                             layer.use_pass_vector = False
533                             pass
534
535                 node_tree.toggle_mute = True
536
537         # Write back to the custom prop
538         pass_vector = sorted(set(pass_vector))
539         scene['amaranth_pass_vector'] = pass_vector
540
541         return {'FINISHED'}
542         
543
544 # FEATURE: OB/MA ID panel in Node Editor
545 class NODE_PT_indices(bpy.types.Panel):
546     '''Object / Material Indices Panel'''
547     bl_space_type = 'NODE_EDITOR'
548     bl_region_type = 'UI'
549     bl_label = 'Object / Material Indices'
550     bl_options = {'DEFAULT_CLOSED'}
551
552     @classmethod
553     def poll(cls, context):
554         node = context.active_node
555         return node and node.type == 'ID_MASK'
556
557     def draw(self, context):
558         layout = self.layout
559
560         objects = bpy.data.objects
561         materials = bpy.data.materials
562         node = context.active_node
563
564         show_ob_id = False
565         show_ma_id = False
566         matching_ids = False
567
568         if context.active_object:
569             ob_act = context.active_object
570         else:
571             ob_act = False
572
573         for ob in objects:
574             if ob and ob.pass_index > 0:
575                 show_ob_id = True
576         for ma in materials:
577             if ma and ma.pass_index > 0:
578                 show_ma_id = True
579         row = layout.row(align=True)  
580         row.prop(node, 'index', text="Mask Index")
581         row.prop(node, 'use_matching_indices', text="Only Matching IDs")
582         
583         layout.separator()
584
585         if not show_ob_id and not show_ma_id:
586             layout.label(text="No objects or materials indices so far.", icon="INFO")
587
588         if show_ob_id:
589             split = layout.split()
590             col = split.column()
591             col.label(text="Object Name")
592             split.label(text="ID Number")
593             row = layout.row()
594             for ob in objects:
595                 icon = "OUTLINER_DATA_" + ob.type
596                 if ob.library:
597                     icon = "LIBRARY_DATA_DIRECT"
598                 elif ob.is_library_indirect:
599                     icon = "LIBRARY_DATA_INDIRECT"
600
601                 if ob and node.use_matching_indices \
602                       and ob.pass_index == node.index \
603                       and ob.pass_index != 0:
604                     matching_ids = True
605                     row.label(
606                       text="[{}]".format(ob.name)
607                           if ob_act and ob.name == ob_act.name else ob.name,
608                       icon=icon)
609                     row.label(text="%s" % ob.pass_index)
610                     row = layout.row()
611
612                 elif ob and not node.use_matching_indices \
613                         and ob.pass_index > 0:
614
615                     matching_ids = True
616                     row.label(
617                       text="[{}]".format(ob.name)
618                           if ob_act and ob.name == ob_act.name else ob.name,
619                       icon=icon)
620                     row.label(text="%s" % ob.pass_index)
621                     row = layout.row()
622
623             if node.use_matching_indices and not matching_ids:
624                 row.label(text="No objects with ID %s" % node.index, icon="INFO")
625
626             layout.separator()
627
628         if show_ma_id:
629             split = layout.split()
630             col = split.column()
631             col.label(text="Material Name")
632             split.label(text="ID Number")
633             row = layout.row()
634
635             for ma in materials:
636                 icon = "BLANK1"
637                 if ma.use_nodes:
638                     icon = "NODETREE"
639                 elif ma.library:
640                     icon = "LIBRARY_DATA_DIRECT"
641                     if ma.is_library_indirect:
642                         icon = "LIBRARY_DATA_INDIRECT"
643
644                 if ma and node.use_matching_indices \
645                       and ma.pass_index == node.index \
646                       and ma.pass_index != 0:
647                     matching_ids = True
648                     row.label(text="%s" % ma.name, icon=icon)
649                     row.label(text="%s" % ma.pass_index)
650                     row = layout.row()
651
652                 elif ma and not node.use_matching_indices \
653                         and ma.pass_index > 0:
654
655                     matching_ids = True
656                     row.label(text="%s" % ma.name, icon=icon)
657                     row.label(text="%s" % ma.pass_index)
658                     row = layout.row()
659
660             if node.use_matching_indices and not matching_ids:
661                 row.label(text="No materials with ID %s" % node.index, icon="INFO")
662
663
664 # // FEATURE: OB/MA ID panel in Node Editor
665
666 # FEATURE: Unsimplify on render
667 @persistent
668 def unsimplify_render_pre(scene):
669     render = scene.render
670     scene.simplify_status = render.use_simplify
671
672     if scene.use_unsimplify_render:
673         render.use_simplify = False
674
675 @persistent
676 def unsimplify_render_post(scene):
677     render = scene.render
678     render.use_simplify = scene.simplify_status
679
680 def unsimplify_ui(self,context):
681     scene = bpy.context.scene
682     self.layout.prop(scene, 'use_unsimplify_render')
683 # //FEATURE: Unsimplify on render
684
685 # FEATURE: Extra Info Stats
686 def stats_scene(self, context):
687
688     preferences = context.user_preferences.addons[__name__].preferences
689
690     if preferences.use_scene_stats:
691         scenes_count = str(len(bpy.data.scenes))
692         cameras_count = str(len(bpy.data.cameras))
693         cameras_selected = 0
694         meshlights = 0
695         meshlights_visible = 0
696     
697         for ob in context.scene.objects:
698             if ob.material_slots:
699                 for ma in ob.material_slots:
700                     if ma.material:
701                         if ma.material.node_tree:
702                             for no in ma.material.node_tree.nodes:
703                                 if no.type == 'EMISSION':
704                                     meshlights = meshlights + 1
705                                     if ob in context.visible_objects:
706                                         meshlights_visible = meshlights_visible + 1
707                                     break
708             if ob in context.selected_objects:
709                 if ob.type == 'CAMERA':
710                     cameras_selected = cameras_selected + 1
711     
712         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
713     
714         row = self.layout.row(align=True)
715         row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
716                    scenes_count, cameras_selected, cameras_count,
717                    meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
718
719 # //FEATURE: Extra Info Stats
720
721 # FEATURE: Camera Bounds as Render Border
722 class VIEW3D_OT_render_border_camera(Operator):
723     """Set camera bounds as render border"""
724     bl_idname = "view3d.render_border_camera"
725     bl_label = "Camera as Render Border"
726
727     @classmethod
728     def poll(cls, context):
729         return context.space_data.region_3d.view_perspective == 'CAMERA'
730
731     def execute(self, context):
732         render = context.scene.render
733         render.use_border = True
734         render.border_min_x = 0
735         render.border_min_y = 0
736         render.border_max_x = 1
737         render.border_max_y = 1
738
739         return {'FINISHED'}
740
741 def button_render_border_camera(self, context):
742
743     view3d = context.space_data.region_3d
744     
745     if view3d.view_perspective == 'CAMERA':
746         layout = self.layout
747         layout.separator()
748         layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
749                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
750
751 # //FEATURE: Camera Bounds as Render Border
752
753 # FEATURE: Passepartout options on W menu
754 def button_camera_passepartout(self, context):
755
756     view3d = context.space_data.region_3d
757     cam = context.scene.camera.data
758     
759     if view3d.view_perspective == 'CAMERA':
760         layout = self.layout
761         if cam.show_passepartout:
762             layout.prop(cam, "passepartout_alpha", text="Passepartout")
763         else:
764             layout.prop(cam, "show_passepartout")
765
766 # FEATURE: Show Only Render with Alt+Shift+Z
767 class VIEW3D_OT_show_only_render(Operator):
768     bl_idname = "view3d.show_only_render"
769     bl_label = "Show Only Render"
770
771     def execute(self, context):
772         space = bpy.context.space_data
773         
774         if space.show_only_render:
775             space.show_only_render = False
776         else:
777             space.show_only_render = True
778         return {'FINISHED'}
779
780
781 # FEATURE: Display Active Image Node on Image Editor
782 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
783 image_nodes = {"CompositorNodeImage",
784                "ShaderNodeTexImage",
785                "ShaderNodeTexEnvironment"}
786
787 class NODE_OT_show_active_node_image(Operator):
788     """Show active image node image in the image editor"""
789     bl_idname = "node.show_active_node_image"
790     bl_label = "Show Active Node Node"
791     bl_options = {'UNDO'}
792
793     def execute(self, context):
794         preferences = context.user_preferences.addons[__name__].preferences
795         if preferences.use_image_node_display:
796             if context.active_node:
797                 active_node = context.active_node
798                 if active_node.bl_idname in image_nodes and active_node.image:
799                     for area in context.screen.areas:
800                         if area.type == "IMAGE_EDITOR":
801                             for space in area.spaces:
802                                 if space.type == "IMAGE_EDITOR":
803                                     space.image = active_node.image
804                             break
805     
806         return {'FINISHED'}
807 # // FEATURE: Display Active Image Node on Image Editor
808
809 # FEATURE: Select Meshlights
810 class OBJECT_OT_select_meshlights(Operator):
811     """Select light emitting meshes"""
812     bl_idname = "object.select_meshlights"
813     bl_label = "Select Meshlights"
814     bl_options = {'UNDO'}
815
816     @classmethod
817     def poll(cls, context):
818         return context.scene.render.engine == 'CYCLES'
819
820     def execute(self, context):
821         # Deselect everything first
822         bpy.ops.object.select_all(action='DESELECT')
823
824         for ob in context.scene.objects:
825             if ob.material_slots:
826                 for ma in ob.material_slots:
827                     if ma.material:
828                         if ma.material.node_tree:
829                             for no in ma.material.node_tree.nodes:
830                                 if no.type == 'EMISSION':
831                                     ob.select = True
832                                     context.scene.objects.active = ob
833
834         if not context.selected_objects and not context.scene.objects.active:
835             self.report({'INFO'}, "No meshlights to select")
836
837         return {'FINISHED'}
838
839 def button_select_meshlights(self, context):
840     
841     if context.scene.render.engine == 'CYCLES':
842         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
843 # // FEATURE: Select Meshlights
844
845 # FEATURE: Cycles Viewport Extra Settings
846 def material_cycles_settings_extra(self, context):
847     
848     layout = self.layout
849     col = layout.column()
850     row = col.row(align=True)
851     
852     obj = context.object
853     mat = context.material
854     if obj.type == 'MESH':
855         row.prop(obj, "show_transparent", text="Viewport Alpha")
856         row.active = obj.show_transparent
857         row.prop(mat, "alpha", text="Alpha")
858 # // FEATURE: Cycles Viewport Extra Settings
859
860 # FEATURE: Particles Material indicator
861 def particles_material_info(self, context):
862
863     layout = self.layout
864
865     ob = context.object
866     psys = context.particle_system
867
868     layout.label(
869         text="Material: %s" % ob.material_slots[psys.settings.material-1].name \
870             if psys.settings.material <= len(ob.material_slots) \
871             else "No material with this index. Using '{}'".format( \
872                 ob.material_slots[len(ob.material_slots)-1].name))
873 # // FEATURE: Particles Material indicator
874
875 # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
876 class MESH_OT_find_asymmetric(Operator):
877     """
878     Find asymmetric vertices
879     """
880
881     bl_idname = "mesh.find_asymmetric"
882     bl_label = "Find Asymmetric"
883     bl_options = {'UNDO', 'REGISTER'}
884
885     @classmethod
886     def poll(cls, context):
887         object = context.object
888         if object:
889             return object.mode == 'EDIT' and object.type == 'MESH'
890         return False
891
892     def execute(self, context):
893         threshold = 1e-6
894
895         object = context.object
896         bm = bmesh.from_edit_mesh(object.data)
897
898         # Deselect all the vertices
899         for v in bm.verts:
900             v.select = False
901
902         for v1 in bm.verts:
903             if abs(v1.co[0]) < threshold:
904                 continue
905
906             mirror_found = False
907             for v2 in bm.verts:
908                 if v1 == v2:
909                     continue
910                 if v1.co[0] * v2.co[0] > 0.0:
911                     continue
912
913                 mirror_coord = Vector(v2.co)
914                 mirror_coord[0] *= -1
915                 if (mirror_coord - v1.co).length_squared < threshold:
916                     mirror_found = True
917                     break
918             if not mirror_found:
919                 v1.select = True
920
921         bm.select_flush_mode()
922
923         bmesh.update_edit_mesh(object.data)
924
925         return {'FINISHED'}
926
927 class MESH_OT_make_symmetric(Operator):
928     """
929     Make symmetric
930     """
931
932     bl_idname = "mesh.make_symmetric"
933     bl_label = "Make Symmetric"
934     bl_options = {'UNDO', 'REGISTER'}
935
936     @classmethod
937     def poll(cls, context):
938         object = context.object
939         if object:
940             return object.mode == 'EDIT' and object.type == 'MESH'
941         return False
942
943     def execute(self, context):
944         threshold = 1e-6
945
946         object = context.object
947         bm = bmesh.from_edit_mesh(object.data)
948
949         for v1 in bm.verts:
950             if v1.co[0] < threshold:
951                 continue
952             if not v1.select:
953                 continue
954
955             closest_vert = None
956             closest_distance = -1
957             for v2 in bm.verts:
958                 if v1 == v2:
959                     continue
960                 if v2.co[0] > threshold:
961                     continue
962                 if not v2.select:
963                     continue
964
965                 mirror_coord = Vector(v2.co)
966                 mirror_coord[0] *= -1
967                 distance = (mirror_coord - v1.co).length_squared
968                 if closest_vert is None or distance < closest_distance:
969                     closest_distance = distance
970                     closest_vert = v2
971
972             if closest_vert:
973                 closest_vert.select = False
974                 closest_vert.co = Vector(v1.co)
975                 closest_vert.co[0] *= -1
976             v1.select = False
977
978         for v1 in bm.verts:
979             if v1.select:
980                 closest_vert = None
981                 closest_distance = -1
982                 for v2 in bm.verts:
983                     if v1 != v2:
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                 if closest_vert:
991                     v1.select = False
992                     v1.co = Vector(closest_vert.co)
993                     v1.co[0] *= -1
994
995         bm.select_flush_mode()
996         bmesh.update_edit_mesh(object.data)
997
998         return {'FINISHED'}
999 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1000
1001
1002 classes = (SCENE_OT_refresh,
1003            WM_OT_save_reload,
1004            MESH_OT_find_asymmetric,
1005            MESH_OT_make_symmetric,
1006            NODE_OT_AddTemplateVignette,
1007            NODE_MT_amaranth_templates,
1008            FILE_OT_directory_current_blend,
1009            FILE_OT_directory_go_to,
1010            NODE_PT_indices,
1011            NODE_PT_simplify,
1012            NODE_OT_toggle_mute,
1013            NODE_OT_show_active_node_image,
1014            VIEW3D_OT_render_border_camera,
1015            VIEW3D_OT_show_only_render,
1016            OBJECT_OT_select_meshlights,
1017            FILE_PT_libraries)
1018
1019 addon_keymaps = []
1020
1021 kmi_defs = (
1022     ('wm.call_menu', 'W', False, False, False, (('name', NODE_MT_amaranth_templates.bl_idname),)),
1023 )
1024
1025 def register():
1026
1027     bpy.utils.register_class(AmaranthToolsetPreferences)
1028
1029     # UI: Register the panel
1030     init_properties()
1031     for c in classes:
1032         bpy.utils.register_class(c)
1033
1034     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
1035     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
1036     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
1037
1038     bpy.types.INFO_MT_file.append(button_save_reload)
1039     bpy.types.INFO_HT_header.append(stats_scene)
1040
1041     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
1042     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
1043     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
1044
1045     bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
1046
1047     bpy.types.NODE_HT_header.append(node_templates_pulldown)
1048     bpy.types.NODE_HT_header.append(node_stats)
1049
1050     bpy.types.CyclesMaterial_PT_settings.append(material_cycles_settings_extra)
1051
1052     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
1053
1054     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
1055     bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
1056
1057     bpy.types.PARTICLE_PT_render.prepend(particles_material_info)
1058
1059     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
1060     bpy.app.handlers.render_post.append(unsimplify_render_post)
1061
1062     wm = bpy.context.window_manager
1063     kc = wm.keyconfigs.addon
1064     if kc:
1065         km = kc.keymaps.new(name='Window')
1066         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
1067         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
1068
1069         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
1070         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
1071         kmi = km.keymap_items.new('wm.context_toggle_enum', 'Z', 'PRESS', shift=True, alt=False)
1072         kmi.properties.data_path = 'space_data.viewport_shade'
1073         kmi.properties.value_1 = 'SOLID'
1074         kmi.properties.value_2 = 'RENDERED'
1075
1076         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
1077         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
1078         km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
1079
1080         addon_keymaps.append((km, kmi))
1081
1082         # copypasted from the awesome node efficiency tools, future hotkeys proof!
1083         km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
1084         for (identifier, key, CTRL, SHIFT, ALT, props) in kmi_defs:
1085             kmi = km.keymap_items.new(identifier, key, 'PRESS', ctrl=CTRL, shift=SHIFT, alt=ALT)
1086             if props:
1087                 for prop, value in props:
1088                     setattr(kmi.properties, prop, value)
1089             addon_keymaps.append((km, kmi))
1090
1091 def unregister():
1092
1093     bpy.utils.unregister_class(AmaranthToolsetPreferences)
1094
1095     for c in classes:
1096         bpy.utils.unregister_class(c)
1097
1098     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
1099     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
1100     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
1101
1102     bpy.types.INFO_MT_file.remove(button_save_reload)
1103     bpy.types.INFO_HT_header.remove(stats_scene)
1104
1105     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
1106     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
1107     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
1108
1109     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
1110
1111     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
1112     bpy.types.NODE_HT_header.remove(node_stats)
1113
1114     bpy.types.CyclesMaterial_PT_settings.remove(material_cycles_settings_extra)
1115
1116     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
1117
1118     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
1119     bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
1120
1121     bpy.types.PARTICLE_PT_render.remove(particles_material_info)
1122
1123     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
1124     bpy.app.handlers.render_post.remove(unsimplify_render_post)
1125     
1126     for km, kmi in addon_keymaps:
1127         km.keymap_items.remove(kmi)
1128     addon_keymaps.clear()
1129     
1130     clear_properties()
1131
1132 if __name__ == "__main__":
1133     register()