-
Pablo Vazquez authored
- Cycles Sampling, display sampling values for each renderlayers as well. - Rename Missing Node Groups to Node Links, because it can now look for empty node images or images that doesnt exist as well. - Scene Debug: Empty Material Slots, print a list of all the objects that have a empty material slots (common when linked materials got lost) - List Missing Images now show a path if linked from a .blend, clicking on the path will open a new blender instance with that file (same for dupligroups path) Full changelog at: http://pablovazquez.org/amaranth
Pablo Vazquez authored- Cycles Sampling, display sampling values for each renderlayers as well. - Rename Missing Node Groups to Node Links, because it can now look for empty node images or images that doesnt exist as well. - Scene Debug: Empty Material Slots, print a list of all the objects that have a empty material slots (common when linked materials got lost) - List Missing Images now show a path if linked from a .blend, clicking on the path will open a new blender instance with that file (same for dupligroups path) Full changelog at: http://pablovazquez.org/amaranth
scene_amaranth_toolset.py 70.91 KiB
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Amaranth Toolset",
"author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin",
"version": (0, 8, 4),
"blender": (2, 70),
"location": "Everywhere!",
"description": "A collection of tools and settings to improve productivity",
"warning": "",
"wiki_url": "http://pablovazquez.org/amaranth",
"tracker_url": "",
"category": "Scene"}
import bpy
import bmesh
from bpy.types import Operator, AddonPreferences, Panel, Menu
from bpy.props import BoolProperty
from mathutils import Vector
from bpy.app.handlers import persistent
from bl_operators.presets import AddPresetBase
# Preferences
class AmaranthToolsetPreferences(AddonPreferences):
bl_idname = __name__
use_frame_current = BoolProperty(
name="Current Frame Slider",
description="Set the current frame from the Specials menu in the 3D View",
default=True,
)
use_file_save_reload = BoolProperty(
name="Save & Reload File",
description="File menu > Save & Reload, or Ctrl + Shift + W",
default=True,
)
use_scene_refresh = BoolProperty(
name="Refresh Scene",
description="Specials Menu [W], or hit F5",
default=True,
)
use_timeline_extra_info = BoolProperty(
name="Timeline Extra Info",
description="Timeline Header",
default=True,
)
use_image_node_display = BoolProperty(
name="Active Image Node in Editor",
description="Display active node image in image editor",
default=True,
)
use_scene_stats = BoolProperty(
name="Extra Scene Statistics",
description="Display extra scene statistics in Info editor's header",
default=True,
)
def draw(self, context):
layout = self.layout
layout.label(
text="Here you can enable or disable specific tools, "
"in case they interfere with others or are just plain annoying")
split = layout.split(percentage=0.25)
col = split.column()
sub = col.column(align=True)
sub.label(text="3D View", icon="VIEW3D")
sub.prop(self, "use_frame_current")
sub.prop(self, "use_scene_refresh")
sub.separator()
sub.label(text="General", icon="SCENE_DATA")
sub.prop(self, "use_file_save_reload")
sub.prop(self, "use_timeline_extra_info")
sub.prop(self, "use_scene_stats")
sub.separator()
sub.label(text="Nodes Editor", icon="NODETREE")
sub.prop(self, "use_image_node_display")
col = split.column()
sub = col.column(align=True)
sub.label(text="")
sub.label(
text="Set the current frame from the Specials menu in the 3D View [W]")
sub.label(
text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
sub.separator()
sub.label(text="") # General
sub.label(
text="Quickly save and reload the current file (no warning!). "
"File menu or Ctrl+Shift+W")
sub.label(
text="SMPTE Timecode and frames left/ahead on Timeline's header")
sub.label(
text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
sub.separator()
sub.label(text="") # Nodes
sub.label(
text="When selecting an Image node, display it on the Image editor "
"(if any)")
# Properties
def init_properties():
scene = bpy.types.Scene
node = bpy.types.Node
nodes_compo = bpy.types.CompositorNodeTree
scene.use_unsimplify_render = BoolProperty(
default=False,
name="Unsimplify Render",
description="Disable Simplify during render")
scene.simplify_status = BoolProperty(default=False)
node.use_matching_indices = BoolProperty(
default=True,
description="If disabled, display all available indices")
test_items = [
("ALL", "All Types", "", 0),
("BLUR", "Blur", "", 1),
("BOKEHBLUR", "Bokeh Blur", "", 2),
("VECBLUR", "Vector Blur", "", 3),
("DEFOCUS", "Defocus", "", 4),
("R_LAYERS", "Render Layer", "", 5)
]
nodes_compo.types = bpy.props.EnumProperty(
items=test_items, name = "Types")
nodes_compo.toggle_mute = BoolProperty(default=False)
node.status = BoolProperty(default=False)
# Scene Debug
# Cycles Node Types
cycles_shader_node_types = [
("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
("BSDF_GLOSSY", "Glossy BSDF", "", 1),
("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
("BSDF_REFRACTION", "Refraction BSDF", "", 3),
("BSDF_GLASS", "Glass BSDF", "", 4),
("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
("BSDF_VELVET", "Velvet BSDF", "", 7),
("BSDF_TOON", "Toon BSDF", "", 8),
("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
("EMISSION", "Emission", "", 10),
("BSDF_HAIR", "Hair BSDF", "", 11),
("BACKGROUND", "Background", "", 12),
("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
("HOLDOUT", "Holdout", "", 14),
]
scene.amaranth_cycles_node_types = bpy.props.EnumProperty(
items=cycles_shader_node_types, name = "Shader")
scene.amaranth_debug_scene_list_lamps = BoolProperty(
default=False,
name="Lamps List",
description="Display a list of all the lamps")
scene.amaranth_debug_scene_list_missing_images = BoolProperty(
default=False,
name="List Missing Images",
description="Display a list of all the missing images")
def clear_properties():
props = (
"use_unsimplify_render",
"simplify_status",
"use_matching_indices",
"use_simplify_nodes_vector",
"status"
)
wm = bpy.context.window_manager
for p in props:
if p in wm:
del wm[p]
# FEATURE: Refresh Scene!
class SCENE_OT_refresh(Operator):
"""Refresh the current scene"""
bl_idname = "scene.refresh"
bl_label = "Refresh!"
def execute(self, context):
preferences = context.user_preferences.addons[__name__].preferences
scene = context.scene
if preferences.use_scene_refresh:
# Changing the frame is usually the best way to go
scene.frame_current = scene.frame_current
self.report({"INFO"}, "Scene Refreshed!")
return {'FINISHED'}
def button_refresh(self, context):
preferences = context.user_preferences.addons[__name__].preferences
if preferences.use_scene_refresh:
self.layout.separator()
self.layout.operator(
SCENE_OT_refresh.bl_idname,
text="Refresh!",
icon='FILE_REFRESH')
# // FEATURE: Refresh Scene!
# FEATURE: Save & Reload
def save_reload(self, context, path):
if path:
bpy.ops.wm.save_mainfile()
self.report({'INFO'}, "Saved & Reloaded")
bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
else:
bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
class WM_OT_save_reload(Operator):
"""Save and Reload the current blend file"""
bl_idname = "wm.save_reload"
bl_label = "Save & Reload"
def execute(self, context):
path = bpy.data.filepath
save_reload(self, context, path)
return {'FINISHED'}
def button_save_reload(self, context):
preferences = context.user_preferences.addons[__name__].preferences
if preferences.use_file_save_reload:
self.layout.separator()
self.layout.operator(
WM_OT_save_reload.bl_idname,
text="Save & Reload",
icon='FILE_REFRESH')
# // FEATURE: Save & Reload
# FEATURE: Current Frame
def button_frame_current(self, context):
preferences = context.user_preferences.addons[__name__].preferences
scene = context.scene
if preferences.use_frame_current:
self.layout.separator()
self.layout.prop(
scene, "frame_current",
text="Set Current Frame")
# // FEATURE: Current Frame
# FEATURE: Timeline Time + Frames Left
def label_timeline_extra_info(self, context):
preferences = context.user_preferences.addons[__name__].preferences
layout = self.layout
scene = context.scene
if preferences.use_timeline_extra_info:
row = layout.row(align=True)
# Check for preview range
frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
bpy.utils.smpte_from_frame(frame_end - frame_start)))
if (scene.frame_current > frame_end):
row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
elif (scene.frame_current == frame_start):
row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
elif (scene.frame_current == frame_end):
row.label(text="%s End Frame" % scene.frame_current)
else:
row.label(text="%s Frames Left" % (frame_end - scene.frame_current))
# // FEATURE: Timeline Time + Frames Left
# FEATURE: Directory Current Blend
class FILE_OT_directory_current_blend(Operator):
"""Go to the directory of the currently open blend file"""
bl_idname = "file.directory_current_blend"
bl_label = "Current Blend's Folder"
def execute(self, context):
bpy.ops.file.select_bookmark(dir='//')
return {'FINISHED'}
def button_directory_current_blend(self, context):
if bpy.data.filepath:
self.layout.operator(
FILE_OT_directory_current_blend.bl_idname,
text="Current Blend's Folder",
icon='APPEND_BLEND')
# // FEATURE: Directory Current Blend
# FEATURE: Libraries panel on file browser
class FILE_PT_libraries(Panel):
bl_space_type = 'FILE_BROWSER'
bl_region_type = 'CHANNELS'
bl_label = "Libraries"
def draw(self, context):
layout = self.layout
libs = bpy.data.libraries
libslist = []
# Build the list of folders from libraries
import os
for lib in libs:
directory_name = os.path.dirname(lib.filepath)
libslist.append(directory_name)
# Remove duplicates and sort by name
libslist = set(libslist)
libslist = sorted(libslist)
# Draw the box with libs
row = layout.row()
box = row.box()
if libslist:
for filepath in libslist:
if filepath != '//':
row = box.row()
row.alignment = 'LEFT'
props = row.operator(
FILE_OT_directory_go_to.bl_idname,
text=filepath, icon="BOOKMARKS",
emboss=False)
props.filepath = filepath
else:
box.label(text='No libraries loaded')
class FILE_OT_directory_go_to(Operator):
"""Go to this library's directory"""
bl_idname = "file.directory_go_to"
bl_label = "Go To"
filepath = bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
bpy.ops.file.select_bookmark(dir=self.filepath)
return {'FINISHED'}
# FEATURE: Node Templates
class NODE_OT_AddTemplateVignette(Operator):
bl_idname = "node.template_add_vignette"
bl_label = "Add Vignette"
bl_description = "Add a vignette effect"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR' \
and space.node_tree is not None \
and space.tree_type == 'CompositorNodeTree'
# used as reference the setup scene script from master nazgul
def _setupNodes(self, context):
scene = context.scene
space = context.space_data
tree = scene.node_tree
bpy.ops.node.select_all(action='DESELECT')
ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
ellipse.width = 0.8
ellipse.height = 0.4
blur = tree.nodes.new(type='CompositorNodeBlur')
blur.use_relative = True
blur.factor_x = 30
blur.factor_y = 50
ramp = tree.nodes.new(type='CompositorNodeValToRGB')
ramp.color_ramp.interpolation = 'B_SPLINE'
ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
overlay = tree.nodes.new(type='CompositorNodeMixRGB')
overlay.blend_type = 'OVERLAY'
overlay.inputs[0].default_value = 0.8
overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
tree.links.new(blur.outputs["Image"],ramp.inputs[0])
tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
if tree.nodes.active:
blur.location = tree.nodes.active.location
blur.location += Vector((330.0, -250.0))
else:
blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
ellipse.location = blur.location
ellipse.location += Vector((-300.0, 0))
ramp.location = blur.location
ramp.location += Vector((175.0, 0))
overlay.location = ramp.location
overlay.location += Vector((240.0, 275.0))
for node in {ellipse, blur, ramp, overlay}:
node.select = True
node.show_preview = False
bpy.ops.node.join()
frame = ellipse.parent
frame.label = 'Vignette'
frame.use_custom_color = True
frame.color = (0.783538, 0.0241576, 0.0802198)
overlay.parent = None
overlay.label = 'Vignette Overlay'
def execute(self, context):
self._setupNodes(context)
return {'FINISHED'}
# Node Templates Menu
class NODE_MT_amaranth_templates(Menu):
bl_idname = 'NODE_MT_amaranth_templates'
bl_space_type = 'NODE_EDITOR'
bl_label = "Templates"
bl_description = "List of Amaranth Templates"
def draw(self, context):
layout = self.layout
layout.operator(
NODE_OT_AddTemplateVignette.bl_idname,
text="Vignette",
icon='COLOR')
def node_templates_pulldown(self, context):
if context.space_data.tree_type == 'CompositorNodeTree':
layout = self.layout
row = layout.row(align=True)
row.scale_x = 1.3
row.menu("NODE_MT_amaranth_templates",
icon="RADIO")
# // FEATURE: Node Templates
def node_stats(self,context):
if context.scene.node_tree:
tree_type = context.space_data.tree_type
nodes = context.scene.node_tree.nodes
nodes_total = len(nodes.keys())
nodes_selected = 0
for n in nodes:
if n.select:
nodes_selected = nodes_selected + 1
if tree_type == 'CompositorNodeTree':
layout = self.layout
row = layout.row(align=True)
row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
# FEATURE: Simplify Compo Nodes
class NODE_PT_simplify(Panel):
'''Simplify Compositor Panel'''
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_label = 'Simplify'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR' \
and space.node_tree is not None \
and space.tree_type == 'CompositorNodeTree'
def draw(self, context):
layout = self.layout
node_tree = context.scene.node_tree
if node_tree is not None:
layout.prop(node_tree, 'types')
layout.operator(NODE_OT_toggle_mute.bl_idname,
text="Turn On" if node_tree.toggle_mute else "Turn Off",
icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
if node_tree.types == 'VECBLUR':
layout.label(text="This will also toggle the Vector pass {}".format(
"on" if node_tree.toggle_mute else "off"), icon="INFO")
class NODE_OT_toggle_mute(Operator):
""""""
bl_idname = "node.toggle_mute"
bl_label = "Toggle Mute"
def execute(self, context):
scene = context.scene
node_tree = scene.node_tree
node_type = node_tree.types
rlayers = scene.render
if not 'amaranth_pass_vector' in scene.keys():
scene['amaranth_pass_vector'] = []
#can't extend() the list, so make a dummy one
pass_vector = scene['amaranth_pass_vector']
if not pass_vector:
pass_vector = []
if node_tree.toggle_mute:
for node in node_tree.nodes:
if node_type == 'ALL':
node.mute = node.status
if node.type == node_type:
node.mute = node.status
if node_type == 'VECBLUR':
for layer in rlayers.layers:
if layer.name in pass_vector:
layer.use_pass_vector = True
pass_vector.remove(layer.name)
node_tree.toggle_mute = False
else:
for node in node_tree.nodes:
if node_type == 'ALL':
node.mute = True
if node.type == node_type:
node.status = node.mute
node.mute = True
if node_type == 'VECBLUR':
for layer in rlayers.layers:
if layer.use_pass_vector:
pass_vector.append(layer.name)
layer.use_pass_vector = False
pass
node_tree.toggle_mute = True
# Write back to the custom prop
pass_vector = sorted(set(pass_vector))
scene['amaranth_pass_vector'] = pass_vector
return {'FINISHED'}
# FEATURE: OB/MA ID panel in Node Editor
class NODE_PT_indices(Panel):
'''Object / Material Indices Panel'''
bl_space_type = 'NODE_EDITOR'
bl_region_type = 'UI'
bl_label = 'Object / Material Indices'
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
node = context.active_node
return node and node.type == 'ID_MASK'
def draw(self, context):
layout = self.layout
objects = bpy.data.objects
materials = bpy.data.materials
node = context.active_node
show_ob_id = False
show_ma_id = False
matching_ids = False
if context.active_object:
ob_act = context.active_object
else:
ob_act = False
for ob in objects:
if ob and ob.pass_index > 0:
show_ob_id = True
for ma in materials:
if ma and ma.pass_index > 0:
show_ma_id = True
row = layout.row(align=True)
row.prop(node, 'index', text="Mask Index")
row.prop(node, 'use_matching_indices', text="Only Matching IDs")
layout.separator()
if not show_ob_id and not show_ma_id:
layout.label(text="No objects or materials indices so far.", icon="INFO")
if show_ob_id:
split = layout.split()
col = split.column()
col.label(text="Object Name")
split.label(text="ID Number")
row = layout.row()
for ob in objects:
icon = "OUTLINER_DATA_" + ob.type
if ob.library:
icon = "LIBRARY_DATA_DIRECT"
elif ob.is_library_indirect:
icon = "LIBRARY_DATA_INDIRECT"
if ob and node.use_matching_indices \
and ob.pass_index == node.index \
and ob.pass_index != 0:
matching_ids = True
row.label(
text="[{}]".format(ob.name)
if ob_act and ob.name == ob_act.name else ob.name,
icon=icon)
row.label(text="%s" % ob.pass_index)
row = layout.row()
elif ob and not node.use_matching_indices \
and ob.pass_index > 0:
matching_ids = True
row.label(
text="[{}]".format(ob.name)
if ob_act and ob.name == ob_act.name else ob.name,
icon=icon)
row.label(text="%s" % ob.pass_index)
row = layout.row()
if node.use_matching_indices and not matching_ids:
row.label(text="No objects with ID %s" % node.index, icon="INFO")
layout.separator()
if show_ma_id:
split = layout.split()
col = split.column()
col.label(text="Material Name")
split.label(text="ID Number")
row = layout.row()
for ma in materials:
icon = "BLANK1"
if ma.use_nodes:
icon = "NODETREE"
elif ma.library:
icon = "LIBRARY_DATA_DIRECT"
if ma.is_library_indirect:
icon = "LIBRARY_DATA_INDIRECT"
if ma and node.use_matching_indices \
and ma.pass_index == node.index \
and ma.pass_index != 0:
matching_ids = True
row.label(text="%s" % ma.name, icon=icon)
row.label(text="%s" % ma.pass_index)
row = layout.row()
elif ma and not node.use_matching_indices \
and ma.pass_index > 0:
matching_ids = True
row.label(text="%s" % ma.name, icon=icon)
row.label(text="%s" % ma.pass_index)
row = layout.row()
if node.use_matching_indices and not matching_ids:
row.label(text="No materials with ID %s" % node.index, icon="INFO")
# // FEATURE: OB/MA ID panel in Node Editor
# FEATURE: Unsimplify on render
@persistent
def unsimplify_render_pre(scene):
render = scene.render
scene.simplify_status = render.use_simplify
if scene.use_unsimplify_render:
render.use_simplify = False
@persistent
def unsimplify_render_post(scene):
render = scene.render
render.use_simplify = scene.simplify_status
def unsimplify_ui(self,context):
scene = bpy.context.scene
self.layout.prop(scene, 'use_unsimplify_render')
# //FEATURE: Unsimplify on render
# FEATURE: Extra Info Stats
def stats_scene(self, context):
preferences = context.user_preferences.addons[__name__].preferences
if preferences.use_scene_stats:
scenes_count = str(len(bpy.data.scenes))
cameras_count = str(len(bpy.data.cameras))
cameras_selected = 0
meshlights = 0
meshlights_visible = 0
for ob in context.scene.objects:
if ob.material_slots:
for ma in ob.material_slots:
if ma.material:
if ma.material.node_tree:
for no in ma.material.node_tree.nodes:
if no.type == 'EMISSION':
for ou in no.outputs:
if ou.links:
meshlights = meshlights + 1
if ob in context.visible_objects:
meshlights_visible = meshlights_visible + 1
break
if ob in context.selected_objects:
if ob.type == 'CAMERA':
cameras_selected = cameras_selected + 1
meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
row = self.layout.row(align=True)
row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
scenes_count, cameras_selected, cameras_count,
meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
# //FEATURE: Extra Info Stats
# FEATURE: Camera Bounds as Render Border
class VIEW3D_OT_render_border_camera(Operator):
"""Set camera bounds as render border"""
bl_idname = "view3d.render_border_camera"
bl_label = "Camera as Render Border"
@classmethod
def poll(cls, context):
return context.space_data.region_3d.view_perspective == 'CAMERA'
def execute(self, context):
render = context.scene.render
render.use_border = True
render.border_min_x = 0
render.border_min_y = 0
render.border_max_x = 1
render.border_max_y = 1
return {'FINISHED'}
def button_render_border_camera(self, context):
view3d = context.space_data.region_3d
if view3d.view_perspective == 'CAMERA':
layout = self.layout
layout.separator()
layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
text="Camera as Render Border", icon="FULLSCREEN_ENTER")
# //FEATURE: Camera Bounds as Render Border
# FEATURE: Passepartout options on W menu
def button_camera_passepartout(self, context):
view3d = context.space_data.region_3d
cam = context.scene.camera.data
if view3d.view_perspective == 'CAMERA':
layout = self.layout
if cam.show_passepartout:
layout.prop(cam, "passepartout_alpha", text="Passepartout")
else:
layout.prop(cam, "show_passepartout")
# FEATURE: Show Only Render with Alt+Shift+Z
class VIEW3D_OT_show_only_render(Operator):
bl_idname = "view3d.show_only_render"
bl_label = "Show Only Render"
def execute(self, context):
space = bpy.context.space_data
if space.show_only_render:
space.show_only_render = False
else:
space.show_only_render = True
return {'FINISHED'}
# FEATURE: Display Active Image Node on Image Editor
# Made by Sergey Sharybin, tweaks from Bassam Kurdali
image_nodes = {"CompositorNodeImage",
"ShaderNodeTexImage",
"ShaderNodeTexEnvironment"}
class NODE_OT_show_active_node_image(Operator):
"""Show active image node image in the image editor"""
bl_idname = "node.show_active_node_image"
bl_label = "Show Active Node Node"
bl_options = {'UNDO'}
def execute(self, context):
preferences = context.user_preferences.addons[__name__].preferences
if preferences.use_image_node_display:
if context.active_node:
active_node = context.active_node
if active_node.bl_idname in image_nodes and active_node.image:
for area in context.screen.areas:
if area.type == "IMAGE_EDITOR":
for space in area.spaces:
if space.type == "IMAGE_EDITOR":
space.image = active_node.image
break
return {'FINISHED'}
# // FEATURE: Display Active Image Node on Image Editor
# FEATURE: Select Meshlights
class OBJECT_OT_select_meshlights(Operator):
"""Select light emitting meshes"""
bl_idname = "object.select_meshlights"
bl_label = "Select Meshlights"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.scene.render.engine == 'CYCLES'
def execute(self, context):
# Deselect everything first
bpy.ops.object.select_all(action='DESELECT')
for ob in context.scene.objects:
if ob.material_slots:
for ma in ob.material_slots:
if ma.material:
if ma.material.node_tree:
for no in ma.material.node_tree.nodes:
if no.type == 'EMISSION':
ob.select = True
context.scene.objects.active = ob
if not context.selected_objects and not context.scene.objects.active:
self.report({'INFO'}, "No meshlights to select")
return {'FINISHED'}
def button_select_meshlights(self, context):
if context.scene.render.engine == 'CYCLES':
self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
# // FEATURE: Select Meshlights
# FEATURE: Mesh Symmetry Tools by Sergey Sharybin
class MESH_OT_find_asymmetric(Operator):
"""
Find asymmetric vertices
"""
bl_idname = "mesh.find_asymmetric"
bl_label = "Find Asymmetric"
bl_options = {'UNDO', 'REGISTER'}
@classmethod
def poll(cls, context):
object = context.object
if object:
return object.mode == 'EDIT' and object.type == 'MESH'
return False
def execute(self, context):
threshold = 1e-6
object = context.object
bm = bmesh.from_edit_mesh(object.data)
# Deselect all the vertices
for v in bm.verts:
v.select = False
for v1 in bm.verts:
if abs(v1.co[0]) < threshold:
continue
mirror_found = False
for v2 in bm.verts:
if v1 == v2:
continue
if v1.co[0] * v2.co[0] > 0.0:
continue
mirror_coord = Vector(v2.co)
mirror_coord[0] *= -1
if (mirror_coord - v1.co).length_squared < threshold:
mirror_found = True
break
if not mirror_found:
v1.select = True
bm.select_flush_mode()
bmesh.update_edit_mesh(object.data)
return {'FINISHED'}
class MESH_OT_make_symmetric(Operator):
"""
Make symmetric
"""
bl_idname = "mesh.make_symmetric"
bl_label = "Make Symmetric"
bl_options = {'UNDO', 'REGISTER'}
@classmethod
def poll(cls, context):
object = context.object
if object:
return object.mode == 'EDIT' and object.type == 'MESH'
return False
def execute(self, context):
threshold = 1e-6
object = context.object
bm = bmesh.from_edit_mesh(object.data)
for v1 in bm.verts:
if v1.co[0] < threshold:
continue
if not v1.select:
continue
closest_vert = None
closest_distance = -1
for v2 in bm.verts:
if v1 == v2:
continue
if v2.co[0] > threshold:
continue
if not v2.select:
continue
mirror_coord = Vector(v2.co)
mirror_coord[0] *= -1
distance = (mirror_coord - v1.co).length_squared
if closest_vert is None or distance < closest_distance:
closest_distance = distance
closest_vert = v2
if closest_vert:
closest_vert.select = False
closest_vert.co = Vector(v1.co)
closest_vert.co[0] *= -1
v1.select = False
for v1 in bm.verts:
if v1.select:
closest_vert = None
closest_distance = -1
for v2 in bm.verts:
if v1 != v2:
mirror_coord = Vector(v2.co)
mirror_coord[0] *= -1
distance = (mirror_coord - v1.co).length_squared
if closest_vert is None or distance < closest_distance:
closest_distance = distance
closest_vert = v2
if closest_vert:
v1.select = False
v1.co = Vector(closest_vert.co)
v1.co[0] *= -1
bm.select_flush_mode()
bmesh.update_edit_mesh(object.data)
return {'FINISHED'}
# // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
# FEATURE: Cycles Render Sampling Extra
def render_cycles_scene_samples(self, context):
layout = self.layout
scenes = bpy.data.scenes
scene = context.scene
cscene = scene.cycles
col = layout.column(align=True)
if len(scene.render.layers) == 1 and \
scene.render.layers[0].samples == 0:
pass
else:
col.separator()
col.label(text="Samples Per RenderLayer:")
for rl in scene.render.layers:
row = col.row(align=True)
row.label(rl.name)
row.prop(rl, "samples", text="%s" %
"Samples" if rl.samples > 0 else "Samples [Auto]")
if (len(bpy.data.scenes) > 1):
col.separator()
col.label(text="Samples Per Scene:")
row = col.row(align=True)
if cscene.progressive == 'PATH':
for s in bpy.data.scenes:
if s != scene:
row = col.row(align=True)
if s.render.engine == 'CYCLES':
cscene = s.cycles
row.label(s.name)
row.prop(cscene, "samples")
else:
row.label(text="Scene: '%s' is not using Cycles" % s.name)
else:
for s in bpy.data.scenes:
if s != scene:
row = col.row(align=True)
if s.render.engine == 'CYCLES':
cscene = s.cycles
row.label(s.name)
row.prop(cscene, "aa_samples",
text="AA Samples")
else:
row.label(text="Scene: '%s' is not using Cycles" % s.name)
# // FEATURE: Dupli Group Path
# // FEATURE: Cycles Render Sampling Extra
# FEATURE: Motion Paths Extras
class POSE_OT_paths_clear_all(Operator):
"""Clear motion paths from all bones"""
bl_idname = "pose.paths_clear_all"
bl_label = "Clear All Motion Paths"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.mode == 'POSE'
def execute(self, context):
#silly but works
for b in context.object.data.bones:
b.select = True
bpy.ops.pose.paths_clear()
b.select = False
return {'FINISHED'}
class POSE_OT_paths_frame_match(Operator):
"""Match Start/End frame of scene to motion path range"""
bl_idname = "pose.paths_frame_match"
bl_label = "Match Frame Range"
bl_options = {'UNDO'}
def execute(self, context):
avs = context.object.pose.animation_visualization
scene = context.scene
if avs.motion_path.type == 'RANGE':
if scene.use_preview_range:
avs.motion_path.frame_start = scene.frame_preview_start
avs.motion_path.frame_end = scene.frame_preview_end
else:
avs.motion_path.frame_start = scene.frame_start
avs.motion_path.frame_end = scene.frame_end
else:
if scene.use_preview_range:
avs.motion_path.frame_before = scene.frame_preview_start
avs.motion_path.frame_after = scene.frame_preview_end
else:
avs.motion_path.frame_before = scene.frame_start
avs.motion_path.frame_after = scene.frame_end
return {'FINISHED'}
def pose_motion_paths_ui(self, context):
layout = self.layout
scene = context.scene
avs = context.object.pose.animation_visualization
if context.active_pose_bone:
mpath = context.active_pose_bone.motion_path
layout.separator()
layout.label(text="Motion Paths Extras:")
split = layout.split()
col = split.column(align=True)
if context.selected_pose_bones:
if mpath:
sub = col.row(align=True)
sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
sub.operator("pose.paths_clear", text="", icon='X')
else:
col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
else:
col.label(text="Select Bones First", icon="ERROR")
col = split.column(align=True)
col.operator(POSE_OT_paths_frame_match.bl_idname,
text="{}".format( "Set Preview Frame Range"
if scene.use_preview_range else "Set Frame Range"),
icon="{}".format("PREVIEW_RANGE"
if scene.use_preview_range else "TIME"))
col = layout.column()
row = col.row(align=True)
if avs.motion_path.type == 'RANGE':
row.prop(avs.motion_path, "frame_start", text="Start")
row.prop(avs.motion_path, "frame_end", text="End")
else:
row.prop(avs.motion_path, "frame_before", text="Before")
row.prop(avs.motion_path, "frame_after", text="After")
layout.separator()
layout.operator(POSE_OT_paths_clear_all.bl_idname, icon="X")
# // FEATURE: Motion Paths Extras
# FEATURE: Final Render Resolution Display
def render_final_resolution_ui(self, context):
rd = context.scene.render
layout = self.layout
final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
if rd.use_border:
final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
str(final_res_x)[:-2], str(final_res_y)[:-2],
str(final_res_x_border), str(final_res_y_border)))
else:
layout.label(text="Final Resolution: {} x {}".format(
str(final_res_x)[:-2], str(final_res_y)[:-2]))
# // FEATURE: Final Render Resolution Display
# FEATURE: Shader Nodes Extra Info
def node_shader_extra(self, context):
if context.space_data.tree_type == 'ShaderNodeTree':
ob = context.active_object
snode = context.space_data
layout = self.layout
if ob and snode.shader_type != 'WORLD':
if ob.type == 'LAMP':
layout.label(text="%s" % ob.name,
icon="LAMP_%s" % ob.data.type)
else:
layout.label(text="%s" % ob.name,
icon="OUTLINER_DATA_%s" % ob.type)
# // FEATURE: Shader Nodes Extra Info
# FEATURE: Scene Debug
class SCENE_OT_cycles_shader_list_nodes(Operator):
"""List Cycles materials containing a specific shader"""
bl_idname = "scene.cycles_list_nodes"
bl_label = "List Materials"
materials = []
@classmethod
def poll(cls, context):
return context.scene.render.engine == 'CYCLES'
def execute(self, context):
node_type = context.scene.amaranth_cycles_node_types
roughness = False
self.__class__.materials = []
print("\n=== Cycles Shader Type: %s === \n" % node_type)
for ma in bpy.data.materials:
if ma.node_tree:
nodes = ma.node_tree.nodes
for no in nodes:
if no.type == node_type:
for ou in no.outputs:
if ou.links:
if no.type in ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']:
roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
else:
roughness = False
else:
print('Note: \nOutput from "%s" node' % node_type,
'in material "%s"' % ma.name,
'not connected',
'\n')
self.__class__.materials = sorted(list(set(self.__class__.materials)))
if ma.name not in self.__class__.materials:
self.__class__.materials.append('%s%s [%s] %s%s' % (
'[L] ' if ma.library else '',
ma.name, ma.users,
'[F]' if ma.use_fake_user else '',
' - [%s]' % roughness if roughness else ''))
if len(self.__class__.materials) == 0:
self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
else:
print("* A total of %d %s using %s were found \n" % (
len(self.__class__.materials),
"material" if len(self.__class__.materials) == 1 else "materials",
node_type))
count = 0
for mat in self.__class__.materials:
print('%02d. %s' % (count+1, self.__class__.materials[count]))
count += 1
print("\n")
self.__class__.materials = sorted(list(set(self.__class__.materials)))
return {'FINISHED'}
class SCENE_OT_cycles_shader_list_nodes_clear(Operator):
"""Clear the list below"""
bl_idname = "scene.cycles_list_nodes_clear"
bl_label = "Clear Materials List"
def execute(self, context):
SCENE_OT_cycles_shader_list_nodes.materials[:] = []
print("* Cleared Cycles Materials List")
return {'FINISHED'}
class SCENE_OT_amaranth_debug_lamp_select(Operator):
'''Select Lamp'''
bl_idname = "scene.amaranth_debug_lamp_select"
bl_label = "Select Lamp"
lamp = bpy.props.StringProperty()
def execute(self, context):
if self.lamp:
lamp = bpy.data.objects[self.lamp]
bpy.ops.object.select_all(action='DESELECT')
lamp.select = True
context.scene.objects.active = lamp
return{'FINISHED'}
class SCENE_OT_list_missing_node_links(Operator):
'''Print a list of missing node links'''
bl_idname = "scene.list_missing_node_links"
bl_label = "List Missing Node Links"
count_groups = 0
count_images = 0
def execute(self, context):
missing_groups = []
missing_images = []
libraries = []
self.__class__.count_groups = 0
self.__class__.count_images = 0
for ma in bpy.data.materials:
if ma.node_tree:
for no in ma.node_tree.nodes:
if no.type == 'GROUP':
if not no.node_tree:
self.__class__.count_groups += 1
users_ngroup = []
for ob in bpy.data.objects:
if ob.material_slots and ma.name in ob.material_slots:
users_ngroup.append("%s%s%s" % (
"[L] " if ob.library else "",
"[F] " if ob.use_fake_user else "",
ob.name))
missing_groups.append("NG: %s%s%s [%s]%s%s\n" % (
"[L] " if ma.library else "",
"[F] " if ma.use_fake_user else "",
ma.name, ma.users,
"\nLI: %s" %
ma.library.filepath if ma.library else "",
"\nOB: %s" % ', '.join(users_ngroup) if users_ngroup else ""))
if ma.library:
libraries.append(ma.library.filepath)
if no.type == 'TEX_IMAGE':
if no.image:
import os.path
image_path_exists = os.path.exists(
bpy.path.abspath(
no.image.filepath, library=no.image.library))
if not no.image or not image_path_exists:
self.__class__.count_images += 1
users_images = []
for ob in bpy.data.objects:
if ob.material_slots and ma.name in ob.material_slots:
users_images.append("%s%s%s" % (
"[L] " if ob.library else "",
"[F] " if ob.use_fake_user else "",
ob.name))
missing_images.append("MA: %s%s%s [%s]%s%s%s%s\n" % (
"[L] " if ma.library else "",
"[F] " if ma.use_fake_user else "",
ma.name, ma.users,
"\nLI: %s" %
ma.library.filepath if ma.library else "",
"\nIM: %s" % no.image.name if no.image else "",
"\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
"\nOB: %s" % ', '.join(users_images) if users_images else ""))
if ma.library:
libraries.append(ma.library.filepath)
# Remove duplicates and sort
missing_groups = sorted(list(set(missing_groups)))
missing_images = sorted(list(set(missing_images)))
libraries = sorted(list(set(libraries)))
print("\n\n== %s missing image %s and %s missing node %s ==" %
("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
"node" if self.__class__.count_images == 1 else "nodes",
"no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
"group" if self.__class__.count_groups == 1 else "groups"))
# List Missing Node Groups
if missing_groups:
print("\n* Missing Node Group Links [NG]\n")
for mig in missing_groups:
print(mig)
# List Missing Image Nodes
if missing_images:
print("\n* Missing Image Nodes Link [IM]\n")
for mii in missing_images:
print(mii)
if missing_groups or missing_images:
if libraries:
print("\nThat's bad, run check on %s:" % (
"this library" if len(libraries) == 1 else "these libraries"))
for li in libraries:
print(li)
else:
self.report({"INFO"}, "Yay! No missing node links")
print("\n")
if missing_groups and missing_images:
self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
(self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))
return{'FINISHED'}
class SCENE_OT_list_missing_material_slots(Operator):
'''List objects with empty material slots'''
bl_idname = "scene.list_missing_material_slots"
bl_label = "List Empty Material Slots"
objects = []
def execute(self, context):
self.__class__.objects = []
for ob in bpy.data.objects:
for ma in ob.material_slots:
if not ma.material:
self.__class__.objects.append('%s%s' % (
'[L] ' if ob.library else '',
ob.name))
self.__class__.objects = sorted(list(set(self.__class__.objects)))
if len(self.__class__.objects) == 0:
self.report({"INFO"}, "No objects with empty material slots found")
else:
print("\n* A total of %d %s with empty material slots was found \n" % (
len(self.__class__.objects),
"object" if len(self.__class__.objects) == 1 else "objects"))
count = 0
for obs in self.__class__.objects:
print('%02d. %s' % (count+1, self.__class__.objects[count]))
count += 1
print("\n")
return{'FINISHED'}
class SCENE_OT_list_missing_material_slots_clear(Operator):
"""Clear the list below"""
bl_idname = "scene.list_missing_material_slots_clear"
bl_label = "Clear Empty Material Slots List"
def execute(self, context):
SCENE_OT_list_missing_material_slots.objects[:] = []
print("* Cleared Empty Material Slots List")
return {'FINISHED'}
class SCENE_OT_blender_instance_open(Operator):
'''Open in a new Blender instance'''
bl_idname = "scene.blender_instance_open"
bl_label = "Open Blender Instance"
filepath = bpy.props.StringProperty()
def execute(self, context):
if self.filepath:
filepath = bpy.path.abspath(self.filepath)
import subprocess
try:
subprocess.Popen([bpy.app.binary_path, filepath])
except:
print("Error on the new Blender instance")
import traceback
traceback.print_exc()
return{'FINISHED'}
class SCENE_PT_scene_debug(Panel):
'''Scene Debug'''
bl_label = 'Scene Debug'
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
objects = bpy.data.objects
ob_act = context.active_object
images = bpy.data.images
lamps = bpy.data.lamps
images_missing = []
list_lamps = scene.amaranth_debug_scene_list_lamps
list_missing_images = scene.amaranth_debug_scene_list_missing_images
materials = SCENE_OT_cycles_shader_list_nodes.materials
materials_count = len(SCENE_OT_cycles_shader_list_nodes.materials)
missing_material_slots_obs = SCENE_OT_list_missing_material_slots.objects
missing_material_slots_count = len(SCENE_OT_list_missing_material_slots.objects)
engine = scene.render.engine
# List Lamps
box = layout.box()
row = box.row(align=True)
split = row.split()
col = split.column()
if lamps:
row = col.row(align=True)
row.alignment = 'LEFT'
row.prop(scene, 'amaranth_debug_scene_list_lamps',
icon="%s" % 'TRIA_DOWN' if list_lamps else 'TRIA_RIGHT',
emboss=False)
if objects and list_lamps:
row = box.row(align=True)
split = row.split(percentage=0.42)
col = split.column()
col.label(text="Name")
split = split.split(percentage=0.1)
col = split.column()
col.label(text="", icon="BLANK1")
if engine in ['CYCLES', 'BLENDER_RENDER']:
if engine == 'BLENDER_RENDER':
split = split.split(percentage=0.7)
else:
split = split.split(percentage=0.35)
col = split.column()
col.label(text="Samples")
if engine == 'CYCLES':
split = split.split(percentage=0.35)
col = split.column()
col.label(text="Size")
split = split.split(percentage=0.8)
col = split.column()
col.label(text="Visibility")
for ob in objects:
if ob and ob.type == 'LAMP':
lamp = ob.data
clamp = ob.data.cycles
row = box.row(align=True)
split = row.split(percentage=0.5)
col = split.column()
row = col.row()
row.alignment = 'LEFT'
row.active = ob.name in context.scene.objects
row.operator("scene.amaranth_debug_lamp_select",
text='%s %s%s' % (
" [L] " if ob.library else "",
ob.name,
"" if ob.name in context.scene.objects else " [Not in Scene]"),
icon="LAMP_%s" % ob.data.type,
emboss=False).lamp = ob.name
if engine == 'CYCLES':
split = split.split(percentage=0.35)
col = split.column()
if scene.cycles.progressive == 'BRANCHED_PATH':
col.prop(clamp, "samples", text="")
if scene.cycles.progressive == 'PATH':
col.label(text="N/A")
if engine == 'BLENDER_RENDER':
split = split.split(percentage=0.7)
col = split.column()
if lamp.type == 'HEMI':
col.label(text="Not Available")
elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
row = col.row(align=True)
row.prop(lamp, "shadow_ray_samples_x", text="X")
if lamp.shape == 'RECTANGLE':
row.prop(lamp, "shadow_ray_samples_y", text="Y")
elif lamp.shadow_method == 'RAY_SHADOW':
col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
elif lamp.shadow_method == 'BUFFER_SHADOW':
col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
else:
col.label(text="No Shadow")
if engine == 'CYCLES':
split = split.split(percentage=0.4)
col = split.column()
if lamp.type in ['POINT','SUN', 'SPOT']:
col.label(text="%.2f" % lamp.shadow_soft_size)
elif lamp.type == 'HEMI':
col.label(text="N/A")
else:
col.label(text="%.2f" % lamp.size)
split = split.split(percentage=0.8)
col = split.column()
row = col.row(align=True)
row.prop(ob, "hide", text="", emboss=False)
row.prop(ob, "hide_render", text="", emboss=False)
split = split.split(percentage=0.3)
col = split.column()
col.label(text="", icon="%s" % "TRIA_LEFT" if ob == ob_act else "BLANK1")
else:
row = col.row(align=True)
row.alignment = 'LEFT'
row.label(text="Lamps List", icon="RIGHTARROW_THIN")
split = split.split()
col = split.column()
col.label(text="No Lamps", icon="LAMP_DATA")
# List Missing Images
box = layout.box()
row = box.row(align=True)
split = row.split()
col = split.column()
if images:
import os.path
for im in images:
if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']:
if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
images_missing.append(["%s%s [%s]%s" % (
'[L] ' if im.library else '',
im.name, im.users,
' [F]' if im.use_fake_user else ''),
im.filepath if im.filepath else 'No Filepath',
im.library.filepath if im.library else ''])
if images_missing:
row = col.row(align=True)
row.alignment = 'LEFT'
row.prop(scene, 'amaranth_debug_scene_list_missing_images',
icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
emboss=False)
split = split.split()
col = split.column()
col.label(text="%s missing %s" % (
str(len(images_missing)),
'image' if len(images_missing) == 1 else 'images'),
icon="ERROR")
if list_missing_images:
col = box.column(align=True)
for mis in images_missing:
col.label(text=mis[0],
icon="IMAGE_DATA")
col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
if mis[2]:
row = col.row(align=True)
row.alignment = "LEFT"
row.operator(SCENE_OT_blender_instance_open.bl_idname,
text=mis[2],
icon="LINK_BLEND",
emboss=False).filepath=mis[2]
col.separator()
else:
row = col.row(align=True)
row.alignment = 'LEFT'
row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")
split = split.split()
col = split.column()
col.label(text="%s %s loading correctly" % (
str(len(images)),
'image' if len(images) == 1 else 'images'),
icon="IMAGE_DATA")
else:
row = col.row(align=True)
row.alignment = 'LEFT'
row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
# List Cycles Materials by Shader
if engine == 'CYCLES':
box = layout.box()
split = box.split()
col = split.column(align=True)
col.prop(scene, 'amaranth_cycles_node_types',
icon="MATERIAL")
row = split.row(align=True)
row.operator(SCENE_OT_cycles_shader_list_nodes.bl_idname,
icon="SORTSIZE",
text="List Materials Using Shader")
if materials_count != 0:
row.operator(SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
icon="X", text="")
col.separator()
try:
materials
except NameError:
pass
else:
if materials_count != 0:
col = box.column(align=True)
count = 0
col.label(text="%s %s found" % (materials_count,
'material' if materials_count == 1 else 'materials'), icon="INFO")
for mat in materials:
count += 1
col.label(text='%s' % (materials[count-1]), icon="MATERIAL")
# List Missing Node Trees
box = layout.box()
row = box.row(align=True)
split = row.split()
col = split.column(align=True)
split = col.split()
split.label(text="Node Links")
split.operator(SCENE_OT_list_missing_node_links.bl_idname,
icon="NODETREE")
if SCENE_OT_list_missing_node_links.count_groups != 0 or \
SCENE_OT_list_missing_node_links.count_images != 0:
col.label(text="Warning! Check Console", icon="ERROR")
if SCENE_OT_list_missing_node_links.count_groups != 0:
col.label(text="%s" % ("%s node %s missing link" % (
str(SCENE_OT_list_missing_node_links.count_groups),
"group" if SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
icon="NODETREE")
if SCENE_OT_list_missing_node_links.count_images != 0:
col.label(text="%s" % ("%s image %s missing link" % (
str(SCENE_OT_list_missing_node_links.count_images),
"node" if SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
icon="IMAGE_DATA")
col.separator()
# List Empty Materials Slots
box = layout.box()
split = box.split()
col = split.column(align=True)
col.label(text="Material Slots")
row = split.row(align=True)
row.operator(SCENE_OT_list_missing_material_slots.bl_idname,
icon="MATERIAL",
text="List Empty Materials Slots")
if missing_material_slots_count != 0:
row.operator(SCENE_OT_list_missing_material_slots_clear.bl_idname,
icon="X", text="")
col.separator()
try:
missing_material_slots_obs
except NameError:
pass
else:
if missing_material_slots_count != 0:
col = box.column(align=True)
count = 0
col.label(text="%s %s with empty material slots found" % (
missing_material_slots_count,
'object' if missing_material_slots_count == 1 else 'objects'),
icon="INFO")
for obs in missing_material_slots_obs:
count += 1
col.label(text='%s' % (
missing_material_slots_obs[count-1]),
icon="OBJECT_DATA")
# // FEATURE: Scene Debug
# FEATURE: Dupli Group Path
def ui_dupli_group_library_path(self, context):
ob = context.object
lib = ob.dupli_group.library.filepath
row = self.layout.row()
row.alignment = 'LEFT'
if ob and ob.dupli_group and ob.dupli_group.library:
row.operator(SCENE_OT_blender_instance_open.bl_idname,
text="Library: %s" % lib,
emboss=False,
icon="LINK_BLEND").filepath=lib
# // FEATURE: Dupli Group Path
# FEATURE: Color Management Presets
class SCENE_MT_color_management_presets(Menu):
"""List of Color Management presets"""
bl_label = "Color Management Presets"
preset_subdir = "color"
preset_operator = "script.execute_preset"
draw = Menu.draw_preset
class AddPresetColorManagement(AddPresetBase, Operator):
"""Add or remove a Color Management preset"""
bl_idname = "scene.color_management_preset_add"
bl_label = "Add Color Management Preset"
preset_menu = "SCENE_MT_color_management_presets"
preset_defines = [
"scene = bpy.context.scene"
]
preset_values = [
"scene.view_settings.view_transform",
"scene.display_settings.display_device",
"scene.view_settings.exposure",
"scene.view_settings.gamma",
"scene.view_settings.look",
"scene.view_settings.use_curve_mapping",
"scene.sequencer_colorspace_settings.name",
]
preset_subdir = "color"
def ui_color_management_presets(self, context):
layout = self.layout
row = layout.row(align=True)
row.menu("SCENE_MT_color_management_presets", text=bpy.types.SCENE_MT_color_management_presets.bl_label)
row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
layout.separator()
# // FEATURE: Color Management Presets
# FEATURE: Sequencer Extra Info
def act_strip(context):
try:
return context.scene.sequence_editor.active_strip
except AttributeError:
return None
def ui_sequencer_extra_info(self, context):
layout = self.layout
strip = act_strip(context)
if strip:
seq_type = strip.type
if seq_type and seq_type == 'IMAGE':
elem = strip.strip_elem_from_frame(context.scene.frame_current)
if elem:
layout.label(text="%s %s" % (
elem.filename,
"[%s]" % (context.scene.frame_current - strip.frame_start)))
# // FEATURE: Sequencer Extra Info
classes = (SCENE_MT_color_management_presets,
AddPresetColorManagement,
SCENE_PT_scene_debug,
SCENE_OT_refresh,
SCENE_OT_cycles_shader_list_nodes,
SCENE_OT_cycles_shader_list_nodes_clear,
SCENE_OT_amaranth_debug_lamp_select,
SCENE_OT_list_missing_node_links,
SCENE_OT_list_missing_material_slots,
SCENE_OT_list_missing_material_slots_clear,
SCENE_OT_blender_instance_open,
WM_OT_save_reload,
MESH_OT_find_asymmetric,
MESH_OT_make_symmetric,
NODE_OT_AddTemplateVignette,
NODE_MT_amaranth_templates,
FILE_OT_directory_current_blend,
FILE_OT_directory_go_to,
NODE_PT_indices,
NODE_PT_simplify,
NODE_OT_toggle_mute,
NODE_OT_show_active_node_image,
VIEW3D_OT_render_border_camera,
VIEW3D_OT_show_only_render,
OBJECT_OT_select_meshlights,
POSE_OT_paths_clear_all,
POSE_OT_paths_frame_match,
FILE_PT_libraries)
addon_keymaps = []
def register():
bpy.utils.register_class(AmaranthToolsetPreferences)
# UI: Register the panel
init_properties()
for c in classes:
bpy.utils.register_class(c)
bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
bpy.types.INFO_MT_file.append(button_save_reload)
bpy.types.INFO_HT_header.append(stats_scene)
bpy.types.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
bpy.types.NODE_HT_header.append(node_templates_pulldown)
bpy.types.NODE_HT_header.append(node_stats)
bpy.types.NODE_HT_header.append(node_shader_extra)
bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
bpy.types.DATA_PT_display.append(pose_motion_paths_ui)
bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)
bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)
bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)
bpy.app.handlers.render_pre.append(unsimplify_render_pre)
bpy.app.handlers.render_post.append(unsimplify_render_post)
wm = bpy.context.window_manager
kc = wm.keyconfigs.addon
if kc:
km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS')
kmi.properties.name = "NODE_MT_amaranth_templates"
km = kc.keymaps.new(name='Window')
kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)
km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)
km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
kmi.properties.data_path = 'area.type'
kmi.properties.value = 'DOPESHEET_EDITOR'
km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
kmi.properties.data_path = 'area.type'
kmi.properties.value = 'GRAPH_EDITOR'
km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
kmi.properties.data_path = 'space_data.mode'
kmi.properties.value_1 = 'ACTION'
kmi.properties.value_2 = 'DOPESHEET'
addon_keymaps.append((km, kmi))
def unregister():
bpy.utils.unregister_class(AmaranthToolsetPreferences)
for c in classes:
bpy.utils.unregister_class(c)
bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
bpy.types.INFO_MT_file.remove(button_save_reload)
bpy.types.INFO_HT_header.remove(stats_scene)
bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
bpy.types.NODE_HT_header.remove(node_templates_pulldown)
bpy.types.NODE_HT_header.remove(node_stats)
bpy.types.NODE_HT_header.remove(node_shader_extra)
bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)
bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)
bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)
bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)
bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
bpy.app.handlers.render_post.remove(unsimplify_render_post)
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
clear_properties()
if __name__ == "__main__":
register()