From e538c997a9e5ab3ae931ee10b53c4c5e809267fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cchristopheseux=E2=80=9D?= <“seuxchristophe@hotmail.fr”> Date: Tue, 3 Jan 2023 10:17:58 +0100 Subject: [PATCH] finish refacto, now testing and fixing bug --- __init__.py | 4 + action/operators.py | 38 +++-- adapters/adapter.py | 299 ++++++++++++++++++++-------------------- adapters/scan_folder.py | 42 ++++-- common/template.py | 7 + prefs.py | 1 + 6 files changed, 214 insertions(+), 177 deletions(-) diff --git a/__init__.py b/__init__.py index 75db1c9..80a4722 100644 --- a/__init__.py +++ b/__init__.py @@ -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 diff --git a/action/operators.py b/action/operators.py index 5d23e5b..76d9e39 100644 --- a/action/operators.py +++ b/action/operators.py @@ -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) diff --git a/adapters/adapter.py b/adapters/adapter.py index add2b01..f2665ae 100644 --- a/adapters/adapter.py +++ b/adapters/adapter.py @@ -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() \ No newline at end of file + return Template(template).format(params).resolve() + ''' \ No newline at end of file diff --git a/adapters/scan_folder.py b/adapters/scan_folder.py index 4857925..01541ce 100644 --- a/adapters/scan_folder.py +++ b/adapters/scan_folder.py @@ -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, diff --git a/common/template.py b/common/template.py index 91e1421..36b1997 100644 --- a/common/template.py +++ b/common/template.py @@ -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})' \ No newline at end of file diff --git a/prefs.py b/prefs.py index bad46f9..423b0da 100644 --- a/prefs.py +++ b/prefs.py @@ -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='')