Compare commits
6 Commits
3.0.0
...
fix-psa-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6493ace078 | ||
|
|
0d9e2a4b60 | ||
|
|
248440052b | ||
|
|
ded6fc8980 | ||
|
|
19ff47cc83 | ||
|
|
31c0ec16ab |
@@ -1,6 +1,7 @@
|
||||
from typing import Dict, Iterable
|
||||
|
||||
from bpy.types import Action
|
||||
from mathutils import Matrix
|
||||
|
||||
from .data import *
|
||||
from ..helpers import *
|
||||
@@ -19,13 +20,7 @@ class PsaBuilderOptions(object):
|
||||
self.should_trim_timeline_marker_sequences = True
|
||||
self.sequence_name_prefix = ''
|
||||
self.sequence_name_suffix = ''
|
||||
|
||||
|
||||
class PsaBuilderPerformance:
|
||||
def __init__(self):
|
||||
self.frame_set_duration = datetime.timedelta()
|
||||
self.key_build_duration = datetime.timedelta()
|
||||
self.key_add_duration = datetime.timedelta()
|
||||
self.root_motion = False
|
||||
|
||||
|
||||
class PsaBuilder(object):
|
||||
@@ -53,7 +48,6 @@ class PsaBuilder(object):
|
||||
raise RuntimeError(f'Invalid FPS source "{options.fps_source}"')
|
||||
|
||||
def build(self, context, options: PsaBuilderOptions) -> Psa:
|
||||
performance = PsaBuilderPerformance()
|
||||
active_object = context.view_layer.objects.active
|
||||
|
||||
if active_object.type != 'ARMATURE':
|
||||
@@ -201,40 +195,38 @@ class PsaBuilder(object):
|
||||
frame_count = frame_max - frame_min + 1
|
||||
|
||||
for frame in range(frame_count):
|
||||
with Timer() as t:
|
||||
context.scene.frame_set(frame_min + frame)
|
||||
performance.frame_set_duration += t.duration
|
||||
context.scene.frame_set(frame_min + frame)
|
||||
|
||||
for pose_bone in pose_bones:
|
||||
with Timer() as t:
|
||||
key = Psa.Key()
|
||||
key = Psa.Key()
|
||||
|
||||
if pose_bone.parent is not None:
|
||||
pose_bone_matrix = pose_bone.matrix
|
||||
pose_bone_parent_matrix = pose_bone.parent.matrix
|
||||
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
||||
else:
|
||||
if options.root_motion:
|
||||
# Export root motion
|
||||
pose_bone_matrix = armature.matrix_world @ pose_bone.matrix
|
||||
else:
|
||||
pose_bone_matrix = pose_bone.matrix
|
||||
|
||||
if pose_bone.parent is not None:
|
||||
pose_bone_parent_matrix = pose_bone.parent.matrix
|
||||
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
||||
location = pose_bone_matrix.to_translation()
|
||||
rotation = pose_bone_matrix.to_quaternion().normalized()
|
||||
|
||||
location = pose_bone_matrix.to_translation()
|
||||
rotation = pose_bone_matrix.to_quaternion().normalized()
|
||||
if pose_bone.parent is not None:
|
||||
rotation.conjugate()
|
||||
|
||||
if pose_bone.parent is not None:
|
||||
rotation.x = -rotation.x
|
||||
rotation.y = -rotation.y
|
||||
rotation.z = -rotation.z
|
||||
key.location.x = location.x
|
||||
key.location.y = location.y
|
||||
key.location.z = location.z
|
||||
key.rotation.x = rotation.x
|
||||
key.rotation.y = rotation.y
|
||||
key.rotation.z = rotation.z
|
||||
key.rotation.w = rotation.w
|
||||
key.time = 1.0 / psa_sequence.fps
|
||||
|
||||
key.location.x = location.x
|
||||
key.location.y = location.y
|
||||
key.location.z = location.z
|
||||
key.rotation.x = rotation.x
|
||||
key.rotation.y = rotation.y
|
||||
key.rotation.z = rotation.z
|
||||
key.rotation.w = rotation.w
|
||||
key.time = 1.0 / psa_sequence.fps
|
||||
performance.key_build_duration += t.duration
|
||||
|
||||
with Timer() as t:
|
||||
psa.keys.append(key)
|
||||
performance.key_add_duration += t.duration
|
||||
psa.keys.append(key)
|
||||
|
||||
psa_sequence.bone_count = len(pose_bones)
|
||||
psa_sequence.track_time = frame_count
|
||||
@@ -264,8 +256,12 @@ class PsaBuilder(object):
|
||||
frame_max = sorted_timeline_markers[next_marker_index].frame
|
||||
if options.should_trim_timeline_marker_sequences:
|
||||
nla_strips = get_nla_strips_in_timeframe(object, marker.frame, frame_max)
|
||||
frame_max = min(frame_max, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips)))
|
||||
frame_min = max(frame_min, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips)))
|
||||
if len(nla_strips) > 0:
|
||||
frame_max = min(frame_max, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips)))
|
||||
frame_min = max(frame_min, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips)))
|
||||
else:
|
||||
# No strips in between this marker and the next, just export this as a one-frame animation.
|
||||
frame_max = frame_min
|
||||
else:
|
||||
# There is no next marker.
|
||||
# Find the final frame of all the NLA strips and use that as the last frame of this sequence.
|
||||
|
||||
@@ -65,6 +65,12 @@ def should_use_original_sequence_names_updated(_, context):
|
||||
|
||||
|
||||
class PsaExportPropertyGroup(PropertyGroup):
|
||||
root_motion: BoolProperty(
|
||||
name='Root Motion',
|
||||
options=set(),
|
||||
default=False,
|
||||
description='When set, the root bone will be transformed as it appears in the scene',
|
||||
)
|
||||
sequence_source: EnumProperty(
|
||||
name='Source',
|
||||
options=set(),
|
||||
@@ -122,10 +128,21 @@ class PsaExportPropertyGroup(PropertyGroup):
|
||||
)
|
||||
sequence_name_prefix: StringProperty(name='Prefix', options=set())
|
||||
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
||||
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
||||
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
||||
sequence_filter_asset: BoolProperty(default=False, name='Show assets',
|
||||
description='Show actions that belong to an asset library', options=set())
|
||||
sequence_filter_name: StringProperty(
|
||||
default='',
|
||||
name='Filter by Name',
|
||||
options={'TEXTEDIT_UPDATE'},
|
||||
description='Only show items matching this name (use \'*\' as wildcard)')
|
||||
sequence_use_filter_invert: BoolProperty(
|
||||
default=False,
|
||||
name='Invert',
|
||||
options=set(),
|
||||
description='Invert filtering (show hidden items, and vice versa)')
|
||||
sequence_filter_asset: BoolProperty(
|
||||
default=False,
|
||||
name='Show assets',
|
||||
options=set(),
|
||||
description='Show actions that belong to an asset library')
|
||||
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
|
||||
|
||||
|
||||
@@ -165,6 +182,9 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
# SOURCE
|
||||
layout.prop(pg, 'sequence_source', text='Source')
|
||||
|
||||
# ROOT MOTION
|
||||
layout.prop(pg, 'root_motion', text='Root Motion')
|
||||
|
||||
# SELECT ALL/NONE
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Select')
|
||||
@@ -175,7 +195,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
if pg.sequence_source == 'ACTIONS':
|
||||
rows = max(3, min(len(pg.action_list), 10))
|
||||
|
||||
layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
||||
layout.template_list('PSA_UL_ExportSequenceList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
@@ -186,7 +206,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
rows = max(3, min(len(pg.marker_list), 10))
|
||||
layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index',
|
||||
layout.template_list('PSA_UL_ExportSequenceList', '', pg, 'marker_list', pg, 'marker_list_index',
|
||||
rows=rows)
|
||||
|
||||
col = layout.column()
|
||||
@@ -297,6 +317,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
options.should_trim_timeline_marker_sequences = pg.should_trim_timeline_marker_sequences
|
||||
options.sequence_name_prefix = pg.sequence_name_prefix
|
||||
options.sequence_name_suffix = pg.sequence_name_suffix
|
||||
options.root_motion = pg.root_motion
|
||||
|
||||
builder = PsaBuilder()
|
||||
|
||||
@@ -311,38 +332,26 @@ class PsaExportOperator(Operator, ExportHelper):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class PSA_UL_ExportTimelineMarkerList(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
||||
|
||||
def filter_items(self, context, data, property):
|
||||
pg = context.scene.psa_export
|
||||
sequences = getattr(data, property)
|
||||
flt_flags = filter_sequences(pg, sequences)
|
||||
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'name')
|
||||
return flt_flags, flt_neworder
|
||||
|
||||
|
||||
def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]:
|
||||
bitflag_filter_item = 1 << 30
|
||||
flt_flags = [bitflag_filter_item] * len(sequences)
|
||||
|
||||
if pg.sequence_filter_name is not None:
|
||||
if pg.sequence_filter_name:
|
||||
# Filter name is non-empty.
|
||||
for i, sequence in enumerate(sequences):
|
||||
if not fnmatch.fnmatch(sequence.name, f'*{pg.sequence_filter_name}*'):
|
||||
flt_flags[i] &= ~bitflag_filter_item
|
||||
|
||||
# Invert filter flags for all items.
|
||||
if pg.sequence_use_filter_invert:
|
||||
for i, sequence in enumerate(sequences):
|
||||
flt_flags[i] ^= bitflag_filter_item
|
||||
|
||||
if not pg.sequence_filter_asset:
|
||||
for i, sequence in enumerate(sequences):
|
||||
if hasattr(sequence, 'action') and sequence.action.asset_data is not None:
|
||||
flt_flags[i] &= ~bitflag_filter_item
|
||||
|
||||
if pg.sequence_use_filter_invert:
|
||||
# Invert filter flags for all items.
|
||||
for i, sequence in enumerate(sequences):
|
||||
flt_flags[i] ^= bitflag_filter_item
|
||||
|
||||
return flt_flags
|
||||
|
||||
|
||||
@@ -355,16 +364,16 @@ def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_p
|
||||
return visible_sequences
|
||||
|
||||
|
||||
class PSA_UL_ExportActionList(UIList):
|
||||
class PSA_UL_ExportSequenceList(UIList):
|
||||
|
||||
def __init__(self):
|
||||
super(PSA_UL_ExportActionList, self).__init__()
|
||||
super(PSA_UL_ExportSequenceList, self).__init__()
|
||||
# Show the filtering options by default.
|
||||
self.use_filter_show = True
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
||||
if item.action.asset_data is not None:
|
||||
if hasattr(item, 'action') and item.action.asset_data is not None:
|
||||
layout.label(text='', icon='ASSET_MANAGER')
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
@@ -373,10 +382,13 @@ class PSA_UL_ExportActionList(UIList):
|
||||
subrow = row.row(align=True)
|
||||
subrow.prop(pg, 'sequence_filter_name', text="")
|
||||
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
||||
subrow = row.row(align=True)
|
||||
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
||||
# subrow.prop(pg, 'sequence_use_filter_sort_reverse', text='', icon='SORT_ASC')
|
||||
|
||||
if pg.sequence_source == 'ACTIONS':
|
||||
subrow = row.row(align=True)
|
||||
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
||||
|
||||
|
||||
def filter_items(self, context, data, property):
|
||||
pg = context.scene.psa_export
|
||||
actions = getattr(data, property)
|
||||
@@ -490,8 +502,7 @@ classes = (
|
||||
PsaExportTimelineMarkerListItem,
|
||||
PsaExportPropertyGroup,
|
||||
PsaExportOperator,
|
||||
PSA_UL_ExportActionList,
|
||||
PSA_UL_ExportTimelineMarkerList,
|
||||
PSA_UL_ExportSequenceList,
|
||||
PsaExportActionsSelectAll,
|
||||
PsaExportActionsDeselectAll,
|
||||
PsaExportBoneGroupsSelectAll,
|
||||
|
||||
Reference in New Issue
Block a user