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