# SPDX-License-Identifier: GPL-2.0-or-later import bpy import os from bpy.props import ( BoolProperty, CollectionProperty, EnumProperty, IntProperty, PointerProperty, StringProperty, ) from bpy.types import PropertyGroup, UIList from pprint import pprint as pp from vse_toolbox.bl_utils import get_addon_prefs, get_scene_settings from vse_toolbox.constants import ASSET_PREVIEWS, TRACKERS from vse_toolbox.file_utils import norm_str def get_episodes_items(self, context): settings = get_scene_settings() project = settings.active_project if not project: return [('NONE', 'None', '', 0)] episodes = project.episodes if not episodes: return [('NONE', 'None', '', 0)] return [(e, e, '', i) for i, e in enumerate(episodes.keys())] def get_project_items(self, context): if not self.projects: return [('NONE', 'None', '', 0)] return [(p, p, '', i) for i, p in enumerate(self.projects.keys())] def on_project_updated(self, context): settings = get_scene_settings() settings['episodes'] = 0 #print('Update active Project') bpy.ops.vse_toolbox.load_assets() if settings.active_project: settings.active_project.set_strip_metadata() os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id def on_episode_updated(self, context): os.environ['TRACKER_EPISODE_ID'] = settings.active_episode.id def get_tracker_items(self, context): return [(norm_str(a.__name__, format=str.upper), a.__name__, "", i) for i, a in enumerate(TRACKERS)] class Asset(PropertyGroup): name : StringProperty(default='') id : StringProperty(default='') norm_name : StringProperty(default='') asset_type : StringProperty(default='') tracker_name : StringProperty(default='') preview : StringProperty(default='') @property def label(self): return f"{self.asset_type} / {self.norm_name}" @property def icon_id(self): ico = ASSET_PREVIEWS.get(self.preview) if ico: return ico.icon_id class AssetCasting(PropertyGroup): id : StringProperty(default='') instance : IntProperty(default=1) def __iter__(self): return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) @property def asset(self): settings = get_scene_settings() project = settings.active_project return project.assets.get(self.id) def to_dict(self): return {k: v for k,v in self.items()} class AssetType(PropertyGroup): __annotations__ = {} class MetadataType(PropertyGroup): choices = [] choice : EnumProperty(items=lambda s, c: [(c, c.replace(' ', '_').upper(), '') for c in s['choices']]) class TaskType(PropertyGroup): __annotations__ = {} class TaskStatus(PropertyGroup): __annotations__ = {} class Metadata(PropertyGroup): __annotations__ = {} class Episode(PropertyGroup): id : StringProperty(default='') @property def active(self): settings = get_scene_settings() return self.get(settings.project_name) class Project(PropertyGroup): id : StringProperty(default='') shot_start_number : IntProperty(name="Shot Start Number", default=10, min=0) sequence_start_number : IntProperty(name="Sequence Start Number", default=10, min=0) sequence_increment : IntProperty( name="Sequence Increment", default=10, min=0, step=10) shot_increment : IntProperty( name="Shot Increment", default=10, min=0, step=10) sequence_template : StringProperty( name="Sequence Name", default="sq{index:03d}") episode_template : StringProperty( name="Episode Name", default="ep{index:03d}") shot_template : StringProperty( name="Shot Name", default="{sequence}_sh{index:04d}") render_template : StringProperty( name="Render Name", default="//render/{strip_name}.{ext}") episode_name : EnumProperty(items=get_episodes_items, update=on_episode_updated) episodes : CollectionProperty(type=Episode) assets : CollectionProperty(type=Asset) asset_types : CollectionProperty(type=AssetType) metadata_types : CollectionProperty(type=MetadataType) task_types : CollectionProperty(type=TaskType) task_statuses : CollectionProperty(type=TaskStatus) type : StringProperty() def set_strip_metadata(self): # Clear Metadatas for attr in list(Metadata.__annotations__.keys()): if hasattr(Metadata, attr): delattr(Metadata, attr) del Metadata.__annotations__[attr] for metadata_type in self.metadata_types: prop_name = metadata_type.name if metadata_type.get('choices'): prop = bpy.props.EnumProperty(items=[(c, c.replace(' ', '_').upper(), '') for c in metadata_type['choices']]) else: prop = bpy.props.StringProperty() Metadata.__annotations__[prop_name] = prop setattr(Metadata, prop_name, prop) class VSETB_UL_casting(UIList): order_by_type : BoolProperty(default=False) def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): settings = get_scene_settings() project = settings.active_project asset = item.asset if asset is None: #TODO deal if asset was removed layout.label(text=f'Asset not Found ({item.get("_name", "...")})') return icon_id = asset.icon_id params = {'icon_value': icon_id} if icon_id else {'icon': 'BLANK1'} # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: layout.label(**params) split = layout.split(factor=0.6) split.label(text=f"{asset.norm_name.title()}") split.label(text=f"{asset.asset_type.title()}") split.prop(item, 'instance', text='') elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="") def draw_filter(self, context, layout): row = layout.row() subrow = row.row(align=True) subrow.prop(self, "filter_name", text="") subrow.prop(self, "use_filter_invert", text="", icon='ARROW_LEFTRIGHT') subrow.separator() subrow.prop(self, "order_by_type", text="Order by Type", icon='MESH_DATA') def filter_items(self, context, data, propname): """Filter and order items in the list.""" helper_funcs = bpy.types.UI_UL_list filtered = [] ordered = [] items = getattr(data, propname) # Filtering by name if self.filter_name: filtered = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, items, "name", reverse=self.use_filter_sort_alpha) # Order by types if self.order_by_type: _sort = [(idx, casting_item) for idx, casting_item in enumerate(items)] sort_items = helper_funcs.sort_items_helper ordered = sort_items(_sort, lambda x: x[1].asset.label) return filtered, ordered class VSETB_PGT_scene_settings(PropertyGroup): projects : CollectionProperty(type=Project) project_name : EnumProperty(items=get_project_items, update=on_project_updated) tracker_name : EnumProperty(items=get_tracker_items) toogle_prefs : BoolProperty( description='Toogle VSE ToolBox Preferences', default=True) auto_select_strip : BoolProperty( name='Auto Select Strip',description='Auto select strip', default=True) channel : EnumProperty( items=[ ('AUDIO', 'Audio', '', 0), ('MOVIE', 'Movie', '', 1), ('SHOTS', 'Shots', '', 2), ('SEQUENCES', 'Sequences', '', 3), ('STAMPS', 'Sequences', '', 4), ] ) sequence_channel_name : StringProperty( name="Sequences Channel Name", default="Sequences") shot_channel_name : StringProperty( name="Shot Channel Name", default="Shots") @property def active_project(self): settings = get_scene_settings() return settings.projects.get(settings.project_name) @property def active_episode(self): project = self.active_project if project: return project.episodes.get(project.episode_name) class VSETB_PGT_strip_settings(PropertyGroup): casting : CollectionProperty(type=AssetCasting) casting_index : IntProperty(name='Casting Index', default=0) source_name : StringProperty(name='') metadata : PointerProperty(type=Metadata) classes=( Asset, AssetCasting, AssetType, TaskStatus, Episode, Metadata, MetadataType, TaskType, Project, VSETB_UL_casting, VSETB_PGT_scene_settings, VSETB_PGT_strip_settings, ) from bpy.app.handlers import persistent @persistent def load_handler(dummy): settings = get_scene_settings() if settings.active_project: settings.active_project.set_strip_metadata() os.environ['TRACKER_PROJECT_ID'] = settings.active_project.id def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.vsetb_settings = PointerProperty(type=VSETB_PGT_scene_settings) bpy.types.Sequence.vsetb_strip_settings = PointerProperty(type=VSETB_PGT_strip_settings) #load_metadata_types() bpy.app.handlers.load_post.append(load_handler) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) del bpy.types.Sequence.vsetb_strip_settings del bpy.types.Scene.vsetb_settings bpy.app.handlers.load_post.remove(load_handler)