Added NLA track sequence source option (reimplementation of feature-nla-track-sequence-source branch).
This just needs a bit of time in the oven to test it out.
This commit is contained in:
@@ -34,7 +34,7 @@ def rgb_to_srgb(c: float):
|
||||
return 12.92 * c
|
||||
|
||||
|
||||
def get_nla_strips_in_timeframe(animation_data: AnimData, frame_min: float, frame_max: float) -> List[NlaStrip]:
|
||||
def get_nla_strips_in_frame_range(animation_data: AnimData, frame_min: float, frame_max: float) -> List[NlaStrip]:
|
||||
if animation_data is None:
|
||||
return []
|
||||
strips = []
|
||||
|
||||
@@ -8,10 +8,10 @@ from bpy.types import Context, Armature, Action, Object, AnimData, TimelineMarke
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
from bpy_types import Operator
|
||||
|
||||
from io_scene_psk_psa.helpers import populate_bone_group_list, get_nla_strips_in_timeframe
|
||||
from io_scene_psk_psa.psa.builder import build_psa, PsaBuildSequence, PsaBuildOptions
|
||||
from io_scene_psk_psa.psa.export.properties import PSA_PG_export, PSA_PG_export_action_list_item, filter_sequences
|
||||
from io_scene_psk_psa.psa.writer import write_psa
|
||||
from .properties import PSA_PG_export, PSA_PG_export_action_list_item, filter_sequences
|
||||
from ..builder import build_psa, PsaBuildSequence, PsaBuildOptions
|
||||
from ..writer import write_psa
|
||||
from ...helpers import populate_bone_group_list, get_nla_strips_in_frame_range
|
||||
|
||||
|
||||
def is_action_for_armature(armature: Armature, action: Action):
|
||||
@@ -150,7 +150,7 @@ def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context:
|
||||
if next_marker_index < len(sorted_timeline_markers):
|
||||
# There is a next marker. Use that next marker's frame position as the last frame of this sequence.
|
||||
frame_end = sorted_timeline_markers[next_marker_index].frame
|
||||
nla_strips = get_nla_strips_in_timeframe(animation_data, marker.frame, frame_end)
|
||||
nla_strips = get_nla_strips_in_frame_range(animation_data, marker.frame, frame_end)
|
||||
if len(nla_strips) > 0:
|
||||
frame_end = min(frame_end, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips)))
|
||||
frame_start = max(frame_start, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips)))
|
||||
@@ -254,12 +254,18 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
# SOURCE
|
||||
layout.prop(pg, 'sequence_source', text='Source')
|
||||
|
||||
if pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
if pg.sequence_source in {'TIMELINE_MARKERS', 'NLA_TRACK_STRIPS'}:
|
||||
# ANIMDATA SOURCE
|
||||
layout.prop(pg, 'should_override_animation_data')
|
||||
if pg.should_override_animation_data:
|
||||
layout.prop(pg, 'animation_data_override', text='')
|
||||
|
||||
if pg.sequence_source == 'NLA_TRACK_STRIPS':
|
||||
flow = layout.grid_flow()
|
||||
flow.use_property_split = True
|
||||
flow.use_property_decorate = False
|
||||
flow.prop(pg, 'nla_track')
|
||||
|
||||
# SELECT ALL/NONE
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Select')
|
||||
@@ -269,19 +275,13 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
# ACTIONS
|
||||
if pg.sequence_source == 'ACTIONS':
|
||||
rows = max(3, min(len(pg.action_list), 10))
|
||||
|
||||
layout.template_list('PSA_UL_export_sequences', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
col.prop(pg, 'sequence_name_prefix')
|
||||
col.prop(pg, 'sequence_name_suffix')
|
||||
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
rows = max(3, min(len(pg.marker_list), 10))
|
||||
layout.template_list('PSA_UL_export_sequences', '', pg, 'marker_list', pg, 'marker_list_index',
|
||||
rows=rows)
|
||||
layout.template_list('PSA_UL_export_sequences', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows)
|
||||
elif pg.sequence_source == 'NLA_TRACK_STRIPS':
|
||||
rows = max(3, min(len(pg.nla_strip_list), 10))
|
||||
layout.template_list('PSA_UL_export_sequences', '', pg, 'nla_strip_list', pg, 'nla_strip_list_index', rows=rows)
|
||||
|
||||
col = layout.column()
|
||||
col.use_property_split = True
|
||||
@@ -360,6 +360,8 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
raise RuntimeError('No actions were selected for export')
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS' and len(pg.marker_names) == 0:
|
||||
raise RuntimeError('No timeline markers were selected for export')
|
||||
elif pg.sequence_source == 'NLA_TRACK_STRIPS' and len(pg.nla_strip_list) == 0:
|
||||
raise RuntimeError('No NLA track strips were selected for export')
|
||||
|
||||
# Populate the export sequence list.
|
||||
animation_data_object = get_animation_data_object(context)
|
||||
@@ -371,29 +373,38 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
export_sequences: List[PsaBuildSequence] = []
|
||||
|
||||
if pg.sequence_source == 'ACTIONS':
|
||||
for action in filter(lambda x: x.is_selected, pg.action_list):
|
||||
if len(action.action.fcurves) == 0:
|
||||
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
||||
if len(action_item.action.fcurves) == 0:
|
||||
continue
|
||||
export_sequence = PsaBuildSequence()
|
||||
export_sequence.nla_state.action = action.action
|
||||
export_sequence.name = action.name
|
||||
export_sequence.nla_state.frame_start = action.frame_start
|
||||
export_sequence.nla_state.frame_end = action.frame_end
|
||||
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [action.action])
|
||||
export_sequence.compression_ratio = action.action.psa_export.compression_ratio
|
||||
export_sequence.key_quota = action.action.psa_export.key_quota
|
||||
export_sequence.nla_state.action = action_item.action
|
||||
export_sequence.name = action_item.name
|
||||
export_sequence.nla_state.frame_start = action_item.frame_start
|
||||
export_sequence.nla_state.frame_end = action_item.frame_end
|
||||
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [action_item.action])
|
||||
export_sequence.compression_ratio = action_item.action.psa_export.compression_ratio
|
||||
export_sequence.key_quota = action_item.action.psa_export.key_quota
|
||||
export_sequences.append(export_sequence)
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
for marker in pg.marker_list:
|
||||
for marker_item in filter(lambda x: x.is_selected, pg.marker_list):
|
||||
export_sequence = PsaBuildSequence()
|
||||
export_sequence.name = marker.name
|
||||
export_sequence.name = marker_item.name
|
||||
export_sequence.nla_state.action = None
|
||||
export_sequence.nla_state.frame_start = marker.frame_start
|
||||
export_sequence.nla_state.frame_end = marker.frame_end
|
||||
export_sequence.nla_state.frame_start = marker_item.frame_start
|
||||
export_sequence.nla_state.frame_end = marker_item.frame_end
|
||||
nla_strips_actions = set(
|
||||
map(lambda x: x.action, get_nla_strips_in_timeframe(animation_data, marker.frame_start, marker.frame_end)))
|
||||
map(lambda x: x.action, get_nla_strips_in_frame_range(animation_data, marker_item.frame_start, marker_item.frame_end)))
|
||||
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, nla_strips_actions)
|
||||
export_sequences.append(export_sequence)
|
||||
elif pg.sequence_source == 'NLA_TRACK_STRIPS':
|
||||
for nla_strip_item in filter(lambda x: x.is_selected, pg.nla_strip_list):
|
||||
export_sequence = PsaBuildSequence()
|
||||
export_sequence.name = nla_strip_item.name
|
||||
export_sequence.nla_state.action = nla_strip_item.action
|
||||
export_sequence.nla_state.frame_start = nla_strip_item.frame_start
|
||||
export_sequence.nla_state.frame_end = nla_strip_item.frame_end
|
||||
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [nla_strip_item.action])
|
||||
export_sequences.append(export_sequence)
|
||||
else:
|
||||
raise ValueError(f'Unhandled sequence source: {pg.sequence_source}')
|
||||
|
||||
@@ -432,6 +443,8 @@ class PSA_OT_export_actions_select_all(Operator):
|
||||
return pg.action_list
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
return pg.marker_list
|
||||
elif pg.sequence_source == 'NLA_TRACK_STRIPS':
|
||||
return pg.nla_strip_list
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@@ -463,6 +476,8 @@ class PSA_OT_export_actions_deselect_all(Operator):
|
||||
return pg.action_list
|
||||
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||
return pg.marker_list
|
||||
elif pg.sequence_source == 'NLA_TRACK_STRIPS':
|
||||
return pg.nla_strip_list
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import re
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from bpy.props import BoolProperty, PointerProperty, EnumProperty, FloatProperty, CollectionProperty, IntProperty, \
|
||||
StringProperty
|
||||
from bpy.types import PropertyGroup, Object, Action
|
||||
from bpy.types import PropertyGroup, Object, Action, AnimData, Context
|
||||
|
||||
from ...types import PSX_PG_bone_group_list_item
|
||||
|
||||
@@ -25,7 +26,7 @@ class PSA_PG_export_action_list_item(PropertyGroup):
|
||||
is_pose_marker: BoolProperty(options={'HIDDEN'})
|
||||
|
||||
|
||||
class PSA_PG_export_timeline_markers(PropertyGroup):
|
||||
class PSA_PG_export_timeline_markers(PropertyGroup): # TODO: rename this to singular
|
||||
marker_index: IntProperty()
|
||||
name: StringProperty()
|
||||
is_selected: BoolProperty(default=True)
|
||||
@@ -33,6 +34,51 @@ class PSA_PG_export_timeline_markers(PropertyGroup):
|
||||
frame_end: IntProperty(options={'HIDDEN'})
|
||||
|
||||
|
||||
class PSA_PG_export_nla_strip_list_item(PropertyGroup):
|
||||
name: StringProperty()
|
||||
action: PointerProperty(type=Action)
|
||||
frame_start: FloatProperty()
|
||||
frame_end: FloatProperty()
|
||||
is_selected: BoolProperty(default=True)
|
||||
|
||||
|
||||
def nla_track_update_cb(self: 'PSA_PG_export', context: Context) -> None:
|
||||
self.nla_strip_list.clear()
|
||||
if context.object is None or context.object.animation_data is None:
|
||||
return
|
||||
match = re.match(r'^(\d+).+$', self.nla_track)
|
||||
self.nla_track_index = int(match.group(1)) if match else -1
|
||||
if self.nla_track_index >= 0:
|
||||
nla_track = context.object.animation_data.nla_tracks[self.nla_track_index]
|
||||
for nla_strip in nla_track.strips:
|
||||
strip: PSA_PG_export_nla_strip_list_item = self.nla_strip_list.add()
|
||||
strip.action = nla_strip.action
|
||||
strip.name = nla_strip.name
|
||||
strip.frame_start = nla_strip.frame_start
|
||||
strip.frame_end = nla_strip.frame_end
|
||||
|
||||
|
||||
def get_animation_data(pg: 'PSA_PG_export', context: Context) -> Optional[AnimData]:
|
||||
animation_data_object = context.object
|
||||
if pg.should_override_animation_data:
|
||||
animation_data_object = pg.animation_data_override
|
||||
return animation_data_object.animation_data if animation_data_object else None
|
||||
|
||||
|
||||
def nla_track_search_cb(self, context: Context, edit_text: str):
|
||||
pg = getattr(context.scene, 'psa_export')
|
||||
animation_data = get_animation_data(pg, context)
|
||||
if animation_data is None:
|
||||
return
|
||||
for index, nla_track in enumerate(animation_data.nla_tracks):
|
||||
yield f'{index} - {nla_track.name}'
|
||||
|
||||
|
||||
def animation_data_override_update_cb(self: 'PSA_PG_export', context: Context):
|
||||
# Reset NLA track selection
|
||||
self.nla_track = ''
|
||||
|
||||
|
||||
class PSA_PG_export(PropertyGroup):
|
||||
root_motion: BoolProperty(
|
||||
name='Root Motion',
|
||||
@@ -46,10 +92,12 @@ class PSA_PG_export(PropertyGroup):
|
||||
name='Override Animation Data',
|
||||
options=empty_set,
|
||||
default=False,
|
||||
description='Use the animation data from a different object instead of the selected object'
|
||||
description='Use the animation data from a different object instead of the selected object',
|
||||
update=animation_data_override_update_cb,
|
||||
)
|
||||
animation_data_override: PointerProperty(
|
||||
type=Object,
|
||||
update=animation_data_override_update_cb,
|
||||
poll=psa_export_property_group_animation_data_override_poll
|
||||
)
|
||||
sequence_source: EnumProperty(
|
||||
@@ -58,10 +106,18 @@ class PSA_PG_export(PropertyGroup):
|
||||
description='',
|
||||
items=(
|
||||
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
||||
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT',
|
||||
1),
|
||||
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences are delineated by scene timeline markers', 'MARKER_HLT', 1),
|
||||
('NLA_TRACK_STRIPS', 'NLA Track Strips', 'Sequences are delineated by the start & end times of strips on the selected NLA track', 'NLA', 2)
|
||||
)
|
||||
)
|
||||
nla_track: StringProperty(
|
||||
name='NLA Track',
|
||||
options=empty_set,
|
||||
description='',
|
||||
search=nla_track_search_cb,
|
||||
update=nla_track_update_cb
|
||||
)
|
||||
nla_track_index: IntProperty(name='NLA Track Index', default=-1)
|
||||
fps_source: EnumProperty(
|
||||
name='FPS Source',
|
||||
options=empty_set,
|
||||
@@ -80,6 +136,8 @@ class PSA_PG_export(PropertyGroup):
|
||||
action_list_index: IntProperty(default=0)
|
||||
marker_list: CollectionProperty(type=PSA_PG_export_timeline_markers)
|
||||
marker_list_index: IntProperty(default=0)
|
||||
nla_strip_list: CollectionProperty(type=PSA_PG_export_nla_strip_list_item)
|
||||
nla_strip_list_index: IntProperty(default=0)
|
||||
bone_filter_mode: EnumProperty(
|
||||
name='Bone Filter',
|
||||
options=empty_set,
|
||||
@@ -145,7 +203,7 @@ def filter_sequences(pg: PSA_PG_export, sequences) -> List[int]:
|
||||
|
||||
if not pg.sequence_filter_asset:
|
||||
for i, sequence in enumerate(sequences):
|
||||
if hasattr(sequence, 'action') and sequence.action.asset_data is not None:
|
||||
if hasattr(sequence, 'action') and sequence.action is not None and sequence.action.asset_data is not None:
|
||||
flt_flags[i] &= ~bitflag_filter_item
|
||||
|
||||
if not pg.sequence_filter_pose_marker:
|
||||
@@ -164,5 +222,6 @@ def filter_sequences(pg: PSA_PG_export, sequences) -> List[int]:
|
||||
classes = (
|
||||
PSA_PG_export_action_list_item,
|
||||
PSA_PG_export_timeline_markers,
|
||||
PSA_PG_export_nla_strip_list_item,
|
||||
PSA_PG_export,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ class PSA_UL_export_sequences(UIList):
|
||||
item = cast(PSA_PG_export_action_list_item, item)
|
||||
is_pose_marker = hasattr(item, 'is_pose_marker') and item.is_pose_marker
|
||||
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
||||
if hasattr(item, 'action') and item.action.asset_data is not None:
|
||||
if hasattr(item, 'action') and item.action is not None and item.action.asset_data is not None:
|
||||
layout.label(text='', icon='ASSET_MANAGER')
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
Reference in New Issue
Block a user