270 lines
8.7 KiB
Python
270 lines
8.7 KiB
Python
|
|
||
|
"""
|
||
|
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
|