asset_library/action/operators.py

1077 lines
35 KiB
Python
Raw Normal View History

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,
2022-12-28 00:09:57 +01:00
#get_catalog_path,
#read_catalog,
2022-12-24 15:30:32 +01:00
#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'}
2022-12-28 00:09:57 +01:00
"""
2022-12-24 15:30:32 +01:00
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"}
2022-12-28 00:09:57 +01:00
"""
2022-12-24 15:30:32 +01:00
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,
2022-12-28 00:09:57 +01:00
#ACTIONLIB_OT_publish,
2022-12-24 15:30:32 +01:00
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)