6c34a54632fb571b661ceeeba37680e287aa87b7
[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, 8, 4),
23     "blender": (2, 70),
24     "location": "Everywhere!",
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, Menu
35 from bpy.props import BoolProperty
36 from mathutils import Vector
37 from bpy.app.handlers import persistent
38 from bl_operators.presets import AddPresetBase
39
40 # Preferences
41 class AmaranthToolsetPreferences(AddonPreferences):
42     bl_idname = __name__
43     use_frame_current = BoolProperty(
44             name="Current Frame Slider",
45             description="Set the current frame from the Specials menu in the 3D View",
46             default=True,
47             )
48     use_file_save_reload = BoolProperty(
49             name="Save & Reload File",
50             description="File menu > Save & Reload, or Ctrl + Shift + W",
51             default=True,
52             )
53
54     use_scene_refresh = BoolProperty(
55             name="Refresh Scene",
56             description="Specials Menu [W], or hit F5",
57             default=True,
58             )
59     use_timeline_extra_info = BoolProperty(
60             name="Timeline Extra Info",
61             description="Timeline Header",
62             default=True,
63             )
64     use_image_node_display = BoolProperty(
65             name="Active Image Node in Editor",
66             description="Display active node image in image editor",
67             default=True,
68             )
69     use_scene_stats = BoolProperty(
70             name="Extra Scene Statistics",
71             description="Display extra scene statistics in Info editor's header",
72             default=True,
73             )
74
75
76     def draw(self, context):
77         layout = self.layout
78
79         layout.label(
80             text="Here you can enable or disable specific tools, "
81                  "in case they interfere with others or are just plain annoying")
82
83         split = layout.split(percentage=0.25)
84
85         col = split.column()
86         sub = col.column(align=True)
87         sub.label(text="3D View", icon="VIEW3D")
88         sub.prop(self, "use_frame_current")
89         sub.prop(self, "use_scene_refresh")
90
91         sub.separator()
92
93         sub.label(text="General", icon="SCENE_DATA")
94         sub.prop(self, "use_file_save_reload")
95         sub.prop(self, "use_timeline_extra_info")
96         sub.prop(self, "use_scene_stats")
97
98         sub.separator()
99
100         sub.label(text="Nodes Editor", icon="NODETREE")
101         sub.prop(self, "use_image_node_display")
102
103         col = split.column()
104         sub = col.column(align=True)
105         sub.label(text="")
106         sub.label(
107             text="Set the current frame from the Specials menu in the 3D View [W]")
108         sub.label(
109             text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
110
111         sub.separator()
112         sub.label(text="") # General
113         sub.label(
114             text="Quickly save and reload the current file (no warning!). "
115                  "File menu or Ctrl+Shift+W")
116         sub.label(
117             text="SMPTE Timecode and frames left/ahead on Timeline's header")
118         sub.label(
119             text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
120
121         sub.separator()
122         sub.label(text="") # Nodes
123         sub.label(
124             text="When selecting an Image node, display it on the Image editor "
125                  "(if any)")
126
127 # Properties
128 def init_properties():
129
130     scene = bpy.types.Scene
131     node = bpy.types.Node
132     nodes_compo = bpy.types.CompositorNodeTree
133
134     scene.use_unsimplify_render = BoolProperty(
135         default=False,
136         name="Unsimplify Render",
137         description="Disable Simplify during render")
138     scene.simplify_status = BoolProperty(default=False)
139
140     node.use_matching_indices = BoolProperty(
141         default=True,
142         description="If disabled, display all available indices")
143
144     test_items = [
145         ("ALL", "All Types", "", 0),
146         ("BLUR", "Blur", "", 1),
147         ("BOKEHBLUR", "Bokeh Blur", "", 2),
148         ("VECBLUR", "Vector Blur", "", 3),
149         ("DEFOCUS", "Defocus", "", 4),
150         ("R_LAYERS", "Render Layer", "", 5)
151         ]
152
153     nodes_compo.types = bpy.props.EnumProperty(
154         items=test_items, name = "Types")
155
156     nodes_compo.toggle_mute = BoolProperty(default=False)
157     node.status = BoolProperty(default=False)
158
159     # Scene Debug
160     # Cycles Node Types
161     cycles_shader_node_types = [
162         ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
163         ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
164         ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
165         ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
166         ("BSDF_GLASS", "Glass BSDF", "", 4),
167         ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
168         ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
169         ("BSDF_VELVET", "Velvet BSDF", "", 7),
170         ("BSDF_TOON", "Toon BSDF", "", 8),
171         ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
172         ("EMISSION", "Emission", "", 10),
173         ("BSDF_HAIR", "Hair BSDF", "", 11),
174         ("BACKGROUND", "Background", "", 12),
175         ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
176         ("HOLDOUT", "Holdout", "", 14),
177         ]
178
179     scene.amaranth_cycles_node_types = bpy.props.EnumProperty(
180         items=cycles_shader_node_types, name = "Shader")
181
182     scene.amaranth_debug_scene_list_lamps = BoolProperty(
183         default=False,
184         name="Lamps List",
185         description="Display a list of all the lamps")
186
187     scene.amaranth_debug_scene_list_missing_images = BoolProperty(
188         default=False,
189         name="List Missing Images",
190         description="Display a list of all the missing images")
191
192
193 def clear_properties():
194     props = (
195         "use_unsimplify_render",
196         "simplify_status",
197         "use_matching_indices",
198         "use_simplify_nodes_vector",
199         "status"
200     )
201     
202     wm = bpy.context.window_manager
203     for p in props:
204         if p in wm:
205             del wm[p]
206
207 # FEATURE: Refresh Scene!
208 class SCENE_OT_refresh(Operator):
209     """Refresh the current scene"""
210     bl_idname = "scene.refresh"
211     bl_label = "Refresh!"
212     
213     def execute(self, context):
214         preferences = context.user_preferences.addons[__name__].preferences
215         scene = context.scene
216
217         if preferences.use_scene_refresh:    
218             # Changing the frame is usually the best way to go
219             scene.frame_current = scene.frame_current
220             self.report({"INFO"}, "Scene Refreshed!")
221             
222         return {'FINISHED'}
223
224 def button_refresh(self, context):
225
226     preferences = context.user_preferences.addons[__name__].preferences
227
228     if preferences.use_scene_refresh:
229         self.layout.separator()
230         self.layout.operator(
231             SCENE_OT_refresh.bl_idname,
232             text="Refresh!",
233             icon='FILE_REFRESH')
234 # // FEATURE: Refresh Scene!
235
236 # FEATURE: Save & Reload
237 def save_reload(self, context, path):
238
239     if path:
240         bpy.ops.wm.save_mainfile()
241         self.report({'INFO'}, "Saved & Reloaded")
242         bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
243     else:
244         bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
245
246 class WM_OT_save_reload(Operator):
247     """Save and Reload the current blend file"""
248     bl_idname = "wm.save_reload"
249     bl_label = "Save & Reload"
250
251     def execute(self, context):
252
253         path = bpy.data.filepath
254         save_reload(self, context, path)
255         return {'FINISHED'}
256
257 def button_save_reload(self, context):
258
259     preferences = context.user_preferences.addons[__name__].preferences
260
261     if preferences.use_file_save_reload:
262         self.layout.separator()
263         self.layout.operator(
264             WM_OT_save_reload.bl_idname,
265             text="Save & Reload",
266             icon='FILE_REFRESH')
267 # // FEATURE: Save & Reload
268
269 # FEATURE: Current Frame
270 def button_frame_current(self, context):
271
272     preferences = context.user_preferences.addons[__name__].preferences
273     scene = context.scene
274
275     if preferences.use_frame_current:
276         self.layout.separator()
277         self.layout.prop(
278             scene, "frame_current",
279             text="Set Current Frame")
280 # // FEATURE: Current Frame
281
282 # FEATURE: Timeline Time + Frames Left
283 def label_timeline_extra_info(self, context):
284
285     preferences = context.user_preferences.addons[__name__].preferences
286     layout = self.layout
287     scene = context.scene
288
289     if preferences.use_timeline_extra_info:
290         row = layout.row(align=True)
291
292         # Check for preview range
293         frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
294         frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
295         
296         row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
297                         bpy.utils.smpte_from_frame(frame_end - frame_start)))
298
299         if (scene.frame_current > frame_end):
300             row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
301         elif (scene.frame_current == frame_start):
302             row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
303         elif (scene.frame_current == frame_end):
304             row.label(text="%s End Frame" % scene.frame_current)
305         else:
306             row.label(text="%s Frames Left" % (frame_end - scene.frame_current))
307
308 # // FEATURE: Timeline Time + Frames Left
309
310 # FEATURE: Directory Current Blend
311 class FILE_OT_directory_current_blend(Operator):
312     """Go to the directory of the currently open blend file"""
313     bl_idname = "file.directory_current_blend"
314     bl_label = "Current Blend's Folder"
315
316     def execute(self, context):
317         bpy.ops.file.select_bookmark(dir='//')
318         return {'FINISHED'}
319
320 def button_directory_current_blend(self, context):
321
322     if bpy.data.filepath:
323         self.layout.operator(
324             FILE_OT_directory_current_blend.bl_idname,
325             text="Current Blend's Folder",
326             icon='APPEND_BLEND')
327 # // FEATURE: Directory Current Blend
328
329 # FEATURE: Libraries panel on file browser
330 class FILE_PT_libraries(Panel):
331     bl_space_type = 'FILE_BROWSER'
332     bl_region_type = 'CHANNELS'
333     bl_label = "Libraries"
334
335     def draw(self, context):
336         layout = self.layout
337
338         libs = bpy.data.libraries
339         libslist = []
340
341         # Build the list of folders from libraries
342         import os
343
344         for lib in libs:
345             directory_name = os.path.dirname(lib.filepath)
346             libslist.append(directory_name)
347
348         # Remove duplicates and sort by name
349         libslist = set(libslist)
350         libslist = sorted(libslist)
351
352         # Draw the box with libs
353         
354         row = layout.row()
355         box = row.box()
356        
357         if libslist:
358             for filepath in libslist:
359                 if filepath != '//':
360                     row = box.row()
361                     row.alignment = 'LEFT'
362                     props = row.operator(
363                         FILE_OT_directory_go_to.bl_idname,
364                         text=filepath, icon="BOOKMARKS",
365                         emboss=False)
366                     props.filepath = filepath
367         else:
368             box.label(text='No libraries loaded')
369
370 class FILE_OT_directory_go_to(Operator):
371     """Go to this library's directory"""
372     bl_idname = "file.directory_go_to"
373     bl_label = "Go To"
374     
375     filepath = bpy.props.StringProperty(subtype="FILE_PATH")
376
377     def execute(self, context):
378
379         bpy.ops.file.select_bookmark(dir=self.filepath)
380         return {'FINISHED'}
381     
382 # FEATURE: Node Templates
383 class NODE_OT_AddTemplateVignette(Operator):
384     bl_idname = "node.template_add_vignette"
385     bl_label = "Add Vignette"
386     bl_description = "Add a vignette effect"
387     bl_options = {'REGISTER', 'UNDO'}
388
389     @classmethod
390     def poll(cls, context):
391         space = context.space_data
392         return space.type == 'NODE_EDITOR' \
393                 and space.node_tree is not None \
394                 and space.tree_type == 'CompositorNodeTree'
395
396     # used as reference the setup scene script from master nazgul
397     def _setupNodes(self, context):
398         scene = context.scene
399         space = context.space_data
400         tree = scene.node_tree
401
402         bpy.ops.node.select_all(action='DESELECT')
403
404         ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
405         ellipse.width = 0.8
406         ellipse.height = 0.4
407         blur = tree.nodes.new(type='CompositorNodeBlur')
408         blur.use_relative = True
409         blur.factor_x = 30
410         blur.factor_y = 50
411         ramp = tree.nodes.new(type='CompositorNodeValToRGB')
412         ramp.color_ramp.interpolation = 'B_SPLINE'
413         ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
414
415         overlay = tree.nodes.new(type='CompositorNodeMixRGB')
416         overlay.blend_type = 'OVERLAY'
417         overlay.inputs[0].default_value = 0.8
418         overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
419
420         tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
421         tree.links.new(blur.outputs["Image"],ramp.inputs[0])
422         tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
423
424         if tree.nodes.active:
425             blur.location = tree.nodes.active.location
426             blur.location += Vector((330.0, -250.0))
427         else:
428             blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
429
430         ellipse.location = blur.location
431         ellipse.location += Vector((-300.0, 0))
432
433         ramp.location = blur.location
434         ramp.location += Vector((175.0, 0))
435
436         overlay.location = ramp.location
437         overlay.location += Vector((240.0, 275.0))
438
439         for node in {ellipse, blur, ramp, overlay}:
440             node.select = True
441             node.show_preview = False
442
443         bpy.ops.node.join()
444
445         frame = ellipse.parent
446         frame.label = 'Vignette'
447         frame.use_custom_color = True
448         frame.color = (0.783538, 0.0241576, 0.0802198)
449         
450         overlay.parent = None
451         overlay.label = 'Vignette Overlay'
452
453     def execute(self, context):
454         self._setupNodes(context)
455
456         return {'FINISHED'}
457
458 # Node Templates Menu
459 class NODE_MT_amaranth_templates(Menu):
460     bl_idname = 'NODE_MT_amaranth_templates'
461     bl_space_type = 'NODE_EDITOR'
462     bl_label = "Templates"
463     bl_description = "List of Amaranth Templates"
464
465     def draw(self, context):
466         layout = self.layout
467         layout.operator(
468             NODE_OT_AddTemplateVignette.bl_idname,
469             text="Vignette",
470             icon='COLOR')
471
472 def node_templates_pulldown(self, context):
473
474     if context.space_data.tree_type == 'CompositorNodeTree':
475         layout = self.layout
476         row = layout.row(align=True)
477         row.scale_x = 1.3
478         row.menu("NODE_MT_amaranth_templates",
479             icon="RADIO")
480 # // FEATURE: Node Templates
481
482 def node_stats(self,context):
483     if context.scene.node_tree:
484         tree_type = context.space_data.tree_type
485         nodes = context.scene.node_tree.nodes
486         nodes_total = len(nodes.keys())
487         nodes_selected = 0
488         for n in nodes:
489             if n.select:
490                 nodes_selected = nodes_selected + 1
491
492         if tree_type == 'CompositorNodeTree':
493             layout = self.layout
494             row = layout.row(align=True)
495             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
496
497 # FEATURE: Simplify Compo Nodes
498 class NODE_PT_simplify(Panel):
499     '''Simplify Compositor Panel'''
500     bl_space_type = 'NODE_EDITOR'
501     bl_region_type = 'UI'
502     bl_label = 'Simplify'
503     bl_options = {'DEFAULT_CLOSED'}
504
505     @classmethod
506     def poll(cls, context):
507         space = context.space_data
508         return space.type == 'NODE_EDITOR' \
509                 and space.node_tree is not None \
510                 and space.tree_type == 'CompositorNodeTree'
511
512     def draw(self, context):
513         layout = self.layout
514         node_tree = context.scene.node_tree
515
516         if node_tree is not None:
517             layout.prop(node_tree, 'types')
518             layout.operator(NODE_OT_toggle_mute.bl_idname,
519                 text="Turn On" if node_tree.toggle_mute else "Turn Off",
520                 icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
521         
522             if node_tree.types == 'VECBLUR':
523                 layout.label(text="This will also toggle the Vector pass {}".format(
524                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
525
526 class NODE_OT_toggle_mute(Operator):
527     """"""
528     bl_idname = "node.toggle_mute"
529     bl_label = "Toggle Mute"
530
531     def execute(self, context):
532         scene = context.scene
533         node_tree = scene.node_tree
534         node_type = node_tree.types
535         rlayers = scene.render
536         
537         if not 'amaranth_pass_vector' in scene.keys():
538             scene['amaranth_pass_vector'] = []
539         
540         #can't extend() the list, so make a dummy one
541         pass_vector = scene['amaranth_pass_vector']
542
543         if not pass_vector:
544             pass_vector = []
545
546         if node_tree.toggle_mute:
547             for node in node_tree.nodes:
548                 if node_type == 'ALL':
549                     node.mute = node.status
550                 if node.type == node_type:
551                     node.mute = node.status
552                 if node_type == 'VECBLUR':
553                     for layer in rlayers.layers:
554                         if layer.name in pass_vector:
555                             layer.use_pass_vector = True
556                             pass_vector.remove(layer.name)
557
558                 node_tree.toggle_mute = False
559
560         else:
561             for node in node_tree.nodes:
562                 if node_type == 'ALL':
563                     node.mute = True
564                 if node.type == node_type:
565                     node.status = node.mute
566                     node.mute = True
567                 if node_type == 'VECBLUR':
568                     for layer in rlayers.layers:
569                         if layer.use_pass_vector:
570                             pass_vector.append(layer.name)
571                             layer.use_pass_vector = False
572                             pass
573
574                 node_tree.toggle_mute = True
575
576         # Write back to the custom prop
577         pass_vector = sorted(set(pass_vector))
578         scene['amaranth_pass_vector'] = pass_vector
579
580         return {'FINISHED'}
581         
582
583 # FEATURE: OB/MA ID panel in Node Editor
584 class NODE_PT_indices(Panel):
585     '''Object / Material Indices Panel'''
586     bl_space_type = 'NODE_EDITOR'
587     bl_region_type = 'UI'
588     bl_label = 'Object / Material Indices'
589     bl_options = {'DEFAULT_CLOSED'}
590
591     @classmethod
592     def poll(cls, context):
593         node = context.active_node
594         return node and node.type == 'ID_MASK'
595
596     def draw(self, context):
597         layout = self.layout
598
599         objects = bpy.data.objects
600         materials = bpy.data.materials
601         node = context.active_node
602
603         show_ob_id = False
604         show_ma_id = False
605         matching_ids = False
606
607         if context.active_object:
608             ob_act = context.active_object
609         else:
610             ob_act = False
611
612         for ob in objects:
613             if ob and ob.pass_index > 0:
614                 show_ob_id = True
615         for ma in materials:
616             if ma and ma.pass_index > 0:
617                 show_ma_id = True
618         row = layout.row(align=True)  
619         row.prop(node, 'index', text="Mask Index")
620         row.prop(node, 'use_matching_indices', text="Only Matching IDs")
621         
622         layout.separator()
623
624         if not show_ob_id and not show_ma_id:
625             layout.label(text="No objects or materials indices so far.", icon="INFO")
626
627         if show_ob_id:
628             split = layout.split()
629             col = split.column()
630             col.label(text="Object Name")
631             split.label(text="ID Number")
632             row = layout.row()
633             for ob in objects:
634                 icon = "OUTLINER_DATA_" + ob.type
635                 if ob.library:
636                     icon = "LIBRARY_DATA_DIRECT"
637                 elif ob.is_library_indirect:
638                     icon = "LIBRARY_DATA_INDIRECT"
639
640                 if ob and node.use_matching_indices \
641                       and ob.pass_index == node.index \
642                       and ob.pass_index != 0:
643                     matching_ids = True
644                     row.label(
645                       text="[{}]".format(ob.name)
646                           if ob_act and ob.name == ob_act.name else ob.name,
647                       icon=icon)
648                     row.label(text="%s" % ob.pass_index)
649                     row = layout.row()
650
651                 elif ob and not node.use_matching_indices \
652                         and ob.pass_index > 0:
653
654                     matching_ids = True
655                     row.label(
656                       text="[{}]".format(ob.name)
657                           if ob_act and ob.name == ob_act.name else ob.name,
658                       icon=icon)
659                     row.label(text="%s" % ob.pass_index)
660                     row = layout.row()
661
662             if node.use_matching_indices and not matching_ids:
663                 row.label(text="No objects with ID %s" % node.index, icon="INFO")
664
665             layout.separator()
666
667         if show_ma_id:
668             split = layout.split()
669             col = split.column()
670             col.label(text="Material Name")
671             split.label(text="ID Number")
672             row = layout.row()
673
674             for ma in materials:
675                 icon = "BLANK1"
676                 if ma.use_nodes:
677                     icon = "NODETREE"
678                 elif ma.library:
679                     icon = "LIBRARY_DATA_DIRECT"
680                     if ma.is_library_indirect:
681                         icon = "LIBRARY_DATA_INDIRECT"
682
683                 if ma and node.use_matching_indices \
684                       and ma.pass_index == node.index \
685                       and ma.pass_index != 0:
686                     matching_ids = True
687                     row.label(text="%s" % ma.name, icon=icon)
688                     row.label(text="%s" % ma.pass_index)
689                     row = layout.row()
690
691                 elif ma and not node.use_matching_indices \
692                         and ma.pass_index > 0:
693
694                     matching_ids = True
695                     row.label(text="%s" % ma.name, icon=icon)
696                     row.label(text="%s" % ma.pass_index)
697                     row = layout.row()
698
699             if node.use_matching_indices and not matching_ids:
700                 row.label(text="No materials with ID %s" % node.index, icon="INFO")
701
702
703 # // FEATURE: OB/MA ID panel in Node Editor
704
705 # FEATURE: Unsimplify on render
706 @persistent
707 def unsimplify_render_pre(scene):
708     render = scene.render
709     scene.simplify_status = render.use_simplify
710
711     if scene.use_unsimplify_render:
712         render.use_simplify = False
713
714 @persistent
715 def unsimplify_render_post(scene):
716     render = scene.render
717     render.use_simplify = scene.simplify_status
718
719 def unsimplify_ui(self,context):
720     scene = bpy.context.scene
721     self.layout.prop(scene, 'use_unsimplify_render')
722 # //FEATURE: Unsimplify on render
723
724 # FEATURE: Extra Info Stats
725 def stats_scene(self, context):
726
727     preferences = context.user_preferences.addons[__name__].preferences
728
729     if preferences.use_scene_stats:
730         scenes_count = str(len(bpy.data.scenes))
731         cameras_count = str(len(bpy.data.cameras))
732         cameras_selected = 0
733         meshlights = 0
734         meshlights_visible = 0
735     
736         for ob in context.scene.objects:
737             if ob.material_slots:
738                 for ma in ob.material_slots:
739                     if ma.material:
740                         if ma.material.node_tree:
741                             for no in ma.material.node_tree.nodes:
742                                 if no.type == 'EMISSION':
743                                     for ou in no.outputs:
744                                         if ou.links:
745                                             meshlights = meshlights + 1
746                                             if ob in context.visible_objects:
747                                                 meshlights_visible = meshlights_visible + 1
748                                             break
749             if ob in context.selected_objects:
750                 if ob.type == 'CAMERA':
751                     cameras_selected = cameras_selected + 1
752     
753         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
754     
755         row = self.layout.row(align=True)
756         row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
757                    scenes_count, cameras_selected, cameras_count,
758                    meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
759
760 # //FEATURE: Extra Info Stats
761
762 # FEATURE: Camera Bounds as Render Border
763 class VIEW3D_OT_render_border_camera(Operator):
764     """Set camera bounds as render border"""
765     bl_idname = "view3d.render_border_camera"
766     bl_label = "Camera as Render Border"
767
768     @classmethod
769     def poll(cls, context):
770         return context.space_data.region_3d.view_perspective == 'CAMERA'
771
772     def execute(self, context):
773         render = context.scene.render
774         render.use_border = True
775         render.border_min_x = 0
776         render.border_min_y = 0
777         render.border_max_x = 1
778         render.border_max_y = 1
779
780         return {'FINISHED'}
781
782 def button_render_border_camera(self, context):
783
784     view3d = context.space_data.region_3d
785     
786     if view3d.view_perspective == 'CAMERA':
787         layout = self.layout
788         layout.separator()
789         layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
790                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
791
792 # //FEATURE: Camera Bounds as Render Border
793
794 # FEATURE: Passepartout options on W menu
795 def button_camera_passepartout(self, context):
796
797     view3d = context.space_data.region_3d
798     cam = context.scene.camera.data
799     
800     if view3d.view_perspective == 'CAMERA':
801         layout = self.layout
802         if cam.show_passepartout:
803             layout.prop(cam, "passepartout_alpha", text="Passepartout")
804         else:
805             layout.prop(cam, "show_passepartout")
806
807 # FEATURE: Show Only Render with Alt+Shift+Z
808 class VIEW3D_OT_show_only_render(Operator):
809     bl_idname = "view3d.show_only_render"
810     bl_label = "Show Only Render"
811
812     def execute(self, context):
813         space = bpy.context.space_data
814         
815         if space.show_only_render:
816             space.show_only_render = False
817         else:
818             space.show_only_render = True
819         return {'FINISHED'}
820
821
822 # FEATURE: Display Active Image Node on Image Editor
823 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
824 image_nodes = {"CompositorNodeImage",
825                "ShaderNodeTexImage",
826                "ShaderNodeTexEnvironment"}
827
828 class NODE_OT_show_active_node_image(Operator):
829     """Show active image node image in the image editor"""
830     bl_idname = "node.show_active_node_image"
831     bl_label = "Show Active Node Node"
832     bl_options = {'UNDO'}
833
834     def execute(self, context):
835         preferences = context.user_preferences.addons[__name__].preferences
836         if preferences.use_image_node_display:
837             if context.active_node:
838                 active_node = context.active_node
839                 if active_node.bl_idname in image_nodes and active_node.image:
840                     for area in context.screen.areas:
841                         if area.type == "IMAGE_EDITOR":
842                             for space in area.spaces:
843                                 if space.type == "IMAGE_EDITOR":
844                                     space.image = active_node.image
845                             break
846     
847         return {'FINISHED'}
848 # // FEATURE: Display Active Image Node on Image Editor
849
850 # FEATURE: Select Meshlights
851 class OBJECT_OT_select_meshlights(Operator):
852     """Select light emitting meshes"""
853     bl_idname = "object.select_meshlights"
854     bl_label = "Select Meshlights"
855     bl_options = {'UNDO'}
856
857     @classmethod
858     def poll(cls, context):
859         return context.scene.render.engine == 'CYCLES'
860
861     def execute(self, context):
862         # Deselect everything first
863         bpy.ops.object.select_all(action='DESELECT')
864
865         for ob in context.scene.objects:
866             if ob.material_slots:
867                 for ma in ob.material_slots:
868                     if ma.material:
869                         if ma.material.node_tree:
870                             for no in ma.material.node_tree.nodes:
871                                 if no.type == 'EMISSION':
872                                     ob.select = True
873                                     context.scene.objects.active = ob
874
875         if not context.selected_objects and not context.scene.objects.active:
876             self.report({'INFO'}, "No meshlights to select")
877
878         return {'FINISHED'}
879
880 def button_select_meshlights(self, context):
881     
882     if context.scene.render.engine == 'CYCLES':
883         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
884 # // FEATURE: Select Meshlights
885
886 # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
887 class MESH_OT_find_asymmetric(Operator):
888     """
889     Find asymmetric vertices
890     """
891
892     bl_idname = "mesh.find_asymmetric"
893     bl_label = "Find Asymmetric"
894     bl_options = {'UNDO', 'REGISTER'}
895
896     @classmethod
897     def poll(cls, context):
898         object = context.object
899         if object:
900             return object.mode == 'EDIT' and object.type == 'MESH'
901         return False
902
903     def execute(self, context):
904         threshold = 1e-6
905
906         object = context.object
907         bm = bmesh.from_edit_mesh(object.data)
908
909         # Deselect all the vertices
910         for v in bm.verts:
911             v.select = False
912
913         for v1 in bm.verts:
914             if abs(v1.co[0]) < threshold:
915                 continue
916
917             mirror_found = False
918             for v2 in bm.verts:
919                 if v1 == v2:
920                     continue
921                 if v1.co[0] * v2.co[0] > 0.0:
922                     continue
923
924                 mirror_coord = Vector(v2.co)
925                 mirror_coord[0] *= -1
926                 if (mirror_coord - v1.co).length_squared < threshold:
927                     mirror_found = True
928                     break
929             if not mirror_found:
930                 v1.select = True
931
932         bm.select_flush_mode()
933
934         bmesh.update_edit_mesh(object.data)
935
936         return {'FINISHED'}
937
938 class MESH_OT_make_symmetric(Operator):
939     """
940     Make symmetric
941     """
942
943     bl_idname = "mesh.make_symmetric"
944     bl_label = "Make Symmetric"
945     bl_options = {'UNDO', 'REGISTER'}
946
947     @classmethod
948     def poll(cls, context):
949         object = context.object
950         if object:
951             return object.mode == 'EDIT' and object.type == 'MESH'
952         return False
953
954     def execute(self, context):
955         threshold = 1e-6
956
957         object = context.object
958         bm = bmesh.from_edit_mesh(object.data)
959
960         for v1 in bm.verts:
961             if v1.co[0] < threshold:
962                 continue
963             if not v1.select:
964                 continue
965
966             closest_vert = None
967             closest_distance = -1
968             for v2 in bm.verts:
969                 if v1 == v2:
970                     continue
971                 if v2.co[0] > threshold:
972                     continue
973                 if not v2.select:
974                     continue
975
976                 mirror_coord = Vector(v2.co)
977                 mirror_coord[0] *= -1
978                 distance = (mirror_coord - v1.co).length_squared
979                 if closest_vert is None or distance < closest_distance:
980                     closest_distance = distance
981                     closest_vert = v2
982
983             if closest_vert:
984                 closest_vert.select = False
985                 closest_vert.co = Vector(v1.co)
986                 closest_vert.co[0] *= -1
987             v1.select = False
988
989         for v1 in bm.verts:
990             if v1.select:
991                 closest_vert = None
992                 closest_distance = -1
993                 for v2 in bm.verts:
994                     if v1 != v2:
995                         mirror_coord = Vector(v2.co)
996                         mirror_coord[0] *= -1
997                         distance = (mirror_coord - v1.co).length_squared
998                         if closest_vert is None or distance < closest_distance:
999                             closest_distance = distance
1000                             closest_vert = v2
1001                 if closest_vert:
1002                     v1.select = False
1003                     v1.co = Vector(closest_vert.co)
1004                     v1.co[0] *= -1
1005
1006         bm.select_flush_mode()
1007         bmesh.update_edit_mesh(object.data)
1008
1009         return {'FINISHED'}
1010 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1011
1012 # FEATURE: Cycles Render Sampling Extra
1013 def render_cycles_scene_samples(self, context):
1014
1015     layout = self.layout
1016
1017     scenes = bpy.data.scenes
1018     scene = context.scene
1019     cscene = scene.cycles
1020
1021     col = layout.column(align=True)
1022
1023     if len(scene.render.layers) == 1 and \
1024         scene.render.layers[0].samples == 0:
1025         pass
1026     else:
1027         col.separator()
1028         col.label(text="Samples Per RenderLayer:")
1029
1030         for rl in scene.render.layers:
1031             row = col.row(align=True)
1032             row.label(rl.name)
1033             row.prop(rl, "samples", text="%s" %
1034                 "Samples" if rl.samples > 0 else "Samples [Auto]")
1035
1036     if (len(bpy.data.scenes) > 1):
1037         col.separator()
1038
1039         col.label(text="Samples Per Scene:")
1040         row = col.row(align=True)
1041
1042         if cscene.progressive == 'PATH':
1043             for s in bpy.data.scenes:
1044                 if s != scene:
1045                     row = col.row(align=True)
1046                     if s.render.engine == 'CYCLES':
1047                         cscene = s.cycles
1048
1049                         row.label(s.name)
1050                         row.prop(cscene, "samples")
1051                     else:
1052                         row.label(text="Scene: '%s' is not using Cycles" % s.name)
1053         else:
1054             for s in bpy.data.scenes:
1055                 if s != scene:
1056                     row = col.row(align=True)
1057                     if s.render.engine == 'CYCLES':
1058                         cscene = s.cycles
1059
1060                         row.label(s.name)
1061                         row.prop(cscene, "aa_samples",
1062                             text="AA Samples")
1063                     else:
1064                         row.label(text="Scene: '%s' is not using Cycles" % s.name)
1065
1066 # // FEATURE: Dupli  Group Path
1067
1068 # // FEATURE: Cycles Render Sampling Extra
1069
1070 # FEATURE: Motion Paths Extras
1071 class POSE_OT_paths_clear_all(Operator):
1072     """Clear motion paths from all bones"""
1073     bl_idname = "pose.paths_clear_all"
1074     bl_label = "Clear All Motion Paths"
1075     bl_options = {'UNDO'}
1076
1077     @classmethod
1078     def poll(cls, context):
1079         return context.mode == 'POSE'
1080
1081     def execute(self, context):
1082         #silly but works
1083         for b in context.object.data.bones:
1084             b.select = True
1085             bpy.ops.pose.paths_clear()
1086             b.select = False
1087         return {'FINISHED'}
1088
1089 class POSE_OT_paths_frame_match(Operator):
1090     """Match Start/End frame of scene to motion path range"""
1091     bl_idname = "pose.paths_frame_match"
1092     bl_label = "Match Frame Range"
1093     bl_options = {'UNDO'}
1094
1095     def execute(self, context):
1096         avs = context.object.pose.animation_visualization
1097         scene = context.scene
1098
1099         if avs.motion_path.type == 'RANGE':
1100             if scene.use_preview_range:
1101                 avs.motion_path.frame_start = scene.frame_preview_start
1102                 avs.motion_path.frame_end = scene.frame_preview_end
1103             else:
1104                 avs.motion_path.frame_start = scene.frame_start
1105                 avs.motion_path.frame_end = scene.frame_end
1106
1107         else:
1108             if scene.use_preview_range:
1109                 avs.motion_path.frame_before = scene.frame_preview_start
1110                 avs.motion_path.frame_after = scene.frame_preview_end
1111             else:
1112                 avs.motion_path.frame_before = scene.frame_start
1113                 avs.motion_path.frame_after = scene.frame_end
1114
1115         return {'FINISHED'}
1116
1117 def pose_motion_paths_ui(self, context):
1118
1119     layout = self.layout
1120     scene = context.scene
1121     avs = context.object.pose.animation_visualization
1122     if context.active_pose_bone:
1123         mpath = context.active_pose_bone.motion_path
1124     layout.separator()    
1125     layout.label(text="Motion Paths Extras:")
1126
1127     split = layout.split()
1128
1129     col = split.column(align=True)
1130
1131     if context.selected_pose_bones:
1132         if mpath:
1133             sub = col.row(align=True)
1134             sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
1135             sub.operator("pose.paths_clear", text="", icon='X')
1136         else:
1137             col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
1138     else:
1139         col.label(text="Select Bones First", icon="ERROR")
1140
1141     col = split.column(align=True)
1142     col.operator(POSE_OT_paths_frame_match.bl_idname,
1143         text="{}".format( "Set Preview Frame Range"
1144                 if scene.use_preview_range else "Set Frame Range"),
1145         icon="{}".format("PREVIEW_RANGE"
1146                 if scene.use_preview_range else "TIME"))
1147
1148     col = layout.column()
1149     row = col.row(align=True)
1150
1151     if avs.motion_path.type == 'RANGE':
1152         row.prop(avs.motion_path, "frame_start", text="Start")
1153         row.prop(avs.motion_path, "frame_end", text="End")
1154     else:
1155         row.prop(avs.motion_path, "frame_before", text="Before")
1156         row.prop(avs.motion_path, "frame_after", text="After")
1157
1158     layout.separator()
1159     layout.operator(POSE_OT_paths_clear_all.bl_idname, icon="X")
1160 # // FEATURE: Motion Paths Extras
1161
1162 # FEATURE: Final Render Resolution Display
1163 def render_final_resolution_ui(self, context):
1164
1165     rd = context.scene.render
1166     layout = self.layout
1167
1168     final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
1169     final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
1170
1171     if rd.use_border:
1172        final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
1173        final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
1174        layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
1175              str(final_res_x)[:-2], str(final_res_y)[:-2],
1176              str(final_res_x_border), str(final_res_y_border)))
1177     else:
1178         layout.label(text="Final Resolution: {} x {}".format(
1179              str(final_res_x)[:-2], str(final_res_y)[:-2]))
1180 # // FEATURE: Final Render Resolution Display
1181
1182 # FEATURE: Shader Nodes Extra Info
1183 def node_shader_extra(self, context):
1184
1185     if context.space_data.tree_type == 'ShaderNodeTree':
1186         ob = context.active_object
1187         snode = context.space_data
1188         layout = self.layout
1189
1190         if ob and snode.shader_type != 'WORLD':
1191             if ob.type == 'LAMP':
1192                 layout.label(text="%s" % ob.name,
1193                              icon="LAMP_%s" % ob.data.type)        
1194             else:
1195                 layout.label(text="%s" % ob.name,
1196                              icon="OUTLINER_DATA_%s" % ob.type)
1197              
1198
1199 # // FEATURE: Shader Nodes Extra Info
1200
1201 # FEATURE: Scene Debug
1202 class SCENE_OT_cycles_shader_list_nodes(Operator):
1203     """List Cycles materials containing a specific shader"""
1204     bl_idname = "scene.cycles_list_nodes"
1205     bl_label = "List Materials"
1206     materials = []
1207
1208     @classmethod
1209     def poll(cls, context):
1210         return context.scene.render.engine == 'CYCLES'
1211
1212     def execute(self, context):
1213         node_type = context.scene.amaranth_cycles_node_types
1214         roughness = False
1215         self.__class__.materials = []
1216
1217         print("\n=== Cycles Shader Type: %s === \n" % node_type)
1218
1219         for ma in bpy.data.materials:
1220             if ma.node_tree:
1221                 nodes = ma.node_tree.nodes
1222                 for no in nodes:
1223                     if no.type == node_type:
1224                         for ou in no.outputs:
1225                             if ou.links:
1226                                 if no.type in ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']:
1227                                     roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
1228                                 else:
1229                                     roughness = False
1230                             else:
1231                                 print('Note: \nOutput from "%s" node' % node_type,
1232                                       'in material "%s"' % ma.name,
1233                                       'not connected',
1234                                       '\n')
1235
1236                             self.__class__.materials = sorted(list(set(self.__class__.materials)))
1237
1238                             if ma.name not in self.__class__.materials:
1239                                 self.__class__.materials.append('%s%s [%s] %s%s' % (
1240                                     '[L] ' if ma.library else '',
1241                                     ma.name, ma.users,
1242                                     '[F]' if ma.use_fake_user else '',
1243                                     ' - [%s]' % roughness if roughness else ''))
1244
1245         if len(self.__class__.materials) == 0:
1246             self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
1247         else:
1248             print("* A total of %d %s using %s were found \n" % (
1249                     len(self.__class__.materials),
1250                     "material" if len(self.__class__.materials) == 1 else "materials",
1251                     node_type))
1252
1253             count = 0
1254
1255             for mat in self.__class__.materials:
1256                 print('%02d. %s' % (count+1, self.__class__.materials[count]))
1257                 count += 1
1258             print("\n")
1259
1260         self.__class__.materials = sorted(list(set(self.__class__.materials)))
1261
1262         return {'FINISHED'}
1263
1264 class SCENE_OT_cycles_shader_list_nodes_clear(Operator):
1265     """Clear the list below"""
1266     bl_idname = "scene.cycles_list_nodes_clear"
1267     bl_label = "Clear Materials List"
1268     
1269     def execute(self, context):
1270         SCENE_OT_cycles_shader_list_nodes.materials[:] = []
1271         print("* Cleared Cycles Materials List")
1272         return {'FINISHED'}
1273
1274 class SCENE_OT_amaranth_debug_lamp_select(Operator):
1275     '''Select Lamp'''
1276     bl_idname = "scene.amaranth_debug_lamp_select"
1277     bl_label = "Select Lamp"
1278     lamp = bpy.props.StringProperty()
1279  
1280     def execute(self, context):
1281         if self.lamp:
1282             lamp = bpy.data.objects[self.lamp]
1283
1284             bpy.ops.object.select_all(action='DESELECT')
1285             lamp.select = True
1286             context.scene.objects.active = lamp
1287
1288         return{'FINISHED'}
1289
1290 class SCENE_OT_list_missing_node_links(Operator):
1291     '''Print a list of missing node links'''
1292     bl_idname = "scene.list_missing_node_links"
1293     bl_label = "List Missing Node Links"
1294
1295     count_groups = 0
1296     count_images = 0
1297
1298     def execute(self, context):
1299         missing_groups = []
1300         missing_images = []
1301         libraries = []
1302         self.__class__.count_groups = 0
1303         self.__class__.count_images = 0
1304
1305         for ma in bpy.data.materials:
1306             if ma.node_tree:
1307                 for no in ma.node_tree.nodes:
1308                     if no.type == 'GROUP':
1309                         if not no.node_tree:
1310                             self.__class__.count_groups += 1
1311
1312                             users_ngroup = []
1313
1314                             for ob in bpy.data.objects:
1315                                 if ob.material_slots and ma.name in ob.material_slots:
1316                                     users_ngroup.append("%s%s%s" % (
1317                                         "[L] " if ob.library else "",
1318                                         "[F] " if ob.use_fake_user else "",
1319                                         ob.name))
1320
1321                             missing_groups.append("NG: %s%s%s [%s]%s%s\n" % (
1322                                 "[L] " if ma.library else "",
1323                                 "[F] " if ma.use_fake_user else "",
1324                                 ma.name, ma.users,
1325                                 "\nLI: %s" % 
1326                                 ma.library.filepath if ma.library else "",
1327                                 "\nOB: %s" % ',  '.join(users_ngroup) if users_ngroup else ""))
1328
1329                             if ma.library:
1330                                 libraries.append(ma.library.filepath)
1331                     if no.type == 'TEX_IMAGE':
1332                         if no.image:
1333                             import os.path
1334                             image_path_exists = os.path.exists(
1335                                                     bpy.path.abspath(
1336                                                         no.image.filepath, library=no.image.library))
1337
1338                         if not no.image or not image_path_exists:
1339                             self.__class__.count_images += 1
1340
1341                             users_images = []
1342
1343                             for ob in bpy.data.objects:
1344                                 if ob.material_slots and ma.name in ob.material_slots:
1345                                     users_images.append("%s%s%s" % (
1346                                         "[L] " if ob.library else "",
1347                                         "[F] " if ob.use_fake_user else "",
1348                                         ob.name))
1349
1350                             missing_images.append("MA: %s%s%s [%s]%s%s%s%s\n" % (
1351                                 "[L] " if ma.library else "",
1352                                 "[F] " if ma.use_fake_user else "",
1353                                 ma.name, ma.users,
1354                                 "\nLI: %s" % 
1355                                 ma.library.filepath if ma.library else "",
1356                                 "\nIM: %s" % no.image.name if no.image else "",
1357                                 "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
1358                                 "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
1359
1360                             if ma.library:
1361                                 libraries.append(ma.library.filepath)
1362
1363         # Remove duplicates and sort
1364         missing_groups = sorted(list(set(missing_groups)))
1365         missing_images = sorted(list(set(missing_images)))
1366         libraries = sorted(list(set(libraries)))
1367
1368         print("\n\n== %s missing image %s and %s missing node %s ==" %
1369             ("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
1370             "node" if self.__class__.count_images == 1 else "nodes",
1371             "no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
1372             "group" if self.__class__.count_groups == 1 else "groups"))
1373
1374         # List Missing Node Groups
1375         if missing_groups:
1376             print("\n* Missing Node Group Links [NG]\n")
1377             for mig in missing_groups:
1378                 print(mig)
1379
1380         # List Missing Image Nodes
1381         if missing_images:
1382             print("\n* Missing Image Nodes Link [IM]\n")
1383
1384             for mii in missing_images:
1385                 print(mii)
1386
1387         if missing_groups or missing_images:
1388             if libraries:
1389                 print("\nThat's bad, run check on %s:" % (
1390                     "this library" if len(libraries) == 1 else "these libraries"))
1391                 for li in libraries:
1392                     print(li)
1393         else:
1394             self.report({"INFO"}, "Yay! No missing node links")            
1395
1396         print("\n")
1397
1398         if missing_groups and missing_images:
1399             self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
1400                 (self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
1401                 self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))
1402
1403         return{'FINISHED'}
1404
1405 class SCENE_OT_list_missing_material_slots(Operator):
1406     '''List objects with empty material slots'''
1407     bl_idname = "scene.list_missing_material_slots"
1408     bl_label = "List Empty Material Slots"
1409  
1410     objects = []
1411  
1412     def execute(self, context):
1413         self.__class__.objects = []
1414
1415         for ob in bpy.data.objects:
1416             for ma in ob.material_slots:
1417                 if not ma.material:
1418                     self.__class__.objects.append('%s%s' % (
1419                         '[L] ' if ob.library else '',
1420                         ob.name))
1421         self.__class__.objects = sorted(list(set(self.__class__.objects)))
1422
1423         if len(self.__class__.objects) == 0:
1424             self.report({"INFO"}, "No objects with empty material slots found")
1425         else:
1426             print("\n* A total of %d %s with empty material slots was found \n" % (
1427                     len(self.__class__.objects),
1428                     "object" if len(self.__class__.objects) == 1 else "objects"))
1429
1430             count = 0
1431
1432             for obs in self.__class__.objects:
1433                 print('%02d. %s' % (count+1, self.__class__.objects[count]))
1434                 count += 1
1435             print("\n")
1436
1437         return{'FINISHED'}
1438
1439 class SCENE_OT_list_missing_material_slots_clear(Operator):
1440     """Clear the list below"""
1441     bl_idname = "scene.list_missing_material_slots_clear"
1442     bl_label = "Clear Empty Material Slots List"
1443     
1444     def execute(self, context):
1445         SCENE_OT_list_missing_material_slots.objects[:] = []
1446         print("* Cleared Empty Material Slots List")
1447         return {'FINISHED'}
1448
1449 class SCENE_OT_blender_instance_open(Operator):
1450     '''Open in a new Blender instance'''
1451     bl_idname = "scene.blender_instance_open"
1452     bl_label = "Open Blender Instance"
1453     filepath = bpy.props.StringProperty()
1454  
1455     def execute(self, context):
1456         if self.filepath:
1457             filepath = bpy.path.abspath(self.filepath)
1458
1459             import subprocess
1460             try:
1461                 subprocess.Popen([bpy.app.binary_path, filepath])
1462             except:
1463                 print("Error on the new Blender instance")
1464                 import traceback
1465                 traceback.print_exc()
1466
1467         return{'FINISHED'}
1468
1469 class SCENE_PT_scene_debug(Panel):
1470     '''Scene Debug'''
1471     bl_label = 'Scene Debug'
1472     bl_space_type = "PROPERTIES"
1473     bl_region_type = "WINDOW"
1474     bl_context = "scene"
1475
1476     def draw(self, context):
1477         layout = self.layout
1478         scene = context.scene
1479         objects =  bpy.data.objects
1480         ob_act = context.active_object
1481         images = bpy.data.images
1482         lamps = bpy.data.lamps
1483         images_missing = []
1484         list_lamps = scene.amaranth_debug_scene_list_lamps
1485         list_missing_images = scene.amaranth_debug_scene_list_missing_images
1486         materials = SCENE_OT_cycles_shader_list_nodes.materials
1487         materials_count = len(SCENE_OT_cycles_shader_list_nodes.materials)
1488         missing_material_slots_obs = SCENE_OT_list_missing_material_slots.objects
1489         missing_material_slots_count = len(SCENE_OT_list_missing_material_slots.objects)
1490         engine = scene.render.engine
1491
1492         # List Lamps
1493         box = layout.box()
1494         row = box.row(align=True)
1495         split = row.split()
1496         col = split.column()
1497         
1498         if lamps:
1499             row = col.row(align=True)
1500             row.alignment = 'LEFT'
1501             row.prop(scene, 'amaranth_debug_scene_list_lamps',
1502                         icon="%s" % 'TRIA_DOWN' if list_lamps else 'TRIA_RIGHT',
1503                         emboss=False)
1504
1505             if objects and list_lamps:
1506                 row = box.row(align=True)
1507                 split = row.split(percentage=0.42)
1508                 col = split.column()
1509                 col.label(text="Name")
1510
1511                 split = split.split(percentage=0.1)
1512                 col = split.column()
1513                 col.label(text="", icon="BLANK1")
1514                 if engine in ['CYCLES', 'BLENDER_RENDER']:
1515                     if engine == 'BLENDER_RENDER':
1516                         split = split.split(percentage=0.7)
1517                     else:
1518                         split = split.split(percentage=0.35)
1519                     col = split.column()
1520                     col.label(text="Samples")
1521
1522                 if engine == 'CYCLES':
1523                     split = split.split(percentage=0.35)
1524                     col = split.column()
1525                     col.label(text="Size")
1526
1527                 split = split.split(percentage=0.8)
1528                 col = split.column()
1529                 col.label(text="Visibility")
1530
1531                 for ob in objects:
1532                     if ob and ob.type == 'LAMP':
1533                         lamp = ob.data
1534                         clamp = ob.data.cycles
1535
1536                         row = box.row(align=True)
1537                         split = row.split(percentage=0.5)
1538                         col = split.column()
1539                         row = col.row()
1540                         row.alignment = 'LEFT'
1541                         row.active = ob.name in context.scene.objects
1542                         row.operator("scene.amaranth_debug_lamp_select",
1543                                     text='%s %s%s' % (
1544                                         " [L] " if ob.library else "",
1545                                         ob.name,
1546                                         "" if ob.name in context.scene.objects else " [Not in Scene]"),
1547                                     icon="LAMP_%s" % ob.data.type,
1548                                     emboss=False).lamp = ob.name
1549
1550                         if engine == 'CYCLES':
1551                             split = split.split(percentage=0.35)
1552                             col = split.column()
1553                             if scene.cycles.progressive == 'BRANCHED_PATH':
1554                                 col.prop(clamp, "samples", text="")
1555                             if scene.cycles.progressive == 'PATH':
1556                                col.label(text="N/A")
1557                            
1558                         if engine == 'BLENDER_RENDER':
1559                             split = split.split(percentage=0.7)
1560                             col = split.column()
1561                             if lamp.type == 'HEMI':
1562                                 col.label(text="Not Available")
1563                             elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
1564                                 row = col.row(align=True)
1565                                 row.prop(lamp, "shadow_ray_samples_x", text="X")
1566                                 if lamp.shape == 'RECTANGLE':
1567                                     row.prop(lamp, "shadow_ray_samples_y", text="Y")
1568                             elif lamp.shadow_method == 'RAY_SHADOW':
1569                                 col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
1570                             elif lamp.shadow_method == 'BUFFER_SHADOW':
1571                                 col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
1572                             else:
1573                                 col.label(text="No Shadow")
1574
1575                         if engine == 'CYCLES':
1576                             split = split.split(percentage=0.4)
1577                             col = split.column()    
1578                             if lamp.type in ['POINT','SUN', 'SPOT']:
1579                                 col.label(text="%.2f" % lamp.shadow_soft_size)
1580                             elif lamp.type == 'HEMI':
1581                                 col.label(text="N/A")
1582                             else:
1583                                 col.label(text="%.2f" % lamp.size)
1584
1585                         split = split.split(percentage=0.8)
1586                         col = split.column()
1587                         row = col.row(align=True)
1588                         row.prop(ob, "hide", text="", emboss=False)
1589                         row.prop(ob, "hide_render", text="", emboss=False)
1590
1591                         split = split.split(percentage=0.3)
1592                         col = split.column()
1593                         col.label(text="", icon="%s" % "TRIA_LEFT" if ob == ob_act else "BLANK1")
1594
1595         else:
1596             row = col.row(align=True)
1597             row.alignment = 'LEFT'
1598             row.label(text="Lamps List", icon="RIGHTARROW_THIN")
1599
1600             split = split.split()
1601             col = split.column()
1602
1603             col.label(text="No Lamps", icon="LAMP_DATA")
1604
1605         # List Missing Images
1606         box = layout.box()
1607         row = box.row(align=True)
1608         split = row.split()
1609         col = split.column()
1610
1611         if images:
1612             import os.path
1613
1614             for im in images:
1615                 if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']: 
1616                     if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
1617                         images_missing.append(["%s%s [%s]%s" % (
1618                             '[L] ' if im.library else '',
1619                             im.name, im.users,
1620                             ' [F]' if im.use_fake_user else ''),
1621                             im.filepath if im.filepath else 'No Filepath',
1622                             im.library.filepath if im.library else ''])
1623
1624             if images_missing:
1625                 row = col.row(align=True)
1626                 row.alignment = 'LEFT'
1627                 row.prop(scene, 'amaranth_debug_scene_list_missing_images',
1628                             icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
1629                             emboss=False)
1630
1631                 split = split.split()
1632                 col = split.column()
1633
1634                 col.label(text="%s missing %s" % (
1635                              str(len(images_missing)),
1636                              'image' if len(images_missing) == 1 else 'images'),
1637                              icon="ERROR")
1638
1639                 if list_missing_images:
1640                     col = box.column(align=True)
1641                     for mis in images_missing:
1642                         col.label(text=mis[0],
1643                          icon="IMAGE_DATA")
1644                         col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
1645                         if mis[2]:
1646                             row = col.row(align=True)
1647                             row.alignment = "LEFT"
1648                             row.operator(SCENE_OT_blender_instance_open.bl_idname,
1649                                          text=mis[2],
1650                                          icon="LINK_BLEND",
1651                                          emboss=False).filepath=mis[2]
1652                         col.separator()
1653             else:
1654                 row = col.row(align=True)
1655                 row.alignment = 'LEFT'
1656                 row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")
1657
1658                 split = split.split()
1659                 col = split.column()
1660
1661                 col.label(text="%s %s loading correctly" % (
1662                              str(len(images)),
1663                              'image' if len(images) == 1 else 'images'),
1664                              icon="IMAGE_DATA")
1665         else:
1666             row = col.row(align=True)
1667             row.alignment = 'LEFT'
1668             row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
1669
1670         # List Cycles Materials by Shader
1671         if engine == 'CYCLES':
1672             box = layout.box()
1673             split = box.split()
1674             col = split.column(align=True)
1675             col.prop(scene, 'amaranth_cycles_node_types',
1676                 icon="MATERIAL")
1677
1678             row = split.row(align=True)
1679             row.operator(SCENE_OT_cycles_shader_list_nodes.bl_idname,
1680                             icon="SORTSIZE",
1681                             text="List Materials Using Shader")
1682             if materials_count != 0: 
1683                 row.operator(SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
1684                                 icon="X", text="")
1685             col.separator()
1686
1687             try:
1688                 materials
1689             except NameError:
1690                 pass
1691             else:
1692                 if materials_count != 0: 
1693                     col = box.column(align=True)
1694                     count = 0
1695                     col.label(text="%s %s found" % (materials_count,
1696                         'material' if materials_count == 1 else 'materials'), icon="INFO")
1697                     for mat in materials:
1698                         count += 1
1699                         col.label(text='%s' % (materials[count-1]), icon="MATERIAL")
1700
1701         # List Missing Node Trees
1702         box = layout.box()
1703         row = box.row(align=True)
1704         split = row.split()
1705         col = split.column(align=True)
1706
1707         split = col.split()
1708         split.label(text="Node Links")
1709         split.operator(SCENE_OT_list_missing_node_links.bl_idname,
1710                         icon="NODETREE")
1711
1712         if SCENE_OT_list_missing_node_links.count_groups != 0 or \
1713             SCENE_OT_list_missing_node_links.count_images != 0:
1714             col.label(text="Warning! Check Console", icon="ERROR")
1715
1716         if SCENE_OT_list_missing_node_links.count_groups != 0:
1717             col.label(text="%s" % ("%s node %s missing link" % (
1718                      str(SCENE_OT_list_missing_node_links.count_groups),
1719                      "group" if SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
1720                      icon="NODETREE")
1721         if SCENE_OT_list_missing_node_links.count_images != 0:
1722             col.label(text="%s" % ("%s image %s missing link" % (
1723                      str(SCENE_OT_list_missing_node_links.count_images),
1724                      "node" if SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
1725                      icon="IMAGE_DATA")
1726
1727         col.separator()
1728
1729         # List Empty Materials Slots
1730         box = layout.box()
1731         split = box.split()
1732         col = split.column(align=True)
1733         col.label(text="Material Slots")
1734
1735         row = split.row(align=True)
1736         row.operator(SCENE_OT_list_missing_material_slots.bl_idname,
1737                         icon="MATERIAL",
1738                         text="List Empty Materials Slots")
1739         if missing_material_slots_count != 0: 
1740             row.operator(SCENE_OT_list_missing_material_slots_clear.bl_idname,
1741                             icon="X", text="")
1742         col.separator()
1743
1744         try:
1745             missing_material_slots_obs
1746         except NameError:
1747             pass
1748         else:
1749             if missing_material_slots_count != 0: 
1750                 col = box.column(align=True)
1751                 count = 0
1752                 col.label(text="%s %s with empty material slots found" % (
1753                     missing_material_slots_count,
1754                     'object' if missing_material_slots_count == 1 else 'objects'),
1755                     icon="INFO")
1756
1757                 for obs in missing_material_slots_obs:
1758                     count += 1
1759                     col.label(text='%s' % (
1760                         missing_material_slots_obs[count-1]),
1761                         icon="OBJECT_DATA")
1762
1763 # // FEATURE: Scene Debug
1764 # FEATURE: Dupli  Group Path
1765 def ui_dupli_group_library_path(self, context):
1766
1767     ob = context.object
1768     lib = ob.dupli_group.library.filepath
1769
1770     row = self.layout.row()
1771     row.alignment = 'LEFT'
1772
1773     if ob and ob.dupli_group and ob.dupli_group.library:
1774         row.operator(SCENE_OT_blender_instance_open.bl_idname,
1775             text="Library: %s" % lib,
1776             emboss=False,
1777             icon="LINK_BLEND").filepath=lib
1778
1779 # // FEATURE: Dupli  Group Path
1780 # FEATURE: Color Management Presets
1781 class SCENE_MT_color_management_presets(Menu):
1782     """List of Color Management presets"""
1783     bl_label = "Color Management Presets"
1784     preset_subdir = "color"
1785     preset_operator = "script.execute_preset"
1786     draw = Menu.draw_preset
1787
1788
1789 class AddPresetColorManagement(AddPresetBase, Operator):
1790     """Add or remove a Color Management preset"""
1791     bl_idname = "scene.color_management_preset_add"
1792     bl_label = "Add Color Management Preset"
1793     preset_menu = "SCENE_MT_color_management_presets"
1794
1795     preset_defines = [
1796         "scene = bpy.context.scene"
1797     ]
1798
1799     preset_values = [
1800         "scene.view_settings.view_transform",
1801         "scene.display_settings.display_device",
1802         "scene.view_settings.exposure",
1803         "scene.view_settings.gamma",
1804         "scene.view_settings.look",
1805         "scene.view_settings.use_curve_mapping",
1806         "scene.sequencer_colorspace_settings.name",
1807     ]
1808
1809     preset_subdir = "color"
1810
1811 def ui_color_management_presets(self, context):
1812     
1813     layout = self.layout
1814
1815     row = layout.row(align=True)
1816     row.menu("SCENE_MT_color_management_presets", text=bpy.types.SCENE_MT_color_management_presets.bl_label)
1817     row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
1818     row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
1819     layout.separator()
1820 # // FEATURE: Color Management Presets
1821
1822 # FEATURE: Sequencer Extra Info
1823 def act_strip(context):
1824     try:
1825         return context.scene.sequence_editor.active_strip
1826     except AttributeError:
1827         return None
1828
1829 def ui_sequencer_extra_info(self, context):
1830
1831     layout = self.layout
1832     strip = act_strip(context)
1833
1834     if strip:
1835         seq_type = strip.type
1836
1837         if seq_type and seq_type == 'IMAGE':
1838             elem = strip.strip_elem_from_frame(context.scene.frame_current)
1839             if elem:
1840                 layout.label(text="%s %s" % (
1841                     elem.filename,
1842                     "[%s]" % (context.scene.frame_current - strip.frame_start)))
1843 # // FEATURE: Sequencer Extra Info
1844
1845 classes = (SCENE_MT_color_management_presets,
1846            AddPresetColorManagement,
1847            SCENE_PT_scene_debug,
1848            SCENE_OT_refresh,
1849            SCENE_OT_cycles_shader_list_nodes,
1850            SCENE_OT_cycles_shader_list_nodes_clear,
1851            SCENE_OT_amaranth_debug_lamp_select,
1852            SCENE_OT_list_missing_node_links,
1853            SCENE_OT_list_missing_material_slots,
1854            SCENE_OT_list_missing_material_slots_clear,
1855            SCENE_OT_blender_instance_open,
1856            WM_OT_save_reload,
1857            MESH_OT_find_asymmetric,
1858            MESH_OT_make_symmetric,
1859            NODE_OT_AddTemplateVignette,
1860            NODE_MT_amaranth_templates,
1861            FILE_OT_directory_current_blend,
1862            FILE_OT_directory_go_to,
1863            NODE_PT_indices,
1864            NODE_PT_simplify,
1865            NODE_OT_toggle_mute,
1866            NODE_OT_show_active_node_image,
1867            VIEW3D_OT_render_border_camera,
1868            VIEW3D_OT_show_only_render,
1869            OBJECT_OT_select_meshlights,
1870            POSE_OT_paths_clear_all,
1871            POSE_OT_paths_frame_match,
1872            FILE_PT_libraries)
1873
1874 addon_keymaps = []
1875
1876 def register():
1877
1878     bpy.utils.register_class(AmaranthToolsetPreferences)
1879
1880     # UI: Register the panel
1881     init_properties()
1882     for c in classes:
1883         bpy.utils.register_class(c)
1884
1885     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
1886     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
1887     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
1888
1889     bpy.types.INFO_MT_file.append(button_save_reload)
1890     bpy.types.INFO_HT_header.append(stats_scene)
1891
1892     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
1893     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
1894     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
1895
1896     bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
1897
1898     bpy.types.NODE_HT_header.append(node_templates_pulldown)
1899     bpy.types.NODE_HT_header.append(node_stats)
1900     bpy.types.NODE_HT_header.append(node_shader_extra)
1901
1902     bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
1903
1904     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
1905
1906     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
1907     bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
1908
1909     bpy.types.DATA_PT_display.append(pose_motion_paths_ui)
1910
1911     bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
1912
1913     bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)
1914
1915     bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)
1916
1917     bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)
1918
1919     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
1920     bpy.app.handlers.render_post.append(unsimplify_render_post)
1921
1922     wm = bpy.context.window_manager
1923     kc = wm.keyconfigs.addon
1924     if kc:
1925         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
1926         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
1927         km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
1928
1929         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
1930         kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS')
1931         kmi.properties.name = "NODE_MT_amaranth_templates"
1932
1933         km = kc.keymaps.new(name='Window')
1934         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
1935         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
1936
1937         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
1938         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
1939
1940         km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
1941         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
1942         kmi.properties.data_path = 'area.type'
1943         kmi.properties.value = 'DOPESHEET_EDITOR'
1944
1945         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
1946         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
1947         kmi.properties.data_path = 'area.type'
1948         kmi.properties.value = 'GRAPH_EDITOR'
1949
1950         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
1951         kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
1952         kmi.properties.data_path = 'space_data.mode'
1953         kmi.properties.value_1 = 'ACTION'
1954         kmi.properties.value_2 = 'DOPESHEET'
1955
1956         addon_keymaps.append((km, kmi))
1957
1958 def unregister():
1959
1960     bpy.utils.unregister_class(AmaranthToolsetPreferences)
1961
1962     for c in classes:
1963         bpy.utils.unregister_class(c)
1964
1965     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
1966     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
1967     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
1968
1969     bpy.types.INFO_MT_file.remove(button_save_reload)
1970     bpy.types.INFO_HT_header.remove(stats_scene)
1971
1972     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
1973     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
1974     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
1975
1976     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
1977
1978     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
1979     bpy.types.NODE_HT_header.remove(node_stats)
1980     bpy.types.NODE_HT_header.remove(node_shader_extra)
1981
1982     bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
1983
1984     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
1985
1986     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
1987     bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
1988
1989     bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)
1990
1991     bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
1992
1993     bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)
1994
1995     bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)
1996
1997     bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)
1998
1999     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
2000     bpy.app.handlers.render_post.remove(unsimplify_render_post)
2001
2002     for km, kmi in addon_keymaps:
2003         km.keymap_items.remove(kmi)
2004     addon_keymaps.clear()
2005     
2006     clear_properties()
2007
2008 if __name__ == "__main__":
2009     register()