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, ADAPTER_DIR, 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.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_adapter_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 AssetLibraryAdapters(PropertyGroup): parent = None def __iter__(self): return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) ''' class ConformAssetLibrary(PropertyGroup): adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters) adapter_name : EnumProperty(items=get_adapter_items) directory : StringProperty( name="Target Directory", subtype='DIR_PATH', default='' ) template_image : StringProperty(default='', description='../{name}_image.png') template_video : StringProperty(default='', description='../{name}_video.mov') template_description : StringProperty(default='', description='../{name}_asset_description.json') #externalize_data: BoolProperty(default=False, name='Externalize Data') blend_depth: IntProperty(default=1, name='Blend Depth') @property def adapter(self): name = norm_str(self.adapter_name) if not hasattr(self.adapters, name): return return getattr(self.adapters, name) def to_dict(self): data = {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'} data['adapter'] = self.adapter.to_dict() data['adapter']['name'] = data.pop('adapter_name') del data['adapters'] return data ''' 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_description : StringProperty(default='', description='../{name}_asset_description.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 # ) #adapter : EnumProperty(items=adapter_ITEMS) adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters) adapter_name : EnumProperty(items=get_adapter_items) parent : StringProperty() # data_file_path : StringProperty( # name="Path", # subtype='FILE_PATH', # default='', # ) #def __init__(self): # self.adapters.parent = self @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.name)] @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 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 library_name(self): if self.use_custom_bundle_name: return self.custom_bundle_name return self.name 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.adapter_name = data['adapter'] # if not self.adapter: # print(f"No adapter named {data['adapter']}") # return # for key, value in data.items(): # if key == 'options': # for k, v in data['options'].items(): # setattr(self.adapter, 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'} data['adapter'] = self.adapter.to_dict() #data['adapter'] = data.pop('adapter_name') data['adapter']['name'] = data.pop('adapter_name') del data['adapters'] #data['conform'] = self.conform.to_dict() 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 prev_name = self.get('asset_library') or name lib = prefs.filepaths.asset_libraries.get(prev_name) lib_path = self.library_path #print('name', name) #print('lib', lib) #print('lib_path', lib_path) #print('self.merge_library ', self.merge_library) #print('prev_name', prev_name) #print('\nset_library_path') #print(f'{self.name=}, {prev_name=}, {lib_path=}, {self.use}') if not lib_path: self.clear_library_path() return if not self.use: if all(not l.use for l in self.merge_libraries): self.clear_library_path() return # Create the Asset Library Path if not lib: 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 much 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, 'adapter_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_extra(self, layout): #box = layout.box() col = layout.column(align=False) row = col.row(align=True) row.use_property_split = False #row.alignment = 'LEFT' icon = "DISCLOSURE_TRI_DOWN" if self.expand_extra else "DISCLOSURE_TRI_RIGHT" row.label(icon='BLANK1') subrow = row.row(align=True) subrow.alignment = 'LEFT' subrow.prop(self, 'expand_extra', icon=icon, emboss=False, text="Conform Options") #row.prop(self, 'expand_extra', text='', icon="OPTIONS", emboss=False) #row.prop(self, 'expand_extra', emboss=False, text='Options') #row.label(text='Conform Options') subrow = row.row(align=True) subrow.alignment = 'RIGHT' subrow.prop(self.conform, "adapter_name", text='') op = subrow.operator('assetlib.diff', text='', icon='FILE_REFRESH')#, icon='MOD_BUILD' op.name = self.name op.conform = True op = subrow.operator('assetlib.generate_previews', text='', icon='SEQ_PREVIEW')#, icon='MOD_BUILD' op.name = self.name #op.conform = True op = subrow.operator('assetlib.bundle', text='', icon='MOD_BUILD')#, icon='MOD_BUILD' op.name = self.name op.directory = self.conform.directory op.conform = True subrow.label(icon='BLANK1') #subrow.separator(factor=3) if self.expand_extra and self.conform.adapter: col.separator() self.conform.adapter.draw_prefs(col) col.separator() col.separator() #row = layout.row(align=True) #row.label(text='Conform Library') col.prop(self.conform, "directory") col.prop(self.conform, "blend_depth") #col.prop(self.conform, "externalize_data") subcol = col.column(align=True) subcol.prop(self.conform, "template_description", text='Template Description', icon='COPY_ID') subcol.prop(self.conform, "template_image", text='Template Image', icon='COPY_ID') subcol.prop(self.conform, "template_video", text='Template Video', icon='COPY_ID') col.separator() """ 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 = 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_description", text='Template Description', 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.adapter: col.separator() self.adapter.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 = [] #action : bpy.props.PointerProperty(type=AssetLibraryPath) #asset : bpy.props.PointerProperty(type=AssetLibraryPath) #adapters = {} author: StringProperty(default=os.getlogin()) image_player: StringProperty(default='') video_player: StringProperty(default='') 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_adapters(self): from asset_library.adapters.adapter import AssetLibraryAdapter #global ADAPTERS print('\n------Load Adapters') ADAPTERS.clear() adapter_files = list(ADAPTER_DIR.glob('*.py')) if self.adapter_directory: user_adapter_dir = Path(self.adapter_directory) if user_adapter_dir.exists(): adapter_files += list(user_adapter_dir.glob('*.py')) for adapter_file in adapter_files: mod = import_module_from_path(adapter_file) if adapter_file.stem.startswith('_'): continue #print(adapter_file) for name, obj in inspect.getmembers(mod): if not inspect.isclass(obj): continue #print(obj.__bases__) if not AssetLibraryAdapter in obj.__mro__: continue # Non registering base adapter if obj is AssetLibraryAdapter or obj.name in (a.name for a in ADAPTERS): continue try: print(f'Register Plugin {name}') bpy.utils.register_class(obj) setattr(AssetLibraryAdapters, norm_str(obj.name), bpy.props.PointerProperty(type=obj)) ADAPTERS.append(obj) except Exception as e: print(f'Could not register adapter {name}') print(e) @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, 'adapter_directory') col.prop(self, 'config_directory') col.separator() #col.prop(self, 'template_description', 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 = [ AssetLibraryAdapters, #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) adapter_dir = os.getenv('ASSETLIB_ADAPTER_DIR') if adapter_dir: prefs['adapter_directory'] = os.path.expandvars(adapter_dir) prefs.load_adapters() def unregister(): for cls in reversed(classes + ADAPTERS): bpy.utils.unregister_class(cls) ADAPTERS.clear()