Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
christophe.seux | 4465148b22 | |
christophe.seux | a01b282f45 |
102
__init__.py
102
__init__.py
|
@ -8,96 +8,62 @@ Extending features of the Asset Browser for a studio use.
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Asset Library",
|
"name": "Asset Library",
|
||||||
"description": "Asset Library based on the Asset Browser.",
|
"description": "Asset Library based on the Asset Browser.",
|
||||||
"author": "Sybren A. Stüvel, Clement Ducarteron, Christophe Seux, Samuel Bernou",
|
"author": "Christophe Seux",
|
||||||
"version": (2, 0),
|
"version": (2, 0),
|
||||||
"blender": (3, 3, 0),
|
"blender": (4, 0, 2),
|
||||||
"warning": "In development, things may change",
|
"warning": "In development, things may change",
|
||||||
"location": "Asset Browser -> Animations, and 3D Viewport -> Animation panel",
|
"location": "Asset Browser",
|
||||||
"category": "Animation",
|
"category": "Import-Export",
|
||||||
}
|
}
|
||||||
|
|
||||||
#from typing import List, Tuple
|
import sys
|
||||||
|
|
||||||
|
from . import operators, properties, ui, preferences, data_type
|
||||||
|
from .core.lib_utils import load_libraries, update_library_path
|
||||||
|
|
||||||
from asset_library import pose
|
|
||||||
from asset_library import action
|
|
||||||
from asset_library import collection
|
|
||||||
from asset_library import file
|
|
||||||
from asset_library import (gui, keymaps, preferences, operators)
|
|
||||||
from asset_library import constants
|
|
||||||
#from asset_library.common.library_type import LibraryType
|
|
||||||
from asset_library.common.bl_utils import get_addon_prefs
|
|
||||||
from asset_library.common.functions import set_env_libraries
|
|
||||||
from asset_library.common.template import Template
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
if 'bpy' in locals():
|
|
||||||
print("Reload Addon Asset Library")
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(constants)
|
|
||||||
importlib.reload(gui)
|
|
||||||
importlib.reload(keymaps)
|
|
||||||
|
|
||||||
importlib.reload(preferences)
|
|
||||||
importlib.reload(operators)
|
|
||||||
importlib.reload(constants)
|
|
||||||
|
|
||||||
importlib.reload(action)
|
|
||||||
importlib.reload(file)
|
|
||||||
importlib.reload(collection)
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
#addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = []
|
|
||||||
|
|
||||||
bl_modules = (
|
bl_modules = (
|
||||||
operators,
|
operators,
|
||||||
pose,
|
properties,
|
||||||
action,
|
ui,
|
||||||
collection,
|
preferences,
|
||||||
file,
|
data_type
|
||||||
keymaps,
|
|
||||||
gui,
|
|
||||||
preferences
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Reload Modules from inside Blender
|
||||||
|
if "bpy" in locals():
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
for mod in modules:
|
||||||
|
importlib.reload(mod)
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
def load_handler():
|
def load_handler():
|
||||||
print('load_handler')
|
print('load_handler')
|
||||||
|
load_libraries()
|
||||||
|
update_library_path()
|
||||||
|
#set_env_libraries()
|
||||||
|
#bpy.ops.assetlib.set_paths(all=True)
|
||||||
|
|
||||||
set_env_libraries()
|
#if not bpy.app.background:
|
||||||
bpy.ops.assetlib.set_paths(all=True)
|
# bpy.ops.assetlib.bundle(blocking=False, mode='AUTO_BUNDLE')
|
||||||
|
|
||||||
if not bpy.app.background:
|
|
||||||
bpy.ops.assetlib.bundle(blocking=False, mode='AUTO_BUNDLE')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register() -> None:
|
def register():
|
||||||
|
"""Register the addon Asset Library for Blender"""
|
||||||
|
|
||||||
for m in bl_modules:
|
|
||||||
m.register()
|
|
||||||
|
|
||||||
#prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
|
|
||||||
|
for mod in bl_modules:
|
||||||
|
mod.register()
|
||||||
|
|
||||||
|
|
||||||
bpy.app.timers.register(load_handler, first_interval=1)
|
bpy.app.timers.register(load_handler, first_interval=1)
|
||||||
|
|
||||||
|
|
||||||
def unregister() -> None:
|
def unregister():
|
||||||
prefs = get_addon_prefs()
|
"""Unregister the addon Asset Library for Blender"""
|
||||||
bpy.utils.previews.remove(prefs.previews)
|
|
||||||
|
|
||||||
for m in reversed(bl_modules):
|
|
||||||
m.unregister()
|
|
||||||
|
|
||||||
|
|
||||||
|
for mod in reversed(bl_modules):
|
||||||
|
mod.unregister()
|
|
@ -1,33 +0,0 @@
|
||||||
|
|
||||||
from asset_library.action import (
|
|
||||||
gui,
|
|
||||||
keymaps,
|
|
||||||
clear_asset,
|
|
||||||
concat_preview,
|
|
||||||
operators,
|
|
||||||
properties,
|
|
||||||
rename_pose,
|
|
||||||
#render_preview
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'bpy' in locals():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(gui)
|
|
||||||
importlib.reload(keymaps)
|
|
||||||
importlib.reload(clear_asset)
|
|
||||||
importlib.reload(concat_preview)
|
|
||||||
importlib.reload(operators)
|
|
||||||
importlib.reload(properties)
|
|
||||||
importlib.reload(rename_pose)
|
|
||||||
#importlib.reload(render_preview)
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
def register():
|
|
||||||
operators.register()
|
|
||||||
keymaps.register()
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
operators.unregister()
|
|
||||||
keymaps.unregister()
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
from bpy.types import PropertyGroup
|
|
||||||
|
|
||||||
|
|
||||||
class Adapter(PropertyGroup):
|
|
||||||
|
|
||||||
#def __init__(self):
|
|
||||||
name = "Base Adapter"
|
|
||||||
#library = None
|
|
||||||
def to_dict(self):
|
|
||||||
return {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}
|
|
|
@ -1,18 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
from asset_library.common import file_utils
|
|
||||||
from asset_library.common import functions
|
|
||||||
from asset_library.common import synchronize
|
|
||||||
from asset_library.common import template
|
|
||||||
from asset_library.common import catalog
|
|
||||||
|
|
||||||
if 'bpy' in locals():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(file_utils)
|
|
||||||
importlib.reload(functions)
|
|
||||||
importlib.reload(synchronize)
|
|
||||||
importlib.reload(template)
|
|
||||||
importlib.reload(catalog)
|
|
||||||
|
|
||||||
import bpy
|
|
|
@ -1,467 +0,0 @@
|
||||||
# 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)
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,381 +0,0 @@
|
||||||
import bpy
|
|
||||||
from pathlib import Path
|
|
||||||
from asset_library.common.file_utils import read_file, write_file
|
|
||||||
from copy import deepcopy
|
|
||||||
import time
|
|
||||||
from itertools import groupby
|
|
||||||
|
|
||||||
|
|
||||||
class AssetCache:
|
|
||||||
def __init__(self, file_cache, data=None):
|
|
||||||
|
|
||||||
self.file_cache = file_cache
|
|
||||||
|
|
||||||
self.catalog = None
|
|
||||||
self.author = None
|
|
||||||
self.description = None
|
|
||||||
self.tags = None
|
|
||||||
self.type = None
|
|
||||||
self.name = None
|
|
||||||
self._metadata = None
|
|
||||||
|
|
||||||
if data:
|
|
||||||
self.set_data(data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filepath(self):
|
|
||||||
return self.file_cache.filepath
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_id(self):
|
|
||||||
return self.file_cache.library_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def metadata(self):
|
|
||||||
metadata = {
|
|
||||||
'.library_id': self.library_id,
|
|
||||||
'.filepath': self.filepath
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata.update(self._metadata)
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
@property
|
|
||||||
def norm_name(self):
|
|
||||||
return self.name.replace(' ', '_').lower()
|
|
||||||
|
|
||||||
def unique_name(self):
|
|
||||||
return (self.filepath / self.name).as_posix()
|
|
||||||
|
|
||||||
def set_data(self, data):
|
|
||||||
catalog = data['catalog']
|
|
||||||
if isinstance(catalog, (list, tuple)):
|
|
||||||
catalog = '/'.join(catalog)
|
|
||||||
|
|
||||||
self.catalog = catalog
|
|
||||||
self.author = data.get('author', '')
|
|
||||||
self.description = data.get('description', '')
|
|
||||||
self.tags = data.get('tags', [])
|
|
||||||
self.type = data.get('type')
|
|
||||||
self.name = data['name']
|
|
||||||
self._metadata = data.get('metadata', {})
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
catalog=self.catalog,
|
|
||||||
author=self.author,
|
|
||||||
metadata=self.metadata,
|
|
||||||
description=self.description,
|
|
||||||
tags=self.tags,
|
|
||||||
type=self.type,
|
|
||||||
name=self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'AssetCache(name={self.name}, catalog={self.catalog})'
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.to_dict() == other.to_dict()
|
|
||||||
|
|
||||||
|
|
||||||
class AssetsCache:
|
|
||||||
def __init__(self, file_cache):
|
|
||||||
|
|
||||||
self.file_cache = file_cache
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
def add(self, asset_cache_data, **kargs):
|
|
||||||
asset_cache = AssetCache(self.file_cache, {**asset_cache_data, **kargs})
|
|
||||||
self._data.append(asset_cache)
|
|
||||||
|
|
||||||
return asset_cache
|
|
||||||
|
|
||||||
def remove(self, asset_cache):
|
|
||||||
if isinstance(asset_cache, str):
|
|
||||||
asset_cache = self.get(asset_cache)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self._data.__iter__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if isinstance(key, str):
|
|
||||||
return self.to_dict()[key]
|
|
||||||
else:
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {a.name: a for a in self}
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
return next((a for a in self if a.name == name), None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'AssetsCache({list(self)})'
|
|
||||||
|
|
||||||
|
|
||||||
class FileCache:
|
|
||||||
def __init__(self, library_cache, data=None):
|
|
||||||
|
|
||||||
self.library_cache = library_cache
|
|
||||||
|
|
||||||
self.filepath = None
|
|
||||||
self.modified = None
|
|
||||||
self.assets = AssetsCache(self)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
self.set_data(data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_id(self):
|
|
||||||
return self.library_cache.library_id
|
|
||||||
|
|
||||||
def set_data(self, data):
|
|
||||||
|
|
||||||
if 'filepath' in data:
|
|
||||||
self.filepath = Path(data['filepath'])
|
|
||||||
|
|
||||||
self.modified = data.get('modified', time.time_ns())
|
|
||||||
|
|
||||||
if data.get('type') == 'FILE':
|
|
||||||
self.assets.add(data)
|
|
||||||
|
|
||||||
for asset_cache_data in data.get('assets', []):
|
|
||||||
self.assets.add(asset_cache_data)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
filepath=self.filepath.as_posix(),
|
|
||||||
modified=self.modified,
|
|
||||||
library_id=self.library_id,
|
|
||||||
assets=[asset_cache.to_dict() for asset_cache in self]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self.assets.__iter__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'FileCache(filepath={self.filepath})'
|
|
||||||
|
|
||||||
|
|
||||||
class AssetCacheDiff:
|
|
||||||
def __init__(self, library_cache, asset_cache, operation):
|
|
||||||
|
|
||||||
self.library_cache = library_cache
|
|
||||||
#self.filepath = data['filepath']
|
|
||||||
self.operation = operation
|
|
||||||
self.asset_cache = asset_cache
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryCacheDiff:
|
|
||||||
def __init__(self, old_cache=None, new_cache=None, filepath=None):
|
|
||||||
|
|
||||||
self.filepath = filepath
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
self.compare(old_cache, new_cache)
|
|
||||||
|
|
||||||
def add(self, asset_cache_datas, operation):
|
|
||||||
if not isinstance(asset_cache_datas, (list, tuple)):
|
|
||||||
asset_cache_datas = [asset_cache_datas]
|
|
||||||
|
|
||||||
new_asset_diffs = []
|
|
||||||
for cache_data in asset_cache_datas:
|
|
||||||
new_asset_diffs.append(AssetCacheDiff(self, cache_data, operation))
|
|
||||||
|
|
||||||
self._data += new_asset_diffs
|
|
||||||
|
|
||||||
return new_asset_diffs
|
|
||||||
|
|
||||||
def compare(self, old_cache, new_cache):
|
|
||||||
if old_cache is None or new_cache is None:
|
|
||||||
print('Cannot Compare cache with None')
|
|
||||||
|
|
||||||
cache_dict = {a.unique_name : a for a in old_cache.asset_caches}
|
|
||||||
new_cache_dict = {a.unique_name : a for a in new_cache.asset_caches}
|
|
||||||
|
|
||||||
assets_added = self.add([v for k, v in new_cache_dict.items() if k not in cache_dict], 'ADD')
|
|
||||||
assets_removed = self.add([v for k, v in cache_dict.items() if k not in new_cache_dict], 'REMOVED')
|
|
||||||
assets_modified = self.add([v for k, v in cache_dict.items() if v not in assets_removed and v!= new_cache_dict[k]], 'MODIFIED')
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
if len(self) == 0:
|
|
||||||
print('No change in the library')
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def group_by(self, key):
|
|
||||||
'''Return groups of file cache diff using the key provided'''
|
|
||||||
data = list(self).sort(key=key)
|
|
||||||
return groupby(data, key=key)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'LibraryCacheDiff(operations={[o for o in self][:2]}...)'
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryCache:
|
|
||||||
def __init__(self, filepath):
|
|
||||||
|
|
||||||
self.filepath = Path(filepath)
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_library(cls, library):
|
|
||||||
filepath = library.library_path / f"blender_assets.{library.id}.json"
|
|
||||||
return cls(filepath)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
return self.filepath.name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_id(self):
|
|
||||||
return self.filepath.stem.split('.')[-1]
|
|
||||||
|
|
||||||
#@property
|
|
||||||
#def filepath(self):
|
|
||||||
# """Get the filepath of the library json file relative to the library"""
|
|
||||||
# return self.directory / self.filename
|
|
||||||
|
|
||||||
def catalogs(self):
|
|
||||||
return set(a.catalog for a in self.asset_caches)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def asset_caches(self):
|
|
||||||
'''Return an iterator to get all asset caches'''
|
|
||||||
return (asset_cache for file_cache in self for asset_cache in file_cache)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tmp_filepath(self):
|
|
||||||
return Path(bpy.app.tempdir) / self.filename
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
print(f'Read cache from {self.filepath}')
|
|
||||||
|
|
||||||
for file_cache_data in read_file(self.filepath):
|
|
||||||
self.add(file_cache_data)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def write(self, tmp=False):
|
|
||||||
filepath = self.filepath
|
|
||||||
if tmp:
|
|
||||||
filepath = self.tmp_filepath
|
|
||||||
|
|
||||||
print(f'Write cache file to {filepath}')
|
|
||||||
write_file(filepath, self._data)
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
def add(self, file_cache_data=None):
|
|
||||||
file_cache = FileCache(self, file_cache_data)
|
|
||||||
|
|
||||||
self._data.append(file_cache)
|
|
||||||
|
|
||||||
return file_cache
|
|
||||||
|
|
||||||
def add_asset_cache(self, asset_cache_data, filepath=None):
|
|
||||||
if filepath is None:
|
|
||||||
filepath = asset_cache_data['filepath']
|
|
||||||
|
|
||||||
file_cache = self.get(filepath)
|
|
||||||
if not file_cache:
|
|
||||||
file_cache = self.add()
|
|
||||||
|
|
||||||
file_cache.assets.add(asset_cache_data)
|
|
||||||
|
|
||||||
# def unflatten_cache(self, cache):
|
|
||||||
# """ Return a new unflattten list of asset data
|
|
||||||
# grouped by filepath"""
|
|
||||||
|
|
||||||
# new_cache = []
|
|
||||||
|
|
||||||
# cache = deepcopy(cache)
|
|
||||||
|
|
||||||
# cache.sort(key=lambda x : x['filepath'])
|
|
||||||
# groups = groupby(cache, key=lambda x : x['filepath'])
|
|
||||||
|
|
||||||
# keys = ['filepath', 'modified', 'library_id']
|
|
||||||
|
|
||||||
# for _, asset_datas in groups:
|
|
||||||
# asset_datas = list(asset_datas)
|
|
||||||
|
|
||||||
# #print(asset_datas[0])
|
|
||||||
|
|
||||||
# asset_info = {k:asset_datas[0][k] for k in keys}
|
|
||||||
# asset_info['assets'] = [{k:v for k, v in a.items() if k not in keys+['operation']} for a in asset_datas]
|
|
||||||
|
|
||||||
# new_cache.append(asset_info)
|
|
||||||
|
|
||||||
# return new_cache
|
|
||||||
|
|
||||||
def diff(self, new_cache=None):
|
|
||||||
"""Compare the library cache with it current state and return the cache differential"""
|
|
||||||
|
|
||||||
old_cache = self.read()
|
|
||||||
|
|
||||||
if new_cache is None:
|
|
||||||
new_cache = self
|
|
||||||
|
|
||||||
return LibraryCacheDiff(old_cache, new_cache)
|
|
||||||
|
|
||||||
def update(self, cache_diff):
|
|
||||||
#Update the cache with the operations
|
|
||||||
for asset_cache_diff in cache_diff:
|
|
||||||
file_cache = self.get(asset_cache_diff.filepath)
|
|
||||||
if not asset_cache:
|
|
||||||
print(f'Filepath {asset_cache_diff.filepath} not in {self}' )
|
|
||||||
continue
|
|
||||||
|
|
||||||
asset_cache = file_cache.get(asset_cache_diff.name)
|
|
||||||
|
|
||||||
if not asset_cache:
|
|
||||||
print(f'Asset {asset_cache_diff.name} not in file_cache {file_cache}' )
|
|
||||||
continue
|
|
||||||
|
|
||||||
if asset_cache_diff.operation == 'REMOVE':
|
|
||||||
file_cache.assets.remove(asset_cache_diff.name)
|
|
||||||
|
|
||||||
elif asset_cache_diff.operation in ('MODIFY', 'ADD'):
|
|
||||||
asset_cache.set_data(asset_cache_diff.asset_cache.to_dict())
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if isinstance(key, str):
|
|
||||||
return self.to_dict()[key]
|
|
||||||
else:
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {a.filepath: a for a in self}
|
|
||||||
|
|
||||||
def get(self, filepath):
|
|
||||||
return next((a for a in self if a.filepath == filepath), None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'LibraryCache(library_id={self.library_id})'
|
|
||||||
|
|
25
constants.py
25
constants.py
|
@ -3,10 +3,18 @@ import bpy
|
||||||
|
|
||||||
|
|
||||||
DATA_TYPE_ITEMS = [
|
DATA_TYPE_ITEMS = [
|
||||||
("ACTION", "Action", "", "ACTION", 0),
|
("NodeTree", "Node Group", "", "NODETREE", 0),
|
||||||
("COLLECTION", "Collection", "", "OUTLINER_OB_GROUP_INSTANCE", 1),
|
("Material", "Material", "", "MATERIAL", 1),
|
||||||
("FILE", "File", "", "FILE", 2)
|
("Object", "Object", "", "OBJECT_DATA", 2),
|
||||||
|
("Action", "Action", "", "ACTION", 3),
|
||||||
|
("Collection", "Collection", "", "OUTLINER_OB_GROUP_INSTANCE", 4),
|
||||||
|
("File", "File", "", "FILE", 5)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
DATA_TYPE_GEO_ITEMS = [DATA_TYPE_ITEMS[0], DATA_TYPE_ITEMS[2]]
|
||||||
|
DATA_TYPE_SHADING_ITEMS = [DATA_TYPE_ITEMS[0], DATA_TYPE_ITEMS[1]]
|
||||||
|
|
||||||
|
CATALOG_ITEMS = {}
|
||||||
DATA_TYPES = [i[0] for i in DATA_TYPE_ITEMS]
|
DATA_TYPES = [i[0] for i in DATA_TYPE_ITEMS]
|
||||||
ICONS = {identifier: icon for identifier, name, description, icon, number in DATA_TYPE_ITEMS}
|
ICONS = {identifier: icon for identifier, name, description, icon, number in DATA_TYPE_ITEMS}
|
||||||
|
|
||||||
|
@ -14,11 +22,14 @@ ASSETLIB_FILENAME = "blender_assets.libs.json"
|
||||||
MODULE_DIR = Path(__file__).parent
|
MODULE_DIR = Path(__file__).parent
|
||||||
RESOURCES_DIR = MODULE_DIR / 'resources'
|
RESOURCES_DIR = MODULE_DIR / 'resources'
|
||||||
|
|
||||||
LIBRARY_TYPE_DIR = MODULE_DIR / 'library_types'
|
PLUGINS_DIR = MODULE_DIR / 'plugins'
|
||||||
LIBRARY_TYPES = []
|
PLUGINS = {}
|
||||||
|
PLUGINS_ITEMS = [('NONE', 'None', '', 0)]
|
||||||
|
|
||||||
ADAPTER_DIR = MODULE_DIR / 'adapters'
|
LIB_DIR = MODULE_DIR / 'libs'
|
||||||
ADAPTERS = []
|
LIB_ITEMS = []
|
||||||
|
|
||||||
|
SCRIPTS_DIR = MODULE_DIR / 'scripts'
|
||||||
|
|
||||||
PREVIEW_ASSETS_SCRIPT = MODULE_DIR / 'common' / 'preview_assets.py'
|
PREVIEW_ASSETS_SCRIPT = MODULE_DIR / 'common' / 'preview_assets.py'
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
Generic Blender functions
|
Generic Blender functions
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from typing import Any, List, Iterable, Optional, Tuple
|
from typing import Any, List, Iterable, Optional, Tuple
|
||||||
|
@ -10,11 +10,13 @@ Datablock = Any
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy_extras import asset_utils
|
from bpy_extras import asset_utils
|
||||||
from asset_library.constants import RESOURCES_DIR
|
#from asset_library.constants import RESOURCES_DIR
|
||||||
#from asset_library.common.file_utils import no
|
#from asset_library.common.file_utils import no
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from .file_utils import norm_str
|
||||||
|
|
||||||
|
|
||||||
class attr_set():
|
class attr_set():
|
||||||
'''Receive a list of tuple [(data_path, "attribute" [, wanted value)] ]
|
'''Receive a list of tuple [(data_path, "attribute" [, wanted value)] ]
|
||||||
|
@ -48,6 +50,20 @@ class attr_set():
|
||||||
for prop, attr, old_val in self.store:
|
for prop, attr, old_val in self.store:
|
||||||
setattr(prop, attr, old_val)
|
setattr(prop, attr, old_val)
|
||||||
|
|
||||||
|
|
||||||
|
def unique_name(name, names):
|
||||||
|
if name not in names:
|
||||||
|
return name
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
org_name = name
|
||||||
|
while name in names:
|
||||||
|
name = f'{org_name}.{i:03d}'
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def get_overriden_col(ob, scene=None):
|
def get_overriden_col(ob, scene=None):
|
||||||
scn = scene or bpy.context.scene
|
scn = scene or bpy.context.scene
|
||||||
|
|
||||||
|
@ -56,16 +72,21 @@ def get_overriden_col(ob, scene=None):
|
||||||
return next((c for c in cols if ob in c.all_objects[:]
|
return next((c for c in cols if ob in c.all_objects[:]
|
||||||
if all(not c.override_library for c in get_col_parents(c))), None)
|
if all(not c.override_library for c in get_col_parents(c))), None)
|
||||||
|
|
||||||
|
|
||||||
def get_view3d_persp():
|
def get_view3d_persp():
|
||||||
windows = bpy.context.window_manager.windows
|
windows = bpy.context.window_manager.windows
|
||||||
view_3ds = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D']
|
view_3ds = [a for w in windows for a in w.screen.areas if a.type == 'VIEW_3D']
|
||||||
view_3d = next((a for a in view_3ds if a.spaces.active.region_3d.view_perspective == 'PERSP'), view_3ds[0])
|
view_3d = next((a for a in view_3ds if a.spaces.active.region_3d.view_perspective == 'PERSP'), view_3ds[0])
|
||||||
return view_3d
|
return view_3d
|
||||||
|
|
||||||
|
|
||||||
def get_viewport():
|
def get_viewport():
|
||||||
screen = bpy.context.screen
|
screen = bpy.context.screen
|
||||||
|
|
||||||
areas = [a for a in screen.areas if a.type == 'VIEW_3D']
|
areas = [a for a in screen.areas if a.type == 'VIEW_3D']
|
||||||
|
if not areas:
|
||||||
|
return
|
||||||
|
|
||||||
areas.sort(key=lambda x : x.width*x.height)
|
areas.sort(key=lambda x : x.width*x.height)
|
||||||
|
|
||||||
return areas[-1]
|
return areas[-1]
|
||||||
|
@ -89,6 +110,7 @@ def biggest_asset_browser_area(screen: bpy.types.Screen) -> Optional[bpy.types.A
|
||||||
|
|
||||||
return max(areas, key=area_sorting_key)
|
return max(areas, key=area_sorting_key)
|
||||||
|
|
||||||
|
|
||||||
def suitable_areas(screen: bpy.types.Screen) -> Iterable[bpy.types.Area]:
|
def suitable_areas(screen: bpy.types.Screen) -> Iterable[bpy.types.Area]:
|
||||||
"""Generator, yield Asset Browser areas."""
|
"""Generator, yield Asset Browser areas."""
|
||||||
|
|
||||||
|
@ -98,6 +120,7 @@ def suitable_areas(screen: bpy.types.Screen) -> Iterable[bpy.types.Area]:
|
||||||
continue
|
continue
|
||||||
yield area
|
yield area
|
||||||
|
|
||||||
|
|
||||||
def area_from_context(context: bpy.types.Context) -> Optional[bpy.types.Area]:
|
def area_from_context(context: bpy.types.Context) -> Optional[bpy.types.Area]:
|
||||||
"""Return an Asset Browser suitable for the given category.
|
"""Return an Asset Browser suitable for the given category.
|
||||||
|
|
||||||
|
@ -122,6 +145,7 @@ def area_from_context(context: bpy.types.Context) -> Optional[bpy.types.Area]:
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def activate_asset(
|
def activate_asset(
|
||||||
asset: bpy.types.Action, asset_browser: bpy.types.Area, *, deferred: bool
|
asset: bpy.types.Action, asset_browser: bpy.types.Area, *, deferred: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -131,20 +155,24 @@ def activate_asset(
|
||||||
assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data)
|
assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data)
|
||||||
space_data.activate_asset_by_id(asset, deferred=deferred)
|
space_data.activate_asset_by_id(asset, deferred=deferred)
|
||||||
|
|
||||||
|
|
||||||
def active_catalog_id(asset_browser: bpy.types.Area) -> str:
|
def active_catalog_id(asset_browser: bpy.types.Area) -> str:
|
||||||
"""Return the ID of the catalog shown in the asset browser."""
|
"""Return the ID of the catalog shown in the asset browser."""
|
||||||
return params(asset_browser).catalog_id
|
return params(asset_browser).catalog_id
|
||||||
|
|
||||||
|
|
||||||
def get_asset_space_params(asset_browser: bpy.types.Area) -> bpy.types.FileAssetSelectParams:
|
def get_asset_space_params(asset_browser: bpy.types.Area) -> bpy.types.FileAssetSelectParams:
|
||||||
"""Return the asset browser parameters given its Area."""
|
"""Return the asset browser parameters given its Area."""
|
||||||
space_data = asset_browser.spaces[0]
|
space_data = asset_browser.spaces[0]
|
||||||
assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data)
|
assert asset_utils.SpaceAssetInfo.is_asset_browser(space_data)
|
||||||
return space_data.params
|
return space_data.params
|
||||||
|
|
||||||
|
|
||||||
def refresh_asset_browsers():
|
def refresh_asset_browsers():
|
||||||
for area in suitable_areas(bpy.context.screen):
|
for area in suitable_areas(bpy.context.screen):
|
||||||
bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
|
bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
|
||||||
|
|
||||||
|
|
||||||
def tag_redraw(screen: bpy.types.Screen) -> None:
|
def tag_redraw(screen: bpy.types.Screen) -> None:
|
||||||
"""Tag all asset browsers for redrawing."""
|
"""Tag all asset browsers for redrawing."""
|
||||||
|
|
||||||
|
@ -186,12 +214,14 @@ def norm_value(value):
|
||||||
value = json.dumps(value)
|
value = json.dumps(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def norm_arg(arg_name, format=str.lower, prefix='--', separator='-'):
|
def norm_arg(arg_name, format=str.lower, prefix='--', separator='-'):
|
||||||
arg_name = norm_str(arg_name, format=format, separator=separator)
|
arg_name = norm_str(arg_name, format=format, separator=separator)
|
||||||
|
|
||||||
return prefix + arg_name
|
return prefix + arg_name
|
||||||
|
|
||||||
def get_bl_cmd(blender=None, background=False, focus=True, blendfile=None, script=None, **kargs):
|
|
||||||
|
def get_bl_cmd(blender=None, background=False, factory_startup=False, focus=True, blendfile=None, script=None, **kargs):
|
||||||
cmd = [str(blender)] if blender else [bpy.app.binary_path]
|
cmd = [str(blender)] if blender else [bpy.app.binary_path]
|
||||||
|
|
||||||
if background:
|
if background:
|
||||||
|
@ -203,6 +233,9 @@ def get_bl_cmd(blender=None, background=False, focus=True, blendfile=None, scrip
|
||||||
|
|
||||||
cmd += ['--python-use-system-env']
|
cmd += ['--python-use-system-env']
|
||||||
|
|
||||||
|
if factory_startup:
|
||||||
|
cmd += ['--factory-startup']
|
||||||
|
|
||||||
if blendfile:
|
if blendfile:
|
||||||
cmd += [str(blendfile)]
|
cmd += [str(blendfile)]
|
||||||
|
|
||||||
|
@ -223,31 +256,12 @@ def get_bl_cmd(blender=None, background=False, focus=True, blendfile=None, scrip
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def get_addon_prefs():
|
def get_addon_prefs():
|
||||||
addon_name = __package__.split('.')[0]
|
addon_name = __package__.split('.')[0]
|
||||||
return bpy.context.preferences.addons[addon_name].preferences
|
return bpy.context.preferences.addons[addon_name].preferences
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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_col_parents(col, root=None, cols=None):
|
def get_col_parents(col, root=None, cols=None):
|
||||||
'''Return a list of parents collections of passed col
|
'''Return a list of parents collections of passed col
|
||||||
root : Pass a collection to search in (recursive)
|
root : Pass a collection to search in (recursive)
|
||||||
|
@ -267,6 +281,7 @@ def get_col_parents(col, root=None, cols=None):
|
||||||
cols = get_col_parents(col, root=sub, cols=cols)
|
cols = get_col_parents(col, root=sub, cols=cols)
|
||||||
return cols
|
return cols
|
||||||
|
|
||||||
|
|
||||||
def get_overriden_col(ob, scene=None):
|
def get_overriden_col(ob, scene=None):
|
||||||
'''Get the collection use for making the override'''
|
'''Get the collection use for making the override'''
|
||||||
scn = scene or bpy.context.scene
|
scn = scene or bpy.context.scene
|
||||||
|
@ -276,6 +291,7 @@ def get_overriden_col(ob, scene=None):
|
||||||
return next((c for c in cols if ob in c.all_objects[:]
|
return next((c for c in cols if ob in c.all_objects[:]
|
||||||
if all(not c.override_library for c in get_col_parents(c))), None)
|
if all(not c.override_library for c in get_col_parents(c))), None)
|
||||||
|
|
||||||
|
|
||||||
def load_assets_from(filepath: Path) -> List[Datablock]:
|
def load_assets_from(filepath: Path) -> List[Datablock]:
|
||||||
if not has_assets(filepath):
|
if not has_assets(filepath):
|
||||||
# Avoid loading any datablocks when there are none marked as asset.
|
# Avoid loading any datablocks when there are none marked as asset.
|
||||||
|
@ -306,6 +322,7 @@ def load_assets_from(filepath: Path) -> List[Datablock]:
|
||||||
loaded_assets.append(datablock)
|
loaded_assets.append(datablock)
|
||||||
return loaded_assets
|
return loaded_assets
|
||||||
|
|
||||||
|
|
||||||
def has_assets(filepath: Path) -> bool:
|
def has_assets(filepath: Path) -> bool:
|
||||||
with bpy.data.libraries.load(str(filepath), assets_only=True) as (
|
with bpy.data.libraries.load(str(filepath), assets_only=True) as (
|
||||||
data_from,
|
data_from,
|
||||||
|
@ -318,17 +335,13 @@ def has_assets(filepath: Path) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def copy_frames(start, end, offset, path):
|
def copy_frames(start, end, offset, path):
|
||||||
for i in range (start, end):
|
for i in range (start, end):
|
||||||
src = path.replace('####', f'{i:04d}')
|
src = path.replace('####', f'{i:04d}')
|
||||||
dst = src.replace(src.split('_')[-1].split('.')[0], f'{i+offset:04d}')
|
dst = src.replace(src.split('_')[-1].split('.')[0], f'{i+offset:04d}')
|
||||||
shutil.copy2(src, dst)
|
shutil.copy2(src, dst)
|
||||||
|
|
||||||
|
|
||||||
def split_path(path) :
|
def split_path(path) :
|
||||||
try :
|
try :
|
||||||
bone_name = path.split('["')[1].split('"]')[0]
|
bone_name = path.split('["')[1].split('"]')[0]
|
||||||
|
@ -342,7 +355,10 @@ def split_path(path) :
|
||||||
return bone_name, prop_name
|
return bone_name, prop_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_asset_type(asset_type):
|
||||||
|
data_types = { p.fixed_type.identifier: p.identifier for p in
|
||||||
|
bpy.types.BlendData.bl_rna.properties if hasattr(p, 'fixed_type')}
|
||||||
|
return data_types[asset_type]
|
||||||
|
|
||||||
|
|
||||||
def load_datablocks(src, names=None, type='objects', link=True, expr=None, assets_only=False) -> list:
|
def load_datablocks(src, names=None, type='objects', link=True, expr=None, assets_only=False) -> list:
|
||||||
|
@ -478,3 +494,85 @@ def get_object_libraries(ob):
|
||||||
filepaths.append(absolute_filepath)
|
filepaths.append(absolute_filepath)
|
||||||
|
|
||||||
return filepaths
|
return filepaths
|
||||||
|
|
||||||
|
|
||||||
|
def clean_name(name):
|
||||||
|
if re.match(r'(.*)\.\d{3}$', name):
|
||||||
|
return name[:-4]
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def is_node_groups_duplicate(node_groups):
|
||||||
|
node_group_types = sorted([n.type for n in node_groups[0].nodes])
|
||||||
|
return all( sorted([n.type for n in ng.nodes]) ==
|
||||||
|
node_group_types for ng in node_groups[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def is_images_duplicate(images):
|
||||||
|
return all( img.filepath == images[0].filepath for image in images)
|
||||||
|
|
||||||
|
|
||||||
|
def is_materials_duplicate(materials):
|
||||||
|
node_group_types = sorted([n.type for n in materials[0].node_tree.nodes])
|
||||||
|
return all( sorted([n.type for n in mat.node_tree.nodes]) ==
|
||||||
|
node_group_types for mat in materials[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def merge_datablock_duplicates(datablocks, blend_data, force=False):
|
||||||
|
"""Merging materials, node_groups or images based on name .001, .002"""
|
||||||
|
|
||||||
|
failed = []
|
||||||
|
merged = []
|
||||||
|
|
||||||
|
datablocks = list(datablocks)
|
||||||
|
|
||||||
|
#blend_data = get_asset_type(datablocks[0].bl_rna.identifier)
|
||||||
|
if blend_data == 'materials':
|
||||||
|
is_datablock_duplicate = is_materials_duplicate
|
||||||
|
elif blend_data == 'node_groups':
|
||||||
|
is_datablock_duplicate = is_node_groups_duplicate
|
||||||
|
elif blend_data == 'images':
|
||||||
|
is_datablock_duplicate = is_images_duplicate
|
||||||
|
else:
|
||||||
|
raise Exception(f'Type, {blend_data} not supported')
|
||||||
|
|
||||||
|
# Group by name
|
||||||
|
groups = {}
|
||||||
|
for datablock in images:
|
||||||
|
groups.setdefault(clean_name(datablock.name), []).append(datablock)
|
||||||
|
|
||||||
|
for datablock in blend_data:
|
||||||
|
name = clean_name(datablock.name)
|
||||||
|
if name in groups and datablock not in groups[name]:
|
||||||
|
groups[name].append(datablock)
|
||||||
|
|
||||||
|
print("\nMerge Duplicate Datablocks...")
|
||||||
|
|
||||||
|
for group in groups.values():
|
||||||
|
if len(group) == 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not force:
|
||||||
|
datablocks.sort(key=lambda x : x.name, reverse=True)
|
||||||
|
|
||||||
|
for datablock in datablocks[1:]:
|
||||||
|
is_duplicate = is_datablock_duplicate((datablock, datablocks[0]))
|
||||||
|
|
||||||
|
if not is_duplicate and not force:
|
||||||
|
failed.append((datablock.name, datablocks[0].name))
|
||||||
|
print(f'Cannot merge Datablock {datablocks.name} with {datablocks[0].name} they are different')
|
||||||
|
continue
|
||||||
|
|
||||||
|
merged.append((datablock.name, datablocks[0].name))
|
||||||
|
print(f'Merge Datablock {datablock.name} into {datablocks[0].name}')
|
||||||
|
|
||||||
|
datablock.user_remap(datablocks[0])
|
||||||
|
datablocks.remove(datablock)
|
||||||
|
blend_data.remove(datablock)
|
||||||
|
|
||||||
|
# Rename groups if it has no duplicate left
|
||||||
|
for datablocks in groups.values():
|
||||||
|
if len(datablocks) == 1 and not datablocks[0].library:
|
||||||
|
datablocks[0].name = clean_name(datablocks[0].name)
|
||||||
|
|
||||||
|
return merged, failed
|
|
@ -2,6 +2,14 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import uuid
|
import uuid
|
||||||
import bpy
|
import bpy
|
||||||
|
from .file_utils import cache
|
||||||
|
|
||||||
|
@cache(1)
|
||||||
|
def read_catalog(library_path):
|
||||||
|
catalog = Catalog(library_path)
|
||||||
|
catalog.read()
|
||||||
|
|
||||||
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
class CatalogItem:
|
class CatalogItem:
|
|
@ -12,9 +12,12 @@ from pathlib import Path
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
from functools import wraps
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def cd(path):
|
def cd(path):
|
||||||
"""Changes working directory and returns to previous on exit."""
|
"""Changes working directory and returns to previous on exit."""
|
||||||
|
@ -25,6 +28,7 @@ def cd(path):
|
||||||
finally:
|
finally:
|
||||||
os.chdir(prev_cwd)
|
os.chdir(prev_cwd)
|
||||||
|
|
||||||
|
|
||||||
def install_module(module_name, package_name=None):
|
def install_module(module_name, package_name=None):
|
||||||
'''Install a python module with pip or return it if already installed'''
|
'''Install a python module with pip or return it if already installed'''
|
||||||
try:
|
try:
|
||||||
|
@ -39,6 +43,7 @@ def install_module(module_name, package_name=None):
|
||||||
|
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def import_module_from_path(path):
|
def import_module_from_path(path):
|
||||||
from importlib import util
|
from importlib import util
|
||||||
|
|
||||||
|
@ -54,6 +59,7 @@ def import_module_from_path(path):
|
||||||
print(f'Cannot import file {path}')
|
print(f'Cannot import file {path}')
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
def norm_str(string, separator='_', format=str.lower, padding=0):
|
def norm_str(string, separator='_', format=str.lower, padding=0):
|
||||||
string = str(string)
|
string = str(string)
|
||||||
string = string.replace('_', ' ')
|
string = string.replace('_', ' ')
|
||||||
|
@ -73,6 +79,7 @@ def norm_str(string, separator='_', format=str.lower, padding=0):
|
||||||
|
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
def remove_version(filepath):
|
def remove_version(filepath):
|
||||||
pattern = '_v[0-9]+\.'
|
pattern = '_v[0-9]+\.'
|
||||||
search = re.search(pattern, filepath)
|
search = re.search(pattern, filepath)
|
||||||
|
@ -82,12 +89,14 @@ def remove_version(filepath):
|
||||||
|
|
||||||
return Path(filepath).name
|
return Path(filepath).name
|
||||||
|
|
||||||
|
|
||||||
def is_exclude(name, patterns) -> bool:
|
def is_exclude(name, patterns) -> bool:
|
||||||
# from fnmatch import fnmatch
|
# from fnmatch import fnmatch
|
||||||
if not isinstance(patterns, (list,tuple)) :
|
if not isinstance(patterns, (list,tuple)) :
|
||||||
patterns = [patterns]
|
patterns = [patterns]
|
||||||
return any([fnmatch(name, p) for p in patterns])
|
return any([fnmatch(name, p) for p in patterns])
|
||||||
|
|
||||||
|
|
||||||
def get_last_files(root, pattern=r'_v\d{3}\.\w+', only_matching=False, ex_file=None, ex_dir=None, keep=1, verbose=False) -> list:
|
def get_last_files(root, pattern=r'_v\d{3}\.\w+', only_matching=False, ex_file=None, ex_dir=None, keep=1, verbose=False) -> list:
|
||||||
'''Recursively get last(s) file(s) (when there is multiple versions) in passed directory
|
'''Recursively get last(s) file(s) (when there is multiple versions) in passed directory
|
||||||
root -> str: Filepath of the folder to scan.
|
root -> str: Filepath of the folder to scan.
|
||||||
|
@ -139,6 +148,7 @@ def get_last_files(root, pattern=r'_v\d{3}\.\w+', only_matching=False, ex_file=N
|
||||||
|
|
||||||
return sorted(files)
|
return sorted(files)
|
||||||
|
|
||||||
|
|
||||||
def copy_file(src, dst, only_new=False, only_recent=False):
|
def copy_file(src, dst, only_new=False, only_recent=False):
|
||||||
if dst.exists():
|
if dst.exists():
|
||||||
if only_new:
|
if only_new:
|
||||||
|
@ -153,6 +163,7 @@ def copy_file(src, dst, only_new=False, only_recent=False):
|
||||||
else:
|
else:
|
||||||
subprocess.call(['cp', str(src), str(dst)])
|
subprocess.call(['cp', str(src), str(dst)])
|
||||||
|
|
||||||
|
|
||||||
def copy_dir(src, dst, only_new=False, only_recent=False, excludes=['.*'], includes=[]):
|
def copy_dir(src, dst, only_new=False, only_recent=False, excludes=['.*'], includes=[]):
|
||||||
src, dst = Path(src), Path(dst)
|
src, dst = Path(src), Path(dst)
|
||||||
|
|
||||||
|
@ -203,6 +214,7 @@ def open_file(filepath, select=False):
|
||||||
cmd += [str(filepath)]
|
cmd += [str(filepath)]
|
||||||
subprocess.Popen(cmd)
|
subprocess.Popen(cmd)
|
||||||
|
|
||||||
|
|
||||||
def open_blender_file(filepath=None):
|
def open_blender_file(filepath=None):
|
||||||
filepath = filepath or bpy.data.filepath
|
filepath = filepath or bpy.data.filepath
|
||||||
|
|
||||||
|
@ -217,6 +229,32 @@ def open_blender_file(filepath=None):
|
||||||
|
|
||||||
subprocess.Popen(cmd)
|
subprocess.Popen(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def cache(timeout):
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
current_time = perf_counter()
|
||||||
|
cache_key = (*args, *kwargs.items()) # Assuming the first argument is the file path
|
||||||
|
|
||||||
|
# Check if the cache is valid
|
||||||
|
if cache_key in _cache:
|
||||||
|
cached_content, cache_time = _cache[cache_key]
|
||||||
|
if (current_time - cache_time) < timeout:
|
||||||
|
|
||||||
|
return cached_content
|
||||||
|
|
||||||
|
# Execute the function and update the cache
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
_cache[cache_key] = (result, current_time)
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def read_file(path):
|
def read_file(path):
|
||||||
'''Read a file with an extension in (json, yaml, yml, txt)'''
|
'''Read a file with an extension in (json, yaml, yml, txt)'''
|
||||||
|
|
||||||
|
@ -255,6 +293,7 @@ def read_file(path):
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def write_file(path, data, indent=4):
|
def write_file(path, data, indent=4):
|
||||||
'''Read a file with an extension in (json, yaml, yml, text)'''
|
'''Read a file with an extension in (json, yaml, yml, text)'''
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
"""
|
||||||
|
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)
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
from . node import ui as node_ui
|
||||||
|
from . node import operator as node_operator
|
||||||
|
from . material import ui as material_ui
|
||||||
|
from . material import operator as material_operator
|
||||||
|
|
||||||
|
bl_modules = (
|
||||||
|
node_ui,
|
||||||
|
node_operator,
|
||||||
|
material_ui,
|
||||||
|
material_operator
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
"""Register the addon Asset Library for Blender"""
|
||||||
|
|
||||||
|
for mod in bl_modules:
|
||||||
|
mod.register()
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
"""Unregister the addon Asset Library for Blender"""
|
||||||
|
|
||||||
|
for mod in reversed(bl_modules):
|
||||||
|
mod.unregister()
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
from asset_library.data_type.action import (
|
||||||
|
keymaps,
|
||||||
|
operators,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
def register():
|
||||||
|
operators.register()
|
||||||
|
keymaps.register()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
operators.unregister()
|
||||||
|
keymaps.unregister()
|
|
@ -4,9 +4,9 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
#sys.path.append(str(Path(__file__).parents[3]))
|
#sys.path.append(str(Path(__file__).parents[3]))
|
||||||
|
|
||||||
from asset_library.action.concat_preview import mosaic_export
|
from asset_library.data_type.action.concat_preview import mosaic_export
|
||||||
from asset_library.common.file_utils import open_file
|
from asset_library.common.file_utils import open_file
|
||||||
from asset_library.action.functions import reset_bone, get_keyframes
|
from asset_library.data_type.action.functions import reset_bone, get_keyframes
|
||||||
from asset_library.common.functions import read_catalog
|
from asset_library.common.functions import read_catalog
|
||||||
|
|
||||||
import bpy
|
import bpy
|
|
@ -34,7 +34,7 @@ from asset_library.pose.pose_usage import(
|
||||||
flip_side_name
|
flip_side_name
|
||||||
)
|
)
|
||||||
|
|
||||||
from asset_library.action.functions import(
|
from asset_library.data_type.action.functions import(
|
||||||
apply_anim,
|
apply_anim,
|
||||||
append_action,
|
append_action,
|
||||||
clean_action,
|
clean_action,
|
||||||
|
@ -43,29 +43,16 @@ from asset_library.action.functions import(
|
||||||
conform_action
|
conform_action
|
||||||
)
|
)
|
||||||
|
|
||||||
from bpy.props import (
|
from bpy.props import (BoolProperty, CollectionProperty, EnumProperty,
|
||||||
BoolProperty,
|
PointerProperty, StringProperty, IntProperty)
|
||||||
CollectionProperty,
|
|
||||||
EnumProperty,
|
|
||||||
PointerProperty,
|
|
||||||
StringProperty,
|
|
||||||
IntProperty
|
|
||||||
)
|
|
||||||
|
|
||||||
from bpy.types import (
|
from bpy.types import (Action, Context, Event, FileSelectEntry, Object,
|
||||||
Action,
|
Operator, PropertyGroup)
|
||||||
Context,
|
|
||||||
Event,
|
|
||||||
FileSelectEntry,
|
|
||||||
Object,
|
|
||||||
Operator,
|
|
||||||
PropertyGroup,
|
|
||||||
)
|
|
||||||
|
|
||||||
from bpy_extras import asset_utils
|
from bpy_extras import asset_utils
|
||||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||||
|
|
||||||
from asset_library.action.functions import (
|
from asset_library.data_type.action.functions import (
|
||||||
is_pose,
|
is_pose,
|
||||||
get_marker,
|
get_marker,
|
||||||
get_keyframes,
|
get_keyframes,
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
from asset_library.collection import (
|
from asset_library.data_type.collection import (
|
||||||
gui,
|
gui,
|
||||||
operators,
|
operators,
|
||||||
keymaps,
|
keymaps,
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
from asset_library.file import (
|
from asset_library.data_type.file import (
|
||||||
operators, gui, keymaps)
|
operators, gui, keymaps)
|
||||||
|
|
||||||
if 'bpy' in locals():
|
if 'bpy' in locals():
|
|
@ -0,0 +1,107 @@
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ...core.bl_utils import get_bl_cmd
|
||||||
|
from ...core.lib_utils import get_asset_data
|
||||||
|
from ...core.catalog import read_catalog
|
||||||
|
from ... import constants
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy.props import StringProperty, EnumProperty
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_update_materials(Operator):
|
||||||
|
bl_idname = 'assetlibrary.update_materials'
|
||||||
|
bl_label = 'Update node'
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'OBJECT', 'CURRENT')], default="CURRENT")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.active_material
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
asset_libraries = context.preferences.filepaths.asset_libraries
|
||||||
|
|
||||||
|
ob = bpy.context.object
|
||||||
|
ntree = context.space_data.edit_tree
|
||||||
|
ntree_name = ntree.name
|
||||||
|
new_ntree = None
|
||||||
|
|
||||||
|
if self.selection == 'OBJECT':
|
||||||
|
materials = [s.material for s in ob.material_slots if s.material]
|
||||||
|
elif self.selection == 'CURRENT':
|
||||||
|
materials = [ob.active_material]
|
||||||
|
else:
|
||||||
|
materials = list(bpy.data.materials)
|
||||||
|
|
||||||
|
mat_names = set(m.name for m in materials)
|
||||||
|
|
||||||
|
for asset_library in asset_libraries:
|
||||||
|
library_path = Path(asset_library.path)
|
||||||
|
blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()]
|
||||||
|
|
||||||
|
images = list(bpy.data.images)
|
||||||
|
materials = list(bpy.data.materials)# Storing original materials to compare with imported ones
|
||||||
|
|
||||||
|
link = (asset_library.import_method == 'LINK')
|
||||||
|
for blend_file in blend_files:
|
||||||
|
print(blend_file)
|
||||||
|
with bpy.data.libraries.load(str(blend_file), assets_only=True, link=link) as (data_from, data_to):
|
||||||
|
|
||||||
|
import_materials = [n for n in data_from.materials if n in mat_names]
|
||||||
|
print("import_materials", import_materials)
|
||||||
|
data_to.materials = import_materials
|
||||||
|
|
||||||
|
mat_names -= set(import_materials) # Store already updated nodes
|
||||||
|
|
||||||
|
new_materials = set(m for m in bpy.data.materials if m not in materials)
|
||||||
|
new_images = set(i for i in bpy.data.images if i not in images)
|
||||||
|
#
|
||||||
|
|
||||||
|
for new_mat in new_materials:
|
||||||
|
new_mat_name = new_mat.library_weak_reference.id_name[2:]
|
||||||
|
local_mat = next((m for m in bpy.data.materials if m.name == new_mat_name and m != new_mat), None)
|
||||||
|
|
||||||
|
if not local_mat:
|
||||||
|
print(f'No local material {new_mat_name}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f'Merge material {local_mat.name} into {new_mat.name}')
|
||||||
|
|
||||||
|
local_mat.user_remap(new_mat)
|
||||||
|
bpy.data.materials.remove(local_mat)
|
||||||
|
|
||||||
|
if not new_mat.library:
|
||||||
|
new_mat.name = new_mat_name
|
||||||
|
new_mat.asset_clear()
|
||||||
|
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "selection", expand=True)
|
||||||
|
|
||||||
|
|
||||||
|
bl_classes = (
|
||||||
|
ASSETLIB_OT_update_materials,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for bl_class in bl_classes:
|
||||||
|
bpy.utils.register_class(bl_class)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for bl_class in reversed(bl_classes):
|
||||||
|
bpy.utils.unregister_class(bl_class)
|
|
@ -0,0 +1,31 @@
|
||||||
|
"""
|
||||||
|
This module contains blender UI elements in the node editor
|
||||||
|
|
||||||
|
:author: Autour de Minuit
|
||||||
|
:maintainers: Christophe Seux
|
||||||
|
:date: 2024
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def draw_menu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
row = layout.row(align=False)
|
||||||
|
layout.operator('assetlibrary.update_materials', text='Update Materials', icon='MATERIAL')
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
# for c in classes:
|
||||||
|
# bpy.utils.register_class(c)
|
||||||
|
|
||||||
|
bpy.types.ASSETLIB_MT_node_editor.append(draw_menu)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
# for c in reversed(classes):
|
||||||
|
# bpy.utils.unregister_class(c)
|
||||||
|
|
||||||
|
bpy.types.ASSETLIB_MT_node_editor.remove(draw_menu)
|
|
@ -0,0 +1,138 @@
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from ...core.bl_utils import get_bl_cmd
|
||||||
|
from ...core.lib_utils import get_asset_data, asset_library_map
|
||||||
|
from ...core.catalog import read_catalog
|
||||||
|
from ... import constants
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy.props import StringProperty, EnumProperty
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_update_nodes(Operator):
|
||||||
|
bl_idname = 'assetlibrary.update_nodes'
|
||||||
|
bl_label = 'Update node'
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
selection : EnumProperty(items=[(s, s.title(), '') for s in ('ALL', 'SELECTED', 'CURRENT')], default="CURRENT", name='All Nodes')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.space_data.edit_tree
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
asset_libraries = context.preferences.filepaths.asset_libraries
|
||||||
|
|
||||||
|
ntree = context.space_data.edit_tree
|
||||||
|
ntree_name = ntree.name
|
||||||
|
new_ntree = None
|
||||||
|
|
||||||
|
if self.selection == 'SELECTED':
|
||||||
|
nodes = [ n.node_tree for n in context.space_data.edit_tree.nodes
|
||||||
|
if n.type == "GROUP" and n.select]
|
||||||
|
elif self.selection == 'CURRENT':
|
||||||
|
active_node = context.space_data.edit_tree
|
||||||
|
nodes = [active_node]
|
||||||
|
else:
|
||||||
|
nodes = list(bpy.data.node_groups)
|
||||||
|
|
||||||
|
library_map = asset_library_map()
|
||||||
|
|
||||||
|
#node_groups = list(bpy.data.node_groups)
|
||||||
|
#images = list(bpy.data.images)
|
||||||
|
#materials = list(bpy.data.materials)
|
||||||
|
|
||||||
|
with remap_datablock_duplicates():
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
blend_file = find_asset_source(library_map, 'node_groups', node.name)
|
||||||
|
link = bool(node.library)
|
||||||
|
|
||||||
|
with bpy.data.libraries.load(str(blend_file), link=link) as (data_from, data_to):
|
||||||
|
data_to.node_groups = [node.name]
|
||||||
|
|
||||||
|
for new_node_group in new_node_groups:
|
||||||
|
new_node_group_name = new_node_group.library_weak_reference.id_name[2:]
|
||||||
|
local_node_group = next((n for n in bpy.data.node_groups if n.name == new_node_group_name and n != new_node_group), None)
|
||||||
|
|
||||||
|
if not local_node_group:
|
||||||
|
print(f'No local node_group {new_node_group_name}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f'Merge node {local_node_group.name} into {new_node_group.name}')
|
||||||
|
|
||||||
|
local_node_group.user_remap(new_node_group)
|
||||||
|
new_node_group.interface_update(context)
|
||||||
|
bpy.data.node_groups.remove(local_node_group)
|
||||||
|
|
||||||
|
new_node_group.name = new_node_group_name
|
||||||
|
new_node_group.asset_clear()
|
||||||
|
|
||||||
|
|
||||||
|
node_names = set(n.name for n in nodes)
|
||||||
|
|
||||||
|
for asset_library in asset_libraries:
|
||||||
|
library_path = Path(asset_library.path)
|
||||||
|
blend_files = [fp for fp in library_path.glob("**/*.blend") if fp.is_file()]
|
||||||
|
|
||||||
|
node_groups = list(bpy.data.node_groups)# Storing original node_geoup to compare with imported
|
||||||
|
|
||||||
|
link = (asset_library.import_method == 'LINK')
|
||||||
|
for blend_file in blend_files:
|
||||||
|
print(blend_file)
|
||||||
|
with bpy.data.libraries.load(str(blend_file), assets_only=True, link=link) as (data_from, data_to):
|
||||||
|
|
||||||
|
import_node_groups = [n for n in data_from.node_groups if n in node_names]
|
||||||
|
print("import_node_groups", import_node_groups)
|
||||||
|
data_to.node_groups = import_node_groups
|
||||||
|
|
||||||
|
node_names -= set(import_node_groups) # Store already updated nodes
|
||||||
|
|
||||||
|
new_node_groups = set(n for n in bpy.data.node_groups if n not in node_groups)
|
||||||
|
|
||||||
|
for new_node_group in new_node_groups:
|
||||||
|
new_node_group_name = new_node_group.library_weak_reference.id_name[2:]
|
||||||
|
local_node_group = next((n for n in bpy.data.node_groups if n.name == new_node_group_name and n != new_node_group), None)
|
||||||
|
|
||||||
|
if not local_node_group:
|
||||||
|
print(f'No local node_group {new_node_group_name}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f'Merge node {local_node_group.name} into {new_node_group.name}')
|
||||||
|
|
||||||
|
local_node_group.user_remap(new_node_group)
|
||||||
|
new_node_group.interface_update(context)
|
||||||
|
bpy.data.node_groups.remove(local_node_group)
|
||||||
|
|
||||||
|
new_node_group.name = new_node_group_name
|
||||||
|
new_node_group.asset_clear()
|
||||||
|
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "selection", expand=True)
|
||||||
|
|
||||||
|
|
||||||
|
bl_classes = (
|
||||||
|
ASSETLIB_OT_update_nodes,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for bl_class in bl_classes:
|
||||||
|
bpy.utils.register_class(bl_class)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for bl_class in reversed(bl_classes):
|
||||||
|
bpy.utils.unregister_class(bl_class)
|
|
@ -0,0 +1,31 @@
|
||||||
|
"""
|
||||||
|
This module contains blender UI elements in the node editor
|
||||||
|
|
||||||
|
:author: Autour de Minuit
|
||||||
|
:maintainers: Christophe Seux
|
||||||
|
:date: 2024
|
||||||
|
"""
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def draw_menu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
row = layout.row(align=False)
|
||||||
|
layout.operator('assetlibrary.update_nodes', text='Update Node Group', icon='IMPORT')
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
# for c in classes:
|
||||||
|
# bpy.utils.register_class(c)
|
||||||
|
|
||||||
|
bpy.types.ASSETLIB_MT_node_editor.append(draw_menu)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
# for c in reversed(classes):
|
||||||
|
# bpy.utils.unregister_class(c)
|
||||||
|
|
||||||
|
bpy.types.ASSETLIB_MT_node_editor.remove(draw_menu)
|
|
@ -35,7 +35,7 @@ from bpy.types import (
|
||||||
from bpy_extras import asset_utils
|
from bpy_extras import asset_utils
|
||||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||||
|
|
||||||
from asset_library.action.functions import (
|
from asset_library.data_type.action.functions import (
|
||||||
get_marker,
|
get_marker,
|
||||||
get_keyframes,
|
get_keyframes,
|
||||||
)
|
)
|
Binary file not shown.
333
gui.py
333
gui.py
|
@ -1,333 +0,0 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Action Library - GUI definition.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from bpy.types import (
|
|
||||||
AssetHandle,
|
|
||||||
Context,
|
|
||||||
Header,
|
|
||||||
Menu,
|
|
||||||
Panel,
|
|
||||||
UIList,
|
|
||||||
WindowManager,
|
|
||||||
WorkSpace,
|
|
||||||
)
|
|
||||||
|
|
||||||
from bpy_extras import asset_utils
|
|
||||||
from asset_library.common.bl_utils import (
|
|
||||||
get_addon_prefs,
|
|
||||||
get_object_libraries,
|
|
||||||
)
|
|
||||||
|
|
||||||
from asset_library.common.functions import (
|
|
||||||
get_active_library
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def pose_library_panel_poll():
|
|
||||||
return bpy.context.object and bpy.context.object.mode == 'POSE'
|
|
||||||
|
|
||||||
class PoseLibraryPanel:
|
|
||||||
@classmethod
|
|
||||||
def pose_library_panel_poll(cls, context: Context) -> bool:
|
|
||||||
return bool(
|
|
||||||
context.object
|
|
||||||
and context.object.mode == 'POSE'
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: Context) -> bool:
|
|
||||||
return cls.pose_library_panel_poll(context);
|
|
||||||
|
|
||||||
|
|
||||||
class AssetLibraryMenu:
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
from bpy_extras.asset_utils import SpaceAssetInfo
|
|
||||||
return SpaceAssetInfo.is_asset_browser_poll(context)
|
|
||||||
|
|
||||||
|
|
||||||
class ASSETLIB_PT_libraries(Panel):
|
|
||||||
bl_label = "Libraries"
|
|
||||||
bl_space_type = 'VIEW_3D'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Item'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: Context) -> bool:
|
|
||||||
return context.object and get_object_libraries(context.object)
|
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
|
||||||
layout = self.layout
|
|
||||||
|
|
||||||
for f in get_object_libraries(context.object):
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.label(text=f)
|
|
||||||
row.operator("assetlib.open_blend", icon='FILE_BLEND', text='').filepath = f
|
|
||||||
|
|
||||||
'''
|
|
||||||
class ASSETLIB_PT_pose_library_usage(Panel):
|
|
||||||
bl_space_type = 'FILE_BROWSER'
|
|
||||||
bl_region_type = "TOOLS"
|
|
||||||
bl_label = "Action Library"
|
|
||||||
# asset_categories = {'ANIMATIONS'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: Context) -> bool:
|
|
||||||
sp = context.space_data
|
|
||||||
|
|
||||||
if not context.object or not context.object.mode == 'POSE':
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not (sp and sp.type == 'FILE_BROWSER' and sp.browse_mode == 'ASSETS'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
|
||||||
layout = self.layout
|
|
||||||
wm = context.window_manager
|
|
||||||
|
|
||||||
sp = context.space_data
|
|
||||||
sp.params.asset_library_ref
|
|
||||||
|
|
||||||
if sp.params.asset_library_ref == 'LOCAL':
|
|
||||||
col = layout.column(align=True)
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.operator("poselib.create_pose_asset", text="Create Pose", icon='POSE_HLT').activate_new_action = False
|
|
||||||
row.operator("actionlib.replace_pose", text='Replace Pose', icon='FILE_REFRESH')
|
|
||||||
col.operator("actionlib.create_anim_asset", text="Create Anim", icon='ANIM')
|
|
||||||
|
|
||||||
col.separator()
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.operator("actionlib.edit_action", text='Edit Action', icon='ACTION')
|
|
||||||
row.operator("actionlib.clear_action", text='Finish Edit', icon='CHECKBOX_HLT')
|
|
||||||
|
|
||||||
col.separator()
|
|
||||||
col.operator("actionlib.generate_preview", icon='RESTRICT_RENDER_OFF', text="Generate Thumbnail")
|
|
||||||
col.operator("actionlib.update_action_data", icon='FILE_TEXT', text="Update Action Data")
|
|
||||||
else:
|
|
||||||
col = layout.column(align=True)
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.operator("actionlib.store_anim_pose", text='Store Anim/Pose', icon='ACTION')
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class ASSETLIB_PT_pose_library_editing(PoseLibraryPanel, asset_utils.AssetBrowserPanel, Panel):
|
|
||||||
bl_space_type = 'FILE_BROWSER'
|
|
||||||
bl_region_type = "TOOL_PROPS"
|
|
||||||
bl_label = "Metadata"
|
|
||||||
#bl_options = {'HIDE_HEADER'}
|
|
||||||
# asset_categories = {'ANIMATIONS'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context: Context) -> bool:
|
|
||||||
sp = context.space_data
|
|
||||||
|
|
||||||
if not (sp and sp.type == 'FILE_BROWSER' and sp.browse_mode == 'ASSETS'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not (context.active_file and context.active_file.asset_data):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
|
||||||
layout = self.layout
|
|
||||||
|
|
||||||
layout.use_property_split = True
|
|
||||||
asset_data = context.active_file.asset_data
|
|
||||||
metadata = ['camera', 'is_single_frame', 'rest_pose']
|
|
||||||
|
|
||||||
if 'camera' in asset_data.keys():
|
|
||||||
layout.prop(asset_data, f'["camera"]', text='Camera', icon='CAMERA_DATA')
|
|
||||||
if 'is_single_frame' in asset_data.keys():
|
|
||||||
layout.prop(asset_data, f'["is_single_frame"]', text='Is Single Frame')
|
|
||||||
if 'rest_pose' in asset_data.keys():
|
|
||||||
layout.prop(asset_data, f'["rest_pose"]', text='Rest Pose', icon='ACTION')
|
|
||||||
if 'filepath' in asset_data.keys():
|
|
||||||
layout.prop(asset_data, f'["filepath"]', text='Filepath')
|
|
||||||
|
|
||||||
|
|
||||||
class ASSETLIB_MT_context_menu(AssetLibraryMenu, Menu):
|
|
||||||
bl_label = "Asset Library Menu"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
|
|
||||||
cls.poll_message_set("Current editor is not an asset browser")
|
|
||||||
return False
|
|
||||||
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
asset_lib_ref = context.space_data.params.asset_library_ref
|
|
||||||
|
|
||||||
lib = get_active_library()
|
|
||||||
if not lib:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
lib = get_active_library()
|
|
||||||
lib.library_type.draw_context_menu(self.layout)
|
|
||||||
|
|
||||||
|
|
||||||
def is_option_region_visible(context, space):
|
|
||||||
from bpy_extras.asset_utils import SpaceAssetInfo
|
|
||||||
|
|
||||||
if SpaceAssetInfo.is_asset_browser(space):
|
|
||||||
pass
|
|
||||||
# For the File Browser, there must be an operator for there to be options
|
|
||||||
# (irrelevant for the Asset Browser).
|
|
||||||
elif not space.active_operator:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for region in context.area.regions:
|
|
||||||
if region.type == 'TOOL_PROPS' and region.width <= 1:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def draw_assetbrowser_header(self, context):
|
|
||||||
lib = get_active_library()
|
|
||||||
|
|
||||||
if not lib:
|
|
||||||
bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons(self, context)
|
|
||||||
return
|
|
||||||
|
|
||||||
space_data = context.space_data
|
|
||||||
params = context.space_data.params
|
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
|
||||||
row.separator()
|
|
||||||
|
|
||||||
row.operator("assetlib.bundle", icon='UV_SYNC_SELECT', text='').name = lib.name
|
|
||||||
#op
|
|
||||||
#op.clean = False
|
|
||||||
#op.only_recent = True
|
|
||||||
|
|
||||||
lib.library_type.draw_header(row)
|
|
||||||
|
|
||||||
if context.selected_files and context.active_file:
|
|
||||||
row.separator()
|
|
||||||
row.label(text=context.active_file.name)
|
|
||||||
|
|
||||||
row.separator_spacer()
|
|
||||||
|
|
||||||
sub = row.row()
|
|
||||||
sub.ui_units_x = 10
|
|
||||||
sub.prop(params, "filter_search", text="", icon='VIEWZOOM')
|
|
||||||
|
|
||||||
row.separator_spacer()
|
|
||||||
|
|
||||||
row.prop_with_popover(
|
|
||||||
params,
|
|
||||||
"display_type",
|
|
||||||
panel="ASSETBROWSER_PT_display",
|
|
||||||
text="",
|
|
||||||
icon_only=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
row.operator(
|
|
||||||
"screen.region_toggle",
|
|
||||||
text="",
|
|
||||||
icon='PREFERENCES',
|
|
||||||
depress=is_option_region_visible(context, space_data)
|
|
||||||
).region_type = 'TOOL_PROPS'
|
|
||||||
|
|
||||||
|
|
||||||
### Messagebus subscription to monitor asset library changes.
|
|
||||||
_msgbus_owner = object()
|
|
||||||
|
|
||||||
def _on_asset_library_changed() -> None:
|
|
||||||
"""Update areas when a different asset library is selected."""
|
|
||||||
refresh_area_types = {'DOPESHEET_EDITOR', 'VIEW_3D'}
|
|
||||||
for win in bpy.context.window_manager.windows:
|
|
||||||
for area in win.screen.areas:
|
|
||||||
if area.type not in refresh_area_types:
|
|
||||||
continue
|
|
||||||
|
|
||||||
area.tag_redraw()
|
|
||||||
|
|
||||||
def register_message_bus() -> None:
|
|
||||||
|
|
||||||
bpy.msgbus.subscribe_rna(
|
|
||||||
key=(bpy.types.FileAssetSelectParams, "asset_library_ref"),
|
|
||||||
owner=_msgbus_owner,
|
|
||||||
args=(),
|
|
||||||
notify=_on_asset_library_changed,
|
|
||||||
options={'PERSISTENT'},
|
|
||||||
)
|
|
||||||
|
|
||||||
def unregister_message_bus() -> None:
|
|
||||||
bpy.msgbus.clear_by_owner(_msgbus_owner)
|
|
||||||
|
|
||||||
@bpy.app.handlers.persistent
|
|
||||||
def _on_blendfile_load_pre(none, other_none) -> None:
|
|
||||||
# The parameters are required, but both are None.
|
|
||||||
unregister_message_bus()
|
|
||||||
|
|
||||||
@bpy.app.handlers.persistent
|
|
||||||
def _on_blendfile_load_post(none, other_none) -> None:
|
|
||||||
# The parameters are required, but both are None.
|
|
||||||
register_message_bus()
|
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
|
||||||
ASSETLIB_PT_pose_library_editing,
|
|
||||||
#ASSETLIB_PT_pose_library_usage,
|
|
||||||
ASSETLIB_MT_context_menu,
|
|
||||||
ASSETLIB_PT_libraries
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register() -> None:
|
|
||||||
for cls in classes:
|
|
||||||
bpy.utils.register_class(cls)
|
|
||||||
|
|
||||||
bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons = bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons
|
|
||||||
bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons = draw_assetbrowser_header
|
|
||||||
|
|
||||||
#WorkSpace.active_pose_asset_index = bpy.props.IntProperty(
|
|
||||||
# name="Active Pose Asset",
|
|
||||||
# # TODO explain which list the index belongs to, or how it can be used to get the pose.
|
|
||||||
# description="Per workspace index of the active pose asset"
|
|
||||||
#)
|
|
||||||
# Register for window-manager. This is a global property that shouldn't be
|
|
||||||
# written to files.
|
|
||||||
#WindowManager.pose_assets = bpy.props.CollectionProperty(type=AssetHandle)
|
|
||||||
|
|
||||||
# bpy.types.UI_MT_list_item_context_menu.prepend(pose_library_list_item_context_menu)
|
|
||||||
# bpy.types.ASSETLIB_MT_context_menu.prepend(pose_library_list_item_context_menu)
|
|
||||||
# bpy.types.ACTIONLIB_MT_context_menu.prepend(pose_library_list_item_context_menu)
|
|
||||||
#bpy.types.ASSETBROWSER_MT_editor_menus.append(draw_assetbrowser_header)
|
|
||||||
|
|
||||||
register_message_bus()
|
|
||||||
bpy.app.handlers.load_pre.append(_on_blendfile_load_pre)
|
|
||||||
bpy.app.handlers.load_post.append(_on_blendfile_load_post)
|
|
||||||
|
|
||||||
|
|
||||||
def unregister() -> None:
|
|
||||||
for cls in reversed(classes):
|
|
||||||
bpy.utils.unregister_class(cls)
|
|
||||||
|
|
||||||
bpy.types.FILEBROWSER_HT_header.draw_asset_browser_buttons = bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons
|
|
||||||
del bpy.types.FILEBROWSER_HT_header._draw_asset_browser_buttons
|
|
||||||
|
|
||||||
unregister_message_bus()
|
|
||||||
|
|
||||||
#del WorkSpace.active_pose_asset_index
|
|
||||||
#del WindowManager.pose_assets
|
|
||||||
|
|
||||||
# bpy.types.UI_MT_list_item_context_menu.remove(pose_library_list_item_context_menu)
|
|
||||||
# bpy.types.ASSETLIB_MT_context_menu.remove(pose_library_list_item_context_menu)
|
|
||||||
# bpy.types.ACTIONLIB_MT_context_menu.remove(pose_library_list_item_context_menu)
|
|
||||||
#bpy.types.ASSETBROWSER_MT_editor_menus.remove(draw_assetbrowser_header)
|
|
58
keymaps.py
58
keymaps.py
|
@ -1,58 +0,0 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from bpy.app.handlers import persistent
|
|
||||||
|
|
||||||
addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = []
|
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
|
||||||
def copy_play_anim(dummy):
|
|
||||||
wm = bpy.context.window_manager
|
|
||||||
km = wm.keyconfigs.addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER")
|
|
||||||
|
|
||||||
km_frames = wm.keyconfigs.user.keymaps.get('Frames')
|
|
||||||
if km_frames:
|
|
||||||
play = km_frames.keymap_items.get('screen.animation_play')
|
|
||||||
if play:
|
|
||||||
kmi = km.keymap_items.new(
|
|
||||||
"assetlib.play_preview",
|
|
||||||
play.type, play.value,
|
|
||||||
any=play.any, shift=play.shift, ctrl=play.ctrl, alt=play.alt,
|
|
||||||
oskey=play.oskey, key_modifier=play.key_modifier,
|
|
||||||
)
|
|
||||||
addon_keymaps.append((km, kmi))
|
|
||||||
|
|
||||||
|
|
||||||
def register() -> None:
|
|
||||||
wm = bpy.context.window_manager
|
|
||||||
if wm.keyconfigs.addon is None:
|
|
||||||
# This happens when Blender is running in the background.
|
|
||||||
return
|
|
||||||
|
|
||||||
km = wm.keyconfigs.addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER")
|
|
||||||
|
|
||||||
kmi = km.keymap_items.new("wm.call_menu", "RIGHTMOUSE", "PRESS")
|
|
||||||
kmi.properties.name = 'ASSETLIB_MT_context_menu'
|
|
||||||
addon_keymaps.append((km, kmi))
|
|
||||||
|
|
||||||
kmi = km.keymap_items.new("assetlib.play_preview", "SPACE", "PRESS")
|
|
||||||
addon_keymaps.append((km, kmi))
|
|
||||||
|
|
||||||
# km = addon.keymaps.new(name = "Grease Pencil Stroke Paint Mode", space_type = "EMPTY")
|
|
||||||
# kmi = km.keymap_items.new('wm.call_panel', type='F2', value='PRESS')
|
|
||||||
|
|
||||||
if 'copy_play_anim' not in [hand.__name__ for hand in bpy.app.handlers.load_post]:
|
|
||||||
bpy.app.handlers.load_post.append(copy_play_anim)
|
|
||||||
|
|
||||||
def unregister() -> None:
|
|
||||||
# Clear shortcuts from the keymap.
|
|
||||||
if 'copy_play_anim' in [hand.__name__ for hand in bpy.app.handlers.load_post]:
|
|
||||||
bpy.app.handlers.load_post.remove(copy_play_anim)
|
|
||||||
|
|
||||||
for km, kmi in addon_keymaps:
|
|
||||||
km.keymap_items.remove(kmi)
|
|
||||||
addon_keymaps.clear()
|
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
from asset_library.library_types import library_type
|
|
||||||
from asset_library.library_types import copy_folder
|
|
||||||
from asset_library.library_types import scan_folder
|
|
||||||
|
|
||||||
if 'bpy' in locals():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(library_type)
|
|
||||||
importlib.reload(copy_folder)
|
|
||||||
importlib.reload(scan_folder)
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
LibraryType = library_type.LibraryType
|
|
||||||
CopyFolder = copy_folder.CopyFolder
|
|
||||||
ScanFolder = scan_folder.ScanFolder
|
|
1224
operators.py
1224
operators.py
File diff suppressed because it is too large
Load Diff
|
@ -4,9 +4,9 @@ Plugin for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from asset_library.library_types.scan_folder import ScanFolder
|
from asset_library.plugins.scan_folder import ScanFolder
|
||||||
from asset_library.common.bl_utils import load_datablocks
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
from asset_library.common.template import Template
|
from asset_library.core.template import Template
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
|
@ -3,15 +3,18 @@
|
||||||
Adapter for making an asset library of all blender file found in a folder
|
Adapter for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from asset_library.library_types.library_type import LibraryType
|
|
||||||
from asset_library.common.file_utils import copy_dir
|
|
||||||
from bpy.props import StringProperty
|
|
||||||
from os.path import expandvars
|
from os.path import expandvars
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
from bpy.props import StringProperty
|
||||||
|
|
||||||
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
|
from asset_library.core.file_utils import copy_dir
|
||||||
|
|
||||||
|
|
||||||
class CopyFolder(LibraryType):
|
|
||||||
|
|
||||||
|
class CopyFolder(LibraryPlugin):
|
||||||
"""Copy library folder from a server to a local disk for better performance"""
|
"""Copy library folder from a server to a local disk for better performance"""
|
||||||
|
|
||||||
name = "Copy Folder"
|
name = "Copy Folder"
|
|
@ -3,13 +3,6 @@
|
||||||
Plugin for making an asset library of all blender file found in a folder
|
Plugin for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from asset_library.library_types.library_type import LibraryType
|
|
||||||
from asset_library.common.template import Template
|
|
||||||
from asset_library.common.file_utils import install_module
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
|
@ -21,8 +14,15 @@ import urllib3
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
||||||
|
|
||||||
class Kitsu(LibraryType):
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
|
from asset_library.core.template import Template
|
||||||
|
from asset_library.core.file_utils import install_module
|
||||||
|
|
||||||
|
|
||||||
|
class Kitsu(LibraryPlugin):
|
||||||
|
|
||||||
name = "Kitsu"
|
name = "Kitsu"
|
||||||
template_name : StringProperty()
|
template_name : StringProperty()
|
|
@ -1,42 +1,43 @@
|
||||||
|
|
||||||
#from asset_library.common.functions import (norm_asset_datas,)
|
|
||||||
from asset_library.common.bl_utils import get_addon_prefs, load_datablocks
|
|
||||||
from asset_library.common.file_utils import read_file, write_file
|
|
||||||
from asset_library.common.template import Template
|
|
||||||
from asset_library.constants import (MODULE_DIR, RESOURCES_DIR)
|
|
||||||
|
|
||||||
from asset_library import (action, collection, file)
|
|
||||||
from asset_library.common.library_cache import LibraryCacheDiff
|
|
||||||
|
|
||||||
from bpy.types import PropertyGroup
|
|
||||||
from bpy.props import StringProperty
|
|
||||||
import bpy
|
|
||||||
from bpy_extras import asset_utils
|
|
||||||
|
|
||||||
from itertools import groupby
|
|
||||||
from pathlib import Path
|
|
||||||
import shutil
|
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
from functools import partial
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from itertools import groupby
|
||||||
|
from functools import partial
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy_extras import asset_utils
|
||||||
|
from bpy.types import PropertyGroup
|
||||||
|
from bpy.props import StringProperty
|
||||||
|
|
||||||
class LibraryType(PropertyGroup):
|
#from asset_library.common.functions import (norm_asset_datas,)
|
||||||
|
from asset_library.core.bl_utils import get_addon_prefs, load_datablocks
|
||||||
|
from asset_library.core.file_utils import read_file, write_file
|
||||||
|
from asset_library.core.template import Template
|
||||||
|
from asset_library.constants import (MODULE_DIR, RESOURCES_DIR)
|
||||||
|
|
||||||
|
#from asset_library.data_type import (action, collection, file)
|
||||||
|
#from asset_library.common.library_cache import LibraryCacheDiff
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LibraryPlugin(PropertyGroup):
|
||||||
|
|
||||||
#def __init__(self):
|
#def __init__(self):
|
||||||
name = "Base Adapter"
|
#name = "Base Adapter"
|
||||||
#library = None
|
#library = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def library(self):
|
def library(self):
|
||||||
prefs = self.addon_prefs
|
prefs = self.addon_prefs
|
||||||
for lib in prefs.libraries:
|
for lib in prefs.libraries:
|
||||||
if lib.library_type == self:
|
if lib.plugin == self:
|
||||||
return lib
|
return lib
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -104,7 +105,7 @@ class LibraryType(PropertyGroup):
|
||||||
return self.library.read_cache(filepath=filepath)
|
return self.library.read_cache(filepath=filepath)
|
||||||
|
|
||||||
def fetch(self):
|
def fetch(self):
|
||||||
raise Exception('This method need to be define in the library_type')
|
raise Exception('This method need to be define in the plugin')
|
||||||
|
|
||||||
def norm_file_name(self, name):
|
def norm_file_name(self, name):
|
||||||
return name.replace(' ', '_')
|
return name.replace(' ', '_')
|
||||||
|
@ -186,7 +187,7 @@ class LibraryType(PropertyGroup):
|
||||||
|
|
||||||
if 'filepath' in asset_handle.asset_data:
|
if 'filepath' in asset_handle.asset_data:
|
||||||
asset_path = asset_handle.asset_data['filepath']
|
asset_path = asset_handle.asset_data['filepath']
|
||||||
asset_path = lib.library_type.format_path(asset_path)
|
asset_path = lib.plugin.format_path(asset_path)
|
||||||
else:
|
else:
|
||||||
asset_path = bpy.types.AssetHandle.get_full_library_path(
|
asset_path = bpy.types.AssetHandle.get_full_library_path(
|
||||||
asset_handle, bpy.context.asset_library_ref
|
asset_handle, bpy.context.asset_library_ref
|
||||||
|
@ -195,22 +196,22 @@ class LibraryType(PropertyGroup):
|
||||||
return asset_path
|
return asset_path
|
||||||
|
|
||||||
def generate_previews(self):
|
def generate_previews(self):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def get_image_path(self, name, catalog, filepath):
|
def get_image_path(self, name, catalog, filepath):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def get_video_path(self, name, catalog, filepath):
|
def get_video_path(self, name, catalog, filepath):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def new_asset(self, asset, asset_cache):
|
def new_asset(self, asset, asset_cache):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def remove_asset(self, asset, asset_cache):
|
def remove_asset(self, asset, asset_cache):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def set_asset_preview(self, asset, asset_cache):
|
def set_asset_preview(self, asset, asset_cache):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def format_asset_data(self, data):
|
def format_asset_data(self, data):
|
||||||
"""Get a dict for use in template fields"""
|
"""Get a dict for use in template fields"""
|
||||||
|
@ -321,7 +322,7 @@ class LibraryType(PropertyGroup):
|
||||||
# return write_file(cache_path, list(asset_infos))
|
# return write_file(cache_path, list(asset_infos))
|
||||||
|
|
||||||
def prop_rel_path(self, path, prop):
|
def prop_rel_path(self, path, prop):
|
||||||
'''Get a filepath relative to a property of the library_type'''
|
'''Get a filepath relative to a property of the plugin'''
|
||||||
field_prop = '{%s}/'%prop
|
field_prop = '{%s}/'%prop
|
||||||
|
|
||||||
prop_value = getattr(self, prop)
|
prop_value = getattr(self, prop)
|
||||||
|
@ -595,7 +596,7 @@ class LibraryType(PropertyGroup):
|
||||||
|
|
||||||
# Write the cache in a temporary file for the generate preview script
|
# Write the cache in a temporary file for the generate preview script
|
||||||
tmp_cache_file = cache.write(tmp=True)
|
tmp_cache_file = cache.write(tmp=True)
|
||||||
bpy.ops.assetlib.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
|
bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
|
||||||
|
|
||||||
elif isinstance(cache_diff, (Path, str)):
|
elif isinstance(cache_diff, (Path, str)):
|
||||||
cache_diff = LibraryCacheDiff(cache_diff).read()#json.loads(Path(cache_diff).read_text(encoding='utf-8'))
|
cache_diff = LibraryCacheDiff(cache_diff).read()#json.loads(Path(cache_diff).read_text(encoding='utf-8'))
|
||||||
|
@ -764,7 +765,7 @@ class LibraryType(PropertyGroup):
|
||||||
# return list(new_cache.values()), cache_diff
|
# return list(new_cache.values()), cache_diff
|
||||||
|
|
||||||
def draw_prefs(self, layout):
|
def draw_prefs(self, layout):
|
||||||
"""Draw the options in the addon preference for this library_type"""
|
"""Draw the options in the addon preference for this plugin"""
|
||||||
|
|
||||||
annotations = self.__class__.__annotations__
|
annotations = self.__class__.__annotations__
|
||||||
for k, v in annotations.items():
|
for k, v in annotations.items():
|
|
@ -3,31 +3,31 @@
|
||||||
Plugin for making an asset library of all blender file found in a folder
|
Plugin for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from asset_library.library_types.library_type import LibraryType
|
|
||||||
from asset_library.common.template import Template
|
|
||||||
from asset_library.common.file_utils import install_module
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty)
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
from itertools import groupby
|
|
||||||
import uuid
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import uuid
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import urllib3
|
import urllib3
|
||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
|
from itertools import groupby
|
||||||
|
from pathlib import Path
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty)
|
||||||
|
|
||||||
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
|
from asset_library.core.template import Template
|
||||||
|
from asset_library.core.file_utils import install_module
|
||||||
|
|
||||||
|
|
||||||
REQ_HEADERS = requests.utils.default_headers()
|
REQ_HEADERS = requests.utils.default_headers()
|
||||||
REQ_HEADERS.update({"User-Agent": "Blender: PH Assets"})
|
REQ_HEADERS.update({"User-Agent": "Blender: PH Assets"})
|
||||||
|
|
||||||
class PolyHaven(LibraryType):
|
class PolyHaven(LibraryPlugin):
|
||||||
|
|
||||||
name = "Poly Haven"
|
name = "Poly Haven"
|
||||||
# template_name : StringProperty()
|
# template_name : StringProperty()
|
|
@ -3,24 +3,25 @@
|
||||||
Plugin for making an asset library of all blender file found in a folder
|
Plugin for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from asset_library.library_types.library_type import LibraryType
|
|
||||||
from asset_library.common.bl_utils import load_datablocks
|
|
||||||
from asset_library.common.template import Template
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
|
||||||
from itertools import groupby
|
|
||||||
import uuid
|
import uuid
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
||||||
|
|
||||||
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
|
from asset_library.core.template import Template
|
||||||
|
|
||||||
|
|
||||||
class ScanFolder(LibraryType):
|
|
||||||
|
class ScanFolder(LibraryPlugin):
|
||||||
|
|
||||||
name = "Scan Folder"
|
name = "Scan Folder"
|
||||||
source_directory : StringProperty(subtype='DIR_PATH')
|
source_directory : StringProperty(subtype='DIR_PATH')
|
||||||
|
@ -57,10 +58,10 @@ class ScanFolder(LibraryType):
|
||||||
return self.format_path(self.source_template_video, dict(name=name, catalog=catalog, filepath=filepath))
|
return self.format_path(self.source_template_video, dict(name=name, catalog=catalog, filepath=filepath))
|
||||||
|
|
||||||
def new_asset(self, asset, asset_data):
|
def new_asset(self, asset, asset_data):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
def remove_asset(self, asset, asset_data):
|
def remove_asset(self, asset, asset_data):
|
||||||
raise Exception('Need to be defined in the library_type')
|
raise Exception('Need to be defined in the plugin')
|
||||||
|
|
||||||
'''
|
'''
|
||||||
def format_asset_info(self, asset_datas, asset_path, modified=None):
|
def format_asset_info(self, asset_datas, asset_path, modified=None):
|
||||||
|
@ -140,7 +141,7 @@ class ScanFolder(LibraryType):
|
||||||
|
|
||||||
# Write the cache in a temporary file for the generate preview script
|
# Write the cache in a temporary file for the generate preview script
|
||||||
tmp_cache_file = cache.write(tmp=True)
|
tmp_cache_file = cache.write(tmp=True)
|
||||||
bpy.ops.assetlib.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
|
bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
|
||||||
|
|
||||||
elif isinstance(cache_diff, (Path, str)):
|
elif isinstance(cache_diff, (Path, str)):
|
||||||
cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8'))
|
cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8'))
|
765
preferences.py
765
preferences.py
|
@ -1,774 +1,59 @@
|
||||||
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import os
|
from bpy.types import AddonPreferences
|
||||||
from os.path import abspath, join
|
from bpy.props import (CollectionProperty, StringProperty)
|
||||||
|
|
||||||
from bpy.types import (AddonPreferences, PointerProperty, PropertyGroup)
|
from . properties import AssetLibrary
|
||||||
from bpy.props import (BoolProperty, StringProperty, CollectionProperty,
|
from . core.bl_utils import get_addon_prefs
|
||||||
EnumProperty, IntProperty)
|
from . core.lib_utils import update_library_path
|
||||||
|
|
||||||
from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS,
|
|
||||||
ICONS, RESOURCES_DIR, LIBRARY_TYPE_DIR, LIBRARY_TYPES, ADAPTERS)
|
|
||||||
|
|
||||||
from asset_library.common.file_utils import import_module_from_path, norm_str
|
|
||||||
from asset_library.common.bl_utils import get_addon_prefs
|
|
||||||
from asset_library.common.library_cache import LibraryCache
|
|
||||||
from asset_library.common.catalog import Catalog
|
|
||||||
#from asset_library.common.functions import get_catalog_path
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import importlib
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
|
|
||||||
def update_library_config(self, context):
|
|
||||||
print('update_library_config not yet implemented')
|
|
||||||
|
|
||||||
def update_library_path(self, context):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
self['bundle_directory'] = str(self.library_path)
|
|
||||||
|
|
||||||
if not self.custom_bundle_name:
|
|
||||||
self['custom_bundle_name'] = self.name
|
|
||||||
|
|
||||||
if not self.custom_bundle_directory:
|
|
||||||
custom_bundle_dir = Path(prefs.bundle_directory, self.library_name).resolve()
|
|
||||||
self['custom_bundle_directory'] = str(custom_bundle_dir)
|
|
||||||
|
|
||||||
#if self.custom_bundle_directory:
|
|
||||||
# self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory))
|
|
||||||
#else:
|
|
||||||
# bundle_directory = join(prefs.bundle_directory, norm_str(self.name))
|
|
||||||
# self['custom_bundle_directory'] = abspath(bundle_directory)
|
|
||||||
|
|
||||||
self.set_library_path()
|
|
||||||
|
|
||||||
def update_all_library_path(self, context):
|
|
||||||
#print('update_all_assetlib_paths')
|
|
||||||
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
#if self.custom_bundle_directory:
|
|
||||||
# self['custom_bundle_directory'] = abspath(bpy.path.abspath(self.custom_bundle_directory))
|
|
||||||
|
|
||||||
for lib in prefs.libraries:
|
|
||||||
update_library_path(lib, context)
|
|
||||||
#lib.set_library_path()
|
|
||||||
|
|
||||||
def get_library_type_items(self, context):
|
|
||||||
#prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
items = [('NONE', 'None', '', 0)]
|
|
||||||
items += [(norm_str(a.name, format=str.upper), a.name, "", i+1) for i, a in enumerate(LIBRARY_TYPES)]
|
|
||||||
return items
|
|
||||||
|
|
||||||
def get_adapters_items(self, context):
|
|
||||||
#prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
items = [('NONE', 'None', '', 0)]
|
|
||||||
items += [(norm_str(a.name, format=str.upper), a.name, "", i+1) for i, a in enumerate(ADAPTERS)]
|
|
||||||
return items
|
|
||||||
|
|
||||||
|
|
||||||
def get_library_items(self, context):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
items = [('NONE', 'None', '', 0)]
|
|
||||||
items += [(l.name, l.name, "", i+1) for i, l in enumerate(prefs.libraries) if l != self]
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def get_store_library_items(self, context):
|
|
||||||
#prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
#libraries = [l for l in prefs.libraries if l.merge_library == self.name]
|
|
||||||
|
|
||||||
return [(l.name, l.name, "", i) for i, l in enumerate([self] + self.merge_libraries)]
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryTypes(PropertyGroup):
|
|
||||||
def __iter__(self):
|
|
||||||
return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name'))
|
|
||||||
|
|
||||||
|
|
||||||
class Adapters(PropertyGroup):
|
|
||||||
def __iter__(self):
|
|
||||||
return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name'))
|
|
||||||
|
|
||||||
|
|
||||||
class AssetLibrary(PropertyGroup):
|
|
||||||
name : StringProperty(name='Name', default='Action Library', update=update_library_path)
|
|
||||||
id : StringProperty()
|
|
||||||
auto_bundle : BoolProperty(name='Auto Bundle', default=False)
|
|
||||||
expand : BoolProperty(name='Expand', default=False)
|
|
||||||
use : BoolProperty(name='Use', default=True, update=update_library_path)
|
|
||||||
data_type : EnumProperty(name='Type', items=DATA_TYPE_ITEMS, default='COLLECTION')
|
|
||||||
|
|
||||||
|
|
||||||
#template_image : StringProperty(default='', description='../{name}_image.png')
|
|
||||||
#template_video : StringProperty(default='', description='../{name}_video.mov')
|
|
||||||
#template_info : StringProperty(default='', description='../{name}_asset_info.json')
|
|
||||||
|
|
||||||
bundle_directory : StringProperty(
|
|
||||||
name="Bundle Directory",
|
|
||||||
subtype='DIR_PATH',
|
|
||||||
default=''
|
|
||||||
)
|
|
||||||
|
|
||||||
use_custom_bundle_directory : BoolProperty(default=False, update=update_library_path)
|
|
||||||
custom_bundle_directory : StringProperty(
|
|
||||||
name="Bundle Directory",
|
|
||||||
subtype='DIR_PATH',
|
|
||||||
default='',
|
|
||||||
update=update_library_path
|
|
||||||
)
|
|
||||||
#use_merge : BoolProperty(default=False, update=update_library_path)
|
|
||||||
|
|
||||||
use_custom_bundle_name : BoolProperty(default=False, update=update_library_path)
|
|
||||||
custom_bundle_name : StringProperty(name='Merge Name', update=update_library_path)
|
|
||||||
#merge_library : EnumProperty(name='Merge Library', items=get_library_items, update=update_library_path)
|
|
||||||
#merge_name : StringProperty(name='Merge Name', update=update_library_path)
|
|
||||||
|
|
||||||
#Library when adding an asset to the library if merge with another
|
|
||||||
store_library: EnumProperty(items=get_store_library_items, name="Library")
|
|
||||||
|
|
||||||
template: StringProperty()
|
|
||||||
expand_extra : BoolProperty(name='Expand', default=False)
|
|
||||||
blend_depth : IntProperty(name='Blend Depth', default=1)
|
|
||||||
|
|
||||||
# source_directory : StringProperty(
|
|
||||||
# name="Path",
|
|
||||||
# subtype='DIR_PATH',
|
|
||||||
# default='',
|
|
||||||
# update=update_library_path
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
#library_type : EnumProperty(items=library_type_ITEMS)
|
|
||||||
library_types : bpy.props.PointerProperty(type=LibraryTypes)
|
|
||||||
library_type_name : EnumProperty(items=get_library_type_items)
|
|
||||||
|
|
||||||
adapters : bpy.props.PointerProperty(type=Adapters)
|
|
||||||
adapter_name : EnumProperty(items=get_adapters_items)
|
|
||||||
|
|
||||||
parent_name : StringProperty()
|
|
||||||
|
|
||||||
# data_file_path : StringProperty(
|
|
||||||
# name="Path",
|
|
||||||
# subtype='FILE_PATH',
|
|
||||||
# default='',
|
|
||||||
# )
|
|
||||||
|
|
||||||
#def __init__(self):
|
|
||||||
# self.library_types.parent = self
|
|
||||||
|
|
||||||
@property
|
|
||||||
def parent(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
if self.parent_name:
|
|
||||||
return prefs.libraries[self.parent_name]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def merge_libraries(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
return [l for l in prefs.libraries if l != self and (l.library_path == self.library_path)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def child_libraries(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
return [l for l in prefs.libraries if l != self and (l.parent == self)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data_types(self):
|
|
||||||
data_type = self.data_type
|
|
||||||
if data_type == 'FILE':
|
|
||||||
data_type = 'COLLECTION'
|
|
||||||
return f'{data_type.lower()}s'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_type(self):
|
|
||||||
name = norm_str(self.library_type_name)
|
|
||||||
if not hasattr(self.library_types, name):
|
|
||||||
return
|
|
||||||
|
|
||||||
return getattr(self.library_types, name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def adapter(self):
|
|
||||||
name = norm_str(self.adapter_name)
|
|
||||||
if not hasattr(self.adapters, name):
|
|
||||||
return
|
|
||||||
|
|
||||||
return getattr(self.adapters, name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
asset_lib_ref = bpy.context.space_data.params.asset_library_ref
|
|
||||||
|
|
||||||
#TODO work also outside asset_library_area
|
|
||||||
if asset_lib_ref not in prefs.libraries:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return prefs.libraries[asset_lib_ref]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_path(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
library_name = self.library_name
|
|
||||||
#if not self.use_custom_bundle_name:
|
|
||||||
# library_name = norm_str(library_name)
|
|
||||||
|
|
||||||
if self.use_custom_bundle_directory:
|
|
||||||
return Path(self.custom_bundle_directory).resolve()
|
|
||||||
else:
|
|
||||||
library_name = norm_str(library_name)
|
|
||||||
return Path(prefs.bundle_directory, library_name).resolve()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bundle_dir(self):
|
|
||||||
return self.library_path.as_posix()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def library_name(self):
|
|
||||||
if self.use_custom_bundle_name:
|
|
||||||
return self.custom_bundle_name
|
|
||||||
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def read_catalog(self):
|
|
||||||
return Catalog(self.library_path).read()
|
|
||||||
|
|
||||||
def read_cache(self, filepath=None):
|
|
||||||
if filepath:
|
|
||||||
return LibraryCache(filepath).read()
|
|
||||||
|
|
||||||
return LibraryCache.from_library(self).read()
|
|
||||||
|
|
||||||
def clear_library_path(self):
|
|
||||||
#print('Clear Library Path', self.name)
|
|
||||||
|
|
||||||
prefs = bpy.context.preferences
|
|
||||||
libs = prefs.filepaths.asset_libraries
|
|
||||||
|
|
||||||
#path = self.library_path.as_posix()
|
|
||||||
|
|
||||||
for l in reversed(libs):
|
|
||||||
#lib_path = Path(l.path).resolve().as_posix()
|
|
||||||
|
|
||||||
prev_name = self.get('asset_library') or self.library_name
|
|
||||||
|
|
||||||
#print(l.name, prev_name)
|
|
||||||
|
|
||||||
if (l.name == prev_name):
|
|
||||||
index = list(libs).index(l)
|
|
||||||
try:
|
|
||||||
bpy.ops.preferences.asset_library_remove(index=index)
|
|
||||||
return
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
#print('No library removed')
|
|
||||||
|
|
||||||
def set_dict(self, data, obj=None):
|
|
||||||
""""Recursive method to set all attribute from a dict to this instance"""
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
obj = self
|
|
||||||
|
|
||||||
# Make shure the input dict is not modidied
|
|
||||||
data = data.copy()
|
|
||||||
|
|
||||||
#print(obj)
|
|
||||||
|
|
||||||
for key, value in data.items():
|
|
||||||
if isinstance(value, dict):
|
|
||||||
|
|
||||||
if 'name' in value:
|
|
||||||
setattr(obj, f'{key}_name', value.pop('name'))
|
|
||||||
|
|
||||||
#print('Nested value', getattr(obj, key))
|
|
||||||
self.set_dict(value, obj=getattr(obj, key))
|
|
||||||
|
|
||||||
elif key in obj.bl_rna.properties.keys():
|
|
||||||
if key == 'id':
|
|
||||||
value = str(value)
|
|
||||||
|
|
||||||
elif key == 'custom_bundle_name':
|
|
||||||
if not 'use_custom_bundle_name' in data.values():
|
|
||||||
obj["use_custom_bundle_name"] = True
|
|
||||||
|
|
||||||
elif isinstance(value, str):
|
|
||||||
value = os.path.expandvars(value)
|
|
||||||
value = os.path.expanduser(value)
|
|
||||||
|
|
||||||
#print('set attr', key, value)
|
|
||||||
setattr(obj, key, value)
|
|
||||||
#obj[key] = value
|
|
||||||
|
|
||||||
else:
|
|
||||||
print(f'Prop {key} of {obj} not exist')
|
|
||||||
|
|
||||||
self['bundle_directory'] = str(self.library_path)
|
|
||||||
|
|
||||||
if not self.custom_bundle_name:
|
|
||||||
self['custom_bundle_name'] = self.name
|
|
||||||
|
|
||||||
# self.library_type_name = data['library_type']
|
|
||||||
# if not self.library_type:
|
|
||||||
# print(f"No library_type named {data['library_type']}")
|
|
||||||
# return
|
|
||||||
|
|
||||||
|
|
||||||
# for key, value in data.items():
|
|
||||||
# if key == 'options':
|
|
||||||
# for k, v in data['options'].items():
|
|
||||||
# setattr(self.library_type, k, v)
|
|
||||||
# elif key in self.bl_rna.properties.keys():
|
|
||||||
# if key == 'id':
|
|
||||||
# value = str(value)
|
|
||||||
|
|
||||||
# if key == 'custom_bundle_name':
|
|
||||||
# if not 'use_custom_bundle_name' in data.values():
|
|
||||||
# self["use_custom_bundle_name"] = True
|
|
||||||
|
|
||||||
# self[key] = value
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
data = {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}
|
|
||||||
|
|
||||||
if self.library_type:
|
|
||||||
data['library_type'] = self.library_type.to_dict()
|
|
||||||
data['library_type']['name'] = data.pop('library_type_name')
|
|
||||||
del data['library_types']
|
|
||||||
|
|
||||||
if self.adapter:
|
|
||||||
data['adapter'] = self.adapter.to_dict()
|
|
||||||
data['adapter']['name'] = data.pop('adapter_name')
|
|
||||||
del data['adapters']
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def set_library_path(self):
|
|
||||||
'''Update the Blender Preference Filepaths tab with the addon libraries'''
|
|
||||||
|
|
||||||
prefs = bpy.context.preferences
|
|
||||||
name = self.library_name
|
|
||||||
lib_path = self.library_path
|
|
||||||
|
|
||||||
self.clear_library_path()
|
|
||||||
|
|
||||||
|
|
||||||
if not self.use or not lib_path:
|
|
||||||
# if all(not l.use for l in self.merge_libraries):
|
|
||||||
# self.clear_library_path()
|
|
||||||
return
|
|
||||||
|
|
||||||
# lib = None
|
|
||||||
# if self.get('asset_library'):
|
|
||||||
# #print('old_name', self['asset_library'])
|
|
||||||
# lib = prefs.filepaths.asset_libraries.get(self['asset_library'])
|
|
||||||
|
|
||||||
# if not lib:
|
|
||||||
# #print('keys', prefs.filepaths.asset_libraries.keys())
|
|
||||||
# #print('name', name)
|
|
||||||
# #print(prefs.filepaths.asset_libraries.get(name))
|
|
||||||
# lib = prefs.filepaths.asset_libraries.get(name)
|
|
||||||
|
|
||||||
# Create the Asset Library Path
|
|
||||||
lib = prefs.filepaths.asset_libraries.get(name)
|
|
||||||
if not lib:
|
|
||||||
#print(f'Creating the lib {name}')
|
|
||||||
try:
|
|
||||||
bpy.ops.preferences.asset_library_add(directory=str(lib_path))
|
|
||||||
except AttributeError:
|
|
||||||
return
|
|
||||||
|
|
||||||
lib = prefs.filepaths.asset_libraries[-1]
|
|
||||||
|
|
||||||
lib.name = name
|
|
||||||
|
|
||||||
self['asset_library'] = name
|
|
||||||
lib.path = str(lib_path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_user(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
return self in prefs.user_libraries.values()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_env(self):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
return self in prefs.env_libraries.values()
|
|
||||||
|
|
||||||
|
|
||||||
def add_row(self, layout, data=None, prop=None, label='',
|
|
||||||
boolean=None, factor=0.39):
|
|
||||||
'''Act like the use_property_split but with more control'''
|
|
||||||
|
|
||||||
enabled = True
|
|
||||||
split = layout.split(factor=factor, align=True)
|
|
||||||
|
|
||||||
row = split.row(align=False)
|
|
||||||
row.use_property_split = False
|
|
||||||
row.alignment= 'RIGHT'
|
|
||||||
row.label(text=str(label))
|
|
||||||
if boolean:
|
|
||||||
boolean_data = self
|
|
||||||
if isinstance(boolean, (list, tuple)):
|
|
||||||
boolean_data, boolean = boolean
|
|
||||||
|
|
||||||
row.prop(boolean_data, boolean, text='')
|
|
||||||
enabled = getattr(boolean_data, boolean)
|
|
||||||
|
|
||||||
row = split.row(align=True)
|
|
||||||
row.enabled = enabled
|
|
||||||
|
|
||||||
if isinstance(data, str):
|
|
||||||
row.label(text=data)
|
|
||||||
else:
|
|
||||||
row.prop(data or self, prop, text='')
|
|
||||||
|
|
||||||
return split
|
|
||||||
|
|
||||||
|
|
||||||
def draw_operators(self, layout):
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.alignment = 'RIGHT'
|
|
||||||
row.prop(self, 'library_type_name', text='')
|
|
||||||
row.prop(self, 'auto_bundle', text='', icon='UV_SYNC_SELECT')
|
|
||||||
|
|
||||||
row.operator("assetlib.diff", text='', icon='FILE_REFRESH').name = self.name
|
|
||||||
|
|
||||||
op = row.operator("assetlib.bundle", icon='MOD_BUILD', text='')
|
|
||||||
op.name = self.name
|
|
||||||
|
|
||||||
layout.separator(factor=3)
|
|
||||||
|
|
||||||
def draw(self, layout):
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
#box = layout.box()
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
#row.use_property_split = False
|
|
||||||
|
|
||||||
#row.alignment = 'LEFT'
|
|
||||||
icon = "DISCLOSURE_TRI_DOWN" if self.expand else "DISCLOSURE_TRI_RIGHT"
|
|
||||||
row.prop(self, 'expand', icon=icon, emboss=False, text='')
|
|
||||||
|
|
||||||
if self.is_user:
|
|
||||||
row.prop(self, 'use', text='')
|
|
||||||
row.prop(self, 'data_type', icon_only=True, emboss=False)
|
|
||||||
row.prop(self, 'name', text='')
|
|
||||||
|
|
||||||
self.draw_operators(row)
|
|
||||||
|
|
||||||
index = list(prefs.user_libraries).index(self)
|
|
||||||
row.operator("assetlib.remove_user_library", icon="X", text='', emboss=False).index = index
|
|
||||||
|
|
||||||
else:
|
|
||||||
row.prop(self, 'use', text='')
|
|
||||||
row.label(icon=ICONS[self.data_type])
|
|
||||||
#row.label(text=self.name)
|
|
||||||
subrow = row.row(align=True)
|
|
||||||
subrow.alignment = 'LEFT'
|
|
||||||
subrow.prop(self, 'expand', emboss=False, text=self.name)
|
|
||||||
#row.separator_spacer()
|
|
||||||
|
|
||||||
self.draw_operators(row)
|
|
||||||
|
|
||||||
sub_row = row.row()
|
|
||||||
sub_row.enabled = False
|
|
||||||
sub_row.label(icon='FAKE_USER_ON')
|
|
||||||
|
|
||||||
if self.expand:
|
|
||||||
col = layout.column(align=False)
|
|
||||||
col.use_property_split = True
|
|
||||||
#row = col.row(align=True)
|
|
||||||
|
|
||||||
row = self.add_row(col,
|
|
||||||
prop="custom_bundle_name",
|
|
||||||
boolean="use_custom_bundle_name",
|
|
||||||
label='Custom Bundle Name')
|
|
||||||
|
|
||||||
row.enabled = not self.use_custom_bundle_directory
|
|
||||||
|
|
||||||
prop = "bundle_directory"
|
|
||||||
if self.use_custom_bundle_directory:
|
|
||||||
prop = "custom_bundle_directory"
|
|
||||||
|
|
||||||
self.add_row(col, prop=prop,
|
|
||||||
boolean="use_custom_bundle_directory",
|
|
||||||
label='Custom Bundle Directory',
|
|
||||||
)
|
|
||||||
|
|
||||||
col.prop(self, "blend_depth")
|
|
||||||
|
|
||||||
#subcol = col.column(align=True)
|
|
||||||
#subcol.prop(self, "template_info", text='Template Info', icon='COPY_ID')
|
|
||||||
#subcol.prop(self, "template_image", text='Template Image', icon='COPY_ID')
|
|
||||||
#subcol.prop(self, "template_video", text='Template Video', icon='COPY_ID')
|
|
||||||
|
|
||||||
if self.library_type:
|
|
||||||
col.separator()
|
|
||||||
self.library_type.draw_prefs(col)
|
|
||||||
|
|
||||||
for lib in self.child_libraries:
|
|
||||||
lib.draw(layout)
|
|
||||||
|
|
||||||
col.separator()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Collections:
|
|
||||||
'''Util Class to merge multiple collections'''
|
|
||||||
|
|
||||||
collections = []
|
|
||||||
|
|
||||||
def __init__(self, *collection):
|
|
||||||
self.collections = collection
|
|
||||||
|
|
||||||
for col in collection:
|
|
||||||
#print('Merge methods')
|
|
||||||
for attr in dir(col):
|
|
||||||
if attr.startswith('_'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
value = getattr(col, attr)
|
|
||||||
#if not callable(value):
|
|
||||||
# continue
|
|
||||||
|
|
||||||
setattr(self, attr, value)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
if isinstance(item, str):
|
|
||||||
return item in self.to_dict()
|
|
||||||
else:
|
|
||||||
return item in self
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self.to_list().__iter__()
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
|
||||||
if isinstance(item, int):
|
|
||||||
return self.to_list()[item]
|
|
||||||
else:
|
|
||||||
return self.to_dict()[item]
|
|
||||||
|
|
||||||
def get(self, item, fallback=None):
|
|
||||||
return self.to_dict().get(item) or fallback
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {k:v for c in self.collections for k, v in c.items()}
|
|
||||||
|
|
||||||
def to_list(self):
|
|
||||||
return [v for c in self.collections for v in c.values()]
|
|
||||||
|
|
||||||
def get_parent(self, item):
|
|
||||||
for c in self.collections:
|
|
||||||
if item in c.values():
|
|
||||||
return c
|
|
||||||
|
|
||||||
def index(self, item):
|
|
||||||
c = self.get_parent(item)
|
|
||||||
|
|
||||||
if not c:
|
|
||||||
return item in self
|
|
||||||
|
|
||||||
return list(c.values()).index(item)
|
|
||||||
|
|
||||||
|
|
||||||
#class AssetLibraryOptions(PropertyGroup):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
class AssetLibraryPrefs(AddonPreferences):
|
class AssetLibraryPrefs(AddonPreferences):
|
||||||
bl_idname = __package__
|
bl_idname = __package__
|
||||||
|
|
||||||
adapters = []
|
config_path : StringProperty(subtype="FILE_PATH")
|
||||||
library_types = []
|
libraries : CollectionProperty(type=AssetLibrary)
|
||||||
previews = bpy.utils.previews.new()
|
|
||||||
preview_modal = False
|
|
||||||
add_asset_dict = {}
|
|
||||||
|
|
||||||
#action : bpy.props.PointerProperty(type=AssetLibraryPath)
|
|
||||||
#asset : bpy.props.PointerProperty(type=AssetLibraryPath)
|
|
||||||
#library_types = {}
|
|
||||||
author: StringProperty(default=os.getlogin())
|
|
||||||
|
|
||||||
image_player: StringProperty(default='')
|
|
||||||
video_player: StringProperty(default='')
|
|
||||||
|
|
||||||
library_type_directory : StringProperty(name="Library Type Directory", subtype='DIR_PATH')
|
|
||||||
adapter_directory : StringProperty(name="Adapter Directory", subtype='DIR_PATH')
|
|
||||||
|
|
||||||
env_libraries : CollectionProperty(type=AssetLibrary)
|
|
||||||
user_libraries : CollectionProperty(type=AssetLibrary)
|
|
||||||
expand_settings: BoolProperty(default=False)
|
|
||||||
bundle_directory : StringProperty(
|
bundle_directory : StringProperty(
|
||||||
name="Path",
|
name="Path",
|
||||||
subtype='DIR_PATH',
|
subtype='DIR_PATH',
|
||||||
default='',
|
default='',
|
||||||
update=update_all_library_path
|
update=lambda s, c: update_library_path()
|
||||||
)
|
)
|
||||||
|
|
||||||
config_directory : StringProperty(
|
|
||||||
name="Config Path",
|
|
||||||
subtype='FILE_PATH',
|
|
||||||
default=str(RESOURCES_DIR/"asset_library_config.json"),
|
|
||||||
update=update_library_config
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_library_types(self):
|
|
||||||
from asset_library.library_types.library_type import LibraryType
|
|
||||||
|
|
||||||
print('Asset Library: Load Library Types')
|
|
||||||
|
|
||||||
LIBRARY_TYPES.clear()
|
|
||||||
|
|
||||||
library_type_files = list(LIBRARY_TYPE_DIR.glob('*.py'))
|
|
||||||
if self.library_type_directory:
|
|
||||||
user_LIBRARY_TYPE_DIR = Path(self.library_type_directory)
|
|
||||||
if user_LIBRARY_TYPE_DIR.exists():
|
|
||||||
library_type_files += list(user_LIBRARY_TYPE_DIR.glob('*.py'))
|
|
||||||
|
|
||||||
for library_type_file in library_type_files:
|
|
||||||
if library_type_file.stem.startswith('_'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
mod = import_module_from_path(library_type_file)
|
|
||||||
|
|
||||||
|
|
||||||
#print(library_type_file)
|
|
||||||
for name, obj in inspect.getmembers(mod):
|
|
||||||
|
|
||||||
if not inspect.isclass(obj):
|
|
||||||
continue
|
|
||||||
|
|
||||||
#print(obj.__bases__)
|
|
||||||
if not LibraryType in obj.__mro__:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Non registering base library_type
|
|
||||||
if obj is LibraryType or obj.name in (a.name for a in LIBRARY_TYPES):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(f'Register Plugin {name}')
|
|
||||||
bpy.utils.register_class(obj)
|
|
||||||
setattr(LibraryTypes, norm_str(obj.name), bpy.props.PointerProperty(type=obj))
|
|
||||||
LIBRARY_TYPES.append(obj)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Could not register library_type {name}')
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
def load_adapters(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
@property
|
|
||||||
def libraries(self):
|
|
||||||
return Collections(self.env_libraries, self.user_libraries)
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
prefs = get_addon_prefs()
|
prefs = get_addon_prefs()
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
#layout.use_property_split = True
|
col = layout.column(align=False)
|
||||||
|
|
||||||
main_col = layout.column(align=False)
|
|
||||||
|
|
||||||
box = main_col.box()
|
|
||||||
row = box.row(align=True)
|
|
||||||
icon = "DISCLOSURE_TRI_DOWN" if self.expand_settings else "DISCLOSURE_TRI_RIGHT"
|
|
||||||
row.prop(self, 'expand_settings', icon=icon, emboss=False, text='')
|
|
||||||
row.label(icon='PREFERENCES')
|
|
||||||
row.label(text='Settings')
|
|
||||||
#row.separator_spacer()
|
|
||||||
subrow = row.row()
|
|
||||||
subrow.alignment = 'RIGHT'
|
|
||||||
subrow.operator("assetlib.reload_addon", text='Reload Addon')
|
|
||||||
|
|
||||||
if prefs.expand_settings:
|
|
||||||
col = box.column(align=True)
|
|
||||||
col.use_property_split = True
|
|
||||||
|
|
||||||
#col.prop(self, 'use_single_path', text='Single Path')
|
|
||||||
col.prop(self, 'bundle_directory', text='Bundle Directory')
|
|
||||||
|
|
||||||
|
col.prop(self, "config_path", text='Config')
|
||||||
|
col.prop(self, "bundle_directory", text='Bundle Directory')
|
||||||
col.separator()
|
col.separator()
|
||||||
|
|
||||||
col.prop(self, 'library_type_directory')
|
row = col.row()
|
||||||
col.prop(self, 'config_directory')
|
row.label(text='Libraries:')
|
||||||
|
#row.alignment = 'RIGHT'
|
||||||
col.separator()
|
row.separator_spacer()
|
||||||
|
row.operator("assetlibrary.reload_addon", icon='FILE_REFRESH', text='')
|
||||||
#col.prop(self, 'template_info', text='Asset Description Template', icon='COPY_ID')
|
row.operator("assetlibrary.add_library", icon="ADD", text='', emboss=False)
|
||||||
|
|
||||||
#col.separator()
|
|
||||||
|
|
||||||
#col.prop(self, 'template_image', text='Template Image', icon='COPY_ID')
|
|
||||||
col.prop(self, 'image_player', text='Image Player') #icon='OUTLINER_OB_IMAGE'
|
|
||||||
|
|
||||||
#col.separator()
|
|
||||||
|
|
||||||
#col.prop(self, 'template_video', text='Template Video', icon='COPY_ID')
|
|
||||||
col.prop(self, 'video_player', text='Video Player') #icon='FILE_MOVIE'
|
|
||||||
|
|
||||||
col.separator()
|
|
||||||
|
|
||||||
col.operator("assetlib.add_user_library", text='Bundle All Libraries', icon='MOD_BUILD')
|
|
||||||
|
|
||||||
for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries):
|
for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries):
|
||||||
if lib.parent:
|
lib.draw(col)
|
||||||
continue
|
|
||||||
|
|
||||||
box = main_col.box()
|
|
||||||
lib.draw(box)
|
|
||||||
|
|
||||||
row = main_col.row()
|
|
||||||
row.alignment = 'RIGHT'
|
|
||||||
row.operator("assetlib.add_user_library", icon="ADD", text='', emboss=False)
|
|
||||||
|
|
||||||
|
|
||||||
classes = [
|
|
||||||
LibraryTypes,
|
|
||||||
Adapters,
|
classes = (
|
||||||
#ConformAssetLibrary,
|
|
||||||
AssetLibrary,
|
|
||||||
AssetLibraryPrefs,
|
AssetLibraryPrefs,
|
||||||
]
|
)
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
prefs = get_addon_prefs()
|
|
||||||
|
|
||||||
# Read Env and override preferences
|
|
||||||
bundle_dir = os.getenv('ASSETLIB_BUNDLE_DIR')
|
|
||||||
if bundle_dir:
|
|
||||||
prefs['bundle_directory'] = os.path.expandvars(bundle_dir)
|
|
||||||
|
|
||||||
config_dir = os.getenv('ASSETLIB_CONFIG_DIR')
|
|
||||||
if config_dir:
|
|
||||||
prefs['config_directory'] = os.path.expandvars(config_dir)
|
|
||||||
|
|
||||||
LIBRARY_TYPE_DIR = os.getenv('ASSETLIB_LIBRARY_TYPE_DIR')
|
|
||||||
if LIBRARY_TYPE_DIR:
|
|
||||||
prefs['library_type_directory'] = os.path.expandvars(LIBRARY_TYPE_DIR)
|
|
||||||
|
|
||||||
ADAPTER_DIR = os.getenv('ASSETLIB_ADAPTER_DIR')
|
|
||||||
if ADAPTER_DIR:
|
|
||||||
prefs['adapter_directory'] = os.path.expandvars(ADAPTER_DIR)
|
|
||||||
|
|
||||||
prefs.load_library_types()
|
|
||||||
prefs.load_adapters()
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for cls in reversed(classes + LIBRARY_TYPES):
|
for cls in reversed(classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
LIBRARY_TYPES.clear()
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import (AddonPreferences, PropertyGroup)
|
||||||
|
from bpy.props import (BoolProperty, StringProperty, CollectionProperty,
|
||||||
|
EnumProperty, IntProperty, PointerProperty)
|
||||||
|
|
||||||
|
from .constants import PLUGINS, PLUGINS_DIR, PLUGINS_ITEMS
|
||||||
|
from .core.file_utils import import_module_from_path, norm_str
|
||||||
|
|
||||||
|
from .core.bl_utils import get_addon_prefs
|
||||||
|
from .core.lib_utils import update_library_path
|
||||||
|
|
||||||
|
|
||||||
|
def load_plugins():
|
||||||
|
from .plugins.library_plugin import LibraryPlugin
|
||||||
|
print('Asset Library: Load Library Plugins')
|
||||||
|
|
||||||
|
plugin_files = list(PLUGINS_DIR.glob('*.py'))
|
||||||
|
# if self.plugin_directory:
|
||||||
|
# user_plugin_DIR = Path(self.plugin_directory)
|
||||||
|
# if user_plugin_DIR.exists():
|
||||||
|
# plugin_files += list(user_plugin_DIR.glob('*.py'))
|
||||||
|
|
||||||
|
for plugin_file in plugin_files:
|
||||||
|
if plugin_file.stem.startswith('_'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
mod = import_module_from_path(plugin_file)
|
||||||
|
|
||||||
|
for name, obj in inspect.getmembers(mod):
|
||||||
|
if not inspect.isclass(obj) or (obj is LibraryPlugin):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (LibraryPlugin not in obj.__mro__) or (obj in PLUGINS):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f'Register Plugin {name}')
|
||||||
|
bpy.utils.register_class(obj)
|
||||||
|
setattr(Plugins, norm_str(obj.name), PointerProperty(type=obj))
|
||||||
|
PLUGINS[obj.name] = obj
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Could not register plugin {name}')
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
plugins = sorted(PLUGINS.keys())
|
||||||
|
plugin_items = [('none', 'None', '', 0)]
|
||||||
|
plugin_items += [(norm_str(p), p, "") for p in plugins]
|
||||||
|
|
||||||
|
PLUGINS_ITEMS[:] = plugin_items
|
||||||
|
|
||||||
|
return PLUGINS
|
||||||
|
|
||||||
|
|
||||||
|
class Plugins(PropertyGroup):
|
||||||
|
"""Container holding the registed library plugins"""
|
||||||
|
def __iter__(self):
|
||||||
|
return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name'))
|
||||||
|
|
||||||
|
|
||||||
|
class AssetLibrary(PropertyGroup):
|
||||||
|
"""Library item defining one library with his plugin and settings"""
|
||||||
|
|
||||||
|
name : StringProperty(name='Name', default='', update=lambda s, c : update_library_path())
|
||||||
|
expand : BoolProperty(name='Expand', default=False)
|
||||||
|
use : BoolProperty(name='Use', default=True, update=lambda s, c : update_library_path())
|
||||||
|
is_user : BoolProperty(default=True)
|
||||||
|
path : StringProperty(subtype='DIR_PATH', update=lambda s, c : update_library_path())
|
||||||
|
|
||||||
|
plugins : PointerProperty(type=Plugins)
|
||||||
|
plugin_name : EnumProperty(items=lambda s, c : PLUGINS_ITEMS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin(self):
|
||||||
|
return getattr(self.plugins, self.plugin_name, None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def index(self):
|
||||||
|
prefs = get_addon_prefs()
|
||||||
|
return list(prefs.libraries).index(self)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def draw_operators(self, layout):
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.alignment = 'RIGHT'
|
||||||
|
row.prop(self, 'plugin_name', text='')
|
||||||
|
row.prop(self, 'auto_bundle', text='', icon='UV_SYNC_SELECT')
|
||||||
|
|
||||||
|
row.operator("assetlibrary.diff", text='', icon='FILE_REFRESH').name = self.name
|
||||||
|
|
||||||
|
op = row.operator("assetlibrary.sync", icon='MOD_BUILD', text='')
|
||||||
|
op.name = self.name
|
||||||
|
|
||||||
|
layout.separator(factor=3)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_dict(self, data, obj=None):
|
||||||
|
""""Recursive method to set all attribute from a dict to this instance"""
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
obj = self
|
||||||
|
|
||||||
|
# Make shure the input dict is not modidied
|
||||||
|
data = data.copy()
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
self.set_dict(value, obj=getattr(obj, key))
|
||||||
|
|
||||||
|
elif key in obj.bl_rna.properties.keys():
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = os.path.expandvars(value)
|
||||||
|
value = os.path.expanduser(value)
|
||||||
|
|
||||||
|
setattr(obj, key, value)
|
||||||
|
else:
|
||||||
|
print(f'Prop {key} of {obj} not exist')
|
||||||
|
|
||||||
|
|
||||||
|
def draw(self, layout):
|
||||||
|
prefs = get_addon_prefs()
|
||||||
|
#col = layout.column(align=True)
|
||||||
|
box = layout.box()
|
||||||
|
|
||||||
|
row = box.row(align=True)
|
||||||
|
icon = "DISCLOSURE_TRI_DOWN" if self.expand else "DISCLOSURE_TRI_RIGHT"
|
||||||
|
row.prop(self, 'expand', icon=icon, emboss=False, text='')
|
||||||
|
|
||||||
|
row.prop(self, 'use', text='')
|
||||||
|
#row.label(icon="ASSET_MANAGER")
|
||||||
|
row.prop(self, 'name', text='')
|
||||||
|
row.separator(factor=0.5)
|
||||||
|
sub = row.row()
|
||||||
|
sub.alignment = 'RIGHT'
|
||||||
|
sub.prop(self, 'plugin_name', text='')
|
||||||
|
row.separator(factor=0.5)
|
||||||
|
|
||||||
|
op = row.operator("assetlibrary.synchronize", icon='UV_SYNC_SELECT', text='')
|
||||||
|
op.name = self.name
|
||||||
|
|
||||||
|
row.separator(factor=0.5)
|
||||||
|
row.operator("assetlibrary.remove_library", icon="REMOVE", text='', emboss=False).index = self.index
|
||||||
|
|
||||||
|
#self.draw_operators(row)
|
||||||
|
if self.expand:
|
||||||
|
col = box.column(align=False)
|
||||||
|
|
||||||
|
col.use_property_split = True
|
||||||
|
col.prop(self, 'path', text='Path')
|
||||||
|
|
||||||
|
if self.plugin:
|
||||||
|
col.separator()
|
||||||
|
self.plugin.draw_prefs(col)
|
||||||
|
|
||||||
|
|
||||||
|
class WindowManagerProperties(PropertyGroup):
|
||||||
|
"""Library item defining one library with his plugin and settings"""
|
||||||
|
|
||||||
|
asset : PointerProperty(type=bpy.types.ID)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
Plugins,
|
||||||
|
AssetLibrary,
|
||||||
|
WindowManagerProperties
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
bpy.types.WindowManager.asset_library = PointerProperty(type=WindowManagerProperties)
|
||||||
|
load_plugins()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
del bpy.types.WindowManager.asset_library
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 731 B |
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
from asset_library import constants
|
||||||
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
|
|
||||||
|
|
||||||
|
def publish_asset(data_type, name):
|
||||||
|
bpy.app.use_userpref_skip_save_on_exit = True
|
||||||
|
|
||||||
|
blend_file = bpy.data.filepath
|
||||||
|
preview_blend = constants.RESOURCES_DIR / 'asset_preview.blend'
|
||||||
|
col = load_datablocks(preview_blend, names='Preview', type='collections', link=False)
|
||||||
|
bpy.context.scene.collection.children.link(col)
|
||||||
|
|
||||||
|
if data_type == 'node_groups':
|
||||||
|
ntree = bpy.data.node_groups[name]
|
||||||
|
mod = bpy.data.objects['Cube'].modifiers.new(ntree_name, 'NODES')
|
||||||
|
mod.node_group = ntree
|
||||||
|
|
||||||
|
bpy.context.preferences.filepaths.save_version = 0
|
||||||
|
bpy.ops.wm.save_mainfile(compress=True, exit=True)
|
||||||
|
#bpy.ops.wm.quit_blender()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
parser = argparse.ArgumentParser(description='build_collection_blends',
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
|
parser.add_argument('--data-type')
|
||||||
|
parser.add_argument('--datablock')
|
||||||
|
|
||||||
|
if '--' in sys.argv :
|
||||||
|
index = sys.argv.index('--')
|
||||||
|
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
publish_asset(**vars(args))
|
|
@ -0,0 +1,96 @@
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import bpy
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from asset_library import constants
|
||||||
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
|
from asset_library.core.lib_utils import clear_time_tag, create_time_tag, version_file
|
||||||
|
from asset_library.core.catalog import Catalog
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_datablocks():
|
||||||
|
blend_datas = [
|
||||||
|
bpy.data.collections, bpy.data.objects,
|
||||||
|
bpy.data.materials, bpy.data.node_groups, bpy.data.worlds]
|
||||||
|
|
||||||
|
return [o for blend_data in blend_datas for o in blend_data]
|
||||||
|
|
||||||
|
|
||||||
|
def publish_library_assets(library, assets, catalogs):
|
||||||
|
bpy.app.use_userpref_skip_save_on_exit = True
|
||||||
|
bpy.context.preferences.filepaths.save_version = 0
|
||||||
|
|
||||||
|
for asset, catalog_path in zip(assets, catalogs):
|
||||||
|
asset_path, asset_type, asset_name = asset.rsplit('/', 2)
|
||||||
|
|
||||||
|
#print(asset_path, asset_type, asset_name)
|
||||||
|
|
||||||
|
asset = load_datablocks(asset_path, names=asset_name, type=asset_type, link=False)
|
||||||
|
|
||||||
|
if not asset.asset_data:
|
||||||
|
asset.asset_mark()
|
||||||
|
|
||||||
|
# clear asset_mark of all other assets
|
||||||
|
for datablock in get_all_datablocks():
|
||||||
|
if datablock != asset and datablock.asset_data:
|
||||||
|
datablock.asset_clear()
|
||||||
|
|
||||||
|
if asset_type == 'node_groups':
|
||||||
|
mod = bpy.data.objects['Cube'].modifiers.new(asset.name, 'NODES')
|
||||||
|
mod.node_group = asset
|
||||||
|
|
||||||
|
elif asset_type == 'objects':
|
||||||
|
bpy.data.objects.remove(bpy.data.objects['Cube'])
|
||||||
|
bpy.data.collections['Preview'].objects.link(asset)
|
||||||
|
|
||||||
|
elif asset_type == 'materials':
|
||||||
|
bpy.data.objects['Cube'].data.materials.append(asset)
|
||||||
|
|
||||||
|
#catalog_path = asset.asset_data.catalog_simple_name.replace('-', '/')
|
||||||
|
asset_publish_path = Path(library, catalog_path, asset.name).with_suffix('.blend')
|
||||||
|
asset_publish_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Assign or Create catalog
|
||||||
|
catalog = Catalog(library)
|
||||||
|
catalog.read()
|
||||||
|
|
||||||
|
if catalog_item := catalog.get(path=catalog_path):
|
||||||
|
catalog_id = catalog_item.id
|
||||||
|
else:
|
||||||
|
catalog_id = catalog.add(catalog_path).id
|
||||||
|
catalog.write()
|
||||||
|
|
||||||
|
asset.asset_data.catalog_id = catalog_id
|
||||||
|
|
||||||
|
# Created time tag
|
||||||
|
clear_time_tag(asset)
|
||||||
|
create_time_tag(asset)
|
||||||
|
|
||||||
|
version_file(asset_publish_path)
|
||||||
|
|
||||||
|
bpy.ops.object.make_local(type='ALL')
|
||||||
|
bpy.ops.file.make_paths_relative()
|
||||||
|
bpy.ops.wm.save_as_mainfile(filepath=str(asset_publish_path), compress=True, copy=True, relative_remap=True)
|
||||||
|
bpy.ops.wm.revert_mainfile()
|
||||||
|
|
||||||
|
bpy.ops.wm.quit_blender()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
parser = argparse.ArgumentParser(description='build_collection_blends',
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
|
||||||
|
parser.add_argument('--library')
|
||||||
|
parser.add_argument('--assets', nargs='+')
|
||||||
|
parser.add_argument('--catalogs', nargs='+')
|
||||||
|
|
||||||
|
if '--' in sys.argv :
|
||||||
|
index = sys.argv.index('--')
|
||||||
|
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
publish_library_assets(**vars(args))
|
|
@ -1,270 +0,0 @@
|
||||||
|
|
||||||
import bpy
|
|
||||||
from pathlib import Path
|
|
||||||
from asset_library.common.file_utils import read_file, write_file
|
|
||||||
from copy import deepcopy
|
|
||||||
import time
|
|
||||||
from itertools import groupby
|
|
||||||
|
|
||||||
|
|
||||||
class AssetCache:
|
|
||||||
def __init__(self, file_cache, data):
|
|
||||||
|
|
||||||
self.file_cache = file_cache
|
|
||||||
|
|
||||||
self._data = data
|
|
||||||
|
|
||||||
self.catalog = data['catalog']
|
|
||||||
self.author = data.get('author', '')
|
|
||||||
self.description = data.get('description', '')
|
|
||||||
self.tags = data.get('tags', [])
|
|
||||||
self.type = data.get('type')
|
|
||||||
self.name = data['name']
|
|
||||||
self._metadata = data.get('metadata', {})
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filepath(self):
|
|
||||||
return self.file_cache.filepath
|
|
||||||
|
|
||||||
@property
|
|
||||||
def metadata(self):
|
|
||||||
metadata = {
|
|
||||||
'.library_id': self.library.id,
|
|
||||||
'.filepath': self.filepath
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata.update(self.metadata)
|
|
||||||
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
@property
|
|
||||||
def norm_name(self):
|
|
||||||
return self.name.replace(' ', '_').lower()
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
catalog=self.catalog,
|
|
||||||
author=self.author,
|
|
||||||
metadata=self.metadata,
|
|
||||||
description=self.description,
|
|
||||||
tags=self.tags,
|
|
||||||
type=self.type,
|
|
||||||
name=self.name
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'AssetCache(name={self.name}, type={self.type}, catalog={self.catalog})'
|
|
||||||
|
|
||||||
|
|
||||||
class FileCache:
|
|
||||||
def __init__(self, library_cache, data):
|
|
||||||
|
|
||||||
self.library_cache = library_cache
|
|
||||||
self.filepath = data['filepath']
|
|
||||||
self.modified = data.get('modified', time.time_ns())
|
|
||||||
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
for asset_cache_data in data.get('assets', []):
|
|
||||||
self.add(asset_cache_data)
|
|
||||||
|
|
||||||
def add(self, asset_cache_data):
|
|
||||||
asset_cache = AssetCache(self, asset_cache_data)
|
|
||||||
self._data.append(asset_cache)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(
|
|
||||||
filepath=self.filepath.as_posix(),
|
|
||||||
modified=self.modified,
|
|
||||||
library_id=self.library_cache.library.id,
|
|
||||||
assets=[asset_cache.to_dict() for asset_cache in self]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self._data.__iter__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'FileCache(filepath={self.filepath})'
|
|
||||||
|
|
||||||
|
|
||||||
class AssetCacheDiff:
|
|
||||||
def __init__(self, library_diff, asset_cache, operation):
|
|
||||||
|
|
||||||
self.library_cache = library_cache
|
|
||||||
self.filepath = data['filepath']
|
|
||||||
self.operation = operation
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryCacheDiff:
|
|
||||||
def __init__(self, filepath=None):
|
|
||||||
|
|
||||||
self.filepath = filepath
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
def add(self, asset_diff):
|
|
||||||
asset_diff = AssetCacheDiff(self, asset_diff)
|
|
||||||
self._data.append(asset_cache_diff)
|
|
||||||
|
|
||||||
def set(self, asset_diffs):
|
|
||||||
for asset_diff in asset_diffs:
|
|
||||||
self.add(asset_diff)
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
print(f'Read cache from {self.filepath}')
|
|
||||||
|
|
||||||
for asset_diff_data in read_file(self.filepath):
|
|
||||||
self.add(asset_diff_data)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def group_by(self, key):
|
|
||||||
'''Return groups of file cache diff using the key provided'''
|
|
||||||
data = list(self).sort(key=key)
|
|
||||||
return groupby(data, key=key)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
|
|
||||||
class LibraryCache:
|
|
||||||
|
|
||||||
def __init__(self, directory, id):
|
|
||||||
|
|
||||||
self.directory = directory
|
|
||||||
self.id = id
|
|
||||||
self._data = []
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_library(cls, library):
|
|
||||||
return cls(library.library_path, library.id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
return f"blender_assets.{self.id}.json"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filepath(self):
|
|
||||||
"""Get the filepath of the library json file relative to the library"""
|
|
||||||
return self.directory / self.filename
|
|
||||||
|
|
||||||
@property
|
|
||||||
def asset_caches(self):
|
|
||||||
'''Return an iterator to get all asset caches'''
|
|
||||||
return (asset_cache for file_cache in self for asset_cache in file_cache)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tmp_filepath(self):
|
|
||||||
return Path(bpy.app.tempdir) / self.filename
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
print(f'Read cache from {self.filepath}')
|
|
||||||
|
|
||||||
for file_cache_data in read_file(self.filepath):
|
|
||||||
self.add(file_cache_data)
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def write(self, temp=False):
|
|
||||||
filepath = self.filepath
|
|
||||||
if temp:
|
|
||||||
filepath = self.tmp_filepath
|
|
||||||
|
|
||||||
print(f'Write cache file to {filepath}')
|
|
||||||
write_file(filepath, self._data)
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
def add(self, file_cache_data):
|
|
||||||
file_cache = FileCache(self, file_cache_data)
|
|
||||||
|
|
||||||
self._data.append(file_cache)
|
|
||||||
|
|
||||||
def unflatten_cache(self, cache):
|
|
||||||
""" Return a new unflattten list of asset data
|
|
||||||
grouped by filepath"""
|
|
||||||
|
|
||||||
new_cache = []
|
|
||||||
|
|
||||||
cache = deepcopy(cache)
|
|
||||||
|
|
||||||
cache.sort(key=lambda x : x['filepath'])
|
|
||||||
groups = groupby(cache, key=lambda x : x['filepath'])
|
|
||||||
|
|
||||||
keys = ['filepath', 'modified', 'library_id']
|
|
||||||
|
|
||||||
for _, asset_datas in groups:
|
|
||||||
asset_datas = list(asset_datas)
|
|
||||||
|
|
||||||
#print(asset_datas[0])
|
|
||||||
|
|
||||||
asset_info = {k:asset_datas[0][k] for k in keys}
|
|
||||||
asset_info['assets'] = [{k:v for k, v in a.items() if k not in keys+['operation']} for a in asset_datas]
|
|
||||||
|
|
||||||
new_cache.append(asset_info)
|
|
||||||
|
|
||||||
return new_cache
|
|
||||||
|
|
||||||
def diff(self, new_cache):
|
|
||||||
"""Compare the library cache with it current state and return the cache differential"""
|
|
||||||
|
|
||||||
cache = self.read()
|
|
||||||
|
|
||||||
cache_dict = {f"{a['filepath']}/{a['name']}": a for a in cache.asset_caches}
|
|
||||||
new_cache_dict = {f"{a['filepath']}/{a['name']}" : a for a in new_cache.asset_caches}
|
|
||||||
|
|
||||||
assets_added = [AssetCacheDiff(v, 'ADD') for k, v in new_cache.items() if k not in cache]
|
|
||||||
assets_removed = [AssetCacheDiff(v, 'REMOVED') for k, v in cache.items() if k not in new_cache]
|
|
||||||
assets_modified = [AssetCacheDiff(v, 'MODIFIED') 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')
|
|
||||||
|
|
||||||
cache_diff = LibraryCacheDiff()
|
|
||||||
cache_diff.set(assets_added+assets_removed+assets_modified)
|
|
||||||
|
|
||||||
if not len(LibraryCacheDiff):
|
|
||||||
print('No change in the library')
|
|
||||||
|
|
||||||
return cache_diff
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._data)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._data[key]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'LibraryCache(library={self.library.name})'
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
prefs = bpy.context.preferences.addons['asset_library'].preferences
|
|
||||||
|
|
||||||
|
|
||||||
library = prefs.env_libraries[0]
|
|
||||||
library_cache = LibraryCache.from_library(library).read()
|
|
||||||
|
|
||||||
data = library.library_type.fetch()
|
|
||||||
print(data)
|
|
||||||
|
|
||||||
print(library_cache[0][0])
|
|
||||||
|
|
||||||
#library_cache.diff(library.library_type.fetch())
|
|
||||||
|
|
||||||
|
|
||||||
#print(library_cache[0])
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Menu
|
||||||
|
|
||||||
|
from .core.lib_utils import get_active_library
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_MT_node_editor(Menu):
|
||||||
|
bl_label = "Asset"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
op = layout.operator("assetlibrary.publish_asset", text='Publish Node Group', icon='NODETREE')
|
||||||
|
op.data_type = 'NodeTree'
|
||||||
|
|
||||||
|
if context.space_data.tree_type == 'GeometryNodeTree':
|
||||||
|
op = layout.operator("assetlibrary.publish_asset", text='Publish Object', icon='OBJECT_DATA')
|
||||||
|
op.data_type = 'Object'
|
||||||
|
|
||||||
|
elif context.space_data.tree_type == 'ShaderNodeTree':
|
||||||
|
op = layout.operator("assetlibrary.publish_asset", text='Publish Material', icon='MATERIAL')
|
||||||
|
op.data_type = 'Material'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_assetbrowser_header(self, context):
|
||||||
|
lib = get_active_library()
|
||||||
|
|
||||||
|
if not lib:
|
||||||
|
FILEBROWSER_HT_header._draw_asset_browser_buttons(self, context)
|
||||||
|
return
|
||||||
|
|
||||||
|
space_data = context.space_data
|
||||||
|
params = context.space_data.params
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.separator()
|
||||||
|
|
||||||
|
row.operator("assetlibrary.bundle", icon='UV_SYNC_SELECT', text='').name = lib.name
|
||||||
|
#op
|
||||||
|
#op.clean = False
|
||||||
|
#op.only_recent = True
|
||||||
|
|
||||||
|
lib.plugin.draw_header(row)
|
||||||
|
|
||||||
|
if context.selected_files and context.active_file:
|
||||||
|
row.separator()
|
||||||
|
row.label(text=context.active_file.name)
|
||||||
|
|
||||||
|
row.separator_spacer()
|
||||||
|
|
||||||
|
sub = row.row()
|
||||||
|
sub.ui_units_x = 10
|
||||||
|
sub.prop(params, "filter_search", text="", icon='VIEWZOOM')
|
||||||
|
|
||||||
|
row.separator_spacer()
|
||||||
|
|
||||||
|
row.prop_with_popover(
|
||||||
|
params,
|
||||||
|
"display_type",
|
||||||
|
panel="ASSETBROWSER_PT_display",
|
||||||
|
text="",
|
||||||
|
icon_only=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
row.operator(
|
||||||
|
"screen.region_toggle",
|
||||||
|
text="",
|
||||||
|
icon='PREFERENCES',
|
||||||
|
depress=is_option_region_visible(context, space_data)
|
||||||
|
).region_type = 'TOOL_PROPS'
|
||||||
|
|
||||||
|
|
||||||
|
def draw_assetbrowser_asset_menu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator("assetlibrary.publish_assets", text='Publish Assets', icon='ASSET_MANAGER')
|
||||||
|
|
||||||
|
# if not get_active_library():
|
||||||
|
# return
|
||||||
|
|
||||||
|
# self.layout.separator()
|
||||||
|
# box = self.layout.box()
|
||||||
|
# row = box.row()
|
||||||
|
# row.separator(factor=0.5)
|
||||||
|
# row.label(text='Asset Library')
|
||||||
|
# row.separator(factor=0.5)
|
||||||
|
|
||||||
|
# classes = (,
|
||||||
|
# # ASSETLIB_PT_pose_library_editing,
|
||||||
|
# # ASSETLIB_PT_pose_library_usage,
|
||||||
|
# # ASSETLIB_MT_context_menu,
|
||||||
|
# # ASSETLIB_PT_libraries
|
||||||
|
# )
|
||||||
|
|
||||||
|
def draw_asset_preview_menu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator("assetlibrary.save_asset_preview")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_node_tree_menu(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
row = layout.row(align=False)
|
||||||
|
row.menu('ASSETLIB_MT_node_editor')
|
||||||
|
|
||||||
|
|
||||||
|
bl_classes = (
|
||||||
|
ASSETLIB_MT_node_editor,)
|
||||||
|
|
||||||
|
|
||||||
|
def register() -> None:
|
||||||
|
for bl_class in bl_classes:
|
||||||
|
bpy.utils.register_class(bl_class)
|
||||||
|
|
||||||
|
#bpy.types.ASSETBROWSER_MT_editor_menus.append(draw_assetbrowser_header)
|
||||||
|
bpy.types.ASSETBROWSER_MT_metadata_preview_menu.append(draw_asset_preview_menu)
|
||||||
|
bpy.types.NODE_MT_editor_menus.append(draw_node_tree_menu)
|
||||||
|
|
||||||
|
bpy.types.ASSETBROWSER_MT_asset.append(draw_assetbrowser_asset_menu)
|
||||||
|
|
||||||
|
def unregister() -> None:
|
||||||
|
for bl_class in reversed(bl_classes):
|
||||||
|
bpy.utils.unregister_class(bl_class)
|
||||||
|
|
||||||
|
bpy.types.ASSETBROWSER_MT_editor_menus.remove(draw_assetbrowser_header)
|
||||||
|
bpy.types.NODE_MT_editor_menus.remove(draw_node_tree_menu)
|
||||||
|
bpy.types.ASSETBROWSER_MT_asset.remove(draw_assetbrowser_asset_menu)
|
Loading…
Reference in New Issue