How to make a thumbnail activating a class blender 2.80

As example I made this simplified addon. Instead of having buttons, I want to show it thumbnails. Once a thumbnail is chosen it should activate a class (ADD_OT_cube1 or class ADD_OT_cube2). Here the example with just buttons:

import bpy

# Here comes bl_info

# Add cube 1 to scene
class ADD_OT_cube1(bpy.types.Operator):
    bl_idname = 'add.cube1'
    bl_description = 'Adds a cube. Blue, small.'
    bl_category = 'SuperCube'
    bl_label = 'Add Blue Cube'

    def execute(self, context):
        path = os.path.dirname(__file__) + "/ojbects/cubes.blend\\Collection\\"
        object_name = "cube_01"
        bpy.ops.wm.append(filename=object_name, directory=path)
        return {"FINISHED"}

# Add cube 2 to scene
class ADD_OT_cube2(bpy.types.Operator):
    bl_idname = 'add.cube2'
    bl_description = 'Adds a cube. Red, Eating a banana.'
    bl_category = 'SuperCube'
    bl_label = 'Add Grazy Cube'

    def execute(self, context):
        path = os.path.dirname(__file__) + "/ojbects/cubes.blend\\Collection\\"
        object_name = "cube_02"
        bpy.ops.wm.append(filename=object_name, directory=path)
        return {"FINISHED"}


# The menu in the N-Panel 
class ADD_MT_menu(bpy.types.Menu):
    bl_label = "Add Cubes"
    bl_idname = "ADD_MT_menu"

    def draw(self, context):
        layout = self.layout
        layout.operator("add.cube1")
        layout.operator("add.cube2")



# Register Classes
classes = (
    ADD_OT_cube1,
    ADD_OT_cube2,
    ADD_MT_menu)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)

if __name__ == "__main__":
    register()

I hope you can help me out.
There is the ui_previews_dynamic_enum.py template that comes with blender (which I am struggling with to understand i fully) but I don’t need the dynamic part since my previews are already defined. And I wonder how I can make it so that a selection of the thumbnail activates ADD_OT_cube1 or ADD_OT_cube1

And the template ui_previews_custom_icon.py is not what I am looking for because I have more than 20 thumbnails. That would end up in 20 big buttons I suppose.

Oh well, Found a tutorial that shows what I need on: https://www.youtube.com/watch?time_continue=1139&v=cnz02CCRThQ but there is no download. So I typed it over from the video, and tried to port it over to 2.80 here the results so far:
(Don’t run it, because it gives some nasty errors)

import os
import bpy
import bpy.utils.previews

def get_image(filepath):
	name = os.path.basename(filepath)
	if name not in bpy.data.images:
		bpy.ops.image.open(filepath = filepath)
	return bpy.data.images(name)

def open_image(self, context): 
	image = get_image(self.reference_images_enum)
	for area in bpy.context.screen.areas : 
		if area.type == 'IMAGE_EDITOR' : 
				area.spaces.active.image = image
				
def get_previews_collection(self, context): 
	images = []
	custom_icons = preview_collections["main"]
	directory = "C:\Icons"
	i = 0
	
	if directory == custom_icons.my_previews_dir: 
		return custom_icons.my_previews
	
	for file in os.listdir(directory): 
		if file.endswitch(".png"): 
			fullpath = os.path.join(directory, file) 
			custom_icons.load(file, fullpath, 'IMAGE')
			images.append((fullpath, file, fullpath, custom_icons(file).icon_id, i))
			i+=1
			
	custom_icons.my_previews = images
	custom_icons.my_previews_dir = directory
	return custom_icons.my_previews

class ReferenceManagerPanel(bpy.types.Panel): 
	""" A customer Panel in the viewport toolbar """ 
	bl_label = "Reference Images"
	bl_space_type = "VIEW_3D"
	bl_region_type = "TOOLS"
	
	def draw(self, context): 
		layout = self.layout
		
		row = layout.row
		row.template_icon_view(context.window_manager, "reference_images_enum")
		
		row = layout.row
		row.prop(context.window_manager, "reference_images_enum") 
		
preview_collections = {}

def register(): 
	bpy.types.WindowManager.reference_images_enum = bpy.props.EnumProperty(
		items = get_previews_collection, 
		update  = open_image
	)
	
preview_collection = bpy.utils.previews.new()
preview_collection.my_previews_dir = ""
preview_collection.my_previews = ()
preview_collections['main'] = preview_collection 

bpy.utils.register_class(ReferenceManagerPanel) 

def unregister(): 
	del bpy.types.Scene.reference_images_enum
	
	for preview_collection in preview_collections.values():
		bpy.utils.previews.remove(preview_collection)
	preview_collections.clear()
	
	bpy.utils.unregister_class(ReferenceManagerPanel)
	
if __name__ == "__main__":
	register()
The error with this script: 
Exception ignored in: <function ImagePreviewCollection.__del__ at 0x00000285630A2510>
Traceback (most recent call last):
  File "C:\Users\Flatron\Downloads\blender-280-April_15\blender-2.80.0-git.860a9f979d60-windows64\2.80\scripts\modules\bpy\utils\previews.py", line 79, in __del__
    f"{self!r}: left open, remove with "
ResourceWarning: <ImagePreviewCollection id=0x285689d9678[0], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>: left open, remove with 'bpy.utils.previews.remove()'
Exception ignored in: <function ImagePreviewCollection.__del__ at 0x00000285630A2510>
Traceback (most recent call last):
  File "C:\Users\Flatron\Downloads\blender-280-April_15\blender-2.80.0-git.860a9f979d60-windows64\2.80\scripts\modules\bpy\utils\previews.py", line 79, in __del__
    f"{self!r}: left open, remove with "
ResourceWarning: <ImagePreviewCollection id=0x285689d99e8[0], <super: <class 'ImagePreviewCollection'>, <ImagePreviewCollection object>>>: left open, remove with 'bpy.utils.previews.remove()'

Trying to figure out how it works by analysing Simple Asset Manager. Here some notes:

Simple Asset Manager: 
Found in def execute_instert(context, link): 
	- selected_preview = bpy.data.windows_managers["WinMan"].asset_manager_prevs
	- append_element(selected_preview, link) , refers to function to append_elements. 

	Or: 
in Class SAM_ReplaceMaterialButton(bpy.types.Operator): 
	Is an example of a class that performs action depending on what preview is chosen. 
	selected_preview = wm.asset_manager_prevs
	files = append_material(selected_preview) 

gen_thumbnails(image_paths, enum_items, pcoll, empty_path, directory): 
	is a function that seems to fill the previews.  
	it is using pcoll:  pcoll.load(filepath, imgpath, 'IMAGE')

enum_previews_from_directory_items(self, context): 
	A function that seems to manage previews in to a collection or something. 
	related to: preferences, directory, pcoll, 
        returns pcoll.asset_manager_prevs

def SAM_UI(self.context): 
	This is a function that makes the UI where previews are shown.  Interesting is: 
	- row.template_icon_view(wm, "asset_manager_prevs", show_labels=True)

class SAM_panel(bpy.types.Panel): 
	Is a class that uses the previous function to show UI

class SAM_Popup(bpy.types.Operator): 
	Seems to be the same but then a popup? 

def register():
	WindowManager.asset_manager_prev_dir = StringProperty(
		name="Folder Path",
		subtype='DIR_PATH', 
		default="") 
	WindowManager.asset_manager_prevs = EnumProperty(
		items=enum_preview_from_directory_items
		pcoll = bpy.utils.previews.new()
		pcoll.asset_manager_prev_dir = ""
		pcoll.asset_manager_prevs = ""

		preview_collections["main"] = pcoll
		bpy.types.Scene.asset_manager = PointerProperty(
			type=SimpleAssetManager)
		
		bpy.types.VIEW3D_MT_add.append(SAM_button) 

def unregister(): 
		to unregister related as in register.

I thought it would be relatively easy to get thumbnails.

Probably I have to dive into;

bpy.data.windows_managers (or WindowManager(ID))
bpy.utils.previews
template_icon_view

Ehj, quite frustrated at the moment.

Have you used the template from 2.80 : ui_previews_dynamic_enum.py ?
And bpy.context.area.type = ‘IMAGE_EDITOR’ is now bpy.context.area.ui_type == ‘VIEW’

I have seen it yes, but then I thought I better look for another approach because:

  • It dynamically scans the folder / fill the previews while I have a fixed set.
  • I didn’t understand how then, once a preview is selected, to activate a class/function.

Maybe I can dive into that again, finding out how to trigger something once a thumnails has been selected.

Ah, I was looking for what IMAGE_EDITOR is now, couldn’t find it. Thanks!

Maybe you can transfer this one:


This is a very simple addon for 2.79.

@mkbreuer
Thanks, that looks a bit easier to analyse then the bigger addons. And it does exactly what I need. I am not a hero yet in coding so that I easily can convert it to 2.8, but relieving that I am looking in the right direction now. I hope I can manage it in a few hours.

I didn’t manage it yet to convert (even the simple addon) to 2.80. But I managed to get the reference manager (typed it over from video) at least showing up now after a bit more tweaks:

import os
import bpy
import bpy.utils.previews

def get_image(filepath):
	name = os.path.basename(filepath)
	if name not in bpy.data.images:
		bpy.ops.image.open(filepath = filepath)
	return bpy.data.images(name)

def open_image(self, context):
	image = get_image(self.reference_images_enum)
	for area in bpy.context.screen.areas :
		if area.ui_type == 'VIEW' :
				area.spaces.active.image = image
				
def get_previews_collection(self, context):
	images = []
	custom_icons = preview_collections["main"]
	directory = "C:\Icons"
	i = 0

	if directory == custom_icons.my_previews_dir:
		return custom_icons.my_previews
	
	for file in os.listdir(directory):
		if file.endswith(".png"):
			fullpath = os.path.join(directory, file)
			custom_icons.load(file, fullpath, 'IMAGE')
			images.append(( fullpath, file, fullpath, custom_icons(file).icon_id, i))
			i+=1
			


	custom_icons.my_previews = images
	custom_icons.my_previews_dir = directory
	return custom_icons.my_previews

class ReferenceManagerPanel(bpy.types.Panel):
	""" A custom Panel in the viewport toolbar """
	bl_label = "Reference Images"
	bl_space_type = "VIEW_3D"
	bl_region_type = "UI"

	def draw(self, context):
		layout = self.layout

		row = layout.row()
		row.template_icon_view(context.window_manager, "reference_images_enum")

		row = layout.row()
		row.prop(context.window_manager, "reference_images_enum") 

preview_collections = {}

def register():
	bpy.types.WindowManager.reference_images_enum = bpy.props.EnumProperty(
	   items = get_previews_collection,
	   update = open_image
	)

	preview_collection = bpy.utils.previews.new()
	preview_collection.my_previews_dir = ""
	preview_collection.my_previews = ()
	preview_collections['main'] = preview_collection 

	bpy.utils.register_class(ReferenceManagerPanel) 
	
def unregister():
	del bpy.types.Scene.reference_images_enum

	for preview_collection in preview_collections.values():
		bpy.utils.previews.remove(preview_collection)
    preview_collections.clear()

	bpy.utils.unregister_class(ReferenceManagerPanel)
if __name__ == "__main__":
	register()

But I get the error :

Traceback (most recent call last):
  File "\ReferenceTemplateManager_04.py", line 29, in get_previews_collection
  File "C:\Users\Flatron\Downloads\blender-280-April_15\blender-2.80.0-git.860a9f979d60-windows64\2.80\scripts\modules\bpy\utils\previews.py", line 97, in load
    raise KeyError("key {name!r} already exists")
KeyError: 'key {name!r} already exists'
File "\ReferenceTemplateManager_04.py", line 17, in get_previews_collection

I hope someone can see what the issue it. If I just got this running then I very happy.

Look at here: Python Template demo script error
also: replace the parantheses with square brackets

1 Like

Thanks,
That seems to be the same solution Stephan gave on Simple python fix for script
For both the solutions is that it’s checks if the icon is already loaded or not.