First Commit

This commit is contained in:
“christopheseux”
2022-12-24 15:30:32 +01:00
parent 3c7f133a85
commit e3d24aa4e3
49 changed files with 8466 additions and 4 deletions
+25
View File
@@ -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()
+124
View File
@@ -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))
+163
View File
@@ -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))
+14
View File
@@ -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
+22
View File
@@ -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()
+102
View File
@@ -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)