319 lines
10 KiB
Python
319 lines
10 KiB
Python
"""
|
|
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) |