2022-12-24 15:30:32 +01:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
Pose Library - operators.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from math import radians
|
|
|
|
from mathutils import Vector
|
|
|
|
from pathlib import Path
|
|
|
|
from tempfile import gettempdir
|
|
|
|
from typing import Optional, Set
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import shutil
|
|
|
|
import subprocess
|
|
|
|
import uuid
|
|
|
|
import time
|
|
|
|
from pathlib import Path
|
|
|
|
from functools import partial
|
|
|
|
|
|
|
|
|
|
|
|
from asset_library.pose.pose_creation import(
|
|
|
|
create_pose_asset_from_context,
|
|
|
|
assign_from_asset_browser
|
|
|
|
)
|
|
|
|
|
|
|
|
from asset_library.pose.pose_usage import(
|
|
|
|
select_bones,
|
|
|
|
flip_side_name
|
|
|
|
)
|
|
|
|
|
|
|
|
from asset_library.action.functions import(
|
|
|
|
apply_anim,
|
|
|
|
append_action,
|
|
|
|
clean_action,
|
|
|
|
reset_bone,
|
|
|
|
is_asset_action,
|
|
|
|
conform_action
|
|
|
|
)
|
|
|
|
|
|
|
|
from bpy.props import (
|
|
|
|
BoolProperty,
|
|
|
|
CollectionProperty,
|
|
|
|
EnumProperty,
|
|
|
|
PointerProperty,
|
|
|
|
StringProperty,
|
|
|
|
IntProperty
|
|
|
|
)
|
|
|
|
|
|
|
|
from bpy.types import (
|
|
|
|
Action,
|
|
|
|
Context,
|
|
|
|
Event,
|
|
|
|
FileSelectEntry,
|
|
|
|
Object,
|
|
|
|
Operator,
|
|
|
|
PropertyGroup,
|
|
|
|
)
|
|
|
|
|
|
|
|
from bpy_extras import asset_utils
|
|
|
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
|
|
|
|
|
|
|
from asset_library.action.functions import (
|
|
|
|
is_pose,
|
|
|
|
get_marker,
|
|
|
|
get_keyframes,
|
|
|
|
)
|
|
|
|
|
|
|
|
from asset_library.common.functions import (
|
|
|
|
#get_actionlib_dir,
|
|
|
|
get_asset_source,
|
|
|
|
get_catalog_path,
|
|
|
|
read_catalog,
|
|
|
|
#set_actionlib_dir,
|
|
|
|
resync_lib,
|
|
|
|
get_active_library,
|
|
|
|
get_active_catalog,
|
|
|
|
asset_warning_callback
|
|
|
|
)
|
|
|
|
|
|
|
|
from asset_library.common.bl_utils import (
|
|
|
|
area_from_context,
|
|
|
|
attr_set,
|
|
|
|
get_addon_prefs,
|
|
|
|
copy_frames,
|
|
|
|
split_path,
|
|
|
|
get_preview,
|
|
|
|
get_view3d_persp,
|
|
|
|
load_assets_from,
|
|
|
|
get_asset_space_params,
|
|
|
|
get_bl_cmd,
|
|
|
|
get_overriden_col
|
|
|
|
)
|
|
|
|
|
|
|
|
from asset_library.common.file_utils import (
|
|
|
|
remove_version,
|
|
|
|
synchronize,
|
|
|
|
open_file,
|
|
|
|
copy_dir,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_restore_previous_action(Operator):
|
|
|
|
bl_idname = "actionlib.restore_previous_action"
|
|
|
|
bl_label = "Restore Previous Action"
|
|
|
|
bl_description = "Switch back to the previous Action, after creating a pose asset"
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
return bool(
|
|
|
|
context.scene.actionlib.previous_action
|
|
|
|
and context.object
|
|
|
|
and context.object.animation_data
|
|
|
|
and context.object.animation_data.action
|
|
|
|
and context.object.animation_data.action.asset_data is not None
|
|
|
|
)
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
# This is the Action that was just created with "Create Pose Asset".
|
|
|
|
# It has to be re-applied after switching to the previous action,
|
|
|
|
# to ensure the character keeps the same pose.
|
|
|
|
self.pose_action = context.object.animation_data.action
|
|
|
|
|
|
|
|
prev_action = context.scene.actionlib.previous_action
|
|
|
|
context.object.animation_data.action = prev_action
|
|
|
|
context.scene.actionlib.previous_action = None
|
|
|
|
|
|
|
|
# Wait a bit for the action assignment to be handled, before applying the pose.
|
|
|
|
wm = context.window_manager
|
|
|
|
self._timer = wm.event_timer_add(0.001, window=context.window)
|
|
|
|
wm.modal_handler_add(self)
|
|
|
|
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
|
|
def modal(self, context, event):
|
|
|
|
if event.type != 'TIMER':
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
|
|
wm = context.window_manager
|
|
|
|
wm.event_timer_remove(self._timer)
|
|
|
|
|
|
|
|
context.object.pose.apply_pose_from_action(self.pose_action)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_assign_action(Operator):
|
|
|
|
bl_idname = "actionlib.assign_action"
|
|
|
|
bl_label = "Assign Action"
|
|
|
|
bl_description = "Set this pose Action as active Action on the active Object"
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
assign: BoolProperty(name="Assign", default=True) # type: ignore
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
if self.assign:
|
|
|
|
context.object.animation_data_create().action = context.id
|
|
|
|
else:
|
|
|
|
if context.object.animation_data.action:
|
|
|
|
context.object.animation_data.action = None
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_replace_pose(Operator):
|
|
|
|
bl_idname = "actionlib.replace_pose"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Update Pose'
|
|
|
|
bl_description = 'Update selected Pose. ! Works only on Pose, not Anim !'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
# wm = context.window_manager
|
|
|
|
# if context.active_file and wm.edit_pose:
|
|
|
|
# if context.active_file.name == wm.edit_pose_action:
|
|
|
|
# return True
|
|
|
|
# else:
|
|
|
|
# cls.poll_message_set(f"Current Action {context.id.name} different than Edit Action {wm.edit_pose_action}")
|
|
|
|
# return False
|
|
|
|
if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
wm = context.window_manager
|
|
|
|
active = context.active_file
|
|
|
|
#print('active: ', active)
|
|
|
|
|
|
|
|
select_bones(
|
|
|
|
context.object,
|
|
|
|
context.asset_file_handle.local_id,
|
|
|
|
selected_side='BOTH',
|
|
|
|
toggle=False)
|
|
|
|
|
|
|
|
data = {
|
|
|
|
'name':active.name,
|
|
|
|
'catalog_id':active.asset_data.catalog_id,
|
|
|
|
}
|
|
|
|
data.update(dict(active.asset_data))
|
|
|
|
|
|
|
|
# if 'camera' in asset_data.keys():
|
|
|
|
# data.update({'camera':active.asset_data['camera']})
|
|
|
|
# if 'is_single_frame' in asset_data.keys():
|
|
|
|
# data.update({'is_single_frame' : active.asset_data['is_single_frame']})
|
|
|
|
|
|
|
|
#print('data: ', data)
|
|
|
|
|
|
|
|
action = create_pose_asset_from_context(
|
|
|
|
context,
|
|
|
|
data['name'],
|
|
|
|
)
|
|
|
|
if not action:
|
|
|
|
self.report( # type: ignore
|
|
|
|
{"ERROR"},
|
|
|
|
f"Can't Update Current Pose",
|
|
|
|
)
|
|
|
|
return {"CANCELLED"}
|
|
|
|
|
|
|
|
bpy.ops.asset.clear()
|
|
|
|
_old_action = bpy.data.actions[active.name]
|
|
|
|
_old_action.use_fake_user = False
|
|
|
|
bpy.data.actions.remove(_old_action)
|
|
|
|
|
|
|
|
action.name = data['name']
|
|
|
|
action.asset_data.catalog_id = data['catalog_id']
|
|
|
|
|
|
|
|
for k, v in data.items():
|
|
|
|
if k in ('camera', 'is_single_frame'):
|
|
|
|
action.asset_data[k] = v
|
|
|
|
|
|
|
|
if not is_pose(action) and 'pose' in action.asset_data.tags.keys() and 'anim' not in action.asset_data.tags.keys():
|
|
|
|
for tag in action.asset_data.tags:
|
|
|
|
if tag != 'pose':
|
|
|
|
continue
|
|
|
|
action.asset_data.tags.remove(tag)
|
|
|
|
action.asset_data.tags.new('anim')
|
|
|
|
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_apply_anim(Operator):
|
|
|
|
bl_idname = "actionlib.apply_anim"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Apply Anim'
|
|
|
|
bl_description = 'Apply selected Anim to selected bones'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
return context.mode == 'POSE'
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
ob = context.object
|
|
|
|
active_action = context.active_file
|
|
|
|
to_remove = False
|
|
|
|
|
|
|
|
prefs = get_addon_prefs()
|
|
|
|
#params = get_asset_space_params(context.area)
|
|
|
|
asset_library_ref = context.asset_library_ref
|
|
|
|
|
|
|
|
if asset_library_ref == 'LOCAL':
|
|
|
|
action = bpy.data.actions[active_action.name]
|
|
|
|
to_remove = False
|
|
|
|
else:
|
|
|
|
asset_file_handle = bpy.context.asset_file_handle
|
|
|
|
if asset_file_handle is None:
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
if asset_file_handle.local_id:
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
|
|
lib = get_active_library()
|
|
|
|
if 'filepath' in asset_file_handle.asset_data:
|
|
|
|
action_path = asset_file_handle.asset_data['filepath']
|
|
|
|
action_path = lib.adapter.format_path(action_path)
|
|
|
|
else:
|
|
|
|
action_path = bpy.types.AssetHandle.get_full_library_path(
|
|
|
|
asset_file_handle, asset_library_ref
|
|
|
|
)
|
|
|
|
|
|
|
|
if action_path and Path(action_path).exists():
|
|
|
|
action = append_action(
|
|
|
|
action_path=action_path,
|
|
|
|
action_name=active_action.name,
|
|
|
|
)
|
|
|
|
to_remove = True
|
|
|
|
else:
|
|
|
|
self.report({"WARNING"}, f"Could not load action path {action_path} not exist")
|
|
|
|
return {"CANCELLED"}
|
|
|
|
|
|
|
|
bones = [b.name for b in context.selected_pose_bones_from_active_object]
|
|
|
|
apply_anim(action, context.active_object, bones=bones)
|
|
|
|
if to_remove:
|
|
|
|
bpy.data.actions.remove(action)
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_publish(Operator):
|
|
|
|
bl_idname = "actionlib.publish"
|
|
|
|
bl_label = "Publish Library"
|
|
|
|
bl_description = "Publish Pose Lib"
|
|
|
|
|
|
|
|
#ExportHelper mixin class uses this
|
|
|
|
filename_ext = ""
|
|
|
|
use_filter_folder = True
|
|
|
|
check_extension = None
|
|
|
|
# filter_glob = StringProperty(
|
|
|
|
# default="*.blend",
|
|
|
|
# )
|
|
|
|
|
|
|
|
filepath: StringProperty(
|
|
|
|
name="File Path",
|
|
|
|
description="Filepath used for exporting the file",
|
|
|
|
maxlen=1024,
|
|
|
|
subtype='DIR_PATH',
|
|
|
|
)
|
|
|
|
selected_actions : CollectionProperty(type=PropertyGroup)
|
|
|
|
render_preview : BoolProperty(default=False)
|
|
|
|
clean : BoolProperty(default=False)
|
|
|
|
|
|
|
|
def invoke(self, context, _event):
|
|
|
|
scn = context.scene
|
|
|
|
|
|
|
|
filename = remove_version(bpy.data.filepath)
|
|
|
|
filename = re.sub('_actionlib', '', filename)
|
|
|
|
|
|
|
|
if scn.actionlib.publish_path:
|
|
|
|
action_dir = Path(os.path.expandvars(scn.actionlib.publish_path))
|
|
|
|
if action_dir.is_file():
|
|
|
|
action_dir = action_dir.parent
|
|
|
|
self.filepath = str(action_dir)
|
|
|
|
else:
|
|
|
|
self.filepath = str(Path(get_actionlib_dir()) / Path(filename).stem)
|
|
|
|
|
|
|
|
context.window_manager.fileselect_add(self)
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
scn = context.scene
|
|
|
|
scn.actionlib.publish_path = self.filepath.replace(
|
|
|
|
get_actionlib_dir(), '$ACTIONLIB_DIR'
|
|
|
|
)
|
|
|
|
|
|
|
|
action_dir = Path(self.filepath)
|
|
|
|
|
|
|
|
try:
|
|
|
|
bpy.ops.asset.catalogs_save()
|
|
|
|
except RuntimeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
actionlib_dir = get_actionlib_dir()
|
|
|
|
|
|
|
|
current_catalog = get_catalog_path()
|
|
|
|
current_catalog_data = read_catalog(current_catalog)
|
|
|
|
|
|
|
|
catalog_global = get_catalog_path(actionlib_dir)
|
|
|
|
catalog_data = read_catalog(catalog_global)
|
|
|
|
|
|
|
|
for d in catalog_global.parent.rglob('*'):
|
|
|
|
if not d.is_dir():
|
|
|
|
continue
|
|
|
|
|
|
|
|
reldir = d.relative_to(actionlib_dir)
|
|
|
|
|
|
|
|
if reldir not in catalog_data:
|
|
|
|
catalog_data[reldir.as_posix()] = {
|
|
|
|
'id':str(uuid.uuid4()),
|
|
|
|
'name':'-'.join(reldir.parts)
|
|
|
|
}
|
|
|
|
|
|
|
|
catalog_path = action_dir.relative_to(actionlib_dir)
|
|
|
|
for k, v in current_catalog_data.items():
|
|
|
|
v['name'] = '-'.join([*catalog_path.parts, v['name']])
|
|
|
|
catalog_data[(catalog_path / k).as_posix()] = v
|
|
|
|
|
|
|
|
catalog_global_lines = ['VERSION 1', '']
|
|
|
|
for k, v in sorted(catalog_data.items()):
|
|
|
|
if any(i in k for i in ('anim', 'pose', 'preview')):
|
|
|
|
continue
|
|
|
|
catalog_global_lines.append(':'.join([v['id'], k, v['name']]))
|
|
|
|
|
|
|
|
catalog_global.write_text('\n'.join(catalog_global_lines))
|
|
|
|
|
|
|
|
if not self.selected_actions:
|
|
|
|
render_actions_name = [a.name for a in bpy.data.actions if is_asset_action(a)]
|
|
|
|
else:
|
|
|
|
render_actions_name = [a.name for a in self.selected_actions]
|
|
|
|
|
|
|
|
publish_actions_name = []
|
|
|
|
publish_actions = {}
|
|
|
|
for a in bpy.data.actions:
|
|
|
|
if is_asset_action(a):
|
|
|
|
if not a.asset_data.catalog_id in publish_actions:
|
|
|
|
publish_actions[a.asset_data.catalog_id] = set()
|
|
|
|
|
|
|
|
publish_actions[a.asset_data.catalog_id].add(a)
|
|
|
|
publish_actions_name.append(a.name)
|
|
|
|
|
|
|
|
for cat_id, actions in publish_actions.items():
|
|
|
|
# Cleanup actions
|
|
|
|
store_actions = actions.copy()
|
|
|
|
for a in actions:
|
|
|
|
conform_action(a)
|
|
|
|
|
|
|
|
action_rel_path = next(k for k, v in current_catalog_data.items() if v['id']==cat_id)
|
|
|
|
action_path = action_dir / action_rel_path
|
|
|
|
action_path = action_path.parent / f'{action_dir.name}_{action_path.name}.blend'
|
|
|
|
|
|
|
|
action_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
print(f'Saving File to: {action_path}')
|
|
|
|
bpy.data.libraries.write(str(action_path), store_actions, compress=True)
|
|
|
|
|
|
|
|
# Render Previews
|
|
|
|
_tmp_filepath = Path(gettempdir()) / Path(bpy.data.filepath).name
|
|
|
|
print('_tmp_filepath: ', _tmp_filepath)
|
|
|
|
|
|
|
|
bpy.ops.wm.save_as_mainfile(filepath=str(_tmp_filepath), copy=True, compress=True)
|
|
|
|
|
|
|
|
if self.render_preview:
|
|
|
|
print('[>-] Rendering Preview..')
|
|
|
|
cmd = [
|
|
|
|
bpy.app.binary_path,
|
|
|
|
'--no-window-focus',
|
|
|
|
'--window-geometry', '5000', '0', '10', '10',
|
|
|
|
str(_tmp_filepath),
|
|
|
|
'--python-use-system-env',
|
|
|
|
'--python', str(Path(__file__).parent / 'render_preview.py'),
|
|
|
|
'--',
|
|
|
|
'--directory', str(action_dir),
|
|
|
|
'--asset-catalog', str(current_catalog),
|
|
|
|
'--render-actions', *render_actions_name,
|
|
|
|
'--publish-actions', *publish_actions_name,
|
|
|
|
'--remove-folder', json.dumps(self.clean)
|
|
|
|
]
|
|
|
|
|
|
|
|
print('cmd: ', cmd)
|
|
|
|
subprocess.Popen(cmd)
|
|
|
|
|
|
|
|
print('[>-] Publish Done.')
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_create_anim_asset(Operator):
|
|
|
|
bl_idname = "actionlib.create_anim_asset"
|
|
|
|
bl_label = "Create Anim Asset"
|
|
|
|
bl_description = (
|
|
|
|
"Create a new Action that contains the anim of the selected bones, and mark it as Asset. "
|
|
|
|
"The asset will be stored in the current blend file"
|
|
|
|
)
|
|
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if not context.object:
|
|
|
|
return
|
|
|
|
if not context.object.animation_data:
|
|
|
|
cls.poll_message_set("Object has no Keyframes")
|
|
|
|
return
|
|
|
|
if not context.object.animation_data.action:
|
|
|
|
cls.poll_message_set("Object has no Action")
|
|
|
|
return
|
|
|
|
|
|
|
|
# Make sure that if there is an asset browser open, the artist can see the newly created pose asset.
|
|
|
|
asset_browse_area: Optional[bpy.types.Area] = area_from_context(context)
|
|
|
|
if not asset_browse_area:
|
|
|
|
# No asset browser is visible, so there also aren't any expectations
|
|
|
|
# that this asset will be visible.
|
|
|
|
return True
|
|
|
|
|
|
|
|
params = get_asset_space_params(asset_browse_area)
|
|
|
|
if params.asset_library_ref != 'LOCAL':
|
|
|
|
cls.poll_message_set("Asset Browser must be set to the Current File library")
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
### ADD ADM
|
|
|
|
action = context.object.animation_data.action
|
|
|
|
action.asset_mark()
|
|
|
|
action.asset_generate_preview()
|
|
|
|
|
|
|
|
data = action.asset_data
|
|
|
|
#data.catalog_id = str(uuid.UUID(int=0))
|
|
|
|
asset_browse_area: Optional[bpy.types.Area] = area_from_context(context)
|
|
|
|
asset_space_params = params(asset_browse_area)
|
|
|
|
|
|
|
|
data.catalog_id = asset_space_params.catalog_id
|
|
|
|
|
|
|
|
data_dict = dict(
|
|
|
|
is_single_frame=False,
|
|
|
|
camera= context.scene.camera.name if context.scene.camera else '',
|
|
|
|
)
|
|
|
|
data.tags.new('anim')
|
|
|
|
|
|
|
|
for k, v in data_dict.items():
|
|
|
|
data[k] = v
|
|
|
|
###
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_apply_selected_action(Operator):
|
|
|
|
bl_idname = "actionlib.apply_selected_action"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Apply Pose/Anim'
|
|
|
|
bl_description = 'Apply selected Action to selected bones'
|
|
|
|
|
|
|
|
flipped: BoolProperty(name="Flipped", default=False) # type: ignore
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.mode == 'POSE' and context.area.ui_type == 'ASSETS':
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
active_action = context.active_file
|
|
|
|
|
|
|
|
if 'pose' in active_action.asset_data.tags.keys():
|
|
|
|
bpy.ops.poselib.apply_pose_asset_for_keymap(flipped=self.flipped)
|
|
|
|
else:
|
|
|
|
bpy.ops.actionlib.apply_anim()
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_edit_action(Operator):
|
|
|
|
bl_idname = "actionlib.edit_action"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Edit Action'
|
|
|
|
bl_description = 'Assign active action and set Camera'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
scn = context.scene
|
|
|
|
rest_pose = bpy.data.actions.get(context.id.asset_data.get('rest_pose', ''))
|
|
|
|
|
|
|
|
context.object.animation_data_create()
|
|
|
|
keyframes = get_keyframes(context.id)
|
|
|
|
if not keyframes:
|
|
|
|
self.report({'ERROR'}, f'No Keyframes found for {context.id.name}.')
|
|
|
|
return
|
|
|
|
scn.frame_set(keyframes[0])
|
|
|
|
|
|
|
|
context.object.animation_data.action = None
|
|
|
|
|
|
|
|
for b in context.object.pose.bones:
|
|
|
|
if re.match('^[A-Z]+\.', b.name):
|
|
|
|
continue
|
|
|
|
reset_bone(b)
|
|
|
|
|
|
|
|
context.object.animation_data.action = rest_pose
|
|
|
|
context.view_layer.update()
|
|
|
|
context.object.animation_data.action = context.id
|
|
|
|
|
|
|
|
if 'camera' in context.id.asset_data.keys():
|
|
|
|
scn.camera = bpy.data.objects[context.id.asset_data['camera']]
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_clear_action(Operator):
|
|
|
|
bl_idname = "actionlib.clear_action"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Clear Action'
|
|
|
|
bl_description = 'Assign active action and set Camera'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
wm = context.window_manager
|
|
|
|
|
|
|
|
context.object.animation_data_create()
|
|
|
|
context.object.animation_data.action = None
|
|
|
|
context.id.asset_generate_preview()
|
|
|
|
|
|
|
|
for a in context.screen.areas:
|
|
|
|
if a.type == 'DOPESHEET_EDITOR' and a.ui_type == 'DOPESHEET':
|
|
|
|
a.tag_redraw()
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_generate_preview(Operator):
|
|
|
|
bl_idname = "actionlib.generate_preview"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Generate Preview'
|
|
|
|
bl_description = 'Genreate Preview for Active Action'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.object.type == 'ARMATURE' and context.mode == 'POSE':
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
_action = context.object.animation_data.action
|
|
|
|
_camera = context.scene.camera
|
|
|
|
|
|
|
|
context.object.animation_data_create()
|
|
|
|
actions = context.selected_files + [_action]
|
|
|
|
|
|
|
|
for a in context.selected_files:
|
|
|
|
rest_pose = bpy.data.actions.get(a.asset_data.get('rest_pose', ''))
|
|
|
|
if rest_pose:
|
|
|
|
context.object.animation_data.action = rest_pose
|
|
|
|
bpy.context.view_layer.update()
|
|
|
|
else:
|
|
|
|
context.object.animation_data.action = None
|
|
|
|
for b in context.object.pose.bones:
|
|
|
|
if re.match('^[A-Z]+\.', b.name):
|
|
|
|
continue
|
|
|
|
reset_bone(b)
|
|
|
|
|
|
|
|
a_cam = bpy.data.objects.get(a.asset_data['camera'])
|
|
|
|
if a_cam:
|
|
|
|
context.scene.camera = a_cam
|
|
|
|
bpy.context.view_layer.update()
|
|
|
|
|
|
|
|
a.local_id.asset_generate_preview()
|
|
|
|
|
|
|
|
context.object.animation_data.action = _action
|
|
|
|
context.scene.camera = _camera
|
|
|
|
bpy.context.view_layer.update()
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_update_action_data(Operator):
|
|
|
|
bl_idname = "actionlib.update_action_data"
|
|
|
|
bl_options = {"REGISTER", "INTERNAL"}
|
|
|
|
bl_label = 'Udpate Action Data'
|
|
|
|
bl_description = 'Update Action Metadata'
|
|
|
|
|
|
|
|
tags: EnumProperty(
|
|
|
|
name='Tags',
|
|
|
|
items=(
|
|
|
|
('POSE', "pose", ""),
|
|
|
|
('ANIM', "anim", ""),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
use_tags: BoolProperty(default=False)
|
|
|
|
use_camera: BoolProperty(default=False)
|
|
|
|
use_rest_pose: BoolProperty(default=False)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.id:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
scn = context.scene
|
|
|
|
scn.actionlib.camera = scn.camera
|
|
|
|
|
|
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
layout = self.layout
|
|
|
|
layout.use_property_split = True
|
|
|
|
|
|
|
|
scn = context.scene
|
|
|
|
|
|
|
|
heading = layout.column(align=True, heading="Tags")
|
|
|
|
row = heading.row(align=True)
|
|
|
|
row.prop(self, "use_tags", text="")
|
|
|
|
sub = row.row()
|
|
|
|
sub.active = self.use_tags
|
|
|
|
sub.prop(self, "tags", text="")
|
|
|
|
|
|
|
|
heading = layout.column(align=True, heading="Camera")
|
|
|
|
row = heading.row(align=True)
|
|
|
|
row.prop(self, "use_camera", text="")
|
|
|
|
sub = row.row()
|
|
|
|
sub.active = self.use_camera
|
|
|
|
sub.prop(scn.actionlib, "camera", text="", icon='CAMERA_DATA')
|
|
|
|
|
|
|
|
heading = layout.column(align=True, heading="Rest Pose")
|
|
|
|
row = heading.row(align=True)
|
|
|
|
row.prop(self, "use_rest_pose", text="")
|
|
|
|
sub = row.row()
|
|
|
|
sub.active = self.use_rest_pose
|
|
|
|
sub.prop(scn.actionlib, "rest_pose", text="")
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
scn = context.scene
|
|
|
|
|
|
|
|
for action in context.selected_files:
|
|
|
|
if self.use_camera:
|
|
|
|
action.asset_data['camera'] = context.scene.actionlib.camera.name
|
|
|
|
|
|
|
|
if self.use_tags:
|
|
|
|
tag = self.tags.lower()
|
|
|
|
not_tag = 'anim' if tag == 'pose' else 'pose'
|
|
|
|
|
|
|
|
if tag not in action.asset_data.tags.keys():
|
|
|
|
if not_tag in action.asset_data.tags.keys():
|
|
|
|
for t in action.asset_data.tags:
|
|
|
|
if t != not_tag:
|
|
|
|
continue
|
|
|
|
action.asset_data.tags.remove(t)
|
|
|
|
|
|
|
|
action.asset_data.tags.new(tag)
|
|
|
|
|
|
|
|
if 'pose' in action.asset_data.tags.keys():
|
|
|
|
action.asset_data['is_single_frame'] = True
|
|
|
|
else:
|
|
|
|
action.asset_data['is_single_frame'] = False
|
|
|
|
|
|
|
|
|
|
|
|
if self.use_rest_pose:
|
|
|
|
name = scn.actionlib.rest_pose.name if scn.actionlib.rest_pose else ''
|
|
|
|
action.asset_data['rest_pose'] = name
|
|
|
|
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_assign_rest_pose(Operator):
|
|
|
|
bl_idname = "actionlib.mark_as_rest_pose"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Mark As Rest Pose'
|
|
|
|
bl_description = 'Mark Pose as Rest Pose'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
if context.area.ui_type == 'ASSETS':
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
active_action = context.active_file
|
|
|
|
context.scene.actionlib.rest_pose = context.id
|
|
|
|
|
|
|
|
print(f"'{active_action.name.title()}' marked as Rest Pose.")
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_open_blendfile(Operator):
|
|
|
|
bl_idname = "actionlib.open_blendfile"
|
|
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
bl_label = 'Set Paths'
|
|
|
|
bl_description = 'Open Containing File'
|
|
|
|
|
|
|
|
replace_local : BoolProperty(default=False)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context):
|
|
|
|
if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
|
|
|
|
return False
|
|
|
|
|
|
|
|
sp = context.space_data
|
|
|
|
if sp.params.asset_library_ref == 'LOCAL':
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def execute(self, context: Context) -> Set[str]:
|
|
|
|
asset_path = get_asset_source(replace_local=self.replace_local)
|
|
|
|
|
|
|
|
cmd = get_bl_cmd(
|
|
|
|
blender=bpy.app.binary_path,
|
|
|
|
blendfile=str(asset_path),
|
|
|
|
)
|
|
|
|
subprocess.Popen(cmd)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
#LIBRARY_ITEMS = []
|
|
|
|
|
|
|
|
class ACTIONLIB_OT_store_anim_pose(Operator):
|
|
|
|
bl_idname = "actionlib.store_anim_pose"
|
|
|
|
bl_label = "Add Action to the current library"
|
|
|
|
bl_description = "Store current pose/anim to local library"
|
|
|
|
|
|
|
|
#use_new_folder: BoolProperty(default=False)
|
|
|
|
warning: StringProperty(name='')
|
|
|
|
path: StringProperty(name='Path')
|
|
|
|
catalog: StringProperty(name='Catalog', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
|
|
|
|
name: StringProperty(name='Name', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
|
|
|
|
action_type: EnumProperty(name='Type', items=[('POSE', 'Pose', ''), ('ANIMATION', 'Animation', '')])
|
|
|
|
frame_start: IntProperty(name="Frame Start")
|
|
|
|
frame_end: IntProperty(name="Frame End")
|
|
|
|
tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)')
|
|
|
|
|
|
|
|
#library: EnumProperty(items=lambda s, c: s.library_items, name="Library")
|
|
|
|
#library: EnumProperty(items=lambda s, c: LIBRARY_ITEMS, name="Library")
|
|
|
|
#CLIPBOARD_ASSET_MARKER = "ASSET-BLEND="
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def poll(cls, context: Context) -> bool:
|
|
|
|
ob = context.object
|
|
|
|
if not ob:
|
|
|
|
cls.poll_message_set(f'You have no active object')
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not ob.type == 'ARMATURE':
|
|
|
|
cls.poll_message_set(f'Active object {ob.name} is not of type ARMATURE')
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not ob.animation_data or not ob.animation_data.action:
|
|
|
|
cls.poll_message_set(f'Active object {ob.name} has no action or keyframes')
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
# def draw_tags(self, asset, layout):
|
|
|
|
# row = layout.row()
|
|
|
|
# row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset.asset_data, "tags",
|
|
|
|
# asset.asset_data, "active_tag", rows=4)
|
|
|
|
|
|
|
|
# col = row.column(align=True)
|
|
|
|
# col.operator("asset.tag_add", icon='ADD', text="")
|
|
|
|
# col.operator("asset.tag_remove", icon='REMOVE', text="")
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
layout = self.layout
|
|
|
|
layout.separator()
|
|
|
|
|
|
|
|
#row = layout.row(align=True)
|
|
|
|
layout.use_property_split = True
|
|
|
|
#layout.alignment = 'LEFT'
|
|
|
|
#layout.ui_units_x = 50
|
|
|
|
|
|
|
|
if self.current_library.merge_libraries:
|
|
|
|
layout.prop(self.current_library, 'store_library', expand=False)
|
|
|
|
|
|
|
|
#row.label(text='Action Name')
|
|
|
|
layout.prop(self, "action_type", text="Type", expand=True)
|
|
|
|
if self.action_type == 'ANIMATION':
|
|
|
|
col = layout.column(align=True)
|
|
|
|
col.prop(self, "frame_start")
|
|
|
|
col.prop(self, "frame_end", text='End')
|
|
|
|
|
|
|
|
layout.prop(self, "catalog", text="Catalog")
|
|
|
|
layout.prop(self, "name", text="Name")
|
|
|
|
|
|
|
|
#layout.separator()
|
|
|
|
#self.draw_tags(self.asset_action, layout)
|
|
|
|
layout.prop(self, 'tags')
|
|
|
|
|
|
|
|
#layout.prop()
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
col = layout.column()
|
|
|
|
col.use_property_split = False
|
|
|
|
#row.enabled = False
|
|
|
|
|
|
|
|
if self.path:
|
|
|
|
col.label(text=self.path)
|
|
|
|
|
|
|
|
if self.warning:
|
|
|
|
col.label(icon='ERROR', text=self.warning)
|
|
|
|
|
|
|
|
def set_action_type(self):
|
|
|
|
ob = bpy.context.object
|
|
|
|
action = ob.animation_data.action
|
|
|
|
|
|
|
|
bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object]
|
|
|
|
|
|
|
|
keyframes_selected = get_keyframes(action, selected=True, includes=bones)
|
|
|
|
self.frame_start = min(keyframes_selected)
|
|
|
|
self.frame_end = max(keyframes_selected)
|
|
|
|
|
|
|
|
self.action_type = 'POSE'
|
|
|
|
if (self.frame_start != self.frame_end):
|
|
|
|
self.action_type = 'ANIMATION'
|
|
|
|
|
|
|
|
def invoke(self, context, event):
|
|
|
|
prefs = get_addon_prefs()
|
|
|
|
ob = context.object
|
|
|
|
|
|
|
|
self.asset_action = ob.animation_data.action.copy()
|
|
|
|
self.asset_action.asset_mark()
|
|
|
|
self.area = context.area
|
|
|
|
self.current_library = get_active_library()
|
|
|
|
#self.sce
|
|
|
|
|
|
|
|
#lib = self.current_library
|
|
|
|
self.tags = os.getlogin()
|
|
|
|
|
|
|
|
|
|
|
|
#print(self, self.library_items)
|
|
|
|
|
|
|
|
self.catalog = get_active_catalog()
|
|
|
|
self.set_action_type()
|
|
|
|
|
|
|
|
return context.window_manager.invoke_props_dialog(self, width=450)
|
|
|
|
|
|
|
|
def action_to_asset(self, action):
|
|
|
|
#action.asset_mark()
|
|
|
|
action.name = self.name
|
|
|
|
action.asset_generate_preview()
|
|
|
|
|
|
|
|
# Remove Keyframes
|
|
|
|
bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object]
|
|
|
|
clean_action(
|
|
|
|
action=action,
|
|
|
|
frame_start=self.frame_start,
|
|
|
|
frame_end=self.frame_end,
|
|
|
|
excludes=['world', 'walk'],
|
|
|
|
includes=bones,
|
|
|
|
)
|
|
|
|
|
|
|
|
## Define Tags
|
|
|
|
tags = [t.strip() for t in self.tags.split(',')]
|
|
|
|
tag_range = f'f{self.frame_start}'
|
|
|
|
is_single_frame = True
|
|
|
|
if self.action_type == 'ANIM':
|
|
|
|
is_single_frame = False
|
|
|
|
tag_range = f'f{self.frame_start}-f{self.frame_end}'
|
|
|
|
|
|
|
|
tags.append(self.action_type)
|
|
|
|
tags.append(tag_range)
|
|
|
|
|
|
|
|
for tag in tags:
|
|
|
|
action.asset_data.tags.new(tag)
|
|
|
|
|
|
|
|
action.asset_data['is_single_frame'] = is_single_frame
|
|
|
|
action.asset_data['rig'] = bpy.context.object.name
|
|
|
|
col = get_overriden_col(bpy.context.object)
|
|
|
|
if col:
|
|
|
|
action.asset_data['col'] = col.name
|
|
|
|
|
|
|
|
return action
|
|
|
|
|
|
|
|
def render_preview(self, image_path, video_path):
|
|
|
|
ctx = bpy.context
|
|
|
|
scn = ctx.scene
|
|
|
|
vl = ctx.view_layer
|
|
|
|
area = get_view3d_persp()
|
|
|
|
space = area.spaces.active
|
|
|
|
|
|
|
|
preview_attrs = [
|
|
|
|
(scn, 'use_preview_range', True),
|
|
|
|
(scn, 'frame_preview_start', self.frame_start),
|
|
|
|
(scn, 'frame_preview_end', self.frame_end),
|
|
|
|
(scn.render, 'resolution_percentage', 100),
|
|
|
|
(space.overlay, 'show_overlays', False),
|
|
|
|
(space.region_3d, 'view_perspective', 'CAMERA'),
|
|
|
|
]
|
|
|
|
|
|
|
|
image_attrs = [
|
|
|
|
(scn.render, 'resolution_x', 512),
|
|
|
|
(scn.render, 'resolution_y', 512),
|
|
|
|
(scn.render, 'film_transparent', True),
|
|
|
|
(scn.render.image_settings, 'file_format', 'PNG'),
|
|
|
|
(scn.render.image_settings, 'color_mode', 'RGBA'),
|
|
|
|
(scn.render.image_settings, 'color_depth', 8),
|
|
|
|
(scn.render, 'use_overwrite', True),
|
|
|
|
(scn.render, 'filepath', str(image_path))
|
|
|
|
]
|
|
|
|
|
|
|
|
video_attrs = [
|
|
|
|
(scn.render, 'resolution_x', 1280),
|
|
|
|
(scn.render, 'resolution_y', 720),
|
|
|
|
(scn.render.image_settings, 'file_format', 'FFMPEG'),
|
|
|
|
(scn.render.ffmpeg, 'format', 'QUICKTIME'),
|
|
|
|
(scn.render.ffmpeg, 'codec', 'H264'),
|
|
|
|
(scn.render.ffmpeg, 'ffmpeg_preset', 'GOOD'),
|
|
|
|
(scn.render.ffmpeg, 'constant_rate_factor', 'HIGH'),
|
|
|
|
(scn.render.ffmpeg, 'gopsize', 12),
|
|
|
|
(scn.render, 'filepath', str(video_path)),
|
|
|
|
]
|
|
|
|
|
|
|
|
with attr_set(preview_attrs+image_attrs):
|
|
|
|
with ctx.temp_override(area=area):
|
|
|
|
bpy.ops.render.opengl(write_still=True)
|
|
|
|
|
|
|
|
if self.action_type == "ANIMATION":
|
|
|
|
with attr_set(preview_attrs+video_attrs):
|
|
|
|
with ctx.temp_override(area=area):
|
|
|
|
bpy.ops.render.opengl(animation=True)
|
|
|
|
|
|
|
|
def save_pose_preview(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def refresh(self, area):
|
|
|
|
bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
|
|
|
|
#space_data.activate_asset_by_id(asset, deferred=deferred)
|
|
|
|
|
|
|
|
def execute(self, context: Context):
|
|
|
|
scn = context.scene
|
|
|
|
vl = context.view_layer
|
|
|
|
ob = context.object
|
|
|
|
|
|
|
|
prefs = get_addon_prefs()
|
|
|
|
|
|
|
|
lib = self.current_library
|
|
|
|
if lib.merge_libraries:
|
|
|
|
lib = prefs.libraries[self.current_library.store_library]
|
|
|
|
|
|
|
|
#lib_path = lib.library_path
|
|
|
|
name = lib.adapter.norm_file_name(self.name)
|
|
|
|
asset_path = lib.adapter.get_asset_path(name=name, catalog=self.catalog)
|
|
|
|
img_path = lib.adapter.get_path('image', name, asset_path)
|
|
|
|
video_path = lib.adapter.get_path('video', name, asset_path)
|
|
|
|
|
|
|
|
## Copy Action
|
|
|
|
current_action = ob.animation_data.action
|
|
|
|
asset_action = self.asset_action
|
|
|
|
ob.animation_data.action = asset_action
|
|
|
|
vl.update()
|
|
|
|
|
|
|
|
self.action_to_asset(asset_action)
|
|
|
|
|
|
|
|
#Saving the preview
|
|
|
|
|
|
|
|
self.render_preview(img_path, video_path)
|
|
|
|
|
|
|
|
with context.temp_override(id=asset_action):
|
|
|
|
bpy.ops.ed.lib_id_load_custom_preview(
|
|
|
|
filepath=str(img_path)
|
|
|
|
)
|
|
|
|
|
|
|
|
lib.adapter.write_asset(asset=asset_action, asset_path=asset_path)
|
|
|
|
|
|
|
|
asset_description = lib.adapter.get_asset_description(
|
|
|
|
asset=asset_action,
|
|
|
|
catalog=self.catalog,
|
|
|
|
modified=time.time_ns()
|
|
|
|
)
|
|
|
|
|
2022-12-27 23:49:57 +01:00
|
|
|
lib.adapter.write_description_file(asset_description, asset_path)
|
2022-12-24 15:30:32 +01:00
|
|
|
|
|
|
|
# Restore action and cleanup
|
|
|
|
ob.animation_data.action = current_action
|
|
|
|
|
|
|
|
asset_action.asset_clear()
|
|
|
|
asset_action.use_fake_user = False
|
|
|
|
bpy.data.actions.remove(asset_action)
|
|
|
|
|
|
|
|
# TODO Write a proper method for this
|
|
|
|
diff_path = Path(bpy.app.tempdir, 'diff.json')
|
|
|
|
|
|
|
|
diff = [dict(a, operation='ADD') for a in lib.adapter.norm_asset_datas([asset_description])]
|
|
|
|
diff_path.write_text(json.dumps(diff, indent=4))
|
|
|
|
|
|
|
|
bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True)
|
|
|
|
|
|
|
|
#self.area.tag_redraw()
|
|
|
|
|
|
|
|
self.report({'INFO'}, f'"{self.name}" has been added to the library.')
|
|
|
|
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
|
|
|
|
|
|
classes = (
|
|
|
|
ACTIONLIB_OT_assign_action,
|
|
|
|
ACTIONLIB_OT_restore_previous_action,
|
|
|
|
ACTIONLIB_OT_publish,
|
|
|
|
ACTIONLIB_OT_apply_anim,
|
|
|
|
ACTIONLIB_OT_replace_pose,
|
|
|
|
ACTIONLIB_OT_create_anim_asset,
|
|
|
|
ACTIONLIB_OT_apply_selected_action,
|
|
|
|
ACTIONLIB_OT_edit_action,
|
|
|
|
ACTIONLIB_OT_clear_action,
|
|
|
|
ACTIONLIB_OT_generate_preview,
|
|
|
|
ACTIONLIB_OT_update_action_data,
|
|
|
|
ACTIONLIB_OT_assign_rest_pose,
|
|
|
|
ACTIONLIB_OT_store_anim_pose,
|
|
|
|
)
|
|
|
|
|
|
|
|
register, unregister = bpy.utils.register_classes_factory(classes)
|