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