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