start refacto

This commit is contained in:
2024-05-27 17:22:45 +02:00
parent f7c125ae7b
commit a01b282f45
61 changed files with 528 additions and 3243 deletions
+17
View File
@@ -0,0 +1,17 @@
from asset_library.plugins import plugin
from asset_library.plugins import copy_folder
from asset_library.plugins import scan_folder
if 'bpy' in locals():
import importlib
importlib.reload(plugin)
importlib.reload(copy_folder)
importlib.reload(scan_folder)
import bpy
LibraryPlugin = plugin.LibraryPlugin
CopyFolder = copy_folder.CopyFolder
ScanFolder = scan_folder.ScanFolder
+216
View File
@@ -0,0 +1,216 @@
"""
Plugin for making an asset library of all blender file found in a folder
"""
from .scan_folder import ScanFolder
from ..core.bl_utils import load_datablocks
from ..core.template import Template
import bpy
from bpy.props import (StringProperty, IntProperty, BoolProperty)
import re
from pathlib import Path
from itertools import groupby
import uuid
import os
import shutil
import json
import time
from pprint import pprint
class Conform(ScanFolder):
name = "Conform"
source_directory : StringProperty(subtype='DIR_PATH')
target_template_file : StringProperty()
target_template_info : StringProperty()
target_template_image : StringProperty()
target_template_video : StringProperty()
def draw_prefs(self, layout):
layout.prop(self, "source_directory", text="Source : Directory")
col = layout.column(align=True)
col.prop(self, "source_template_file", icon='COPY_ID', text='Template file')
col.prop(self, "source_template_image", icon='COPY_ID', text='Template image')
col.prop(self, "source_template_video", icon='COPY_ID', text='Template video')
col.prop(self, "source_template_info", icon='COPY_ID', text='Template info')
col = layout.column(align=True)
col.prop(self, "target_template_file", icon='COPY_ID', text='Target : Template file')
col.prop(self, "target_template_image", icon='COPY_ID', text='Template image')
col.prop(self, "target_template_video", icon='COPY_ID', text='Template video')
col.prop(self, "target_template_info", icon='COPY_ID', text='Template info')
def get_asset_bundle_path(self, asset_data):
"""Template file are relative"""
src_directory = Path(self.source_directory).resolve()
src_template_file = Template(self.source_template_file)
asset_path = Path(asset_data['filepath']).as_posix()
asset_path = self.format_path(asset_path)
rel_path = asset_path.relative_to(src_directory).as_posix()
field_data = src_template_file.parse(rel_path)
#field_data = {f"catalog_{k}": v for k, v in field_data.items()}
# Change the int in the template by string to allow format
#target_template_file = re.sub(r'{(\d+)}', r'{cat\1}', self.target_template_file)
format_data = self.format_asset_data(asset_data)
#format_data['asset_name'] = format_data['asset_name'].lower().replace(' ', '_')
path = Template(self.target_template_file).format(format_data, **field_data).with_suffix('.blend')
path = Path(self.bundle_directory, path).resolve()
return path
def set_asset_preview(self, asset, asset_data):
'''Load an externalize image as preview for an asset using the target template'''
image_template = self.target_template_image
if not image_template:
return
asset_path = self.get_asset_bundle_path(asset_data)
image_path = self.find_path(image_template, asset_data, filepath=asset_path)
if image_path:
with bpy.context.temp_override(id=asset):
bpy.ops.ed.lib_id_load_custom_preview(
filepath=str(image_path)
)
else:
print(f'No image found for {image_template} on {asset.name}')
if asset.preview:
return asset.preview
def generate_previews(self, cache_diff):
print('Generate previews...')
# if cache in (None, ''):
# cache = self.fetch()
# elif isinstance(cache, (Path, str)):
# cache = self.read_cache(cache)
if isinstance(cache, (Path, str)):
cache_diff = LibraryCacheDiff(cache_diff)
#TODO Support all multiple data_type
for asset_info in cache:
if asset_info.get('type', self.data_type) == 'FILE':
self.generate_blend_preview(asset_info)
else:
self.generate_asset_preview(asset_info)
def generate_asset_preview(self, asset_info):
"""Only generate preview when conforming a library"""
#print('\ngenerate_preview', asset_info['filepath'])
scn = bpy.context.scene
vl = bpy.context.view_layer
#Creating the preview for collection, object or material
#camera = scn.camera
data_type = self.data_type #asset_info['data_type']
asset_path = self.format_path(asset_info['filepath'])
# Check if a source video exists and if so copying it in the new directory
if self.source_template_video and self.target_template_video:
for asset_data in asset_info['assets']:
asset_data = dict(asset_data, filepath=asset_path)
dst_asset_path = self.get_asset_bundle_path(asset_data)
dst_video_path = self.format_path(self.target_template_video, asset_data, filepath=dst_asset_path)
if dst_video_path.exists():
print(f'The dest video {dst_video_path} already exist')
continue
src_video_path = self.find_path(self.source_template_video, asset_data)
if src_video_path:
print(f'Copy video from {src_video_path} to {dst_video_path}')
self.copy_file(src_video_path, dst_video_path)
# Check if asset as a preview image or need it to be generated
asset_data_names = {}
if self.target_template_image:
for asset_data in asset_info['assets']:
asset_data = dict(asset_data, filepath=asset_path)
name = asset_data['name']
dst_asset_path = self.get_asset_bundle_path(asset_data)
dst_image_path = self.format_path(self.target_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
if self.source_template_image:
src_image_path = self.find_path(self.source_template_image, asset_data)
if src_image_path:
if src_image_path.suffix == dst_image_path.suffix:
self.copy_file(src_image_path, dst_image_path)
else:
print(src_image_path)
self.save_image(src_image_path, dst_image_path, remove=True)
continue
#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', list(asset_data_names.keys()))
asset_names = list(asset_data_names.keys())
assets = self.load_datablocks(asset_path, names=asset_names, link=True, type=data_type)
for asset in assets:
if not asset:
continue
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)
bpy.ops.view3d.camera_to_view_selected()
instance = vl.objects.active
#scn.collection.children.link(asset)
scn.render.filepath = str(image_path)
print(f'Render asset {asset.name} to {image_path}')
bpy.ops.render.render(write_still=True)
#instance.user_clear()
asset.user_clear()
bpy.data.objects.remove(instance)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
+49
View File
@@ -0,0 +1,49 @@
"""
Adapter for making an asset library of all blender file found in a folder
"""
from os.path import expandvars
import bpy
from bpy.props import StringProperty
from .library_plugin import LibraryPlugin
from ..core.file_utils import copy_dir
class CopyFolder(LibraryPlugin):
"""Copy library folder from a server to a local disk for better performance"""
name = "Copy Folder"
source_directory : StringProperty()
includes : StringProperty()
excludes : StringProperty()
def bundle(self, cache_diff=None):
src = expandvars(self.source_directory)
dst = expandvars(self.bundle_directory)
includes = [inc.strip() for inc in self.includes.split(',')]
excludes = [ex.strip() for ex in self.excludes.split(',')]
print(f'Copy Folder from {src} to {dst}...')
copy_dir(
src, dst, only_recent=True,
excludes=excludes, includes=includes
)
def filter_prop(self, prop):
if prop in ('template_info', 'template_video', 'template_image', 'blend_depth'):
return False
return True
# def draw_prop(self, layout, prop):
# if prop in ('template_info', 'template_video', 'template_image', 'blend_depth'):
# return
# super().draw_prop(layout)
View File
+269
View File
@@ -0,0 +1,269 @@
"""
Plugin for making an asset library of all blender file found in a folder
"""
import re
from pathlib import Path
from itertools import groupby
import uuid
import os
import shutil
import json
import urllib3
import traceback
import time
import bpy
from bpy.props import (StringProperty, IntProperty, BoolProperty)
from .library_plugin import LibraryPlugin
from ..core.template import Template
from ..core.file_utils import install_module
class Kitsu(LibraryPlugin):
name = "Kitsu"
template_name : StringProperty()
template_file : StringProperty()
source_directory : StringProperty(subtype='DIR_PATH')
#blend_depth: IntProperty(default=1)
source_template_image : StringProperty()
target_template_image : StringProperty()
url: StringProperty()
login: StringProperty()
password: StringProperty(subtype='PASSWORD')
project_name: StringProperty()
def connect(self, url=None, login=None, password=None):
'''Connect to kitsu api using provided url, login and password'''
gazu = install_module('gazu')
urllib3.disable_warnings()
if not self.url:
print(f'Kitsu Url: {self.url} is empty')
return
url = self.url
if not url.endswith('/api'):
url += '/api'
print(f'Info: Setting Host for kitsu {url}')
gazu.client.set_host(url)
if not gazu.client.host_is_up():
print('Error: Kitsu Host is down')
try:
print(f'Info: Log in to kitsu as {self.login}')
res = gazu.log_in(self.login, self.password)
print(f'Info: Sucessfully login to Kitsu as {res["user"]["full_name"]}')
return res['user']
except Exception as e:
print(f'Error: {traceback.format_exc()}')
def get_asset_path(self, name, catalog, directory=None):
directory = directory or self.source_directory
return Path(directory, self.get_asset_relative_path(name, catalog))
def get_asset_info(self, data, asset_path):
modified = time.time_ns()
catalog = data['entity_type_name'].title()
asset_path = self.prop_rel_path(asset_path, 'source_directory')
#asset_name = self.norm_file_name(data['name'])
asset_info = dict(
filepath=asset_path,
modified=modified,
library_id=self.library.id,
assets=[dict(
catalog=catalog,
metadata=data.get('data', {}),
description=data['description'],
tags=[],
type=self.data_type,
#image=self.library.template_image,
#video=self.library.template_video,
name=data['name'])
]
)
return asset_info
# def bundle(self, cache_diff=None):
# """Group all asset in one or multiple blends for the asset browser"""
# return super().bundle(cache_diff=cache_diff)
def set_asset_preview(self, asset, asset_data):
'''Load an externalize image as preview for an asset using the source template'''
asset_path = self.format_path(Path(asset_data['filepath']).as_posix())
image_path = self.find_path(self.target_template_image, asset_data, filepath=asset_path)
if image_path:
with bpy.context.temp_override(id=asset):
bpy.ops.ed.lib_id_load_custom_preview(
filepath=str(image_path)
)
else:
print(f'No image found for {self.target_template_image} on {asset.name}')
if asset.preview:
return asset.preview
def generate_previews(self, cache=None):
print('Generate previews...')
if cache in (None, ''):
cache = self.fetch()
elif isinstance(cache, (Path, str)):
cache = self.read_cache(cache)
#TODO Support all multiple data_type
for asset_info in cache:
if asset_info.get('type', self.data_type) == 'FILE':
self.generate_blend_preview(asset_info)
else:
self.generate_asset_preview(asset_info)
def generate_asset_preview(self, asset_info):
data_type = self.data_type
scn = bpy.context.scene
vl = bpy.context.view_layer
asset_path = self.format_path(asset_info['filepath'])
lens = 85
if not asset_path.exists():
print(f'Blend file {asset_path} not exit')
return
asset_data_names = {}
# First check wich assets need a preview
for asset_data in asset_info['assets']:
name = asset_data['name']
image_path = self.format_path(self.target_template_image, asset_data, filepath=asset_path)
if image_path.exists():
continue
#Store in a dict all asset_data that does not have preview
asset_data_names[name] = dict(asset_data, image_path=image_path)
if not asset_data_names:
print(f'All previews already existing for {asset_path}')
return
#asset_names = [a['name'] for a in asset_info['assets']]
asset_names = list(asset_data_names.keys())
assets = self.load_datablocks(asset_path, names=asset_names, link=True, type=data_type)
print(asset_names)
print(assets)
for asset in assets:
if not asset:
continue
print(f'Generate Preview for asset {asset.name}')
asset_data = asset_data_names[asset.name]
#print(self.target_template_image, asset_path)
image_path = self.format_path(self.target_template_image, asset_data, filepath=asset_path)
# Force redo preview
# 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)
scn.camera.data.lens = lens
bpy.ops.view3d.camera_to_view_selected()
scn.camera.data.lens -= 5
instance = vl.objects.active
#scn.collection.children.link(asset)
scn.render.filepath = str(image_path)
scn.render.image_settings.file_format = self.format_from_ext(image_path.suffix)
scn.render.image_settings.color_mode = 'RGBA'
scn.render.image_settings.quality = 90
print(f'Render asset {asset.name} to {image_path}')
bpy.ops.render.render(write_still=True)
#instance.user_clear()
asset.user_clear()
bpy.data.objects.remove(instance)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
def fetch(self):
"""Gather in a list all assets found in the folder"""
print(f'Fetch Assets for {self.library.name}')
gazu = install_module('gazu')
self.connect()
template_file = Template(self.template_file)
template_name = Template(self.template_name)
project = gazu.client.fetch_first('projects', {'name': self.project_name})
entity_types = gazu.client.fetch_all('entity-types')
entity_types_ids = {e['id']: e['name'] for e in entity_types}
cache = self.read_cache()
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(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))
except Exception:
print(f'Warning: Could not parse {asset_name} with template {template_name}')
asset_path = template_file.find(asset_field_data)
if not asset_path:
print(f'Warning: Could not find file for {template_file.format(asset_field_data)}')
continue
asset_path = self.prop_rel_path(asset_path, 'source_directory')
asset_cache_data = dict(
catalog=asset_data['entity_type_name'].title(),
metadata=asset_data.get('data', {}),
description=asset_data['description'],
tags=[],
type=self.data_type,
name=asset_data['name']
)
cache.add_asset_cache(asset_cache_data, filepath=asset_path)
return cache
+773
View File
@@ -0,0 +1,773 @@
import os
import shutil
import json
import uuid
import time
import subprocess
from pathlib import Path
from itertools import groupby
from functools import partial
from glob import glob
from copy import deepcopy
import bpy
from bpy_extras import asset_utils
from bpy.types import PropertyGroup
from bpy.props import StringProperty
#from asset_library.common.functions import (norm_asset_datas,)
from ..core.bl_utils import get_addon_prefs, load_datablocks
from ..core.file_utils import read_file, write_file
from ..core.template import Template
from ..constants import (MODULE_DIR, RESOURCES_DIR)
from ..data_type import (action, collection, file)
#from asset_library.common.library_cache import LibraryCacheDiff
class LibraryPlugin(PropertyGroup):
#def __init__(self):
#name = "Base Adapter"
#library = None
@property
def library(self):
prefs = self.addon_prefs
for lib in prefs.libraries:
if lib.plugin == self:
return lib
@property
def bundle_directory(self):
return self.library.library_path
@property
def data_type(self):
return self.library.data_type
@property
def data_types(self):
return self.library.data_types
# def get_catalog_path(self, directory=None):
# directory = directory or self.bundle_directory
# return Path(directory, 'blender_assets.cats.txt')
# @property
# def cache_file(self):
# return Path(self.bundle_directory) / f"blender_assets.{self.library.id}.json"
# @property
# def tmp_cache_file(self):
# return Path(bpy.app.tempdir) / f"blender_assets.{self.library.id}.json"
# @property
# def diff_file(self):
# return Path(bpy.app.tempdir, 'diff.json')
@property
def preview_blend(self):
return MODULE_DIR / self.data_type.lower() / "preview.blend"
@property
def preview_assets_file(self):
return Path(bpy.app.tempdir, "preview_assets_file.json")
@property
def addon_prefs(self):
return get_addon_prefs()
@property
def module_type(self):
lib_type = self.library.data_type
if lib_type == 'ACTION':
return action
elif lib_type == 'FILE':
return file
elif lib_type == 'COLLECTION':
return collection
@property
def format_data(self):
"""Dict for formating template"""
return dict(self.to_dict(), bundle_dir=self.library.bundle_dir, parent=self.library.parent)
def to_dict(self):
return {p: getattr(self, p) for p in self.bl_rna.properties.keys() if p !='rna_type'}
def read_catalog(self):
return self.library.read_catalog()
def read_cache(self, filepath=None):
return self.library.read_cache(filepath=filepath)
def fetch(self):
raise Exception('This method need to be define in the plugin')
def norm_file_name(self, name):
return name.replace(' ', '_')
def read_file(self, file):
return read_file(file)
def write_file(self, file, data):
return write_file(file, data)
def copy_file(self, source, destination):
src = Path(source)
dst = Path(destination)
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 {src}: source and destination are the same')
return
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, assets_only=False):
"""Link or append a datablock from a blendfile"""
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_data(self, asset):
"""Extract asset information on a datablock"""
return dict(
name=asset.name,
type=asset.bl_rna.name.upper(),
author=asset.asset_data.author,
tags=list(asset.asset_data.tags.keys()),
metadata=dict(asset.asset_data),
description=asset.asset_data.description,
)
def get_asset_relative_path(self, name, catalog):
'''Get a relative path for the asset'''
name = self.norm_file_name(name)
return Path(catalog, name, name).with_suffix('.blend')
def get_active_asset_library(self):
prefs = get_addon_prefs()
asset_handle = bpy.context.asset_file_handle
if not asset_handle:
return self
lib = None
if '.library_id' in asset_handle.asset_data:
lib_id = asset_handle.asset_data['.library_id']
lib = next((l for l in prefs.libraries if l.id == lib_id), None)
if not lib:
print(f"No library found for id {lib_id}")
if not lib:
lib = self
return lib
def get_active_asset_path(self):
'''Get the full path of the active asset_handle from the asset brower'''
prefs = get_addon_prefs()
asset_handle = bpy.context.asset_file_handle
lib = self.get_active_asset_library()
if 'filepath' in asset_handle.asset_data:
asset_path = asset_handle.asset_data['filepath']
asset_path = lib.plugin.format_path(asset_path)
else:
asset_path = bpy.types.AssetHandle.get_full_library_path(
asset_handle, bpy.context.asset_library_ref
)
return asset_path
def generate_previews(self):
raise Exception('Need to be defined in the plugin')
def get_image_path(self, name, catalog, filepath):
raise Exception('Need to be defined in the plugin')
def get_video_path(self, name, catalog, filepath):
raise Exception('Need to be defined in the plugin')
def new_asset(self, asset, asset_cache):
raise Exception('Need to be defined in the plugin')
def remove_asset(self, asset, asset_cache):
raise Exception('Need to be defined in the plugin')
def set_asset_preview(self, asset, asset_cache):
raise Exception('Need to be defined in the plugin')
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('/', '_'),
}
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()
params = dict(
**data,
**self.format_data,
)
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 read_asset_info_file(self, asset_path) -> dict:
# """Read the description file of the asset"""
# description_path = self.get_description_path(asset_path)
# return self.read_file(description_path)
# def write_description_file(self, asset_data, asset_path) -> None:
# description_path = self.get_description_path(asset_path)
# return write_file(description_path, asset_data)
def write_asset(self, asset, asset_path):
Path(asset_path).parent.mkdir(exist_ok=True, parents=True)
bpy.data.libraries.write(
str(asset_path),
{asset},
path_remap="NONE",
fake_user=True,
compress=True
)
# def read_catalog(self, directory=None):
# """Read the catalog file of the library target directory or of the specified directory"""
# catalog_path = self.get_catalog_path(directory)
# if not catalog_path.exists():
# return {}
# cat_data = {}
# for line in catalog_path.read_text(encoding="utf-8").split('\n'):
# if line.startswith(('VERSION', '#')) or not line:
# continue
# cat_id, cat_path, cat_name = line.split(':')
# cat_data[cat_path] = {'id':cat_id, 'name':cat_name}
# return cat_data
# def write_catalog(self, catalog_data, directory=None):
# """Write the catalog file in the library target directory or of the specified directory"""
# catalog_path = self.get_catalog_path(directory)
# lines = ['VERSION 1', '']
# # Add missing parents catalog
# norm_data = {}
# 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 cat_data or p in norm_data:
# continue
# norm_data[p.as_posix()] = {'id': str(uuid.uuid4()), 'name': '-'.join(p.parts)}
# for cat_path, cat_data in sorted(norm_data.items()):
# cat_name = cat_data['name'].replace('/', '-')
# lines.append(f"{cat_data['id']}:{cat_path}:{cat_name}")
# print(f'Catalog writen at: {catalog_path}')
# catalog_path.write_text('\n'.join(lines), encoding="utf-8")
# 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_infos, 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_infos))
def prop_rel_path(self, path, prop):
'''Get a filepath relative to a property of the plugin'''
field_prop = '{%s}/'%prop
prop_value = getattr(self, prop)
prop_value = Path(os.path.expandvars(prop_value)).resolve()
rel_path = Path(path).resolve().relative_to(prop_value).as_posix()
return field_prop + rel_path
def format_from_ext(self, ext):
if ext.startswith('.'):
ext = ext[1:]
file_format = ext.upper()
if file_format == 'JPG':
file_format = 'JPEG'
elif file_format == 'EXR':
file_format = 'OPEN_EXR'
return file_format
def save_image(self, image, filepath, remove=False):
filepath = Path(filepath)
if isinstance(image, (str, Path)):
image = bpy.data.images.load(str(image))
image.update()
image.filepath_raw = str(filepath)
file_format = self.format_from_ext(filepath.suffix)
image.file_format = file_format
image.save()
if remove:
bpy.data.images.remove(image)
else:
return image
def write_preview(self, preview, filepath):
if not preview or not filepath:
return
filepath = Path(filepath)
filepath.parent.mkdir(parents=True, exist_ok=True)
img_size = preview.image_size
px = [0] * img_size[0] * img_size[1] * 4
preview.image_pixels_float.foreach_get(px)
img = bpy.data.images.new(name=filepath.name, width=img_size[0], height=img_size[1], is_data=True, alpha=True)
img.pixels.foreach_set(px)
self.save_image(img, filepath, remove=True)
def draw_header(self, layout):
"""Draw the header of the Asset Browser Window"""
#layout.separator()
self.module_type.gui.draw_header(layout)
def draw_context_menu(self, layout):
"""Draw the context menu of the Asset Browser Window"""
self.module_type.gui.draw_context_menu(layout)
def generate_blend_preview(self, asset_info):
asset_name = asset_info['name']
catalog = asset_info['catalog']
asset_path = self.format_path(asset_info['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_info.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_info):
"""Only generate preview when conforming a library"""
#print('\ngenerate_preview', asset_info['filepath'])
scn = bpy.context.scene
#Creating the preview for collection, object or material
camera = scn.camera
vl = bpy.context.view_layer
data_type = self.data_type #asset_info['data_type']
asset_path = self.format_path(asset_info['filepath'])
# Check if a source video exists and if so copying it in the new directory
if self.library.template_video:
for asset_data in asset_info['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:
print(f'Copy video from {src_video_path} to {dst_video_path}')
self.copy_file(src_video_path, dst_video_path)
# Check if asset as a preview image or need it to be generated
asset_data_names = {}
if self.library.template_image:
for asset_data in asset_info['assets']:
name = asset_data['name']
dst_asset_path = self.get_asset_bundle_path(asset_data)
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_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:
if src_image_path.suffix == dst_image_path.suffix:
self.copy_file(src_image_path, dst_image_path)
else:
#img = bpy.data.images.load(str(src_image_path))
self.save_image(src_image_path, dst_image_path, remove=True)
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)
for asset in assets:
if not asset:
continue
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)
bpy.ops.view3d.camera_to_view_selected()
instance = vl.objects.active
#scn.collection.children.link(asset)
scn.render.filepath = str(image_path)
print(f'Render asset {asset.name} to {image_path}')
bpy.ops.render.render(write_still=True)
#instance.user_clear()
asset.user_clear()
bpy.data.objects.remove(instance)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
'''
# def set_asset_catalog(self, asset, asset_data, catalog_data):
# """Find the catalog if already exist or create it"""
# catalog_name = asset_data['catalog']
# catalog = catalog_data.get(catalog_name)
# catalog_item = self.catalog.add(asset_data['catalog'])
# asset.asset_data.catalog_id = catalog_item.id
# if not catalog:
# catalog = {'id': str(uuid.uuid4()), 'name': catalog_name}
# catalog_data[catalog_name] = catalog
# asset.asset_data.catalog_id = catalog['id']
def set_asset_metadata(self, asset, asset_cache):
"""Create custom prop to an asset base on provided data"""
for k, v in asset_cache.metadata.items():
asset.asset_data[k] = v
def set_asset_tags(self, asset, asset_cache):
"""Create asset tags base on provided data"""
if asset_cache.tags is not None:
for tag in list(asset.asset_data.tags):
asset.asset_data.tags.remove(tag)
for tag in asset_cache.tags:
asset.asset_data.tags.new(tag, skip_if_exists=True)
def set_asset_info(self, asset, asset_cache):
"""Set asset description base on provided data"""
asset.asset_data.author = asset_cache.author
asset.asset_data.description = asset_cache.description
def get_asset_bundle_path(self, asset_cache):
"""Get the bundle path for that asset"""
catalog_parts = asset_cache.catalog_item.parts
blend_name = asset_cache.norm_name
path_parts = catalog_parts[:self.library.blend_depth]
return Path(self.bundle_directory, *path_parts, blend_name, blend_name).with_suffix('.blend')
def bundle(self, cache_diff=None):
"""Group all new assets in one or multiple blends for the asset browser"""
supported_types = ('FILE', 'ACTION', 'COLLECTION')
supported_operations = ('ADD', 'REMOVE', 'MODIFY')
if self.data_type not in supported_types:
print(f'{self.data_type} is not supported yet supported types are {supported_types}')
return
catalog = self.read_catalog()
cache = None
write_cache = False
if not cache_diff:
# Get list of all modifications
cache = self.fetch()
cache_diff = cache.diff()
# Write the cache in a temporary file for the generate preview script
tmp_cache_file = cache.write(tmp=True)
bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
elif isinstance(cache_diff, (Path, str)):
cache_diff = LibraryCacheDiff(cache_diff).read()#json.loads(Path(cache_diff).read_text(encoding='utf-8'))
total_diffs = len(cache_diff)
print(f'Total Diffs={total_diffs}')
if total_diffs == 0:
print('No assets found')
return
i = 0
for bundle_path, asset_diffs in cache_diff.group_by(self.get_asset_bundle_path):
if bundle_path.exists():
print(f'Opening existing bundle blend: {bundle_path}')
bpy.ops.wm.open_mainfile(filepath=str(bundle_path))
else:
print(f'Create new bundle blend to: {bundle_path}')
bpy.ops.wm.read_homefile(use_empty=True)
for asset_diff in asset_diffs:
if total_diffs <= 100 or i % int(total_diffs / 10) == 0:
print(f'Progress: {int(i / total_diffs * 100)+1}')
operation = asset_diff.operation
asset_cache = asset_diff.asset_cache
asset = getattr(bpy.data, self.data_types).get(asset_cache.name)
if operation == 'REMOVE':
if asset:
getattr(bpy.data, self.data_types).remove(asset)
else:
print(f'ERROR : Remove Asset: {asset_cache.name} not found in {bundle_path}')
continue
elif operation == 'MODIFY':
if not asset:
print(f'WARNING: Modifiy Asset: {asset_cache.name} not found in {bundle_path} it will be created')
if operation == 'ADD' or not asset:
if asset:
#raise Exception(f"Asset {asset_data['name']} Already in Blend")
print(f"Asset {asset_cache.name} Already in Blend")
getattr(bpy.data, self.data_types).remove(asset)
#print(f"INFO: Add new asset: {asset_data['name']}")
asset = getattr(bpy.data, self.data_types).new(name=asset_cache.name)
asset.asset_mark()
self.set_asset_preview(asset, asset_cache)
#if not asset_preview:
# assets_to_preview.append((asset_data['filepath'], asset_data['name'], asset_data['data_type']))
#if self.externalize_data:
# self.write_preview(preview, filepath)
#self.set_asset_catalog(asset, asset_data['catalog'])
asset.asset_data.catalog_id = catalog.add(asset_cache.catalog).id
self.set_asset_metadata(asset, asset_cache)
self.set_asset_tags(asset, asset_cache)
self.set_asset_info(asset, asset_cache)
i += 1
#self.write_asset_preview_file()
print(f'Saving Blend to {bundle_path}')
bundle_path.parent.mkdir(exist_ok=True, parents=True)
bpy.ops.wm.save_as_mainfile(filepath=str(bundle_path), compress=True)
if write_cache:
cache.write()
#self.write_catalog(catalog_data)
catalog.write()
bpy.ops.wm.quit_blender()
# def unflatten_cache(self, cache):
# """ Return a new unflattten list of asset data
# grouped by filepath"""
# new_cache = []
# cache = deepcopy(cache)
# cache.sort(key=lambda x : x['filepath'])
# groups = groupby(cache, key=lambda x : x['filepath'])
# keys = ['filepath', 'modified', 'library_id']
# for _, asset_datas in groups:
# asset_datas = list(asset_datas)
# #print(asset_datas[0])
# asset_info = {k:asset_datas[0][k] for k in keys}
# asset_info['assets'] = [{k:v for k, v in a.items() if k not in keys+['operation']} for a in asset_datas]
# new_cache.append(asset_info)
# return new_cache
# def flatten_cache(self, cache):
# """ Return a new flat list of asset data
# the filepath keys are merge with the assets keys"""
# # If the cache has a wrong format
# if not cache or not isinstance(cache[0], dict):
# return []
# new_cache = []
# for asset_info in cache:
# asset_info = asset_info.copy()
# if 'assets' in asset_info:
# assets = asset_info.pop('assets')
# for asset_data in assets:
# new_cache.append({**asset_info, **asset_data})
# else:
# new_cache.append(asset_info)
# return new_cache
# def diff(self, asset_infos=None):
# """Compare the library cache with it current state and return the new cache and the difference"""
# cache = self.read_cache()
# if cache is None:
# print(f'Fetch The library {self.library.name} for the first time, might be long...')
# cache = []
# asset_infos = asset_infos or self.fetch()
# cache = {f"{a['filepath']}/{a['name']}": a for a in self.flatten_cache(cache)}
# new_cache = {f"{a['filepath']}/{a['name']}" : a for a in self.flatten_cache(asset_infos)}
# assets_added = [v for k, v in new_cache.items() if k not in cache]
# assets_removed = [v for k, v in cache.items() if k not in new_cache]
# assets_modified = [v for k, v in cache.items() if v not in assets_removed and v!= new_cache[k]]
# if assets_added:
# print(f'{len(assets_added)} Assets Added \n{tuple(a["name"] for a in assets_added[:10])}\n')
# if assets_removed:
# print(f'{len(assets_removed)} Assets Removed \n{tuple(a["name"] for a in assets_removed[:10])}\n')
# if assets_modified:
# print(f'{len(assets_modified)} Assets Modified \n{tuple(a["name"] for a in assets_modified[:10])}\n')
# assets_added = [dict(a, operation='ADD') for a in assets_added]
# assets_removed = [dict(a, operation='REMOVE') for a in assets_removed]
# assets_modified = [dict(a, operation='MODIFY') for a in assets_modified]
# cache_diff = assets_added + assets_removed + assets_modified
# if not cache_diff:
# print('No change in the library')
# return list(new_cache.values()), cache_diff
def draw_prefs(self, layout):
"""Draw the options in the addon preference for this plugin"""
annotations = self.__class__.__annotations__
for k, v in annotations.items():
layout.prop(self, k, text=bpy.path.display_name(k))
+141
View File
@@ -0,0 +1,141 @@
"""
Plugin for making an asset library of all blender file found in a folder
"""
import os
import re
import uuid
import shutil
import json
import requests
import urllib3
import traceback
import time
from itertools import groupby
from pathlib import Path
from pprint import pprint as pp
import bpy
from bpy.props import (StringProperty, IntProperty, BoolProperty, EnumProperty)
from .library_plugin import LibraryPlugin
from ..core.template import Template
from ..core.file_utils import install_module
REQ_HEADERS = requests.utils.default_headers()
REQ_HEADERS.update({"User-Agent": "Blender: PH Assets"})
class PolyHaven(LibraryPlugin):
name = "Poly Haven"
# template_name : StringProperty()
# template_file : StringProperty()
directory : StringProperty(subtype='DIR_PATH')
asset_type : EnumProperty(items=[(i.replace(' ', '_').upper(), i, '') for i in ('HDRIs', 'Models', 'Textures')], default='HDRIS')
main_category : StringProperty(
default='artificial light, natural light, nature, studio, skies, urban'
)
secondary_category : StringProperty(
default='high constrast, low constrast, medium constrast, midday, morning-afternoon, night, sunrise-sunset'
)
#blend_depth: IntProperty(default=1)
# url: StringProperty()
# login: StringProperty()
# password: StringProperty(subtype='PASSWORD')
# project_name: StringProperty()
def get_asset_path(self, name, catalog, directory=None):
# chemin: Source, Asset_type, asset_name / asset_name.blend -> PolyHaven/HDRIs/test/test.blend
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 bundle(self, cache_diff=None):
# """Group all asset in one or multiple blends for the asset browser"""
# return super().bundle(cache_diff=cache_diff)
def format_asset_info(self, asset_info, asset_path):
# prend un asset info et output un asset description
asset_path = self.prop_rel_path(asset_path, 'source_directory')
modified = asset_info.get('modified', time.time_ns())
return dict(
filepath=asset_path,
modified=modified,
library_id=self.library.id,
assets=[dict(
catalog=asset_data.get('catalog', asset_info['catalog']),
author=asset_data.get('author'),
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 asset_info['assets']
]
)
def fetch(self):
"""Gather in a list all assets found in the folder"""
print(f'Fetch Assets for {self.library.name}')
print('self.asset_type: ', self.asset_type)
url = f"https://api.polyhaven.com/assets?t={self.asset_type.lower()}"
# url2 = f"https://polyhaven.com/{self.asset_type.lower()}"
# url += "&future=true" if early_access else ""
# verify_ssl = not bpy.context.preferences.addons["polyhavenassets"].preferences.disable_ssl_verify
verify_ssl = False
try:
res = requests.get(url, headers=REQ_HEADERS, verify=verify_ssl)
res2 = requests.get(url2, headers=REQ_HEADERS, verify=verify_ssl)
except Exception as e:
msg = f"[{type(e).__name__}] Error retrieving {url}"
print(msg)
# return (msg, None)
if res.status_code != 200:
error = f"Error retrieving asset list, status code: {res.status_code}"
print(error)
# return (error, None)
catalog = None
# return (None, res.json())
for asset_info in res.json().values():
main_category = None
secondary_category = None
for category in asset_info['categories']:
if category in self.main_category and not main_category:
main_category = category
if category in self.secondary_category and not secondary_category:
secondary_category = category
if main_category and secondary_category:
catalog = f'{main_category}_{secondary_category}'
if not catalog:
return
asset_path = self.get_asset_path(asset_info['name'], catalog)
print('asset_path: ', asset_path)
asset_info = self.format_asset_info(asset_info, asset_path)
print('asset_info: ', asset_info)
# return self.format_asset_info([asset['name'], self.get_asset_path(asset['name'], catalog) for asset, asset_infos in res.json().items()])
# pp(res.json())
# pp(res2.json())
# print(res2)
# return asset_infos
+302
View File
@@ -0,0 +1,302 @@
"""
Plugin for making an asset library of all blender file found in a folder
"""
import re
import uuid
import os
import shutil
import json
import time
from pathlib import Path
from itertools import groupby
import bpy
from bpy.props import (StringProperty, IntProperty, BoolProperty)
from .library_plugin import LibraryPlugin
from ..core.bl_utils import load_datablocks
from ..core.template import Template
class ScanFolder(LibraryPlugin):
name = "Scan Folder"
source_directory : StringProperty(subtype='DIR_PATH')
source_template_file : StringProperty()
source_template_image : StringProperty()
source_template_video : StringProperty()
source_template_info : StringProperty()
def draw_prefs(self, layout):
layout.prop(self, "source_directory", text="Source: Directory")
col = layout.column(align=True)
col.prop(self, "source_template_file", icon='COPY_ID', text='Template file')
col.prop(self, "source_template_image", icon='COPY_ID', text='Template image')
col.prop(self, "source_template_video", icon='COPY_ID', text='Template video')
col.prop(self, "source_template_info", icon='COPY_ID', text='Template info')
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.source_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.source_template_video, dict(name=name, catalog=catalog, filepath=filepath))
def new_asset(self, asset, asset_data):
raise Exception('Need to be defined in the plugin')
def remove_asset(self, asset, asset_data):
raise Exception('Need to be defined in the plugin')
'''
def format_asset_info(self, asset_datas, asset_path, modified=None):
asset_path = self.prop_rel_path(asset_path, 'source_directory')
modified = modified or time.time_ns()
library_id = self.library.id
# if self.data_type == 'FILE':
# return dict(
# filepath=asset_path,
# author=asset_info.get('author'),
# modified=modified,
# library_id=library_id,
# catalog=asset_info['catalog'],
# tags=[],
# description=asset_info.get('description', ''),
# type=self.data_type,
# #image=self.source_template_image,
# name=asset_info['name']
# )
return dict(
filepath=asset_path,
modified=modified,
library_id=library_id,
assets=[dict(
catalog=asset_data['catalog'],
author=asset_data.get('author', ''),
metadata=asset_data.get('metadata', {}),
description=asset_data.get('description', ''),
tags=asset_data.get('tags', []),
type=self.data_type,
name=asset_data['name']) for asset_data in asset_datas
]
)
'''
def set_asset_preview(self, asset, asset_cache):
'''Load an externalize image as preview for an asset using the source template'''
asset_path = self.format_path(asset_cache.filepath)
image_template = self.source_template_image
if not image_template:
return
image_path = self.find_path(image_template, asset_cache.to_dict(), filepath=asset_path)
if image_path:
with bpy.context.temp_override(id=asset):
bpy.ops.ed.lib_id_load_custom_preview(
filepath=str(image_path)
)
else:
print(f'No image found for {image_template} on {asset.name}')
if asset.preview:
return asset.preview
def bundle(self, cache_diff=None):
"""Group all new assets in one or multiple blends for the asset browser"""
if self.data_type not in ('FILE', 'ACTION', 'COLLECTION'):
print(f'{self.data_type} is not supported yet')
return
#catalog_data = self.read_catalog()
catalog = self.read_catalog()
cache = None
if not cache_diff:
# Get list of all modifications
cache = self.fetch()
cache_diff = cache.diff()
# Write the cache in a temporary file for the generate preview script
tmp_cache_file = cache.write(tmp=True)
bpy.ops.assetlibrary.generate_previews(name=self.library.name, cache=str(tmp_cache_file))
elif isinstance(cache_diff, (Path, str)):
cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8'))
if self.library.blend_depth == 0:
raise Exception('Blender depth must be 1 at min')
total_assets = len(cache_diff)
print(f'total_assets={total_assets}')
if total_assets == 0:
print('No assets found')
return
i = 0
for blend_path, asset_cache_diffs in cache_diff.group_by(key=self.get_asset_bundle_path):
if blend_path.exists():
print(f'Opening existing bundle blend: {blend_path}')
bpy.ops.wm.open_mainfile(filepath=str(blend_path))
else:
print(f'Create new bundle blend to: {blend_path}')
bpy.ops.wm.read_homefile(use_empty=True)
for asset_cache_diff in asset_cache_diffs:
if total_assets <= 100 or i % int(total_assets / 10) == 0:
print(f'Progress: {int(i / total_assets * 100)+1}')
operation = asset_cache_diff.operation
asset_cache = asset_cache_diff.asset_cache
asset_name = asset_cache.name
asset = getattr(bpy.data, self.data_types).get(asset_name)
if operation == 'REMOVE':
if asset:
getattr(bpy.data, self.data_types).remove(asset)
else:
print(f'ERROR : Remove Asset: {asset_name} not found in {blend_path}')
continue
if asset_cache_diff.operation == 'MODIFY' and not asset:
print(f'WARNING: Modifiy Asset: {asset_name} not found in {blend_path} it will be created')
if operation == 'ADD' or not asset:
if asset:
#raise Exception(f"Asset {asset_name} Already in Blend")
print(f"Asset {asset_name} Already in Blend")
getattr(bpy.data, self.data_types).remove(asset)
#print(f"INFO: Add new asset: {asset_name}")
asset = getattr(bpy.data, self.data_types).new(name=asset_name)
else:
print(f'operation {operation} not supported should be in (ADD, REMOVE, MODIFY)')
continue
asset.asset_mark()
asset.asset_data.catalog_id = catalog.add(asset_cache_diff.catalog).id
self.set_asset_preview(asset, asset_cache)
self.set_asset_metadata(asset, asset_cache)
self.set_asset_tags(asset, asset_cache)
self.set_asset_info(asset, asset_cache)
i += 1
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 the variable cache_diff was given we need to update the cache with the diff
if cache is None:
cache = self.read_cache()
cache.update(cache_diff)
cache.write()
catalog.update(cache.catalogs)
catalog.write()
bpy.ops.wm.quit_blender()
def fetch(self):
"""Gather in a list all assets found in the folder"""
print(f'Fetch Assets for {self.library.name}')
source_directory = Path(self.source_directory)
template_file = Template(self.source_template_file)
#catalog_data = self.read_catalog(directory=source_directory)
#catalog_ids = {v['id']: k for k, v in catalog_data.items()}
#self.catalog.read()
cache = self.read_cache()
print(f'Search for blend using glob template: {template_file.glob_pattern}')
print(f'Scanning Folder {source_directory}...')
#new_cache = LibraryCache()
for asset_path in template_file.glob(source_directory):
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
file_cache = next((a for a in cache if a.filepath == source_rel_path), None)
if file_cache:
if file_cache.modified >= modified: #print(asset_path, 'is skipped because not modified')
continue
else:
file_cache = cache.add(filepath=source_rel_path)
rel_path = asset_path.relative_to(source_directory).as_posix()
field_data = template_file.parse(rel_path)
# Create the catalog path from the actual path of the asset
catalog = [v for k,v in sorted(field_data.items()) if re.findall('cat[0-9]+', k)]
#catalogs = [c.replace('_', ' ').title() for c in catalogs]
asset_name = field_data.get('asset_name', asset_path.stem)
if self.data_type == 'FILE':
file_cache.set_data(
name=asset_name,
type='FILE',
catalog=catalog,
modified=modified
)
continue
# Now check if there is a asset description file (Commented for now propably not usefull)
#asset_info_path = self.find_path(self.source_template_info, asset_info, filepath=asset_path)
#if asset_info_path:
# new_cache.append(self.read_file(asset_info_path))
# continue
# Scan the blend file for assets inside
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')
for asset in assets:
#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_info['catalog']#asset_path.relative_to(self.source_directory).as_posix()
# For now the catalog used is the one extract from the template file
file_cache.assets.add(self.get_asset_data(asset), catalog=catalog)
getattr(bpy.data, self.data_types).remove(asset)
return cache