"""Video SequencerサイドバーのSE Palette UI。""" from __future__ import annotations import os import bpy import bpy.utils.previews as previews from bpy.types import Panel from .operators import populate_items _preview_collection = None def clear_previews(): global _preview_collection if _preview_collection is not None: try: previews.remove(_preview_collection) except Exception: pass _preview_collection = None def _custom_icon_value(item) -> int: global _preview_collection raw_path = item.icon_path.strip() if not raw_path: return 0 filepath = os.path.abspath(bpy.path.abspath(raw_path)) if not os.path.isfile(filepath): return 0 try: if _preview_collection is None: _preview_collection = previews.new() key = f"{filepath}:{os.path.getmtime(filepath)}" if key not in _preview_collection: _preview_collection.load(key, filepath, "IMAGE") return _preview_collection[key].icon_id except Exception: return 0 def _draw_insert_button(row, item): icon_value = _custom_icon_value(item) kwargs = {"text": item.label} if icon_value: kwargs["icon_value"] = icon_value else: kwargs["icon"] = item.icon or "PLAY_SOUND" operator = row.operator("se_palette.insert", **kwargs) operator.filepath = item.filepath def draw_palette(layout, context): """ポップアップなど任意のUILayoutへSE Paletteを描画する。""" props = context.scene.se_palette folder_box = layout.box() row = folder_box.row(align=True) row.prop(props, "folder", text="") row.operator("se_palette.scan", text="", icon="FILE_REFRESH") if not props.items and os.path.isdir(os.path.abspath(bpy.path.abspath(props.folder))): try: populate_items(props) except Exception: pass settings = layout.box() row = settings.row(align=True) row.prop(props, "channel") row.prop(props, "volume") row = settings.row(align=True) row.prop(props, "advance_playhead") row.prop(props, "compact") settings.prop(props, "grid_columns", slider=True) row = layout.row(align=True) row.prop(props, "search", text="", icon="VIEWZOOM") row.operator("se_palette.stop_preview", text="", icon="PAUSE") folder = os.path.abspath(bpy.path.abspath(props.folder)) if not os.path.isdir(folder): warning = layout.row() warning.alert = True warning.label(text="Select an SE folder", icon="ERROR") return query = props.search.strip().casefold() visible_items = [ item for item in props.items if not query or query in item.label.casefold() or query in item.filename.casefold() ] if not visible_items: layout.label( text="No matching sound effects" if query else "No sound effects found", icon="INFO", ) return grid = layout.grid_flow( row_major=True, columns=props.grid_columns, even_columns=True, even_rows=True, align=True, ) for item in visible_items: card = grid.box() insert_row = card.row(align=True) insert_row.scale_y = 1.0 if props.compact else 1.5 _draw_insert_button(insert_row, item) controls = card.row(align=True) preview = controls.operator( "se_palette.preview", text="" if props.compact else "Preview", icon="PLAY", ) preview.filepath = item.filepath edit = controls.operator( "se_palette.edit_item", text="" if props.compact else "Edit", icon="PREFERENCES", ) edit.filepath = item.filepath layout.label(text=f"{len(visible_items)} / {len(props.items)} sounds", icon="SOUND") class SEPALETTE_PT_main(Panel): """VSEサイドバーに表示するSE Palette。""" bl_label = "SE Palette" bl_idname = "SEPALETTE_PT_main" bl_space_type = "SEQUENCE_EDITOR" bl_region_type = "UI" bl_category = "SE Palette" def draw(self, context): draw_palette(self.layout, context)