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