start refacto update
parent
a01b282f45
commit
4465148b22
31
__init__.py
31
__init__.py
|
@ -16,14 +16,18 @@ bl_info = {
|
||||||
"category": "Import-Export",
|
"category": "Import-Export",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from . import operators, properties, ui, preferences
|
from . import operators, properties, ui, preferences, data_type
|
||||||
|
from .core.lib_utils import load_libraries, update_library_path
|
||||||
|
|
||||||
modules = (
|
|
||||||
|
bl_modules = (
|
||||||
operators,
|
operators,
|
||||||
properties,
|
properties,
|
||||||
ui,
|
ui,
|
||||||
preferences
|
preferences,
|
||||||
|
data_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reload Modules from inside Blender
|
# Reload Modules from inside Blender
|
||||||
|
@ -33,16 +37,33 @@ if "bpy" in locals():
|
||||||
for mod in modules:
|
for mod in modules:
|
||||||
importlib.reload(mod)
|
importlib.reload(mod)
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
|
||||||
|
def load_handler():
|
||||||
|
print('load_handler')
|
||||||
|
load_libraries()
|
||||||
|
update_library_path()
|
||||||
|
#set_env_libraries()
|
||||||
|
#bpy.ops.assetlib.set_paths(all=True)
|
||||||
|
|
||||||
|
#if not bpy.app.background:
|
||||||
|
# bpy.ops.assetlib.bundle(blocking=False, mode='AUTO_BUNDLE')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
"""Register the addon Asset Library for Blender"""
|
"""Register the addon Asset Library for Blender"""
|
||||||
|
|
||||||
for mod in modules:
|
for mod in bl_modules:
|
||||||
mod.register()
|
mod.register()
|
||||||
|
|
||||||
|
|
||||||
|
bpy.app.timers.register(load_handler, first_interval=1)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
"""Unregister the addon Asset Library for Blender"""
|
"""Unregister the addon Asset Library for Blender"""
|
||||||
|
|
||||||
for mod in reversed(modules):
|
for mod in reversed(bl_modules):
|
||||||
mod.unregister()
|
mod.unregister()
|
21
constants.py
21
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}
|
||||||
|
|
||||||
|
@ -15,9 +23,14 @@ MODULE_DIR = Path(__file__).parent
|
||||||
RESOURCES_DIR = MODULE_DIR / 'resources'
|
RESOURCES_DIR = MODULE_DIR / 'resources'
|
||||||
|
|
||||||
PLUGINS_DIR = MODULE_DIR / 'plugins'
|
PLUGINS_DIR = MODULE_DIR / 'plugins'
|
||||||
PLUGINS = set()
|
PLUGINS = {}
|
||||||
PLUGINS_ITEMS = [('NONE', 'None', '', 0)]
|
PLUGINS_ITEMS = [('NONE', 'None', '', 0)]
|
||||||
|
|
||||||
|
LIB_DIR = MODULE_DIR / 'libs'
|
||||||
|
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'
|
||||||
|
|
||||||
#ADD_ASSET_DICT = {}
|
#ADD_ASSET_DICT = {}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
"""
|
|
||||||
Util function for this addon
|
|
||||||
"""
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from . bl_utils import get_addon_prefs
|
|
||||||
|
|
||||||
|
|
||||||
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(self, context):
|
|
||||||
"""Removing all asset libraries and recreate them"""
|
|
||||||
|
|
||||||
addon_prefs = get_addon_prefs()
|
|
||||||
libs = 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
|
|
|
@ -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
|
||||||
|
@ -15,6 +15,8 @@ from bpy_extras import asset_utils
|
||||||
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)] ]
|
||||||
|
@ -82,6 +84,9 @@ 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]
|
||||||
|
@ -216,7 +221,7 @@ def norm_arg(arg_name, format=str.lower, prefix='--', 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:
|
||||||
|
@ -228,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)]
|
||||||
|
|
||||||
|
@ -347,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:
|
||||||
|
@ -483,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()
|
|
@ -1,26 +1,9 @@
|
||||||
|
|
||||||
from asset_library.action import (
|
from asset_library.data_type.action import (
|
||||||
gui,
|
|
||||||
keymaps,
|
keymaps,
|
||||||
clear_asset,
|
|
||||||
concat_preview,
|
|
||||||
operators,
|
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
|
import bpy
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
568
operators.py
568
operators.py
|
@ -1,10 +1,20 @@
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
from pathlib import Path
|
||||||
import bpy
|
import bpy
|
||||||
|
import subprocess
|
||||||
|
import gpu
|
||||||
|
from gpu_extras.batch import batch_for_shader
|
||||||
|
from mathutils import Vector
|
||||||
|
from math import sqrt
|
||||||
|
|
||||||
from bpy.types import Operator
|
from bpy_extras.io_utils import ExportHelper
|
||||||
from bpy.props import (BoolProperty, EnumProperty, StringProperty, IntProperty)
|
from bpy.types import Operator, PropertyGroup
|
||||||
from .core.bl_utils import get_addon_prefs, unique_name
|
from bpy.props import (BoolProperty, EnumProperty, StringProperty, IntProperty, CollectionProperty)
|
||||||
|
from .core.catalog import read_catalog
|
||||||
|
from .core.bl_utils import get_addon_prefs, unique_name, get_asset_type, get_bl_cmd, get_viewport
|
||||||
|
from .core.lib_utils import get_asset_full_path, get_asset_catalog_path, find_asset_data, clear_time_tag
|
||||||
|
from . import constants
|
||||||
|
|
||||||
|
|
||||||
class ASSETLIB_OT_reload_addon(Operator):
|
class ASSETLIB_OT_reload_addon(Operator):
|
||||||
|
@ -133,11 +143,557 @@ class ASSETLIB_OT_synchronize(Operator):
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_save_asset_preview(Operator):
|
||||||
|
bl_idname = "assetlibrary.save_asset_preview"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_label = 'Save Asset Preview'
|
||||||
|
bl_description = 'Save Asset Preview'
|
||||||
|
|
||||||
|
filepath: StringProperty(
|
||||||
|
name="File Path",
|
||||||
|
description="Filepath used for exporting the image",
|
||||||
|
subtype='FILE_PATH',
|
||||||
|
)
|
||||||
|
check_existing: BoolProperty(
|
||||||
|
name="Check Existing",
|
||||||
|
description="Check and warn on overwriting existing files",
|
||||||
|
default=True,
|
||||||
|
options={'HIDDEN'},
|
||||||
|
)
|
||||||
|
|
||||||
|
quality: IntProperty(subtype='PERCENTAGE', min=0, max=100, default=90, name='Quality')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
prefs = get_addon_prefs()
|
||||||
|
|
||||||
|
preview = None
|
||||||
|
|
||||||
|
if context.asset.local_id:
|
||||||
|
preview = context.asset.local_id.preview
|
||||||
|
width, height = preview.image_size
|
||||||
|
pixels = [0] * width * height * 4
|
||||||
|
preview.image_pixels_float.foreach_get(pixels)
|
||||||
|
|
||||||
|
else:
|
||||||
|
asset_path = context.asset.full_library_path
|
||||||
|
asset_type, asset_name = Path(context.asset.full_path).parts[-2:]
|
||||||
|
asset_type = get_asset_type(asset_type)
|
||||||
|
|
||||||
|
with bpy.data.temp_data(filepath=asset_path) as temp_data:
|
||||||
|
with temp_data.libraries.load(asset_path, assets_only=True, link=True) as (data_from, data_to):
|
||||||
|
setattr(data_to, asset_type, [asset_name])
|
||||||
|
if assets := getattr(data_to, asset_type):
|
||||||
|
preview = assets[0].preview
|
||||||
|
width, height = preview.image_size
|
||||||
|
# Has to read pixel in the with statement for it to work
|
||||||
|
pixels = [0] * width * height * 4
|
||||||
|
preview.image_pixels_float.foreach_get(pixels)
|
||||||
|
|
||||||
|
if not preview:
|
||||||
|
self.report({'ERROR'}, 'Cannot retrieve preview')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
image = bpy.data.images.new('Asset Preview', width=width, height=height, alpha=True)
|
||||||
|
image.pixels.foreach_set(pixels)
|
||||||
|
try:
|
||||||
|
image.save(filepath=self.filepath, quality=self.quality)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
self.report({'ERROR'}, 'Cannot write preview')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
path = Path(context.asset.name)
|
||||||
|
if bpy.data.filepath:
|
||||||
|
path = Path(bpy.data.filepath, context.asset.name)
|
||||||
|
|
||||||
|
self.filepath = str(path.with_suffix('.webp'))
|
||||||
|
|
||||||
|
context.window_manager.fileselect_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_make_custom_preview(Operator):
|
||||||
|
bl_idname = "assetlibrary.make_custom_preview"
|
||||||
|
bl_label = "Custom Preview"
|
||||||
|
bl_description = "Make a preview"
|
||||||
|
|
||||||
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
||||||
|
|
||||||
|
def draw_border(self, context):
|
||||||
|
if not self.is_down:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 50% alpha, 2 pixel width line
|
||||||
|
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||||
|
#gpu.state.line_width_set(1.0)
|
||||||
|
batch = batch_for_shader(shader, 'LINE_LOOP', {"pos": self.border})
|
||||||
|
shader.uniform_float("color", (1.0, 0.0, 0.0, 1))
|
||||||
|
batch.draw(shader)
|
||||||
|
|
||||||
|
# restore opengl defaults
|
||||||
|
#gpu.state.line_width_set(1.0)
|
||||||
|
#gpu.state.blend_set('NONE')
|
||||||
|
|
||||||
|
def grab_view3d(self, context):
|
||||||
|
width = int(self.release_window_pos.x - self.press_window_pos.x)
|
||||||
|
height = width#int(self.press_window_pos.y - self.release_window_pos.y)
|
||||||
|
x = int(self.press_window_pos.x)
|
||||||
|
y = int(self.press_window_pos.y - width)
|
||||||
|
|
||||||
|
print(x, y, width, height)
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
fb = gpu.state.active_framebuffer_get()
|
||||||
|
buffer = fb.read_color(x, y, width, height, 4, 0, 'FLOAT')
|
||||||
|
|
||||||
|
buffer.dimensions = width * height * 4
|
||||||
|
|
||||||
|
img = bpy.data.images.get('.Asset Preview')
|
||||||
|
if img:
|
||||||
|
bpy.data.images.remove(img)
|
||||||
|
img = bpy.data.images.new('.Asset Preview', width, height)
|
||||||
|
#img.scale(width, height)
|
||||||
|
img.pixels.foreach_set(buffer)
|
||||||
|
img.scale(256, 256)
|
||||||
|
|
||||||
|
pixels = [0] * 256 * 256 * 4
|
||||||
|
img.pixels.foreach_get(pixels)
|
||||||
|
|
||||||
|
bpy.data.images.remove(img)
|
||||||
|
return pixels
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
self.mouse_pos = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||||
|
|
||||||
|
if event.type == 'LEFTMOUSE' and event.value == 'PRESS':
|
||||||
|
self.press_window_pos = Vector((event.mouse_x, event.mouse_y))
|
||||||
|
self.press_pos = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||||
|
print('Start Border')
|
||||||
|
|
||||||
|
self.is_down = True
|
||||||
|
|
||||||
|
elif event.type == 'MOUSEMOVE' and self.is_down:
|
||||||
|
|
||||||
|
|
||||||
|
width = int(self.mouse_pos.x - self.press_pos.x)
|
||||||
|
X = (self.press_pos.x-1, self.mouse_pos.x +2)
|
||||||
|
Y = (self.press_pos.y+1, self.press_pos.y-width-2)
|
||||||
|
#print(self.mouse_pos, self.press_pos )
|
||||||
|
|
||||||
|
#X = sorted((self.press_pos.x, self.mouse_pos.x))
|
||||||
|
#Y = sorted((self.press_pos.y, self.mouse_pos.y))
|
||||||
|
#Constraint to square
|
||||||
|
#Y[0] = Y[1] - (X[1] - X[0])
|
||||||
|
|
||||||
|
self.border = [(X[0], Y[0]), (X[1], Y[0]), (X[1], Y[1]), (X[0], Y[1])]
|
||||||
|
|
||||||
|
elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
||||||
|
self.release_window_pos = Vector((event.mouse_x, event.mouse_y))
|
||||||
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||||
|
context.area.tag_redraw()
|
||||||
|
self.store_preview(context)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
elif event.type in {'RIGHTMOUSE', 'ESC'}:
|
||||||
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def store_preview(self, context):
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
pixels = self.grab_view3d(context)
|
||||||
|
asset.preview.image_size = 256, 256
|
||||||
|
asset.preview.image_pixels_float.foreach_set(pixels)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.press_window_pos = Vector((0, 0))
|
||||||
|
self.release_window_pos = Vector((0, 0))
|
||||||
|
|
||||||
|
self.press_pos = Vector((0, 0))
|
||||||
|
|
||||||
|
self.is_down = False
|
||||||
|
self.border = []
|
||||||
|
|
||||||
|
# Add the region OpenGL drawing callback
|
||||||
|
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
|
||||||
|
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_border, (context,), 'WINDOW', 'POST_PIXEL')
|
||||||
|
|
||||||
|
area = get_viewport()
|
||||||
|
region = next(r for r in area.regions if r.type =="WINDOW")
|
||||||
|
with context.temp_override(area=area, space_data=area.spaces.active, region=region):
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
#else:
|
||||||
|
# self.report({'WARNING'}, "View3D not found, cannot run operator")
|
||||||
|
# return {'CANCELLED'}
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_add_tag(Operator):
|
||||||
|
bl_idname = "assetlibrary.tag_add"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_label = 'Add Tag'
|
||||||
|
bl_description = 'Add Tag'
|
||||||
|
|
||||||
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
|
||||||
|
new_tag = asset.asset_data.tags.new(name='Tag')
|
||||||
|
index = list(asset.asset_data.tags).index(new_tag)
|
||||||
|
asset.asset_data.active_tag = index
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_remove_tag(Operator):
|
||||||
|
bl_idname = "assetlibrary.tag_remove"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_label = 'Remove Tag'
|
||||||
|
bl_description = 'Remove Tag'
|
||||||
|
|
||||||
|
#data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
|
||||||
|
if asset.asset_data.active_tag == -1:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
active_tag = asset.asset_data.tags[asset.asset_data.active_tag]
|
||||||
|
asset.asset_data.tags.remove(active_tag)
|
||||||
|
asset.asset_data.active_tag -=1
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_publish_asset(Operator):
|
||||||
|
bl_idname = "assetlibrary.publish_asset"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_label = 'Publish Asset'
|
||||||
|
bl_description = 'Publish Asset'
|
||||||
|
|
||||||
|
name : StringProperty(name='Name')
|
||||||
|
library : EnumProperty(name="Library", items=lambda s, c: constants.LIB_ITEMS)
|
||||||
|
#description : StringProperty(name='Description')
|
||||||
|
catalog : StringProperty(name='Catalog')
|
||||||
|
data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
||||||
|
catalog_items : CollectionProperty(type=PropertyGroup)
|
||||||
|
|
||||||
|
new_asset = False
|
||||||
|
is_asset = False
|
||||||
|
viewport = None
|
||||||
|
use_overlay = False
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def poll(self, context):
|
||||||
|
# return context.space_data.type == 'NODE_EDITOR' and context.space_data.edit_tree
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if self.data_type == 'NodeTree':
|
||||||
|
asset = context.space_data.edit_tree
|
||||||
|
elif self.data_type == 'Material':
|
||||||
|
asset = context.object.active_material
|
||||||
|
elif self.data_type == 'Object':
|
||||||
|
asset = context.object
|
||||||
|
|
||||||
|
if asset.asset_data:
|
||||||
|
self.is_asset = True
|
||||||
|
else:
|
||||||
|
asset.asset_mark()
|
||||||
|
asset.preview_ensure()
|
||||||
|
asset.preview.image_size = 256, 256
|
||||||
|
|
||||||
|
self.viewport = get_viewport()
|
||||||
|
if self.viewport:
|
||||||
|
self.use_overlay = self.viewport.spaces.active.overlay.show_overlays
|
||||||
|
self.viewport.spaces.active.overlay.show_overlays = False
|
||||||
|
|
||||||
|
bl_libs = context.preferences.filepaths.asset_libraries
|
||||||
|
constants.LIB_ITEMS[:] = [(lib.name, lib.name, "") for lib in bl_libs if lib.name]
|
||||||
|
|
||||||
|
asset_type = get_asset_type(self.data_type)
|
||||||
|
asset_data = find_asset_data(asset.name, asset_type=asset_type, preview=True)
|
||||||
|
|
||||||
|
for lib in bl_libs:
|
||||||
|
for catalog_item in read_catalog(lib.path):
|
||||||
|
c = self.catalog_items.add()
|
||||||
|
c.name = catalog_item.path
|
||||||
|
|
||||||
|
self.name = asset.name
|
||||||
|
self.new_asset = True
|
||||||
|
if asset_data:
|
||||||
|
catalog = read_catalog(asset_data['library'].path)
|
||||||
|
if catalog_item := catalog.get(id=asset_data["catalog_id"]):
|
||||||
|
self.catalog = catalog_item.path
|
||||||
|
|
||||||
|
self.new_asset = False
|
||||||
|
self.library = asset_data['library'].name
|
||||||
|
|
||||||
|
if not self.is_asset:
|
||||||
|
if asset_data.get('preview_size'):
|
||||||
|
asset.preview.image_size = asset_data['preview_size']
|
||||||
|
asset.preview.image_pixels_float.foreach_set(asset_data['preview_pixels'])
|
||||||
|
|
||||||
|
asset.asset_data.description = asset_data['description']
|
||||||
|
|
||||||
|
for tag in asset_data['tags']:
|
||||||
|
asset.asset_data.tags.new(name=tag, skip_if_exists=True)
|
||||||
|
clear_time_tag(asset)
|
||||||
|
|
||||||
|
#asset.preview_ensure()
|
||||||
|
context.window_manager.asset_library.asset = asset
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def check(self, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def cancel(self, context):
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
if self.viewport:
|
||||||
|
self.viewport.spaces.active.overlay.show_overlays = self.use_overlay
|
||||||
|
if not self.is_asset:
|
||||||
|
asset.asset_clear()
|
||||||
|
|
||||||
|
def split_row(self, layout, name):
|
||||||
|
split = layout.split(factor=0.225)
|
||||||
|
split.alignment = 'RIGHT'
|
||||||
|
split.label(text=name)
|
||||||
|
return split
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Name")
|
||||||
|
split.prop(self, "name", text='')
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Library")
|
||||||
|
split.prop(self, "library", text='')
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Catalog")
|
||||||
|
split.prop_search(self, "catalog", self, "catalog_items", results_are_suggestions=True, text='')
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Description")
|
||||||
|
split.prop(asset.asset_data, "description", text='')
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Tags")
|
||||||
|
row = split.row()
|
||||||
|
row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset.asset_data, "tags",
|
||||||
|
asset.asset_data, "active_tag", rows=3)
|
||||||
|
|
||||||
|
col = row.column(align=True)
|
||||||
|
col.operator("assetlibrary.tag_add", icon='ADD', text="")
|
||||||
|
col.operator("assetlibrary.tag_remove", icon='REMOVE', text="")
|
||||||
|
|
||||||
|
split = self.split_row(layout, "Preview")
|
||||||
|
row = split.row()
|
||||||
|
box = row.box()
|
||||||
|
box.template_icon(icon_value=asset.preview.icon_id, scale=5.0)
|
||||||
|
|
||||||
|
col = row.column(align=False)
|
||||||
|
if self.viewport:
|
||||||
|
col.prop(self.viewport.spaces.active.overlay, 'show_overlays', icon="OVERLAY", text="")
|
||||||
|
col.operator("assetlibrary.make_custom_preview", icon='SCENE', text="")
|
||||||
|
#op.data_type = self.data_type
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bl_libs = context.preferences.filepaths.asset_libraries
|
||||||
|
asset = context.window_manager.asset_library.asset
|
||||||
|
|
||||||
|
publish_library = bl_libs[self.library]
|
||||||
|
asset_temp_blend = Path(bpy.app.tempdir, self.name).with_suffix('.blend')
|
||||||
|
|
||||||
|
bpy.data.libraries.write(str(asset_temp_blend), {asset}, path_remap="ABSOLUTE")
|
||||||
|
|
||||||
|
self.cancel(context) # To clear the asset mark and restore overlay
|
||||||
|
|
||||||
|
asset_type = get_asset_type(self.data_type)
|
||||||
|
asset_full_path = Path(asset_temp_blend, asset_type, self.name)
|
||||||
|
|
||||||
|
cmd = get_bl_cmd(
|
||||||
|
background=True,
|
||||||
|
factory_startup=True,
|
||||||
|
blendfile=constants.RESOURCES_DIR / 'asset_preview.blend',
|
||||||
|
script=constants.SCRIPTS_DIR / 'publish_library_assets.py',
|
||||||
|
library=publish_library.path,
|
||||||
|
assets=[asset_full_path.as_posix()],
|
||||||
|
catalogs=[self.catalog]
|
||||||
|
)
|
||||||
|
|
||||||
|
print(cmd)
|
||||||
|
subprocess.call(cmd)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_publish_assets(Operator):
|
||||||
|
bl_idname = "assetlibrary.publish_assets"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
bl_label = 'Publish Assets'
|
||||||
|
bl_description = 'Publish Assets'
|
||||||
|
|
||||||
|
library : EnumProperty(name="Library", items=lambda s, c: constants.LIB_ITEMS)
|
||||||
|
override : BoolProperty(default=True)
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def poll(self, context):
|
||||||
|
# return context.space_data.edit_tree
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
bl_libs = context.preferences.filepaths.asset_libraries
|
||||||
|
constants.LIB_ITEMS[:] = [(lib.name, lib.name, "") for lib in bl_libs]
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
col = layout.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
|
||||||
|
layout.prop(self, "library")
|
||||||
|
layout.prop(self, "override")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bl_libs = context.preferences.filepaths.asset_libraries
|
||||||
|
publish_library = bl_libs[self.library]
|
||||||
|
preview_blend = constants.RESOURCES_DIR / 'asset_preview.blend'
|
||||||
|
|
||||||
|
cmd = get_bl_cmd(
|
||||||
|
background=True,
|
||||||
|
factory_startup=True,
|
||||||
|
blendfile=preview_blend,
|
||||||
|
script=constants.SCRIPTS_DIR / 'publish_library_assets.py',
|
||||||
|
library=publish_library.path,
|
||||||
|
assets=[get_asset_full_path(a) for a in context.selected_assets],
|
||||||
|
catalogs=[get_asset_catalog_path(a) for a in context.selected_assets]
|
||||||
|
)
|
||||||
|
|
||||||
|
print(cmd)
|
||||||
|
subprocess.call(cmd)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class ASSETLIB_OT_update_assets(Operator):
|
||||||
|
bl_idname = 'assetlibrary.update_assets'
|
||||||
|
bl_label = 'Update node'
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
data_type : EnumProperty(name="Type", items=lambda s, c: constants.DATA_TYPE_ITEMS)
|
||||||
|
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
|
||||||
|
|
||||||
|
if self.data_type == 'NodeTree':
|
||||||
|
assets = [context.space_data.edit_tree]
|
||||||
|
blend_data = bpy.data.node_groups
|
||||||
|
|
||||||
|
if self.selection == 'SELECTED':
|
||||||
|
assets = [ n.node_tree for n in context.space_data.edit_tree.nodes
|
||||||
|
if n.type == "GROUP" and n.select]
|
||||||
|
elif self.selection == 'ALL':
|
||||||
|
assets = list(bpy.data.node_groups)
|
||||||
|
|
||||||
|
elif self.data_type == 'Material':
|
||||||
|
asset = context.object.active_material
|
||||||
|
blend_data = bpy.data.materials
|
||||||
|
if self.selection == 'ALL':
|
||||||
|
assets = list(bpy.data.materials)
|
||||||
|
|
||||||
|
elif self.data_type == 'Object':
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
elif self.selection == 'CURRENT':
|
||||||
|
active_node = context.space_data.edit_tree
|
||||||
|
assets = [active_node]
|
||||||
|
else:
|
||||||
|
assets = list(bpy.data.node_groups)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
ASSETLIB_OT_reload_addon,
|
ASSETLIB_OT_reload_addon,
|
||||||
ASSETLIB_OT_add_library,
|
#ASSETLIB_OT_add_library,
|
||||||
ASSETLIB_OT_remove_library,
|
#ASSETLIB_OT_remove_library,
|
||||||
ASSETLIB_OT_synchronize
|
#ASSETLIB_OT_synchronize,
|
||||||
|
ASSETLIB_OT_save_asset_preview,
|
||||||
|
ASSETLIB_OT_make_custom_preview,
|
||||||
|
ASSETLIB_OT_publish_asset,
|
||||||
|
ASSETLIB_OT_publish_assets,
|
||||||
|
ASSETLIB_OT_add_tag,
|
||||||
|
ASSETLIB_OT_remove_tag
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
from asset_library.plugins import plugin
|
|
||||||
from asset_library.plugins import copy_folder
|
|
||||||
from asset_library.plugins import scan_folder
|
|
||||||
|
|
||||||
if 'bpy' in locals():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(plugin)
|
|
||||||
importlib.reload(copy_folder)
|
|
||||||
importlib.reload(scan_folder)
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
LibraryPlugin = plugin.LibraryPlugin
|
|
||||||
CopyFolder = copy_folder.CopyFolder
|
|
||||||
ScanFolder = scan_folder.ScanFolder
|
|
|
@ -4,9 +4,9 @@ Plugin for making an asset library of all blender file found in a folder
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from .scan_folder import ScanFolder
|
from asset_library.plugins.scan_folder import ScanFolder
|
||||||
from ..core.bl_utils import load_datablocks
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
from ..core.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)
|
||||||
|
|
|
@ -8,8 +8,8 @@ from os.path import expandvars
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import StringProperty
|
from bpy.props import StringProperty
|
||||||
|
|
||||||
from .library_plugin import LibraryPlugin
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
from ..core.file_utils import copy_dir
|
from asset_library.core.file_utils import copy_dir
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,9 @@ import time
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
||||||
|
|
||||||
from .library_plugin import LibraryPlugin
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
from ..core.template import Template
|
from asset_library.core.template import Template
|
||||||
from ..core.file_utils import install_module
|
from asset_library.core.file_utils import install_module
|
||||||
|
|
||||||
|
|
||||||
class Kitsu(LibraryPlugin):
|
class Kitsu(LibraryPlugin):
|
||||||
|
|
|
@ -17,12 +17,12 @@ from bpy.types import PropertyGroup
|
||||||
from bpy.props import StringProperty
|
from bpy.props import StringProperty
|
||||||
|
|
||||||
#from asset_library.common.functions import (norm_asset_datas,)
|
#from asset_library.common.functions import (norm_asset_datas,)
|
||||||
from ..core.bl_utils import get_addon_prefs, load_datablocks
|
from asset_library.core.bl_utils import get_addon_prefs, load_datablocks
|
||||||
from ..core.file_utils import read_file, write_file
|
from asset_library.core.file_utils import read_file, write_file
|
||||||
from ..core.template import Template
|
from asset_library.core.template import Template
|
||||||
from ..constants import (MODULE_DIR, RESOURCES_DIR)
|
from asset_library.constants import (MODULE_DIR, RESOURCES_DIR)
|
||||||
|
|
||||||
from ..data_type import (action, collection, file)
|
#from asset_library.data_type import (action, collection, file)
|
||||||
#from asset_library.common.library_cache import LibraryCacheDiff
|
#from asset_library.common.library_cache import LibraryCacheDiff
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ from pprint import pprint as pp
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty)
|
from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty)
|
||||||
|
|
||||||
from .library_plugin import LibraryPlugin
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
from ..core.template import Template
|
from asset_library.core.template import Template
|
||||||
from ..core.file_utils import install_module
|
from asset_library.core.file_utils import install_module
|
||||||
|
|
||||||
|
|
||||||
REQ_HEADERS = requests.utils.default_headers()
|
REQ_HEADERS = requests.utils.default_headers()
|
||||||
|
|
|
@ -15,9 +15,9 @@ from itertools import groupby
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
from bpy.props import (StringProperty, IntProperty, BoolProperty)
|
||||||
|
|
||||||
from .library_plugin import LibraryPlugin
|
from asset_library.plugins.library_plugin import LibraryPlugin
|
||||||
from ..core.bl_utils import load_datablocks
|
from asset_library.core.bl_utils import load_datablocks
|
||||||
from ..core.template import Template
|
from asset_library.core.template import Template
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,18 +6,19 @@ from bpy.props import (CollectionProperty, StringProperty)
|
||||||
|
|
||||||
from . properties import AssetLibrary
|
from . properties import AssetLibrary
|
||||||
from . core.bl_utils import get_addon_prefs
|
from . core.bl_utils import get_addon_prefs
|
||||||
from . core.asset_library_utils import update_library_path
|
from . core.lib_utils import update_library_path
|
||||||
|
|
||||||
|
|
||||||
class AssetLibraryPrefs(AddonPreferences):
|
class AssetLibraryPrefs(AddonPreferences):
|
||||||
bl_idname = __package__
|
bl_idname = __package__
|
||||||
|
|
||||||
|
config_path : StringProperty(subtype="FILE_PATH")
|
||||||
libraries : CollectionProperty(type=AssetLibrary)
|
libraries : CollectionProperty(type=AssetLibrary)
|
||||||
bundle_directory : StringProperty(
|
bundle_directory : StringProperty(
|
||||||
name="Path",
|
name="Path",
|
||||||
subtype='DIR_PATH',
|
subtype='DIR_PATH',
|
||||||
default='',
|
default='',
|
||||||
update=update_library_path
|
update=lambda s, c: update_library_path()
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
@ -26,6 +27,7 @@ class AssetLibraryPrefs(AddonPreferences):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column(align=False)
|
col = layout.column(align=False)
|
||||||
|
|
||||||
|
col.prop(self, "config_path", text='Config')
|
||||||
col.prop(self, "bundle_directory", text='Bundle Directory')
|
col.prop(self, "bundle_directory", text='Bundle Directory')
|
||||||
col.separator()
|
col.separator()
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
import os
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import (AddonPreferences, PropertyGroup)
|
from bpy.types import (AddonPreferences, PropertyGroup)
|
||||||
from bpy.props import (BoolProperty, StringProperty, CollectionProperty,
|
from bpy.props import (BoolProperty, StringProperty, CollectionProperty,
|
||||||
EnumProperty, IntProperty, PointerProperty)
|
EnumProperty, IntProperty, PointerProperty)
|
||||||
|
|
||||||
from .core.bl_utils import get_addon_prefs
|
|
||||||
from .core.file_utils import import_module_from_path, norm_str
|
|
||||||
from .core.asset_library_utils import update_library_path
|
|
||||||
from .constants import PLUGINS, PLUGINS_DIR, PLUGINS_ITEMS
|
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():
|
def load_plugins():
|
||||||
|
from .plugins.library_plugin import LibraryPlugin
|
||||||
print('Asset Library: Load Library Plugins')
|
print('Asset Library: Load Library Plugins')
|
||||||
|
|
||||||
plugin_files = list(PLUGINS_DIR.glob('*.py'))
|
plugin_files = list(PLUGINS_DIR.glob('*.py'))
|
||||||
|
@ -28,29 +31,31 @@ def load_plugins():
|
||||||
mod = import_module_from_path(plugin_file)
|
mod = import_module_from_path(plugin_file)
|
||||||
|
|
||||||
for name, obj in inspect.getmembers(mod):
|
for name, obj in inspect.getmembers(mod):
|
||||||
if not inspect.isclass(obj):
|
if not inspect.isclass(obj) or (obj is LibraryPlugin):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if AssetLibrary not in obj.__mro__ or obj is AssetLibrary:
|
if (LibraryPlugin not in obj.__mro__) or (obj in PLUGINS):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(f'Register Plugin {name}')
|
print(f'Register Plugin {name}')
|
||||||
bpy.utils.register_class(obj)
|
bpy.utils.register_class(obj)
|
||||||
setattr(Plugins, norm_str(obj.name), PointerProperty(type=obj))
|
setattr(Plugins, norm_str(obj.name), PointerProperty(type=obj))
|
||||||
PLUGINS.append(obj)
|
PLUGINS[obj.name] = obj
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'Could not register plugin {name}')
|
print(f'Could not register plugin {name}')
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
plugin_items = [('NONE', 'None', '', 0)]
|
plugins = sorted(PLUGINS.keys())
|
||||||
plugin_items += [(norm_str(p.name, format=str.upper), p.name, "", i+1) for i, a in enumerate(PLUGINS)]
|
plugin_items = [('none', 'None', '', 0)]
|
||||||
|
plugin_items += [(norm_str(p), p, "") for p in plugins]
|
||||||
|
|
||||||
PLUGINS_ITEMS[:] = plugin_items
|
PLUGINS_ITEMS[:] = plugin_items
|
||||||
|
|
||||||
return PLUGINS
|
return PLUGINS
|
||||||
|
|
||||||
|
|
||||||
class Plugins(PropertyGroup):
|
class Plugins(PropertyGroup):
|
||||||
"""Container holding the registed library plugins"""
|
"""Container holding the registed library plugins"""
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
@ -60,15 +65,19 @@ class Plugins(PropertyGroup):
|
||||||
class AssetLibrary(PropertyGroup):
|
class AssetLibrary(PropertyGroup):
|
||||||
"""Library item defining one library with his plugin and settings"""
|
"""Library item defining one library with his plugin and settings"""
|
||||||
|
|
||||||
name : StringProperty(name='Name', default='', update=update_library_path)
|
name : StringProperty(name='Name', default='', update=lambda s, c : update_library_path())
|
||||||
expand : BoolProperty(name='Expand', default=False)
|
expand : BoolProperty(name='Expand', default=False)
|
||||||
use : BoolProperty(name='Use', default=True, update=update_library_path)
|
use : BoolProperty(name='Use', default=True, update=lambda s, c : update_library_path())
|
||||||
is_env : BoolProperty(default=False)
|
is_user : BoolProperty(default=True)
|
||||||
#path : StringProperty(subtype='DIR_PATH')
|
path : StringProperty(subtype='DIR_PATH', update=lambda s, c : update_library_path())
|
||||||
|
|
||||||
plugins : PointerProperty(type=Plugins)
|
plugins : PointerProperty(type=Plugins)
|
||||||
plugin_name : EnumProperty(items=lambda s, c : PLUGINS_ITEMS)
|
plugin_name : EnumProperty(items=lambda s, c : PLUGINS_ITEMS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugin(self):
|
||||||
|
return getattr(self.plugins, self.plugin_name, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def index(self):
|
def index(self):
|
||||||
prefs = get_addon_prefs()
|
prefs = get_addon_prefs()
|
||||||
|
@ -89,6 +98,30 @@ class AssetLibrary(PropertyGroup):
|
||||||
layout.separator(factor=3)
|
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):
|
def draw(self, layout):
|
||||||
prefs = get_addon_prefs()
|
prefs = get_addon_prefs()
|
||||||
#col = layout.column(align=True)
|
#col = layout.column(align=True)
|
||||||
|
@ -102,6 +135,10 @@ class AssetLibrary(PropertyGroup):
|
||||||
#row.label(icon="ASSET_MANAGER")
|
#row.label(icon="ASSET_MANAGER")
|
||||||
row.prop(self, 'name', text='')
|
row.prop(self, 'name', text='')
|
||||||
row.separator(factor=0.5)
|
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 = row.operator("assetlibrary.synchronize", icon='UV_SYNC_SELECT', text='')
|
||||||
op.name = self.name
|
op.name = self.name
|
||||||
|
@ -112,14 +149,26 @@ class AssetLibrary(PropertyGroup):
|
||||||
#self.draw_operators(row)
|
#self.draw_operators(row)
|
||||||
if self.expand:
|
if self.expand:
|
||||||
col = box.column(align=False)
|
col = box.column(align=False)
|
||||||
col.use_property_split = True
|
|
||||||
|
|
||||||
col.prop(self, "path")
|
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 = (
|
classes = (
|
||||||
Plugins,
|
Plugins,
|
||||||
AssetLibrary,
|
AssetLibrary,
|
||||||
|
WindowManagerProperties
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,8 +176,11 @@ def register():
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
bpy.types.WindowManager.asset_library = PointerProperty(type=WindowManagerProperties)
|
||||||
load_plugins()
|
load_plugins()
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
del bpy.types.WindowManager.asset_library
|
Binary file not shown.
Binary file not shown.
|
@ -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))
|
75
ui.py
75
ui.py
|
@ -1,6 +1,25 @@
|
||||||
|
|
||||||
from bpy.types import FILEBROWSER_HT_header, ASSETBROWSER_MT_editor_menus
|
import bpy
|
||||||
from .core.asset_library_utils import get_active_library
|
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):
|
def draw_assetbrowser_header(self, context):
|
||||||
|
@ -51,16 +70,19 @@ def draw_assetbrowser_header(self, context):
|
||||||
).region_type = 'TOOL_PROPS'
|
).region_type = 'TOOL_PROPS'
|
||||||
|
|
||||||
|
|
||||||
def draw_assetbrowser_header(self, context):
|
def draw_assetbrowser_asset_menu(self, context):
|
||||||
if not get_active_library():
|
layout = self.layout
|
||||||
return
|
layout.operator("assetlibrary.publish_assets", text='Publish Assets', icon='ASSET_MANAGER')
|
||||||
|
|
||||||
self.layout.separator()
|
# if not get_active_library():
|
||||||
box = self.layout.box()
|
# return
|
||||||
row = box.row()
|
|
||||||
row.separator(factor=0.5)
|
# self.layout.separator()
|
||||||
row.label(text='Asset Library')
|
# box = self.layout.box()
|
||||||
row.separator(factor=0.5)
|
# row = box.row()
|
||||||
|
# row.separator(factor=0.5)
|
||||||
|
# row.label(text='Asset Library')
|
||||||
|
# row.separator(factor=0.5)
|
||||||
|
|
||||||
# classes = (,
|
# classes = (,
|
||||||
# # ASSETLIB_PT_pose_library_editing,
|
# # ASSETLIB_PT_pose_library_editing,
|
||||||
|
@ -69,18 +91,35 @@ def draw_assetbrowser_header(self, context):
|
||||||
# # ASSETLIB_PT_libraries
|
# # ASSETLIB_PT_libraries
|
||||||
# )
|
# )
|
||||||
|
|
||||||
classes = []
|
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:
|
def register() -> None:
|
||||||
for cls in classes:
|
for bl_class in bl_classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(bl_class)
|
||||||
|
|
||||||
ASSETBROWSER_MT_editor_menus.append(draw_assetbrowser_header)
|
#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:
|
def unregister() -> None:
|
||||||
for cls in reversed(classes):
|
for bl_class in reversed(bl_classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(bl_class)
|
||||||
|
|
||||||
ASSETBROWSER_MT_editor_menus.remove(draw_assetbrowser_header)
|
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