refacto
This commit is contained in:
+3
-11
@@ -1,26 +1,18 @@
|
||||
|
||||
#from asset_library.bundle_blend import bundle_blend, bundle_library
|
||||
#from file_utils import (norm_str, norm_value,
|
||||
# norm_arg, get_bl_cmd, copy_file, copy_dir)
|
||||
#from asset_library.functions import
|
||||
|
||||
#from asset_library.common import bundle_blend
|
||||
from asset_library.common import file_utils
|
||||
from asset_library.common import functions
|
||||
from asset_library.common import synchronize
|
||||
from asset_library.common import template
|
||||
from asset_library.common import catalog
|
||||
|
||||
if 'bpy' in locals():
|
||||
import importlib
|
||||
|
||||
#importlib.reload(bundle_blend)
|
||||
importlib.reload(file_utils)
|
||||
importlib.reload(functions)
|
||||
importlib.reload(synchronize)
|
||||
importlib.reload(template)
|
||||
importlib.reload(catalog)
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
|
||||
|
||||
import bpy
|
||||
+4
-2
@@ -456,10 +456,12 @@ def get_preview(asset_path='', asset_name=''):
|
||||
return next((f for f in asset_preview_dir.rglob('*') if f.stem.lower().endswith(name)), None)
|
||||
|
||||
def get_object_libraries(ob):
|
||||
if not ob :
|
||||
if ob is None:
|
||||
return []
|
||||
|
||||
libraries = [ob.library, ob.data.library]
|
||||
libraries = [ob.library]
|
||||
if ob.data:
|
||||
libraries += [ob.data.library]
|
||||
|
||||
if ob.type in ('MESH', 'CURVE'):
|
||||
libraries += [m.library for m in ob.data.materials if m]
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
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})'
|
||||
@@ -0,0 +1,381 @@
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
from asset_library.common.file_utils import read_file, write_file
|
||||
from copy import deepcopy
|
||||
import time
|
||||
from itertools import groupby
|
||||
|
||||
|
||||
class AssetCache:
|
||||
def __init__(self, file_cache, data=None):
|
||||
|
||||
self.file_cache = file_cache
|
||||
|
||||
self.catalog = None
|
||||
self.author = None
|
||||
self.description = None
|
||||
self.tags = None
|
||||
self.type = None
|
||||
self.name = None
|
||||
self._metadata = None
|
||||
|
||||
if data:
|
||||
self.set_data(data)
|
||||
|
||||
@property
|
||||
def filepath(self):
|
||||
return self.file_cache.filepath
|
||||
|
||||
@property
|
||||
def library_id(self):
|
||||
return self.file_cache.library_id
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
metadata = {
|
||||
'.library_id': self.library_id,
|
||||
'.filepath': self.filepath
|
||||
}
|
||||
|
||||
metadata.update(self._metadata)
|
||||
|
||||
return metadata
|
||||
|
||||
@property
|
||||
def norm_name(self):
|
||||
return self.name.replace(' ', '_').lower()
|
||||
|
||||
def unique_name(self):
|
||||
return (self.filepath / self.name).as_posix()
|
||||
|
||||
def set_data(self, data):
|
||||
catalog = data['catalog']
|
||||
if isinstance(catalog, (list, tuple)):
|
||||
catalog = '/'.join(catalog)
|
||||
|
||||
self.catalog = catalog
|
||||
self.author = data.get('author', '')
|
||||
self.description = data.get('description', '')
|
||||
self.tags = data.get('tags', [])
|
||||
self.type = data.get('type')
|
||||
self.name = data['name']
|
||||
self._metadata = data.get('metadata', {})
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
catalog=self.catalog,
|
||||
author=self.author,
|
||||
metadata=self.metadata,
|
||||
description=self.description,
|
||||
tags=self.tags,
|
||||
type=self.type,
|
||||
name=self.name
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f'AssetCache(name={self.name}, catalog={self.catalog})'
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.to_dict() == other.to_dict()
|
||||
|
||||
|
||||
class AssetsCache:
|
||||
def __init__(self, file_cache):
|
||||
|
||||
self.file_cache = file_cache
|
||||
self._data = []
|
||||
|
||||
def add(self, asset_cache_data, **kargs):
|
||||
asset_cache = AssetCache(self.file_cache, {**asset_cache_data, **kargs})
|
||||
self._data.append(asset_cache)
|
||||
|
||||
return asset_cache
|
||||
|
||||
def remove(self, asset_cache):
|
||||
if isinstance(asset_cache, str):
|
||||
asset_cache = self.get(asset_cache)
|
||||
|
||||
def __iter__(self):
|
||||
return self._data.__iter__()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, str):
|
||||
return self.to_dict()[key]
|
||||
else:
|
||||
return self._data[key]
|
||||
|
||||
def to_dict(self):
|
||||
return {a.name: a for a in self}
|
||||
|
||||
def get(self, name):
|
||||
return next((a for a in self if a.name == name), None)
|
||||
|
||||
def __repr__(self):
|
||||
return f'AssetsCache({list(self)})'
|
||||
|
||||
|
||||
class FileCache:
|
||||
def __init__(self, library_cache, data=None):
|
||||
|
||||
self.library_cache = library_cache
|
||||
|
||||
self.filepath = None
|
||||
self.modified = None
|
||||
self.assets = AssetsCache(self)
|
||||
|
||||
if data:
|
||||
self.set_data(data)
|
||||
|
||||
@property
|
||||
def library_id(self):
|
||||
return self.library_cache.library_id
|
||||
|
||||
def set_data(self, data):
|
||||
|
||||
if 'filepath' in data:
|
||||
self.filepath = Path(data['filepath'])
|
||||
|
||||
self.modified = data.get('modified', time.time_ns())
|
||||
|
||||
if data.get('type') == 'FILE':
|
||||
self.assets.add(data)
|
||||
|
||||
for asset_cache_data in data.get('assets', []):
|
||||
self.assets.add(asset_cache_data)
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
filepath=self.filepath.as_posix(),
|
||||
modified=self.modified,
|
||||
library_id=self.library_id,
|
||||
assets=[asset_cache.to_dict() for asset_cache in self]
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return self.assets.__iter__()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __repr__(self):
|
||||
return f'FileCache(filepath={self.filepath})'
|
||||
|
||||
|
||||
class AssetCacheDiff:
|
||||
def __init__(self, library_cache, asset_cache, operation):
|
||||
|
||||
self.library_cache = library_cache
|
||||
#self.filepath = data['filepath']
|
||||
self.operation = operation
|
||||
self.asset_cache = asset_cache
|
||||
|
||||
|
||||
class LibraryCacheDiff:
|
||||
def __init__(self, old_cache=None, new_cache=None, filepath=None):
|
||||
|
||||
self.filepath = filepath
|
||||
self._data = []
|
||||
|
||||
self.compare(old_cache, new_cache)
|
||||
|
||||
def add(self, asset_cache_datas, operation):
|
||||
if not isinstance(asset_cache_datas, (list, tuple)):
|
||||
asset_cache_datas = [asset_cache_datas]
|
||||
|
||||
new_asset_diffs = []
|
||||
for cache_data in asset_cache_datas:
|
||||
new_asset_diffs.append(AssetCacheDiff(self, cache_data, operation))
|
||||
|
||||
self._data += new_asset_diffs
|
||||
|
||||
return new_asset_diffs
|
||||
|
||||
def compare(self, old_cache, new_cache):
|
||||
if old_cache is None or new_cache is None:
|
||||
print('Cannot Compare cache with None')
|
||||
|
||||
cache_dict = {a.unique_name : a for a in old_cache.asset_caches}
|
||||
new_cache_dict = {a.unique_name : a for a in new_cache.asset_caches}
|
||||
|
||||
assets_added = self.add([v for k, v in new_cache_dict.items() if k not in cache_dict], 'ADD')
|
||||
assets_removed = self.add([v for k, v in cache_dict.items() if k not in new_cache_dict], 'REMOVED')
|
||||
assets_modified = self.add([v for k, v in cache_dict.items() if v not in assets_removed and v!= new_cache_dict[k]], 'MODIFIED')
|
||||
|
||||
if assets_added:
|
||||
print(f'{len(assets_added)} Assets Added \n{tuple(a.name for a in assets_added[:10])}...\n')
|
||||
if assets_removed:
|
||||
print(f'{len(assets_removed)} Assets Removed \n{tuple(a.name for a in assets_removed[:10])}...\n')
|
||||
if assets_modified:
|
||||
print(f'{len(assets_modified)} Assets Modified \n{tuple(a.name for a in assets_modified[:10])}...\n')
|
||||
|
||||
if len(self) == 0:
|
||||
print('No change in the library')
|
||||
|
||||
return self
|
||||
|
||||
def group_by(self, key):
|
||||
'''Return groups of file cache diff using the key provided'''
|
||||
data = list(self).sort(key=key)
|
||||
return groupby(data, key=key)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._data[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __repr__(self):
|
||||
return f'LibraryCacheDiff(operations={[o for o in self][:2]}...)'
|
||||
|
||||
|
||||
class LibraryCache:
|
||||
def __init__(self, filepath):
|
||||
|
||||
self.filepath = Path(filepath)
|
||||
self._data = []
|
||||
|
||||
@classmethod
|
||||
def from_library(cls, library):
|
||||
filepath = library.library_path / f"blender_assets.{library.id}.json"
|
||||
return cls(filepath)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self.filepath.name
|
||||
|
||||
@property
|
||||
def library_id(self):
|
||||
return self.filepath.stem.split('.')[-1]
|
||||
|
||||
#@property
|
||||
#def filepath(self):
|
||||
# """Get the filepath of the library json file relative to the library"""
|
||||
# return self.directory / self.filename
|
||||
|
||||
def catalogs(self):
|
||||
return set(a.catalog for a in self.asset_caches)
|
||||
|
||||
@property
|
||||
def asset_caches(self):
|
||||
'''Return an iterator to get all asset caches'''
|
||||
return (asset_cache for file_cache in self for asset_cache in file_cache)
|
||||
|
||||
@property
|
||||
def tmp_filepath(self):
|
||||
return Path(bpy.app.tempdir) / self.filename
|
||||
|
||||
def read(self):
|
||||
print(f'Read cache from {self.filepath}')
|
||||
|
||||
for file_cache_data in read_file(self.filepath):
|
||||
self.add(file_cache_data)
|
||||
|
||||
return self
|
||||
|
||||
def write(self, tmp=False):
|
||||
filepath = self.filepath
|
||||
if tmp:
|
||||
filepath = self.tmp_filepath
|
||||
|
||||
print(f'Write cache file to {filepath}')
|
||||
write_file(filepath, self._data)
|
||||
return filepath
|
||||
|
||||
def add(self, file_cache_data=None):
|
||||
file_cache = FileCache(self, file_cache_data)
|
||||
|
||||
self._data.append(file_cache)
|
||||
|
||||
return file_cache
|
||||
|
||||
def add_asset_cache(self, asset_cache_data, filepath=None):
|
||||
if filepath is None:
|
||||
filepath = asset_cache_data['filepath']
|
||||
|
||||
file_cache = self.get(filepath)
|
||||
if not file_cache:
|
||||
file_cache = self.add()
|
||||
|
||||
file_cache.assets.add(asset_cache_data)
|
||||
|
||||
# def unflatten_cache(self, cache):
|
||||
# """ Return a new unflattten list of asset data
|
||||
# grouped by filepath"""
|
||||
|
||||
# new_cache = []
|
||||
|
||||
# cache = deepcopy(cache)
|
||||
|
||||
# cache.sort(key=lambda x : x['filepath'])
|
||||
# groups = groupby(cache, key=lambda x : x['filepath'])
|
||||
|
||||
# keys = ['filepath', 'modified', 'library_id']
|
||||
|
||||
# for _, asset_datas in groups:
|
||||
# asset_datas = list(asset_datas)
|
||||
|
||||
# #print(asset_datas[0])
|
||||
|
||||
# asset_info = {k:asset_datas[0][k] for k in keys}
|
||||
# asset_info['assets'] = [{k:v for k, v in a.items() if k not in keys+['operation']} for a in asset_datas]
|
||||
|
||||
# new_cache.append(asset_info)
|
||||
|
||||
# return new_cache
|
||||
|
||||
def diff(self, new_cache=None):
|
||||
"""Compare the library cache with it current state and return the cache differential"""
|
||||
|
||||
old_cache = self.read()
|
||||
|
||||
if new_cache is None:
|
||||
new_cache = self
|
||||
|
||||
return LibraryCacheDiff(old_cache, new_cache)
|
||||
|
||||
def update(self, cache_diff):
|
||||
#Update the cache with the operations
|
||||
for asset_cache_diff in cache_diff:
|
||||
file_cache = self.get(asset_cache_diff.filepath)
|
||||
if not asset_cache:
|
||||
print(f'Filepath {asset_cache_diff.filepath} not in {self}' )
|
||||
continue
|
||||
|
||||
asset_cache = file_cache.get(asset_cache_diff.name)
|
||||
|
||||
if not asset_cache:
|
||||
print(f'Asset {asset_cache_diff.name} not in file_cache {file_cache}' )
|
||||
continue
|
||||
|
||||
if asset_cache_diff.operation == 'REMOVE':
|
||||
file_cache.assets.remove(asset_cache_diff.name)
|
||||
|
||||
elif asset_cache_diff.operation in ('MODIFY', 'ADD'):
|
||||
asset_cache.set_data(asset_cache_diff.asset_cache.to_dict())
|
||||
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, str):
|
||||
return self.to_dict()[key]
|
||||
else:
|
||||
return self._data[key]
|
||||
|
||||
def to_dict(self):
|
||||
return {a.filepath: a for a in self}
|
||||
|
||||
def get(self, filepath):
|
||||
return next((a for a in self if a.filepath == filepath), None)
|
||||
|
||||
def __repr__(self):
|
||||
return f'LibraryCache(library_id={self.library_id})'
|
||||
|
||||
Reference in New Issue
Block a user