468 lines
14 KiB
Python
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)
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|