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