import bpy import os from os.path import abspath, join from bpy.types import (AddonPreferences, PointerProperty, PropertyGroup) from bpy.props import (BoolProperty, StringProperty, CollectionProperty, EnumProperty, IntProperty) from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS, ICONS, RESOURCES_DIR, LIBRARY_TYPE_DIR, LIBRARY_TYPES, ADAPTERS) from asset_library.common.file_utils import import_module_from_path, norm_str from asset_library.common.bl_utils import get_addon_prefs from asset_library.common.library_cache import LibraryCache from asset_library.common.catalog import Catalog #from asset_library.common.functions import get_catalog_path from pathlib import Path import importlib import inspect def update_library_config(self, context): print('update_library_config not yet implemented') def update_library_path(self, context): prefs = get_addon_prefs() self['bundle_directory'] = str(self.library_path) if not self.custom_bundle_name: self['custom_bundle_name'] = self.name if not self.custom_bundle_directory: custom_bundle_dir = Path(prefs.bundle_directory, self.library_name).resolve() self['custom_bundle_directory'] = str(custom_bundle_dir) #if self.custom_bundle_directory: # self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory)) #else: # bundle_directory = join(prefs.bundle_directory, norm_str(self.name)) # self['custom_bundle_directory'] = abspath(bundle_directory) self.set_library_path() def update_all_library_path(self, context): #print('update_all_assetlib_paths') prefs = get_addon_prefs() #if self.custom_bundle_directory: # self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory)) for lib in prefs.libraries: update_library_path(lib, context) #lib.set_library_path() def get_library_type_items(self, context): #prefs = get_addon_prefs() items = [('NONE', 'None', '', 0)] items += [(norm_str(a.name, format=str.upper), a.name, "", i+1) for i, a in enumerate(LIBRARY_TYPES)] return items def get_adapters_items(self, context): #prefs = get_addon_prefs() items = [('NONE', 'None', '', 0)] items += [(norm_str(a.name, format=str.upper), a.name, "", i+1) for i, a in enumerate(ADAPTERS)] return items def get_library_items(self, context): prefs = get_addon_prefs() items = [('NONE', 'None', '', 0)] items += [(l.name, l.name, "", i+1) for i, l in enumerate(prefs.libraries) if l != self] return items def get_store_library_items(self, context): #prefs = get_addon_prefs() #libraries = [l for l in prefs.libraries if l.merge_library == self.name] return [(l.name, l.name, "", i) for i, l in enumerate([self] + self.merge_libraries)] class LibraryTypes(PropertyGroup): def __iter__(self): return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) class Adapters(PropertyGroup): def __iter__(self): return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) class AssetLibrary(PropertyGroup): name : StringProperty(name='Name', default='Action Library', update=update_library_path) id : StringProperty() auto_bundle : BoolProperty(name='Auto Bundle', default=False) expand : BoolProperty(name='Expand', default=False) use : BoolProperty(name='Use', default=True, update=update_library_path) data_type : EnumProperty(name='Type', items=DATA_TYPE_ITEMS, default='COLLECTION') #template_image : StringProperty(default='', description='../{name}_image.png') #template_video : StringProperty(default='', description='../{name}_video.mov') #template_info : StringProperty(default='', description='../{name}_asset_info.json') bundle_directory : StringProperty( name="Bundle Directory", subtype='DIR_PATH', default='' ) use_custom_bundle_directory : BoolProperty(default=False, update=update_library_path) custom_bundle_directory : StringProperty( name="Bundle Directory", subtype='DIR_PATH', default='', update=update_library_path ) #use_merge : BoolProperty(default=False, update=update_library_path) use_custom_bundle_name : BoolProperty(default=False, update=update_library_path) custom_bundle_name : StringProperty(name='Merge Name', update=update_library_path) #merge_library : EnumProperty(name='Merge Library', items=get_library_items, update=update_library_path) #merge_name : StringProperty(name='Merge Name', update=update_library_path) #Library when adding an asset to the library if merge with another store_library: EnumProperty(items=get_store_library_items, name="Library") template: StringProperty() expand_extra : BoolProperty(name='Expand', default=False) blend_depth : IntProperty(name='Blend Depth', default=1) # source_directory : StringProperty( # name="Path", # subtype='DIR_PATH', # default='', # update=update_library_path # ) #library_type : EnumProperty(items=library_type_ITEMS) library_types : bpy.props.PointerProperty(type=LibraryTypes) library_type_name : EnumProperty(items=get_library_type_items) adapters : bpy.props.PointerProperty(type=Adapters) adapter_name : EnumProperty(items=get_adapters_items) parent_name : StringProperty() # data_file_path : StringProperty( # name="Path", # subtype='FILE_PATH', # default='', # ) #def __init__(self): # self.library_types.parent = self @property def parent(self): prefs = get_addon_prefs() if self.parent_name: return prefs.libraries[self.parent_name] @property def merge_libraries(self): prefs = get_addon_prefs() return [l for l in prefs.libraries if l != self and (l.library_path == self.library_path)] @property def child_libraries(self): prefs = get_addon_prefs() return [l for l in prefs.libraries if l != self and (l.parent == self)] @property def data_types(self): data_type = self.data_type if data_type == 'FILE': data_type = 'COLLECTION' return f'{data_type.lower()}s' @property def library_type(self): name = norm_str(self.library_type_name) if not hasattr(self.library_types, name): return return getattr(self.library_types, name) @property def adapter(self): name = norm_str(self.adapter_name) if not hasattr(self.adapters, name): return return getattr(self.adapters, name) @property def library(self): prefs = get_addon_prefs() asset_lib_ref = bpy.context.space_data.params.asset_library_ref #TODO work also outside asset_library_area if asset_lib_ref not in prefs.libraries: return None return prefs.libraries[asset_lib_ref] @property def library_path(self): prefs = get_addon_prefs() library_name = self.library_name #if not self.use_custom_bundle_name: # library_name = norm_str(library_name) if self.use_custom_bundle_directory: return Path(self.custom_bundle_directory).resolve() else: library_name = norm_str(library_name) return Path(prefs.bundle_directory, library_name).resolve() @property def bundle_dir(self): return self.library_path.as_posix() @property def library_name(self): if self.use_custom_bundle_name: return self.custom_bundle_name return self.name def read_catalog(self): return Catalog(self.library_path).read() def read_cache(self, filepath=None): if filepath: return LibraryCache(filepath).read() return LibraryCache.from_library(self).read() def clear_library_path(self): #print('Clear Library Path', self.name) prefs = bpy.context.preferences libs = prefs.filepaths.asset_libraries #path = self.library_path.as_posix() for l in reversed(libs): #lib_path = Path(l.path).resolve().as_posix() prev_name = self.get('asset_library') or self.library_name #print(l.name, prev_name) if (l.name == prev_name): index = list(libs).index(l) try: bpy.ops.preferences.asset_library_remove(index=index) return except AttributeError: pass #print('No library removed') def set_dict(self, data, obj=None): """"Recursive method to set all attribute from a dict to this instance""" if obj is None: obj = self # Make shure the input dict is not modidied data = data.copy() #print(obj) for key, value in data.items(): if isinstance(value, dict): if 'name' in value: setattr(obj, f'{key}_name', value.pop('name')) #print('Nested value', getattr(obj, key)) self.set_dict(value, obj=getattr(obj, key)) elif key in obj.bl_rna.properties.keys(): if key == 'id': value = str(value) elif key == 'custom_bundle_name': if not 'use_custom_bundle_name' in data.values(): obj["use_custom_bundle_name"] = True elif isinstance(value, str): value = os.path.expandvars(value) value = os.path.expanduser(value) #print('set attr', key, value) setattr(obj, key, value) #obj[key] = value else: print(f'Prop {key} of {obj} not exist') self['bundle_directory'] = str(self.library_path) if not self.custom_bundle_name: self['custom_bundle_name'] = self.name # self.library_type_name = data['library_type'] # if not self.library_type: # print(f"No library_type named {data['library_type']}") # return # for key, value in data.items(): # if key == 'options': # for k, v in data['options'].items(): # setattr(self.library_type, k, v) # elif key in self.bl_rna.properties.keys(): # if key == 'id': # value = str(value) # if key == 'custom_bundle_name': # if not 'use_custom_bundle_name' in data.values(): # self["use_custom_bundle_name"] = True # self[key] = value def to_dict(self): data = {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'} if self.library_type: data['library_type'] = self.library_type.to_dict() data['library_type']['name'] = data.pop('library_type_name') del data['library_types'] if self.adapter: data['adapter'] = self.adapter.to_dict() data['adapter']['name'] = data.pop('adapter_name') del data['adapters'] return data def set_library_path(self): '''Update the Blender Preference Filepaths tab with the addon libraries''' prefs = bpy.context.preferences name = self.library_name lib_path = self.library_path self.clear_library_path() if not self.use or not lib_path: # if all(not l.use for l in self.merge_libraries): # self.clear_library_path() return # lib = None # if self.get('asset_library'): # #print('old_name', self['asset_library']) # lib = prefs.filepaths.asset_libraries.get(self['asset_library']) # if not lib: # #print('keys', prefs.filepaths.asset_libraries.keys()) # #print('name', name) # #print(prefs.filepaths.asset_libraries.get(name)) # lib = prefs.filepaths.asset_libraries.get(name) # Create the Asset Library Path lib = prefs.filepaths.asset_libraries.get(name) if not lib: #print(f'Creating the lib {name}') try: bpy.ops.preferences.asset_library_add(directory=str(lib_path)) except AttributeError: return lib = prefs.filepaths.asset_libraries[-1] lib.name = name self['asset_library'] = name lib.path = str(lib_path) @property def is_user(self): prefs = get_addon_prefs() return self in prefs.user_libraries.values() @property def is_env(self): prefs = get_addon_prefs() return self in prefs.env_libraries.values() def add_row(self, layout, data=None, prop=None, label='', boolean=None, factor=0.39): '''Act like the use_property_split but with more control''' enabled = True split = layout.split(factor=factor, align=True) row = split.row(align=False) row.use_property_split = False row.alignment= 'RIGHT' row.label(text=str(label)) if boolean: boolean_data = self if isinstance(boolean, (list, tuple)): boolean_data, boolean = boolean row.prop(boolean_data, boolean, text='') enabled = getattr(boolean_data, boolean) row = split.row(align=True) row.enabled = enabled if isinstance(data, str): row.label(text=data) else: row.prop(data or self, prop, text='') return split def draw_operators(self, layout): row = layout.row(align=True) row.alignment = 'RIGHT' row.prop(self, 'library_type_name', text='') row.prop(self, 'auto_bundle', text='', icon='UV_SYNC_SELECT') row.operator("assetlib.diff", text='', icon='FILE_REFRESH').name = self.name op = row.operator("assetlib.bundle", icon='MOD_BUILD', text='') op.name = self.name layout.separator(factor=3) def draw(self, layout): prefs = get_addon_prefs() #box = layout.box() row = layout.row(align=True) #row.use_property_split = False #row.alignment = 'LEFT' icon = "DISCLOSURE_TRI_DOWN" if self.expand else "DISCLOSURE_TRI_RIGHT" row.prop(self, 'expand', icon=icon, emboss=False, text='') if self.is_user: row.prop(self, 'use', text='') row.prop(self, 'data_type', icon_only=True, emboss=False) row.prop(self, 'name', text='') self.draw_operators(row) index = list(prefs.user_libraries).index(self) row.operator("assetlib.remove_user_library", icon="X", text='', emboss=False).index = index else: row.prop(self, 'use', text='') row.label(icon=ICONS[self.data_type]) #row.label(text=self.name) subrow = row.row(align=True) subrow.alignment = 'LEFT' subrow.prop(self, 'expand', emboss=False, text=self.name) #row.separator_spacer() self.draw_operators(row) sub_row = row.row() sub_row.enabled = False sub_row.label(icon='FAKE_USER_ON') if self.expand: col = layout.column(align=False) col.use_property_split = True #row = col.row(align=True) row = self.add_row(col, prop="custom_bundle_name", boolean="use_custom_bundle_name", label='Custom Bundle Name') row.enabled = not self.use_custom_bundle_directory prop = "bundle_directory" if self.use_custom_bundle_directory: prop = "custom_bundle_directory" self.add_row(col, prop=prop, boolean="use_custom_bundle_directory", label='Custom Bundle Directory', ) col.prop(self, "blend_depth") #subcol = col.column(align=True) #subcol.prop(self, "template_info", text='Template Info', icon='COPY_ID') #subcol.prop(self, "template_image", text='Template Image', icon='COPY_ID') #subcol.prop(self, "template_video", text='Template Video', icon='COPY_ID') if self.library_type: col.separator() self.library_type.draw_prefs(col) for lib in self.child_libraries: lib.draw(layout) col.separator() class Collections: '''Util Class to merge multiple collections''' collections = [] def __init__(self, *collection): self.collections = collection for col in collection: #print('Merge methods') for attr in dir(col): if attr.startswith('_'): continue value = getattr(col, attr) #if not callable(value): # continue setattr(self, attr, value) def __contains__(self, item): if isinstance(item, str): return item in self.to_dict() else: return item in self def __iter__(self): return self.to_list().__iter__() def __getitem__(self, item): if isinstance(item, int): return self.to_list()[item] else: return self.to_dict()[item] def get(self, item, fallback=None): return self.to_dict().get(item) or fallback def to_dict(self): return {k:v for c in self.collections for k, v in c.items()} def to_list(self): return [v for c in self.collections for v in c.values()] def get_parent(self, item): for c in self.collections: if item in c.values(): return c def index(self, item): c = self.get_parent(item) if not c: return item in self return list(c.values()).index(item) #class AssetLibraryOptions(PropertyGroup): # pass class AssetLibraryPrefs(AddonPreferences): bl_idname = __package__ adapters = [] library_types = [] previews = bpy.utils.previews.new() preview_modal = False add_asset_dict = {} #action : bpy.props.PointerProperty(type=AssetLibraryPath) #asset : bpy.props.PointerProperty(type=AssetLibraryPath) #library_types = {} author: StringProperty(default=os.getlogin()) image_player: StringProperty(default='') video_player: StringProperty(default='') library_type_directory : StringProperty(name="Library Type Directory", subtype='DIR_PATH') adapter_directory : StringProperty(name="Adapter Directory", subtype='DIR_PATH') env_libraries : CollectionProperty(type=AssetLibrary) user_libraries : CollectionProperty(type=AssetLibrary) expand_settings: BoolProperty(default=False) bundle_directory : StringProperty( name="Path", subtype='DIR_PATH', default='', update=update_all_library_path ) config_directory : StringProperty( name="Config Path", subtype='FILE_PATH', default=str(RESOURCES_DIR/"asset_library_config.json"), update=update_library_config ) def load_library_types(self): from asset_library.library_types.library_type import LibraryType print('Asset Library: Load Library Types') LIBRARY_TYPES.clear() library_type_files = list(LIBRARY_TYPE_DIR.glob('*.py')) if self.library_type_directory: user_LIBRARY_TYPE_DIR = Path(self.library_type_directory) if user_LIBRARY_TYPE_DIR.exists(): library_type_files += list(user_LIBRARY_TYPE_DIR.glob('*.py')) for library_type_file in library_type_files: if library_type_file.stem.startswith('_'): continue mod = import_module_from_path(library_type_file) #print(library_type_file) for name, obj in inspect.getmembers(mod): if not inspect.isclass(obj): continue #print(obj.__bases__) if not LibraryType in obj.__mro__: continue # Non registering base library_type if obj is LibraryType or obj.name in (a.name for a in LIBRARY_TYPES): continue try: print(f'Register Plugin {name}') bpy.utils.register_class(obj) setattr(LibraryTypes, norm_str(obj.name), bpy.props.PointerProperty(type=obj)) LIBRARY_TYPES.append(obj) except Exception as e: print(f'Could not register library_type {name}') print(e) def load_adapters(self): return @property def libraries(self): return Collections(self.env_libraries, self.user_libraries) def draw(self, context): prefs = get_addon_prefs() layout = self.layout #layout.use_property_split = True main_col = layout.column(align=False) box = main_col.box() row = box.row(align=True) icon = "DISCLOSURE_TRI_DOWN" if self.expand_settings else "DISCLOSURE_TRI_RIGHT" row.prop(self, 'expand_settings', icon=icon, emboss=False, text='') row.label(icon='PREFERENCES') row.label(text='Settings') #row.separator_spacer() subrow = row.row() subrow.alignment = 'RIGHT' subrow.operator("assetlib.reload_addon", text='Reload Addon') if prefs.expand_settings: col = box.column(align=True) col.use_property_split = True #col.prop(self, 'use_single_path', text='Single Path') col.prop(self, 'bundle_directory', text='Bundle Directory') col.separator() col.prop(self, 'library_type_directory') col.prop(self, 'config_directory') col.separator() #col.prop(self, 'template_info', text='Asset Description Template', icon='COPY_ID') #col.separator() #col.prop(self, 'template_image', text='Template Image', icon='COPY_ID') col.prop(self, 'image_player', text='Image Player') #icon='OUTLINER_OB_IMAGE' #col.separator() #col.prop(self, 'template_video', text='Template Video', icon='COPY_ID') col.prop(self, 'video_player', text='Video Player') #icon='FILE_MOVIE' col.separator() col.operator("assetlib.add_user_library", text='Bundle All Libraries', icon='MOD_BUILD') for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries): if lib.parent: continue box = main_col.box() lib.draw(box) row = main_col.row() row.alignment = 'RIGHT' row.operator("assetlib.add_user_library", icon="ADD", text='', emboss=False) classes = [ LibraryTypes, Adapters, #ConformAssetLibrary, AssetLibrary, AssetLibraryPrefs, ] def register(): for cls in classes: bpy.utils.register_class(cls) prefs = get_addon_prefs() # Read Env and override preferences bundle_dir = os.getenv('ASSETLIB_BUNDLE_DIR') if bundle_dir: prefs['bundle_directory'] = os.path.expandvars(bundle_dir) config_dir = os.getenv('ASSETLIB_CONFIG_DIR') if config_dir: prefs['config_directory'] = os.path.expandvars(config_dir) LIBRARY_TYPE_DIR = os.getenv('ASSETLIB_LIBRARY_TYPE_DIR') if LIBRARY_TYPE_DIR: prefs['library_type_directory'] = os.path.expandvars(LIBRARY_TYPE_DIR) ADAPTER_DIR = os.getenv('ASSETLIB_ADAPTER_DIR') if ADAPTER_DIR: prefs['adapter_directory'] = os.path.expandvars(ADAPTER_DIR) prefs.load_library_types() prefs.load_adapters() def unregister(): for cls in reversed(classes + LIBRARY_TYPES): bpy.utils.unregister_class(cls) LIBRARY_TYPES.clear()