asset_library/common/functions.py

468 lines
14 KiB
Python

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