f251bdb79b5dcf0165a1eea40c1c72a45ff9a226
[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, 8),
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, EnumProperty,
36                        FloatProperty, IntProperty,
37                        StringProperty)
38 from mathutils import Vector
39 from bpy.app.handlers import persistent
40 from bl_operators.presets import AddPresetBase
41
42 # Preferences
43 class AmaranthToolsetPreferences(AddonPreferences):
44     bl_idname = __name__
45     use_frame_current = BoolProperty(
46             name="Current Frame Slider",
47             description="Set the current frame from the Specials menu in the 3D View",
48             default=True,
49             )
50     use_file_save_reload = BoolProperty(
51             name="Save & Reload File",
52             description="File menu > Save & Reload, or Ctrl + Shift + W",
53             default=True,
54             )
55
56     use_scene_refresh = BoolProperty(
57             name="Refresh Scene",
58             description="Specials Menu [W], or hit F5",
59             default=True,
60             )
61     use_timeline_extra_info = BoolProperty(
62             name="Timeline Extra Info",
63             description="Timeline Header",
64             default=True,
65             )
66     use_image_node_display = BoolProperty(
67             name="Active Image Node in Editor",
68             description="Display active node image in image editor",
69             default=True,
70             )
71     use_scene_stats = BoolProperty(
72             name="Extra Scene Statistics",
73             description="Display extra scene statistics in Info editor's header",
74             default=True,
75             )
76
77
78     def draw(self, context):
79         layout = self.layout
80
81         layout.label(
82             text="Here you can enable or disable specific tools, "
83                  "in case they interfere with others or are just plain annoying")
84
85         split = layout.split(percentage=0.25)
86
87         col = split.column()
88         sub = col.column(align=True)
89         sub.label(text="3D View", icon="VIEW3D")
90         sub.prop(self, "use_frame_current")
91         sub.prop(self, "use_scene_refresh")
92
93         sub.separator()
94
95         sub.label(text="General", icon="SCENE_DATA")
96         sub.prop(self, "use_file_save_reload")
97         sub.prop(self, "use_timeline_extra_info")
98         sub.prop(self, "use_scene_stats")
99
100         sub.separator()
101
102         sub.label(text="Nodes Editor", icon="NODETREE")
103         sub.prop(self, "use_image_node_display")
104
105         col = split.column()
106         sub = col.column(align=True)
107         sub.label(text="")
108         sub.label(
109             text="Set the current frame from the Specials menu in the 3D View [W]")
110         sub.label(
111             text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
112
113         sub.separator()
114         sub.label(text="") # General
115         sub.label(
116             text="Quickly save and reload the current file (no warning!). "
117                  "File menu or Ctrl+Shift+W")
118         sub.label(
119             text="SMPTE Timecode and frames left/ahead on Timeline's header")
120         sub.label(
121             text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
122
123         sub.separator()
124         sub.label(text="") # Nodes
125         sub.label(
126             text="When selecting an Image node, display it on the Image editor "
127                  "(if any)")
128
129 # Properties
130 def init_properties():
131
132     scene = bpy.types.Scene
133     node = bpy.types.Node
134     nodes_compo = bpy.types.CompositorNodeTree
135
136     scene.use_unsimplify_render = BoolProperty(
137         default=False,
138         name="Unsimplify Render",
139         description="Disable Simplify during render")
140     scene.simplify_status = BoolProperty(default=False)
141
142     node.use_matching_indices = BoolProperty(
143         default=True,
144         description="If disabled, display all available indices")
145
146     nodes_compo_types = [
147         ("ALL", "All Types", "", 0),
148         ("BLUR", "Blur", "", 1),
149         ("BOKEHBLUR", "Bokeh Blur", "", 2),
150         ("VECBLUR", "Vector Blur", "", 3),
151         ("DEFOCUS", "Defocus", "", 4),
152         ("R_LAYERS", "Render Layer", "", 5)
153         ]
154
155     nodes_compo.types = EnumProperty(
156         items=nodes_compo_types, name = "Types")
157
158     nodes_compo.toggle_mute = BoolProperty(default=False)
159     node.status = BoolProperty(default=False)
160
161     # Scene Debug
162     # Cycles Node Types
163     cycles_shader_node_types = [
164         ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
165         ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
166         ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
167         ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
168         ("BSDF_GLASS", "Glass BSDF", "", 4),
169         ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
170         ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
171         ("BSDF_VELVET", "Velvet BSDF", "", 7),
172         ("BSDF_TOON", "Toon BSDF", "", 8),
173         ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
174         ("EMISSION", "Emission", "", 10),
175         ("BSDF_HAIR", "Hair BSDF", "", 11),
176         ("BACKGROUND", "Background", "", 12),
177         ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
178         ("HOLDOUT", "Holdout", "", 14),
179         ("VOLUME_ABSORPTION", "Volume Absorption", "", 15),
180         ("VOLUME_SCATTER", "Volume Scatter", "", 16)
181         ]
182
183     scene.amaranth_cycles_node_types = EnumProperty(
184         items=cycles_shader_node_types, name = "Shader")
185
186     scene.amaranth_debug_scene_list_lamps = BoolProperty(
187         default=False,
188         name="Lamps List",
189         description="Display a list of all the lamps")
190
191     scene.amaranth_debug_scene_list_missing_images = BoolProperty(
192         default=False,
193         name="List Missing Images",
194         description="Display a list of all the missing images")
195
196     scene.amaranth_cycles_list_sampling = BoolProperty(
197         default=False,
198         name="Samples Per:")
199
200     bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
201     bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector
202     
203     bpy.types.CyclesRenderSettings.use_samples_final = BoolProperty(
204         name="Use Final Render Samples",
205         description="Use current shader samples as final render samples",
206         default=False,)
207
208
209 def clear_properties():
210     props = (
211         "use_unsimplify_render",
212         "simplify_status",
213         "use_matching_indices",
214         "use_simplify_nodes_vector",
215         "status",
216         "types",
217         "toggle_mute",
218         "amaranth_cycles_node_types",
219         "amaranth_debug_scene_list_lamps",
220         "amaranth_debug_scene_list_missing_images",
221         "amarath_cycles_list_sampling",
222         "normal_vector",
223         "use_samples_final"
224     )
225     
226     wm = bpy.context.window_manager
227     for p in props:
228         if p in wm:
229             del wm[p]
230
231 # Some settings are bound to be saved on a startup py file
232 def amaranth_text_startup(context):
233
234     amth_text_name = "AmaranthStartup.py"
235     amth_text_exists = False
236
237     global amth_text
238
239     try:
240         for tx in bpy.data.texts:
241             if tx.name == amth_text_name:
242                 amth_text_exists = True
243                 amth_text = bpy.data.texts[amth_text_name]
244                 break
245             else:
246                 amth_text_exists = False
247                 bpy.ops.text.new()
248                 amth_text = bpy.data.texts[-1]
249                 amth_text.name = amth_text_name
250                 amth_text.write("# Amaranth Startup Script\nimport bpy\n\n")
251                 amth_text.use_module = True
252                 break
253
254         return amth_text_exists
255     except AttributeError:
256         return None
257
258 # FEATURE: Refresh Scene!
259 class AMTH_SCENE_OT_refresh(Operator):
260     """Refresh the current scene"""
261     bl_idname = "scene.refresh"
262     bl_label = "Refresh!"
263     
264     def execute(self, context):
265         preferences = context.user_preferences.addons[__name__].preferences
266         scene = context.scene
267
268         if preferences.use_scene_refresh:    
269             # Changing the frame is usually the best way to go
270             scene.frame_current = scene.frame_current
271             self.report({"INFO"}, "Scene Refreshed!")
272             
273         return {'FINISHED'}
274
275 def button_refresh(self, context):
276
277     preferences = context.user_preferences.addons[__name__].preferences
278
279     if preferences.use_scene_refresh:
280         self.layout.separator()
281         self.layout.operator(
282             AMTH_SCENE_OT_refresh.bl_idname,
283             text="Refresh!",
284             icon='FILE_REFRESH')
285 # // FEATURE: Refresh Scene!
286
287 # FEATURE: Save & Reload
288 def save_reload(self, context, path):
289
290     if path:
291         bpy.ops.wm.save_mainfile()
292         self.report({'INFO'}, "Saved & Reloaded")
293         bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
294     else:
295         bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
296
297 class AMTH_WM_OT_save_reload(Operator):
298     """Save and Reload the current blend file"""
299     bl_idname = "wm.save_reload"
300     bl_label = "Save & Reload"
301
302     def execute(self, context):
303
304         path = bpy.data.filepath
305         save_reload(self, context, path)
306         return {'FINISHED'}
307
308 def button_save_reload(self, context):
309
310     preferences = context.user_preferences.addons[__name__].preferences
311
312     if preferences.use_file_save_reload:
313         self.layout.separator()
314         self.layout.operator(
315             AMTH_WM_OT_save_reload.bl_idname,
316             text="Save & Reload",
317             icon='FILE_REFRESH')
318 # // FEATURE: Save & Reload
319
320 # FEATURE: Current Frame
321 def button_frame_current(self, context):
322
323     preferences = context.user_preferences.addons[__name__].preferences
324     scene = context.scene
325
326     if preferences.use_frame_current:
327         self.layout.separator()
328         self.layout.prop(
329             scene, "frame_current",
330             text="Set Current Frame")
331 # // FEATURE: Current Frame
332
333 # FEATURE: Timeline Time + Frames Left
334 def label_timeline_extra_info(self, context):
335
336     preferences = context.user_preferences.addons[__name__].preferences
337     layout = self.layout
338     scene = context.scene
339
340     if preferences.use_timeline_extra_info:
341         row = layout.row(align=True)
342
343         # Check for preview range
344         frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
345         frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
346         
347         row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
348                         bpy.utils.smpte_from_frame(frame_end - frame_start)))
349
350         if (scene.frame_current > frame_end):
351             row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
352         elif (scene.frame_current == frame_start):
353             row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
354         elif (scene.frame_current == frame_end):
355             row.label(text="%s End Frame" % scene.frame_current)
356         else:
357             row.label(text="%s Frames Left" % (frame_end - scene.frame_current))
358
359 # // FEATURE: Timeline Time + Frames Left
360
361 # FEATURE: Directory Current Blend
362 class AMTH_FILE_OT_directory_current_blend(Operator):
363     """Go to the directory of the currently open blend file"""
364     bl_idname = "file.directory_current_blend"
365     bl_label = "Current Blend's Folder"
366
367     def execute(self, context):
368         bpy.ops.file.select_bookmark(dir='//')
369         return {'FINISHED'}
370
371 def button_directory_current_blend(self, context):
372
373     if bpy.data.filepath:
374         self.layout.operator(
375             AMTH_FILE_OT_directory_current_blend.bl_idname,
376             text="Current Blend's Folder",
377             icon='APPEND_BLEND')
378 # // FEATURE: Directory Current Blend
379
380 # FEATURE: Libraries panel on file browser
381 class AMTH_FILE_PT_libraries(Panel):
382     bl_space_type = 'FILE_BROWSER'
383     bl_region_type = 'CHANNELS'
384     bl_label = "Libraries"
385
386     def draw(self, context):
387         layout = self.layout
388
389         libs = bpy.data.libraries
390         libslist = []
391
392         # Build the list of folders from libraries
393         import os
394
395         for lib in libs:
396             directory_name = os.path.dirname(lib.filepath)
397             libslist.append(directory_name)
398
399         # Remove duplicates and sort by name
400         libslist = set(libslist)
401         libslist = sorted(libslist)
402
403         # Draw the box with libs
404
405         row = layout.row()
406         box = row.box()
407
408         if libslist:
409             col = box.column()
410             for filepath in libslist:
411                 if filepath != '//':
412                     row = col.row()
413                     row.alignment = 'LEFT'
414                     props = row.operator(
415                         AMTH_FILE_OT_directory_go_to.bl_idname,
416                         text=filepath, icon="BOOKMARKS",
417                         emboss=False)
418                     props.filepath = filepath
419         else:
420             box.label(text='No libraries loaded')
421
422 class AMTH_FILE_OT_directory_go_to(Operator):
423     """Go to this library's directory"""
424     bl_idname = "file.directory_go_to"
425     bl_label = "Go To"
426
427     filepath = bpy.props.StringProperty(subtype="FILE_PATH")
428
429     def execute(self, context):
430         bpy.ops.file.select_bookmark(dir=self.filepath)
431         return {'FINISHED'}
432
433 # FEATURE: Node Templates
434 class AMTH_NODE_OT_AddTemplateVignette(Operator):
435     bl_idname = "node.template_add_vignette"
436     bl_label = "Add Vignette"
437     bl_description = "Add a vignette effect"
438     bl_options = {'REGISTER', 'UNDO'}
439
440     @classmethod
441     def poll(cls, context):
442         space = context.space_data
443         return space.type == 'NODE_EDITOR' \
444                 and space.node_tree is not None \
445                 and space.tree_type == 'CompositorNodeTree'
446
447     # used as reference the setup scene script from master nazgul
448     def _setupNodes(self, context):
449         scene = context.scene
450         space = context.space_data
451         tree = scene.node_tree
452
453         bpy.ops.node.select_all(action='DESELECT')
454
455         ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
456         ellipse.width = 0.8
457         ellipse.height = 0.4
458         blur = tree.nodes.new(type='CompositorNodeBlur')
459         blur.use_relative = True
460         blur.factor_x = 30
461         blur.factor_y = 50
462         ramp = tree.nodes.new(type='CompositorNodeValToRGB')
463         ramp.color_ramp.interpolation = 'B_SPLINE'
464         ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
465
466         overlay = tree.nodes.new(type='CompositorNodeMixRGB')
467         overlay.blend_type = 'OVERLAY'
468         overlay.inputs[0].default_value = 0.8
469         overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
470
471         tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
472         tree.links.new(blur.outputs["Image"],ramp.inputs[0])
473         tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
474
475         if tree.nodes.active:
476             blur.location = tree.nodes.active.location
477             blur.location += Vector((330.0, -250.0))
478         else:
479             blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
480
481         ellipse.location = blur.location
482         ellipse.location += Vector((-300.0, 0))
483
484         ramp.location = blur.location
485         ramp.location += Vector((175.0, 0))
486
487         overlay.location = ramp.location
488         overlay.location += Vector((240.0, 275.0))
489
490         for node in {ellipse, blur, ramp, overlay}:
491             node.select = True
492             node.show_preview = False
493
494         bpy.ops.node.join()
495
496         frame = ellipse.parent
497         frame.label = 'Vignette'
498         frame.use_custom_color = True
499         frame.color = (0.783538, 0.0241576, 0.0802198)
500         
501         overlay.parent = None
502         overlay.label = 'Vignette Overlay'
503
504     def execute(self, context):
505         self._setupNodes(context)
506
507         return {'FINISHED'}
508
509 # Node Templates Menu
510 class AMTH_NODE_MT_amaranth_templates(Menu):
511     bl_idname = 'AMTH_NODE_MT_amaranth_templates'
512     bl_space_type = 'NODE_EDITOR'
513     bl_label = "Templates"
514     bl_description = "List of Amaranth Templates"
515
516     def draw(self, context):
517         layout = self.layout
518         layout.operator(
519             AMTH_NODE_OT_AddTemplateVignette.bl_idname,
520             text="Vignette",
521             icon='COLOR')
522
523 def node_templates_pulldown(self, context):
524
525     if context.space_data.tree_type == 'CompositorNodeTree':
526         layout = self.layout
527         row = layout.row(align=True)
528         row.scale_x = 1.3
529         row.menu("AMTH_NODE_MT_amaranth_templates",
530             icon="RADIO")
531 # // FEATURE: Node Templates
532
533 def node_stats(self,context):
534     if context.scene.node_tree:
535         tree_type = context.space_data.tree_type
536         nodes = context.scene.node_tree.nodes
537         nodes_total = len(nodes.keys())
538         nodes_selected = 0
539         for n in nodes:
540             if n.select:
541                 nodes_selected = nodes_selected + 1
542
543         if tree_type == 'CompositorNodeTree':
544             layout = self.layout
545             row = layout.row(align=True)
546             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
547
548 # FEATURE: Simplify Compo Nodes
549 class AMTH_NODE_PT_simplify(Panel):
550     '''Simplify Compositor Panel'''
551     bl_space_type = 'NODE_EDITOR'
552     bl_region_type = 'UI'
553     bl_label = 'Simplify'
554     bl_options = {'DEFAULT_CLOSED'}
555
556     @classmethod
557     def poll(cls, context):
558         space = context.space_data
559         return space.type == 'NODE_EDITOR' \
560                 and space.node_tree is not None \
561                 and space.tree_type == 'CompositorNodeTree'
562
563     def draw(self, context):
564         layout = self.layout
565         node_tree = context.scene.node_tree
566
567         if node_tree is not None:
568             layout.prop(node_tree, 'types')
569             layout.operator(AMTH_NODE_OT_toggle_mute.bl_idname,
570                 text="Turn On" if node_tree.toggle_mute else "Turn Off",
571                 icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
572         
573             if node_tree.types == 'VECBLUR':
574                 layout.label(text="This will also toggle the Vector pass {}".format(
575                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
576
577 class AMTH_NODE_OT_toggle_mute(Operator):
578     """"""
579     bl_idname = "node.toggle_mute"
580     bl_label = "Toggle Mute"
581
582     def execute(self, context):
583         scene = context.scene
584         node_tree = scene.node_tree
585         node_type = node_tree.types
586         rlayers = scene.render
587         
588         if not 'amaranth_pass_vector' in scene.keys():
589             scene['amaranth_pass_vector'] = []
590         
591         #can't extend() the list, so make a dummy one
592         pass_vector = scene['amaranth_pass_vector']
593
594         if not pass_vector:
595             pass_vector = []
596
597         if node_tree.toggle_mute:
598             for node in node_tree.nodes:
599                 if node_type == 'ALL':
600                     node.mute = node.status
601                 if node.type == node_type:
602                     node.mute = node.status
603                 if node_type == 'VECBLUR':
604                     for layer in rlayers.layers:
605                         if layer.name in pass_vector:
606                             layer.use_pass_vector = True
607                             pass_vector.remove(layer.name)
608
609                 node_tree.toggle_mute = False
610
611         else:
612             for node in node_tree.nodes:
613                 if node_type == 'ALL':
614                     node.mute = True
615                 if node.type == node_type:
616                     node.status = node.mute
617                     node.mute = True
618                 if node_type == 'VECBLUR':
619                     for layer in rlayers.layers:
620                         if layer.use_pass_vector:
621                             pass_vector.append(layer.name)
622                             layer.use_pass_vector = False
623                             pass
624
625                 node_tree.toggle_mute = True
626
627         # Write back to the custom prop
628         pass_vector = sorted(set(pass_vector))
629         scene['amaranth_pass_vector'] = pass_vector
630
631         return {'FINISHED'}
632         
633
634 # FEATURE: OB/MA ID panel in Node Editor
635 class AMTH_NODE_PT_indices(Panel):
636     '''Object / Material Indices Panel'''
637     bl_space_type = 'NODE_EDITOR'
638     bl_region_type = 'UI'
639     bl_label = 'Object / Material Indices'
640     bl_options = {'DEFAULT_CLOSED'}
641
642     @classmethod
643     def poll(cls, context):
644         node = context.active_node
645         return node and node.type == 'ID_MASK'
646
647     def draw(self, context):
648         layout = self.layout
649
650         objects = bpy.data.objects
651         materials = bpy.data.materials
652         node = context.active_node
653
654         show_ob_id = False
655         show_ma_id = False
656         matching_ids = False
657
658         if context.active_object:
659             ob_act = context.active_object
660         else:
661             ob_act = False
662
663         for ob in objects:
664             if ob and ob.pass_index > 0:
665                 show_ob_id = True
666         for ma in materials:
667             if ma and ma.pass_index > 0:
668                 show_ma_id = True
669         row = layout.row(align=True)  
670         row.prop(node, 'index', text="Mask Index")
671         row.prop(node, 'use_matching_indices', text="Only Matching IDs")
672         
673         layout.separator()
674
675         if not show_ob_id and not show_ma_id:
676             layout.label(text="No objects or materials indices so far.", icon="INFO")
677
678         if show_ob_id:
679             split = layout.split()
680             col = split.column()
681             col.label(text="Object Name")
682             split.label(text="ID Number")
683             row = layout.row()
684             for ob in objects:
685                 icon = "OUTLINER_DATA_" + ob.type
686                 if ob.library:
687                     icon = "LIBRARY_DATA_DIRECT"
688                 elif ob.is_library_indirect:
689                     icon = "LIBRARY_DATA_INDIRECT"
690
691                 if ob and node.use_matching_indices \
692                       and ob.pass_index == node.index \
693                       and ob.pass_index != 0:
694                     matching_ids = True
695                     row.label(
696                       text="[{}]".format(ob.name)
697                           if ob_act and ob.name == ob_act.name else ob.name,
698                       icon=icon)
699                     row.label(text="%s" % ob.pass_index)
700                     row = layout.row()
701
702                 elif ob and not node.use_matching_indices \
703                         and ob.pass_index > 0:
704
705                     matching_ids = True
706                     row.label(
707                       text="[{}]".format(ob.name)
708                           if ob_act and ob.name == ob_act.name else ob.name,
709                       icon=icon)
710                     row.label(text="%s" % ob.pass_index)
711                     row = layout.row()
712
713             if node.use_matching_indices and not matching_ids:
714                 row.label(text="No objects with ID %s" % node.index, icon="INFO")
715
716             layout.separator()
717
718         if show_ma_id:
719             split = layout.split()
720             col = split.column()
721             col.label(text="Material Name")
722             split.label(text="ID Number")
723             row = layout.row()
724
725             for ma in materials:
726                 icon = "BLANK1"
727                 if ma.use_nodes:
728                     icon = "NODETREE"
729                 elif ma.library:
730                     icon = "LIBRARY_DATA_DIRECT"
731                     if ma.is_library_indirect:
732                         icon = "LIBRARY_DATA_INDIRECT"
733
734                 if ma and node.use_matching_indices \
735                       and ma.pass_index == node.index \
736                       and ma.pass_index != 0:
737                     matching_ids = True
738                     row.label(text="%s" % ma.name, icon=icon)
739                     row.label(text="%s" % ma.pass_index)
740                     row = layout.row()
741
742                 elif ma and not node.use_matching_indices \
743                         and ma.pass_index > 0:
744
745                     matching_ids = True
746                     row.label(text="%s" % ma.name, icon=icon)
747                     row.label(text="%s" % ma.pass_index)
748                     row = layout.row()
749
750             if node.use_matching_indices and not matching_ids:
751                 row.label(text="No materials with ID %s" % node.index, icon="INFO")
752
753
754 # // FEATURE: OB/MA ID panel in Node Editor
755
756 # FEATURE: Unsimplify on render
757 @persistent
758 def unsimplify_render_pre(scene):
759     render = scene.render
760     scene.simplify_status = render.use_simplify
761
762     if scene.use_unsimplify_render:
763         render.use_simplify = False
764
765 @persistent
766 def unsimplify_render_post(scene):
767     render = scene.render
768     render.use_simplify = scene.simplify_status
769
770 def unsimplify_ui(self,context):
771     scene = bpy.context.scene
772     self.layout.prop(scene, 'use_unsimplify_render')
773 # //FEATURE: Unsimplify on render
774
775 # FEATURE: Extra Info Stats
776 def stats_scene(self, context):
777
778     preferences = context.user_preferences.addons[__name__].preferences
779
780     if preferences.use_scene_stats:
781         scenes_count = str(len(bpy.data.scenes))
782         cameras_count = str(len(bpy.data.cameras))
783         cameras_selected = 0
784         meshlights = 0
785         meshlights_visible = 0
786     
787         for ob in context.scene.objects:
788             if ob.material_slots:
789                 for ma in ob.material_slots:
790                     if ma.material:
791                         if ma.material.node_tree:
792                             for no in ma.material.node_tree.nodes:
793                                 if no.type == 'EMISSION':
794                                     for ou in no.outputs:
795                                         if ou.links:
796                                             meshlights += 1
797                                             if ob in context.visible_objects:
798                                                 meshlights_visible += 1
799                                             break
800             if ob in context.selected_objects:
801                 if ob.type == 'CAMERA':
802                     cameras_selected += 1
803     
804         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
805     
806         row = self.layout.row(align=True)
807         row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
808                    scenes_count, cameras_selected, cameras_count,
809                    meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
810
811 # //FEATURE: Extra Info Stats
812
813 # FEATURE: Camera Bounds as Render Border
814 class AMTH_VIEW3D_OT_render_border_camera(Operator):
815     """Set camera bounds as render border"""
816     bl_idname = "view3d.render_border_camera"
817     bl_label = "Camera as Render Border"
818
819     @classmethod
820     def poll(cls, context):
821         return context.space_data.region_3d.view_perspective == 'CAMERA'
822
823     def execute(self, context):
824         render = context.scene.render
825         render.use_border = True
826         render.border_min_x = 0
827         render.border_min_y = 0
828         render.border_max_x = 1
829         render.border_max_y = 1
830
831         return {'FINISHED'}
832
833 def button_render_border_camera(self, context):
834
835     view3d = context.space_data.region_3d
836     
837     if view3d.view_perspective == 'CAMERA':
838         layout = self.layout
839         layout.separator()
840         layout.operator(AMTH_VIEW3D_OT_render_border_camera.bl_idname,
841                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
842
843 # //FEATURE: Camera Bounds as Render Border
844
845 # FEATURE: Passepartout options on W menu
846 def button_camera_passepartout(self, context):
847
848     view3d = context.space_data.region_3d
849     cam = context.scene.camera.data
850     
851     if view3d.view_perspective == 'CAMERA':
852         layout = self.layout
853         if cam.show_passepartout:
854             layout.prop(cam, "passepartout_alpha", text="Passepartout")
855         else:
856             layout.prop(cam, "show_passepartout")
857
858 # FEATURE: Show Only Render with Alt+Shift+Z
859 class AMTH_VIEW3D_OT_show_only_render(Operator):
860     bl_idname = "view3d.show_only_render"
861     bl_label = "Show Only Render"
862
863     def execute(self, context):
864         space = bpy.context.space_data
865         
866         if space.show_only_render:
867             space.show_only_render = False
868         else:
869             space.show_only_render = True
870         return {'FINISHED'}
871
872
873 # FEATURE: Display Active Image Node on Image Editor
874 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
875 image_nodes = {"CompositorNodeImage",
876                "ShaderNodeTexImage",
877                "ShaderNodeTexEnvironment"}
878
879 class AMTH_NODE_OT_show_active_node_image(Operator):
880     """Show active image node image in the image editor"""
881     bl_idname = "node.show_active_node_image"
882     bl_label = "Show Active Node Node"
883     bl_options = {'UNDO'}
884
885     def execute(self, context):
886         preferences = context.user_preferences.addons[__name__].preferences
887         if preferences.use_image_node_display:
888             if context.active_node:
889                 active_node = context.active_node
890                 if active_node.bl_idname in image_nodes and active_node.image:
891                     for area in context.screen.areas:
892                         if area.type == "IMAGE_EDITOR":
893                             for space in area.spaces:
894                                 if space.type == "IMAGE_EDITOR":
895                                     space.image = active_node.image
896                             break
897     
898         return {'FINISHED'}
899 # // FEATURE: Display Active Image Node on Image Editor
900
901 # FEATURE: Select Meshlights
902 class AMTH_OBJECT_OT_select_meshlights(Operator):
903     """Select light emitting meshes"""
904     bl_idname = "object.select_meshlights"
905     bl_label = "Select Meshlights"
906     bl_options = {'UNDO'}
907
908     @classmethod
909     def poll(cls, context):
910         return context.scene.render.engine == 'CYCLES'
911
912     def execute(self, context):
913         # Deselect everything first
914         bpy.ops.object.select_all(action='DESELECT')
915
916         for ob in context.scene.objects:
917             if ob.material_slots:
918                 for ma in ob.material_slots:
919                     if ma.material:
920                         if ma.material.node_tree:
921                             for no in ma.material.node_tree.nodes:
922                                 if no.type == 'EMISSION':
923                                     ob.select = True
924                                     context.scene.objects.active = ob
925
926         if not context.selected_objects and not context.scene.objects.active:
927             self.report({'INFO'}, "No meshlights to select")
928
929         return {'FINISHED'}
930
931 def button_select_meshlights(self, context):
932     
933     if context.scene.render.engine == 'CYCLES':
934         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
935 # // FEATURE: Select Meshlights
936
937 # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
938 class AMTH_MESH_OT_find_asymmetric(Operator):
939     """
940     Find asymmetric vertices
941     """
942
943     bl_idname = "mesh.find_asymmetric"
944     bl_label = "Find Asymmetric"
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         # Deselect all the vertices
961         for v in bm.verts:
962             v.select = False
963
964         for v1 in bm.verts:
965             if abs(v1.co[0]) < threshold:
966                 continue
967
968             mirror_found = False
969             for v2 in bm.verts:
970                 if v1 == v2:
971                     continue
972                 if v1.co[0] * v2.co[0] > 0.0:
973                     continue
974
975                 mirror_coord = Vector(v2.co)
976                 mirror_coord[0] *= -1
977                 if (mirror_coord - v1.co).length_squared < threshold:
978                     mirror_found = True
979                     break
980             if not mirror_found:
981                 v1.select = True
982
983         bm.select_flush_mode()
984
985         bmesh.update_edit_mesh(object.data)
986
987         return {'FINISHED'}
988
989 class AMTH_MESH_OT_make_symmetric(Operator):
990     """
991     Make symmetric
992     """
993
994     bl_idname = "mesh.make_symmetric"
995     bl_label = "Make Symmetric"
996     bl_options = {'UNDO', 'REGISTER'}
997
998     @classmethod
999     def poll(cls, context):
1000         object = context.object
1001         if object:
1002             return object.mode == 'EDIT' and object.type == 'MESH'
1003         return False
1004
1005     def execute(self, context):
1006         threshold = 1e-6
1007
1008         object = context.object
1009         bm = bmesh.from_edit_mesh(object.data)
1010
1011         for v1 in bm.verts:
1012             if v1.co[0] < threshold:
1013                 continue
1014             if not v1.select:
1015                 continue
1016
1017             closest_vert = None
1018             closest_distance = -1
1019             for v2 in bm.verts:
1020                 if v1 == v2:
1021                     continue
1022                 if v2.co[0] > threshold:
1023                     continue
1024                 if not v2.select:
1025                     continue
1026
1027                 mirror_coord = Vector(v2.co)
1028                 mirror_coord[0] *= -1
1029                 distance = (mirror_coord - v1.co).length_squared
1030                 if closest_vert is None or distance < closest_distance:
1031                     closest_distance = distance
1032                     closest_vert = v2
1033
1034             if closest_vert:
1035                 closest_vert.select = False
1036                 closest_vert.co = Vector(v1.co)
1037                 closest_vert.co[0] *= -1
1038             v1.select = False
1039
1040         for v1 in bm.verts:
1041             if v1.select:
1042                 closest_vert = None
1043                 closest_distance = -1
1044                 for v2 in bm.verts:
1045                     if v1 != v2:
1046                         mirror_coord = Vector(v2.co)
1047                         mirror_coord[0] *= -1
1048                         distance = (mirror_coord - v1.co).length_squared
1049                         if closest_vert is None or distance < closest_distance:
1050                             closest_distance = distance
1051                             closest_vert = v2
1052                 if closest_vert:
1053                     v1.select = False
1054                     v1.co = Vector(closest_vert.co)
1055                     v1.co[0] *= -1
1056
1057         bm.select_flush_mode()
1058         bmesh.update_edit_mesh(object.data)
1059
1060         return {'FINISHED'}
1061 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1062
1063 # FEATURE: Cycles Render Sampling Extra
1064 def render_cycles_scene_samples(self, context):
1065
1066     layout = self.layout
1067
1068     scenes = bpy.data.scenes
1069     scene = context.scene
1070     cscene = scene.cycles
1071     render = scene.render
1072     list_sampling = scene.amaranth_cycles_list_sampling
1073
1074     if cscene.progressive == 'BRANCHED_PATH':
1075         layout.separator()
1076         split = layout.split()
1077         col = split.column()
1078
1079         col.operator(
1080             AMTH_RENDER_OT_cycles_samples_percentage_set.bl_idname,
1081             text="%s" % 'Set as Render Samples' if cscene.use_samples_final else 'Set New Render Samples',
1082             icon="%s" % 'PINNED' if cscene.use_samples_final else 'UNPINNED')
1083
1084         col = split.column()
1085         row = col.row(align=True)
1086         row.enabled = True if scene.get('amth_cycles_samples_final') else False
1087
1088         row.operator(
1089             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1090             text="100%").percent=100
1091         row.operator(
1092             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1093             text="75%").percent=75
1094         row.operator(
1095             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1096             text="50%").percent=50
1097         row.operator(
1098             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1099             text="25%").percent=25
1100
1101     # List Lamps
1102     if (len(scene.render.layers) > 1) or \
1103         (len(bpy.data.scenes) > 1):
1104
1105         layout.separator()
1106         box = layout.box()
1107         row = box.row(align=True)
1108         col = row.column(align=True)
1109
1110         row = col.row(align=True)
1111         row.alignment = 'LEFT'
1112         row.prop(scene, 'amaranth_cycles_list_sampling',
1113                     icon="%s" % 'TRIA_DOWN' if list_sampling else 'TRIA_RIGHT',
1114                     emboss=False)
1115
1116     if list_sampling:
1117         if len(scene.render.layers) == 1 and \
1118             render.layers[0].samples == 0:
1119             pass
1120         else:
1121             col.separator()
1122             col.label(text="RenderLayers:", icon='RENDERLAYERS')
1123
1124             for rl in scene.render.layers:
1125                 row = col.row(align=True)
1126                 row.label(rl.name, icon='BLANK1')
1127                 row.prop(rl, "samples", text="%s" %
1128                     "Samples" if rl.samples > 0 else "Automatic (%s)" % (
1129                         cscene.aa_samples if cscene.progressive == 'BRANCHED_PATH' else cscene.samples))
1130
1131         if (len(bpy.data.scenes) > 1):
1132             col.separator()
1133
1134             col.label(text="Scenes:", icon='SCENE_DATA')
1135             row = col.row(align=True)
1136
1137             if cscene.progressive == 'PATH':
1138                 for s in bpy.data.scenes:
1139                     if s != scene:
1140                         row = col.row(align=True)
1141                         if s.render.engine == 'CYCLES':
1142                             cscene = s.cycles
1143
1144                             row.label(s.name)
1145                             row.prop(cscene, "samples", icon='BLANK1')
1146                         else:
1147                             row.label(text="Scene: '%s' is not using Cycles" % s.name)
1148             else:
1149                 for s in bpy.data.scenes:
1150                     if s != scene:
1151                         row = col.row(align=True)
1152                         if s.render.engine == 'CYCLES':
1153                             cscene = s.cycles
1154
1155                             row.label(s.name, icon='BLANK1')
1156                             row.prop(cscene, "aa_samples",
1157                                 text="AA Samples")
1158                         else:
1159                             row.label(text="Scene: '%s' is not using Cycles" % s.name)
1160
1161 # // FEATURE: Cycles Render Sampling Extra
1162
1163 # FEATURE: Motion Paths Extras
1164 class AMTH_POSE_OT_paths_clear_all(Operator):
1165     """Clear motion paths from all bones"""
1166     bl_idname = "pose.paths_clear_all"
1167     bl_label = "Clear All Motion Paths"
1168     bl_options = {'UNDO'}
1169
1170     @classmethod
1171     def poll(cls, context):
1172         return context.mode == 'POSE'
1173
1174     def execute(self, context):
1175         #silly but works
1176         for b in context.object.data.bones:
1177             b.select = True
1178             bpy.ops.pose.paths_clear()
1179             b.select = False
1180         return {'FINISHED'}
1181
1182 class AMTH_POSE_OT_paths_frame_match(Operator):
1183     """Match Start/End frame of scene to motion path range"""
1184     bl_idname = "pose.paths_frame_match"
1185     bl_label = "Match Frame Range"
1186     bl_options = {'UNDO'}
1187
1188     def execute(self, context):
1189         avs = context.object.pose.animation_visualization
1190         scene = context.scene
1191
1192         if avs.motion_path.type == 'RANGE':
1193             if scene.use_preview_range:
1194                 avs.motion_path.frame_start = scene.frame_preview_start
1195                 avs.motion_path.frame_end = scene.frame_preview_end
1196             else:
1197                 avs.motion_path.frame_start = scene.frame_start
1198                 avs.motion_path.frame_end = scene.frame_end
1199
1200         else:
1201             if scene.use_preview_range:
1202                 avs.motion_path.frame_before = scene.frame_preview_start
1203                 avs.motion_path.frame_after = scene.frame_preview_end
1204             else:
1205                 avs.motion_path.frame_before = scene.frame_start
1206                 avs.motion_path.frame_after = scene.frame_end
1207
1208         return {'FINISHED'}
1209
1210 def pose_motion_paths_ui(self, context):
1211
1212     layout = self.layout
1213     scene = context.scene
1214     avs = context.object.pose.animation_visualization
1215     if context.active_pose_bone:
1216         mpath = context.active_pose_bone.motion_path
1217     layout.separator()    
1218     layout.label(text="Motion Paths Extras:")
1219
1220     split = layout.split()
1221
1222     col = split.column(align=True)
1223
1224     if context.selected_pose_bones:
1225         if mpath:
1226             sub = col.row(align=True)
1227             sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
1228             sub.operator("pose.paths_clear", text="", icon='X')
1229         else:
1230             col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
1231     else:
1232         col.label(text="Select Bones First", icon="ERROR")
1233
1234     col = split.column(align=True)
1235     col.operator(AMTH_POSE_OT_paths_frame_match.bl_idname,
1236         text="{}".format( "Set Preview Frame Range"
1237                 if scene.use_preview_range else "Set Frame Range"),
1238         icon="{}".format("PREVIEW_RANGE"
1239                 if scene.use_preview_range else "TIME"))
1240
1241     col = layout.column()
1242     row = col.row(align=True)
1243
1244     if avs.motion_path.type == 'RANGE':
1245         row.prop(avs.motion_path, "frame_start", text="Start")
1246         row.prop(avs.motion_path, "frame_end", text="End")
1247     else:
1248         row.prop(avs.motion_path, "frame_before", text="Before")
1249         row.prop(avs.motion_path, "frame_after", text="After")
1250
1251     layout.separator()
1252     layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
1253 # // FEATURE: Motion Paths Extras
1254
1255 # FEATURE: Final Render Resolution Display
1256 def render_final_resolution_ui(self, context):
1257
1258     rd = context.scene.render
1259     layout = self.layout
1260
1261     final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
1262     final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
1263
1264     if rd.use_border:
1265        final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
1266        final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
1267        layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
1268              str(final_res_x)[:-2], str(final_res_y)[:-2],
1269              str(final_res_x_border), str(final_res_y_border)))
1270     else:
1271         layout.label(text="Final Resolution: {} x {}".format(
1272              str(final_res_x)[:-2], str(final_res_y)[:-2]))
1273 # // FEATURE: Final Render Resolution Display
1274
1275 # FEATURE: Shader Nodes Extra Info
1276 def node_shader_extra(self, context):
1277
1278     if context.space_data.tree_type == 'ShaderNodeTree':
1279         ob = context.active_object
1280         snode = context.space_data
1281         layout = self.layout
1282
1283         if ob and snode.shader_type != 'WORLD':
1284             if ob.type == 'LAMP':
1285                 layout.label(text="%s" % ob.name,
1286                              icon="LAMP_%s" % ob.data.type)        
1287             else:
1288                 layout.label(text="%s" % ob.name,
1289                              icon="OUTLINER_DATA_%s" % ob.type)
1290              
1291
1292 # // FEATURE: Shader Nodes Extra Info
1293
1294 # FEATURE: Scene Debug
1295 class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
1296     """List Cycles materials containing a specific shader"""
1297     bl_idname = "scene.cycles_list_nodes"
1298     bl_label = "List Materials"
1299     materials = []
1300
1301     @classmethod
1302     def poll(cls, context):
1303         return context.scene.render.engine == 'CYCLES'
1304
1305     def execute(self, context):
1306         node_type = context.scene.amaranth_cycles_node_types
1307         roughness = False
1308         self.__class__.materials = []
1309         shaders_roughness = ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']
1310
1311         print("\n=== Cycles Shader Type: %s === \n" % node_type)
1312
1313         for ma in bpy.data.materials:
1314             if ma.node_tree:
1315                 nodes = ma.node_tree.nodes
1316                 
1317                 print_unconnected = ('Note: \nOutput from "%s" node' % node_type,
1318                                         'in material "%s"' % ma.name, 'not connected\n')
1319
1320                 for no in nodes:
1321                     if no.type == node_type:
1322                         for ou in no.outputs:
1323                             if ou.links:
1324                                 connected = True
1325                                 if no.type in shaders_roughness:
1326                                     roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
1327                                 else:
1328                                     roughness = False
1329                             else:
1330                                 connected = False
1331                                 print(print_unconnected)
1332
1333                             if ma.name not in self.__class__.materials:
1334                                 self.__class__.materials.append('%s%s [%s] %s%s%s' % (
1335                                     '[L] ' if ma.library else '',
1336                                     ma.name, ma.users,
1337                                     '[F]' if ma.use_fake_user else '',
1338                                     ' - [%s]' % roughness if roughness else '',
1339                                     ' * Output not connected' if not connected else ''))
1340
1341                     elif no.type == 'GROUP':
1342                         if no.node_tree:
1343                             for nog in no.node_tree.nodes:
1344                                 if nog.type == node_type:
1345                                     for ou in nog.outputs:
1346                                         if ou.links:
1347                                             connected = True
1348                                             if nog.type in shaders_roughness:
1349                                                 roughness = 'R: %.4f' % nog.inputs['Roughness'].default_value
1350                                             else:
1351                                                 roughness = False
1352                                         else:
1353                                             connected = False
1354                                             print(print_unconnected)
1355
1356                                         if ma.name not in self.__class__.materials:
1357                                             self.__class__.materials.append('%s%s%s [%s] %s%s%s' % (
1358                                                 '[L] ' if ma.library else '',
1359                                                 'Node Group:  %s%s  ->  ' % (
1360                                                     '[L] ' if no.node_tree.library else '',
1361                                                     no.node_tree.name),
1362                                                 ma.name, ma.users,
1363                                                 '[F]' if ma.use_fake_user else '',
1364                                                 ' - [%s]' % roughness if roughness else '',
1365                                                 ' * Output not connected' if not connected else ''))
1366
1367                     self.__class__.materials = sorted(list(set(self.__class__.materials)))
1368
1369         if len(self.__class__.materials) == 0:
1370             self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
1371         else:
1372             print("* A total of %d %s using %s was found \n" % (
1373                     len(self.__class__.materials),
1374                     "material" if len(self.__class__.materials) == 1 else "materials",
1375                     node_type))
1376
1377             count = 0
1378
1379             for mat in self.__class__.materials:
1380                 print('%02d. %s' % (count+1, self.__class__.materials[count]))
1381                 count += 1
1382             print("\n")
1383
1384         self.__class__.materials = sorted(list(set(self.__class__.materials)))
1385
1386         return {'FINISHED'}
1387
1388 class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(Operator):
1389     """Clear the list below"""
1390     bl_idname = "scene.cycles_list_nodes_clear"
1391     bl_label = "Clear Materials List"
1392     
1393     def execute(self, context):
1394         AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
1395         print("* Cleared Cycles Materials List")
1396         return {'FINISHED'}
1397
1398 class AMTH_SCENE_OT_amaranth_debug_lamp_select(Operator):
1399     '''Select Lamp'''
1400     bl_idname = "scene.amaranth_debug_lamp_select"
1401     bl_label = "Select Lamp"
1402     lamp = bpy.props.StringProperty()
1403  
1404     def execute(self, context):
1405         if self.lamp:
1406             lamp = bpy.data.objects[self.lamp]
1407
1408             bpy.ops.object.select_all(action='DESELECT')
1409             lamp.select = True
1410             context.scene.objects.active = lamp
1411
1412         return{'FINISHED'}
1413
1414 class AMTH_SCENE_OT_list_missing_node_links(Operator):
1415     '''Print a list of missing node links'''
1416     bl_idname = "scene.list_missing_node_links"
1417     bl_label = "List Missing Node Links"
1418
1419     count_groups = 0
1420     count_images = 0
1421     count_image_node_unlinked = 0
1422
1423     def execute(self, context):
1424         missing_groups = []
1425         missing_images = []
1426         image_nodes_unlinked = []
1427         libraries = []
1428         self.__class__.count_groups = 0
1429         self.__class__.count_images = 0
1430         self.__class__.count_image_node_unlinked = 0
1431
1432         for ma in bpy.data.materials:
1433             if ma.node_tree:
1434                 for no in ma.node_tree.nodes:
1435                     if no.type == 'GROUP':
1436                         if not no.node_tree:
1437                             self.__class__.count_groups += 1
1438
1439                             users_ngroup = []
1440
1441                             for ob in bpy.data.objects:
1442                                 if ob.material_slots and ma.name in ob.material_slots:
1443                                     users_ngroup.append("%s%s%s" % (
1444                                         "[L] " if ob.library else "",
1445                                         "[F] " if ob.use_fake_user else "",
1446                                         ob.name))
1447
1448                             missing_groups.append("NG: %s%s%s [%s]%s%s\n" % (
1449                                 "[L] " if ma.library else "",
1450                                 "[F] " if ma.use_fake_user else "",
1451                                 ma.name, ma.users,
1452                                 "\nLI: %s" % 
1453                                 ma.library.filepath if ma.library else "",
1454                                 "\nOB: %s" % ',  '.join(users_ngroup) if users_ngroup else ""))
1455
1456                             if ma.library:
1457                                 libraries.append(ma.library.filepath)
1458                     if no.type == 'TEX_IMAGE':
1459
1460                         outputs_empty = not no.outputs['Color'].is_linked and not no.outputs['Alpha'].is_linked
1461
1462                         if no.image:
1463                             import os.path
1464                             image_path_exists = os.path.exists(
1465                                                     bpy.path.abspath(
1466                                                         no.image.filepath, library=no.image.library))
1467
1468                         if outputs_empty or not \
1469                            no.image or not \
1470                            image_path_exists:
1471
1472                             users_images = []
1473
1474                             for ob in bpy.data.objects:
1475                                 if ob.material_slots and ma.name in ob.material_slots:
1476                                     users_images.append("%s%s%s" % (
1477                                         "[L] " if ob.library else "",
1478                                         "[F] " if ob.use_fake_user else "",
1479                                         ob.name))
1480
1481                             if outputs_empty:
1482                                 self.__class__.count_image_node_unlinked += 1
1483
1484                                 image_nodes_unlinked.append("%s%s%s%s%s [%s]%s%s%s%s\n" % (
1485                                     "NO: %s" % no.name,
1486                                     "\nMA: ",
1487                                     "[L] " if ma.library else "",
1488                                     "[F] " if ma.use_fake_user else "",
1489                                     ma.name, ma.users,
1490                                     "\nLI: %s" % 
1491                                     ma.library.filepath if ma.library else "",
1492                                     "\nIM: %s" % no.image.name if no.image else "",
1493                                     "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
1494                                     "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
1495                             
1496
1497                             if not no.image or not image_path_exists:
1498                                 self.__class__.count_images += 1
1499
1500                                 missing_images.append("MA: %s%s%s [%s]%s%s%s%s\n" % (
1501                                     "[L] " if ma.library else "",
1502                                     "[F] " if ma.use_fake_user else "",
1503                                     ma.name, ma.users,
1504                                     "\nLI: %s" % 
1505                                     ma.library.filepath if ma.library else "",
1506                                     "\nIM: %s" % no.image.name if no.image else "",
1507                                     "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
1508                                     "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
1509
1510                                 if ma.library:
1511                                     libraries.append(ma.library.filepath)
1512
1513         # Remove duplicates and sort
1514         missing_groups = sorted(list(set(missing_groups)))
1515         missing_images = sorted(list(set(missing_images)))
1516         image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
1517         libraries = sorted(list(set(libraries)))
1518
1519         print("\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
1520             ("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
1521             "node" if self.__class__.count_images == 1 else "nodes",
1522             "no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
1523             "group" if self.__class__.count_groups == 1 else "groups",
1524             "no" if self.__class__.count_image_node_unlinked == 0 else str(self.__class__.count_image_node_unlinked),
1525             "node" if self.__class__.count_groups == 1 else "nodes"))
1526
1527         # List Missing Node Groups
1528         if missing_groups:
1529             print("\n* Missing Node Group Links [NG]\n")
1530             for mig in missing_groups:
1531                 print(mig)
1532
1533         # List Missing Image Nodes
1534         if missing_images:
1535             print("\n* Missing Image Nodes Link [IM]\n")
1536
1537             for mii in missing_images:
1538                 print(mii)
1539
1540         # List Image Nodes with its outputs unlinked
1541         if image_nodes_unlinked:
1542             print("\n* Image Nodes Unlinked [IM]\n")
1543
1544             for nou in image_nodes_unlinked:
1545                 print(nou)
1546
1547         if missing_groups or \
1548            missing_images or \
1549            image_nodes_unlinked:
1550             if libraries:
1551                 print("\nThat's bad, run check on %s:" % (
1552                     "this library" if len(libraries) == 1 else "these libraries"))
1553                 for li in libraries:
1554                     print(li)
1555         else:
1556             self.report({"INFO"}, "Yay! No missing node links")            
1557
1558         print("\n")
1559
1560         if missing_groups and missing_images:
1561             self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
1562                 (self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
1563                 self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))
1564
1565         return{'FINISHED'}
1566
1567 class AMTH_SCENE_OT_list_missing_material_slots(Operator):
1568     '''List objects with empty material slots'''
1569     bl_idname = "scene.list_missing_material_slots"
1570     bl_label = "List Empty Material Slots"
1571
1572     objects = []
1573     libraries = []
1574
1575     def execute(self, context):
1576         self.__class__.objects = []
1577         self.__class__.libraries = []
1578
1579         for ob in bpy.data.objects:
1580             for ma in ob.material_slots:
1581                 if not ma.material:
1582                     self.__class__.objects.append('%s%s' % (
1583                         '[L] ' if ob.library else '',
1584                         ob.name))
1585                     if ob.library:
1586                         self.__class__.libraries.append(ob.library.filepath)
1587
1588         self.__class__.objects = sorted(list(set(self.__class__.objects)))
1589         self.__class__.libraries = sorted(list(set(self.__class__.libraries)))
1590
1591         if len(self.__class__.objects) == 0:
1592             self.report({"INFO"}, "No objects with empty material slots found")
1593         else:
1594             print("\n* A total of %d %s with empty material slots was found \n" % (
1595                     len(self.__class__.objects),
1596                     "object" if len(self.__class__.objects) == 1 else "objects"))
1597
1598             count = 0
1599             count_lib = 0
1600
1601             for obs in self.__class__.objects:
1602                 print('%02d. %s' % (
1603                     count+1, self.__class__.objects[count]))
1604                 count += 1
1605
1606             if self.__class__.libraries:
1607                 print("\n\n* Check %s:\n" % 
1608                     ("this library" if len(self.__class__.libraries) == 1
1609                         else "these libraries"))
1610
1611                 for libs in self.__class__.libraries:
1612                     print('%02d. %s' % (
1613                         count_lib+1, self.__class__.libraries[count_lib]))
1614                     count_lib += 1
1615             print("\n")
1616
1617         return{'FINISHED'}
1618
1619 class AMTH_SCENE_OT_list_missing_material_slots_clear(Operator):
1620     """Clear the list below"""
1621     bl_idname = "scene.list_missing_material_slots_clear"
1622     bl_label = "Clear Empty Material Slots List"
1623     
1624     def execute(self, context):
1625         AMTH_SCENE_OT_list_missing_material_slots.objects[:] = []
1626         print("* Cleared Empty Material Slots List")
1627         return {'FINISHED'}
1628
1629 class AMTH_SCENE_OT_blender_instance_open(Operator):
1630     '''Open in a new Blender instance'''
1631     bl_idname = "scene.blender_instance_open"
1632     bl_label = "Open Blender Instance"
1633     filepath = bpy.props.StringProperty()
1634  
1635     def execute(self, context):
1636         if self.filepath:
1637             filepath = bpy.path.abspath(self.filepath)
1638
1639             import subprocess
1640             try:
1641                 subprocess.Popen([bpy.app.binary_path, filepath])
1642             except:
1643                 print("Error on the new Blender instance")
1644                 import traceback
1645                 traceback.print_exc()
1646
1647         return{'FINISHED'}
1648
1649 class AMTH_SCENE_PT_scene_debug(Panel):
1650     '''Scene Debug'''
1651     bl_label = 'Scene Debug'
1652     bl_space_type = "PROPERTIES"
1653     bl_region_type = "WINDOW"
1654     bl_context = "scene"
1655
1656     def draw(self, context):
1657         layout = self.layout
1658         scene = context.scene
1659         objects =  bpy.data.objects
1660         ob_act = context.active_object
1661         images = bpy.data.images
1662         lamps = bpy.data.lamps
1663         images_missing = []
1664         list_lamps = scene.amaranth_debug_scene_list_lamps
1665         list_missing_images = scene.amaranth_debug_scene_list_missing_images
1666         materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
1667         materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
1668         missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
1669         missing_material_slots_count = len(AMTH_SCENE_OT_list_missing_material_slots.objects)
1670         missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
1671         engine = scene.render.engine
1672
1673         # List Lamps
1674         box = layout.box()
1675         row = box.row(align=True)
1676         split = row.split()
1677         col = split.column()
1678         
1679         if lamps:
1680             row = col.row(align=True)
1681             row.alignment = 'LEFT'
1682             row.prop(scene, 'amaranth_debug_scene_list_lamps',
1683                         icon="%s" % 'TRIA_DOWN' if list_lamps else 'TRIA_RIGHT',
1684                         emboss=False)
1685
1686             if objects and list_lamps:
1687                 row = box.row(align=True)
1688                 split = row.split(percentage=0.42)
1689                 col = split.column()
1690                 col.label(text="Name")
1691
1692                 split = split.split(percentage=0.1)
1693                 col = split.column()
1694                 col.label(text="", icon="BLANK1")
1695                 if engine in ['CYCLES', 'BLENDER_RENDER']:
1696                     if engine == 'BLENDER_RENDER':
1697                         split = split.split(percentage=0.7)
1698                     else:
1699                         split = split.split(percentage=0.35)
1700                     col = split.column()
1701                     col.label(text="Samples")
1702
1703                 if engine == 'CYCLES':
1704                     split = split.split(percentage=0.35)
1705                     col = split.column()
1706                     col.label(text="Size")
1707
1708                 split = split.split(percentage=0.8)
1709                 col = split.column()
1710                 col.label(text="Visibility")
1711
1712                 for ob in objects:
1713                     if ob and ob.type == 'LAMP':
1714                         lamp = ob.data
1715                         clamp = ob.data.cycles
1716
1717                         row = box.row(align=True)
1718                         split = row.split(percentage=0.5)
1719                         col = split.column()
1720                         row = col.row()
1721                         row.alignment = 'LEFT'
1722                         row.operator("scene.amaranth_debug_lamp_select",
1723                                     text='%s %s%s' % (
1724                                         " [L] " if ob.library else "",
1725                                         ob.name,
1726                                         "" if ob.name in context.scene.objects else " [Not in Scene]"),
1727                                     icon="LAMP_%s" % ob.data.type,
1728                                     emboss=False).lamp = ob.name
1729                         if ob.library:
1730                             row = col.row(align=True)
1731                             row.alignment = "LEFT"
1732                             row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
1733                                          text=ob.library.filepath,
1734                                          icon="LINK_BLEND",
1735                                          emboss=False).filepath=ob.library.filepath
1736
1737                         if engine == 'CYCLES':
1738                             split = split.split(percentage=0.35)
1739                             col = split.column()
1740                             if scene.cycles.progressive == 'BRANCHED_PATH':
1741                                 col.prop(clamp, "samples", text="")
1742                             if scene.cycles.progressive == 'PATH':
1743                                col.label(text="N/A")
1744                            
1745                         if engine == 'BLENDER_RENDER':
1746                             split = split.split(percentage=0.7)
1747                             col = split.column()
1748                             if lamp.type == 'HEMI':
1749                                 col.label(text="Not Available")
1750                             elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
1751                                 row = col.row(align=True)
1752                                 row.prop(lamp, "shadow_ray_samples_x", text="X")
1753                                 if lamp.shape == 'RECTANGLE':
1754                                     row.prop(lamp, "shadow_ray_samples_y", text="Y")
1755                             elif lamp.shadow_method == 'RAY_SHADOW':
1756                                 col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
1757                             elif lamp.shadow_method == 'BUFFER_SHADOW':
1758                                 col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
1759                             else:
1760                                 col.label(text="No Shadow")
1761
1762                         if engine == 'CYCLES':
1763                             split = split.split(percentage=0.4)
1764                             col = split.column()
1765                             if lamp.type in ['POINT','SUN', 'SPOT']:
1766                                 col.label(text="%.2f" % lamp.shadow_soft_size)
1767                             elif lamp.type == 'HEMI':
1768                                 col.label(text="N/A")
1769                             elif lamp.type == 'AREA' and lamp.shape == 'RECTANGLE':
1770                                 col.label(text="%.2fx%.2f" % (lamp.size, lamp.size_y))
1771                             else:
1772                                 col.label(text="%.2f" % lamp.size)
1773
1774                         split = split.split(percentage=0.8)
1775                         col = split.column()
1776                         row = col.row(align=True)
1777                         row.prop(ob, "hide", text="", emboss=False)
1778                         row.prop(ob, "hide_render", text="", emboss=False)
1779
1780                         split = split.split(percentage=0.3)
1781                         col = split.column()
1782                         col.label(text="", icon="%s" % "TRIA_LEFT" if ob == ob_act else "BLANK1")
1783
1784         else:
1785             row = col.row(align=True)
1786             row.alignment = 'LEFT'
1787             row.label(text="Lamps List", icon="RIGHTARROW_THIN")
1788
1789             split = split.split()
1790             col = split.column()
1791
1792             col.label(text="No Lamps", icon="LAMP_DATA")
1793
1794         # List Missing Images
1795         box = layout.box()
1796         row = box.row(align=True)
1797         split = row.split()
1798         col = split.column()
1799
1800         if images:
1801             import os.path
1802
1803             for im in images:
1804                 if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']: 
1805                     if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
1806                         images_missing.append(["%s%s [%s]%s" % (
1807                             '[L] ' if im.library else '',
1808                             im.name, im.users,
1809                             ' [F]' if im.use_fake_user else ''),
1810                             im.filepath if im.filepath else 'No Filepath',
1811                             im.library.filepath if im.library else ''])
1812
1813             if images_missing:
1814                 row = col.row(align=True)
1815                 row.alignment = 'LEFT'
1816                 row.prop(scene, 'amaranth_debug_scene_list_missing_images',
1817                             icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
1818                             emboss=False)
1819
1820                 split = split.split()
1821                 col = split.column()
1822
1823                 col.label(text="%s missing %s" % (
1824                              str(len(images_missing)),
1825                              'image' if len(images_missing) == 1 else 'images'),
1826                              icon="ERROR")
1827
1828                 if list_missing_images:
1829                     col = box.column(align=True)
1830                     for mis in images_missing:
1831                         col.label(text=mis[0],
1832                          icon="IMAGE_DATA")
1833                         col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
1834                         if mis[2]:
1835                             row = col.row(align=True)
1836                             row.alignment = "LEFT"
1837                             row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
1838                                          text=mis[2],
1839                                          icon="LINK_BLEND",
1840                                          emboss=False).filepath=mis[2]
1841                         col.separator()
1842             else:
1843                 row = col.row(align=True)
1844                 row.alignment = 'LEFT'
1845                 row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")
1846
1847                 split = split.split()
1848                 col = split.column()
1849
1850                 col.label(text="%s %s loading correctly" % (
1851                              str(len(images)),
1852                              'image' if len(images) == 1 else 'images'),
1853                              icon="IMAGE_DATA")
1854         else:
1855             row = col.row(align=True)
1856             row.alignment = 'LEFT'
1857             row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
1858
1859         # List Cycles Materials by Shader
1860         if engine == 'CYCLES':
1861             box = layout.box()
1862             split = box.split()
1863             col = split.column(align=True)
1864             col.prop(scene, 'amaranth_cycles_node_types',
1865                 icon="MATERIAL")
1866
1867             row = split.row(align=True)
1868             row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
1869                             icon="SORTSIZE",
1870                             text="List Materials Using Shader")
1871             if materials_count != 0: 
1872                 row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
1873                                 icon="X", text="")
1874             col.separator()
1875
1876             try:
1877                 materials
1878             except NameError:
1879                 pass
1880             else:
1881                 if materials_count != 0: 
1882                     col = box.column(align=True)
1883                     count = 0
1884                     col.label(text="%s %s found" % (materials_count,
1885                         'material' if materials_count == 1 else 'materials'), icon="INFO")
1886                     for mat in materials:
1887                         count += 1
1888                         col.label(text='%s' % (materials[count-1]), icon="MATERIAL")
1889
1890         # List Missing Node Trees
1891         box = layout.box()
1892         row = box.row(align=True)
1893         split = row.split()
1894         col = split.column(align=True)
1895
1896         split = col.split()
1897         split.label(text="Node Links")
1898         split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
1899                         icon="NODETREE")
1900
1901         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
1902             AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
1903             AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
1904             col.label(text="Warning! Check Console", icon="ERROR")
1905
1906         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
1907             col.label(text="%s" % ("%s node %s missing link" % (
1908                      str(AMTH_SCENE_OT_list_missing_node_links.count_groups),
1909                      "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
1910                      icon="NODETREE")
1911         if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
1912             col.label(text="%s" % ("%s image %s missing link" % (
1913                      str(AMTH_SCENE_OT_list_missing_node_links.count_images),
1914                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
1915                      icon="IMAGE_DATA")
1916
1917         if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
1918             col.label(text="%s" % ("%s image %s with no output conected" % (
1919                      str(AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
1920                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
1921                      icon="NODE")
1922
1923         # List Empty Materials Slots
1924         box = layout.box()
1925         split = box.split()
1926         col = split.column(align=True)
1927         col.label(text="Material Slots")
1928
1929         row = split.row(align=True)
1930         row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
1931                         icon="MATERIAL",
1932                         text="List Empty Materials Slots")
1933         if missing_material_slots_count != 0: 
1934             row.operator(AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
1935                             icon="X", text="")
1936         col.separator()
1937
1938         try:
1939             missing_material_slots_obs
1940         except NameError:
1941             pass
1942         else:
1943             if missing_material_slots_count != 0: 
1944                 col = box.column(align=True)
1945                 count = 0
1946                 count_lib = 0
1947                 col.label(text="%s %s with empty material slots found" % (
1948                     missing_material_slots_count,
1949                     'object' if missing_material_slots_count == 1 else 'objects'),
1950                     icon="INFO")
1951
1952                 for obs in missing_material_slots_obs:
1953                     count += 1
1954                     col.label(text='%s' % (
1955                         missing_material_slots_obs[count-1]),
1956                         icon="OBJECT_DATA")
1957
1958                 if missing_material_slots_lib:
1959                     col.separator()
1960                     col.label("Check %s:" % (
1961                         "this library" if
1962                             len(missing_material_slots_lib) == 1
1963                                 else "these libraries"))
1964                     
1965                     for libs in missing_material_slots_lib:
1966                         count_lib += 1
1967                         row = col.row(align=True)
1968                         row.alignment = "LEFT"
1969                         row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
1970                                      text=missing_material_slots_lib[count_lib-1],
1971                                      icon="LINK_BLEND",
1972                                      emboss=False).filepath=missing_material_slots_lib[count_lib-1]
1973
1974 # // FEATURE: Scene Debug
1975 # FEATURE: Dupli  Group Path
1976 def ui_dupli_group_library_path(self, context):
1977
1978     ob = context.object
1979
1980     row = self.layout.row()
1981     row.alignment = 'LEFT'
1982
1983     if ob and ob.dupli_group and ob.dupli_group.library:
1984         lib = ob.dupli_group.library.filepath
1985
1986         row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
1987             text="Library: %s" % lib,
1988             emboss=False,
1989             icon="LINK_BLEND").filepath=lib
1990
1991 # // FEATURE: Dupli  Group Path
1992 # FEATURE: Color Management Presets
1993 class AMTH_SCENE_MT_color_management_presets(Menu):
1994     """List of Color Management presets"""
1995     bl_label = "Color Management Presets"
1996     preset_subdir = "color"
1997     preset_operator = "script.execute_preset"
1998     draw = Menu.draw_preset
1999
2000
2001 class AMTH_AddPresetColorManagement(AddPresetBase, Operator):
2002     """Add or remove a Color Management preset"""
2003     bl_idname = "scene.color_management_preset_add"
2004     bl_label = "Add Color Management Preset"
2005     preset_menu = "AMTH_SCENE_MT_color_management_presets"
2006
2007     preset_defines = [
2008         "scene = bpy.context.scene"
2009     ]
2010
2011     preset_values = [
2012         "scene.view_settings.view_transform",
2013         "scene.display_settings.display_device",
2014         "scene.view_settings.exposure",
2015         "scene.view_settings.gamma",
2016         "scene.view_settings.look",
2017         "scene.view_settings.use_curve_mapping",
2018         "scene.sequencer_colorspace_settings.name",
2019     ]
2020
2021     preset_subdir = "color"
2022
2023 def ui_color_management_presets(self, context):
2024     
2025     layout = self.layout
2026
2027     row = layout.row(align=True)
2028     row.menu("AMTH_SCENE_MT_color_management_presets", text=bpy.types.AMTH_SCENE_MT_color_management_presets.bl_label)
2029     row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
2030     row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
2031     layout.separator()
2032 # // FEATURE: Color Management Presets
2033
2034 # FEATURE: Sequencer Extra Info
2035 def act_strip(context):
2036     try:
2037         return context.scene.sequence_editor.active_strip
2038     except AttributeError:
2039         return None
2040
2041 def ui_sequencer_extra_info(self, context):
2042
2043     layout = self.layout
2044     strip = act_strip(context)
2045
2046     if strip:
2047         seq_type = strip.type
2048
2049         if seq_type and seq_type == 'IMAGE':
2050             elem = strip.strip_elem_from_frame(context.scene.frame_current)
2051             if elem:
2052                 layout.label(text="%s %s" % (
2053                     elem.filename,
2054                     "[%s]" % (context.scene.frame_current - strip.frame_start)))
2055 # // FEATURE: Sequencer Extra Info
2056
2057 # FEATURE: Normal Node Values, by Lukas Tönne
2058 def normal_vector_get(self):
2059     return self.outputs['Normal'].default_value
2060
2061 def normal_vector_set(self, values):
2062     # default_value allows un-normalized values,
2063     # do this here to prevent awkward results
2064     values = Vector(values).normalized()
2065     self.outputs['Normal'].default_value = values
2066
2067 prop_normal_vector = bpy.props.FloatVectorProperty(
2068                         name="Normal", size=3, subtype='XYZ',
2069                         min=-1.0, max=1.0, soft_min=-1.0, soft_max=1.0,
2070                         get=normal_vector_get, set=normal_vector_set
2071                         )
2072
2073 def act_node(context):
2074     try:
2075         return context.active_node
2076     except AttributeError:
2077         return None
2078
2079 def ui_node_normal_values(self, context):
2080
2081     node = act_node(context)
2082
2083     if act_node:
2084         if node and node.type == 'NORMAL':
2085             self.layout.prop(node, "normal_vector", text="")
2086
2087 # // FEATURE: Normal Node Values, by Lukas Tönne
2088
2089 # FEATURE: Object ID for objects inside DupliGroups
2090 class AMTH_OBJECT_OT_id_dupligroup(Operator):
2091     '''Set the Object ID for objects in the dupli group'''
2092     bl_idname = "object.amaranth_object_id_duplis"
2093     bl_label = "Apply Object ID to Duplis"
2094
2095     clear = False
2096
2097     @classmethod
2098     def poll(cls, context):
2099         return context.active_object.dupli_group
2100
2101     def execute(self, context):
2102         self.__class__.clear = False
2103         ob = context.active_object
2104         amth_text_exists = amaranth_text_startup(context)
2105
2106         script_exists = False
2107         script_intro = "# OB ID: %s" % ob.name
2108         obdata = "bpy.data.objects['%s']" % ob.name
2109         script = "%s" % (
2110             "\nif %(obdata)s and %(obdata)s.dupli_group and %(obdata)s.pass_index != 0: %(obname)s \n"
2111             "    for dob in %(obdata)s.dupli_group.objects: %(obname)s \n"
2112             "        dob.pass_index = %(obdata)s.pass_index %(obname)s \n" %
2113                 {'obdata' : obdata, 'obname' : script_intro})
2114
2115         for txt in bpy.data.texts:
2116             if txt.name == amth_text.name:
2117                 for li in txt.lines:
2118                     if script_intro == li.body:
2119                         script_exists = True
2120                         continue
2121
2122         if not script_exists:
2123             amth_text.write("\n")
2124             amth_text.write(script_intro)
2125             amth_text.write(script)
2126
2127         if ob and ob.dupli_group:
2128             if ob.pass_index != 0:
2129                 for dob in ob.dupli_group.objects:
2130                     dob.pass_index = ob.pass_index
2131
2132         self.report({'INFO'},
2133             "%s ID: %s to all objects in this Dupli Group" % (
2134                 "Applied" if not script_exists else "Updated",
2135                 ob.pass_index))
2136
2137         return{'FINISHED'}
2138
2139 class AMTH_OBJECT_OT_id_dupligroup_clear(Operator):
2140     '''Clear the Object ID from objects in dupli group'''
2141     bl_idname = "object.amaranth_object_id_duplis_clear"
2142     bl_label = "Clear Object ID from Duplis"
2143
2144     @classmethod
2145     def poll(cls, context):
2146         return context.active_object.dupli_group
2147
2148     def execute(self, context):
2149         context.active_object.pass_index = 0
2150         AMTH_OBJECT_OT_id_dupligroup.clear = True
2151         amth_text_exists = amaranth_text_startup(context)
2152         match_first = "# OB ID: %s" % context.active_object.name
2153
2154         if amth_text_exists:
2155             for txt in bpy.data.texts:
2156                 if txt.name == amth_text.name:
2157                     for li in txt.lines:
2158                         if match_first in li.body:
2159                             li.body = ''
2160                             continue
2161
2162         self.report({'INFO'}, "Object IDs back to normal")
2163         return{'FINISHED'}
2164
2165 def ui_object_id_duplis(self, context):
2166
2167     if context.active_object.dupli_group:
2168         split = self.layout.split()
2169         row = split.row(align=True)
2170         row.enabled = context.active_object.pass_index != 0
2171         row.operator(
2172             AMTH_OBJECT_OT_id_dupligroup.bl_idname)
2173         row.operator(
2174             AMTH_OBJECT_OT_id_dupligroup_clear.bl_idname,
2175             icon="X", text="")
2176         split.separator()
2177
2178         if AMTH_OBJECT_OT_id_dupligroup.clear:
2179             self.layout.label(text="Next time you save/reload this file, "
2180                                         "object IDs will be back to normal",
2181                               icon="INFO")
2182
2183 # // FEATURE: Object ID for objects inside DupliGroups
2184 # UI: Warning about Z not connected when using EXR
2185 def ui_render_output_z(self, context):
2186
2187     scene = bpy.context.scene
2188     image = scene.render.image_settings
2189     if scene.render.use_compositing and \
2190         image.file_format == 'OPEN_EXR' and \
2191         image.use_zbuffer:
2192         if scene.node_tree and scene.node_tree.nodes:
2193             for no in scene.node_tree.nodes:
2194                 if no.type == 'COMPOSITE':
2195                     if not no.inputs['Z'].is_linked:
2196                         self.layout.label(
2197                             text="The Z output in node \"%s\" is not connected" % 
2198                                 no.name, icon="ERROR")
2199
2200 # // UI: Warning about Z not connected
2201
2202 # FEATURE: Delete Materials not assigned to any verts
2203 class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
2204     '''Remove materials not assigned to any vertex'''
2205     bl_idname = "object.amaranth_object_material_remove_unassigned"
2206     bl_label = "Remove Unassigned Materials"
2207
2208     @classmethod
2209     def poll(cls, context):
2210         return context.active_object.material_slots
2211
2212     def execute(self, context):
2213
2214         act_ob = context.active_object
2215         count = len(act_ob.material_slots)
2216         materials_removed = []
2217         act_ob.active_material_index = 0
2218
2219         for slot in act_ob.material_slots:
2220             count -= 1
2221
2222             bpy.ops.object.mode_set(mode='EDIT')
2223             bpy.ops.mesh.select_all(action='DESELECT')
2224             act_ob.active_material_index = count
2225             bpy.ops.object.material_slot_select()
2226             
2227             if act_ob.data.total_vert_sel == 0 or \
2228                 (len(act_ob.material_slots) == 1 and not \
2229                     act_ob.material_slots[0].material):
2230                 materials_removed.append(
2231                     "%s" % act_ob.active_material.name if act_ob.active_material else "Empty")
2232                 bpy.ops.object.mode_set(mode='OBJECT')
2233                 bpy.ops.object.material_slot_remove()
2234             else:
2235                 pass
2236
2237         bpy.ops.object.mode_set(mode='EDIT')
2238         bpy.ops.mesh.select_all(action='DESELECT')
2239         bpy.ops.object.mode_set(mode='OBJECT')
2240
2241         if materials_removed:
2242             print("\n* Removed %s Unassigned Materials \n" % len(materials_removed))
2243
2244             count_mr = 0
2245
2246             for mr in materials_removed:
2247                 count_mr += 1
2248                 print("%0.2d. %s" % (count_mr, materials_removed[count_mr - 1]))
2249
2250             print("\n")
2251             self.report({'INFO'}, "Removed %s Unassigned Materials" %
2252                 len(materials_removed))
2253
2254         return{'FINISHED'}
2255
2256 def ui_material_remove_unassigned(self, context):
2257
2258     self.layout.operator(
2259         AMTH_OBJECT_OT_material_remove_unassigned.bl_idname,
2260         icon="X")
2261
2262 # // FEATURE: Delete Materials not assigned to any verts
2263
2264 # FEATURE: Cycles Samples Percentage
2265 class AMTH_RENDER_OT_cycles_samples_percentage_set(Operator):
2266     '''Save the current number of samples per shader as final (gets saved in .blend)'''
2267     bl_idname = "scene.amaranth_cycles_samples_percentage_set"
2268     bl_label = "Set as Render Samples"
2269
2270     def execute(self, context):
2271         cycles = context.scene.cycles
2272         cycles.use_samples_final = True
2273
2274         context.scene['amth_cycles_samples_final'] = [
2275             cycles.diffuse_samples,
2276             cycles.glossy_samples,
2277             cycles.transmission_samples,
2278             cycles.ao_samples,
2279             cycles.mesh_light_samples,
2280             cycles.subsurface_samples,
2281             cycles.volume_samples]
2282
2283         self.report({'INFO'}, "Render Samples Saved")
2284
2285         return{'FINISHED'}
2286
2287
2288 class AMTH_RENDER_OT_cycles_samples_percentage(Operator):
2289     '''Set a percentage of the final render samples'''
2290     bl_idname = "scene.amaranth_cycles_samples_percentage"
2291     bl_label = "Set Render Samples Percentage"
2292
2293     percent = IntProperty(
2294                 name="Percentage",
2295                 description="Percentage to divide render samples by",
2296                 subtype='PERCENTAGE',
2297                 default=0)
2298
2299     def execute(self, context):
2300         percent = self.percent
2301         cycles = context.scene.cycles
2302         cycles_samples_final = context.scene['amth_cycles_samples_final']
2303
2304         cycles.use_samples_final = False
2305
2306         if percent == 100:
2307             cycles.use_samples_final = True
2308
2309         cycles.diffuse_samples = int((cycles_samples_final[0] / 100) * percent)
2310         cycles.glossy_samples = int((cycles_samples_final[1] / 100) * percent)
2311         cycles.transmission_samples = int((cycles_samples_final[2] / 100) * percent)
2312         cycles.ao_samples = int((cycles_samples_final[3] / 100) * percent)
2313         cycles.mesh_light_samples = int((cycles_samples_final[4] / 100) * percent)
2314         cycles.subsurface_samples = int((cycles_samples_final[5] / 100) * percent)
2315         cycles.volume_samples = int((cycles_samples_final[6] / 100) * percent)
2316
2317         return{'FINISHED'}
2318
2319 classes = (AMTH_SCENE_MT_color_management_presets,
2320            AMTH_AddPresetColorManagement,
2321            AMTH_SCENE_PT_scene_debug,
2322            AMTH_SCENE_OT_refresh,
2323            AMTH_SCENE_OT_cycles_shader_list_nodes,
2324            AMTH_SCENE_OT_cycles_shader_list_nodes_clear,
2325            AMTH_SCENE_OT_amaranth_debug_lamp_select,
2326            AMTH_SCENE_OT_list_missing_node_links,
2327            AMTH_SCENE_OT_list_missing_material_slots,
2328            AMTH_SCENE_OT_list_missing_material_slots_clear,
2329            AMTH_SCENE_OT_blender_instance_open,
2330            AMTH_WM_OT_save_reload,
2331            AMTH_MESH_OT_find_asymmetric,
2332            AMTH_MESH_OT_make_symmetric,
2333            AMTH_NODE_OT_AddTemplateVignette,
2334            AMTH_NODE_MT_amaranth_templates,
2335            AMTH_FILE_OT_directory_current_blend,
2336            AMTH_FILE_OT_directory_go_to,
2337            AMTH_NODE_PT_indices,
2338            AMTH_NODE_PT_simplify,
2339            AMTH_NODE_OT_toggle_mute,
2340            AMTH_NODE_OT_show_active_node_image,
2341            AMTH_VIEW3D_OT_render_border_camera,
2342            AMTH_VIEW3D_OT_show_only_render,
2343            AMTH_OBJECT_OT_select_meshlights,
2344            AMTH_OBJECT_OT_id_dupligroup,
2345            AMTH_OBJECT_OT_id_dupligroup_clear,
2346            AMTH_OBJECT_OT_material_remove_unassigned,
2347            AMTH_POSE_OT_paths_clear_all,
2348            AMTH_POSE_OT_paths_frame_match,
2349            AMTH_RENDER_OT_cycles_samples_percentage,
2350            AMTH_RENDER_OT_cycles_samples_percentage_set,
2351            AMTH_FILE_PT_libraries)
2352
2353 addon_keymaps = []
2354
2355 def register():
2356
2357     bpy.utils.register_class(AmaranthToolsetPreferences)
2358
2359     # UI: Register the panel
2360     init_properties()
2361     for c in classes:
2362         bpy.utils.register_class(c)
2363
2364     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
2365     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
2366     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
2367
2368     bpy.types.INFO_MT_file.append(button_save_reload)
2369     bpy.types.INFO_HT_header.append(stats_scene)
2370
2371     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
2372     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
2373     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
2374
2375     bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
2376
2377     bpy.types.NODE_HT_header.append(node_templates_pulldown)
2378     bpy.types.NODE_HT_header.append(node_stats)
2379     bpy.types.NODE_HT_header.append(node_shader_extra)
2380     bpy.types.NODE_PT_active_node_properties.append(ui_node_normal_values)
2381
2382     bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
2383
2384     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
2385
2386     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
2387     bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
2388
2389     bpy.types.DATA_PT_display.append(pose_motion_paths_ui)
2390
2391     bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
2392     bpy.types.RENDER_PT_output.append(ui_render_output_z)
2393
2394     bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)
2395
2396     bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)
2397
2398     bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)
2399
2400     bpy.types.OBJECT_PT_relations.append(ui_object_id_duplis)
2401
2402     bpy.types.MATERIAL_MT_specials.append(ui_material_remove_unassigned)
2403
2404     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
2405     bpy.app.handlers.render_post.append(unsimplify_render_post)
2406
2407     wm = bpy.context.window_manager
2408     kc = wm.keyconfigs.addon
2409     if kc:
2410         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
2411         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
2412         km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
2413
2414         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
2415         kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS')
2416         kmi.properties.name = "AMTH_NODE_MT_amaranth_templates"
2417
2418         km = kc.keymaps.new(name='Window')
2419         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
2420         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
2421
2422         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
2423         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
2424
2425         km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
2426         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
2427         kmi.properties.data_path = 'area.type'
2428         kmi.properties.value = 'DOPESHEET_EDITOR'
2429
2430         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
2431         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
2432         kmi.properties.data_path = 'area.type'
2433         kmi.properties.value = 'GRAPH_EDITOR'
2434
2435         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
2436         kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
2437         kmi.properties.data_path = 'space_data.mode'
2438         kmi.properties.value_1 = 'ACTION'
2439         kmi.properties.value_2 = 'DOPESHEET'
2440
2441         addon_keymaps.append((km, kmi))
2442
2443 def unregister():
2444
2445     bpy.utils.unregister_class(AmaranthToolsetPreferences)
2446
2447     for c in classes:
2448         bpy.utils.unregister_class(c)
2449
2450     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
2451     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
2452     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
2453
2454     bpy.types.INFO_MT_file.remove(button_save_reload)
2455     bpy.types.INFO_HT_header.remove(stats_scene)
2456
2457     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
2458     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
2459     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
2460
2461     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
2462
2463     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
2464     bpy.types.NODE_HT_header.remove(node_stats)
2465     bpy.types.NODE_HT_header.remove(node_shader_extra)
2466     bpy.types.NODE_PT_active_node_properties.remove(ui_node_normal_values)
2467
2468     bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
2469
2470     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
2471
2472     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
2473     bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
2474
2475     bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)
2476
2477     bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
2478     bpy.types.RENDER_PT_output.remove(ui_render_output_z)
2479
2480     bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)
2481
2482     bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)
2483
2484     bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)
2485
2486     bpy.types.OBJECT_PT_relations.remove(ui_object_id_duplis)
2487
2488     bpy.types.MATERIAL_MT_specials.remove(ui_material_remove_unassigned)
2489
2490     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
2491     bpy.app.handlers.render_post.remove(unsimplify_render_post)
2492
2493     for km, kmi in addon_keymaps:
2494         km.keymap_items.remove(kmi)
2495     addon_keymaps.clear()
2496     
2497     clear_properties()
2498
2499 if __name__ == "__main__":
2500     register()