Compare commits

...

6 Commits

Author SHA1 Message Date
Yurii Ti
6493ace078 [PSA Export] Added missing names and descriptions for filter properties 2022-05-05 08:23:05 +03:00
Yurii Ti
0d9e2a4b60 [PSA Export] Fixed incorrect sequence filtering results
* Fixed a bug where the asset filter button would have no effect when
  reverse filtering is enabled.

* Fixed a bug where enabling reverse filtering would hide all items when
  the input field is empty.
2022-05-05 07:17:49 +03:00
Yurii Ti
248440052b [PSA Export] Refactored action and marker lists into a single class
Filter box is now visible by default when timeline markers are selected
as source.
2022-05-05 07:06:49 +03:00
Yurii Ti
ded6fc8980 [PSA Export] Fixed a bug where timeline markers couldn't be filtered 2022-05-05 07:00:47 +03:00
Colin Basnett
19ff47cc83 * Added "Root Motion" option to enable root motion on export (vs. stationary root at world origin!)
* Removed performance debugging code
2022-04-30 17:33:59 -07:00
Colin Basnett
31c0ec16ab Fixed a runtime error that would occur if the user attempted to export animations using timeline markers and there were no strips occupying the space between markers 2022-04-29 19:41:15 -07:00
2 changed files with 75 additions and 68 deletions

View File

@@ -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.

View File

@@ -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,