diff --git a/__init__.py b/__init__.py index 693c1b1..505dc95 100644 --- a/__init__.py +++ b/__init__.py @@ -8,96 +8,41 @@ Extending features of the Asset Browser for a studio use. bl_info = { "name": "Asset Library", "description": "Asset Library based on the Asset Browser.", - "author": "Sybren A. Stüvel, Clement Ducarteron, Christophe Seux, Samuel Bernou", + "author": "Christophe Seux", "version": (2, 0), - "blender": (3, 3, 0), + "blender": (4, 0, 2), "warning": "In development, things may change", - "location": "Asset Browser -> Animations, and 3D Viewport -> Animation panel", - "category": "Animation", + "location": "Asset Browser", + "category": "Import-Export", } -#from typing import List, Tuple +from . import operators, properties, ui, preferences -from asset_library import pose -from asset_library import action -from asset_library import collection -from asset_library import file -from asset_library import (gui, keymaps, preferences, operators) -from asset_library import constants -#from asset_library.common.library_type import LibraryType -from asset_library.common.bl_utils import get_addon_prefs -from asset_library.common.functions import set_env_libraries -from asset_library.common.template import Template - -import re - - -if 'bpy' in locals(): - print("Reload Addon Asset Library") - - import importlib - - importlib.reload(constants) - importlib.reload(gui) - importlib.reload(keymaps) - - importlib.reload(preferences) - importlib.reload(operators) - importlib.reload(constants) - - importlib.reload(action) - importlib.reload(file) - importlib.reload(collection) - -import bpy -import os - - -#addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = [] - -bl_modules = ( +modules = ( operators, - pose, - action, - collection, - file, - keymaps, - gui, + properties, + ui, preferences ) - -def load_handler(): - print('load_handler') - - set_env_libraries() - bpy.ops.assetlib.set_paths(all=True) - - if not bpy.app.background: - bpy.ops.assetlib.bundle(blocking=False, mode='AUTO_BUNDLE') - - - -def register() -> None: +# Reload Modules from inside Blender +if "bpy" in locals(): + import importlib + for mod in modules: + importlib.reload(mod) - for m in bl_modules: - m.register() - #prefs = get_addon_prefs() +def register(): + """Register the addon Asset Library for Blender""" + for mod in modules: + mod.register() +def unregister(): + """Unregister the addon Asset Library for Blender""" - bpy.app.timers.register(load_handler, first_interval=1) - - -def unregister() -> None: - prefs = get_addon_prefs() - bpy.utils.previews.remove(prefs.previews) - - for m in reversed(bl_modules): - m.unregister() - - + for mod in reversed(modules): + mod.unregister() \ No newline at end of file diff --git a/adapters/adapter.py b/adapters/adapter.py deleted file mode 100644 index 6c627d9..0000000 --- a/adapters/adapter.py +++ /dev/null @@ -1,12 +0,0 @@ - - -from bpy.types import PropertyGroup - - -class Adapter(PropertyGroup): - - #def __init__(self): - name = "Base Adapter" - #library = None - def to_dict(self): - return {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'} \ No newline at end of file diff --git a/common/__init__.py b/common/__init__.py deleted file mode 100644 index 0a02c21..0000000 --- a/common/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ - - -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(file_utils) - importlib.reload(functions) - importlib.reload(synchronize) - importlib.reload(template) - importlib.reload(catalog) - -import bpy \ No newline at end of file diff --git a/common/functions.py b/common/functions.py deleted file mode 100644 index bdff6a9..0000000 --- a/common/functions.py +++ /dev/null @@ -1,467 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - - -""" -Function relative to the asset browser addon -""" - -from pathlib import Path -import json -import os -import re - -import time -#from asset_library.constants import ASSETLIB_FILENAME -import inspect -from asset_library.common.file_utils import read_file -from asset_library.common.bl_utils import get_addon_prefs -import uuid - -import bpy - - -def command(func): - '''Decorator to be used from printed functions argument and run time''' - func_name = func.__name__.replace('_', ' ').title() - - def _command(*args, **kargs): - - bound = inspect.signature(func).bind(*args, **kargs) - bound.apply_defaults() - - args_str = ', '.join([f'{k}={v}' for k, v in bound.arguments.items()]) - print(f'\n[>-] {func_name} ({args_str}) --- Start ---') - - t0 = time.time() - result = func(*args, **kargs) - - print(f'[>-] {func_name} --- Finished (total time : {time.time() - t0:.2f}s) ---') - return result - - return _command - -def asset_warning_callback(self, context): - """Callback function to display a warning message when ading or modifying an asset""" - self.warning = '' - - if not self.name: - self.warning = 'You need to specify a name' - return - if not self.catalog: - self.warning = 'You need to specify a catalog' - return - - lib = get_active_library() - action_path = lib.library_type.get_asset_relative_path(self.name, self.catalog) - self.path = action_path.as_posix() - - if lib.merge_libraries: - prefs = get_addon_prefs() - lib = prefs.libraries[lib.store_library] - - if not lib.library_type.get_asset_path(self.name, self.catalog).parents[1].exists(): - self.warning = 'A new folder will be created' - -def get_active_library(): - '''Get the pref library properties from the active library of the asset browser''' - prefs = get_addon_prefs() - asset_lib_ref = bpy.context.space_data.params.asset_library_ref - - #Check for merged library - for l in prefs.libraries: - if l.library_name == asset_lib_ref: - return l - -def get_active_catalog(): - '''Get the active catalog path''' - - lib = get_active_library() - cat_data = lib.library_type.read_catalog() - cat_data = {v['id']:k for k,v in cat_data.items()} - - cat_id = bpy.context.space_data.params.catalog_id - if cat_id in cat_data: - return cat_data[cat_id] - - return '' - -""" -def norm_asset_datas(asset_file_datas): - ''' Return a new flat list of asset data - the filepath keys are merge with the assets keys''' - - asset_datas = [] - for asset_file_data in asset_file_datas: - asset_file_data = asset_file_data.copy() - if 'assets' in asset_file_data: - - assets = asset_file_data.pop('assets') - for asset_data in assets: - - asset_datas.append({**asset_file_data, **asset_data}) - - else: - asset_datas.append(asset_file_data) - - return asset_datas - -def cache_diff(cache, new_cache): - '''Compare and return the difference between two asset datas list''' - - #TODO use an id to be able to tell modified asset if renamed - #cache = {a.get('id', a['name']) : a for a in norm_asset_datas(cache)} - #new_cache = {a.get('id', a['name']) : a for a in norm_asset_datas(new_cache)} - - cache = {f"{a['filepath']}/{a['name']}": a for a in norm_asset_datas(cache)} - new_cache = {f"{a['filepath']}/{a['name']}" : a for a in norm_asset_datas(new_cache)} - - assets_added = [v for k, v in new_cache.items() if k not in cache] - assets_removed = [v for k, v in cache.items() if k not in new_cache] - assets_modified = [v for k, v in cache.items() if v not in assets_removed and v!= new_cache[k]] - - 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') - - assets_added = [dict(a, operation='ADD') for a in assets_added] - assets_removed = [dict(a, operation='REMOVE') for a in assets_removed] - assets_modified = [dict(a, operation='MODIFY') for a in assets_modified] - - assets_diff = assets_added + assets_removed + assets_modified - if not assets_diff: - print('No change in the library') - - return assets_diff - -def clean_default_lib(): - prefs = bpy.context.preferences - - if not prefs.filepaths.asset_libraries: - print('[>-] No Asset Libraries Filepaths Setted.') - return - - lib, lib_id = get_lib_id( - library_name='User Library', - asset_libraries=prefs.filepaths.asset_libraries - ) - if lib: - bpy.ops.preferences.asset_library_remove(index=lib_id) - -def get_asset_source(replace_local=False): - sp = bpy.context.space_data - prefs = bpy.context.preferences.addons[__package__].preferences - asset_file_handle = bpy.context.asset_file_handle - - if asset_file_handle is None: - return None - - if asset_file_handle.local_id: - publish_path = os.path.expandvars(scn.actionlib.get('publish_path')) - if not publish_path: - print('[>.] No \'Publish Dir\' found. Publish file first.' ) - return None - - return Path(publish_path) - - asset_library_ref = bpy.context.asset_library_ref - source_path = bpy.types.AssetHandle.get_full_library_path(asset_file_handle, asset_library_ref) - - if replace_local: - if 'custom' in sp.params.asset_library_ref.lower(): - actionlib_path = prefs.action.custom_path - actionlib_path_local = prefs.action.custom_path_local - else: - actionlib_path = prefs.action.path - actionlib_path_local = prefs.action.path_local - - source_path = re.sub(actionlib_dir_local, actionlib_dir, source_path) - - return source_path -""" -''' -def get_catalog_path(filepath=None): - filepath = filepath or bpy.data.filepath - filepath = Path(filepath) - - if filepath.is_file(): - filepath = filepath.parent - - filepath.mkdir(parents=True, exist_ok=True) - - catalog = filepath / 'blender_assets.cats.txt' - if not catalog.exists(): - catalog.touch(exist_ok=False) - - return catalog -''' - -# def read_catalog(path, key='path'): -# cat_data = {} - -# supported_keys = ('path', 'id', 'name') - -# if key not in supported_keys: -# raise Exception(f'Not supported key: {key} for read catalog, supported keys are {supported_keys}') - -# for line in Path(path).read_text(encoding="utf-8").split('\n'): -# if line.startswith(('VERSION', '#')) or not line: -# continue - -# cat_id, cat_path, cat_name = line.split(':') - -# if key == 'id': -# cat_data[cat_id] = {'path':cat_path, 'name':cat_name} -# elif key == 'path': -# cat_data[cat_path] = {'id':cat_id, 'name':cat_name} -# elif key =='name': -# cat_data[cat_name] = {'id':cat_id, 'path':cat_path} - -# return cat_data -""" -def read_catalog(path): - cat_data = {} - - for line in Path(path).read_text(encoding="utf-8").split('\n'): - if line.startswith(('VERSION', '#')) or not line: - continue - - cat_id, cat_path, cat_name = line.split(':') - cat_data[cat_path] = {'id':cat_id, 'name':cat_name} - - return cat_data - -def write_catalog(path, data): - lines = ['VERSION 1', ''] - - # Add missing parents catalog - norm_data = {} - for cat_path, cat_data in data.items(): - norm_data[cat_path] = cat_data - for p in Path(cat_path).parents[:-1]: - if p in data or p in norm_data: - continue - - norm_data[p.as_posix()] = {'id': str(uuid.uuid4()), 'name': '-'.join(p.parts)} - - for cat_path, cat_data in sorted(norm_data.items()): - cat_name = cat_data['name'].replace('/', '-') - lines.append(f"{cat_data['id']}:{cat_path}:{cat_name}") - - print(f'Catalog writen at: {path}') - Path(path).write_text('\n'.join(lines), encoding="utf-8") - -def create_catalog_file(json_path : str|Path, keep_existing_category : bool = True): - '''create asset catalog file from json - if catalog already exists, keep existing catalog uid''' - - json_path = Path(json_path) - # if not json.exists(): return - assert json_path.exists(), 'Json not exists !' - - category_datas = json.loads(json_path.read_text(encoding="utf-8")) - - catalog_path = json_path.parent / 'blender_assets.cats.txt' - catalog_data = {} - if catalog_path.exists(): - catalog_data = read_catalog(catalog_path) - ## retrun a format catalog_data[path] = {'id':id, 'name':name} - ## note: 'path' in catalog is 'name' in category_datas - - catalog_lines = ['VERSION 1', ''] - - ## keep existing - for c in category_datas: - # keep same catalog line for existing category keys - if keep_existing_category and catalog_data.get(c['name']): - print(c['name'], 'category exists') - cat = catalog_data[c['name']] #get - catalog_lines.append(f"{cat['id']}:{c['name']}:{cat['name']}") - else: - print(c['name'], 'new category') - # add new category - catalog_lines.append(f"{c['id']}:{c['name']}:{c['name'].replace('/', '-')}") - - ## keep category that are non-existing in json ? - if keep_existing_category: - for k in catalog_data.keys(): - if next((c['name'] for c in category_datas if c['name'] == k), None): - continue - print(k, 'category not existing in json') - cat = catalog_data[k] - # rebuild existing line - catalog_lines.append(f"{cat['id']}:{k}:{cat['name']}") - - ## write_text overwrite the file - catalog_path.write_text('\n'.join(catalog_lines), encoding="utf-8") - - print(f'Catalog saved at: {catalog_path}') - - return -""" - -def clear_env_libraries(): - print('clear_env_libraries') - - prefs = get_addon_prefs() - asset_libraries = bpy.context.preferences.filepaths.asset_libraries - - for env_lib in prefs.env_libraries: - name = env_lib.get('asset_library') - if not name: - continue - - asset_lib = asset_libraries.get(name) - if not asset_lib: - continue - - index = list(asset_libraries).index(asset_lib) - bpy.ops.preferences.asset_library_remove(index=index) - - prefs.env_libraries.clear() - -''' - env_libs = get_env_libraries() - paths = [Path(l['path']).resolve().as_posix() for n, l in env_libs.items()] - - for i, l in reversed(enumerate(libs)): - lib_path = Path(l.path).resolve().as_posix() - - if (l.name in env_libs or lib_path in paths): - libs.remove(i) - ''' - -def set_env_libraries(path=None) -> list: - '''Read the environments variables and create the libraries''' - - #from asset_library.prefs import AssetLibraryOptions - prefs = get_addon_prefs() - path = path or prefs.config_directory - - #print('Read', path) - library_data = read_file(path) - - clear_env_libraries() - - if not library_data: - return - - libs = [] - - for lib_info in library_data: - lib = prefs.env_libraries.add() - - lib.set_dict(lib_info) - - libs.append(lib) - - return libs - -''' -def get_env_libraries(): - env_libraries = {} - - for k, v in os.environ.items(): - if not re.findall('ASSET_LIBRARY_[0-9]', k): - continue - - lib_infos = v.split(os.pathsep) - - if len(lib_infos) == 5: - name, data_type, tpl, src_path, bdl_path = lib_infos - elif len(lib_infos) == 4: - name, data_type, tpl, src_path = lib_infos - bdl_path = '' - else: - print(f'Wrong env key {k}', lib_infos) - continue - - source_type = 'TEMPLATE' - if tpl.lower().endswith(('.json', '.yml', 'yaml')): - source_type = 'DATA_FILE' - - env_libraries[name] = { - 'data_type': data_type, - 'source_directory': src_path, - 'bundle_directory': bdl_path, - 'source_type': source_type, - 'template': tpl, - } - - return env_libraries -''' - - - - - -def resync_lib(name, waiting_time): - bpy.app.timers.register( - lambda: bpy.ops.assetlib.synchronize(only_recent=True, name=name), - first_interval=waiting_time - ) - - - -''' -def set_assetlib_paths(): - prefs = bpy.context.preferences - - assetlib_name = 'Assets' - assetlib = prefs.filepaths.asset_libraries.get(assetlib_name) - - if not assetlib: - bpy.ops.preferences.asset_library_add(directory=str(assetlib_path)) - assetlib = prefs.filepaths.asset_libraries[-1] - assetlib.name = assetlib_name - - assetlib.path = str(actionlib_dir) - -def set_actionlib_paths(): - prefs = bpy.context.preferences - - actionlib_name = 'Action Library' - actionlib_custom_name = 'Action Library Custom' - - actionlib = prefs.filepaths.asset_libraries.get(actionlib_name) - - if not assetlib: - bpy.ops.preferences.asset_library_add(directory=str(assetlib_path)) - assetlib = prefs.filepaths.asset_libraries[-1] - assetlib.name = assetlib_name - - actionlib_dir = get_actionlib_dir(custom=custom) - local_actionlib_dir = get_actionlib_dir(local=True, custom=custom) - - if local_actionlib_dir: - actionlib_dir = local_actionlib_dir - - if actionlib_name not in prefs.filepaths.asset_libraries: - bpy.ops.preferences.asset_library_add(directory=str(actionlib_dir)) - - #lib, lib_id = get_lib_id( - # library_path=actionlib_dir, - # asset_libraries=prefs.filepaths.asset_libraries - #) - - #if not lib: - # print(f'Cannot set dir for {actionlib_name}') - # return - - prefs.filepaths.asset_libraries[lib_id].name = actionlib_name - #prefs.filepaths.asset_libraries[lib_id].path = str(actionlib_dir) - ''' - - - - - - - - - - - - diff --git a/common/library_cache.py b/common/library_cache.py deleted file mode 100644 index b8de562..0000000 --- a/common/library_cache.py +++ /dev/null @@ -1,381 +0,0 @@ -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})' - diff --git a/constants.py b/constants.py index afc57c2..b5db4bb 100644 --- a/constants.py +++ b/constants.py @@ -14,11 +14,9 @@ ASSETLIB_FILENAME = "blender_assets.libs.json" MODULE_DIR = Path(__file__).parent RESOURCES_DIR = MODULE_DIR / 'resources' -LIBRARY_TYPE_DIR = MODULE_DIR / 'library_types' -LIBRARY_TYPES = [] - -ADAPTER_DIR = MODULE_DIR / 'adapters' -ADAPTERS = [] +PLUGINS_DIR = MODULE_DIR / 'plugins' +PLUGINS = set() +PLUGINS_ITEMS = [('NONE', 'None', '', 0)] PREVIEW_ASSETS_SCRIPT = MODULE_DIR / 'common' / 'preview_assets.py' diff --git a/adapters/__init__.py b/core/__init__.py similarity index 100% rename from adapters/__init__.py rename to core/__init__.py diff --git a/core/asset_library_utils.py b/core/asset_library_utils.py new file mode 100644 index 0000000..c1c7560 --- /dev/null +++ b/core/asset_library_utils.py @@ -0,0 +1,54 @@ +""" +Util function for this addon +""" + +import bpy +from . bl_utils import get_addon_prefs + + +def thumbnail_blend_file(input_blend, output_img): + input_blend = Path(input_blend).resolve() + output_img = Path(output_img).resolve() + + print(f'Thumbnailing {input_blend} to {output_img}') + blender_thumbnailer = Path(bpy.app.binary_path).parent / 'blender-thumbnailer' + + output_img.parent.mkdir(exist_ok=True, parents=True) + + subprocess.call([blender_thumbnailer, str(input_blend), str(output_img)]) + + success = output_img.exists() + + if not success: + empty_preview = RESOURCES_DIR / 'empty_preview.png' + shutil.copy(str(empty_preview), str(output_img)) + + return success + + +def get_active_library(): + '''Get the pref library properties from the active library of the asset browser''' + prefs = get_addon_prefs() + lib_ref = bpy.context.space_data.params.asset_library_reference + + #Check for merged library + for l in prefs.libraries: + if l.name == lib_ref: + return l + + +def update_library_path(self, context): + """Removing all asset libraries and recreate them""" + + addon_prefs = get_addon_prefs() + libs = context.preferences.filepaths.asset_libraries + + for i, lib in reversed(list(enumerate(libs))): + if (addon_lib := addon_prefs.libraries.get(lib.name)) and addon_lib.path == lib.path: + bpy.ops.preferences.asset_library_remove(index=i) + + for addon_lib in addon_prefs.libraries: + if not addon_lib.use: + continue + bpy.ops.preferences.asset_library_add(directory=str(addon_lib.path)) + libs[-1].name = addon_lib.name \ No newline at end of file diff --git a/common/bl_utils.py b/core/bl_utils.py similarity index 95% rename from common/bl_utils.py rename to core/bl_utils.py index 07c1082..5e8358c 100644 --- a/common/bl_utils.py +++ b/core/bl_utils.py @@ -10,7 +10,7 @@ Datablock = Any import bpy from bpy_extras import asset_utils -from asset_library.constants import RESOURCES_DIR +#from asset_library.constants import RESOURCES_DIR #from asset_library.common.file_utils import no from os.path import abspath import subprocess @@ -48,6 +48,20 @@ class attr_set(): for prop, attr, old_val in self.store: setattr(prop, attr, old_val) + +def unique_name(name, names): + if name not in names: + return name + + i = 1 + org_name = name + while name in names: + name = f'{org_name}.{i:03d}' + i += 1 + + return name + + def get_overriden_col(ob, scene=None): scn = scene or bpy.context.scene @@ -56,12 +70,14 @@ def get_overriden_col(ob, scene=None): return next((c for c in cols if ob in c.all_objects[:] if all(not c.override_library for c in get_col_parents(c))), None) + def get_view3d_persp(): windows = bpy.context.window_manager.windows view_3ds = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D'] view_3d = next((a for a in view_3ds if a.spaces.active.region_3d.view_perspective == 'PERSP'), view_3ds[0]) return view_3d + def get_viewport(): screen = bpy.context.screen @@ -89,6 +105,7 @@ def biggest_asset_browser_area(screen: bpy.types.Screen) -> Optional[bpy.types.A return max(areas, key=area_sorting_key) + def suitable_areas(screen: bpy.types.Screen) -> Iterable[bpy.types.Area]: """Generator, yield Asset Browser areas.""" @@ -98,6 +115,7 @@ def suitable_areas(screen: bpy.types.Screen) -> Iterable[bpy.types.Area]: continue yield area + def area_from_context(context: bpy.types.Context) -> Optional[bpy.types.Area]: """Return an Asset Browser suitable for the given category. @@ -122,6 +140,7 @@ def area_from_context(context: bpy.types.Context) -> Optional[bpy.types.Area]: return None + def activate_asset( asset: bpy.types.Action, asset_browser: bpy.types.Area, *, deferred: bool ) -> None: @@ -131,20 +150,24 @@ def activate_asset( assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data) space_data.activate_asset_by_id(asset, deferred=deferred) + def active_catalog_id(asset_browser: bpy.types.Area) -> str: """Return the ID of the catalog shown in the asset browser.""" return params(asset_browser).catalog_id + def get_asset_space_params(asset_browser: bpy.types.Area) -> bpy.types.FileAssetSelectParams: """Return the asset browser parameters given its Area.""" space_data = asset_browser.spaces[0] assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data) return space_data.params + def refresh_asset_browsers(): for area in suitable_areas(bpy.context.screen): bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]}) + def tag_redraw(screen: bpy.types.Screen) -> None: """Tag all asset browsers for redrawing.""" @@ -186,11 +209,13 @@ def norm_value(value): value = json.dumps(value) return value + def norm_arg(arg_name, format=str.lower, prefix='--', separator='-'): arg_name = norm_str(arg_name, format=format, separator=separator) return prefix + arg_name + def get_bl_cmd(blender=None, background=False, focus=True, blendfile=None, script=None, **kargs): cmd = [str(blender)] if blender else [bpy.app.binary_path] @@ -223,31 +248,12 @@ def get_bl_cmd(blender=None, background=False, focus=True, blendfile=None, scrip return cmd + def get_addon_prefs(): addon_name = __package__.split('.')[0] return bpy.context.preferences.addons[addon_name].preferences - -def thumbnail_blend_file(input_blend, output_img): - input_blend = Path(input_blend).resolve() - output_img = Path(output_img).resolve() - - print(f'Thumbnailing {input_blend} to {output_img}') - blender_thumbnailer = Path(bpy.app.binary_path).parent / 'blender-thumbnailer' - - output_img.parent.mkdir(exist_ok=True, parents=True) - - subprocess.call([blender_thumbnailer, str(input_blend), str(output_img)]) - - success = output_img.exists() - - if not success: - empty_preview = RESOURCES_DIR / 'empty_preview.png' - shutil.copy(str(empty_preview), str(output_img)) - - return success - def get_col_parents(col, root=None, cols=None): '''Return a list of parents collections of passed col root : Pass a collection to search in (recursive) @@ -267,6 +273,7 @@ def get_col_parents(col, root=None, cols=None): cols = get_col_parents(col, root=sub, cols=cols) return cols + def get_overriden_col(ob, scene=None): '''Get the collection use for making the override''' scn = scene or bpy.context.scene @@ -276,6 +283,7 @@ def get_overriden_col(ob, scene=None): return next((c for c in cols if ob in c.all_objects[:] if all(not c.override_library for c in get_col_parents(c))), None) + def load_assets_from(filepath: Path) -> List[Datablock]: if not has_assets(filepath): # Avoid loading any datablocks when there are none marked as asset. @@ -306,6 +314,7 @@ def load_assets_from(filepath: Path) -> List[Datablock]: loaded_assets.append(datablock) return loaded_assets + def has_assets(filepath: Path) -> bool: with bpy.data.libraries.load(str(filepath), assets_only=True) as ( data_from, @@ -318,17 +327,13 @@ def has_assets(filepath: Path) -> bool: return False - - - - - def copy_frames(start, end, offset, path): for i in range (start, end): src = path.replace('####', f'{i:04d}') dst = src.replace(src.split('_')[-1].split('.')[0], f'{i+offset:04d}') shutil.copy2(src, dst) + def split_path(path) : try : bone_name = path.split('["')[1].split('"]')[0] diff --git a/common/catalog.py b/core/catalog.py similarity index 100% rename from common/catalog.py rename to core/catalog.py diff --git a/common/file_utils.py b/core/file_utils.py similarity index 100% rename from common/file_utils.py rename to core/file_utils.py diff --git a/common/template.py b/core/template.py similarity index 100% rename from common/template.py rename to core/template.py diff --git a/library_types/data_file.py b/data_type/__init__.py similarity index 100% rename from library_types/data_file.py rename to data_type/__init__.py diff --git a/action/__init__.py b/data_type/action/__init__.py similarity index 100% rename from action/__init__.py rename to data_type/action/__init__.py diff --git a/action/_render_preview.py b/data_type/action/_render_preview.py similarity index 100% rename from action/_render_preview.py rename to data_type/action/_render_preview.py diff --git a/action/clear_asset.py b/data_type/action/clear_asset.py similarity index 100% rename from action/clear_asset.py rename to data_type/action/clear_asset.py diff --git a/action/concat_preview.py b/data_type/action/concat_preview.py similarity index 100% rename from action/concat_preview.py rename to data_type/action/concat_preview.py diff --git a/action/functions.py b/data_type/action/functions.py similarity index 100% rename from action/functions.py rename to data_type/action/functions.py diff --git a/action/gui.py b/data_type/action/gui.py similarity index 100% rename from action/gui.py rename to data_type/action/gui.py diff --git a/action/keymaps.py b/data_type/action/keymaps.py similarity index 100% rename from action/keymaps.py rename to data_type/action/keymaps.py diff --git a/action/operators.py b/data_type/action/operators.py similarity index 100% rename from action/operators.py rename to data_type/action/operators.py diff --git a/action/preview.blend b/data_type/action/preview.blend similarity index 100% rename from action/preview.blend rename to data_type/action/preview.blend diff --git a/action/properties.py b/data_type/action/properties.py similarity index 100% rename from action/properties.py rename to data_type/action/properties.py diff --git a/action/rename_pose.py b/data_type/action/rename_pose.py similarity index 100% rename from action/rename_pose.py rename to data_type/action/rename_pose.py diff --git a/collection/__init__.py b/data_type/collection/__init__.py similarity index 100% rename from collection/__init__.py rename to data_type/collection/__init__.py diff --git a/collection/build_collection_blends.py b/data_type/collection/build_collection_blends.py similarity index 100% rename from collection/build_collection_blends.py rename to data_type/collection/build_collection_blends.py diff --git a/collection/create_collection_library.py b/data_type/collection/create_collection_library.py similarity index 100% rename from collection/create_collection_library.py rename to data_type/collection/create_collection_library.py diff --git a/collection/gui.py b/data_type/collection/gui.py similarity index 100% rename from collection/gui.py rename to data_type/collection/gui.py diff --git a/collection/keymaps.py b/data_type/collection/keymaps.py similarity index 100% rename from collection/keymaps.py rename to data_type/collection/keymaps.py diff --git a/collection/operators.py b/data_type/collection/operators.py similarity index 100% rename from collection/operators.py rename to data_type/collection/operators.py diff --git a/collection/preview.blend b/data_type/collection/preview.blend similarity index 100% rename from collection/preview.blend rename to data_type/collection/preview.blend diff --git a/file/__init__.py b/data_type/file/__init__.py similarity index 100% rename from file/__init__.py rename to data_type/file/__init__.py diff --git a/file/bundle.py b/data_type/file/bundle.py similarity index 100% rename from file/bundle.py rename to data_type/file/bundle.py diff --git a/file/gui.py b/data_type/file/gui.py similarity index 100% rename from file/gui.py rename to data_type/file/gui.py diff --git a/file/keymaps.py b/data_type/file/keymaps.py similarity index 100% rename from file/keymaps.py rename to data_type/file/keymaps.py diff --git a/file/operators.py b/data_type/file/operators.py similarity index 100% rename from file/operators.py rename to data_type/file/operators.py diff --git a/common/preview.blend b/data_type/file/preview.blend similarity index 100% rename from common/preview.blend rename to data_type/file/preview.blend diff --git a/pose/__init__.py b/data_type/pose/__init__.py similarity index 100% rename from pose/__init__.py rename to data_type/pose/__init__.py diff --git a/pose/conversion.py b/data_type/pose/conversion.py similarity index 100% rename from pose/conversion.py rename to data_type/pose/conversion.py diff --git a/pose/operators.py b/data_type/pose/operators.py similarity index 100% rename from pose/operators.py rename to data_type/pose/operators.py diff --git a/pose/pose_creation.py b/data_type/pose/pose_creation.py similarity index 100% rename from pose/pose_creation.py rename to data_type/pose/pose_creation.py diff --git a/pose/pose_usage.py b/data_type/pose/pose_usage.py similarity index 100% rename from pose/pose_usage.py rename to data_type/pose/pose_usage.py diff --git a/file/preview.blend b/file/preview.blend deleted file mode 100644 index a92321b..0000000 Binary files a/file/preview.blend and /dev/null differ diff --git a/gui.py b/gui.py deleted file mode 100644 index 3d81c31..0000000 --- a/gui.py +++ /dev/null @@ -1,333 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - - -""" -Action Library - GUI definition. -""" - -import bpy -from pathlib import Path - -from bpy.types import ( - AssetHandle, - Context, - Header, - Menu, - Panel, - UIList, - WindowManager, - WorkSpace, -) - -from bpy_extras import asset_utils -from asset_library.common.bl_utils import ( - get_addon_prefs, - get_object_libraries, -) - -from asset_library.common.functions import ( - get_active_library -) - - - -def pose_library_panel_poll(): - return bpy.context.object and bpy.context.object.mode == 'POSE' - -class PoseLibraryPanel: - @classmethod - def pose_library_panel_poll(cls, context: Context) -> bool: - return bool( - context.object - and context.object.mode == 'POSE' - ) - - @classmethod - def poll(cls, context: Context) -> bool: - return cls.pose_library_panel_poll(context); - - -class AssetLibraryMenu: - @classmethod - def poll(cls, context): - from bpy_extras.asset_utils import SpaceAssetInfo - return SpaceAssetInfo.is_asset_browser_poll(context) - - -class ASSETLIB_PT_libraries(Panel): - bl_label = "Libraries" - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = 'Item' - - @classmethod - def poll(cls, context: Context) -> bool: - return context.object and get_object_libraries(context.object) - - def draw(self, context: Context) -> None: - layout = self.layout - - for f in get_object_libraries(context.object): - row = layout.row(align=True) - row.label(text=f) - row.operator("assetlib.open_blend", icon='FILE_BLEND', text='').filepath = f - -''' -class ASSETLIB_PT_pose_library_usage(Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = "TOOLS" - bl_label = "Action Library" - # asset_categories = {'ANIMATIONS'} - - @classmethod - def poll(cls, context: Context) -> bool: - sp = context.space_data - - if not context.object or not context.object.mode == 'POSE': - return False - - if not (sp and sp.type == 'FILE_BROWSER' and sp.browse_mode == 'ASSETS'): - return False - - return True - - def draw(self, context: Context) -> None: - layout = self.layout - wm = context.window_manager - - sp = context.space_data - sp.params.asset_library_ref - - if sp.params.asset_library_ref == 'LOCAL': - col = layout.column(align=True) - row = col.row(align=True) - row.operator("poselib.create_pose_asset", text="Create Pose", icon='POSE_HLT').activate_new_action = False - row.operator("actionlib.replace_pose", text='Replace Pose', icon='FILE_REFRESH') - col.operator("actionlib.create_anim_asset", text="Create Anim", icon='ANIM') - - col.separator() - row = col.row(align=True) - row.operator("actionlib.edit_action", text='Edit Action', icon='ACTION') - row.operator("actionlib.clear_action", text='Finish Edit', icon='CHECKBOX_HLT') - - col.separator() - col.operator("actionlib.generate_preview", icon='RESTRICT_RENDER_OFF', text="Generate Thumbnail") - col.operator("actionlib.update_action_data", icon='FILE_TEXT', text="Update Action Data") - else: - col = layout.column(align=True) - row = col.row(align=True) - row.operator("actionlib.store_anim_pose", text='Store Anim/Pose', icon='ACTION') -''' - - -class ASSETLIB_PT_pose_library_editing(PoseLibraryPanel, asset_utils.AssetBrowserPanel, Panel): - bl_space_type = 'FILE_BROWSER' - bl_region_type = "TOOL_PROPS" - bl_label = "Metadata" - #bl_options = {'HIDE_HEADER'} - # asset_categories = {'ANIMATIONS'} - - @classmethod - def poll(cls, context: Context) -> bool: - sp = context.space_data - - if not (sp and sp.type == 'FILE_BROWSER' and sp.browse_mode == 'ASSETS'): - return False - - if not (context.active_file and context.active_file.asset_data): - return False - - return True - - def draw(self, context: Context) -> None: - layout = self.layout - - layout.use_property_split = True - asset_data = context.active_file.asset_data - metadata = ['camera', 'is_single_frame', 'rest_pose'] - - if 'camera' in asset_data.keys(): - layout.prop(asset_data, f'["camera"]', text='Camera', icon='CAMERA_DATA') - if 'is_single_frame' in asset_data.keys(): - layout.prop(asset_data, f'["is_single_frame"]', text='Is Single Frame') - if 'rest_pose' in asset_data.keys(): - layout.prop(asset_data, f'["rest_pose"]', text='Rest Pose', icon='ACTION') - if 'filepath' in asset_data.keys(): - layout.prop(asset_data, f'["filepath"]', text='Filepath') - - -class ASSETLIB_MT_context_menu(AssetLibraryMenu, Menu): - bl_label = "Asset Library Menu" - - @classmethod - def poll(cls, context): - if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data): - cls.poll_message_set("Current editor is not an asset browser") - return False - - prefs = get_addon_prefs() - asset_lib_ref = context.space_data.params.asset_library_ref - - lib = get_active_library() - if not lib: - return False - - return True - - def draw(self, context): - lib = get_active_library() - lib.library_type.draw_context_menu(self.layout) - - -def is_option_region_visible(context, space): - from bpy_extras.asset_utils import SpaceAssetInfo - - if SpaceAssetInfo.is_asset_browser(space): - pass - # For the File Browser, there must be an operator for there to be options - # (irrelevant for the Asset Browser). - elif not space.active_operator: - return False - - for region in context.area.regions: - if region.type == 'TOOL_PROPS' and region.width <= 1: - return False - - return True - - -def draw_assetbrowser_header(self, context): - lib = get_active_library() - - if not lib: - bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons(self, context) - return - - space_data = context.space_data - params = context.space_data.params - - row = self.layout.row(align=True) - row.separator() - - row.operator("assetlib.bundle", icon='UV_SYNC_SELECT', text='').name = lib.name - #op - #op.clean = False - #op.only_recent = True - - lib.library_type.draw_header(row) - - if context.selected_files and context.active_file: - row.separator() - row.label(text=context.active_file.name) - - row.separator_spacer() - - sub = row.row() - sub.ui_units_x = 10 - sub.prop(params, "filter_search", text="", icon='VIEWZOOM') - - row.separator_spacer() - - row.prop_with_popover( - params, - "display_type", - panel="ASSETBROWSER_PT_display", - text="", - icon_only=True, - ) - - row.operator( - "screen.region_toggle", - text="", - icon='PREFERENCES', - depress=is_option_region_visible(context, space_data) - ).region_type = 'TOOL_PROPS' - - -### Messagebus subscription to monitor asset library changes. -_msgbus_owner = object() - -def _on_asset_library_changed() -> None: - """Update areas when a different asset library is selected.""" - refresh_area_types = {'DOPESHEET_EDITOR', 'VIEW_3D'} - for win in bpy.context.window_manager.windows: - for area in win.screen.areas: - if area.type not in refresh_area_types: - continue - - area.tag_redraw() - -def register_message_bus() -> None: - - bpy.msgbus.subscribe_rna( - key=(bpy.types.FileAssetSelectParams, "asset_library_ref"), - owner=_msgbus_owner, - args=(), - notify=_on_asset_library_changed, - options={'PERSISTENT'}, - ) - -def unregister_message_bus() -> None: - bpy.msgbus.clear_by_owner(_msgbus_owner) - -@bpy.app.handlers.persistent -def _on_blendfile_load_pre(none, other_none) -> None: - # The parameters are required, but both are None. - unregister_message_bus() - -@bpy.app.handlers.persistent -def _on_blendfile_load_post(none, other_none) -> None: - # The parameters are required, but both are None. - register_message_bus() - - -classes = ( - ASSETLIB_PT_pose_library_editing, - #ASSETLIB_PT_pose_library_usage, - ASSETLIB_MT_context_menu, - ASSETLIB_PT_libraries -) - - -def register() -> None: - for cls in classes: - bpy.utils.register_class(cls) - - bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons = bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons - bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons = draw_assetbrowser_header - - #WorkSpace.active_pose_asset_index = bpy.props.IntProperty( - # name="Active Pose Asset", - # # TODO explain which list the index belongs to, or how it can be used to get the pose. - # description="Per workspace index of the active pose asset" - #) - # Register for window-manager. This is a global property that shouldn't be - # written to files. - #WindowManager.pose_assets = bpy.props.CollectionProperty(type=AssetHandle) - - # bpy.types.UI_MT_list_item_context_menu.prepend(pose_library_list_item_context_menu) - # bpy.types.ASSETLIB_MT_context_menu.prepend(pose_library_list_item_context_menu) - # bpy.types.ACTIONLIB_MT_context_menu.prepend(pose_library_list_item_context_menu) - #bpy.types.ASSETBROWSER_MT_editor_menus.append(draw_assetbrowser_header) - - register_message_bus() - bpy.app.handlers.load_pre.append(_on_blendfile_load_pre) - bpy.app.handlers.load_post.append(_on_blendfile_load_post) - - -def unregister() -> None: - for cls in reversed(classes): - bpy.utils.unregister_class(cls) - - bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons = bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons - del bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons - - unregister_message_bus() - - #del WorkSpace.active_pose_asset_index - #del WindowManager.pose_assets - - # bpy.types.UI_MT_list_item_context_menu.remove(pose_library_list_item_context_menu) - # bpy.types.ASSETLIB_MT_context_menu.remove(pose_library_list_item_context_menu) - # bpy.types.ACTIONLIB_MT_context_menu.remove(pose_library_list_item_context_menu) - #bpy.types.ASSETBROWSER_MT_editor_menus.remove(draw_assetbrowser_header) diff --git a/keymaps.py b/keymaps.py deleted file mode 100644 index 27b875f..0000000 --- a/keymaps.py +++ /dev/null @@ -1,58 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later - - -from typing import List, Tuple - -import bpy -from bpy.app.handlers import persistent - -addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = [] - - -@persistent -def copy_play_anim(dummy): - wm = bpy.context.window_manager - km = wm.keyconfigs.addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER") - - km_frames = wm.keyconfigs.user.keymaps.get('Frames') - if km_frames: - play = km_frames.keymap_items.get('screen.animation_play') - if play: - kmi = km.keymap_items.new( - "assetlib.play_preview", - play.type, play.value, - any=play.any, shift=play.shift, ctrl=play.ctrl, alt=play.alt, - oskey=play.oskey, key_modifier=play.key_modifier, - ) - addon_keymaps.append((km, kmi)) - - -def register() -> None: - wm = bpy.context.window_manager - if wm.keyconfigs.addon is None: - # This happens when Blender is running in the background. - return - - km = wm.keyconfigs.addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER") - - kmi = km.keymap_items.new("wm.call_menu", "RIGHTMOUSE", "PRESS") - kmi.properties.name = 'ASSETLIB_MT_context_menu' - addon_keymaps.append((km, kmi)) - - kmi = km.keymap_items.new("assetlib.play_preview", "SPACE", "PRESS") - addon_keymaps.append((km, kmi)) - - # km = addon.keymaps.new(name = "Grease Pencil Stroke Paint Mode", space_type = "EMPTY") - # kmi = km.keymap_items.new('wm.call_panel', type='F2', value='PRESS') - - if 'copy_play_anim' not in [hand.__name__ for hand in bpy.app.handlers.load_post]: - bpy.app.handlers.load_post.append(copy_play_anim) - -def unregister() -> None: - # Clear shortcuts from the keymap. - if 'copy_play_anim' in [hand.__name__ for hand in bpy.app.handlers.load_post]: - bpy.app.handlers.load_post.remove(copy_play_anim) - - for km, kmi in addon_keymaps: - km.keymap_items.remove(kmi) - addon_keymaps.clear() \ No newline at end of file diff --git a/library_types/__init__.py b/library_types/__init__.py deleted file mode 100644 index 9edfe9b..0000000 --- a/library_types/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ - -from asset_library.library_types import library_type -from asset_library.library_types import copy_folder -from asset_library.library_types import scan_folder - -if 'bpy' in locals(): - import importlib - - importlib.reload(library_type) - importlib.reload(copy_folder) - importlib.reload(scan_folder) - -import bpy - -LibraryType = library_type.LibraryType -CopyFolder = copy_folder.CopyFolder -ScanFolder = scan_folder.ScanFolder diff --git a/operators.py b/operators.py index 03b1681..c133d60 100644 --- a/operators.py +++ b/operators.py @@ -1,344 +1,76 @@ - -from typing import Set -#import shutil -from pathlib import Path -import subprocess import importlib -import time -import json - import bpy -from bpy_extras import asset_utils -from bpy.types import Context, Operator -from bpy.props import ( - BoolProperty, - EnumProperty, - StringProperty, - IntProperty) -#from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS, MODULE_DIR) -import asset_library -from asset_library.common.bl_utils import ( - attr_set, - get_addon_prefs, - get_bl_cmd, - get_view3d_persp, - #suitable_areas, - refresh_asset_browsers, - load_datablocks) - -from asset_library.common.file_utils import open_blender_file, synchronize -from asset_library.common.functions import get_active_library, asset_warning_callback - -from textwrap import dedent -from tempfile import gettempdir -import gpu -from gpu_extras.batch import batch_for_shader -import blf -import bgl +from bpy.types import Operator +from bpy.props import (BoolProperty, EnumProperty, StringProperty, IntProperty) +from .core.bl_utils import get_addon_prefs, unique_name -class ASSETLIB_OT_remove_assets(Operator): - bl_idname = "assetlib.remove_assets" - bl_options = {"REGISTER", "UNDO", "INTERNAL"} - bl_label = 'Remove Assets' - bl_description = 'Remove Selected Assets' +class ASSETLIB_OT_reload_addon(Operator): + bl_idname = "assetlibrary.reload_addon" + bl_options = {"UNDO"} + bl_label = 'Reload Asset Library Addon' + bl_description = 'Reload The Asset Library Addon and the addapters' - @classmethod - def poll(cls, context): - if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data): - return False + def execute(self, context): - sp = context.space_data - if sp.params.asset_library_ref == 'LOCAL': - return False + print('Execute reload', __package__) - return True + addon = importlib.import_module(__package__) - def execute(self, context: Context) -> Set[str]: - asset = context.active_file - - lib = get_active_library() - lib_type = lib.library_type - - catalog = lib.read_catalog() - - if not catalog.context.item: - self.report({'ERROR'}, 'The active asset is not in the catalog') - return {'CANCELLED'} - - asset_name = context.asset_file_handle.name - asset_path = lib_type.format_path(asset.asset_data['filepath']) - asset_catalog = catalog.context.path - - img_path = lib_type.get_image_path(name=asset_name, catalog=asset_catalog, filepath=asset_path) - video_path = lib_type.get_video_path(name=asset_name, catalog=asset_catalog, filepath=asset_path) - - if asset_path and asset_path.exists(): - asset_path.unlink() - if img_path and img_path.exists(): - img_path.unlink() - if video_path and video_path.exists(): - video_path.unlink() - #open_blender_file(filepath) - - try: - asset_path.parent.rmdir() - except Exception:#Directory not empty - pass - - bpy.ops.assetlib.bundle(name=lib.name, blocking=True) + addon.unregister() + importlib.reload(addon) + for mod in addon.modules: + importlib.reload(mod) + addon.register() return {'FINISHED'} -class ASSETLIB_OT_edit_data(Operator): - bl_idname = "assetlib.edit_data" - bl_label = "Edit Asset Data" - bl_description = "Edit Current Asset Data" +class ASSETLIB_OT_remove_library(Operator): + bl_idname = "assetlibrary.remove_library" bl_options = {"REGISTER", "UNDO"} - - warning: StringProperty(name='') - path: StringProperty(name='Path') - catalog: StringProperty(name='Catalog', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'}) - name: StringProperty(name='Name', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'}) - tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)') - description: StringProperty(name='Description') - - @classmethod - def poll(cls, context): - if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data): - return False - return True - - def execute(self, context: Context) -> Set[str]: - prefs = get_addon_prefs() - - lib = get_active_library() - if lib.merge_libraries: - lib = prefs.libraries[lib.store_library] - - new_name = lib.library_type.norm_file_name(self.name) - new_asset_path = lib.library_type.get_asset_path(name=new_name, catalog=self.catalog) - - #asset_data = lib.library_type.get_asset_data(self.asset) - asset_data = dict( - tags=[t.strip() for t in self.tags.split(',') if t], - description=self.description, - ) - - #lib.library_type.set_asset_catalog(asset, asset_data, catalog_data) - self.asset.name = self.name - lib.library_type.set_asset_tags(self.asset, asset_data) - lib.library_type.set_asset_info(self.asset, asset_data) - - self.old_asset_path.unlink() - lib.library_type.write_asset(asset=self.asset, asset_path=new_asset_path) - - if self.old_image_path.exists(): - new_img_path = lib.library_type.get_image_path(new_name, self.catalog, new_asset_path) - self.old_image_path.rename(new_img_path) - - if self.old_video_path.exists(): - new_video_path = lib.library_type.get_video_path(new_name, self.catalog, new_asset_path) - self.old_video_path.rename(new_video_path) - - #if self.old_description_path.exists(): - # self.old_description_path.unlink() - - try: - self.old_asset_path.parent.rmdir() - except Exception: #The folder is not empty - pass - - diff_path = Path(bpy.app.tempdir, 'diff.json') - diff = [dict(name=self.old_asset_name, catalog=self.old_catalog, filepath=str(self.old_asset_path), operation='REMOVE')] - - asset_data = lib.library_type.get_asset_data(self.asset) - diff += [dict(asset_data, - image=str(new_img_path), - filepath=str(new_asset_path), - type=lib.data_type, - library_id=lib.id, - catalog=self.catalog, - operation='ADD' - )] - - print(diff) - - diff_path.write_text(json.dumps(diff, indent=4), encoding='utf-8') - - bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True) - - return {"FINISHED"} - - def draw(self, context): - layout = self.layout - layout.separator() - - layout.use_property_split = True - - lib = get_active_library() - - if lib.merge_libraries: - layout.prop(lib, 'store_library', expand=False) - - layout.prop(self, "catalog", text="Catalog") - layout.prop(self, "name", text="Name") - layout.prop(self, 'tags') - layout.prop(self, 'description') - - #layout.prop() - - layout.separator() - col = layout.column() - col.use_property_split = False - #row.enabled = False - - if self.path: - col.label(text=self.path) - - if self.warning: - col.label(icon='ERROR', text=self.warning) - - def invoke(self, context, event): - - lib = get_active_library() - - active_lib = lib.library_type.get_active_asset_library() - - lib.store_library = active_lib.name - - asset_handle = context.asset_file_handle - - catalog_file = lib.library_type.read_catalog() - catalog_ids = {v['id']: {'path': k, 'name': v['name']} for k,v in catalog_file.items()} - - #asset_handle = context.asset_file_handle - self.old_asset_name = asset_handle.name - self.old_asset_path = lib.library_type.get_active_asset_path() - - self.asset = load_datablocks(self.old_asset_path, self.old_asset_name, type=lib.data_types) - - if not self.asset: - self.report({'ERROR'}, 'No asset found') - - self.name = self.old_asset_name - self.description = asset_handle.asset_data.description - - tags = [t.strip() for t in self.asset.asset_data.tags.keys() if t] - self.tags = ', '.join(tags) - #asset_path - self.old_catalog = catalog_ids[asset_handle.asset_data.catalog_id]['path'] - self.catalog = self.old_catalog - - self.old_image_path = lib.library_type.get_image_path(name=self.name, catalog=self.catalog, filepath=self.old_asset_path) - self.old_video_path = lib.library_type.get_video_path(name=self.name, catalog=self.catalog, filepath=self.old_asset_path) - - #self.old_description_path = lib.library_type.get_description_path(self.old_asset_path) - - #self.old_asset_info = lib.library_type.read_asset_info_file(self.old_asset_path) - #self.old_asset_info = lib.library_type.norm_asset_datas([self.old_asset_info])[0] - - - - - - return context.window_manager.invoke_props_dialog(self, width=450) - - def cancel(self, context): - print('Cancel Edit Data, removing the asset') - - lib = get_active_library() - active_lib = lib.library_type.get_active_asset_library() - - getattr(bpy.data, active_lib.data_types).remove(self.asset) - -class ASSETLIB_OT_remove_user_library(Operator): - bl_idname = "assetlib.remove_user_library" - bl_options = {"REGISTER", "UNDO"} - bl_label = 'Remove User Library' - bl_description = 'Remove User Library' + bl_label = 'Remove Library' + bl_description = 'Remove Library' index : IntProperty(default=-1) - def execute(self, context: Context) -> Set[str]: + def execute(self, context): prefs = get_addon_prefs() - prefs.user_libraries.remove(self.index) + addon_lib = prefs.libraries[self.index] + bl_libs = context.preferences.filepaths.asset_libraries + if (bl_lib := bl_libs.get(addon_lib.name)) and bl_lib.path == addon_lib.path: + index = list(bl_libs).index(bl_lib) + bpy.ops.preferences.asset_library_remove(index=index) + + prefs.libraries.remove(self.index) return {'FINISHED'} -class ASSETLIB_OT_add_user_library(Operator): - bl_idname = "assetlib.add_user_library" +class ASSETLIB_OT_add_library(Operator): + bl_idname = "assetlibrary.add_library" bl_options = {"REGISTER", "UNDO"} - bl_label = 'Add User Library' - bl_description = 'Add User Library' + bl_label = 'Add Library' + bl_description = 'Add Library' - - def execute(self, context: Context) -> Set[str]: + def execute(self, context): prefs = get_addon_prefs() - lib = prefs.user_libraries.add() + lib = prefs.libraries.add() lib.expand = True + lib.name = unique_name('Asset Library', [l.name for l in prefs.libraries]) return {'FINISHED'} -class ASSETLIB_OT_open_blend(Operator): - bl_idname = "assetlib.open_blend" +class ASSETLIB_OT_synchronize(Operator): + bl_idname = "assetlibrary.synchronize" bl_options = {"REGISTER", "UNDO"} - bl_label = 'Open Blender File' - bl_description = 'Open blender file' - - #filepath : StringProperty(subtype='FILE_PATH') - - def execute(self, context: Context) -> Set[str]: - #asset = context.active_file - #prefs = get_addon_prefs() - - lib = get_active_library() - - #filepath = lib.library_type.format_path(asset.asset_data['filepath']) - - filepath = lib.library_type.get_active_asset_path() - - open_blender_file(filepath) - - return {'FINISHED'} - - -class ASSETLIB_OT_set_paths(Operator): - bl_idname = "assetlib.set_paths" - bl_options = {"REGISTER", "UNDO", "INTERNAL"} - bl_label = 'Set Paths' - bl_description = 'Set Library Paths' - - name: StringProperty() - all: BoolProperty(default=False) - - def execute(self, context: Context) -> Set[str]: - prefs = get_addon_prefs() - print('Set Paths') - if self.all: - libs = prefs.libraries - else: - libs = [prefs.libraries[self.name]] - - for lib in libs: - lib.clear_library_path() - lib.set_library_path() - - return {'FINISHED'} - - -class ASSETLIB_OT_bundle_library(Operator): - bl_idname = "assetlib.bundle" - bl_options = {"INTERNAL"} - bl_label = 'Bundle Library' - bl_description = 'Bundle all matching asset found inside one blend' + bl_label = 'Synchronize' + bl_description = 'Synchronize Action Lib to Local Directory' name : StringProperty() diff : StringProperty() @@ -351,7 +83,7 @@ class ASSETLIB_OT_bundle_library(Operator): # bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]}) #space_data.activate_asset_by_id(asset, deferred=deferred) - def execute(self, context: Context) -> Set[str]: + def execute(self, context): prefs = get_addon_prefs() libs = [] @@ -377,7 +109,7 @@ class ASSETLIB_OT_bundle_library(Operator): for lib_data in {lib_datas}: lib = prefs.env_libraries.add() lib.set_dict(lib_data) - lib.library_type.bundle(cache_diff='{self.diff}') + lib.plugin.bundle(cache_diff='{self.diff}') bpy.ops.wm.quit_blender() """) @@ -401,437 +133,19 @@ class ASSETLIB_OT_bundle_library(Operator): return {'FINISHED'} -class ASSETLIB_OT_reload_addon(Operator): - bl_idname = "assetlib.reload_addon" - bl_options = {"UNDO"} - bl_label = 'Reload Asset Library Addon' - bl_description = 'Reload The Asset Library Addon and the addapters' - - def execute(self, context: Context) -> Set[str]: - - print('Execute reload') - - asset_library.unregister() - importlib.reload(asset_library) - asset_library.register() - - return {'FINISHED'} - - -class ASSETLIB_OT_diff(Operator): - bl_idname = "assetlib.diff" - bl_options = {"REGISTER", "UNDO"} - bl_label = 'Synchronize' - bl_description = 'Synchronize Action Lib to Local Directory' - - name : StringProperty() - conform : BoolProperty(default=False) - - def execute(self, context: Context) -> Set[str]: - prefs = get_addon_prefs() - - lib = prefs.libraries.get(self.name) - lib.library_type.diff() - - return {'FINISHED'} - -''' -class ASSETLIB_OT_conform_library(Operator): - bl_idname = "assetlib.conform_library" - bl_options = {"REGISTER", "UNDO"} - bl_label = "Conform Library" - bl_description = "Split each assets per blend and externalize preview" - - name : StringProperty() - template_image : StringProperty() - template_video : StringProperty() - directory : StringProperty(subtype='DIR_PATH', name='Filepath') - - def execute(self, context: Context) -> Set[str]: - prefs = get_addon_prefs() - - lib = prefs.libraries.get(self.name) - #lib.library_type.conform(self.directory) - - templates = {} - if self.template_image: - templates['image'] = self.template_image - if self.template_video: - templates['video'] = self.template_video - - - script_path = Path(bpy.app.tempdir) / 'bundle_library.py' - script_code = dedent(f""" - import bpy - prefs = bpy.context.preferences.addons["asset_library"].preferences - lib = prefs.env_libraries.add() - lib.set_dict({lib.to_dict()}) - lib.library_type.conform(directory='{self.directory}', templates={templates}) - """) - - script_path.write_text(script_code) - - cmd = get_bl_cmd(script=str(script_path), background=True) - - subprocess.Popen(cmd) - - return {'FINISHED'} - - def invoke(self, context, event): - context.window_manager.fileselect_add(self) - return {'RUNNING_MODAL'} -''' - -class ASSETLIB_OT_make_custom_preview(Operator): - bl_idname = "assetlib.make_custom_preview" - bl_label = "Custom Preview" - bl_description = "Set a camera to preview an asset" - - image_size : IntProperty(default=512) - modal : BoolProperty(default=False) - - def modal(self, context, event): - if event.type in {'ESC'}: # Cancel - self.restore() - return {'CANCELLED'} - - elif event.type in {'RET', 'NUMPAD_ENTER'}: # Cancel - return self.execute(context) - #return {'FINISHED'} - - return {'PASS_THROUGH'} - - def execute(self, context): - - prefs = get_addon_prefs() - bpy.ops.render.opengl(write_still=True) - - img_path = context.scene.render.filepath - - #print('Load Image to previews') - prefs.previews.load(Path(img_path).stem, img_path, 'IMAGE') - #img = bpy.data.images.load(context.scene.render.filepath) - #img.update() - #img.preview_ensure() - - - #Copy the image with a new name - # render = bpy.data.images['Render Result'] - - # render_pixels = [0] * self.image_size * self.image_size * 4 - # render.pixels.foreach_get(render_pixels) - # img = bpy.data.images.new(name=img_name, width=self.image_size, height=self.image_size, is_data=True, alpha=True) - # img.pixels.foreach_set(render_pixels) - - #img.scale(128, 128) - #img.preview_ensure() - - # preview_size = render.size - - # pixels = [0] * preview_size[0] * preview_size[1] * 4 - # render.pixels.foreach_get(pixels) - - # image.preview.image_size = preview_size - # image.preview.image_pixels_float.foreach_set(pixels) - - - - self.restore() - - #self.is_running = False - prefs.preview_modal = False - - return {"FINISHED"} - - def restore(self): - print('RESTORE') - try: - bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') - except: - print('Failed remove handler') - pass - - bpy.data.objects.remove(self.camera) - self.attr_changed.restore() - - def draw_callback_px(self, context): - if context.space_data != self._space_data: - return - - dpi = context.preferences.system.dpi - - bg_color = (0.8, 0.1, 0.1, 0.5) - font_color = (1, 1, 1, 1) - text = f'Escape: Cancel Enter: Make Preview' - font_id = 0 - dim = blf.dimensions(font_id, text) - - #gpu.state.line_width_set(100) - # bgl.glLineWidth(100) - # self.shader_2d.bind() - # self.shader_2d.uniform_float("color", bg_color) - # self.screen_framing.draw(self.shader_2d) - - # # Reset - # gpu.state.line_width_set(1) - - # -dim[0]/2, +dim[1]/2 + 5 - - # Display Text - blf.color(font_id, *font_color) # unpack color - blf.position(font_id, context.region.width/2 -dim[0]/2, dim[1]/2 + 5, 0) - blf.size(font_id, 12, dpi) - blf.draw(font_id, f'Escape: Cancel Enter: Make Preview') - - def get_image_name(self): - prefs = get_addon_prefs() - preview_names = [p for p in prefs.previews.keys()] - preview_names.sort() - - index = 0 - if preview_names: - index = int(preview_names[-1][-2:]) + 1 - - return f'preview_{index:03d}' - - def invoke(self, context, event): - prefs = get_addon_prefs() - cam_data = bpy.data.cameras.new(name='Preview Camera') - self.camera = bpy.data.objects.new(name='Preview Camera', object_data=cam_data) - - #view_3d = get_view3d_persp() - - scn = context.scene - space = context.space_data - - matrix = space.region_3d.view_matrix.inverted() - if space.region_3d.view_perspective == 'CAMERA': - matrix = scn.camera.matrix_world - - self.camera.matrix_world = matrix - - img_name = self.get_image_name() - img_path = Path(bpy.app.tempdir, img_name).with_suffix('.webp') - - self.attr_changed = attr_set([ - (space.overlay, 'show_overlays', False), - (space.region_3d, 'view_perspective', 'CAMERA'), - (space.region_3d, 'view_camera_offset'), - (space.region_3d, 'view_camera_zoom'), - (space, 'lock_camera', True), - (space, 'show_region_ui', False), - (scn, 'camera', self.camera), - (scn.render, 'resolution_percentage', 100), - (scn.render, 'resolution_x', self.image_size), - (scn.render, 'resolution_y', self.image_size), - (scn.render, 'film_transparent', True), - (scn.render.image_settings, 'file_format', 'WEBP'), - (scn.render.image_settings, 'color_mode', 'RGBA'), - #(scn.render.image_settings, 'color_depth', '8'), - (scn.render, 'use_overwrite', True), - (scn.render, 'filepath', str(img_path)), - ]) - - bpy.ops.view3d.view_center_camera() - space.region_3d.view_camera_zoom -= 6 - space.region_3d.view_camera_offset[1] += 0.03 - - w, h = (context.region.width, context.region.height) - - self._space_data = context.space_data - - if self.modal: - prefs.preview_modal = True - - self.shader_2d = gpu.shader.from_builtin('2D_UNIFORM_COLOR') - self.screen_framing = batch_for_shader( - self.shader_2d, 'LINE_LOOP', {"pos": [(0,0), (0,h), (w,h), (w,0)]}) - - self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, (context,), 'WINDOW', 'POST_PIXEL') - context.window_manager.modal_handler_add(self) - - return {'RUNNING_MODAL'} - else: - return self.execute(context) - - -class ASSETLIB_OT_generate_previews(Operator): - bl_idname = "assetlib.generate_previews" - bl_options = {"REGISTER", "UNDO"} - bl_label = "Generate Previews" - bl_description = "Generate and write the image for assets" - - cache : StringProperty() - preview_blend : StringProperty() - name : StringProperty() - blocking : BoolProperty(default=True) - - def execute(self, context: Context) -> Set[str]: - prefs = get_addon_prefs() - lib = prefs.libraries.get(self.name) - # self.write_file(self.diff_file, self.diff) - - # preview_assets = [(a.asset_data['filepath'], self.data_types, a.name) for a in assets] - - # self.preview_assets_file.write_text(json.dumps(preview_assets), encoding='utf-8') - - # cmd = [ - # bpy.app.binary_path, '-b', '--use-system-env', - # '--python', str(PREVIEW_ASSETS_SCRIPT), '--', - # '--preview-blend', str(self.preview_blend), - # '--preview-assets-file', str(self.preview_assets_file) - # ] - # subprocess.call(cmd) - preview_blend = self.preview_blend or lib.library_type.preview_blend - - if not preview_blend or not Path(preview_blend).exists(): - preview_blend = MODULE_DIR / 'common' / 'preview.blend' - - script_path = Path(bpy.app.tempdir) / 'generate_previews.py' - script_code = dedent(f""" - import bpy - prefs = bpy.context.preferences.addons["asset_library"].preferences - lib = prefs.env_libraries.add() - lib.set_dict({lib.to_dict()}) - - bpy.ops.wm.open_mainfile(filepath='{preview_blend}', load_ui=True) - lib.library_type.generate_previews(cache='{self.cache}') - """) - - script_path.write_text(script_code) - - cmd = get_bl_cmd(script=str(script_path), background=True) - - if self.blocking: - subprocess.call(cmd) - else: - subprocess.Popen(cmd) - - - return {'FINISHED'} - - -class ASSETLIB_OT_play_preview(Operator): - bl_idname = "assetlib.play_preview" - bl_options = {"REGISTER", "UNDO", "INTERNAL"} - bl_label = 'Play Preview' - bl_description = 'Play Preview' - - @classmethod - def poll(cls, context: Context) -> bool: - if not context.active_file: - return False - - if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data): - cls.poll_message_set("Current editor is not an asset browser") - return False - - lib = get_active_library() - if not lib: - return False - - return True - - def execute(self, context: Context) -> Set[str]: - asset = context.active_file - prefs = get_addon_prefs() - - lib = get_active_library() - - #filepath = lib.library_type.format_path(asset.asset_data['filepath']) - asset_path = lib.library_type.get_active_asset_path() - - asset_image = lib.library_type.get_image(asset.name, asset_path) - asset_video = lib.library_type.get_video(asset.name, asset_path) - - if not asset_image and not asset_video: - self.report({'ERROR'}, f'Preview for {asset.name} not found.') - return {"CANCELLED"} - - if asset_video: - self.report({'INFO'}, f'Video found. {asset_video}.') - - if prefs.video_player: - subprocess.Popen([prefs.video_player, asset_video]) - else: - bpy.ops.wm.path_open(filepath=str(asset_video)) - else: - self.report({'INFO'}, f'Image found. {asset_image}.') - - if prefs.image_player: - subprocess.Popen([prefs.image_player, asset_image]) - else: - bpy.ops.wm.path_open(filepath=str(asset_image)) - - - return {"FINISHED"} - - -class ASSETLIB_OT_synchronize(Operator): - bl_idname = "assetlib.synchronize" - bl_options = {"REGISTER", "UNDO"} - bl_label = 'Synchronize' - bl_description = 'Synchronize Action Lib to Local Directory' - - clean : BoolProperty(default=False) - only_new : BoolProperty(default=False) - only_recent : BoolProperty(default=False) - name: StringProperty() - all: BoolProperty(default=False) - - def execute(self, context: Context) -> Set[str]: - - print('Not yet Implemented, have to be replace by Bundle instead') - return {'FINISHED'} - - prefs = get_addon_prefs() - print('Synchronize') - if self.all: - libs = prefs.libraries - else: - libs = [prefs.libraries.get(self.name)] - - for lib in libs: - if self.clean and Path(lib.path_local).exists(): - pass - print('To check first') - #shutil.rmtree(path_local) - - if not lib.path_local: - continue - - synchronize( - src=lib.path, - dst=lib.path_local, - only_new=self.only_new, - only_recent=self.only_recent - ) - - return {'FINISHED'} - classes = ( - ASSETLIB_OT_play_preview, - ASSETLIB_OT_open_blend, - ASSETLIB_OT_set_paths, - ASSETLIB_OT_synchronize, - ASSETLIB_OT_add_user_library, - ASSETLIB_OT_remove_user_library, - ASSETLIB_OT_diff, - ASSETLIB_OT_generate_previews, - ASSETLIB_OT_bundle_library, - ASSETLIB_OT_remove_assets, - ASSETLIB_OT_edit_data, - #ASSETLIB_OT_conform_library, ASSETLIB_OT_reload_addon, - ASSETLIB_OT_make_custom_preview + ASSETLIB_OT_add_library, + ASSETLIB_OT_remove_library, + ASSETLIB_OT_synchronize ) -def register(): - #bpy.types.UserAssetLibrary.is_env = False +def register(): for cls in classes: bpy.utils.register_class(cls) + def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000..a9a7056 --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,17 @@ + +from asset_library.plugins import plugin +from asset_library.plugins import copy_folder +from asset_library.plugins import scan_folder + +if 'bpy' in locals(): + import importlib + + importlib.reload(plugin) + importlib.reload(copy_folder) + importlib.reload(scan_folder) + +import bpy + +LibraryPlugin = plugin.LibraryPlugin +CopyFolder = copy_folder.CopyFolder +ScanFolder = scan_folder.ScanFolder diff --git a/library_types/conform.py b/plugins/conform.py similarity index 97% rename from library_types/conform.py rename to plugins/conform.py index dac0c95..1669d55 100644 --- a/library_types/conform.py +++ b/plugins/conform.py @@ -4,9 +4,9 @@ Plugin for making an asset library of all blender file found in a folder """ -from asset_library.library_types.scan_folder import ScanFolder -from asset_library.common.bl_utils import load_datablocks -from asset_library.common.template import Template +from .scan_folder import ScanFolder +from ..core.bl_utils import load_datablocks +from ..core.template import Template import bpy from bpy.props import (StringProperty, IntProperty, BoolProperty) diff --git a/library_types/copy_folder.py b/plugins/copy_folder.py similarity index 86% rename from library_types/copy_folder.py rename to plugins/copy_folder.py index c016fb0..3562f35 100644 --- a/library_types/copy_folder.py +++ b/plugins/copy_folder.py @@ -3,15 +3,18 @@ Adapter for making an asset library of all blender file found in a folder """ - -from asset_library.library_types.library_type import LibraryType -from asset_library.common.file_utils import copy_dir -from bpy.props import StringProperty from os.path import expandvars + import bpy +from bpy.props import StringProperty + +from .library_plugin import LibraryPlugin +from ..core.file_utils import copy_dir -class CopyFolder(LibraryType): + + +class CopyFolder(LibraryPlugin): """Copy library folder from a server to a local disk for better performance""" name = "Copy Folder" diff --git a/resources/asset_library_config.json b/plugins/data_file.py similarity index 100% rename from resources/asset_library_config.json rename to plugins/data_file.py diff --git a/library_types/kitsu.py b/plugins/kitsu.py similarity index 97% rename from library_types/kitsu.py rename to plugins/kitsu.py index 68c2f25..94111a1 100644 --- a/library_types/kitsu.py +++ b/plugins/kitsu.py @@ -3,13 +3,6 @@ Plugin for making an asset library of all blender file found in a folder """ - -from asset_library.library_types.library_type import LibraryType -from asset_library.common.template import Template -from asset_library.common.file_utils import install_module - -import bpy -from bpy.props import (StringProperty, IntProperty, BoolProperty) import re from pathlib import Path from itertools import groupby @@ -21,8 +14,15 @@ import urllib3 import traceback import time +import bpy +from bpy.props import (StringProperty, IntProperty, BoolProperty) -class Kitsu(LibraryType): +from .library_plugin import LibraryPlugin +from ..core.template import Template +from ..core.file_utils import install_module + + +class Kitsu(LibraryPlugin): name = "Kitsu" template_name : StringProperty() diff --git a/library_types/library_type.py b/plugins/library_plugin.py similarity index 95% rename from library_types/library_type.py rename to plugins/library_plugin.py index bd5034a..0e10c5e 100644 --- a/library_types/library_type.py +++ b/plugins/library_plugin.py @@ -1,42 +1,43 @@ -#from asset_library.common.functions import (norm_asset_datas,) -from asset_library.common.bl_utils import get_addon_prefs, load_datablocks -from asset_library.common.file_utils import read_file, write_file -from asset_library.common.template import Template -from asset_library.constants import (MODULE_DIR, RESOURCES_DIR) - -from asset_library import (action, collection, file) -from asset_library.common.library_cache import LibraryCacheDiff - -from bpy.types import PropertyGroup -from bpy.props import StringProperty -import bpy -from bpy_extras import asset_utils - -from itertools import groupby -from pathlib import Path -import shutil import os +import shutil import json import uuid import time -from functools import partial import subprocess +from pathlib import Path +from itertools import groupby +from functools import partial from glob import glob from copy import deepcopy +import bpy +from bpy_extras import asset_utils +from bpy.types import PropertyGroup +from bpy.props import StringProperty -class LibraryType(PropertyGroup): +#from asset_library.common.functions import (norm_asset_datas,) +from ..core.bl_utils import get_addon_prefs, load_datablocks +from ..core.file_utils import read_file, write_file +from ..core.template import Template +from ..constants import (MODULE_DIR, RESOURCES_DIR) + +from ..data_type import (action, collection, file) +#from asset_library.common.library_cache import LibraryCacheDiff + + + +class LibraryPlugin(PropertyGroup): #def __init__(self): - name = "Base Adapter" + #name = "Base Adapter" #library = None @property def library(self): prefs = self.addon_prefs for lib in prefs.libraries: - if lib.library_type == self: + if lib.plugin == self: return lib @property @@ -104,7 +105,7 @@ class LibraryType(PropertyGroup): return self.library.read_cache(filepath=filepath) def fetch(self): - raise Exception('This method need to be define in the library_type') + raise Exception('This method need to be define in the plugin') def norm_file_name(self, name): return name.replace(' ', '_') @@ -186,7 +187,7 @@ class LibraryType(PropertyGroup): if 'filepath' in asset_handle.asset_data: asset_path = asset_handle.asset_data['filepath'] - asset_path = lib.library_type.format_path(asset_path) + asset_path = lib.plugin.format_path(asset_path) else: asset_path = bpy.types.AssetHandle.get_full_library_path( asset_handle, bpy.context.asset_library_ref @@ -195,22 +196,22 @@ class LibraryType(PropertyGroup): return asset_path def generate_previews(self): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def get_image_path(self, name, catalog, filepath): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def get_video_path(self, name, catalog, filepath): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def new_asset(self, asset, asset_cache): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def remove_asset(self, asset, asset_cache): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def set_asset_preview(self, asset, asset_cache): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def format_asset_data(self, data): """Get a dict for use in template fields""" @@ -321,7 +322,7 @@ class LibraryType(PropertyGroup): # return write_file(cache_path, list(asset_infos)) def prop_rel_path(self, path, prop): - '''Get a filepath relative to a property of the library_type''' + '''Get a filepath relative to a property of the plugin''' field_prop = '{%s}/'%prop prop_value = getattr(self, prop) @@ -595,7 +596,7 @@ class LibraryType(PropertyGroup): # Write the cache in a temporary file for the generate preview script tmp_cache_file = cache.write(tmp=True) - bpy.ops.assetlib.generate_previews(name=self.library.name, cache=str(tmp_cache_file)) + bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file)) elif isinstance(cache_diff, (Path, str)): cache_diff = LibraryCacheDiff(cache_diff).read()#json.loads(Path(cache_diff).read_text(encoding='utf-8')) @@ -764,7 +765,7 @@ class LibraryType(PropertyGroup): # return list(new_cache.values()), cache_diff def draw_prefs(self, layout): - """Draw the options in the addon preference for this library_type""" + """Draw the options in the addon preference for this plugin""" annotations = self.__class__.__annotations__ for k, v in annotations.items(): diff --git a/library_types/poly_haven.py b/plugins/poly_haven.py similarity index 96% rename from library_types/poly_haven.py rename to plugins/poly_haven.py index 074fb0f..908740d 100644 --- a/library_types/poly_haven.py +++ b/plugins/poly_haven.py @@ -3,31 +3,31 @@ Plugin for making an asset library of all blender file found in a folder """ - -from asset_library.library_types.library_type import LibraryType -from asset_library.common.template import Template -from asset_library.common.file_utils import install_module - -import bpy -from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty) -import re -from pathlib import Path -from itertools import groupby -import uuid import os +import re +import uuid import shutil import json import requests import urllib3 import traceback import time - +from itertools import groupby +from pathlib import Path from pprint import pprint as pp +import bpy +from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty) + +from .library_plugin import LibraryPlugin +from ..core.template import Template +from ..core.file_utils import install_module + + REQ_HEADERS = requests.utils.default_headers() REQ_HEADERS.update({"User-Agent": "Blender: PH Assets"}) -class PolyHaven(LibraryType): +class PolyHaven(LibraryPlugin): name = "Poly Haven" # template_name : StringProperty() diff --git a/library_types/scan_folder.py b/plugins/scan_folder.py similarity index 96% rename from library_types/scan_folder.py rename to plugins/scan_folder.py index cd13567..babb60e 100644 --- a/library_types/scan_folder.py +++ b/plugins/scan_folder.py @@ -3,24 +3,25 @@ Plugin for making an asset library of all blender file found in a folder """ - -from asset_library.library_types.library_type import LibraryType -from asset_library.common.bl_utils import load_datablocks -from asset_library.common.template import Template - -import bpy -from bpy.props import (StringProperty, IntProperty, BoolProperty) import re -from pathlib import Path -from itertools import groupby import uuid import os import shutil import json import time +from pathlib import Path +from itertools import groupby + +import bpy +from bpy.props import (StringProperty, IntProperty, BoolProperty) + +from .library_plugin import LibraryPlugin +from ..core.bl_utils import load_datablocks +from ..core.template import Template -class ScanFolder(LibraryType): + +class ScanFolder(LibraryPlugin): name = "Scan Folder" source_directory : StringProperty(subtype='DIR_PATH') @@ -57,10 +58,10 @@ class ScanFolder(LibraryType): return self.format_path(self.source_template_video, dict(name=name, catalog=catalog, filepath=filepath)) def new_asset(self, asset, asset_data): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') def remove_asset(self, asset, asset_data): - raise Exception('Need to be defined in the library_type') + raise Exception('Need to be defined in the plugin') ''' def format_asset_info(self, asset_datas, asset_path, modified=None): @@ -140,7 +141,7 @@ class ScanFolder(LibraryType): # Write the cache in a temporary file for the generate preview script tmp_cache_file = cache.write(tmp=True) - bpy.ops.assetlib.generate_previews(name=self.library.name, cache=str(tmp_cache_file)) + bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file)) elif isinstance(cache_diff, (Path, str)): cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8')) diff --git a/preferences.py b/preferences.py index 98dc4c1..0e55d5b 100644 --- a/preferences.py +++ b/preferences.py @@ -1,774 +1,57 @@ - -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() \ No newline at end of file + + +import bpy +from bpy.types import AddonPreferences +from bpy.props import (CollectionProperty, StringProperty) + +from . properties import AssetLibrary +from . core.bl_utils import get_addon_prefs +from . core.asset_library_utils import update_library_path + + +class AssetLibraryPrefs(AddonPreferences): + bl_idname = __package__ + + libraries : CollectionProperty(type=AssetLibrary) + bundle_directory : StringProperty( + name="Path", + subtype='DIR_PATH', + default='', + update=update_library_path + ) + + def draw(self, context): + prefs = get_addon_prefs() + + layout = self.layout + col = layout.column(align=False) + + col.prop(self, "bundle_directory", text='Bundle Directory') + col.separator() + + row = col.row() + row.label(text='Libraries:') + #row.alignment = 'RIGHT' + row.separator_spacer() + row.operator("assetlibrary.reload_addon", icon='FILE_REFRESH', text='') + row.operator("assetlibrary.add_library", icon="ADD", text='', emboss=False) + + for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries): + lib.draw(col) + + + + +classes = ( + AssetLibraryPrefs, +) + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) diff --git a/properties.py b/properties.py new file mode 100644 index 0000000..fe50dc8 --- /dev/null +++ b/properties.py @@ -0,0 +1,134 @@ + +import inspect + +import bpy +from bpy.types import (AddonPreferences, PropertyGroup) +from bpy.props import (BoolProperty, StringProperty, CollectionProperty, + EnumProperty, IntProperty, PointerProperty) + +from .core.bl_utils import get_addon_prefs +from .core.file_utils import import_module_from_path, norm_str +from .core.asset_library_utils import update_library_path +from .constants import PLUGINS, PLUGINS_DIR, PLUGINS_ITEMS + + +def load_plugins(): + print('Asset Library: Load Library Plugins') + + plugin_files = list(PLUGINS_DIR.glob('*.py')) + # if self.plugin_directory: + # user_plugin_DIR = Path(self.plugin_directory) + # if user_plugin_DIR.exists(): + # plugin_files += list(user_plugin_DIR.glob('*.py')) + + for plugin_file in plugin_files: + if plugin_file.stem.startswith('_'): + continue + + mod = import_module_from_path(plugin_file) + + for name, obj in inspect.getmembers(mod): + if not inspect.isclass(obj): + continue + + if AssetLibrary not in obj.__mro__ or obj is AssetLibrary: + continue + + try: + print(f'Register Plugin {name}') + bpy.utils.register_class(obj) + setattr(Plugins, norm_str(obj.name), PointerProperty(type=obj)) + PLUGINS.append(obj) + + except Exception as e: + print(f'Could not register plugin {name}') + print(e) + + plugin_items = [('NONE', 'None', '', 0)] + plugin_items += [(norm_str(p.name, format=str.upper), p.name, "", i+1) for i, a in enumerate(PLUGINS)] + + PLUGINS_ITEMS[:] = plugin_items + + return PLUGINS + +class Plugins(PropertyGroup): + """Container holding the registed library plugins""" + 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): + """Library item defining one library with his plugin and settings""" + + name : StringProperty(name='Name', default='', update=update_library_path) + expand : BoolProperty(name='Expand', default=False) + use : BoolProperty(name='Use', default=True, update=update_library_path) + is_env : BoolProperty(default=False) + #path : StringProperty(subtype='DIR_PATH') + + plugins : PointerProperty(type=Plugins) + plugin_name : EnumProperty(items=lambda s, c : PLUGINS_ITEMS) + + @property + def index(self): + prefs = get_addon_prefs() + return list(prefs.libraries).index(self) + + """ + def draw_operators(self, layout): + row = layout.row(align=True) + row.alignment = 'RIGHT' + row.prop(self, 'plugin_name', text='') + row.prop(self, 'auto_bundle', text='', icon='UV_SYNC_SELECT') + + row.operator("assetlibrary.diff", text='', icon='FILE_REFRESH').name = self.name + + op = row.operator("assetlibrary.sync", icon='MOD_BUILD', text='') + op.name = self.name + + layout.separator(factor=3) + """ + + def draw(self, layout): + prefs = get_addon_prefs() + #col = layout.column(align=True) + box = layout.box() + + row = box.row(align=True) + icon = "DISCLOSURE_TRI_DOWN" if self.expand else "DISCLOSURE_TRI_RIGHT" + row.prop(self, 'expand', icon=icon, emboss=False, text='') + + row.prop(self, 'use', text='') + #row.label(icon="ASSET_MANAGER") + row.prop(self, 'name', text='') + row.separator(factor=0.5) + + op = row.operator("assetlibrary.synchronize", icon='UV_SYNC_SELECT', text='') + op.name = self.name + + row.separator(factor=0.5) + row.operator("assetlibrary.remove_library", icon="REMOVE", text='', emboss=False).index = self.index + + #self.draw_operators(row) + if self.expand: + col = box.column(align=False) + col.use_property_split = True + + col.prop(self, "path") + + +classes = ( + Plugins, + AssetLibrary, +) + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + load_plugins() + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/resources/empty_preview.png b/resources/empty_preview.png deleted file mode 100644 index 003b38d..0000000 Binary files a/resources/empty_preview.png and /dev/null differ diff --git a/common/synchronize.py b/scripts/synchronize.py similarity index 100% rename from common/synchronize.py rename to scripts/synchronize.py diff --git a/tests/test_library_cache.py b/tests/test_library_cache.py deleted file mode 100644 index c8dcc9d..0000000 --- a/tests/test_library_cache.py +++ /dev/null @@ -1,270 +0,0 @@ - -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): - - self.file_cache = file_cache - - self._data = data - - self.catalog = data['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', {}) - - @property - def filepath(self): - return self.file_cache.filepath - - @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 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 __str__(self): - return f'AssetCache(name={self.name}, type={self.type}, catalog={self.catalog})' - - -class FileCache: - def __init__(self, library_cache, data): - - self.library_cache = library_cache - self.filepath = data['filepath'] - self.modified = data.get('modified', time.time_ns()) - - self._data = [] - - for asset_cache_data in data.get('assets', []): - self.add(asset_cache_data) - - def add(self, asset_cache_data): - asset_cache = AssetCache(self, asset_cache_data) - self._data.append(asset_cache) - - def to_dict(self): - return dict( - filepath=self.filepath.as_posix(), - modified=self.modified, - library_id=self.library_cache.library.id, - assets=[asset_cache.to_dict() for asset_cache in self] - ) - - def __iter__(self): - return self._data.__iter__() - - def __getitem__(self, key): - return self._data[key] - - def __str__(self): - return f'FileCache(filepath={self.filepath})' - - -class AssetCacheDiff: - def __init__(self, library_diff, asset_cache, operation): - - self.library_cache = library_cache - self.filepath = data['filepath'] - self.operation = operation - - -class LibraryCacheDiff: - def __init__(self, filepath=None): - - self.filepath = filepath - self._data = [] - - def add(self, asset_diff): - asset_diff = AssetCacheDiff(self, asset_diff) - self._data.append(asset_cache_diff) - - def set(self, asset_diffs): - for asset_diff in asset_diffs: - self.add(asset_diff) - - def read(self): - print(f'Read cache from {self.filepath}') - - for asset_diff_data in read_file(self.filepath): - self.add(asset_diff_data) - - 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 __len__(self): - return len(self._data) - - def __getitem__(self, key): - return self._data[key] - - -class LibraryCache: - - def __init__(self, directory, id): - - self.directory = directory - self.id = id - self._data = [] - - @classmethod - def from_library(cls, library): - return cls(library.library_path, library.id) - - @property - def filename(self): - return f"blender_assets.{self.id}.json" - - @property - def filepath(self): - """Get the filepath of the library json file relative to the library""" - return self.directory / self.filename - - @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, temp=False): - filepath = self.filepath - if temp: - filepath = self.tmp_filepath - - print(f'Write cache file to {filepath}') - write_file(filepath, self._data) - return filepath - - def add(self, file_cache_data): - file_cache = FileCache(self, file_cache_data) - - self._data.append(file_cache) - - 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): - """Compare the library cache with it current state and return the cache differential""" - - cache = self.read() - - cache_dict = {f"{a['filepath']}/{a['name']}": a for a in cache.asset_caches} - new_cache_dict = {f"{a['filepath']}/{a['name']}" : a for a in new_cache.asset_caches} - - assets_added = [AssetCacheDiff(v, 'ADD') for k, v in new_cache.items() if k not in cache] - assets_removed = [AssetCacheDiff(v, 'REMOVED') for k, v in cache.items() if k not in new_cache] - assets_modified = [AssetCacheDiff(v, 'MODIFIED') for k, v in cache.items() if v not in assets_removed and v!= new_cache[k]] - - 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') - - cache_diff = LibraryCacheDiff() - cache_diff.set(assets_added+assets_removed+assets_modified) - - if not len(LibraryCacheDiff): - print('No change in the library') - - return cache_diff - - def __len__(self): - return len(self._data) - - def __iter__(self): - return iter(self._data) - - def __getitem__(self, key): - return self._data[key] - - def __str__(self): - return f'LibraryCache(library={self.library.name})' - -print() - -prefs = bpy.context.preferences.addons['asset_library'].preferences - - -library = prefs.env_libraries[0] -library_cache = LibraryCache.from_library(library).read() - -data = library.library_type.fetch() -print(data) - -print(library_cache[0][0]) - -#library_cache.diff(library.library_type.fetch()) - - -#print(library_cache[0]) diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..859dbdf --- /dev/null +++ b/ui.py @@ -0,0 +1,86 @@ + +from bpy.types import FILEBROWSER_HT_header, ASSETBROWSER_MT_editor_menus +from .core.asset_library_utils import get_active_library + + +def draw_assetbrowser_header(self, context): + lib = get_active_library() + + if not lib: + FILEBROWSER_HT_header._draw_asset_browser_buttons(self, context) + return + + space_data = context.space_data + params = context.space_data.params + + row = self.layout.row(align=True) + row.separator() + + row.operator("assetlibrary.bundle", icon='UV_SYNC_SELECT', text='').name = lib.name + #op + #op.clean = False + #op.only_recent = True + + lib.plugin.draw_header(row) + + if context.selected_files and context.active_file: + row.separator() + row.label(text=context.active_file.name) + + row.separator_spacer() + + sub = row.row() + sub.ui_units_x = 10 + sub.prop(params, "filter_search", text="", icon='VIEWZOOM') + + row.separator_spacer() + + row.prop_with_popover( + params, + "display_type", + panel="ASSETBROWSER_PT_display", + text="", + icon_only=True, + ) + + row.operator( + "screen.region_toggle", + text="", + icon='PREFERENCES', + depress=is_option_region_visible(context, space_data) + ).region_type = 'TOOL_PROPS' + + +def draw_assetbrowser_header(self, context): + if not get_active_library(): + return + + self.layout.separator() + box = self.layout.box() + row = box.row() + row.separator(factor=0.5) + row.label(text='Asset Library') + row.separator(factor=0.5) + +# classes = (, +# # ASSETLIB_PT_pose_library_editing, +# # ASSETLIB_PT_pose_library_usage, +# # ASSETLIB_MT_context_menu, +# # ASSETLIB_PT_libraries +# ) + +classes = [] + + +def register() -> None: + for cls in classes: + bpy.utils.register_class(cls) + + ASSETBROWSER_MT_editor_menus.append(draw_assetbrowser_header) + + +def unregister() -> None: + for cls in reversed(classes): + bpy.utils.unregister_class(cls) + + ASSETBROWSER_MT_editor_menus.remove(draw_assetbrowser_header)