3

I want to refresh the Preview Collection everytime the user change the category, in order to do that, I set the generate_previews function to refresh it all the time, but it produce this error:

enter image description here enter image description here

It seems like the Preview Collection can't generate the new previews because it has them already register, but if I try to clear the collection before generating the new previews, it shows this error:

enter image description here

I leave below the script code as well as the JSON file used to collect the image filepath

import bpy
from bpy.types import Panel, EnumProperty, WindowManager
from bpy.props import *
import bpy.utils.previews

import os
import json

props_list = []

with open(os.path.join(os.path.dirname(__file__), "props_list.json")) as json_file:
    data = json.load(json_file)
    props_list = data["Models"]

# UI
class PropsPreviewsPanel(bpy.types.Panel):
    bl_label = "Props Library"
    bl_idname = "OBJECT_PT_previews"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = "Props Library"

    def draw(self, context):
        layout = self.layout
        wm = context.window_manager
        # This tells Blender to draw the my_previews window manager object
        # (Which is our preview)
        row = layout.row()
        row.template_icon_view(context.scene, "props_thumbnails", show_labels=True)

        row = layout.row()
        row.prop(context.scene, 'PropEnum')

        row = layout.row()
        row.prop(context.scene, 'PropEnumSec')

        # Just a way to access which one is selected
        row = layout.row()
        row.label(text="You selected: " + bpy.context.scene.PropEnum)

        row = layout.row()
        row.operator(
            Props_Import.bl_idname,
            text = "Import",
            icon='APPEND_BLEND')

preview_collections = {}

class Props_Import(bpy.types.Operator):
    bl_idname = "object.props_imp"
    bl_label = "Import"
    bl_options = {'REGISTER', 'UNDO'}

def execute_import(self, context):

    dirpath = os.path.join(os.path.dirname(__file__),    "blends/retro.blend/Object/")

    bpy.ops.wm.append(filename="Stop", directory=dirpath)

    return {'FINISHED'}

def generate_previews(self, context):
    # We are accessing all of the information that we generated in the register function below
    pcoll = preview_collections["thumbnail_previews"]
    image_location = pcoll.images_location
    VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg')

    enum_items = []
    i = 0

    # Generate the thumbnails
    for prop, category, subcategory, prop_image in props_list:
        filepath = os.path.join(image_location, prop_image)
        pcoll.clear()
        thumb = pcoll.load(prop, filepath, 'IMAGE', force_reload=True)
        enum_items.append((prop, prop, "", thumb.icon_id, i))
        i += 1

    return enum_items

def generate_subcategories(self, context):

    enum_subcat = []

    if self.PropEnum == 'Kitchen & Food':
        enum_subcat.append(('Glassware', 'Glassware', ''))
        enum_subcat.append(('subcat2', 'Subcategory 2', ''))
        enum_subcat.append(('subcat3', 'Subcategory 3', ''))
        enum_subcat.append(('subcat4', 'Subcategory 4', ''))
        enum_subcat.append(('subcat5', 'Subcategory 5', ''))
        enum_subcat.append(('subcat6', 'Subcategory 6', ''))

    elif self.PropEnum == 'cat2':
        enum_subcat.append(('subcat1', 'Subcategory 1', ''))
        enum_subcat.append(('subcat2', 'Subcategory 2', ''))
        enum_subcat.append(('subcat3', 'Subcategory 3', ''))
        enum_subcat.append(('subcat4', 'Subcategory 4', ''))

    else: enum_subcat = []

    return enum_subcat


def register():
    from bpy.types import Scene
    from bpy.props import StringProperty, EnumProperty

    # Create a new preview collection (only upon register)
    pcoll = bpy.utils.previews.new()

    # This line needs to be uncommented if you install as an addon
    pcoll.images_location = os.path.join(os.path.dirname(__file__), "images")

    # This line is for running as a script. Make sure images are in a folder called images in the same
    # location as the Blender file. Comment out if you install as an addon
    #pcoll.images_location = bpy.path.abspath('//images')

    # Enable access to our preview collection outside of this function
    preview_collections["thumbnail_previews"] = pcoll

    # This is an EnumProperty to hold all of the images
    # You really can save it anywhere in bpy.types.*  Just make sure the location makes sense
    bpy.types.Scene.props_thumbnails = EnumProperty(
        items = generate_previews
        )

    bpy.types.Scene.PropEnum = EnumProperty(
    items = [('Kitchen & Food', 'Kitchen & Food', ''),
             ('cat2', 'Category 2', ''),
             ('cat3', 'Category 3', ''),
             ('cat4', 'Category 4', ''),
             ('cat5', 'Category 5', ''),
             ('cat6', 'Category 6', '')],
    name = "Category",
    )

    bpy.types.Scene.PropEnumSec = EnumProperty(
    name = "Subcategory",
    items = generate_subcategories
    )

def unregister():
    from bpy.types import WindowManager
    for pcoll in preview_collections.values():
        bpy.utils.previews.remove(pcoll)
    preview_collections.clear()

    del bpy.types.Scene.props_thumbnails

if __name__ == "__main__":
    register()

JSON File:

{
  "Models": [
    [
      "Burgundy",
      "Kitchen & Food",
      "Glassware",
      "F_Burgundy.png"
    ],
    [
      "Chardonnay",
      "Kitchen & Food",
      "Glassware",
      "F_Chardonnay.png"
    ],
    [
      "Cabernet",
      "Kitchen & Food",
      "Glassware",
      "F_Cabernet.png"
    ],
    [
      "Champagne",
      "Kitchen & Food",
      "Glassware",
      "F_Champagne.png"
    ],
    [
      "Fries",
      "Kitchen & Food",
      "Fast Food",
      "Fastfood.JPG"
    ]
  ]
}
Alejandro Torres
  • 514
  • 3
  • 12

1 Answers1

1

as you can read in this thread the key is use the "update methods" in properties

update (function) – Function to be called when this value is modified, This function must take 2 values (self, context) and return None. Warning there are no safety checks to avoid infinite recursion.

for example:

in API, EnumProperty has:

bpy.props.EnumProperty(items, name="", description="", default=None, options={'ANIMATABLE'}, update=None, get=None, set=None)

keeping this in mind you can modify the template "ui previews dynamic enum" or create your own

in my example our properties are updating preview_dir_update and preview_enum_update

that methods must be change for you in order to work as you want.

example updating preview collection

import bpy, os, json

print("#"*20) 
preview_collections = {}


props_list = []

filepath = bpy.data.filepath directory = os.path.dirname(filepath)

wm = bpy.context.window_manager 
wm.my_previews_dir = directory


with open(os.path.join(directory, "props_list.json")) as json_file:
    data = json.load(json_file)
    props_list = data["Models"]

#print(props_list)

def generate_subcategories(self, context):

    enum_subcat = []

    if self.PropEnum == 'Kitchen & Food':
        enum_subcat.append(('Glassware', 'Glassware', ''))
        enum_subcat.append(('Fast Food', 'Fast Food', ''))
        enum_subcat.append(('subcat3', 'Subcategory 3', ''))
        enum_subcat.append(('subcat4', 'Subcategory 4', ''))
        enum_subcat.append(('subcat5', 'Subcategory 5', ''))
        enum_subcat.append(('subcat6', 'Subcategory 6', ''))

    elif self.PropEnum == 'cat2':
        enum_subcat.append(('subcat1', 'Subcategory 1', ''))
        enum_subcat.append(('subcat2', 'Subcategory 2', ''))
        enum_subcat.append(('subcat3', 'Subcategory 3', ''))
        enum_subcat.append(('subcat4', 'Subcategory 4', ''))

    else: enum_subcat = []

    return enum_subcat



def enum_previews_from_directory_items(self, context):
    #print("a")
    pcoll = preview_collections.get("main")
    if not pcoll:
        return []

    if self.my_previews_dir == "": # use better default
        # put some code in here to populate default list
        print("MAKE A NEW THUMB LIST HERE")
        newlist = []
        '''
        # a list of items with name, filepath to image, and unique i

        thumb = pcoll.load(filepath, filepath, 'IMAGE')
        item = (name, name, "", thumb.icon_id, i) 
        newlist.append(item)
        '''       
        return newlist

    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")

        row = layout.row()
        row.prop(context.scene, 'PropEnum')

        row = layout.row()
        row.prop(context.scene, 'PropEnumSec')


# We can store multiple preview collections here,
# however in this example we only store "main"

def preview_dir_update(wm, context):
    #print("wm.my_previews_dir = %s" % wm.my_previews_dir)

    """EnumProperty callback"""
    enum_items = []

    VALID_EXTENSIONS = ('.png', '.jpg', '.jpeg')

    a= bpy.context.scene.PropEnum
    b= bpy.context.scene.PropEnumSec


    VALID_FILES = []

    for o in props_list:
        if(o[1] == a and o[2]==b):
            #print(o[1] + " " + o[2] + " " +o[3])
            if not o[3] in VALID_FILES:
                VALID_FILES.append(o[3])


    ## this line if you change the folder path
    ## i mean if the images folder path is not the same of blender file
    wm = context.window_manager
    directory = wm.my_previews_dir



    # Get the preview collection (defined in register func).
    pcoll = preview_collections["main"]
    pcoll.clear()

    '''
    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 in VALID_FILES:                
                n, e = os.path.splitext(fn)
                if e in VALID_EXTENSIONS:
                    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 None


def preview_enum_update(wm, context):
    print("wm.my_previews = %s" % wm.my_previews)
    return None

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="",
            update=preview_dir_update,
            )

    WindowManager.my_previews = EnumProperty(
            items=enum_previews_from_directory_items,
            update=preview_enum_update,
                )

    # 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 = preview_collections.setdefault("main", bpy.utils.previews.new())
    pcoll.my_previews_dir = directory
    pcoll.my_previews = ()

    preview_collections["main"] = pcoll

    bpy.types.Scene.PropEnum = EnumProperty(
    items = [('Kitchen & Food', 'Kitchen & Food', ''),
             ('cat2', 'Category 2', ''),
             ('cat3', 'Category 3', ''),
             ('cat4', 'Category 4', ''),
             ('cat5', 'Category 5', ''),
             ('cat6', 'Category 6', '')],
    name = "Category",
    update=preview_dir_update
    )

    bpy.types.Scene.PropEnumSec = EnumProperty(
    items = generate_subcategories,
    name = "Category",
    update=preview_dir_update
    )

    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()
yhoyo
  • 2,285
  • 13
  • 32
  • I tried to run the script you provide but it seems like it has some errors in the filepath variable and the directory variable, besides that, is it posible with this script to have all the previews in one single folder and only call the previews by their name? – Alejandro Torres Jul 31 '18 at 14:30
  • @AlejandroTorres do you speak spanish? ... that errors are because the blender's version. mine is 2.79. I'm sure that you can solve that easily. and yes. you can have all the image in one folder and call here by their names... – yhoyo Jul 31 '18 at 18:45
  • Yes, I speak spanish, the code works perfectly. I made some changes to work in my add-on. Feel free to ask in my inbox for a free copy of my add-on as soon as I released. Thanks – Alejandro Torres Aug 01 '18 at 02:30