asset_library/common/catalog.py

200 lines
5.6 KiB
Python

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})'