finish refacto, now testing and fixing bug

master
“christopheseux” 2023-01-03 10:17:58 +01:00
parent 501cb460c8
commit e538c997a9
6 changed files with 214 additions and 177 deletions

View File

@ -45,6 +45,10 @@ if 'bpy' in locals():
importlib.reload(operators)
importlib.reload(constants)
importlib.reload(action)
importlib.reload(file)
importlib.reload(collection)
import bpy
import os

View File

@ -794,6 +794,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
frame_start: IntProperty(name="Frame Start")
frame_end: IntProperty(name="Frame End")
tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)')
description: StringProperty(name='Description')
#library: EnumProperty(items=lambda s, c: s.library_items, name="Library")
#library: EnumProperty(items=lambda s, c: LIBRARY_ITEMS, name="Library")
@ -828,6 +829,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
def draw(self, context):
layout = self.layout
layout.separator()
prefs = get_addon_prefs()
#row = layout.row(align=True)
layout.use_property_split = True
@ -850,6 +852,8 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
#layout.separator()
#self.draw_tags(self.asset_action, layout)
layout.prop(self, 'tags')
layout.prop(self, 'description', text='Description')
#layout.prop(prefs, 'author', text='Author')
#layout.prop()
@ -889,7 +893,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
#self.sce
#lib = self.current_library
self.tags = os.getlogin()
self.tags = ''
#print(self, self.library_items)
@ -901,6 +905,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
def action_to_asset(self, action):
#action.asset_mark()
prefs = get_addon_prefs()
action.name = self.name
action.asset_generate_preview()
@ -930,6 +935,10 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
action.asset_data['is_single_frame'] = is_single_frame
action.asset_data['rig'] = bpy.context.object.name
action.asset_data.description = self.description
action.asset_data.author = prefs.author
col = get_overriden_col(bpy.context.object)
if col:
action.asset_data['col'] = col.name
@ -1003,10 +1012,10 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
lib = prefs.libraries[self.current_library.store_library]
#lib_path = lib.library_path
name = lib.adapter.norm_file_name(self.name)
asset_path = lib.adapter.get_asset_path(name=name, catalog=self.catalog)
img_path = lib.adapter.get_path('image', name, asset_path)
video_path = lib.adapter.get_path('video', name, asset_path)
#name = lib.adapter.norm_file_name(self.name)
asset_path = lib.adapter.get_asset_path(name=self.name, catalog=self.catalog)
img_path = lib.adapter.get_image_path(name=self.name, catalog=self.catalog, filepath=asset_path)
video_path = lib.adapter.get_video_path(name=self.name, catalog=self.catalog, filepath=asset_path)
## Copy Action
current_action = ob.animation_data.action
@ -1027,13 +1036,16 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
lib.adapter.write_asset(asset=asset_action, asset_path=asset_path)
asset_description = lib.adapter.get_asset_description(
asset=asset_action,
catalog=self.catalog,
modified=time.time_ns()
)
lib.adapter.write_description_file(asset_description, asset_path)
# asset_description = lib.adapter.get_asset_description(dict(
# author=prefs.author,
# assets=[dict(
# name=asset_action.name,
# description=self.description,
# catalog=self.catalog,
# tags=self.tags.split(',')
# )]), asset_path
# )
# lib.adapter.write_description_file(asset_description, asset_path)
# Restore action and cleanup
ob.animation_data.action = current_action
@ -1045,7 +1057,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
# TODO Write a proper method for this
diff_path = Path(bpy.app.tempdir, 'diff.json')
diff = [dict(a, operation='ADD') for a in lib.adapter.norm_asset_datas([asset_description])]
diff = [dict(a, operation='ADD') for a in lib.adapter.norm_cache([asset_description])]
diff_path.write_text(json.dumps(diff, indent=4))
bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True)

View File

@ -20,6 +20,7 @@ import uuid
import time
from functools import partial
import subprocess
from glob import glob
class AssetLibraryAdapter(PropertyGroup):
@ -108,6 +109,11 @@ class AssetLibraryAdapter(PropertyGroup):
def to_dict(self):
return {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}
@property
def format_data(self):
"""Dict for formating template"""
return dict(self.to_dict(), bundle_dir=self.library.library_path)
def fetch(self):
raise Exception('This method need to be define in the adapter')
@ -193,29 +199,78 @@ class AssetLibraryAdapter(PropertyGroup):
return asset_path
def get_template_path(self, template, name, asset_path, catalog):
if template.startswith('.'): #the template is relative
template = Path(asset_path, template).as_posix()
def get_image_path(self, name, catalog, filepath):
raise Exception('Need to be defined in the adapter')
params = {
'asset_name': name,
'asset_path': Path(asset_path),
'catalog': catalog,
'catalog_name': catalog.replace('/', '_'),
def get_video_path(self, name, catalog, filepath):
raise Exception('Need to be defined in the adapter')
def format_asset_data(self, data):
"""Get a dict for use in template fields"""
return {
'asset_name': data['name'],
'asset_path': Path(data['filepath']),
'catalog': data['catalog'],
'catalog_name': data['catalog'].replace('/', '_'),
}
return self.format_path(template, **params)
def get_description_path(self, name, asset_path, catalog) -> Path:
""""Get the path of the json or yaml describing all assets data in one file"""
return self.get_template_path(self.library.template_description, name, asset_path, catalog)
def format_path(self, template, data={}, **kargs):
if not template:
return None
if data:
data = self.format_asset_data(dict(data, **kargs))
else:
data = kargs
if template.startswith('.'): #the template is relative
template = Path(data['asset_path'], template).as_posix()
def get_image_path(self, name, asset_path, catalog) -> Path:
return self.get_template_path(self.library.template_image, name, asset_path, catalog)
params = dict(
**data,
**self.format_data,
)
def get_video_path(self, name, asset_path, catalog) -> Path:
return self.get_template_path(self.library.template_video, name, asset_path, catalog)
return Template(template).format(params).resolve()
def find_path(self, template, data, **kargs):
path = self.format_path(template, data, **kargs)
paths = glob(str(path))
if paths:
return Path(paths[0])
# def get_template_path(self, template, name, asset_path, catalog, **kargs):
# if not template:
# return None
# if template.startswith('.'): #the template is relative
# template = Path(asset_path, template).as_posix()
# params = {
# 'asset_name': name,
# 'asset_path': Path(asset_path),
# 'catalog': catalog,
# 'catalog_name': catalog.replace('/', '_'),
# }
# params.update(kargs)
# return self.format_path(template, **params)
# def get_description_path(self, name, asset_path, catalog, **kargs) -> Path:
# """"Get the path of the json or yaml describing all assets data in one file"""
# return self.get_template_path(self.library.template_description, name, asset_path, catalog)
# def get_image_path(self, name, asset_path, catalog, **kargs) -> Path:
# return self.get_template_path(self.library.template_image, name, asset_path, catalog)
# def get_video_path(self, name, asset_path, catalog, **kargs) -> Path:
# return self.get_template_path(self.library.template_video, name, asset_path, catalog)
'''
def get_path(self, type, name, asset_path, template=None) -> Path:
@ -322,7 +377,7 @@ class AssetLibraryAdapter(PropertyGroup):
for cat_path, cat_data in catalog_data.items():
norm_data[cat_path] = cat_data
for p in Path(cat_path).parents[:-1]:
if p in data or p in norm_data:
if p in cat_data or p in norm_data:
continue
norm_data[p.as_posix()] = {'id': str(uuid.uuid4()), 'name': '-'.join(p.parts)}
@ -383,66 +438,12 @@ class AssetLibraryAdapter(PropertyGroup):
#layout.separator()
self.module_type.gui.draw_context_menu(layout)
def group_key(self, asset_data):
"""Key used to group assets inside one blend"""
# def group_key(self, asset_data):
# """Key used to group assets inside one blend"""
catalog_parts = asset_data['catalog'].split('/') + [asset_data['name']]
# catalog_parts = asset_data['catalog'].split('/') + [asset_data['name']]
return catalog_parts[:self.library.blend_depth]
#def transfert_preview(self, )
'''
def generate_previews(self, assets, callback):
def _generate_previews(assets, callback, src_assets=None):
if src_assets:
src_assets = []
if bpy.app.is_job_running('RENDER_PREVIEW'):
print("Waiting for render...")
return 0.2 # waiting time
while assets: # generate next preview
asset = assets.pop()
#print(f"Creating preview for world {world.name}...")
asset_path = asset.asset_data['filepath']
src_asset = self.load_datablocks(asset_path, names=asset.name, link=False, type=self.data_types)
if not src_asset:
#print(f'No asset named {asset.name} in {asset_path]}')
return
src_assets.append(src_asset)
# # set image in the preview object's material
# obj = bpy.context.active_object
# image = world.node_tree.nodes['Environment Texture'].image
# obj.material_slots[0].material.node_tree.nodes['Image Texture'].image = image
if self.data_type == 'COLLECTION':
asset.children.link(src_asset)
# start preview render
with bpy.context.temp_override(id=asset):
bpy.ops.ed.lib_id_generate_preview()
return 0.2
for asset in src_asset:
asset.user_clear()
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
callback()
return None
assets = assets.copy()
# create preview images
bpy.app.timers.register(
functools.partial(
_generate_previews,
assets,
callback
)
)
'''
# return catalog_parts[:self.library.blend_depth]
def generate_blend_preview(self, asset_description):
asset_name = asset_description['name']
@ -480,7 +481,7 @@ class AssetLibraryAdapter(PropertyGroup):
def generate_asset_preview(self, asset_description):
"""Only generate preview when conforming a library"""
print('\ngenerate_preview', asset_description['filepath'])
#print('\ngenerate_preview', asset_description['filepath'])
scn = bpy.context.scene
#Creating the preview for collection, object or material
@ -489,31 +490,55 @@ class AssetLibraryAdapter(PropertyGroup):
data_type = self.data_type #asset_description['data_type']
asset_path = self.format_path(asset_description['filepath'])
# Check if a source video exists and if so copying it in the new directory
for asset_data in asset_description['assets']:
dst_asset_path = self.get_asset_bundle_path(asset_data)
dst_video_path = self.format_path(self.library.template_video, asset_data, filepath=dst_asset_path) #Template(src_video_path).find(asset_data, asset_path=dst_asset_path, **self.format_data)
if dst_video_path.exists():
print(f'The dest video {dst_video_path} already exist')
continue
src_video_template = asset_data.get('video')
if not src_video_template:
continue
src_video_path = self.find_path(src_video_template, asset_data, filepath=asset_path)#Template(src_video_path).find(asset_data, asset_path=dst_asset_path, **self.format_data)
if src_video_path:
self.copy_file(src_video_path, dst_video_path)
print(f'Copy video from {src_video_path} to {dst_video_path}')
# Check if asset as a preview image or need it to be generated
asset_data_names = {}
for asset_data in asset_description['assets']:
name = asset_data['name']
catalog = asset_data['catalog']
dst_asset_path = self.get_asset_bundle_path(asset_data)
dst_image_path = self.get_image_path(name, asset_path, catalog)
dst_image_path = self.format_path(self.library.template_image, asset_data, filepath=dst_asset_path)
if dst_image_path.exists():
print(f'The dest image {dst_image_path} already exist')
continue
# Check if a source image exists and if so copying it in the new directory
src_image_path = asset_data.get('image')
if src_image_path:
src_image_path = self.get_template_path(src_image_path, name, asset_path, catalog)
if src_image_path and src_image_path.exists():
src_image_template = asset_data.get('image')
if src_image_template:
src_image_path = self.find_path(src_image_template, asset_data, filepath=asset_path)
if src_image_path:
self.copy_file(src_image_path, dst_image_path)
#print(f'Copy image from {src_image_path} to {dst_image_path}')
return
#Store in a dict all asset_data that does not have preview
asset_data_names[name] = dict(asset_data, image_path=dst_image_path)
if not asset_data_names:
# No preview to generate
return
#print('Making Preview for', asset_data_names)
asset_names = list(asset_data_names.keys())
assets = self.load_datablocks(asset_path, names=asset_names, link=True, type=data_type)
@ -524,6 +549,12 @@ class AssetLibraryAdapter(PropertyGroup):
asset_data = asset_data_names[asset.name]
image_path = asset_data['image_path']
if asset.preview:
print(f'Writing asset preview to {image_path}')
self.write_preview(asset.preview, image_path)
continue
if data_type == 'COLLECTION':
bpy.ops.object.collection_instance_add(name=asset.name)
@ -544,6 +575,7 @@ class AssetLibraryAdapter(PropertyGroup):
bpy.data.objects.remove(instance)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
def generate_previews(self, cache=None):
@ -560,7 +592,7 @@ class AssetLibraryAdapter(PropertyGroup):
#TODO Support all multiple data_type
for asset_description in cache:
if asset_description['type'] == 'FILE':
if asset_description.get('type', self.data_type) == 'FILE':
self.generate_blend_preview(asset_description)
else:
self.generate_asset_preview(asset_description)
@ -580,17 +612,17 @@ class AssetLibraryAdapter(PropertyGroup):
def set_asset_preview(self, asset, asset_data):
'''Load an externalize image as preview for an asset'''
name = asset_data['name']
asset_path = asset_data['filepath']
catalog = asset_data['catalog']
asset_path = self.format_path(asset_data['filepath'])
image_path = asset_data.get('image')
image_template = asset_data.get('image')
if self.library.template_image:
image_path = self.get_image_path(name, asset_path, catalog)
elif image_path:
image_path = self.get_template_path(image_path, name, asset_path, catalog)
asset_path = self.get_asset_bundle_path(asset_data)
image_template = self.library.template_image
image_path = self.find_path(image_template, asset_data, filepath=asset_path)
if image_path and image_path.exists():
if image_path:
print(f'Set asset preview for {image_path} for {asset}')
with bpy.context.temp_override(id=asset):
bpy.ops.ed.lib_id_load_custom_preview(
filepath=str(image_path)
@ -598,50 +630,6 @@ class AssetLibraryAdapter(PropertyGroup):
if asset.preview:
return asset.preview
return
#Creating the preview for collection, object or material
src_asset = self.load_datablocks(asset_data['filepath'], names=asset_data['name'], link=False, type=self.data_types)
if not src_asset:
print(f'No asset named {asset_data["name"]} in {asset_data["filepath"]}')
return
if not self.data_type == 'COLLECTION':
print(f'Generate preview of type {self.data_type} not supported yet')
return
# asset.children.link(src_asset)
# bpy.ops.ed.lib_id_generate_preview({"id": asset})
# while bpy.app.is_job_running("RENDER_PREVIEW"):
# print(bpy.app.is_job_running("RENDER_PREVIEW"))
# time.sleep(0.2)
# getattr(bpy.data, self.data_types).remove(src_asset)
# return asset.preview
#src_asset.user_clear()
#return src_asset
#asset.children.unlink(src_asset)
#getattr(bpy.data, self.data_types).remove(src_asset)
# time.sleep(1)
# #Transfering pixels between previews
# w, h = src_asset.preview.image_size
# pixels = [0] * (w*h*4)
# src_asset.preview.image_pixels_float.foreach_get(pixels)
# asset.preview_ensure()
# asset.preview.image_size = src_asset.preview.image_size
# asset.preview.image_pixels_float.foreach_set(pixels)
#print('pixels transfered')
#bpy.app.timers.register(partial(getattr(bpy.data, self.data_types).remove, src_asset), first_interval=1)
def set_asset_catalog(self, asset, asset_data, catalog_data):
"""Find the catalog if already exist or create it"""
@ -679,9 +667,21 @@ class AssetLibraryAdapter(PropertyGroup):
continue
asset.asset_data.tags.new(tag, skip_if_exists=True)
def set_asset_description(self, asset, asset_data):
def set_asset_info(self, asset, asset_data):
"""Set asset description base on provided data"""
asset.asset_data.description = asset_data.get('description', '')
#print(asset_data.get('description', ''))
asset.asset_data.author = asset_data.get('author') or ''
asset.asset_data.description = asset_data.get('description') or ''
def get_asset_bundle_path(self, asset_data):
catalog_parts = asset_data['catalog'].split('/') + [asset_data['name']]
sub_path = catalog_parts[:self.library.blend_depth]
blend_name = sub_path[-1].replace(' ', '_').lower()
return Path(self.bundle_directory, *sub_path, blend_name).with_suffix('.blend')
def bundle(self, cache_diff=None):
"""Group all new assets in one or multiple blends for the asset browser"""
@ -719,8 +719,8 @@ class AssetLibraryAdapter(PropertyGroup):
raise Exception('Blender depth must be 1 at min')
#groups = [(cache_diff)]
else:
cache_diff.sort(key=self.group_key)
groups = groupby(cache_diff, key=self.group_key)
cache_diff.sort(key=self.get_asset_bundle_path)
groups = groupby(cache_diff, key=self.get_asset_bundle_path)
total_assets = len(cache_diff)
print(f'total_assets={total_assets}')
@ -734,9 +734,9 @@ class AssetLibraryAdapter(PropertyGroup):
i = 0
#assets_to_preview = []
for sub_path, asset_datas in groups:
blend_name = sub_path[-1].replace(' ', '_').lower()
blend_path = Path(self.bundle_directory, *sub_path, blend_name).with_suffix('.blend')
for blend_path, asset_datas in groups:
#blend_name = sub_path[-1].replace(' ', '_').lower()
#blend_path = Path(self.bundle_directory, *sub_path, blend_name).with_suffix('.blend')
if blend_path.exists():
print(f'Opening existing bundle blend: {blend_path}')
@ -786,15 +786,13 @@ class AssetLibraryAdapter(PropertyGroup):
self.set_asset_catalog(asset, asset_data, catalog_data)
self.set_asset_metadata(asset, asset_data)
self.set_asset_tags(asset, asset_data)
self.set_asset_description(asset, asset_data)
self.set_asset_info(asset, asset_data)
i += 1
#self.write_asset_preview_file()
print(f'Saving Blend to {blend_path}')
blend_path.parent.mkdir(exist_ok=True, parents=True)
@ -873,13 +871,14 @@ class AssetLibraryAdapter(PropertyGroup):
for k, v in annotations.items():
layout.prop(self, k, text=bpy.path.display_name(k))
'''
def format_path(self, template, **kargs):
params = dict(
bundle_dir=Path(self.bundle_directory),
#conform_dir=Path(self.library.conform.directory),
**kargs,
**self.to_dict(),
)
return Template(template).format(params).resolve()
return Template(template).format(params).resolve()
'''

View File

@ -17,6 +17,7 @@ import uuid
import os
import shutil
import json
import time
class ScanFolderLibrary(AssetLibraryAdapter):
@ -39,8 +40,21 @@ class ScanFolderLibrary(AssetLibraryAdapter):
#
def get_asset_path(self, name, catalog, directory=None):
directory = directory or self.source_directory
catalog = self.norm_file_name(catalog)
name = self.norm_file_name(name)
return Path(directory, self.get_asset_relative_path(name, catalog))
def get_image_path(self, name, catalog, filepath):
catalog = self.norm_file_name(catalog)
name = self.norm_file_name(name)
return self.format_path(self.template_image, dict(name=name, catalog=catalog, filepath=filepath))
def get_video_path(self, name, catalog, filepath):
catalog = self.norm_file_name(catalog)
name = self.norm_file_name(name)
return self.format_path(self.template_video, dict(name=name, catalog=catalog, filepath=filepath))
'''
def get_asset_description(self, asset, catalog, modified):
@ -70,11 +84,13 @@ class ScanFolderLibrary(AssetLibraryAdapter):
def get_asset_description(self, data, asset_path):
asset_path = self.prop_rel_path(asset_path, 'source_directory')
modified = data.get('modified', time.time_ns())
if self.data_type == 'FILE':
return dict(
filepath=asset_path,
modified=data['modified'],
author=data.get('author'),
modified=modified,
catalog=data['catalog'],
tags=[],
type=self.data_type,
@ -84,10 +100,11 @@ class ScanFolderLibrary(AssetLibraryAdapter):
return dict(
filepath=asset_path,
modified=data['modified'],
modified=modified,
library_id=self.library.id,
assets=[dict(
catalog=asset_data['catalog'],
author=data.get('author'),
metadata=asset_data.get('metadata', {}),
description=asset_data.get('description'),
tags=asset_data.get('tags', []),
@ -392,8 +409,10 @@ class ScanFolderLibrary(AssetLibraryAdapter):
catalogs = [v for k,v in sorted(field_data.items()) if k.isdigit()]
catalogs = [c.replace('_', ' ').title() for c in catalogs]
asset_name = field_data.get('asset_name', asset_path.stem)
asset_datas = {
"name": field_data['asset_name'],
"name": asset_name,
"catalog": '/'.join(catalogs),
"assets": [],
'modified': modified
@ -405,13 +424,8 @@ class ScanFolderLibrary(AssetLibraryAdapter):
continue
# Now check if there is a asset description file
asset_description_path = self.get_template_path(
self.template_description,
asset_datas['asset_name'],
asset_path,
asset_datas['catalog'])
if asset_description_path and asset_description_path.exists():
asset_description_path = self.find_path(self.template_description, asset_datas, filepath=asset_path)
if asset_description_path:
new_cache.append(self.read_file(asset_description_path))
continue
@ -421,11 +435,11 @@ class ScanFolderLibrary(AssetLibraryAdapter):
print(f'Found {len(assets)} {self.data_types} inside')
for asset in assets:
catalog_path = catalog_ids.get(asset.asset_data.catalog_id)
#catalog_path = catalog_ids.get(asset.asset_data.catalog_id)
if not catalog_path:
print(f'No catalog found for asset {asset.name}')
catalog_path = asset_path.relative_to(self.source_directory).as_posix()
#if not catalog_path:
# print(f'No catalog found for asset {asset.name}')
catalog_path = asset_datas['catalog']#asset_path.relative_to(self.source_directory).as_posix()
asset_datas['assets'] += [dict(
catalog=catalog_path,

View File

@ -79,9 +79,16 @@ class Template:
def find(self, data, **kargs):
pattern = self.format(data, **kargs)
pattern_str = str(pattern)
if '*' not in pattern_str and '?' not in pattern_str:
return pattern
paths = glob(pattern.as_posix())
if paths:
return Path(paths[0])
return pattern
def __repr__(self):
return f'Template({self.raw})'

View File

@ -638,6 +638,7 @@ class AssetLibraryPrefs(AddonPreferences):
#action : bpy.props.PointerProperty(type=AssetLibraryPath)
#asset : bpy.props.PointerProperty(type=AssetLibraryPath)
#adapters = {}
author: StringProperty(default=os.getlogin())
image_player: StringProperty(default='')
video_player: StringProperty(default='')