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