blender/release/scripts/templates_py/ui_previews_dynamic_enum.py
Bastien Montagne a80c1e50bc Fix T44822: python enums' itemf callback did not handle 'NULL' context case.
Enum's itemf callback can be called without context in some cases (UI, doc generation...).
Python's enum properties did not handle this at all - it's kind of odd this did not cause
more trouble and wasn't notice earlier... Probably dynamic enums using context are not
much used in py code.

Note about nodes: those are heavy users of dynamic enum with context. Now,
we expect `NodeCategory.poll()` and `NodeItem.poll()` to always be called with
a valid context (since when there is no context available, we can assume `poll()`
is always True). `NodeCategory.items()`, however, must accept NULL context, so if
you use custom `items` callable for your custom node categories, you may need
to update it (as was done here for builtin `node_group_items()`).
2015-05-25 14:24:49 +02:00

138 lines
4.0 KiB
Python

# This sample script demonstrates a dynamic EnumProperty with custom icons.
# The EnumProperty is populated dynamically with thumbnails of the contents of
# a chosen directory in 'enum_previews_from_directory_items'.
# Then, the same enum is displayed with different interfaces. Note that the
# generated icon previews do not have Blender IDs, which means that they can
# not be used with UILayout templates that require IDs,
# such as template_list and template_ID_preview.
#
# Other use cases:
# - make a fixed list of enum_items instead of calculating them in a function
# - generate isolated thumbnails to use as custom icons in buttons
# and menu items
#
# For custom icons, see the template "ui_previews_custom_icon.py".
#
# For distributable scripts, it is recommended to place the icons inside the
# script directory and access it relative to the py script file for portability:
#
# os.path.join(os.path.dirname(__file__), "images")
import os
import bpy
def enum_previews_from_directory_items(self, context):
"""EnumProperty callback"""
enum_items = []
if context is None:
return enum_items
wm = context.window_manager
directory = wm.my_previews_dir
# Get the preview collection (defined in register func).
pcoll = preview_collections["main"]
if directory == pcoll.my_previews_dir:
return pcoll.my_previews
print("Scanning directory: %s" % directory)
if directory and os.path.exists(directory):
# Scan the directory for png files
image_paths = []
for fn in os.listdir(directory):
if fn.lower().endswith(".png"):
image_paths.append(fn)
for i, name in enumerate(image_paths):
# generates a thumbnail preview for a file.
filepath = os.path.join(directory, name)
thumb = pcoll.load(filepath, filepath, 'IMAGE')
enum_items.append((name, name, "", thumb.icon_id, i))
pcoll.my_previews = enum_items
pcoll.my_previews_dir = directory
return pcoll.my_previews
class PreviewsExamplePanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Previews Example Panel"
bl_idname = "OBJECT_PT_previews"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
wm = context.window_manager
row = layout.row()
row.prop(wm, "my_previews_dir")
row = layout.row()
row.template_icon_view(wm, "my_previews")
row = layout.row()
row.prop(wm, "my_previews")
# We can store multiple preview collections here,
# however in this example we only store "main"
preview_collections = {}
def register():
from bpy.types import WindowManager
from bpy.props import (
StringProperty,
EnumProperty,
)
WindowManager.my_previews_dir = StringProperty(
name="Folder Path",
subtype='DIR_PATH',
default=""
)
WindowManager.my_previews = EnumProperty(
items=enum_previews_from_directory_items,
)
# Note that preview collections returned by bpy.utils.previews
# are regular Python objects - you can use them to store custom data.
#
# This is especially useful here, since:
# - It avoids us regenerating the whole enum over and over.
# - It can store enum_items' strings
# (remember you have to keep those strings somewhere in py,
# else they get freed and Blender references invalid memory!).
import bpy.utils.previews
pcoll = bpy.utils.previews.new()
pcoll.my_previews_dir = ""
pcoll.my_previews = ()
preview_collections["main"] = pcoll
bpy.utils.register_class(PreviewsExamplePanel)
def unregister():
from bpy.types import WindowManager
del WindowManager.my_previews
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
bpy.utils.unregister_class(PreviewsExamplePanel)
if __name__ == "__main__":
register()