""" 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)