New option for the amaranth addon: Use current framerate as amount of frames to jump...
[blender-addons-contrib.git] / scene_amaranth_toolset.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 bl_info = {
20     "name": "Amaranth Toolset",
21     "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne",
22     "version": (0, 9, 4),
23     "blender": (2, 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                "ShaderNodeTexImage",
1028                "ShaderNodeTexEnvironment"}
1029
1030 class AMTH_NODE_OT_show_active_node_image(Operator):
1031     """Show active image node image in the image editor"""
1032     bl_idname = "node.show_active_node_image"
1033     bl_label = "Show Active Node Node"
1034     bl_options = {'UNDO'}
1035
1036     def execute(self, context):
1037         preferences = context.user_preferences.addons[__name__].preferences
1038         if preferences.use_image_node_display:
1039             if context.active_node:
1040                 active_node = context.active_node
1041                 if active_node.bl_idname in image_nodes and active_node.image:
1042                     for area in context.screen.areas:
1043                         if area.type == "IMAGE_EDITOR":
1044                             for space in area.spaces:
1045                                 if space.type == "IMAGE_EDITOR":
1046                                     space.image = active_node.image
1047                             break
1048     
1049         return {'FINISHED'}
1050 # // FEATURE: Display Active Image Node on Image Editor
1051
1052 # FEATURE: Select Meshlights
1053 class AMTH_OBJECT_OT_select_meshlights(Operator):
1054     """Select light emitting meshes"""
1055     bl_idname = "object.select_meshlights"
1056     bl_label = "Select Meshlights"
1057     bl_options = {'UNDO'}
1058
1059     @classmethod
1060     def poll(cls, context):
1061         return context.scene.render.engine == 'CYCLES'
1062
1063     def execute(self, context):
1064         # Deselect everything first
1065         bpy.ops.object.select_all(action='DESELECT')
1066
1067         for ob in context.scene.objects:
1068             if cycles_is_emission(context, ob):
1069                 ob.select = True
1070                 context.scene.objects.active = ob
1071
1072         if not context.selected_objects and not context.scene.objects.active:
1073             self.report({'INFO'}, "No meshlights to select")
1074
1075         return {'FINISHED'}
1076
1077 def button_select_meshlights(self, context):
1078     if cycles_exists and context.scene.render.engine == 'CYCLES':
1079         self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
1080 # // FEATURE: Select Meshlights
1081
1082 # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1083 class AMTH_MESH_OT_find_asymmetric(Operator):
1084     """
1085     Find asymmetric vertices
1086     """
1087
1088     bl_idname = "mesh.find_asymmetric"
1089     bl_label = "Find Asymmetric"
1090     bl_options = {'UNDO', 'REGISTER'}
1091
1092     @classmethod
1093     def poll(cls, context):
1094         object = context.object
1095         if object:
1096             return object.mode == 'EDIT' and object.type == 'MESH'
1097         return False
1098
1099     def execute(self, context):
1100         threshold = 1e-6
1101
1102         object = context.object
1103         bm = bmesh.from_edit_mesh(object.data)
1104
1105         # Deselect all the vertices
1106         for v in bm.verts:
1107             v.select = False
1108
1109         for v1 in bm.verts:
1110             if abs(v1.co[0]) < threshold:
1111                 continue
1112
1113             mirror_found = False
1114             for v2 in bm.verts:
1115                 if v1 == v2:
1116                     continue
1117                 if v1.co[0] * v2.co[0] > 0.0:
1118                     continue
1119
1120                 mirror_coord = Vector(v2.co)
1121                 mirror_coord[0] *= -1
1122                 if (mirror_coord - v1.co).length_squared < threshold:
1123                     mirror_found = True
1124                     break
1125             if not mirror_found:
1126                 v1.select = True
1127
1128         bm.select_flush_mode()
1129
1130         bmesh.update_edit_mesh(object.data)
1131
1132         return {'FINISHED'}
1133
1134 class AMTH_MESH_OT_make_symmetric(Operator):
1135     """
1136     Make symmetric
1137     """
1138
1139     bl_idname = "mesh.make_symmetric"
1140     bl_label = "Make Symmetric"
1141     bl_options = {'UNDO', 'REGISTER'}
1142
1143     @classmethod
1144     def poll(cls, context):
1145         object = context.object
1146         if object:
1147             return object.mode == 'EDIT' and object.type == 'MESH'
1148         return False
1149
1150     def execute(self, context):
1151         threshold = 1e-6
1152
1153         object = context.object
1154         bm = bmesh.from_edit_mesh(object.data)
1155
1156         for v1 in bm.verts:
1157             if v1.co[0] < threshold:
1158                 continue
1159             if not v1.select:
1160                 continue
1161
1162             closest_vert = None
1163             closest_distance = -1
1164             for v2 in bm.verts:
1165                 if v1 == v2:
1166                     continue
1167                 if v2.co[0] > threshold:
1168                     continue
1169                 if not v2.select:
1170                     continue
1171
1172                 mirror_coord = Vector(v2.co)
1173                 mirror_coord[0] *= -1
1174                 distance = (mirror_coord - v1.co).length_squared
1175                 if closest_vert is None or distance < closest_distance:
1176                     closest_distance = distance
1177                     closest_vert = v2
1178
1179             if closest_vert:
1180                 closest_vert.select = False
1181                 closest_vert.co = Vector(v1.co)
1182                 closest_vert.co[0] *= -1
1183             v1.select = False
1184
1185         for v1 in bm.verts:
1186             if v1.select:
1187                 closest_vert = None
1188                 closest_distance = -1
1189                 for v2 in bm.verts:
1190                     if v1 != v2:
1191                         mirror_coord = Vector(v2.co)
1192                         mirror_coord[0] *= -1
1193                         distance = (mirror_coord - v1.co).length_squared
1194                         if closest_vert is None or distance < closest_distance:
1195                             closest_distance = distance
1196                             closest_vert = v2
1197                 if closest_vert:
1198                     v1.select = False
1199                     v1.co = Vector(closest_vert.co)
1200                     v1.co[0] *= -1
1201
1202         bm.select_flush_mode()
1203         bmesh.update_edit_mesh(object.data)
1204
1205         return {'FINISHED'}
1206 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
1207
1208 # FEATURE: Cycles Render Sampling Extra
1209 def render_cycles_scene_samples(self, context):
1210
1211     layout = self.layout
1212     scenes = bpy.data.scenes
1213     scene = context.scene
1214     render = scene.render
1215     if cycles_exists:
1216         cscene = scene.cycles
1217         list_sampling = scene.amaranth_cycles_list_sampling
1218
1219     # Set Render Samples
1220     if cycles_exists and cscene.progressive == 'BRANCHED_PATH':
1221         layout.separator()
1222         split = layout.split()
1223         col = split.column()
1224
1225         col.operator(
1226             AMTH_RENDER_OT_cycles_samples_percentage_set.bl_idname,
1227             text="%s" % 'Set as Render Samples' if cscene.use_samples_final else 'Set New Render Samples',
1228             icon="%s" % 'PINNED' if cscene.use_samples_final else 'UNPINNED')
1229
1230         col = split.column()
1231         row = col.row(align=True)
1232         row.enabled = True if scene.get('amth_cycles_samples_final') else False
1233
1234         row.operator(
1235             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1236             text="100%").percent=100
1237         row.operator(
1238             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1239             text="75%").percent=75
1240         row.operator(
1241             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1242             text="50%").percent=50
1243         row.operator(
1244             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
1245             text="25%").percent=25
1246
1247     # List Samples
1248     if (len(scene.render.layers) > 1) or \
1249         (len(bpy.data.scenes) > 1):
1250
1251         box = layout.box()
1252         row = box.row(align=True)
1253         col = row.column(align=True)
1254
1255         row = col.row(align=True)
1256         row.alignment = 'LEFT'
1257         row.prop(scene, 'amaranth_cycles_list_sampling',
1258                     icon="%s" % 'TRIA_DOWN' if list_sampling else 'TRIA_RIGHT',
1259                     emboss=False)
1260
1261     if list_sampling:
1262         if len(scene.render.layers) == 1 and \
1263             render.layers[0].samples == 0:
1264             pass
1265         else:
1266             col.separator()
1267             col.label(text="RenderLayers:", icon='RENDERLAYERS')
1268
1269             for rl in scene.render.layers:
1270                 row = col.row(align=True)
1271                 row.label(rl.name, icon='BLANK1')
1272                 row.prop(rl, "samples", text="%s" %
1273                     "Samples" if rl.samples > 0 else "Automatic (%s)" % (
1274                         cscene.aa_samples if cscene.progressive == 'BRANCHED_PATH' else cscene.samples))
1275
1276         if (len(bpy.data.scenes) > 1):
1277             col.separator()
1278
1279             col.label(text="Scenes:", icon='SCENE_DATA')
1280
1281             if cycles_exists and cscene.progressive == 'PATH':
1282                 for s in bpy.data.scenes:
1283                     if s != scene:
1284                         row = col.row(align=True)
1285                         if s.render.engine == 'CYCLES':
1286                             cscene = s.cycles
1287
1288                             row.label(s.name)
1289                             row.prop(cscene, "samples", icon='BLANK1')
1290                         else:
1291                             row.label(text="Scene: '%s' is not using Cycles" % s.name)
1292             else:
1293                 for s in bpy.data.scenes:
1294                     if s != scene:
1295                         row = col.row(align=True)
1296                         if s.render.engine == 'CYCLES':
1297                             cscene = s.cycles
1298
1299                             row.label(s.name, icon='BLANK1')
1300                             row.prop(cscene, "aa_samples",
1301                                 text="AA Samples")
1302                         else:
1303                             row.label(text="Scene: '%s' is not using Cycles" % s.name)
1304
1305 # // FEATURE: Cycles Render Sampling Extra
1306
1307 # FEATURE: Motion Paths Extras
1308 class AMTH_POSE_OT_paths_clear_all(Operator):
1309     """Clear motion paths from all bones"""
1310     bl_idname = "pose.paths_clear_all"
1311     bl_label = "Clear All Motion Paths"
1312     bl_options = {'UNDO'}
1313
1314     @classmethod
1315     def poll(cls, context):
1316         return context.mode == 'POSE'
1317
1318     def execute(self, context):
1319         #silly but works
1320         for b in context.object.data.bones:
1321             b.select = True
1322             bpy.ops.pose.paths_clear()
1323             b.select = False
1324         return {'FINISHED'}
1325
1326 class AMTH_POSE_OT_paths_frame_match(Operator):
1327     """Match Start/End frame of scene to motion path range"""
1328     bl_idname = "pose.paths_frame_match"
1329     bl_label = "Match Frame Range"
1330     bl_options = {'UNDO'}
1331
1332     def execute(self, context):
1333         avs = context.object.pose.animation_visualization
1334         scene = context.scene
1335
1336         if avs.motion_path.type == 'RANGE':
1337             if scene.use_preview_range:
1338                 avs.motion_path.frame_start = scene.frame_preview_start
1339                 avs.motion_path.frame_end = scene.frame_preview_end
1340             else:
1341                 avs.motion_path.frame_start = scene.frame_start
1342                 avs.motion_path.frame_end = scene.frame_end
1343
1344         else:
1345             if scene.use_preview_range:
1346                 avs.motion_path.frame_before = scene.frame_preview_start
1347                 avs.motion_path.frame_after = scene.frame_preview_end
1348             else:
1349                 avs.motion_path.frame_before = scene.frame_start
1350                 avs.motion_path.frame_after = scene.frame_end
1351
1352         return {'FINISHED'}
1353
1354 def pose_motion_paths_ui(self, context):
1355
1356     layout = self.layout
1357     scene = context.scene
1358     avs = context.object.pose.animation_visualization
1359     if context.active_pose_bone:
1360         mpath = context.active_pose_bone.motion_path
1361     layout.separator()    
1362     layout.label(text="Motion Paths Extras:")
1363
1364     split = layout.split()
1365
1366     col = split.column(align=True)
1367
1368     if context.selected_pose_bones:
1369         if mpath:
1370             sub = col.row(align=True)
1371             sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
1372             sub.operator("pose.paths_clear", text="", icon='X')
1373         else:
1374             col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
1375     else:
1376         col.label(text="Select Bones First", icon="ERROR")
1377
1378     col = split.column(align=True)
1379     col.operator(AMTH_POSE_OT_paths_frame_match.bl_idname,
1380         text="{}".format( "Set Preview Frame Range"
1381                 if scene.use_preview_range else "Set Frame Range"),
1382         icon="{}".format("PREVIEW_RANGE"
1383                 if scene.use_preview_range else "TIME"))
1384
1385     col = layout.column()
1386     row = col.row(align=True)
1387
1388     if avs.motion_path.type == 'RANGE':
1389         row.prop(avs.motion_path, "frame_start", text="Start")
1390         row.prop(avs.motion_path, "frame_end", text="End")
1391     else:
1392         row.prop(avs.motion_path, "frame_before", text="Before")
1393         row.prop(avs.motion_path, "frame_after", text="After")
1394
1395     layout.separator()
1396     layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
1397 # // FEATURE: Motion Paths Extras
1398
1399 # FEATURE: Final Render Resolution Display
1400 def render_final_resolution_ui(self, context):
1401
1402     rd = context.scene.render
1403     layout = self.layout
1404
1405     final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
1406     final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
1407
1408     if rd.use_border:
1409        final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
1410        final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
1411        layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
1412              str(final_res_x)[:-2], str(final_res_y)[:-2],
1413              str(final_res_x_border), str(final_res_y_border)))
1414     else:
1415         layout.label(text="Final Resolution: {} x {}".format(
1416              str(final_res_x)[:-2], str(final_res_y)[:-2]))
1417 # // FEATURE: Final Render Resolution Display
1418
1419 # FEATURE: Shader Nodes Extra Info
1420 def node_shader_extra(self, context):
1421
1422     if context.space_data.tree_type == 'ShaderNodeTree':
1423         ob = context.active_object
1424         snode = context.space_data
1425         layout = self.layout
1426
1427         if ob and snode.shader_type != 'WORLD':
1428             if ob.type == 'LAMP':
1429                 layout.label(text="%s" % ob.name,
1430                              icon="LAMP_%s" % ob.data.type)        
1431             else:
1432                 layout.label(text="%s" % ob.name,
1433                              icon="OUTLINER_DATA_%s" % ob.type)
1434              
1435
1436 # // FEATURE: Shader Nodes Extra Info
1437
1438 # FEATURE: Scene Debug
1439 class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
1440     """List Cycles materials containing a specific shader"""
1441     bl_idname = "scene.cycles_list_nodes"
1442     bl_label = "List Materials"
1443     materials = []
1444
1445     @classmethod
1446     def poll(cls, context):
1447         return cycles_exists and context.scene.render.engine == 'CYCLES'
1448
1449     def execute(self, context):
1450         node_type = context.scene.amaranth_cycles_node_types
1451         roughness = False
1452         self.__class__.materials = []
1453         shaders_roughness = ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']
1454
1455         print("\n=== Cycles Shader Type: %s === \n" % node_type)
1456
1457         for ma in bpy.data.materials:
1458             if ma.node_tree:
1459                 nodes = ma.node_tree.nodes
1460                 
1461                 print_unconnected = ('Note: \nOutput from "%s" node' % node_type,
1462                                         'in material "%s"' % ma.name, 'not connected\n')
1463
1464                 for no in nodes:
1465                     if no.type == node_type:
1466                         for ou in no.outputs:
1467                             if ou.links:
1468                                 connected = True
1469                                 if no.type in shaders_roughness:
1470                                     roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
1471                                 else:
1472                                     roughness = False
1473                             else:
1474                                 connected = False
1475                                 print(print_unconnected)
1476
1477                             if ma.name not in self.__class__.materials:
1478                                 self.__class__.materials.append('%s%s [%s] %s%s%s' % (
1479                                     '[L] ' if ma.library else '',
1480                                     ma.name, ma.users,
1481                                     '[F]' if ma.use_fake_user else '',
1482                                     ' - [%s]' % roughness if roughness else '',
1483                                     ' * Output not connected' if not connected else ''))
1484
1485                     elif no.type == 'GROUP':
1486                         if no.node_tree:
1487                             for nog in no.node_tree.nodes:
1488                                 if nog.type == node_type:
1489                                     for ou in nog.outputs:
1490                                         if ou.links:
1491                                             connected = True
1492                                             if nog.type in shaders_roughness:
1493                                                 roughness = 'R: %.4f' % nog.inputs['Roughness'].default_value
1494                                             else:
1495                                                 roughness = False
1496                                         else:
1497                                             connected = False
1498                                             print(print_unconnected)
1499
1500                                         if ma.name not in self.__class__.materials:
1501                                             self.__class__.materials.append('%s%s%s [%s] %s%s%s' % (
1502                                                 '[L] ' if ma.library else '',
1503                                                 'Node Group:  %s%s  ->  ' % (
1504                                                     '[L] ' if no.node_tree.library else '',
1505                                                     no.node_tree.name),
1506                                                 ma.name, ma.users,
1507                                                 '[F]' if ma.use_fake_user else '',
1508                                                 ' - [%s]' % roughness if roughness else '',
1509                                                 ' * Output not connected' if not connected else ''))
1510
1511                     self.__class__.materials = sorted(list(set(self.__class__.materials)))
1512
1513         if len(self.__class__.materials) == 0:
1514             self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
1515         else:
1516             print("* A total of %d %s using %s was found \n" % (
1517                     len(self.__class__.materials),
1518                     "material" if len(self.__class__.materials) == 1 else "materials",
1519                     node_type))
1520
1521             count = 0
1522
1523             for mat in self.__class__.materials:
1524                 print('%02d. %s' % (count+1, self.__class__.materials[count]))
1525                 count += 1
1526             print("\n")
1527
1528         self.__class__.materials = sorted(list(set(self.__class__.materials)))
1529
1530         return {'FINISHED'}
1531
1532 class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(Operator):
1533     """Clear the list below"""
1534     bl_idname = "scene.cycles_list_nodes_clear"
1535     bl_label = "Clear Materials List"
1536
1537     @classmethod
1538     def poll(cls, context):
1539         return cycles_exists
1540
1541     def execute(self, context):
1542         AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
1543         print("* Cleared Cycles Materials List")
1544         return {'FINISHED'}
1545
1546 class AMTH_SCENE_OT_amaranth_object_select(Operator):
1547     '''Select object'''
1548     bl_idname = "scene.amaranth_object_select"
1549     bl_label = "Select Object"
1550     object = bpy.props.StringProperty()
1551  
1552     def execute(self, context):
1553         if self.object:
1554             object = bpy.data.objects[self.object]
1555
1556             bpy.ops.object.select_all(action='DESELECT')
1557             object.select = True
1558             context.scene.objects.active = object
1559
1560         return{'FINISHED'}
1561
1562 class AMTH_SCENE_OT_list_missing_node_links(Operator):
1563     '''Print a list of missing node links'''
1564     bl_idname = "scene.list_missing_node_links"
1565     bl_label = "List Missing Node Links"
1566
1567     count_groups = 0
1568     count_images = 0
1569     count_image_node_unlinked = 0
1570
1571     def execute(self, context):
1572         missing_groups = []
1573         missing_images = []
1574         image_nodes_unlinked = []
1575         libraries = []
1576         self.__class__.count_groups = 0
1577         self.__class__.count_images = 0
1578         self.__class__.count_image_node_unlinked = 0
1579
1580         for ma in bpy.data.materials:
1581             if ma.node_tree:
1582                 for no in ma.node_tree.nodes:
1583                     if no.type == 'GROUP':
1584                         if not no.node_tree:
1585                             self.__class__.count_groups += 1
1586
1587                             users_ngroup = []
1588
1589                             for ob in bpy.data.objects:
1590                                 if ob.material_slots and ma.name in ob.material_slots:
1591                                     users_ngroup.append("%s%s%s" % (
1592                                         "[L] " if ob.library else "",
1593                                         "[F] " if ob.use_fake_user else "",
1594                                         ob.name))
1595
1596                             missing_groups.append("MA: %s%s%s [%s]%s%s%s\n" % (
1597                                 "[L] " if ma.library else "",
1598                                 "[F] " if ma.use_fake_user else "",
1599                                 ma.name, ma.users,
1600                                 " *** No users *** " if ma.users == 0 else "",
1601                                 "\nLI: %s" % 
1602                                 ma.library.filepath if ma.library else "",
1603                                 "\nOB: %s" % ',  '.join(users_ngroup) if users_ngroup else ""))
1604
1605                             if ma.library:
1606                                 libraries.append(ma.library.filepath)
1607                     if no.type == 'TEX_IMAGE':
1608
1609                         outputs_empty = not no.outputs['Color'].is_linked and not no.outputs['Alpha'].is_linked
1610
1611                         if no.image:
1612                             import os.path
1613                             image_path_exists = os.path.exists(
1614                                                     bpy.path.abspath(
1615                                                         no.image.filepath, library=no.image.library))
1616
1617                         if outputs_empty or not \
1618                            no.image or not \
1619                            image_path_exists:
1620
1621                             users_images = []
1622
1623                             for ob in bpy.data.objects:
1624                                 if ob.material_slots and ma.name in ob.material_slots:
1625                                     users_images.append("%s%s%s" % (
1626                                         "[L] " if ob.library else "",
1627                                         "[F] " if ob.use_fake_user else "",
1628                                         ob.name))
1629
1630                             if outputs_empty:
1631                                 self.__class__.count_image_node_unlinked += 1
1632
1633                                 image_nodes_unlinked.append("%s%s%s%s%s [%s]%s%s%s%s%s\n" % (
1634                                     "NO: %s" % no.name,
1635                                     "\nMA: ",
1636                                     "[L] " if ma.library else "",
1637                                     "[F] " if ma.use_fake_user else "",
1638                                     ma.name, ma.users,
1639                                     " *** No users *** " if ma.users == 0 else "",
1640                                     "\nLI: %s" % 
1641                                     ma.library.filepath if ma.library else "",
1642                                     "\nIM: %s" % no.image.name if no.image else "",
1643                                     "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
1644                                     "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
1645                             
1646
1647                             if not no.image or not image_path_exists:
1648                                 self.__class__.count_images += 1
1649
1650                                 missing_images.append("MA: %s%s%s [%s]%s%s%s%s%s\n" % (
1651                                     "[L] " if ma.library else "",
1652                                     "[F] " if ma.use_fake_user else "",
1653                                     ma.name, ma.users,
1654                                     " *** No users *** " if ma.users == 0 else "",
1655                                     "\nLI: %s" % 
1656                                     ma.library.filepath if ma.library else "",
1657                                     "\nIM: %s" % no.image.name if no.image else "",
1658                                     "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
1659                                     "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
1660
1661                                 if ma.library:
1662                                     libraries.append(ma.library.filepath)
1663
1664         # Remove duplicates and sort
1665         missing_groups = sorted(list(set(missing_groups)))
1666         missing_images = sorted(list(set(missing_images)))
1667         image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
1668         libraries = sorted(list(set(libraries)))
1669
1670         print("\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
1671             ("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
1672             "node" if self.__class__.count_images == 1 else "nodes",
1673             "no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
1674             "group" if self.__class__.count_groups == 1 else "groups",
1675             "no" if self.__class__.count_image_node_unlinked == 0 else str(self.__class__.count_image_node_unlinked),
1676             "node" if self.__class__.count_groups == 1 else "nodes"))
1677
1678         # List Missing Node Groups
1679         if missing_groups:
1680             print("\n* Missing Node Group Links\n")
1681             for mig in missing_groups:
1682                 print(mig)
1683
1684         # List Missing Image Nodes
1685         if missing_images:
1686             print("\n* Missing Image Nodes Link\n")
1687
1688             for mii in missing_images:
1689                 print(mii)
1690
1691         # List Image Nodes with its outputs unlinked
1692         if image_nodes_unlinked:
1693             print("\n* Image Nodes Unlinked\n")
1694
1695             for nou in image_nodes_unlinked:
1696                 print(nou)
1697
1698         if missing_groups or \
1699            missing_images or \
1700            image_nodes_unlinked:
1701             if libraries:
1702                 print("\nThat's bad, run check on %s:" % (
1703                     "this library" if len(libraries) == 1 else "these libraries"))
1704                 for li in libraries:
1705                     print(li)
1706         else:
1707             self.report({"INFO"}, "Yay! No missing node links")            
1708
1709         print("\n")
1710
1711         if missing_groups and missing_images:
1712             self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
1713                 (self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
1714                 self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))
1715
1716         return{'FINISHED'}
1717
1718 class AMTH_SCENE_OT_list_missing_material_slots(Operator):
1719     '''List objects with empty material slots'''
1720     bl_idname = "scene.list_missing_material_slots"
1721     bl_label = "List Empty Material Slots"
1722
1723     objects = []
1724     libraries = []
1725
1726     def execute(self, context):
1727         self.__class__.objects = []
1728         self.__class__.libraries = []
1729
1730         for ob in bpy.data.objects:
1731             for ma in ob.material_slots:
1732                 if not ma.material:
1733                     self.__class__.objects.append('%s%s' % (
1734                         '[L] ' if ob.library else '',
1735                         ob.name))
1736                     if ob.library:
1737                         self.__class__.libraries.append(ob.library.filepath)
1738
1739         self.__class__.objects = sorted(list(set(self.__class__.objects)))
1740         self.__class__.libraries = sorted(list(set(self.__class__.libraries)))
1741
1742         if len(self.__class__.objects) == 0:
1743             self.report({"INFO"}, "No objects with empty material slots found")
1744         else:
1745             print("\n* A total of %d %s with empty material slots was found \n" % (
1746                     len(self.__class__.objects),
1747                     "object" if len(self.__class__.objects) == 1 else "objects"))
1748
1749             count = 0
1750             count_lib = 0
1751
1752             for obs in self.__class__.objects:
1753                 print('%02d. %s' % (
1754                     count+1, self.__class__.objects[count]))
1755                 count += 1
1756
1757             if self.__class__.libraries:
1758                 print("\n\n* Check %s:\n" % 
1759                     ("this library" if len(self.__class__.libraries) == 1
1760                         else "these libraries"))
1761
1762                 for libs in self.__class__.libraries:
1763                     print('%02d. %s' % (
1764                         count_lib+1, self.__class__.libraries[count_lib]))
1765                     count_lib += 1
1766             print("\n")
1767
1768         return{'FINISHED'}
1769
1770 class AMTH_SCENE_OT_list_missing_material_slots_clear(Operator):
1771     """Clear the list below"""
1772     bl_idname = "scene.list_missing_material_slots_clear"
1773     bl_label = "Clear Empty Material Slots List"
1774     
1775     def execute(self, context):
1776         AMTH_SCENE_OT_list_missing_material_slots.objects[:] = []
1777         print("* Cleared Empty Material Slots List")
1778         return {'FINISHED'}
1779
1780 class AMTH_SCENE_OT_blender_instance_open(Operator):
1781     '''Open in a new Blender instance'''
1782     bl_idname = "scene.blender_instance_open"
1783     bl_label = "Open Blender Instance"
1784     filepath = bpy.props.StringProperty()
1785
1786     def execute(self, context):
1787         if self.filepath:
1788             import os.path
1789             filepath = os.path.normpath(bpy.path.abspath(self.filepath))
1790
1791             import subprocess
1792             try:
1793                 subprocess.Popen([bpy.app.binary_path, filepath])
1794             except:
1795                 print("Error on the new Blender instance")
1796                 import traceback
1797                 traceback.print_exc()
1798
1799         return{'FINISHED'}
1800
1801 class AMTH_SCENE_PT_scene_debug(Panel):
1802     '''Scene Debug'''
1803     bl_label = 'Scene Debug'
1804     bl_space_type = "PROPERTIES"
1805     bl_region_type = "WINDOW"
1806     bl_context = "scene"
1807
1808     def draw_header(self, context):
1809         layout = self.layout
1810         layout.label(text="", icon="RADIO")
1811
1812     def draw(self, context):
1813         layout = self.layout
1814         scene = context.scene
1815         objects =  bpy.data.objects
1816         ob_act = context.active_object
1817         images = bpy.data.images
1818         lamps = bpy.data.lamps
1819         images_missing = []
1820         list_missing_images = scene.amaranth_debug_scene_list_missing_images
1821         materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
1822         materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
1823         missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
1824         missing_material_slots_count = len(AMTH_SCENE_OT_list_missing_material_slots.objects)
1825         missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
1826         engine = scene.render.engine
1827
1828         # List Missing Images
1829         box = layout.box()
1830         row = box.row(align=True)
1831         split = row.split()
1832         col = split.column()
1833
1834         if images:
1835             import os.path
1836
1837             for im in images:
1838                 if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']: 
1839                     if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
1840                         images_missing.append(["%s%s [%s]%s" % (
1841                             '[L] ' if im.library else '',
1842                             im.name, im.users,
1843                             ' [F]' if im.use_fake_user else ''),
1844                             im.filepath if im.filepath else 'No Filepath',
1845                             im.library.filepath if im.library else ''])
1846
1847             if images_missing:
1848                 row = col.row(align=True)
1849                 row.alignment = 'LEFT'
1850                 row.prop(scene, 'amaranth_debug_scene_list_missing_images',
1851                             icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
1852                             emboss=False)
1853
1854                 split = split.split()
1855                 col = split.column()
1856
1857                 col.label(text="%s missing %s" % (
1858                              str(len(images_missing)),
1859                              'image' if len(images_missing) == 1 else 'images'),
1860                              icon="ERROR")
1861
1862                 if list_missing_images:
1863                     col = box.column(align=True)
1864                     for mis in images_missing:
1865                         col.label(text=mis[0],
1866                          icon="IMAGE_DATA")
1867                         col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
1868                         if mis[2]:
1869                             row = col.row(align=True)
1870                             row.alignment = "LEFT"
1871                             row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
1872                                          text=mis[2],
1873                                          icon="LINK_BLEND",
1874                                          emboss=False).filepath=mis[2]
1875                         col.separator()
1876             else:
1877                 row = col.row(align=True)
1878                 row.alignment = 'LEFT'
1879                 row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")
1880
1881                 split = split.split()
1882                 col = split.column()
1883
1884                 col.label(text="%s %s loading correctly" % (
1885                              str(len(images)),
1886                              'image' if len(images) == 1 else 'images'),
1887                              icon="IMAGE_DATA")
1888         else:
1889             row = col.row(align=True)
1890             row.alignment = 'LEFT'
1891             row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
1892
1893         # List Cycles Materials by Shader
1894         if cycles_exists and engine == 'CYCLES':
1895             box = layout.box()
1896             split = box.split()
1897             col = split.column(align=True)
1898             col.prop(scene, 'amaranth_cycles_node_types',
1899                 icon="MATERIAL")
1900
1901             row = split.row(align=True)
1902             row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
1903                             icon="SORTSIZE",
1904                             text="List Materials Using Shader")
1905             if materials_count != 0: 
1906                 row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
1907                                 icon="X", text="")
1908             col.separator()
1909
1910             try:
1911                 materials
1912             except NameError:
1913                 pass
1914             else:
1915                 if materials_count != 0: 
1916                     col = box.column(align=True)
1917                     count = 0
1918                     col.label(text="%s %s found" % (materials_count,
1919                         'material' if materials_count == 1 else 'materials'), icon="INFO")
1920                     for mat in materials:
1921                         count += 1
1922                         col.label(text='%s' % (materials[count-1]), icon="MATERIAL")
1923
1924         # List Missing Node Trees
1925         box = layout.box()
1926         row = box.row(align=True)
1927         split = row.split()
1928         col = split.column(align=True)
1929
1930         split = col.split()
1931         split.label(text="Node Links")
1932         split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
1933                         icon="NODETREE")
1934
1935         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
1936             AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
1937             AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
1938             col.label(text="Warning! Check Console", icon="ERROR")
1939
1940         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
1941             col.label(text="%s" % ("%s node %s missing link" % (
1942                      str(AMTH_SCENE_OT_list_missing_node_links.count_groups),
1943                      "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
1944                      icon="NODETREE")
1945         if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
1946             col.label(text="%s" % ("%s image %s missing link" % (
1947                      str(AMTH_SCENE_OT_list_missing_node_links.count_images),
1948                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
1949                      icon="IMAGE_DATA")
1950
1951         if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
1952             col.label(text="%s" % ("%s image %s with no output conected" % (
1953                      str(AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
1954                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
1955                      icon="NODE")
1956
1957         # List Empty Materials Slots
1958         box = layout.box()
1959         split = box.split()
1960         col = split.column(align=True)
1961         col.label(text="Material Slots")
1962
1963         row = split.row(align=True)
1964         row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
1965                         icon="MATERIAL",
1966                         text="List Empty Materials Slots")
1967         if missing_material_slots_count != 0: 
1968             row.operator(AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
1969                             icon="X", text="")
1970         col.separator()
1971
1972         try:
1973             missing_material_slots_obs
1974         except NameError:
1975             pass
1976         else:
1977             if missing_material_slots_count != 0: 
1978                 col = box.column(align=True)
1979                 count = 0
1980                 count_lib = 0
1981                 col.label(text="%s %s with empty material slots found" % (
1982                     missing_material_slots_count,
1983                     'object' if missing_material_slots_count == 1 else 'objects'),
1984                     icon="INFO")
1985
1986                 for obs in missing_material_slots_obs:
1987                     count += 1
1988
1989                     row = col.row()
1990                     row.alignment = 'LEFT'
1991                     row.label(text='%s' % missing_material_slots_obs[count-1],
1992                                 icon="OBJECT_DATA")
1993
1994                 if missing_material_slots_lib:
1995                     col.separator()
1996                     col.label("Check %s:" % (
1997                         "this library" if
1998                             len(missing_material_slots_lib) == 1
1999                                 else "these libraries"))
2000                     
2001                     for libs in missing_material_slots_lib:
2002                         count_lib += 1
2003                         row = col.row(align=True)
2004                         row.alignment = "LEFT"
2005                         row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
2006                                      text=missing_material_slots_lib[count_lib-1],
2007                                      icon="LINK_BLEND",
2008                                      emboss=False).filepath=missing_material_slots_lib[count_lib-1]
2009
2010 # // FEATURE: Scene Debug
2011 # FEATURE: Dupli  Group Path
2012 def ui_dupli_group_library_path(self, context):
2013
2014     ob = context.object
2015
2016     row = self.layout.row()
2017     row.alignment = 'LEFT'
2018
2019     if ob and ob.dupli_group and ob.dupli_group.library:
2020         lib = ob.dupli_group.library.filepath
2021
2022         row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
2023             text="Library: %s" % lib,
2024             emboss=False,
2025             icon="LINK_BLEND").filepath=lib
2026
2027 # // FEATURE: Dupli  Group Path
2028 # FEATURE: Color Management Presets
2029 class AMTH_SCENE_MT_color_management_presets(Menu):
2030     """List of Color Management presets"""
2031     bl_label = "Color Management Presets"
2032     preset_subdir = "color"
2033     preset_operator = "script.execute_preset"
2034     draw = Menu.draw_preset
2035
2036
2037 class AMTH_AddPresetColorManagement(AddPresetBase, Operator):
2038     """Add or remove a Color Management preset"""
2039     bl_idname = "scene.color_management_preset_add"
2040     bl_label = "Add Color Management Preset"
2041     preset_menu = "AMTH_SCENE_MT_color_management_presets"
2042
2043     preset_defines = [
2044         "scene = bpy.context.scene"
2045     ]
2046
2047     preset_values = [
2048         "scene.view_settings.view_transform",
2049         "scene.display_settings.display_device",
2050         "scene.view_settings.exposure",
2051         "scene.view_settings.gamma",
2052         "scene.view_settings.look",
2053         "scene.view_settings.use_curve_mapping",
2054         "scene.sequencer_colorspace_settings.name",
2055     ]
2056
2057     preset_subdir = "color"
2058
2059 def ui_color_management_presets(self, context):
2060     
2061     layout = self.layout
2062
2063     row = layout.row(align=True)
2064     row.menu("AMTH_SCENE_MT_color_management_presets", text=bpy.types.AMTH_SCENE_MT_color_management_presets.bl_label)
2065     row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
2066     row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
2067     layout.separator()
2068 # // FEATURE: Color Management Presets
2069
2070 # FEATURE: Sequencer Extra Info
2071 def act_strip(context):
2072     try:
2073         return context.scene.sequence_editor.active_strip
2074     except AttributeError:
2075         return None
2076
2077 def ui_sequencer_extra_info(self, context):
2078
2079     layout = self.layout
2080     strip = act_strip(context)
2081
2082     if strip:
2083         seq_type = strip.type
2084
2085         if seq_type and seq_type == 'IMAGE':
2086             elem = strip.strip_elem_from_frame(context.scene.frame_current)
2087             if elem:
2088                 layout.label(text="%s %s" % (
2089                     elem.filename,
2090                     "[%s]" % (context.scene.frame_current - strip.frame_start)))
2091 # // FEATURE: Sequencer Extra Info
2092
2093 # FEATURE: Normal Node Values, by Lukas Tönne
2094 def normal_vector_get(self):
2095     return self.outputs['Normal'].default_value
2096
2097 def normal_vector_set(self, values):
2098     # default_value allows un-normalized values,
2099     # do this here to prevent awkward results
2100     values = Vector(values).normalized()
2101     self.outputs['Normal'].default_value = values
2102
2103 prop_normal_vector = bpy.props.FloatVectorProperty(
2104                         name="Normal", size=3, subtype='XYZ',
2105                         min=-1.0, max=1.0, soft_min=-1.0, soft_max=1.0,
2106                         get=normal_vector_get, set=normal_vector_set
2107                         )
2108
2109 def act_node(context):
2110     try:
2111         return context.active_node
2112     except AttributeError:
2113         return None
2114
2115 def ui_node_normal_values(self, context):
2116
2117     node = act_node(context)
2118
2119     if act_node:
2120         if node and node.type == 'NORMAL':
2121             self.layout.prop(node, "normal_vector", text="")
2122
2123 # // FEATURE: Normal Node Values, by Lukas Tönne
2124
2125 # FEATURE: Object ID for objects inside DupliGroups
2126 class AMTH_OBJECT_OT_id_dupligroup(Operator):
2127     '''Set the Object ID for objects in the dupli group'''
2128     bl_idname = "object.amaranth_object_id_duplis"
2129     bl_label = "Apply Object ID to Duplis"
2130
2131     clear = False
2132
2133     @classmethod
2134     def poll(cls, context):
2135         return context.active_object.dupli_group
2136
2137     def execute(self, context):
2138         self.__class__.clear = False
2139         ob = context.active_object
2140         amth_text_exists = amaranth_text_startup(context)
2141         script_exists = False
2142         script_intro = "# OB ID: %s" % ob.name
2143         obdata = "bpy.data.objects['%s']" % ob.name
2144         script = "%s" % (
2145             "\nif %(obdata)s and %(obdata)s.dupli_group and %(obdata)s.pass_index != 0: %(obname)s \n"
2146             "    for dob in %(obdata)s.dupli_group.objects: %(obname)s \n"
2147             "        dob.pass_index = %(obdata)s.pass_index %(obname)s \n" %
2148                 {'obdata' : obdata, 'obname' : script_intro})
2149
2150         for txt in bpy.data.texts:
2151             if txt.name == amth_text.name:
2152                 for li in txt.lines:
2153                     if script_intro == li.body:
2154                         script_exists = True
2155                         continue
2156
2157         if not script_exists:
2158             amth_text.write("\n")
2159             amth_text.write(script_intro)
2160             amth_text.write(script)
2161
2162         if ob and ob.dupli_group:
2163             if ob.pass_index != 0:
2164                 for dob in ob.dupli_group.objects:
2165                     dob.pass_index = ob.pass_index
2166
2167         self.report({'INFO'},
2168             "%s ID: %s to all objects in this Dupli Group" % (
2169                 "Applied" if not script_exists else "Updated",
2170                 ob.pass_index))
2171
2172         return{'FINISHED'}
2173
2174 class AMTH_OBJECT_OT_id_dupligroup_clear(Operator):
2175     '''Clear the Object ID from objects in dupli group'''
2176     bl_idname = "object.amaranth_object_id_duplis_clear"
2177     bl_label = "Clear Object ID from Duplis"
2178
2179     @classmethod
2180     def poll(cls, context):
2181         return context.active_object.dupli_group
2182
2183     def execute(self, context):
2184         context.active_object.pass_index = 0
2185         AMTH_OBJECT_OT_id_dupligroup.clear = True
2186         amth_text_exists = amaranth_text_startup(context)
2187         match_first = "# OB ID: %s" % context.active_object.name
2188
2189         if amth_text_exists:
2190             for txt in bpy.data.texts:
2191                 if txt.name == amth_text.name:
2192                     for li in txt.lines:
2193                         if match_first in li.body:
2194                             li.body = ''
2195                             continue
2196
2197         self.report({'INFO'}, "Object IDs back to normal")
2198         return{'FINISHED'}
2199
2200 def ui_object_id_duplis(self, context):
2201
2202     if context.active_object.dupli_group:
2203         split = self.layout.split()
2204         row = split.row(align=True)
2205         row.enabled = context.active_object.pass_index != 0
2206         row.operator(
2207             AMTH_OBJECT_OT_id_dupligroup.bl_idname)
2208         row.operator(
2209             AMTH_OBJECT_OT_id_dupligroup_clear.bl_idname,
2210             icon="X", text="")
2211         split.separator()
2212
2213         if AMTH_OBJECT_OT_id_dupligroup.clear:
2214             self.layout.label(text="Next time you save/reload this file, "
2215                                         "object IDs will be back to normal",
2216                               icon="INFO")
2217
2218 # // FEATURE: Object ID for objects inside DupliGroups
2219 # UI: Warning about Z not connected when using EXR
2220 def ui_render_output_z(self, context):
2221
2222     scene = bpy.context.scene
2223     image = scene.render.image_settings
2224     if scene.render.use_compositing and \
2225         image.file_format == 'OPEN_EXR' and \
2226         image.use_zbuffer:
2227         if scene.node_tree and scene.node_tree.nodes:
2228             for no in scene.node_tree.nodes:
2229                 if no.type == 'COMPOSITE':
2230                     if not no.inputs['Z'].is_linked:
2231                         self.layout.label(
2232                             text="The Z output in node \"%s\" is not connected" % 
2233                                 no.name, icon="ERROR")
2234
2235 # // UI: Warning about Z not connected
2236
2237 # FEATURE: Delete Materials not assigned to any verts
2238 class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
2239     '''Remove materials not assigned to any vertex'''
2240     bl_idname = "object.amaranth_object_material_remove_unassigned"
2241     bl_label = "Remove Unassigned Materials"
2242
2243     @classmethod
2244     def poll(cls, context):
2245         return context.active_object.material_slots
2246
2247     def execute(self, context):
2248
2249         scene = context.scene
2250         act_ob = context.active_object
2251         count = len(act_ob.material_slots)
2252         materials_removed = []
2253         act_ob.active_material_index = 0
2254         is_visible = True
2255
2256         if act_ob not in context.visible_objects:
2257             is_visible = False
2258             n = -1
2259             for lay in act_ob.layers:
2260                 n += 1
2261                 if lay:
2262                     break
2263
2264             scene.layers[n] = True
2265
2266         for slot in act_ob.material_slots:
2267             count -= 1
2268
2269             bpy.ops.object.mode_set(mode='EDIT')
2270             bpy.ops.mesh.select_all(action='DESELECT')
2271             act_ob.active_material_index = count
2272             bpy.ops.object.material_slot_select()
2273             
2274             if act_ob.data.total_vert_sel == 0 or \
2275                 (len(act_ob.material_slots) == 1 and not \
2276                     act_ob.material_slots[0].material):
2277                 materials_removed.append(
2278                     "%s" % act_ob.active_material.name if act_ob.active_material else "Empty")
2279                 bpy.ops.object.mode_set(mode='OBJECT')
2280                 bpy.ops.object.material_slot_remove()
2281             else:
2282                 pass
2283
2284         bpy.ops.object.mode_set(mode='EDIT')
2285         bpy.ops.mesh.select_all(action='DESELECT')
2286         bpy.ops.object.mode_set(mode='OBJECT')
2287
2288         if materials_removed:
2289             print("\n* Removed %s Unassigned Materials \n" % len(materials_removed))
2290
2291             count_mr = 0
2292
2293             for mr in materials_removed:
2294                 count_mr += 1
2295                 print("%0.2d. %s" % (count_mr, materials_removed[count_mr - 1]))
2296
2297             print("\n")
2298             self.report({'INFO'}, "Removed %s Unassigned Materials" %
2299                 len(materials_removed))
2300
2301         if not is_visible:
2302             scene.layers[n] = False
2303
2304         return{'FINISHED'}
2305
2306 def ui_material_remove_unassigned(self, context):
2307
2308     self.layout.operator(
2309         AMTH_OBJECT_OT_material_remove_unassigned.bl_idname,
2310         icon="X")
2311
2312 # // FEATURE: Delete Materials not assigned to any verts
2313 # FEATURE: Cycles Samples Percentage
2314 class AMTH_RENDER_OT_cycles_samples_percentage_set(Operator):
2315     '''Save the current number of samples per shader as final (gets saved in .blend)'''
2316     bl_idname = "scene.amaranth_cycles_samples_percentage_set"
2317     bl_label = "Set as Render Samples"
2318
2319     def execute(self, context):
2320         cycles = context.scene.cycles
2321         cycles.use_samples_final = True
2322
2323         context.scene['amth_cycles_samples_final'] = [
2324             cycles.diffuse_samples,
2325             cycles.glossy_samples,
2326             cycles.transmission_samples,
2327             cycles.ao_samples,
2328             cycles.mesh_light_samples,
2329             cycles.subsurface_samples,
2330             cycles.volume_samples]
2331
2332         self.report({'INFO'}, "Render Samples Saved")
2333
2334         return{'FINISHED'}
2335
2336
2337 class AMTH_RENDER_OT_cycles_samples_percentage(Operator):
2338     '''Set a percentage of the final render samples'''
2339     bl_idname = "scene.amaranth_cycles_samples_percentage"
2340     bl_label = "Set Render Samples Percentage"
2341
2342     percent = IntProperty(
2343                 name="Percentage",
2344                 description="Percentage to divide render samples by",
2345                 subtype='PERCENTAGE',
2346                 default=0)
2347
2348     def execute(self, context):
2349         percent = self.percent
2350         cycles = context.scene.cycles
2351         cycles_samples_final = context.scene['amth_cycles_samples_final']
2352
2353         cycles.use_samples_final = False
2354
2355         if percent == 100:
2356             cycles.use_samples_final = True
2357
2358         cycles.diffuse_samples = int((cycles_samples_final[0] / 100) * percent)
2359         cycles.glossy_samples = int((cycles_samples_final[1] / 100) * percent)
2360         cycles.transmission_samples = int((cycles_samples_final[2] / 100) * percent)
2361         cycles.ao_samples = int((cycles_samples_final[3] / 100) * percent)
2362         cycles.mesh_light_samples = int((cycles_samples_final[4] / 100) * percent)
2363         cycles.subsurface_samples = int((cycles_samples_final[5] / 100) * percent)
2364         cycles.volume_samples = int((cycles_samples_final[6] / 100) * percent)
2365
2366         return{'FINISHED'}
2367
2368 # //FEATURE: Cycles Samples Percentage
2369 # FEATURE: Jump forward/backward every N frames
2370 class AMTH_SCREEN_OT_frame_jump(Operator):
2371     '''Jump a number of frames forward/backwards'''
2372     bl_idname = "screen.amaranth_frame_jump"
2373     bl_label = "Jump Frames"
2374
2375     forward = BoolProperty(default=True)
2376
2377     def execute(self, context):
2378         scene = context.scene
2379         preferences = context.user_preferences.addons[__name__].preferences
2380
2381         if preferences.use_framerate:
2382             framedelta = scene.render.fps
2383         else:
2384             framedelta = preferences.frames_jump
2385         if self.forward:
2386             scene.frame_current = scene.frame_current + framedelta
2387         else:
2388             scene.frame_current = scene.frame_current - framedelta
2389
2390         return{'FINISHED'}
2391
2392 def ui_userpreferences_edit(self, context):
2393     preferences = context.user_preferences.addons[__name__].preferences
2394
2395     col = self.layout.column()
2396     split = col.split(percentage=0.21)
2397     split.prop(preferences, "frames_jump",
2398                text="Frames to Jump")
2399
2400 # // FEATURE: Jump forward/backward every N frames
2401 # FEATURE: Set Layers to Render
2402 class AMTH_SCENE_OT_layers_render_save(Operator):
2403     '''Save the current scene layers as those that should be enabled for final renders'''
2404     bl_idname = "scene.amaranth_layers_render_save"
2405     bl_label = "Save as Layers for Render"
2406
2407     def execute(self, context):
2408         which = []
2409         n = -1
2410
2411         for l in context.scene.layers:
2412             n += 1
2413             if l:
2414                 which.append(n)
2415
2416         context.scene['amth_layers_for_render'] = which
2417         self.report({'INFO'}, "Layers for Render Saved")
2418
2419         return{'FINISHED'}
2420
2421 class AMTH_SCENE_OT_layers_render_view(Operator):
2422     '''Enable the scene layers that should be active for final renders'''
2423     bl_idname = "scene.amaranth_layers_render_view"
2424     bl_label = "View Layers for Render"
2425
2426     def execute(self, context):
2427         scene = context.scene
2428         layers_render = scene['amth_layers_for_render']
2429
2430         for window in bpy.context.window_manager.windows:
2431             screen = window.screen
2432
2433             for area in screen.areas:
2434                 if area.type == 'VIEW_3D':
2435                     override = {'window': window, 'screen': screen, 'scene': scene, 
2436                                 'area': area, 'region': area.regions[4],
2437                                 'blend_data': context.blend_data}
2438
2439                     if layers_render:
2440                         bpy.ops.view3d.layers(override, nr=layers_render[0]+1, extend=False, toggle=False)
2441
2442                         for n in layers_render:
2443                             context.scene.layers[n] = True
2444                     else:
2445                         bpy.ops.view3d.layers(override, nr=1, extend=False, toggle=False)
2446                         self.report({'INFO'}, "No layers set for render")
2447
2448                     break
2449
2450         return{'FINISHED'}
2451
2452 class AMTH_SCENE_OT_layers_render_set_individual(Operator):
2453     '''Whether this layer should be enabled or not for final renders'''
2454     bl_idname = "scene.amaranth_layers_render_set_individual"
2455     bl_label = "Set This Layer for Render"
2456
2457     toggle = BoolProperty()
2458     number = IntProperty()
2459
2460     def execute(self, context):
2461         toggle = self.toggle
2462         number = self.number
2463
2464         new_layers = []
2465
2466         for la in context.scene['amth_layers_for_render']:
2467             new_layers.append(la)
2468
2469         if len(context.scene['amth_layers_for_render']) and number in new_layers:
2470             new_layers.remove(number)
2471         else:
2472             new_layers.append(number)
2473
2474         # Remove Duplicates
2475         new_layers = list(set(new_layers))
2476         context.scene['amth_layers_for_render'] = new_layers
2477
2478         bpy.ops.scene.amaranth_layers_render_view()
2479
2480         return{'FINISHED'}
2481
2482 class AMTH_SCENE_OT_layers_render_clear(Operator):
2483     '''Clear layers for render'''
2484     bl_idname = "scene.amaranth_layers_render_clear"
2485     bl_label = "Clear Layers for Render"
2486
2487     def execute(self, context):
2488
2489         if context.scene.get('amth_layers_for_render'):
2490             context.scene['amth_layers_for_render'] = []
2491
2492         return{'FINISHED'}
2493
2494 def ui_layers_for_render(self, context):
2495
2496     preferences = context.user_preferences.addons[__name__].preferences
2497
2498     if preferences.use_layers_for_render:
2499         lfr_available = context.scene.get('amth_layers_for_render')
2500         if lfr_available:
2501             lfr = context.scene['amth_layers_for_render']
2502
2503         layout = self.layout
2504         layout.label("Layers for Rendering:")
2505         split = layout.split()
2506         col = split.column(align=True)
2507         row = col.row(align=True)
2508         row.operator(
2509             AMTH_SCENE_OT_layers_render_save.bl_idname,
2510             text="Replace Layers" if lfr_available else "Save Current Layers for Render",
2511             icon="FILE_REFRESH" if lfr_available else 'LAYER_USED')
2512
2513         if lfr_available:
2514             row.operator(
2515                 AMTH_SCENE_OT_layers_render_clear.bl_idname,
2516                 icon='X', text="")
2517             col = col.column(align=True)
2518             col.enabled = True if lfr_available else False
2519             col.operator(
2520                 AMTH_SCENE_OT_layers_render_view.bl_idname,
2521                 icon="RESTRICT_VIEW_OFF")
2522
2523             split = split.split()
2524             col = split.column(align=True)
2525             row = col.row(align=True)
2526
2527             for n in range(0,5):
2528                 row.operator(
2529                     AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
2530                     icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
2531             row = col.row(align=True)
2532             for n in range(10,15):
2533                 row.operator(
2534                     AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
2535                     icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
2536
2537             split = split.split()
2538             col = split.column(align=True)
2539             row = col.row(align=True)
2540
2541             for n in range(5,10):
2542                 row.operator(
2543                     AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
2544                     icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
2545             row = col.row(align=True)
2546             for n in range(15,20):
2547                 row.operator(
2548                     AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
2549                     icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
2550
2551 def ui_layers_for_render_header(self, context):
2552
2553     preferences = context.user_preferences.addons[__name__].preferences
2554
2555     if preferences.use_layers_for_render:
2556         if context.scene.get('amth_layers_for_render'):
2557             self.layout.operator(
2558                 AMTH_SCENE_OT_layers_render_view.bl_idname,
2559                 text="", icon="IMGDISPLAY")
2560
2561 # // FEATURE: Set Layers to Render
2562 # FEATURE: Lighters Corner
2563 class AMTH_LightersCorner(bpy.types.Panel):
2564     """The Lighters Panel"""
2565     bl_label = "Lighter's Corner"
2566     bl_idname = "AMTH_SCENE_PT_lighters_corner"
2567     bl_space_type = 'PROPERTIES'
2568     bl_region_type = 'WINDOW'
2569     bl_context = "scene"
2570
2571     @classmethod
2572     def poll(cls, context):
2573         any_lamps = False
2574         for ob in bpy.data.objects:
2575             if ob.type == 'LAMP' or cycles_is_emission(context, ob):
2576                 any_lamps = True
2577             else:
2578                 pass
2579         return any_lamps
2580
2581     def draw_header(self, context):
2582         layout = self.layout
2583         layout.label(text="", icon="LAMP_SUN")
2584
2585     def draw(self, context):
2586         layout = self.layout
2587         scene = context.scene
2588         objects =  bpy.data.objects
2589         ob_act = context.active_object
2590         lamps = bpy.data.lamps
2591         list_meshlights = scene.amaranth_lighterscorner_list_meshlights
2592         engine = scene.render.engine
2593
2594         if cycles_exists:
2595             layout.prop(scene, "amaranth_lighterscorner_list_meshlights")
2596
2597         box = layout.box()
2598         if lamps:
2599             if objects:
2600                 row = box.row(align=True)
2601                 split = row.split(percentage=0.45)
2602                 col = split.column()
2603
2604                 col.label(text="Name")
2605
2606                 if engine in ['CYCLES', 'BLENDER_RENDER']:
2607                     if engine == 'BLENDER_RENDER':
2608                         split = split.split(percentage=0.7)
2609                     else:
2610                         split = split.split(percentage=0.27)
2611                     col = split.column()
2612                     col.label(text="Samples")
2613
2614                 if cycles_exists and engine == 'CYCLES':
2615                     split = split.split(percentage=0.2)
2616                     col = split.column()
2617                     col.label(text="Size")
2618
2619                 split = split.split(percentage=1.0)
2620                 col = split.column()
2621                 col.label(text="%sRender Visibility" % 
2622                     'Rays /' if cycles_exists else '')
2623
2624                 for ob in objects:
2625                     is_lamp = ob.type == 'LAMP'
2626                     is_emission = True if cycles_is_emission(context, ob) and list_meshlights else False
2627
2628                     if ob and is_lamp or is_emission:
2629                         lamp = ob.data
2630                         if cycles_exists:
2631                             clamp = ob.data.cycles
2632                             visibility = ob.cycles_visibility
2633
2634                         row = box.row(align=True)
2635                         split = row.split(percentage=1.0)
2636                         col = split.column()
2637                         row = col.row(align=True)
2638                         col.active = ob == ob_act
2639                         row.label(icon="%s" % ('LAMP_%s' % ob.data.type if is_lamp else 'MESH_GRID'))
2640                         split = row.split(percentage=.45)
2641                         col = split.column()
2642                         row = col.row(align=True)
2643                         row.alignment = 'LEFT'
2644                         row.active = True
2645                         row.operator(AMTH_SCENE_OT_amaranth_object_select.bl_idname,
2646                                     text='%s %s%s' % (
2647                                         " [L] " if ob.library else "",
2648                                         ob.name,
2649                                         "" if ob.name in context.scene.objects else " [Not in Scene]"),
2650                                     emboss=False).object = ob.name
2651                         if ob.library:
2652                             row = col.row(align=True)
2653                             row.alignment = "LEFT"
2654                             row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
2655                                          text=ob.library.filepath,
2656                                          icon="LINK_BLEND",
2657                                          emboss=False).filepath=ob.library.filepath
2658
2659                         if cycles_exists and engine == 'CYCLES':
2660                             split = split.split(percentage=0.25)
2661                             col = split.column()
2662                             if is_lamp:
2663                                 if scene.cycles.progressive == 'BRANCHED_PATH':
2664                                     col.prop(clamp, "samples", text="")
2665                                 if scene.cycles.progressive == 'PATH':
2666                                    col.label(text="N/A")
2667                             else:
2668                               col.label(text="N/A")
2669
2670                         if engine == 'BLENDER_RENDER':
2671                             split = split.split(percentage=0.7)
2672                             col = split.column()
2673                             if is_lamp:
2674                                 if lamp.type == 'HEMI':
2675                                     col.label(text="Not Available")
2676                                 elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
2677                                     row = col.row(align=True)
2678                                     row.prop(lamp, "shadow_ray_samples_x", text="X")
2679                                     if lamp.shape == 'RECTANGLE':
2680                                         row.prop(lamp, "shadow_ray_samples_y", text="Y")
2681                                 elif lamp.shadow_method == 'RAY_SHADOW':
2682                                     col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
2683                                 elif lamp.shadow_method == 'BUFFER_SHADOW':
2684                                     col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
2685                                 else:
2686                                     col.label(text="No Shadow")
2687                             else:
2688                               col.label(text="N/A")
2689
2690                         if cycles_exists and engine == 'CYCLES':
2691                             split = split.split(percentage=0.2)
2692                             col = split.column()
2693                             if is_lamp:
2694                                 if lamp.type in ['POINT','SUN', 'SPOT']:
2695                                     col.label(text="%.2f" % lamp.shadow_soft_size)
2696                                 elif lamp.type == 'HEMI':
2697                                     col.label(text="N/A")
2698                                 elif lamp.type == 'AREA' and lamp.shape == 'RECTANGLE':
2699                                     col.label(text="%.2fx%.2f" % (lamp.size, lamp.size_y))
2700                                 else:
2701                                     col.label(text="%.2f" % lamp.size)
2702                             else:
2703                               col.label(text="N/A")
2704
2705                         split = split.split(percentage=1.0)
2706                         col = split.column()
2707                         row = col.row(align=True)
2708                         if cycles_exists:
2709                             row.prop(visibility, "camera", text="")
2710                             row.prop(visibility, "diffuse", text="")
2711                             row.prop(visibility, "glossy", text="")
2712                             row.prop(visibility, "shadow", text="")
2713                             row.separator()
2714                         row.prop(ob, "hide", text="", emboss=False)
2715                         row.prop(ob, "hide_render", text="", emboss=False)
2716         else:
2717             box.label(text="No Lamps", icon="LAMP_DATA")
2718
2719 # FEATURE: Jump to frame in-between next and previous keyframe
2720 class AMTH_SCREEN_OT_keyframe_jump_inbetween(Operator):
2721     '''Jump to half in-between keyframes'''
2722     bl_idname = "screen.amth_keyframe_jump_inbetween"
2723     bl_label = "Jump to Keyframe In-between"
2724
2725     backwards = BoolProperty()
2726
2727     def execute(self, context):
2728         back = self.backwards
2729
2730         scene = context.scene
2731         ob = bpy.context.object
2732         frame_start = scene.frame_start
2733         frame_end = scene.frame_end
2734
2735         if not context.scene.get('amth_keyframes_jump'):
2736             context.scene['amth_keyframes_jump'] = []
2737
2738         keyframes_list = context.scene['amth_keyframes_jump']
2739
2740         for f in range(frame_start, frame_end):
2741             if ob.is_keyframe(f):
2742                  keyframes_list = list(keyframes_list)
2743                  keyframes_list.append(f)
2744
2745         if keyframes_list:
2746             i = 0
2747             keyframes_list_half = []
2748
2749             for item in keyframes_list:
2750                 try:
2751                     keyframes_list_half.append(int((keyframes_list[i] + keyframes_list[i+1]) / 2))
2752                     i += 1
2753                 except:
2754                     pass
2755
2756             if len(keyframes_list_half) > 1:
2757                 if back:
2758                     if scene.frame_current == keyframes_list_half[::-1][-1] or \
2759                         scene.frame_current < keyframes_list_half[::-1][-1]:
2760                         self.report({'INFO'}, "No keyframes behind")
2761                     else:
2762                         for i in keyframes_list_half[::-1]:
2763                             if scene.frame_current > i:
2764                                 scene.frame_current = i
2765                                 break
2766                 else:
2767                     if scene.frame_current == keyframes_list_half[-1] or \
2768                         scene.frame_current > keyframes_list_half[-1]:
2769                         self.report({'INFO'}, "No keyframes ahead")
2770                     else:
2771                         for i in keyframes_list_half:
2772                             if scene.frame_current < i:
2773                                 scene.frame_current = i
2774                                 break
2775             else:
2776                 self.report({'INFO'}, "Object has only 1 keyframe")
2777         else:
2778             self.report({'INFO'}, "Object has no keyframes")
2779
2780         return{'FINISHED'}
2781 # // FEATURE: Jump to frame in-between next and previous keyframe
2782 # FEATURE: Toggle Wire Display
2783 class AMTH_OBJECT_OT_wire_toggle(Operator):
2784     '''Turn on/off wire display on mesh objects'''
2785     bl_idname = "object.amth_wire_toggle"
2786     bl_label = "Display Wireframe"
2787     bl_options = {'REGISTER', 'UNDO'}
2788
2789     clear = BoolProperty(
2790         default=False, name="Clear Wireframe",
2791         description="Clear Wireframe Display")
2792
2793     def execute(self, context):
2794
2795         scene = context.scene
2796         is_all_scenes = scene.amth_wire_toggle_scene_all
2797         is_selected = scene.amth_wire_toggle_is_selected
2798         is_all_edges = scene.amth_wire_toggle_edges_all
2799         is_optimal = scene.amth_wire_toggle_optimal
2800         clear = self.clear
2801
2802         if is_all_scenes:
2803             which = bpy.data.objects
2804         elif is_selected:
2805             if not context.selected_objects:
2806                 self.report({'INFO'}, "No selected objects")
2807             which = context.selected_objects
2808         else:
2809             which = scene.objects
2810
2811         if which:
2812             for ob in which:
2813                 if ob and ob.type in {
2814                     'MESH', 'EMPTY', 'CURVE',
2815                     'META','SURFACE','FONT'}:
2816
2817                     ob.show_wire = False if clear else True
2818                     ob.show_all_edges = is_all_edges
2819
2820                     for mo in ob.modifiers:
2821                         if mo and mo.type == 'SUBSURF':
2822                             mo.show_only_control_edges = is_optimal
2823
2824         return{'FINISHED'}
2825
2826 def ui_object_wire_toggle(self, context):
2827
2828     scene = context.scene
2829
2830     self.layout.separator()
2831     col = self.layout.column(align=True)
2832     row = col.row(align=True)
2833     row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
2834         icon='MOD_WIREFRAME').clear = False
2835     row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
2836         icon='X', text="").clear = True
2837     col.separator()
2838     row = col.row(align=True)
2839     row.prop(scene, "amth_wire_toggle_edges_all")
2840     row.prop(scene, "amth_wire_toggle_optimal")
2841     row = col.row(align=True)
2842     sub = row.row(align=True)
2843     sub.active = not scene.amth_wire_toggle_scene_all
2844     sub.prop(scene, "amth_wire_toggle_is_selected")
2845     sub = row.row(align=True)
2846     sub.active = not scene.amth_wire_toggle_is_selected
2847     sub.prop(scene, "amth_wire_toggle_scene_all")
2848
2849 # //FEATURE: Toggle Wire Display
2850 # FEATURE: Add Meshlight
2851 class AMTH_OBJECT_OT_meshlight_add(bpy.types.Operator):
2852     """Add a light emitting mesh"""
2853     bl_idname = "object.meshlight_add"
2854     bl_label = "Add Meshlight"
2855     bl_options = {'REGISTER', 'UNDO'}
2856
2857     single_sided = BoolProperty(
2858             name="Single Sided",
2859             default=True,
2860             description="Only emit light on one side",
2861             )
2862
2863     visible = BoolProperty(
2864             name="Visible on Camera",
2865             default=False,
2866             description="Show the meshlight on Cycles preview",
2867             )
2868
2869     size = FloatProperty(
2870             name="Size",
2871             description="Meshlight size",
2872             min=0.01, max=100.0,
2873             default=1.0,
2874             )
2875
2876     strength = FloatProperty(
2877             name="Strength",
2878             min=0.01, max=100000.0,
2879             default=1.5,
2880             step=0.25,
2881             )
2882
2883     temperature = FloatProperty(
2884             name="Temperature",
2885             min=800, max=12000.0,
2886             default=5500.0,
2887             step=800.0,
2888             )
2889
2890     rotation = FloatVectorProperty(
2891             name="Rotation",
2892             subtype='EULER',
2893             )
2894
2895     def execute(self, context):
2896         scene = context.scene
2897         exists = False
2898         number = 1
2899
2900         for obs in bpy.data.objects:
2901             if obs.name.startswith("light_meshlight"):
2902                 number +=1
2903
2904         meshlight_name = 'light_meshlight_%.2d' % number
2905
2906         bpy.ops.mesh.primitive_grid_add(
2907             x_subdivisions=4, y_subdivisions=4,
2908             rotation=self.rotation, radius=self.size)
2909
2910         bpy.context.object.name = meshlight_name
2911         meshlight = scene.objects[meshlight_name]
2912         meshlight.show_wire = True
2913         meshlight.show_all_edges = True
2914
2915         material = bpy.data.materials.get(meshlight_name)
2916
2917         if not material:
2918             material = bpy.data.materials.new(meshlight_name)
2919
2920         bpy.ops.object.material_slot_add()
2921         meshlight.active_material = material
2922
2923         material.use_nodes = True
2924         material.diffuse_color = (1, 0.5, 0)
2925         nodes = material.node_tree.nodes
2926         links = material.node_tree.links
2927
2928         # clear default nodes to start nice fresh
2929         for no in nodes:
2930             nodes.remove(no)
2931
2932         if self.single_sided:
2933             geometry = nodes.new(type="ShaderNodeNewGeometry")
2934
2935             transparency = nodes.new(type="ShaderNodeBsdfTransparent")
2936             transparency.inputs[0].default_value = (1,1,1,1)
2937             transparency.location = geometry.location
2938             transparency.location += Vector((0.0, -55.0))
2939
2940             emission = nodes.new(type="ShaderNodeEmission")
2941             emission.inputs['Strength'].default_value = self.strength
2942             emission.location = transparency.location
2943             emission.location += Vector((0.0, -80.0))
2944             emission.inputs[0].show_expanded = True # so it shows slider on properties editor
2945
2946             blackbody = nodes.new(type="ShaderNodeBlackbody")
2947             blackbody.inputs['Temperature'].default_value = self.temperature
2948             blackbody.location = emission.location
2949             blackbody.location += Vector((-180.0, 0.0))
2950
2951             mix = nodes.new(type="ShaderNodeMixShader")
2952             mix.location = geometry.location
2953             mix.location += Vector((180.0, 0.0))
2954             mix.inputs[2].show_expanded = True
2955
2956             output = nodes.new(type="ShaderNodeOutputMaterial")
2957             output.inputs[1].hide = True
2958             output.inputs[2].hide = True
2959             output.location = mix.location
2960             output.location += Vector((180.0, 0.0))
2961
2962             # Make links
2963             links.new(geometry.outputs['Backfacing'], mix.inputs['Fac'])
2964             links.new(transparency.outputs['BSDF'], mix.inputs[1])
2965             links.new(emission.outputs['Emission'], mix.inputs[2])
2966             links.new(blackbody.outputs['Color'], emission.inputs['Color'])
2967             links.new(mix.outputs['Shader'], output.inputs['Surface'])
2968
2969             for sockets in geometry.outputs:
2970                 sockets.hide = True
2971         else:
2972             emission = nodes.new(type="ShaderNodeEmission")
2973             emission.inputs['Strength'].default_value = self.strength
2974             emission.inputs[0].show_expanded = True
2975
2976             blackbody = nodes.new(type="ShaderNodeBlackbody")
2977             blackbody.inputs['Temperature'].default_value = self.temperature
2978             blackbody.location = emission.location
2979             blackbody.location += Vector((-180.0, 0.0))
2980
2981             output = nodes.new(type="ShaderNodeOutputMaterial")
2982             output.inputs[1].hide = True
2983             output.inputs[2].hide = True
2984             output.location = emission.location
2985             output.location += Vector((180.0, 0.0))
2986
2987             links.new(blackbody.outputs['Color'], emission.inputs['Color'])
2988             links.new(emission.outputs['Emission'], output.inputs['Surface'])
2989
2990         material.cycles.sample_as_light = True
2991         meshlight.cycles_visibility.shadow = False
2992         meshlight.cycles_visibility.camera = self.visible
2993
2994         return {'FINISHED'}
2995
2996 def ui_menu_lamps_add(self, context):
2997     if cycles_exists and context.scene.render.engine == 'CYCLES':
2998         self.layout.separator()
2999         self.layout.operator(
3000             AMTH_OBJECT_OT_meshlight_add.bl_idname,
3001             icon="LAMP_AREA", text="Meshlight")
3002
3003 # //FEATURE: Add Meshlight: Single Sided
3004
3005 classes = (AMTH_SCENE_MT_color_management_presets,
3006            AMTH_AddPresetColorManagement,
3007            AMTH_LightersCorner,
3008            AMTH_SCENE_PT_scene_debug,
3009            AMTH_SCENE_OT_refresh,
3010            AMTH_SCENE_OT_cycles_shader_list_nodes,
3011            AMTH_SCENE_OT_cycles_shader_list_nodes_clear,
3012            AMTH_SCENE_OT_amaranth_object_select,
3013            AMTH_SCENE_OT_list_missing_node_links,
3014            AMTH_SCENE_OT_list_missing_material_slots,
3015            AMTH_SCENE_OT_list_missing_material_slots_clear,
3016            AMTH_SCENE_OT_blender_instance_open,
3017            AMTH_SCENE_OT_layers_render_save,
3018            AMTH_SCENE_OT_layers_render_view,
3019            AMTH_SCENE_OT_layers_render_set_individual,
3020            AMTH_SCENE_OT_layers_render_clear,
3021            AMTH_WM_OT_save_reload,
3022            AMTH_MESH_OT_find_asymmetric,
3023            AMTH_MESH_OT_make_symmetric,
3024            AMTH_NODE_OT_AddTemplateVignette,
3025            AMTH_NODE_OT_AddTemplateVectorBlur,
3026            AMTH_NODE_MT_amaranth_templates,
3027            AMTH_FILE_OT_directory_current_blend,
3028            AMTH_FILE_OT_directory_go_to,
3029            AMTH_NODE_PT_indices,
3030            AMTH_NODE_PT_simplify,
3031            AMTH_NODE_OT_toggle_mute,
3032            AMTH_NODE_OT_show_active_node_image,
3033            AMTH_VIEW3D_OT_render_border_camera,
3034            AMTH_VIEW3D_OT_show_only_render,
3035            AMTH_OBJECT_OT_select_meshlights,
3036            AMTH_OBJECT_OT_id_dupligroup,
3037            AMTH_OBJECT_OT_id_dupligroup_clear,
3038            AMTH_OBJECT_OT_material_remove_unassigned,
3039            AMTH_OBJECT_OT_wire_toggle,
3040            AMTH_OBJECT_OT_meshlight_add,
3041            AMTH_POSE_OT_paths_clear_all,
3042            AMTH_POSE_OT_paths_frame_match,
3043            AMTH_RENDER_OT_cycles_samples_percentage,
3044            AMTH_RENDER_OT_cycles_samples_percentage_set,
3045            AMTH_FILE_PT_libraries,
3046            AMTH_SCREEN_OT_frame_jump,
3047            AMTH_SCREEN_OT_keyframe_jump_inbetween)
3048
3049 addon_keymaps = []
3050
3051 def register():
3052
3053     bpy.utils.register_class(AmaranthToolsetPreferences)
3054
3055     # UI: Register the panel
3056     init_properties()
3057     for c in classes:
3058         bpy.utils.register_class(c)
3059
3060     bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
3061     bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
3062     bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
3063     bpy.types.VIEW3D_MT_object_specials.append(button_frame_current)
3064     bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
3065     bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
3066     bpy.types.VIEW3D_HT_header.append(ui_layers_for_render_header)
3067
3068     bpy.types.INFO_MT_file.append(button_save_reload)
3069     bpy.types.INFO_HT_header.append(stats_scene)
3070
3071     bpy.types.TIME_HT_header.append(label_timeline_extra_info)
3072
3073     bpy.types.NODE_HT_header.append(node_templates_pulldown)
3074     bpy.types.NODE_HT_header.append(node_stats)
3075     bpy.types.NODE_HT_header.append(node_shader_extra)
3076     bpy.types.NODE_PT_active_node_properties.append(ui_node_normal_values)
3077
3078     if check_cycles_exists():
3079         bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
3080         bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
3081
3082     bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
3083
3084     bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
3085
3086     bpy.types.DATA_PT_display.append(pose_motion_paths_ui)
3087
3088     bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
3089     bpy.types.RENDER_PT_output.append(ui_render_output_z)
3090
3091     bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)
3092
3093     bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)
3094
3095     bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)
3096
3097     bpy.types.OBJECT_PT_relations.append(ui_object_id_duplis)
3098
3099     bpy.types.MATERIAL_MT_specials.append(ui_material_remove_unassigned)
3100
3101     bpy.types.USERPREF_PT_edit.append(ui_userpreferences_edit)
3102
3103     bpy.types.RENDERLAYER_PT_layers.append(ui_layers_for_render)
3104
3105     bpy.types.VIEW3D_PT_view3d_display.append(ui_object_wire_toggle)
3106
3107     bpy.types.INFO_MT_mesh_add.append(ui_menu_lamps_add)
3108
3109     bpy.app.handlers.render_pre.append(unsimplify_render_pre)
3110     bpy.app.handlers.render_post.append(unsimplify_render_post)
3111
3112     wm = bpy.context.window_manager
3113     kc = wm.keyconfigs.addon
3114     if kc:
3115         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
3116         km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'DOUBLE_CLICK')
3117
3118         km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
3119         kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS')
3120         kmi.properties.name = "AMTH_NODE_MT_amaranth_templates"
3121
3122         km = kc.keymaps.new(name='Window')
3123         kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
3124         kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
3125
3126         km = kc.keymaps.new(name='Frames')
3127         kmi = km.keymap_items.new('screen.amaranth_frame_jump', 'UP_ARROW', 'PRESS', shift=True)
3128         kmi.properties.forward = True
3129         kmi = km.keymap_items.new('screen.amaranth_frame_jump', 'DOWN_ARROW', 'PRESS', shift=True)
3130         kmi.properties.forward = False
3131
3132         km = kc.keymaps.new(name='Frames')
3133         kmi = km.keymap_items.new('screen.amth_keyframe_jump_inbetween', 'UP_ARROW', 'PRESS', shift=True, ctrl=True)
3134         kmi.properties.backwards = False
3135         kmi = km.keymap_items.new('screen.amth_keyframe_jump_inbetween', 'DOWN_ARROW', 'PRESS', shift=True, ctrl=True)
3136         kmi.properties.backwards = True
3137
3138         km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
3139         kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
3140
3141         km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
3142         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
3143         kmi.properties.data_path = 'area.type'
3144         kmi.properties.value = 'DOPESHEET_EDITOR'
3145
3146         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
3147         kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
3148         kmi.properties.data_path = 'area.type'
3149         kmi.properties.value = 'GRAPH_EDITOR'
3150
3151         km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
3152         kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
3153         kmi.properties.data_path = 'space_data.mode'
3154         kmi.properties.value_1 = 'ACTION'
3155         kmi.properties.value_2 = 'DOPESHEET'
3156
3157         addon_keymaps.append((km, kmi))
3158
3159 def unregister():
3160
3161     bpy.utils.unregister_class(AmaranthToolsetPreferences)
3162
3163     for c in classes:
3164         bpy.utils.unregister_class(c)
3165
3166     bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
3167     bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
3168     bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
3169     bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
3170     bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
3171     bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
3172     bpy.types.VIEW3D_HT_header.remove(ui_layers_for_render_header)
3173
3174     bpy.types.INFO_MT_file.remove(button_save_reload)
3175     bpy.types.INFO_HT_header.remove(stats_scene)
3176
3177     bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
3178
3179     bpy.types.NODE_HT_header.remove(node_templates_pulldown)
3180     bpy.types.NODE_HT_header.remove(node_stats)
3181     bpy.types.NODE_HT_header.remove(node_shader_extra)
3182     bpy.types.NODE_PT_active_node_properties.remove(ui_node_normal_values)
3183
3184     if check_cycles_exists():
3185         bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
3186         bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
3187
3188     bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
3189
3190     bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
3191
3192     bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)
3193
3194     bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
3195     bpy.types.RENDER_PT_output.remove(ui_render_output_z)
3196
3197     bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)
3198
3199     bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)
3200
3201     bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)
3202
3203     bpy.types.OBJECT_PT_relations.remove(ui_object_id_duplis)
3204
3205     bpy.types.MATERIAL_MT_specials.remove(ui_material_remove_unassigned)
3206
3207     bpy.types.USERPREF_PT_edit.remove(ui_userpreferences_edit)
3208
3209     bpy.types.RENDERLAYER_PT_layers.remove(ui_layers_for_render)
3210
3211     bpy.types.VIEW3D_PT_view3d_display.remove(ui_object_wire_toggle)
3212
3213     bpy.types.INFO_MT_mesh_add.remove(ui_menu_lamps_add)
3214
3215     bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
3216     bpy.app.handlers.render_post.remove(unsimplify_render_post)
3217
3218     for km, kmi in addon_keymaps:
3219         km.keymap_items.remove(kmi)
3220     addon_keymaps.clear()
3221     
3222     clear_properties()
3223
3224 if __name__ == "__main__":
3225     register()