First Commit
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
|
||||
from asset_library.collection import (
|
||||
gui,
|
||||
operators,
|
||||
keymaps,
|
||||
build_collection_blends,
|
||||
create_collection_library)
|
||||
|
||||
if 'bpy' in locals():
|
||||
import importlib
|
||||
|
||||
importlib.reload(gui)
|
||||
importlib.reload(operators)
|
||||
importlib.reload(keymaps)
|
||||
importlib.reload(build_collection_blends)
|
||||
importlib.reload(create_collection_library)
|
||||
|
||||
|
||||
def register():
|
||||
operators.register()
|
||||
keymaps.register()
|
||||
|
||||
def unregister():
|
||||
operators.unregister()
|
||||
keymaps.unregister()
|
||||
@@ -0,0 +1,124 @@
|
||||
import argparse
|
||||
import bpy
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
from time import time, sleep
|
||||
from itertools import groupby
|
||||
from asset_library.common.bl_utils import load_datablocks, col_as_asset
|
||||
from asset_library.constants import ASSETLIB_FILENAME
|
||||
|
||||
""" blender_assets.libs.json data Structure
|
||||
[
|
||||
{
|
||||
'name': 'chars/main',
|
||||
'id': '013562-56315-4563156-123',
|
||||
'children':
|
||||
[
|
||||
{
|
||||
'filepath' : '/z/...',
|
||||
'name' : 'collection name',
|
||||
'tags' : ['variation', 'machin', 'chose'],
|
||||
'metadata' : {'filepath': '$PROJECT/...', 'version' : 'mushable'}
|
||||
},
|
||||
{
|
||||
'filepath' : '/z/...',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
"""
|
||||
|
||||
def build_collection_blends(path, categories=None, clean=True):
|
||||
|
||||
t0 = time()
|
||||
scn = bpy.context.scene
|
||||
scn.render.resolution_x = scn.render.resolution_y = 1000
|
||||
|
||||
json_path = Path(path) / ASSETLIB_FILENAME
|
||||
if not json_path.exists():
|
||||
return
|
||||
|
||||
# _col_datas = json.loads(json_path.read())[category]
|
||||
category_datas = json.loads(json_path.read_text())
|
||||
|
||||
for category_data in category_datas:
|
||||
if categories and category_data['name'] not in categories:
|
||||
continue
|
||||
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
|
||||
|
||||
#category_data = next(c for c in category_datas if c['name'] == category)
|
||||
#_col_datas = category_data['children']
|
||||
|
||||
cat_name = category_data['name']
|
||||
build_path = Path(path) / cat_name / f'{cat_name}.blend'
|
||||
|
||||
## re-iterate in grouped filepath
|
||||
col_datas = sorted(category_data['children'], key=lambda x: x['filepath'])
|
||||
for filepath, col_data_groups in groupby(col_datas, key=lambda x: x['filepath']):
|
||||
#f = Path(f)
|
||||
if not Path(filepath).exists():
|
||||
print(f'Not exists: {filepath}')
|
||||
continue
|
||||
|
||||
col_data_groups = list(col_data_groups)
|
||||
|
||||
col_names = [a['name'] for a in col_data_groups]
|
||||
linked_cols = load_datablocks(filepath, col_names, link=True, type='collections')
|
||||
|
||||
for i, col in enumerate(linked_cols):
|
||||
# iterate in linked collection and associated data
|
||||
if not col:
|
||||
continue
|
||||
asset_data = col_data_groups[i]
|
||||
|
||||
## asset_data -> {'filepath': str, 'tags': list, 'metadata': dict}
|
||||
|
||||
## Directly link as collection inside a marked collection with same name
|
||||
marked_col = col_as_asset(col, verbose=True)
|
||||
marked_col.asset_data.description = asset_data.get('description', '')
|
||||
marked_col.asset_data.catalog_id = category_data['id'] # assign catalog
|
||||
|
||||
for k, v in asset_data.get('metadata', {}).items():
|
||||
marked_col.asset_data[k] = v
|
||||
|
||||
## exclude collections and generate preview
|
||||
bpy.ops.ed.lib_id_generate_preview({"id": marked_col}) # preview gen
|
||||
vcol = bpy.context.view_layer.layer_collection.children[marked_col.name]
|
||||
vcol.exclude = True
|
||||
|
||||
sleep(1.0)
|
||||
|
||||
## clear all objects (can be very long with a lot of objects...):
|
||||
if clean:
|
||||
print('Removing links...')
|
||||
for lib in reversed(bpy.data.libraries):
|
||||
bpy.data.libraries.remove(lib)
|
||||
|
||||
|
||||
|
||||
# Créer les dossiers intermediaires
|
||||
build_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
print('Saving to', build_path)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(build_path), compress=False)
|
||||
|
||||
print("build time:", f'{time() - t0:.1f}s')
|
||||
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
|
||||
if __name__ == '__main__' :
|
||||
parser = argparse.ArgumentParser(description='build_collection_blends',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('-path') # Trouve/créer le json assetlib.json en sous-dossier de libdir
|
||||
parser.add_argument('--category') # Lit la category dans le json et a link tout dans le blend
|
||||
|
||||
if '--' in sys.argv :
|
||||
index = sys.argv.index('--')
|
||||
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||
|
||||
args = parser.parse_args()
|
||||
build_collection_blends(**vars(args))
|
||||
@@ -0,0 +1,163 @@
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
import subprocess
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
from asset_library.common.functions import create_catalog_file
|
||||
from asset_library.common.file_utils import get_last_files
|
||||
from asset_library.constants import ASSETLIB_FILENAME
|
||||
|
||||
|
||||
"""
|
||||
### Create asset collection
|
||||
|
||||
## create_collection_library: generate all category blend from json
|
||||
## if source_directory is set, call create_collection_json
|
||||
|
||||
## # create_collection_json:
|
||||
## # scan marked blend, create json and call create_catalog_file
|
||||
|
||||
## # create_catalog_file
|
||||
## # create catalog file from json file
|
||||
|
||||
### Json Structure
|
||||
[
|
||||
{
|
||||
'name': 'chars/main',
|
||||
'id': '013562-56315-4563156-123',
|
||||
'children':
|
||||
[
|
||||
{
|
||||
'filepath' : '/z/...',
|
||||
'name' : 'collection name',
|
||||
'tags' : ['variation', 'machin', 'chose'],
|
||||
'metadata' : {'filepath': '$PROJECT/...', 'version' : 'mushable'}
|
||||
},
|
||||
{
|
||||
'filepath' : '/z/...',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
"""
|
||||
|
||||
def create_collection_json(path, source_directory):
|
||||
'''Create a Json from every marked collection in blends
|
||||
contained in folderpath (respect hierachy)
|
||||
'''
|
||||
|
||||
json_path = Path(path) / ASSETLIB_FILENAME
|
||||
|
||||
# scan all last version of the assets ?
|
||||
# get last version files ?
|
||||
# or open all blends and look only for marked collection ? (if versionned, get still get only last)
|
||||
|
||||
# get all blend in dir and subdirs (only last when versionned _v???)
|
||||
blends = get_last_files(source_directory, pattern=r'(_v\d{3})?\.blend$', only_matching=True)
|
||||
|
||||
root_path = Path(source_directory).as_posix().rstrip('/') + '/'
|
||||
print('root_path: ', root_path)
|
||||
# open and check data block marked as asset
|
||||
|
||||
category_datas = []
|
||||
for i, blend in enumerate(blends):
|
||||
fp = Path(blend)
|
||||
print(f'{i+1}/{len(blends)}')
|
||||
|
||||
## What is considered a grouping category ? top level folders ? parents[1] ?
|
||||
|
||||
## Remove root path and extension
|
||||
## top level folder ('chars'), problem if blends at root
|
||||
category = fp.as_posix().replace(root_path, '').split('/')[0]
|
||||
|
||||
## full blend path (chars/perso/perso)
|
||||
# category = fp.as_posix().replace(root_path, '').rsplit('.', 1)[0]
|
||||
|
||||
print(category)
|
||||
|
||||
with bpy.data.libraries.load(blend, link=True, assets_only=True) as (data_from, data_to):
|
||||
## just listing
|
||||
col_name_list = [c for c in data_from.collections]
|
||||
|
||||
if not col_name_list:
|
||||
continue
|
||||
|
||||
col_list = next((c['children'] for c in category_datas if c['name'] == category), None)
|
||||
if col_list is None:
|
||||
col_list = []
|
||||
category_data = {
|
||||
'name': category,
|
||||
'id': str(uuid.uuid4()),
|
||||
'children': col_list,
|
||||
}
|
||||
category_datas.append(category_data)
|
||||
|
||||
|
||||
blend_source_path = blend.as_posix()
|
||||
if (project_root := os.environ.get('PROJECT_ROOT')):
|
||||
blend_source_path = blend_source_path.replace(project_root, '$PROJECT_ROOT')
|
||||
|
||||
|
||||
for name in col_name_list:
|
||||
data = {
|
||||
'filepath' : blend,
|
||||
'name' : name,
|
||||
# 'tags' : [],
|
||||
'metadata' : {'filepath': blend_source_path},
|
||||
}
|
||||
|
||||
col_list.append(data)
|
||||
|
||||
json_path.write_text(json.dumps(category_datas, indent='\t'))
|
||||
## create text catalog from json (keep_existing_category ?)
|
||||
create_catalog_file(json_path, keep_existing_category=True)
|
||||
|
||||
|
||||
def create_collection_library(path, source_directory=None):
|
||||
'''
|
||||
path: store collection library (json and blends database)
|
||||
source_directory: if a source is set, rebuild json and library
|
||||
'''
|
||||
|
||||
if source_directory:
|
||||
if not Path(source_directory).exists():
|
||||
print(f'Source directory not exists: {source_directory}')
|
||||
return
|
||||
|
||||
## scan source and build json in assetlib dir root
|
||||
create_collection_json(path, source_directory)
|
||||
|
||||
json_path = Path(path) / ASSETLIB_FILENAME
|
||||
if not json_path.exists():
|
||||
print(f'No json found at: {json_path}')
|
||||
return
|
||||
|
||||
file_datas = json.loads(json_path.read())
|
||||
|
||||
## For each category in json, execute build_assets_blend script
|
||||
script = Path(__file__).parent / 'build_collection_blends.py'
|
||||
#empty_blend = Path(__file__).parent / 'empty_scene.blend'
|
||||
|
||||
# for category, asset_datas in file_datas.items():
|
||||
for category_data in file_datas:
|
||||
## add an empty blend as second arg
|
||||
cmd = [bpy.app.binary_path, '--python', str(script), '--', '--path', path, '--category', category_data['name']]
|
||||
print('cmd: ', cmd)
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
if __name__ == '__main__' :
|
||||
parser = argparse.ArgumentParser(description='Create Collection Library',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('--path') # trouve/créer le json assetlib.json en sous-dossier de libdir
|
||||
|
||||
if '--' in sys.argv :
|
||||
index = sys.argv.index('--')
|
||||
sys.argv = [sys.argv[index-1], *sys.argv[index+1:]]
|
||||
|
||||
args = parser.parse_args()
|
||||
create_collection_library(**vars(args))
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def draw_context_menu(layout):
|
||||
params = bpy.context.space_data.params
|
||||
|
||||
return
|
||||
|
||||
|
||||
def draw_header(layout):
|
||||
'''Draw the header of the Asset Browser Window'''
|
||||
|
||||
return
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
from typing import List, Tuple
|
||||
|
||||
import bpy
|
||||
|
||||
addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = []
|
||||
|
||||
def register():
|
||||
wm = bpy.context.window_manager
|
||||
addon = wm.keyconfigs.addon
|
||||
if not addon:
|
||||
return
|
||||
|
||||
km = addon.keymaps.new(name="File Browser Main", space_type="FILE_BROWSER")
|
||||
kmi = km.keymap_items.new("assetlib.load_asset", "LEFTMOUSE", "DOUBLE_CLICK") # , shift=True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
def unregister():
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
@@ -0,0 +1,102 @@
|
||||
import bpy
|
||||
from bpy.types import Context, Operator
|
||||
from bpy_extras import asset_utils
|
||||
|
||||
from fnmatch import fnmatch
|
||||
import os
|
||||
import fnmatch
|
||||
from os.path import expandvars
|
||||
from typing import List, Tuple, Set
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
from asset_library.common.bl_utils import load_col
|
||||
from asset_library.common.functions import get_active_library
|
||||
|
||||
|
||||
|
||||
class ASSETLIB_OT_load_asset(Operator):
|
||||
bl_idname = "assetlib.load_asset"
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
bl_label = 'Load Asset'
|
||||
bl_description = 'Link and override asset in current file'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
|
||||
cls.poll_message_set("Current editor is not an asset browser")
|
||||
return False
|
||||
|
||||
lib = get_active_library()
|
||||
if not lib or lib.data_type != 'COLLECTION':
|
||||
return False
|
||||
|
||||
if not context.active_file or 'filepath' not in context.active_file.asset_data:
|
||||
cls.poll_message_set("Has not filepath property")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
|
||||
print('Load Asset')
|
||||
|
||||
lib = get_active_library()
|
||||
print(lib, lib.data_type)
|
||||
|
||||
# dir(asset) : 'asset_data', 'bl_rna', 'id_type', 'local_id', 'name', 'preview_icon_id', 'relative_path', 'rna_type']
|
||||
# dir(asset.asset_data) : 'active_tag', 'author', 'bl_rna', 'catalog_id', 'catalog_simple_name', 'description', 'rna_type', 'tags']
|
||||
|
||||
## get source path
|
||||
# asset_file_handle = context.asset_file_handle
|
||||
# if asset_file_handle is None:
|
||||
# return {'CANCELLED'}
|
||||
# if asset_file_handle.local_id:
|
||||
# return {'CANCELLED'}
|
||||
# asset_library_ref = context.asset_library_ref
|
||||
# source_directory = bpy.types.AssetHandle.get_full_library_path(
|
||||
# asset_file_handle, asset_library_ref
|
||||
# )
|
||||
|
||||
asset = context.active_file
|
||||
if not asset:
|
||||
self.report({"ERROR"}, 'No asset selected')
|
||||
return {'CANCELLED'}
|
||||
|
||||
fp = expandvars(asset.asset_data['filepath'])
|
||||
name = asset.name
|
||||
|
||||
## set mode to object
|
||||
if context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
## get the real direct path with expand_var
|
||||
print('path expanded: ', fp)
|
||||
|
||||
if not Path(fp).exists():
|
||||
self.report({'ERROR'}, f'Not exists: {fp}')
|
||||
return {'CANCELLED'}
|
||||
|
||||
res = load_col(fp, name, link=True, override=True, rig_pattern='*_rig')
|
||||
if res:
|
||||
if res.type == 'ARMATURE':
|
||||
self.report({'INFO'}, f'Override rig {res.name}')
|
||||
elif res.type == 'EMPTY':
|
||||
self.report({'INFO'}, f'Instance collection {res.name}')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
### --- REGISTER ---
|
||||
|
||||
classes = (
|
||||
ASSETLIB_OT_load_asset,
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
Reference in New Issue
Block a user