diff --git a/action/__init__.py b/action/__init__.py index 6aabd72..b55d078 100644 --- a/action/__init__.py +++ b/action/__init__.py @@ -22,6 +22,8 @@ if 'bpy' in locals(): importlib.reload(rename_pose) #importlib.reload(render_preview) +import bpy + def register(): operators.register() keymaps.register() diff --git a/action/operators.py b/action/operators.py index 36121f3..5d23e5b 100644 --- a/action/operators.py +++ b/action/operators.py @@ -72,11 +72,11 @@ from asset_library.action.functions import ( from asset_library.common.functions import ( #get_actionlib_dir, - get_asset_source, + #get_asset_source, #get_catalog_path, #read_catalog, #set_actionlib_dir, - resync_lib, + #resync_lib, get_active_library, get_active_catalog, asset_warning_callback diff --git a/action/preview.blend b/action/preview.blend new file mode 100644 index 0000000..a92321b Binary files /dev/null and b/action/preview.blend differ diff --git a/adapters/adapter.py b/adapters/adapter.py index aa01c08..add2b01 100644 --- a/adapters/adapter.py +++ b/adapters/adapter.py @@ -3,7 +3,7 @@ from asset_library.common.bl_utils import get_addon_prefs, load_datablocks from asset_library.common.file_utils import read_file, write_file from asset_library.common.template import Template -from asset_library.constants import (PREVIEW_ASSETS_SCRIPT, MODULE_DIR) +from asset_library.constants import (MODULE_DIR, RESOURCES_DIR) from asset_library import (action, collection, file) @@ -36,48 +36,26 @@ class AssetLibraryAdapter(PropertyGroup): for lib in prefs.libraries: if lib.adapter == self: return lib - if lib.conform.adapter == self: - return lib - - #@property - #def library_path(self): - # return self.library.library_path @property - def is_conform(self): - prefs = self.addon_prefs - for lib in prefs.libraries: - if lib.adapter == self: - return False - if lib.conform.adapter == self: - return True - - @property - def target_directory(self): - if self.is_conform: - return self.library.conform.directory - - return self.library.bundle_directory + def bundle_directory(self): + return self.library.library_path - @property - def blend_depth(self): - if self.is_conform: - return self.library.conform.blend_depth - - return self.library.blend_depth + # @property + # def blend_depth(self): + # return self.library.blend_depth - @property - def template_image(self): - return Template(self.library.conform.template_image) + # @property + # def template_image(self): + # return Template(self.library.template_image) - @property - def template_video(self): - return Template(self.library.conform.template_video) + # @property + # def template_video(self): + # return Template(self.library.template_video) - @property - def template_description(self): - return Template(self.library.conform.template_description) - + # @property + # def template_description(self): + # return Template(self.library.template_description) @property def data_type(self): @@ -87,21 +65,18 @@ class AssetLibraryAdapter(PropertyGroup): def data_types(self): return self.library.data_types - #@property - #def externalize_data(self): - # return self.library.externalize_data - - #@property - #def catalog_path(self): - # return self.library.catalog_path - def get_catalog_path(self, directory=None): - directory = directory or self.target_directory + directory = directory or self.bundle_directory return Path(directory, 'blender_assets.cats.txt') @property def cache_file(self): - return Path(self.target_directory) / f"blender_assets.{self.library.id}.json" + return Path(self.bundle_directory) / f"blender_assets.{self.library.id}.json" + #return get_asset_datas_file(self.library_path) + + @property + def tmp_cache_file(self): + return Path(bpy.app.tempdir) / f"blender_assets.{self.library.id}.json" #return get_asset_datas_file(self.library_path) @property @@ -149,22 +124,26 @@ class AssetLibraryAdapter(PropertyGroup): src = Path(source) dst = Path(destination) - if not source.exists(): - print(f'Cannot copy file {source}: file not exist') + if not src.exists(): + print(f'Cannot copy file {src}: file not exist') return dst.parent.mkdir(exist_ok=True, parents=True) if src == dst: - print(f'Cannot copy file {source}: source and destination are the same') + print(f'Cannot copy file {src}: source and destination are the same') return - print(f'Copy file from {source} to {destination}') - shutil.copy2(str(source), str(destination)) + print(f'Copy file from {src} to {dst}') + shutil.copy2(str(src), str(dst)) - def load_datablocks(self, src, names=None, type='objects', link=True, expr=None): + def load_datablocks(self, src, names=None, type='objects', link=True, expr=None, assets_only=False): """Link or append a datablock from a blendfile""" - return load_datablocks(src, names=names, type=type, link=link, expr=expr) + + if type.isupper(): + type = f'{type.lower()}s' + + return load_datablocks(src, names=names, type=type, link=link, expr=expr, assets_only=assets_only) def get_asset_relative_path(self, name, catalog): '''Get a relative path for the asset''' @@ -220,7 +199,7 @@ class AssetLibraryAdapter(PropertyGroup): template = Path(asset_path, template).as_posix() params = { - 'name': name, + 'asset_name': name, 'asset_path': Path(asset_path), 'catalog': catalog, 'catalog_name': catalog.replace('/', '_'), @@ -230,13 +209,13 @@ class AssetLibraryAdapter(PropertyGroup): 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.conform.template_description, name, asset_path, catalog) + return self.get_template_path(self.library.template_description, name, asset_path, catalog) def get_image_path(self, name, asset_path, catalog) -> Path: - return self.get_template_path(self.library.conform.template_image, name, asset_path, catalog) + return self.get_template_path(self.library.template_image, name, asset_path, catalog) def get_video_path(self, name, asset_path, catalog) -> Path: - return self.get_template_path(self.library.conform.template_video, name, asset_path, catalog) + return self.get_template_path(self.library.template_video, name, asset_path, catalog) ''' def get_path(self, type, name, asset_path, template=None) -> Path: @@ -355,12 +334,13 @@ class AssetLibraryAdapter(PropertyGroup): print(f'Catalog writen at: {catalog_path}') catalog_path.write_text('\n'.join(lines), encoding="utf-8") - def read_cache(self): - print(f'Read cache from {self.cache_file}') - return self.read_file(self.cache_file) + def read_cache(self, cache_path=None): + cache_path = cache_path or self.cache_file + print(f'Read cache from {cache_path}') + return self.read_file(cache_path) - def write_cache(self, asset_descriptions): - cache_path = self.cache_file + def write_cache(self, asset_descriptions, cache_path=None): + cache_path = cache_path or self.cache_file print(f'cache file writen to {cache_path}') return write_file(cache_path, list(asset_descriptions)) @@ -408,7 +388,7 @@ class AssetLibraryAdapter(PropertyGroup): catalog_parts = asset_data['catalog'].split('/') + [asset_data['name']] - return catalog_parts[:self.blend_depth] + return catalog_parts[:self.library.blend_depth] #def transfert_preview(self, ) @@ -463,10 +443,44 @@ class AssetLibraryAdapter(PropertyGroup): ) ) ''' - def generate_preview(self, asset_description): + + def generate_blend_preview(self, asset_description): + asset_name = asset_description['name'] + catalog = asset_description['catalog'] + + asset_path = self.format_path(asset_description['filepath']) + dst_image_path = self.get_image_path(asset_name, asset_path, catalog) + + if dst_image_path.exists(): + return + + # Check if a source image exists and if so copying it in the new directory + src_image_path = asset_description.get('image') + if src_image_path: + src_image_path = self.get_template_path(src_image_path, asset_name, asset_path, catalog) + if src_image_path and src_image_path.exists(): + self.copy_file(src_image_path, dst_image_path) + return + + print(f'Thumbnailing {asset_path} to {dst_image_path}') + blender_thumbnailer = Path(bpy.app.binary_path).parent / 'blender-thumbnailer' + + dst_image_path.parent.mkdir(exist_ok=True, parents=True) + + subprocess.call([blender_thumbnailer, str(asset_path), str(dst_image_path)]) + + success = dst_image_path.exists() + + if not success: + empty_preview = RESOURCES_DIR / 'empty_preview.png' + self.copy_file(str(empty_preview), str(dst_image_path)) + + return success + + def generate_asset_preview(self, asset_description): """Only generate preview when conforming a library""" - print('\ngenerate_preview', asset_description) + print('\ngenerate_preview', asset_description['filepath']) scn = bpy.context.scene #Creating the preview for collection, object or material @@ -474,19 +488,28 @@ class AssetLibraryAdapter(PropertyGroup): vl = bpy.context.view_layer data_type = self.data_type #asset_description['data_type'] - asset_path = asset_description['filepath'] + asset_path = self.format_path(asset_description['filepath']) + asset_data_names = {} for asset_data in asset_description['assets']: name = asset_data['name'] catalog = asset_data['catalog'] - image_path = self.get_image_path(name, asset_path, catalog) - if image_path.exists(): + dst_image_path = self.get_image_path(name, asset_path, catalog) + if dst_image_path.exists(): 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(): + self.copy_file(src_image_path, 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=image_path) + asset_data_names[name] = dict(asset_data, image_path=dst_image_path) if not asset_data_names: # No preview to generate @@ -512,8 +535,6 @@ class AssetLibraryAdapter(PropertyGroup): scn.render.filepath = str(image_path) - - print(f'Render asset {asset.name} to {image_path}') bpy.ops.render.render(write_still=True) @@ -522,25 +543,27 @@ class AssetLibraryAdapter(PropertyGroup): bpy.data.objects.remove(instance) - #bpy.ops.object.delete(use_global=False) - - #scn.collection.children.unlink(asset) - bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) - def generate_previews(self, asset_descriptions=None): + def generate_previews(self, cache=None): print('Generate previews') - asset_descriptions = asset_descriptions or self.fetch() - + if cache in (None, ''): + cache = self.fetch() + elif isinstance(cache, (Path, str)): + cache = self.read_cache(cache) #cache_diff.sort(key=lambda x :x['filepath']) #blend_groups = groupby(cache_diff, key=lambda x :x['filepath']) #TODO Support all multiple data_type - for asset_description in asset_descriptions: - self.generate_preview(asset_description) + for asset_description in cache: + + if asset_description['type'] == 'FILE': + self.generate_blend_preview(asset_description) + else: + self.generate_asset_preview(asset_description) # filepath = asset_description['filepath'] @@ -561,7 +584,11 @@ class AssetLibraryAdapter(PropertyGroup): asset_path = asset_data['filepath'] catalog = asset_data['catalog'] - image_path = self.get_image_path(name, asset_path, catalog) + image_path = 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) if image_path and image_path.exists(): with bpy.context.temp_override(id=asset): @@ -663,9 +690,6 @@ class AssetLibraryAdapter(PropertyGroup): print(f'{self.data_type} is not supported yet') return - target_dir = self.target_directory - - catalog_data = self.read_catalog() #TODO remove unused catalog write_cache = False @@ -673,12 +697,15 @@ class AssetLibraryAdapter(PropertyGroup): # Get list of all modifications asset_descriptions = self.fetch() + cache, cache_diff = self.diff(asset_descriptions) # Only write complete cache at the end write_cache = True - self.generate_previews(asset_descriptions) + #self.generate_previews(asset_descriptions) + self.write_cache(asset_descriptions, self.tmp_cache_file) + bpy.ops.assetlib.generate_previews(name=self.library.name, cache=str(self.tmp_cache_file)) #print() #print(cache) @@ -688,7 +715,7 @@ class AssetLibraryAdapter(PropertyGroup): cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8')) - if self.blend_depth == 0: + if self.library.blend_depth == 0: raise Exception('Blender depth must be 1 at min') #groups = [(cache_diff)] else: @@ -702,11 +729,14 @@ class AssetLibraryAdapter(PropertyGroup): print('No assets found') return + #data_types = self.data_types + #if self.data_types == 'FILE' + i = 0 #assets_to_preview = [] for sub_path, asset_datas in groups: blend_name = sub_path[-1].replace(' ', '_').lower() - blend_path = Path(target_dir, *sub_path, blend_name).with_suffix('.blend') + 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}') @@ -768,7 +798,6 @@ class AssetLibraryAdapter(PropertyGroup): print(f'Saving Blend to {blend_path}') blend_path.parent.mkdir(exist_ok=True, parents=True) - bpy.ops.wm.save_as_mainfile(filepath=str(blend_path), compress=True) if write_cache: @@ -847,8 +876,8 @@ class AssetLibraryAdapter(PropertyGroup): def format_path(self, template, **kargs): params = dict( - bundle_dir=Path(self.library.bundle_directory), - conform_dir=Path(self.library.conform.directory), + bundle_dir=Path(self.bundle_directory), + #conform_dir=Path(self.library.conform.directory), **kargs, **self.to_dict(), ) diff --git a/adapters/copy_folder.py b/adapters/copy_folder.py index 9871a39..115820d 100644 --- a/adapters/copy_folder.py +++ b/adapters/copy_folder.py @@ -8,6 +8,7 @@ from asset_library.adapters.adapter import AssetLibraryAdapter from asset_library.common.file_utils import copy_dir from bpy.props import StringProperty from os.path import expandvars +import bpy class CopyFolderLibrary(AssetLibraryAdapter): @@ -21,7 +22,7 @@ class CopyFolderLibrary(AssetLibraryAdapter): def bundle(self, cache_diff=None): src = expandvars(self.source_directory) - dst = expandvars(self.target_directory) + dst = expandvars(self.bundle_directory) includes = [inc.strip() for inc in self.includes.split(',')] excludes = [ex.strip() for ex in self.excludes.split(',')] @@ -31,4 +32,15 @@ class CopyFolderLibrary(AssetLibraryAdapter): src, dst, only_recent=True, excludes=excludes, includes=includes ) - \ No newline at end of file + + def filter_prop(self, prop): + if prop in ('template_description', 'template_video', 'template_image', 'blend_depth'): + return False + + return True + + # def draw_prop(self, layout, prop): + # if prop in ('template_description', 'template_video', 'template_image', 'blend_depth'): + # return + + # super().draw_prop(layout) \ No newline at end of file diff --git a/adapters/kitsu.py b/adapters/kitsu.py index 346af08..a3f2f68 100644 --- a/adapters/kitsu.py +++ b/adapters/kitsu.py @@ -28,6 +28,7 @@ class KitsuLibrary(AssetLibraryAdapter): template_name : StringProperty() template_file : StringProperty() source_directory : StringProperty(subtype='DIR_PATH') + #blend_depth: IntProperty(default=1) url: StringProperty() login: StringProperty() @@ -83,8 +84,8 @@ class KitsuLibrary(AssetLibraryAdapter): description=data['description'], tags=[], type=self.data_type, - image=self.template_image.raw, - video=self.template_video.raw, + image=self.library.template_image, + video=self.library.template_video, name=data['name']) ] ) @@ -95,16 +96,6 @@ class KitsuLibrary(AssetLibraryAdapter): # """Group all asset in one or multiple blends for the asset browser""" # return super().bundle(cache_diff=cache_diff) - - def get_preview(self, asset_data): - - name = asset_data['name'] - preview = (f / template_image.format(name=name)).resolve() - if not preview.exists(): - preview_blend_file(f, preview) - - return preview - def fetch(self): """Gather in a list all assets found in the folder""" @@ -122,11 +113,11 @@ class KitsuLibrary(AssetLibraryAdapter): entity_types_ids = {e['id']: e['name'] for e in entity_types} asset_descriptions = [] - for asset_data in gazu.asset.all_assets_for_project(project)[:10]: + for asset_data in gazu.asset.all_assets_for_project(project): asset_data['entity_type_name'] = entity_types_ids[asset_data.pop('entity_type_id')] asset_name = asset_data['name'] - asset_field_data = dict(name=asset_name, type=asset_data['entity_type_name'], source_directory=self.source_directory) + asset_field_data = dict(asset_name=asset_name, type=asset_data['entity_type_name'], source_directory=self.source_directory) try: asset_field_data.update(template_name.parse(asset_name)) @@ -139,7 +130,8 @@ class KitsuLibrary(AssetLibraryAdapter): continue #print(asset_path) - + + # TODO group when multiple asset are store in the same blend asset_descriptions.append(self.get_asset_description(asset_data, asset_path)) #asset = load_datablocks(asset_path, data_type='collections', names=asset_data['name'], link=True) diff --git a/adapters/scan_folder.py b/adapters/scan_folder.py index 3e2351d..4857925 100644 --- a/adapters/scan_folder.py +++ b/adapters/scan_folder.py @@ -23,8 +23,11 @@ class ScanFolderLibrary(AssetLibraryAdapter): name = "Scan Folder" source_directory : StringProperty(subtype='DIR_PATH') - template : StringProperty() - blend_depth : IntProperty() + template_file : StringProperty() + template_image : StringProperty() + template_video : StringProperty() + template_description : StringProperty() + #blend_depth : IntProperty() #externalize_preview : BoolProperty(default=True) #def draw_header(self, layout): @@ -38,6 +41,7 @@ class ScanFolderLibrary(AssetLibraryAdapter): directory = directory or self.source_directory return Path(directory, self.get_asset_relative_path(name, catalog)) + ''' def get_asset_description(self, asset, catalog, modified): asset_path = self.get_asset_relative_path(name=asset.name, catalog=catalog) @@ -61,6 +65,38 @@ class ScanFolderLibrary(AssetLibraryAdapter): ) return asset_description + ''' + + def get_asset_description(self, data, asset_path): + + asset_path = self.prop_rel_path(asset_path, 'source_directory') + + if self.data_type == 'FILE': + return dict( + filepath=asset_path, + modified=data['modified'], + catalog=data['catalog'], + tags=[], + type=self.data_type, + image=self.template_image, + name=data['name'] + ) + + return dict( + filepath=asset_path, + modified=data['modified'], + library_id=self.library.id, + assets=[dict( + catalog=asset_data['catalog'], + metadata=asset_data.get('metadata', {}), + description=asset_data.get('description'), + tags=asset_data.get('tags', []), + type=self.data_type, + image=self.template_image, + video=self.template_video, + name=asset_data['name']) for asset_data in data['assets'] + ] + ) def _find_blend_files(self): '''Get a sorted list of all blender files found matching the template''' @@ -76,13 +112,14 @@ class ScanFolderLibrary(AssetLibraryAdapter): return blend_files + ''' def _group_key(self, asset_data): """Group assets inside one blend""" catalog_parts = asset_data['catalog'].split('/') + [asset_data['name']] return catalog_parts[:self.blend_depth] - + def bundle(self, cache_diff=None): """Group all asset in one or multiple blends for the asset browser""" @@ -238,17 +275,9 @@ class ScanFolderLibrary(AssetLibraryAdapter): self.write_catalog(catalog_data) bpy.ops.wm.quit_blender() + ''' - def get_preview(self, asset_data): - - name = asset_data['name'] - preview = (f / template_image.format(name=name)).resolve() - if not preview.exists(): - preview_blend_file(f, preview) - - return preview - - + ''' def conform(self, directory, templates): """Split each assets per blend and externalize preview""" @@ -317,20 +346,20 @@ class ScanFolderLibrary(AssetLibraryAdapter): self.copy_file(src_video_path, dst_video_path) self.write_catalog(catalog_data, filepath=directory) - + ''' def fetch(self): """Gather in a list all assets found in the folder""" print(f'Fetch Assets for {self.library.name}') - source_directory = Path(os.path.expandvars(self.source_directory)) - template = Template(self.template) - catalog_data = self.read_catalog(filepath=source_directory) - catalog_ids = {v['id']: {'path': k, 'name': v['name']} for k,v in catalog_data.items()} + source_directory = Path(self.source_directory) + template_file = Template(self.template_file) + catalog_data = self.read_catalog(directory=source_directory) + catalog_ids = {v['id']: k for k, v in catalog_data.items()} cache = self.read_cache() or [] - print(f'Search for blend using glob template: {template.glob_pattern}') + print(f'Search for blend using glob template: {template_file.glob_pattern}') print(f'Scanning Folder {source_directory}...') #blend_files = list(source_directory.glob(template.glob_pattern)) @@ -344,102 +373,71 @@ class ScanFolderLibrary(AssetLibraryAdapter): #blend_paths = [] new_cache = [] - for blend_file in template.glob(source_directory):#sorted(blend_files): + for asset_path in template_file.glob(source_directory):#sorted(blend_files): - source_rel_path = self.prop_rel_path(blend_file, 'source_directory') - modified = blend_file.stat().st_mtime_ns + source_rel_path = self.prop_rel_path(asset_path, 'source_directory') + modified = asset_path.stat().st_mtime_ns + # Check if the asset description as already been cached asset_description = next((a for a in cache if a['filepath'] == source_rel_path), None) if asset_description and asset_description['modified'] >= modified: - print(blend_file, 'is skipped because not modified') + print(asset_path, 'is skipped because not modified') new_cache.append(asset_description) continue - - rel_path = blend_file.relative_to(source_directory).as_posix() - #field_values = re.findall(re_pattern, rel_path)[0] - #field_data = {k:v for k,v in zip(field_names, field_values)} - field_data = template.parse(rel_path) - - if not field_data: - raise Exception() - - #asset_data = (blend_file / prefs.template_description.format(name=name)).resolve() + + rel_path = asset_path.relative_to(source_directory).as_posix() + field_data = template_file.parse(rel_path) catalogs = [v for k,v in sorted(field_data.items()) if k.isdigit()] catalogs = [c.replace('_', ' ').title() for c in catalogs] - + + asset_datas = { + "name": field_data['asset_name'], + "catalog": '/'.join(catalogs), + "assets": [], + 'modified': modified + } + if self.data_type == 'FILE': - name = field_data.get('name', blend_file.stem) - image = self.get_path('image', name=name, asset_path=blend_file) - - asset_description = dict( - filepath=source_rel_path, - modified=modified, - catalog='/'.join(catalogs), - tags=[], - type=self.data_type, - image=self.prop_rel_path(image, 'source_directory'), - name=name - ) + asset_description = self.get_asset_description(asset_datas, asset_path) new_cache.append(asset_description) - continue - #First Check if there is a asset_data .json - asset_description = self.read_asset_description_file(blend_file) + # 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 not asset_description: - # Scan the blend file for assets inside and write a custom asset description for info found + if asset_description_path and asset_description_path.exists(): + new_cache.append(self.read_file(asset_description_path)) + continue - print(f'Scanning blendfile {blend_file}...') - with bpy.data.libraries.load(str(blend_file), link=True, assets_only=True) as (data_from, data_to): - asset_names = getattr(data_from, self.data_types) - print(f'Found {len(asset_names)} {self.data_types} inside') + # Scan the blend file for assets inside and write a custom asset description for info found + print(f'Scanning blendfile {asset_path}...') + assets = self.load_datablocks(asset_path, type=self.data_types, link=True, assets_only=True) + print(f'Found {len(assets)} {self.data_types} inside') - setattr(data_to, self.data_types, asset_names) - assets = getattr(data_to, self.data_types) + for asset in assets: + catalog_path = catalog_ids.get(asset.asset_data.catalog_id) - asset_description = dict( - filepath=source_rel_path, - modified=modified, - assets=[] - ) + if not catalog_path: + print(f'No catalog found for asset {asset.name}') + catalog_path = asset_path.relative_to(self.source_directory).as_posix() + + asset_datas['assets'] += [dict( + catalog=catalog_path, + tags=asset.asset_data.tags.keys(), + metadata=dict(asset.asset_data), + type=self.data_type, + name=asset.name + )] + + getattr(bpy.data, self.data_types).remove(asset) - for asset in assets: - asset_catalog_data = catalog_ids.get(asset.asset_data.catalog_id) - - if not asset_catalog_data: - print(f'No catalog found for asset {asset.name}') - asset_catalog_data = {"path": blend_file.relative_to(self.source_directory).as_posix()} - - catalog_path = asset_catalog_data['path'] - - image_path = self.get_path('image', asset.name, catalog_path) - image = self.prop_rel_path(image_path, 'source_directory') - - # Write image only if no image was found - if not image_path.exists(): - image_path = self.get_cache_image_path(asset.name, catalog_path) - image = self.prop_rel_path(image_path, 'library_path') - self.write_preview(asset.preview, image_path) - - video_path = self.get_path('video', asset.name, catalog_path) - video = self.prop_rel_path(video_path, 'source_directory') - - asset_data = dict( - filepath=self.prop_rel_path(blend_file, 'source_directory'), - modified=modified, - catalog=catalog_path, - tags=asset.asset_data.tags.keys(), - type=self.data_type, - image=image, - video=video, - name=asset.name - ) - asset_description['assets'].append(asset_data) - - getattr(bpy.data, self.data_types).remove(asset) + asset_description = self.get_asset_description(asset_datas, asset_path) new_cache.append(asset_description) diff --git a/collection/__init__.py b/collection/__init__.py index b90a7a4..9154052 100644 --- a/collection/__init__.py +++ b/collection/__init__.py @@ -16,6 +16,7 @@ if 'bpy' in locals(): #importlib.reload(build_collection_blends) #importlib.reload(create_collection_library) +import bpy def register(): operators.register() diff --git a/collection/operators.py b/collection/operators.py index 20a33a7..bf5767b 100644 --- a/collection/operators.py +++ b/collection/operators.py @@ -42,43 +42,29 @@ class ASSETLIB_OT_load_asset(Operator): 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'} + active_lib = lib.adapter.get_active_asset_library() asset_path = asset.asset_data['filepath'] - fp = lib.adapter.format_path(asset_path) + asset_path = active_lib.adapter.format_path(asset_path) 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}') + if not Path(asset_path).exists(): + self.report({'ERROR'}, f'Not exists: {asset_path}') return {'CANCELLED'} - res = load_col(fp, name, link=True, override=True, rig_pattern='*_rig') + print('Load collection', asset_path, name) + res = load_col(asset_path, name, link=True, override=True, rig_pattern='*_rig') if res: if res.type == 'ARMATURE': self.report({'INFO'}, f'Override rig {res.name}') diff --git a/collection/preview.blend b/collection/preview.blend index 2ca4d64..a92321b 100644 Binary files a/collection/preview.blend and b/collection/preview.blend differ diff --git a/common/bl_utils.py b/common/bl_utils.py index 5fa190b..4008441 100644 --- a/common/bl_utils.py +++ b/common/bl_utils.py @@ -13,6 +13,7 @@ from bpy_extras import asset_utils from asset_library.constants import RESOURCES_DIR #from asset_library.common.file_utils import no from os.path import abspath +import subprocess class attr_set(): @@ -328,13 +329,10 @@ def split_path(path) : -def load_datablocks(src, names=None, type='objects', link=True, expr=None) -> list: +def load_datablocks(src, names=None, type='objects', link=True, expr=None, assets_only=False) -> list: return_list = not isinstance(names, str) names = names or [] - if type.isupper(): - type = f'{type.lower()}s' - if not isinstance(names, (list, tuple)): names = [names] @@ -342,7 +340,7 @@ def load_datablocks(src, names=None, type='objects', link=True, expr=None) -> li pattern = expr expr = lambda x : fnmatch(x, pattern) - with bpy.data.libraries.load(str(src), link=link) as (data_from, data_to): + with bpy.data.libraries.load(str(src), link=link,assets_only=assets_only) as (data_from, data_to): datablocks = getattr(data_from, type) if expr: names += [i for i in datablocks if expr(i)] @@ -384,14 +382,8 @@ def load_col(filepath, name, link=True, override=True, rig_pattern=None, context # return data_to.collections[0] context = context or bpy.context - collections = load_datablocks(filepath, name, link=link, type='collections') - if not collections: - print(f'No collection "{name}" found in: {filepath}') - return + col = load_datablocks(filepath, name, link=link, type='collections') - col = collections[0] - print('collection:', col.name) - ## create instance object inst = bpy.data.objects.new(col.name, None) inst.instance_collection = col diff --git a/common/functions.py b/common/functions.py index 7012edf..6b9cc1b 100644 --- a/common/functions.py +++ b/common/functions.py @@ -180,7 +180,7 @@ def get_asset_source(replace_local=False): source_path = re.sub(actionlib_dir_local, actionlib_dir, source_path) return source_path -"""" +""" ''' def get_catalog_path(filepath=None): filepath = filepath or bpy.data.filepath diff --git a/common/preview.blend b/common/preview.blend new file mode 100644 index 0000000..a92321b Binary files /dev/null and b/common/preview.blend differ diff --git a/file/__init__.py b/file/__init__.py index 38fcb64..1eed34c 100644 --- a/file/__init__.py +++ b/file/__init__.py @@ -9,6 +9,8 @@ if 'bpy' in locals(): importlib.reload(gui) importlib.reload(keymaps) +import bpy + def register(): operators.register() keymaps.register() diff --git a/file/gui.py b/file/gui.py index 7618ca7..4a664eb 100644 --- a/file/gui.py +++ b/file/gui.py @@ -14,13 +14,21 @@ from bpy.types import ( from bpy_extras import asset_utils from asset_library.common.bl_utils import get_object_libraries, get_addon_prefs +from asset_library.common.functions import get_active_library -def draw_context_menu(self, context): - layout = self.layout +def draw_context_menu(layout): #asset = context.active_file + layout.operator_context = "INVOKE_DEFAULT" + lib = get_active_library() + filepath = lib.adapter.get_active_asset_path() layout.operator("assetlib.open_blend_file", text="Open Blend File")#.filepath = asset.asset_data['filepath'] + op = layout.operator("wm.link", text="Link") + op.filepath = str(filepath) + + op = layout.operator("wm.append", text="Append") + op.filepath = str(filepath) def draw_header(layout): diff --git a/file/operators.py b/file/operators.py index 8b0f56f..582b4d1 100644 --- a/file/operators.py +++ b/file/operators.py @@ -36,9 +36,9 @@ class ASSETLIB_OT_open_blend_file(Operator): def execute(self, context: Context) -> Set[str]: lib = get_active_library() - print(lib, lib.data_type) - filepath = context.active_file.asset_data['filepath'] + filepath = lib.get_active_asset_path() + open_blender_file(filepath) return {'FINISHED'} diff --git a/file/preview.blend b/file/preview.blend new file mode 100644 index 0000000..a92321b Binary files /dev/null and b/file/preview.blend differ diff --git a/operators.py b/operators.py index 62cf8b9..52bca99 100644 --- a/operators.py +++ b/operators.py @@ -302,7 +302,7 @@ class ASSETLIB_OT_bundle_library(Operator): blocking : BoolProperty(default=False) mode : EnumProperty(items=[(i.replace(' ', '_').upper(), i, '') for i in ('None', 'All', 'Auto Bundle')], default='NONE') directory : StringProperty(subtype='DIR_PATH') - conform : BoolProperty(default=False) + #conform : BoolProperty(default=False) #def refresh(self): # for area in suitable_areas(bpy.context.screen): # bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]}) @@ -327,10 +327,6 @@ class ASSETLIB_OT_bundle_library(Operator): print(f'Bundle Libraries: {[l.name for l in libs]}') - adapter = "lib.adapter" - if self.conform: - adapter = "lib.conform.adapter" - script_code = dedent(f""" import bpy prefs = bpy.context.preferences.addons["asset_library"].preferences @@ -338,7 +334,7 @@ class ASSETLIB_OT_bundle_library(Operator): for lib_data in {lib_datas}: lib = prefs.env_libraries.add() lib.set_dict(lib_data) - {adapter}.bundle(cache_diff='{self.diff}') + lib.adapter.bundle(cache_diff='{self.diff}') bpy.ops.wm.quit_blender() """) @@ -400,7 +396,7 @@ class ASSETLIB_OT_diff(Operator): return {'FINISHED'} - +''' class ASSETLIB_OT_conform_library(Operator): bl_idname = "assetlib.conform_library" bl_options = {"REGISTER", "UNDO"} @@ -445,7 +441,7 @@ class ASSETLIB_OT_conform_library(Operator): def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} - +''' class ASSETLIB_OT_generate_previews(Operator): bl_idname = "assetlib.generate_previews" @@ -453,7 +449,7 @@ class ASSETLIB_OT_generate_previews(Operator): bl_label = "Generate Previews" bl_description = "Generate and write the image for assets" - diff : StringProperty() + cache : StringProperty() preview_blend : StringProperty() name : StringProperty() blocking : BoolProperty(default=True) @@ -476,6 +472,9 @@ class ASSETLIB_OT_generate_previews(Operator): # subprocess.call(cmd) preview_blend = self.preview_blend or lib.adapter.preview_blend + if not preview_blend or not Path(preview_blend).exists(): + preview_blend = MODULE_DIR / 'common' / 'preview.blend' + script_path = Path(bpy.app.tempdir) / 'generate_previews.py' script_code = dedent(f""" import bpy @@ -484,7 +483,7 @@ class ASSETLIB_OT_generate_previews(Operator): lib.set_dict({lib.to_dict()}) bpy.ops.wm.open_mainfile(filepath='{preview_blend}', load_ui=True) - lib.conform.adapter.generate_previews() + lib.adapter.generate_previews(cache='{self.cache}') """) script_path.write_text(script_code) @@ -609,7 +608,7 @@ classes = ( ASSETLIB_OT_bundle_library, ASSETLIB_OT_clear_asset, ASSETLIB_OT_edit_data, - ASSETLIB_OT_conform_library, + #ASSETLIB_OT_conform_library, ASSETLIB_OT_reload_addon ) diff --git a/prefs.py b/prefs.py index a7b2e47..bad46f9 100644 --- a/prefs.py +++ b/prefs.py @@ -83,7 +83,7 @@ class AssetLibraryAdapters(PropertyGroup): def __iter__(self): return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) - +''' class ConformAssetLibrary(PropertyGroup): adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters) adapter_name : EnumProperty(items=get_adapter_items) @@ -117,16 +117,19 @@ class ConformAssetLibrary(PropertyGroup): del data['adapters'] return data - +''' class AssetLibrary(PropertyGroup): name : StringProperty(name='Name', default='Action Library', update=update_library_path) id : StringProperty() - auto_bundle : BoolProperty(name='Auto Bundle', default=True) + auto_bundle : BoolProperty(name='Auto Bundle', default=False) expand : BoolProperty(name='Expand', default=False) use : BoolProperty(name='Use', default=True, update=update_library_path) data_type : EnumProperty(name='Type', items=DATA_TYPE_ITEMS, default='COLLECTION') + template_image : StringProperty(default='', description='../{name}_image.png') + template_video : StringProperty(default='', description='../{name}_video.mov') + template_description : StringProperty(default='', description='../{name}_asset_description.json') bundle_directory : StringProperty( name="Bundle Directory", @@ -153,7 +156,7 @@ class AssetLibrary(PropertyGroup): template: StringProperty() expand_extra : BoolProperty(name='Expand', default=False) - blend_depth : IntProperty(name='Blend Depth', default=0) + blend_depth : IntProperty(name='Blend Depth', default=1) # source_directory : StringProperty( # name="Path", @@ -166,8 +169,7 @@ class AssetLibrary(PropertyGroup): #adapter : EnumProperty(items=adapter_ITEMS) adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters) adapter_name : EnumProperty(items=get_adapter_items) - - conform: bpy.props.PointerProperty(type=ConformAssetLibrary) + parent : StringProperty() # data_file_path : StringProperty( # name="Path", @@ -175,8 +177,6 @@ class AssetLibrary(PropertyGroup): # default='', # ) - #expand_conform : BoolProperty(name='Expand Conform', default=False) - #def __init__(self): # self.adapters.parent = self @@ -185,9 +185,17 @@ class AssetLibrary(PropertyGroup): prefs = get_addon_prefs() return [l for l in prefs.libraries if l != self and (l.library_path == self.library_path)] + @property + def child_libraries(self): + prefs = get_addon_prefs() + return [l for l in prefs.libraries if l != self and (l.parent == self.name)] + @property def data_types(self): - return f'{self.data_type.lower()}s' + data_type = self.data_type + if data_type == 'FILE': + data_type = 'COLLECTION' + return f'{data_type.lower()}s' @property def adapter(self): @@ -217,7 +225,7 @@ class AssetLibrary(PropertyGroup): # library_name = norm_str(library_name) if self.use_custom_bundle_directory: - return Path(self.custom_bundle_directory, library_name).resolve() + return Path(self.custom_bundle_directory).resolve() else: library_name = norm_str(library_name) return Path(prefs.bundle_directory, library_name).resolve() @@ -229,29 +237,6 @@ class AssetLibrary(PropertyGroup): return self.name - @property - def template_image(self): - prefs = get_addon_prefs() - return prefs.template_image - - @property - def template_video(self): - prefs = get_addon_prefs() - return prefs.template_video - - @property - def template_description(self): - prefs = get_addon_prefs() - return prefs.template_description - - #@property - #def catalog_path(self): - # return get_catalog_path(self.library_path) - - @property - def options(self): - return {k: getattr(self.adapter, k) for k, v in self.options.bl_rna.properties.keys() if p !='rna_type'} - def clear_library_path(self): #print('Clear Library Path', self.name) @@ -348,7 +333,7 @@ class AssetLibrary(PropertyGroup): data['adapter']['name'] = data.pop('adapter_name') del data['adapters'] - data['conform'] = self.conform.to_dict() + #data['conform'] = self.conform.to_dict() return data @@ -448,6 +433,7 @@ class AssetLibrary(PropertyGroup): layout.separator(factor=3) + """ def draw_extra(self, layout): #box = layout.box() @@ -503,15 +489,14 @@ class AssetLibrary(PropertyGroup): col.separator() - + """ def draw(self, layout): prefs = get_addon_prefs() + #box = layout.box() - box = layout.box() - - row = box.row(align=True) + row = layout.row(align=True) #row.use_property_split = False #row.alignment = 'LEFT' @@ -544,7 +529,7 @@ class AssetLibrary(PropertyGroup): sub_row.label(icon='FAKE_USER_ON') if self.expand: - col = box.column(align=False) + col = layout.column(align=False) col.use_property_split = True #row = col.row(align=True) @@ -564,13 +549,23 @@ class AssetLibrary(PropertyGroup): label='Custom Bundle Directory', ) + col.prop(self, "blend_depth") + + subcol = col.column(align=True) + subcol.prop(self, "template_description", text='Template Description', icon='COPY_ID') + subcol.prop(self, "template_image", text='Template Image', icon='COPY_ID') + subcol.prop(self, "template_video", text='Template Video', icon='COPY_ID') + + if self.adapter: col.separator() self.adapter.draw_prefs(col) - + + for lib in self.child_libraries: + lib.draw(layout) + col.separator() - self.draw_extra(col) - + class Collections: @@ -662,10 +657,7 @@ class AssetLibraryPrefs(AddonPreferences): update=update_all_library_path ) - #use_single_path : BoolProperty(default=True) - #template_description : StringProperty(default='../{name}_asset_description.json') - #template_image : StringProperty(default='../{name}_image.png') - #template_video : StringProperty(default='../{name}_video.mov') + config_directory : StringProperty( name="Config Path", @@ -773,8 +765,11 @@ class AssetLibraryPrefs(AddonPreferences): col.operator("assetlib.add_user_library", text='Bundle All Libraries', icon='MOD_BUILD') for lib in self.libraries:# list(self.env_libraries) + list(self.user_libraries): + if lib.parent: + continue - lib.draw(main_col) + box = main_col.box() + lib.draw(box) row = main_col.row() row.alignment = 'RIGHT' @@ -783,7 +778,7 @@ class AssetLibraryPrefs(AddonPreferences): classes = [ AssetLibraryAdapters, - ConformAssetLibrary, + #ConformAssetLibrary, AssetLibrary, AssetLibraryPrefs, ]