from pathlib import Path import uuid import bpy class CatalogItem: """Represent a single item of a catalog""" def __init__(self, catalog, path=None, name=None, id=None): self.catalog = catalog self.path = path self.name = name self.id = str(id or uuid.uuid4()) if isinstance(self.path, Path): self.path = self.path.as_posix() if self.path and not self.name: self.name = self.norm_name(self.path) def parts(self): return Path(self.name).parts def norm_name(self, name): """Get a norm name from a catalog_path entry""" return name.replace('/', '-') def __repr__(self): return f'CatalogItem(name={self.name}, path={self.path}, id={self.id})' class CatalogContext: """Utility class to get catalog relative to the current context asset browser area""" @staticmethod def poll(): return asset_utils.SpaceAssetInfo.is_asset_browser(bpy.context.space_data) @property def id(self): if not self.poll(): return return bpy.context.space_data.params.catalog_id @property def item(self): if not self.poll(): return return self.get(id=self.active_id) @property def path(self): if not self.poll(): return if self.active_item: return self.active_item.path return '' class Catalog: """Represent the catalog of the blender asset browser library""" def __init__(self, directory=None): self.directory = None self._data = {} if directory: self.directory = Path(directory) self.context = CatalogContext() @property def filepath(self): """Get the filepath of the catalog text file relative to the directory""" if self.directory: return self.directory /'blender_assets.cats.txt' def read(self): """Read the catalog file of the library target directory or of the specified directory""" if not self.filepath or not self.filepath.exists(): return {} self._data.clear() print(f'Read catalog from {self.filepath}') for line in self.filepath.read_text(encoding="utf-8").split('\n'): if line.startswith(('VERSION', '#')) or not line: continue cat_id, cat_path, cat_name = line.split(':') self._data[cat_id] = CatalogItem(self, name=cat_name, id=cat_id, path=cat_path) return self def write(self, sort=True): """Write the catalog file in the library target directory or of the specified directory""" if not self.filepath: raise Exception(f'Cannot write catalog {self} no filepath setted') lines = ['VERSION 1', ''] catalog_items = list(self) if sort: catalog_items.sort(key=lambda x : x.path) for catalog_item in catalog_items: lines.append(f"{catalog_item.id}:{catalog_item.path}:{catalog_item.name}") print(f'Write Catalog at: {self.filepath}') self.filepath.write_text('\n'.join(lines), encoding="utf-8") def get(self, path=None, id=None, fallback=None): """Found a catalog item by is path or id""" if isinstance(path, Path): path = path.as_posix() if id: return self._data.get(id) for catalog_item in self: if catalog_item.path == path: return catalog_item return fallback def remove(self, catalog_item): """Get a CatalogItem with is path and removing it if found""" if not isinstance(catalog_item, CatalogItem): catalog_item = self.get(catalog_item) if catalog_item: return self._data.pop(catalog_item.id) print(f'Warning: {catalog_item} cannot be remove, not in {self}') return None def add(self, catalog_path): """Adding a CatalogItem with the missing parents""" # Add missing parents catalog for parent in Path(catalog_path).parents[:-1]: print(parent, self.get(parent)) if self.get(parent): continue cat_item = CatalogItem(self, path=parent) self._data[cat_item.id] = cat_item cat_item = self.get(catalog_path) if not cat_item: cat_item = CatalogItem(self, path=catalog_path) self._data[cat_item.id] = cat_item return cat_item def update(self, catalogs): 'Add or remove catalog entries if on the list given or not' catalogs = set(catalogs) # Remove doubles added = [c for c in catalogs if not self.get(path=c)] removed = [c.path for c in self if c.path not in catalogs] if added: print(f'{len(added)} Catalog Entry Added \n{tuple(c.name for c in added[:10])}...\n') if removed: print(f'{len(removed)} Catalog Entry Removed \n{tuple(c.name for c in removed[:10])}...\n') for catalog_item in removed: self.remove(catalog_item) for catalog_item in added: self.add(catalog_item) def __iter__(self): return self._data.values().__iter__() def __getitem__(self, key): if isinstance(key, int): return self._data.values()[key] return self._data[key] def __contains__(self, item): if isinstance(item, str): # item is the id return item in self._data else: return item in self def __repr__(self): return f'Catalog(filepath={self.filepath})'