asset_library/core/lib_utils.py

319 lines
10 KiB
Python
Raw Permalink Normal View History

2024-07-04 11:53:58 +02:00
"""
Util function for this addon
"""
import os
from pathlib import Path
import inspect
from datetime import datetime
from time import perf_counter
import platform
import bpy
from .catalog import Catalog, read_catalog
from .bl_utils import get_addon_prefs, get_asset_type
from .file_utils import read_file, write_file, cache, import_module_from_path
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():
"""Removing all asset libraries and recreate them"""
print("update_library_path")
addon_prefs = get_addon_prefs()
libs = bpy.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
def load_library_config(config_path):
""""Load library prefs from config path"""
if not config_path:
return []
config_path = Path(config_path)
if not config_path.exists():
print(f'Config {config_path} not exist')
return []
prefs = get_addon_prefs()
libs = []
for lib_dict in read_file(config_path):
lib = prefs.libraries.add()
lib.is_user = False
lib.set_dict(lib_dict)
libs.append(lib)
return libs
def load_libraries():
""""Load library prefs from config pref and env"""
prefs = get_addon_prefs()
bl_libs = bpy.context.preferences.filepaths.asset_libraries
for i, bl_lib in reversed(list(enumerate(bl_libs))):
addon_lib = prefs.libraries.get(bl_lib.name)
if not addon_lib or addon_lib.is_user:
continue
bpy.ops.preferences.asset_library_remove(index=i)
# Remove lib from addons preferences
for i, addon_lib in reversed(list(enumerate(prefs.libraries))):
if not addon_lib.is_user:
prefs.libraries.remove(i)
env_config = os.getenv('ASSET_LIBRARY_CONFIG')
libs = load_library_config(env_config) + load_library_config(prefs.config_path)
return libs
def clear_time_tag(asset):
# Created time tag
for tag in list(asset.asset_data.tags):
try:
datetime.strptime(tag.name, "%Y-%m-%d %H:%M")
asset.asset_data.tags.remove(tag)
except ValueError:
continue
def create_time_tag(asset):
asset.asset_data.tags.new(datetime.now().strftime("%Y-%m-%d %H:%M"))
def version_file(path, save_versions=3):
if path.exists():
for i in range(save_versions):
version = save_versions - i
version_path = path.with_suffix(f'.blend{version}')
if not version_path.exists():
continue
if i == 0:
version_path.unlink()
else:
version_path.rename(path.with_suffix(f'.blend{version+1}'))
path.rename(path.with_suffix(f'.blend1'))
def list_datablocks(blend_file, asset_types={"objects", "materials", "node_groups"}):
blend_data = {}
with bpy.data.temp_data(filepath=str(blend_file)) as temp_data:
with temp_data.libraries.load(str(blend_file), link=True) as (data_from, data_to):
for asset_type in asset_types:
blend_data[asset_type] = getattr(data_from, asset_type)
return blend_data
def get_asset_data(blend_file, asset_type, name, preview=False):
with bpy.data.temp_data(filepath=str(blend_file)) as temp_data:
with temp_data.libraries.load(str(blend_file), link=True) as (data_from, data_to):
if name not in getattr(data_from, asset_type):
return
setattr(data_to, asset_type, [name])
if assets := getattr(data_to, asset_type):
asset = assets[0]
asset_data = asset.asset_data
data = {
"description": asset_data.description,
"catalog_id": asset_data.catalog_id,
"catalog_simple_name": asset_data.catalog_simple_name,
"path": blend_file,
"tags": list(asset_data.tags.keys())
}
if asset.preview and preview:
image_size = asset.preview.image_size
preview_pixels = [0] * image_size[0] * image_size[1] * 4
asset.preview.image_pixels_float.foreach_get(preview_pixels)
data["preview_pixels"] = preview_pixels
data["preview_size"] = list(image_size)
return data
def find_asset_data(name, asset_type, preview=False):
"""Find info about an asset found in library"""
bl_libs = bpy.context.preferences.filepaths.asset_libraries
# First search for a blend with the same name
for bl_lib in bl_libs:
for blend_file in Path(bl_lib.path).glob(f"**/{name}.blend"):
if asset_data := get_asset_data(blend_file, asset_type, name, preview=preview):
asset_data['library'] = bl_lib
return asset_data
# for bl_lib in bl_libs:
# for blend_file in Path(bl_lib.path).glob("**/*.blend"):
# if asset_data := get_asset_data(blend_file, asset_type, name):
# return bl_lib, asset_data
def get_filepath_library(filepath):
for lib in bpy.context.preferences.filepaths.asset_libraries:
if bpy.path.is_subdir(filepath, lib.path):
return lib
def get_asset_catalog_path(asset, fallback=''):
if asset.local_id:
path = Path(bpy.data.filepath).parent
elif (lib := get_filepath_library(asset.full_library_path)):
if lib:
path = lib.path
else:
return fallback
if not (catalog := read_catalog(path)):
return fallback
if not (catalog_item := catalog.get(id=asset.metadata.catalog_id, fallback=fallback)):
return fallback
return catalog_item.path
def get_asset_full_path(asset):
"""Get a path that represent all informations about an asset path/type/catalog/name"""
asset_path = asset.full_library_path
if asset.local_id:
asset_path = f'{bpy.data.filepath}/{asset_path}'
asset_type, asset_name = Path(asset.full_path).parts[-2:]
asset_type = get_asset_type(asset_type)
return Path(asset_path, asset_type, asset_name).as_posix()
def get_asset_source(datablock):
weak_reference = datablock.library_weak_reference
if isinstance(datablock, bpy.types.Object) and datablock.data:
weak_reference = datablock.data.library_weak_reference
if weak_reference and (source_path := Path(weak_reference.filepath)).exists():
return source_path
asset_libraries = context.preferences.filepaths.asset_libraries
for asset_library in asset_libraries:
library_path = Path(asset_library.path)
if blend_files := list(library_path.glob(f"**/{datablock.name}.blend")):
return
return datablock.library_weak_reference
def get_blender_cache_dir():
if platform.system() == 'Linux':
cache_folder = os.path.expandvars('$HOME/.cache/blender')
elif platform.system() == 'Windows':
cache_folder = os.path.expanduser('%USERPROFILE%/AppData/Local/Blender Foundation/Blender')
elif platform.system() == 'Darwin':
cache_folder = '/Library/Caches/Blender'
return Path(cache_folder)
def find_asset_source(library_map, asset_type, name):
return next( (l for l, blend_data in sorted(library_map.items(), key=lambda x: x[1]['st_mtime'], reverse=True)
if name in blend_data['node_groups']), None)
def asset_library_map():
""""Get a mapping of all datablocks of the blend files from the libraries"""
asset_libraries = bpy.context.preferences.filepaths.asset_libraries
cache_file = get_blender_cache_dir() / 'asset-library.json'
cache = None
if cache_file.exists():
cache = read_file(cache_file)
if cache is None:
cache = {}
file_keys = []
for asset_library in asset_libraries:
library_path = Path(asset_library.path)
for bl_file in library_path.glob("**/*.blend"):
file_keys.append(file_key := bl_file.as_posix())
st_mtime = bl_file.stat().st_mtime
if (bl_cache := cache.get(file_key)) and bl_cache.get('st_mtime') >= st_mtime:
continue
datablocks = list_datablocks(bl_file)
cache[file_key] = dict(st_mtime=st_mtime, **datablocks)
# Remove map when the blend not exists anymore
for file_key in list(cache.keys()):
if file_key not in file_keys:
del cache[file_key]
write_file(cache_file, cache)
return cache
# print(perf_counter() - t0)
# t0 = perf_counter()
# for asset_library in asset_libraries:
# library_path = Path(asset_library.path)
# for blend_file in library_path.glob("**/*.blend"):
# with bpy.data.libraries.load(str(blend_file), link=True) as (data_from, data_to):
# node_groups = data_from.node_groups
# print(node_groups)
# print(perf_counter() - t0)