continue refactoring

master
“christopheseux” 2022-12-27 23:49:57 +01:00
parent fc405797d9
commit 575bbade7b
13 changed files with 506 additions and 156 deletions

View File

@ -1032,7 +1032,7 @@ class ACTIONLIB_OT_store_anim_pose(Operator):
modified=time.time_ns()
)
lib.adapter.write_asset_description(asset_description, asset_path)
lib.adapter.write_description_file(asset_description, asset_path)
# Restore action and cleanup
ob.animation_data.action = current_action

View File

@ -1,8 +1,9 @@
from asset_library.common.functions import (read_catalog, write_catalog, norm_asset_datas, get_catalog_path)
from asset_library.common.functions import (read_catalog, write_catalog, norm_asset_datas)
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 import (action, collection, file)
@ -18,6 +19,7 @@ import json
import uuid
import time
from functools import partial
import subprocess
class AssetLibraryAdapter(PropertyGroup):
@ -26,7 +28,7 @@ class AssetLibraryAdapter(PropertyGroup):
name = "Base Adapter"
#library = None
bundle_directory : StringProperty()
#bundle_directory : StringProperty()
@property
def library(self):
@ -37,29 +39,9 @@ class AssetLibraryAdapter(PropertyGroup):
if lib.conform.adapter == self:
return lib
@property
def library_path(self):
return self.library.library_path
@property
def image_template(self):
return Template(self.library.image_template)
@property
def video_template(self):
return Template(self.library.video_template)
@property
def asset_description_template(self):
return Template(self.library.asset_description_template)
@property
def data_type(self):
return self.library.data_type
@property
def data_types(self):
return self.library.data_types
#@property
#def library_path(self):
# return self.library.library_path
@property
def is_conform(self):
@ -70,29 +52,70 @@ class AssetLibraryAdapter(PropertyGroup):
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_dir
@property
def blend_depth(self):
if self.is_conform:
return self.library.conform.blend_depth
return self.library.blend_depth
@property
def template_image(self):
return Template(self.library.conform.template_image)
@property
def externalize_data(self):
return self.library.externalize_data
def template_video(self):
return Template(self.library.conform.template_video)
@property
def catalog_path(self):
return self.library.catalog_path
def template_description(self):
return Template(self.library.conform.template_description)
def get_catalog_path(self, filepath):
return get_catalog_path(filepath)
@property
def data_type(self):
return self.library.data_type
@property
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
return Path(directory, 'blender_assets.cats.txt')
@property
def cache_file(self):
return Path(self.library_path) / f"blender_assets.{self.library.id}.json"
return Path(self.target_directory) / f"blender_assets.{self.library.id}.json"
#return get_asset_datas_file(self.library_path)
@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()
@ -116,6 +139,12 @@ class AssetLibraryAdapter(PropertyGroup):
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)
@ -185,6 +214,31 @@ 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()
params = {
'name': name,
'asset_path': Path(asset_path),
'catalog': catalog,
'catalog_name': 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.conform.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)
def get_video_path(self, name, asset_path, catalog) -> Path:
return self.get_template_path(self.library.conform.template_video, name, asset_path, catalog)
'''
def get_path(self, type, name, asset_path, template=None) -> Path:
if not template:
template = getattr(self, f'{type}_template')
@ -193,27 +247,40 @@ class AssetLibraryAdapter(PropertyGroup):
template = Template(template)
filepath = Path(asset_path)
return (filepath / template.format(name=name, path=Path(asset_path))).resolve()
params = {
'bundle_dir': self.library.bundle_directory,
'conform_dir': self.library.conform.directory,
'rel_path': '',
'catalog':'',
'catalog_name':'',
'name': name
}
return self.format_path(template, params)#(filepath / template.format(name=name, path=Path(asset_path))).resolve()
#def get_image_path(self, name, asset_path):
# filepath = Path(asset_path)
# image_name = self._get_file_name(name, asset_path)
# return (filepath / self.image_template.format(name=image_name)).resolve()
# return (filepath / self.template_image.format(name=image_name)).resolve()
def get_cache_image_path(self, name, catalog) -> Path:
""""Get the the cache path of a image for asset without an externalized image"""
name = self.norm_file_name(name)
return Path(self.library_path, '.previews', f"{catalog.replace('/', '_')}_{name}").with_suffix(('.png'))
def get_cache_image(self, name, catalog):
cache_image_path = self.get_cache_image_path(name, catalog)
if cache_image_path.exists():
return cache_image_path
'''
#def get_video_path(self, name, asset_path):
# filepath = Path(asset_path)
# video_name = self._get_file_name(name, asset_path)
# return (filepath / self.video_template.format(name=video_name)).resolve()
# return (filepath / self.template_video.format(name=video_name)).resolve()
'''
def get_image(self, name, asset_path):
image_path = self.get_path('image', name, asset_path)
if image_path.exists():
@ -223,21 +290,19 @@ class AssetLibraryAdapter(PropertyGroup):
video_path = self.get_path('video', name, asset_path)
if video_path.exists():
return video_path
'''
def get_asset_description_path(self, asset_path) -> Path:
""""Get the path of the json or yaml describing all assets data in onle file"""
filepath = Path(asset_path)
return (filepath / self.asset_description_template.format(name=filepath.stem)).resolve()
def read_asset_description(self, asset_path) -> dict:
def read_asset_description_file(self, asset_path) -> dict:
"""Read the description file of the asset"""
asset_description_path = self.get_asset_description_path(asset_path)
return read_file(asset_description_path)
description_path = self.get_description_path(asset_path)
return self.read_file(description_path)
def write_asset_description(self, asset_data, asset_path) -> None:
asset_description_path = self.get_asset_description_path(asset_path)
return write_file(asset_description_path, asset_data)
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):
bpy.data.libraries.write(
@ -247,27 +312,48 @@ class AssetLibraryAdapter(PropertyGroup):
fake_user=True,
compress=True
)
def read_catalog(self, filepath=None):
"""Read the catalog file of the library bundle path or of the specified filepath"""
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)
catalog_path = self.catalog_path
if filepath:
catalog_path = self.get_catalog_path(filepath)
return read_catalog(catalog_path)
cat_data = {}
def write_catalog(self, catalog_data, filepath=None):
"""Write the catalog file in the library bundle path or of the specified filepath"""
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}
catalog_path = self.catalog_path
if filepath:
catalog_path = self.get_catalog_path(filepath)
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"""
return write_catalog(catalog_path, catalog_data)
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 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):
return read_file(self.cache_file)
return self.read_file(self.cache_file)
def norm_asset_datas(self, asset_file_datas):
''' Return a new flat list of asset data
@ -326,8 +412,148 @@ class AssetLibraryAdapter(PropertyGroup):
return catalog_parts[:self.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
)
)
'''
def generate_preview(self, asset_description):
"""Only generate preview when conforming a library"""
#print('generate_preview', filepath, asset_names, data_type)
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_description['data_type']
asset_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():
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:
# No preview to generate
return
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 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.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):
cache = self.fetch()
#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 cache:
self.generate_preview(asset_description)
# filepath = asset_description['filepath']
# asset_datas = asset_description["assets"]
# asset_datas.sort(key=lambda x :x.get('type', self.data_type))
# data_type_groups = groupby(asset_datas, key=lambda x :x.get('type', self.data_type))
# for data_type, same_type_asset_datas in data_type_groups:
# asset_names = [a['name'] for a in same_type_asset_datas]
# self.generate_preview(filepath, asset_names, data_type)
def set_asset_preview(self, asset, asset_data):
"""Load an externalize image as preview for an asset"""
'''Load an externalize image as preview for an asset'''
image_path = Path(asset_data['image'])
if not image_path.is_absolute():
@ -339,34 +565,52 @@ class AssetLibraryAdapter(PropertyGroup):
bpy.ops.ed.lib_id_load_custom_preview(
filepath=str(image_path)
)
return
if asset.preview:
return
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
bpy.ops.ed.lib_id_generate_preview({"id": src_asset})
#time.sleep(0.01)
if not self.data_type == 'COLLECTION':
print(f'Generate preview of type {self.data_type} not supported yet')
return
#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.children.link(src_asset)
# bpy.ops.ed.lib_id_generate_preview({"id": asset})
asset.preview_ensure()
asset.preview.image_size = src_asset.preview.image_size
asset.preview.image_pixels_float.foreach_set(pixels)
# 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)
#getattr(bpy.data, self.data_types).remove(src_asset)
#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):
@ -405,6 +649,10 @@ class AssetLibraryAdapter(PropertyGroup):
continue
asset.asset_data.tags.new(tag, skip_if_exists=True)
def set_asset_description(self, asset, asset_data):
"""Set asset description base on provided data"""
asset.asset_data.description = asset_data.get('description', '')
def bundle(self, cache_diff=None):
"""Group all new assets in one or multiple blends for the asset browser"""
@ -412,7 +660,9 @@ class AssetLibraryAdapter(PropertyGroup):
print(f'{self.data_type} is not supported yet')
return
lib_path = self.library_path
target_dir = self.target_directory
catalog_data = self.read_catalog() #TODO remove unused catalog
write_cache = False
@ -423,11 +673,18 @@ class AssetLibraryAdapter(PropertyGroup):
# Only write complete cache at the end
write_cache = True
self.generate_previews()
elif isinstance(cache_diff, (Path, str)):
cache_diff = json.loads(Path(cache_diff).read_text(encoding='utf-8'))
if self.blend_depth == 0:
groups = [(cache_diff)]
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)
@ -440,9 +697,10 @@ class AssetLibraryAdapter(PropertyGroup):
return
i = 0
#assets_to_preview = []
for sub_path, asset_datas in groups:
blend_name = sub_path[-1].replace(' ', '_').lower()
blend_path = Path(lib_path, *sub_path, blend_name).with_suffix('.blend')
blend_path = Path(target_dir, *sub_path, blend_name).with_suffix('.blend')
if blend_path.exists():
print(f'Opening existing bundle blend: {blend_path}')
@ -471,9 +729,10 @@ class AssetLibraryAdapter(PropertyGroup):
elif operation == 'ADD' or not asset:
if asset:
#raise Exception(f"Asset {asset_data['name']} Already in Blend")
print(f"Asset {asset_data['name']} Already in Blend")
getattr(bpy.data, self.data_types).remove(asset)
#print(f"INFO: Add new asset: {asset_data['name']}")
print(f"INFO: Add new asset: {asset_data['name']}")
asset = getattr(bpy.data, self.data_types).new(name=asset_data['name'])
else:
print(f'operation {operation} not supported should be in (ADD, REMOVE, MODIFIED)')
@ -482,26 +741,36 @@ class AssetLibraryAdapter(PropertyGroup):
asset.asset_mark()
self.set_asset_preview(asset, asset_data)
#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_data)
self.set_asset_metadata(asset, asset_data)
self.set_asset_tags(asset, asset_data)
asset.asset_data.description = asset_data.get('description', '')
self.set_asset_description(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)
bpy.ops.wm.save_as_mainfile(filepath=str(blend_path), compress=True)
#blend_path.parent.mkdir(exist_ok=True, parents=True)
#bpy.ops.wm.save_as_mainfile(filepath=str(blend_path), compress=True)
#if write_cache:
# self.write_cache(cache)
#self.write_catalog(catalog_data)
self.write_catalog(catalog_data)
bpy.ops.wm.quit_blender()
def norm_cache(self, cache):
@ -566,4 +835,11 @@ class AssetLibraryAdapter(PropertyGroup):
layout.prop(self, k, text=bpy.path.display_name(k))
def format_path(self, template, **kargs):
return Template(template).format(self.to_dict(), **kargs).resolve()
params = dict(self.to_dict(),
bundle_dir=Path(self.library.bundle_directory),
conform_dir=Path(self.library.conform.directory),
**kargs
)
return Template(template).format(params).resolve()

View File

@ -82,8 +82,8 @@ class KitsuLibrary(AssetLibraryAdapter):
description=data['description'],
tags=[],
type=self.data_type,
image=str(self.image_template.format(name=asset_name)),
video=str(self.video_template.format(name=asset_name)),
image=str(self.template_image.format(name=asset_name)),
video=str(self.template_video.format(name=asset_name)),
name=data['name'])
]
)
@ -98,17 +98,12 @@ class KitsuLibrary(AssetLibraryAdapter):
def get_preview(self, asset_data):
name = asset_data['name']
preview = (f / image_template.format(name=name)).resolve()
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"""
print(f'Conforming {self.library.name} to {directory}')
def fetch(self):
"""Gather in a list all assets found in the folder"""

View File

@ -55,8 +55,8 @@ class ScanFolderLibrary(AssetLibraryAdapter):
metadata=dict(asset.asset_data),
tags=asset.asset_data.tags.keys(),
type=self.data_type,
image=str(self.image_template.format(name=asset_name)),
video=str(self.video_template.format(name=asset_name)),
image=str(self.template_image.format(name=asset_name)),
video=str(self.template_video.format(name=asset_name)),
name=asset.name)
)
@ -178,7 +178,7 @@ class ScanFolderLibrary(AssetLibraryAdapter):
asset.asset_mark()
# Load external preview if exists
#image_template = Template(asset_data['preview'])
#template_image = Template(asset_data['preview'])
image_path = Path(asset_data['image'])
if not image_path.is_absolute():
image_path = Path(asset_data['filepath'], image_path)
@ -242,7 +242,7 @@ class ScanFolderLibrary(AssetLibraryAdapter):
def get_preview(self, asset_data):
name = asset_data['name']
preview = (f / image_template.format(name=name)).resolve()
preview = (f / template_image.format(name=name)).resolve()
if not preview.exists():
preview_blend_file(f, preview)
@ -264,8 +264,8 @@ class ScanFolderLibrary(AssetLibraryAdapter):
catalog_ids = {v['id']: {'path': k, 'name': v['name']} for k,v in catalog_data.items()}
directory = Path(directory).resolve()
image_template = templates.get('image') or self.image_template
video_template = templates.get('video') or self.video_template
template_image = templates.get('image') or self.template_image
template_video = templates.get('video') or self.template_video
# Get list of all modifications
for blend_file in self._find_blend_files():
@ -295,12 +295,12 @@ class ScanFolderLibrary(AssetLibraryAdapter):
asset_path = self.get_asset_path(name=asset.name, catalog=catalog_path, directory=directory)
asset_description = self.get_asset_description(asset, catalog=catalog_path, modified=modified)
self.write_asset_description(asset_description, asset_path)
self.write_description_file(asset_description, asset_path)
#Write blend file containing only one asset
self.write_asset(asset=asset, asset_path=asset_path)
# Copy image if source image found else write the asset preview
src_image_path = self.get_path('image', name=asset.name, asset_path=blend_file, template=image_template)
src_image_path = self.get_path('image', name=asset.name, asset_path=blend_file, template=template_image)
dst_image_path = self.get_path('image', name=asset.name, asset_path=asset_path)
if src_image_path.exists():
@ -309,7 +309,7 @@ class ScanFolderLibrary(AssetLibraryAdapter):
self.write_preview(asset.preview, dst_image_path)
# Copy video if source video found
src_video_path = self.get_path('video', name=asset.name, asset_path=blend_file, template=video_template)
src_video_path = self.get_path('video', name=asset.name, asset_path=blend_file, template=template_video)
#print('src_video_path', src_video_path)
if src_video_path.exists():
@ -364,7 +364,7 @@ class ScanFolderLibrary(AssetLibraryAdapter):
if not field_data:
raise Exception()
#asset_data = (blend_file / prefs.asset_description_template.format(name=name)).resolve()
#asset_data = (blend_file / prefs.template_description.format(name=name)).resolve()
catalogs = [v for k,v in sorted(field_data.items()) if k.isdigit()]
catalogs = [c.replace('_', ' ').title() for c in catalogs]
@ -387,7 +387,7 @@ class ScanFolderLibrary(AssetLibraryAdapter):
continue
#First Check if there is a asset_data .json
asset_description = self.read_asset_description(blend_file)
asset_description = self.read_asset_description_file(blend_file)
if not asset_description:
# Scan the blend file for assets inside and write a custom asset description for info found

BIN
collection/preview.blend Normal file

Binary file not shown.

View File

@ -332,6 +332,9 @@ def load_datablocks(src, names=None, type='objects', link=True, expr=None) -> li
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]

View File

@ -13,6 +13,17 @@ import importlib
import sys
import shutil
import contextlib
@contextlib.contextmanager
def cd(path):
"""Changes working directory and returns to previous on exit."""
prev_cwd = Path.cwd()
os.chdir(path)
try:
yield
finally:
os.chdir(prev_cwd)
def install_module(module_name, package_name=None):
'''Install a python module with pip or return it if already installed'''

View File

@ -181,6 +181,7 @@ def get_asset_source(replace_local=False):
return source_path
'''
def get_catalog_path(filepath=None):
filepath = filepath or bpy.data.filepath
filepath = Path(filepath)
@ -195,7 +196,7 @@ def get_catalog_path(filepath=None):
catalog.touch(exist_ok=False)
return catalog
'''
# def read_catalog(path, key='path'):
# cat_data = {}
@ -219,7 +220,7 @@ def get_catalog_path(filepath=None):
# cat_data[cat_name] = {'id':cat_id, 'path':cat_path}
# return cat_data
"""
def read_catalog(path):
cat_data = {}
@ -299,6 +300,7 @@ def create_catalog_file(json_path : str|Path, keep_existing_category : bool = Tr
print(f'Catalog saved at: {catalog_path}')
return
"""
def clear_env_libraries():
print('clear_env_libraries')

View File

@ -54,14 +54,12 @@ class Template:
def format(self, data=None, **kargs):
#print('format', self.template, data, kargs)
data = {**(data or {}), **kargs}
try:
path = self.template.format(**data)
except KeyError:
print(f'Cannot format {self.template} with {data}')
except KeyError as e:
print(f'Cannot format {self.template} with {data}, field {e} is missing')
return
path = os.path.expandvars(path)

View File

@ -13,4 +13,5 @@ ASSETLIB_FILENAME = "blender_assets.libs.json"
MODULE_DIR = Path(__file__).parent
RESOURCES_DIR = MODULE_DIR / 'resources'
ADAPTER_DIR = MODULE_DIR / 'adapters'
ADAPTERS = []
ADAPTERS = []
PREVIEW_ASSETS_SCRIPT = MODULE_DIR / 'common' / 'preview_assets.py'

View File

@ -17,7 +17,7 @@ command, write_catalog)
@command
def bundle_library(source_directory, bundle_directory, asset_description_template, thumbnail_template,
def bundle_library(source_directory, bundle_directory, template_description, thumbnail_template,
template=None, data_file=None):
field_pattern = r'{(\w+)}'
@ -38,7 +38,7 @@ def bundle_library(source_directory, bundle_directory, asset_description_templat
name = field_data.get('name', f.stem)
thumbnail = (f / thumbnail_template.format(name=name)).resolve()
asset_data = (f / asset_description_template.format(name=name)).resolve()
asset_data = (f / template_description.format(name=name)).resolve()
catalogs = sorted([v for k,v in sorted(field_data.items()) if k.isdigit()])
catalogs = [c.replace('_', ' ').title() for c in catalogs]
@ -163,7 +163,7 @@ if __name__ == '__main__' :
bundle_library(
source_directory=args.source_directory,
bundle_directory=args.bundle_directory,
asset_description_template=args.asset_description_template,
template_description=args.template_description,
thumbnail_template=args.thumbnail_template,
template=args.template,
data_file=args.data_file)

View File

@ -112,8 +112,8 @@ class ASSETLIB_OT_edit_data(Operator):
new_video_path = lib.adapter.get_path('video', new_name, new_asset_path)
self.old_video_path.rename(new_video_path)
if self.old_asset_description_path.exists():
self.old_asset_description_path.unlink()
if self.old_description_path.exists():
self.old_description_path.unlink()
new_asset_description = lib.adapter.get_asset_description(
asset=self.asset,
@ -121,7 +121,7 @@ class ASSETLIB_OT_edit_data(Operator):
modified=time.time_ns()
)
lib.adapter.write_asset_description(new_asset_description, new_asset_path)
lib.adapter.write_description_file(new_asset_description, new_asset_path)
if not list(self.old_asset_path.parent.iterdir()):
self.old_asset_path.parent.rmdir()
@ -187,9 +187,9 @@ class ASSETLIB_OT_edit_data(Operator):
self.old_image_path = lib.adapter.get_path('image', self.old_asset_name, self.old_asset_path)
self.old_video_path = lib.adapter.get_path('video', self.old_asset_name, self.old_asset_path)
self.old_asset_description_path = lib.adapter.get_asset_description_path(self.old_asset_path)
self.old_description_path = lib.adapter.get_description_path(self.old_asset_path)
self.old_asset_description = lib.adapter.read_asset_description(self.old_asset_path)
self.old_asset_description = lib.adapter.read_asset_description_file(self.old_asset_path)
self.old_asset_description = lib.adapter.norm_asset_datas([self.old_asset_description])[0]
@ -408,8 +408,8 @@ class ASSETLIB_OT_conform_library(Operator):
bl_description = "Split each assets per blend and externalize preview"
name : StringProperty()
image_template : StringProperty()
video_template : StringProperty()
template_image : StringProperty()
template_video : StringProperty()
directory : StringProperty(subtype='DIR_PATH', name='Filepath')
def execute(self, context: Context) -> Set[str]:
@ -419,13 +419,13 @@ class ASSETLIB_OT_conform_library(Operator):
#lib.adapter.conform(self.directory)
templates = {}
if self.image_template:
templates['image'] = self.image_template
if self.video_template:
templates['video'] = self.video_template
if self.template_image:
templates['image'] = self.template_image
if self.template_video:
templates['video'] = self.template_video
script_path = Path(gettempdir()) / 'bundle_library.py'
script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
script_code = dedent(f"""
import bpy
prefs = bpy.context.preferences.addons["asset_library"].preferences
@ -447,6 +447,58 @@ class ASSETLIB_OT_conform_library(Operator):
return {'RUNNING_MODAL'}
class ASSETLIB_OT_generate_previews(Operator):
bl_idname = "assetlib.generate_previews"
bl_options = {"REGISTER", "UNDO"}
bl_label = "Generate Previews"
bl_description = "Generate and write the image for assets"
diff : StringProperty()
preview_blend : StringProperty()
name : StringProperty()
blocking : BoolProperty(default=True)
def execute(self, context: Context) -> Set[str]:
prefs = get_addon_prefs()
lib = prefs.libraries.get(self.name)
# self.write_file(self.diff_file, self.diff)
# preview_assets = [(a.asset_data['filepath'], self.data_types, a.name) for a in assets]
# self.preview_assets_file.write_text(json.dumps(preview_assets), encoding='utf-8')
# cmd = [
# bpy.app.binary_path, '-b', '--use-system-env',
# '--python', str(PREVIEW_ASSETS_SCRIPT), '--',
# '--preview-blend', str(self.preview_blend),
# '--preview-assets-file', str(self.preview_assets_file)
# ]
# subprocess.call(cmd)
preview_blend = self.preview_blend or lib.adapter.preview_blend
script_path = Path(bpy.app.tempdir) / 'generate_previews.py'
script_code = dedent(f"""
import bpy
prefs = bpy.context.preferences.addons["asset_library"].preferences
lib = prefs.env_libraries.add()
lib.set_dict({lib.to_dict()})
bpy.ops.wm.open_mainfile(filepath='{preview_blend}', load_ui=True)
lib.conform.adapter.generate_previews()
""")
script_path.write_text(script_code)
cmd = get_bl_cmd(script=str(script_path), background=True)
if self.blocking:
subprocess.call(cmd)
else:
subprocess.Popen(cmd)
return {'FINISHED'}
class ASSETLIB_OT_play_preview(Operator):
bl_idname = "assetlib.play_preview"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
@ -553,6 +605,7 @@ classes = (
ASSETLIB_OT_add_user_library,
ASSETLIB_OT_remove_user_library,
ASSETLIB_OT_diff,
ASSETLIB_OT_generate_previews,
ASSETLIB_OT_bundle_library,
ASSETLIB_OT_clear_asset,
ASSETLIB_OT_edit_data,

View File

@ -12,7 +12,7 @@ from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS,
from asset_library.common.file_utils import import_module_from_path, norm_str
from asset_library.common.bl_utils import get_addon_prefs
from asset_library.common.functions import get_catalog_path
#from asset_library.common.functions import get_catalog_path
from pathlib import Path
import importlib
@ -88,14 +88,16 @@ class ConformAssetLibrary(PropertyGroup):
adapters : bpy.props.PointerProperty(type=AssetLibraryAdapters)
adapter_name : EnumProperty(items=get_adapter_items)
directory : StringProperty(
name="Destination Directory",
name="Target Directory",
subtype='DIR_PATH',
default=''
)
image_template : StringProperty()
video_template : StringProperty()
externalize_data: BoolProperty(default=False, name='Externalize Data')
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')
#externalize_data: BoolProperty(default=False, name='Externalize Data')
blend_depth: IntProperty(default=1, name='Blend Depth')
@property
@ -228,23 +230,23 @@ class AssetLibrary(PropertyGroup):
return self.name
@property
def image_template(self):
def template_image(self):
prefs = get_addon_prefs()
return prefs.image_template
return prefs.template_image
@property
def video_template(self):
def template_video(self):
prefs = get_addon_prefs()
return prefs.video_template
return prefs.template_video
@property
def asset_description_template(self):
def template_description(self):
prefs = get_addon_prefs()
return prefs.asset_description_template
return prefs.template_description
@property
def catalog_path(self):
return get_catalog_path(self.library_path)
#@property
#def catalog_path(self):
# return get_catalog_path(self.library_path)
@property
def options(self):
@ -376,7 +378,7 @@ class AssetLibrary(PropertyGroup):
if not self.use:
if all(not l.use for l in self.merge_libraries):
self.clear_library_path()
return
return
# Create the Asset Library Path
if not lib:
@ -470,6 +472,10 @@ class AssetLibrary(PropertyGroup):
op.name = self.name
op.conform = True
op = subrow.operator('assetlib.generate_previews', text='', icon='SEQ_PREVIEW')#, icon='MOD_BUILD'
op.name = self.name
#op.conform = True
op = subrow.operator('assetlib.bundle', text='', icon='MOD_BUILD')#, icon='MOD_BUILD'
op.name = self.name
op.directory = self.conform.directory
@ -479,17 +485,22 @@ class AssetLibrary(PropertyGroup):
#subrow.separator(factor=3)
if self.expand_extra and self.conform.adapter:
col.separator()
self.conform.adapter.draw_prefs(col)
col.separator()
col.separator()
#row = layout.row(align=True)
#row.label(text='Conform Library')
col.prop(self.conform, "directory")
col.prop(self.conform, "blend_depth")
col.prop(self.conform, "externalize_data")
col.prop(self.conform, "image_template", text='Image Template')
col.prop(self.conform, "video_template", text='Video Template')
#col.prop(self.conform, "externalize_data")
subcol = col.column(align=True)
subcol.prop(self.conform, "template_description", text='Template Description', icon='COPY_ID')
subcol.prop(self.conform, "template_image", text='Template Image', icon='COPY_ID')
subcol.prop(self.conform, "template_video", text='Template Video', icon='COPY_ID')
col.separator()
self.conform.adapter.draw_prefs(col)
col.separator()
@ -651,10 +662,10 @@ class AssetLibraryPrefs(AddonPreferences):
update=update_all_library_path
)
use_single_path : BoolProperty(default=True)
asset_description_template : StringProperty(default='../{name}_asset_description.json')
image_template : StringProperty(default='../{name}_image.png')
video_template : StringProperty(default='../{name}_video.mov')
#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",
@ -745,16 +756,16 @@ class AssetLibraryPrefs(AddonPreferences):
col.separator()
col.prop(self, 'asset_description_template', text='Asset Description Template', icon='COPY_ID')
#col.prop(self, 'template_description', text='Asset Description Template', icon='COPY_ID')
col.separator()
#col.separator()
col.prop(self, 'image_template', text='Image Template', icon='COPY_ID')
#col.prop(self, 'template_image', text='Template Image', icon='COPY_ID')
col.prop(self, 'image_player', text='Image Player') #icon='OUTLINER_OB_IMAGE'
col.separator()
#col.separator()
col.prop(self, 'video_template', text='Video Template', icon='COPY_ID')
#col.prop(self, 'template_video', text='Template Video', icon='COPY_ID')
col.prop(self, 'video_player', text='Video Player') #icon='FILE_MOVIE'
col.separator()