asset_library/action/functions.py

207 lines
6.5 KiB
Python
Raw Normal View History

2022-12-24 15:30:32 +01:00
# SPDX-License-Identifier: GPL-2.0-or-later
"""
Functions related to anim and pose.
"""
from collections.abc import Collection
from typing import Optional, FrozenSet, Set, Union, Iterable, cast
import dataclasses
import functools
import re
import bpy
from bpy.types import (
Action,
Bone,
Context,
FCurve,
Keyframe,
Object,
TimelineMarker
)
from asset_library.common.bl_utils import active_catalog_id, split_path
FCurveValue = Union[float, int]
pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
"""RegExp for matching FCurve data paths."""
def is_pose(action):
for fc in action.fcurves:
if len(fc.keyframe_points) > 1:
return False
return True
def get_bone_visibility(data_path):
bone, prop = split_path(data_path)
ob = bpy.context.object
b_layers = [i for i, val in enumerate(ob.pose.bones[bone].bone.layers) if val]
rig_layers = [(i, val) for i, val in enumerate(ob.data.layers)]
return ob.data.layers[b_layers[0]]
def get_keyframes(action, selected=False, includes=[]):
if selected:
# keyframes = sorted([int(k.co[0]) for f in action.fcurves for k in f.keyframe_points if k.select_control_point and get_bone_visibility(f.data_path)])
keyframes = []
for f in action.fcurves:
bone, prop = split_path(f.data_path)
for k in f.keyframe_points:
if bone not in includes:
continue
if not k.select_control_point:
continue
if not get_bone_visibility(f.data_path):
continue
keyframes += [int(k.co[0])]
if len(keyframes) <= 1:
keyframes = [bpy.context.scene.frame_current]
else:
keyframes = sorted([int(k.co[0]) for f in action.fcurves for k in f.keyframe_points])
return keyframes
def get_marker(action):
if action.pose_markers:
markers = action.pose_markers
return next((m.name for m in markers if m.frame == bpy.context.scene.frame_current), None)
def reset_bone(bone, transform=True, custom_props=True):
if transform:
bone.location = (0, 0, 0)
if bone.rotation_mode == "QUATERNION":
bone.rotation_quaternion = (0, 0, 0, 0)
elif bone.rotation_mode == "AXIS_ANGLE":
bone.rotation_axis_angle = (0, 0, 0, 0)
else:
bone.rotation_euler = (0, 0, 0)
bone.scale = (1, 1, 1)
if custom_props:
for key, value in bone.items():
try:
id_prop = bone.id_properties_ui(key)
except TypeError:
continue
if not isinstance(value, (int, float)) or not id_prop:
continue
bone[key] = id_prop.as_dict()['default']
def is_asset_action(action):
return action.asset_data and action.asset_data.catalog_id != str(uuid.UUID(int=0))
def conform_action(action):
tags = ('pose', 'anim')
if any(tag in action.asset_data.tags.keys() for tag in tags):
return
for fc in action.fcurves:
action.asset_data['is_single_frame'] = True
if len(fc.keyframe_points) > 1:
action.asset_data['is_single_frame'] = False
break
if action.asset_data['is_single_frame']:
action.asset_data.tags.new('pose')
else:
action.asset_data.tags.new('anim')
def clean_action(action='', frame_start=0, frame_end=0, excludes=[], includes=[]):
## Clean Keyframe Before/After Range
for fc in action.fcurves:
bone, prop = split_path(fc.data_path)
# !! Mush Mush dependent. Need to be fix
if bone in excludes or bone not in includes:
action.fcurves.remove(fc)
continue
# Add Keyframe At Start/End Range
for fr in (frame_start, frame_end):
fc_val = fc.evaluate(fr)
fc.keyframe_points.insert(frame=fr, value=fc_val)
fc.update()
# Remove Keyframe out of range
for k in reversed(fc.keyframe_points):
if int(k.co[0]) not in range(frame_start, frame_end+1):
fc.keyframe_points.remove(k)
fc.update()
def append_action(action_path='', action_name=''):
print(f'Loading {action_name} from: {action_path}')
with bpy.data.libraries.load(str(action_path), link=False) as (data_from, data_to):
data_to.actions = [action_name]
return data_to.actions[0]
def apply_anim(action_lib, ob, bones=[]):
from mathutils import Vector
scn = bpy.context.scene
if not ob.animation_data:
ob.animation_data_create()
action = ob.animation_data.action
if not action:
action = bpy.data.actions.new(ob.name)
ob.animation_data.action = action
keys = sorted([k.co[0] for f in action_lib.fcurves for k in f.keyframe_points])
if not keys:
print(f'The action {action_lib.name} has no keyframes')
return
first_key = keys[0]
key_offset = scn.frame_current - first_key
key_attr = ('type', 'interpolation', 'handle_left_type', 'handle_right_type',
'amplitude', 'back', 'easing', 'period', 'handle_right', 'handle_left'
)
for fc in action_lib.fcurves:
bone_name, prop_name = split_path(fc.data_path)
if bones and bone_name not in bones:
continue
action_fc = action.fcurves.find(fc.data_path, index=fc.array_index)
if not action_fc:
action_fc = action.fcurves.new(
fc.data_path,
index=fc.array_index,
action_group=fc.group.name if fc.group else fc.data_path.split('"')[1]
)
for kf_lib in fc.keyframe_points:
kf = action_fc.keyframe_points.insert(
frame=kf_lib.co[0] + key_offset,
value=kf_lib.co[1]
)
for attr in key_attr:
src_val = getattr(kf_lib, attr)
if attr.startswith('handle') and 'type' not in attr:
src_val += Vector((key_offset, 0))
setattr(kf, attr, src_val)
fc.update()
# redraw graph areas
for window in bpy.context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type == 'GRAPH_EDITOR':
area.tag_redraw()