Sequences can now be exported from action pose markers
This commit is contained in:
@@ -1,115 +1,40 @@
|
|||||||
from typing import Dict
|
from typing import Optional
|
||||||
|
|
||||||
from bpy.types import Action, Armature, Bone
|
from bpy.types import Armature, Bone, Action
|
||||||
|
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
|
|
||||||
|
|
||||||
class PsaBuildOptions(object):
|
class PsaExportSequence:
|
||||||
|
class NlaState:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.should_override_animation_data = False
|
self.action: Optional[Action] = None
|
||||||
self.animation_data_override = None
|
self.frame_start: int = 0
|
||||||
self.fps_source = 'SCENE'
|
self.frame_end: int = 0
|
||||||
self.fps_custom = 30.0
|
|
||||||
self.sequence_source = 'ACTIONS'
|
def __init__(self):
|
||||||
self.actions = []
|
self.name: str = ''
|
||||||
self.marker_names = []
|
self.nla_state: PsaExportSequence.NlaState = PsaExportSequence.NlaState()
|
||||||
|
self.fps: float = 30.0
|
||||||
|
|
||||||
|
|
||||||
|
class PsaBuildOptions:
|
||||||
|
def __init__(self):
|
||||||
|
self.animation_data: AnimData
|
||||||
|
self.sequences: List[PsaExportSequence] = []
|
||||||
self.bone_filter_mode = 'ALL'
|
self.bone_filter_mode = 'ALL'
|
||||||
self.bone_group_indices = []
|
self.bone_group_indices: List[int] = []
|
||||||
self.should_use_original_sequence_names = False
|
self.should_use_original_sequence_names = False
|
||||||
self.should_trim_timeline_marker_sequences = True
|
|
||||||
self.should_ignore_bone_name_restrictions = False
|
self.should_ignore_bone_name_restrictions = False
|
||||||
self.sequence_name_prefix = ''
|
self.sequence_name_prefix = ''
|
||||||
self.sequence_name_suffix = ''
|
self.sequence_name_suffix = ''
|
||||||
self.root_motion = False
|
self.root_motion = False
|
||||||
|
|
||||||
|
|
||||||
def get_sequence_fps(context, options: PsaBuildOptions, actions: Iterable[Action]) -> float:
|
|
||||||
if options.fps_source == 'SCENE':
|
|
||||||
return context.scene.render.fps
|
|
||||||
if options.fps_source == 'CUSTOM':
|
|
||||||
return options.fps_custom
|
|
||||||
elif options.fps_source == 'ACTION_METADATA':
|
|
||||||
# Get the minimum value of action metadata FPS values.
|
|
||||||
fps_list = []
|
|
||||||
for action in filter(lambda x: 'psa_sequence_fps' in x, actions):
|
|
||||||
fps = action['psa_sequence_fps']
|
|
||||||
if type(fps) == int or type(fps) == float:
|
|
||||||
fps_list.append(fps)
|
|
||||||
if len(fps_list) > 0:
|
|
||||||
return min(fps_list)
|
|
||||||
else:
|
|
||||||
# No valid action metadata to use, fallback to scene FPS
|
|
||||||
return context.scene.render.fps
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f'Invalid FPS source "{options.fps_source}"')
|
|
||||||
|
|
||||||
|
|
||||||
def get_timeline_marker_sequence_frame_ranges(animation_data, context, options: PsaBuildOptions) -> Dict:
|
|
||||||
# Timeline markers need to be sorted so that we can determine the sequence start and end positions.
|
|
||||||
sequence_frame_ranges = dict()
|
|
||||||
sorted_timeline_markers = list(sorted(context.scene.timeline_markers, key=lambda x: x.frame))
|
|
||||||
sorted_timeline_marker_names = list(map(lambda x: x.name, sorted_timeline_markers))
|
|
||||||
|
|
||||||
for marker_name in options.marker_names:
|
|
||||||
marker = context.scene.timeline_markers[marker_name]
|
|
||||||
frame_min = marker.frame
|
|
||||||
# Determine the final frame of the sequence based on the next marker.
|
|
||||||
# If no subsequent marker exists, use the maximum frame_end from all NLA strips.
|
|
||||||
marker_index = sorted_timeline_marker_names.index(marker_name)
|
|
||||||
next_marker_index = marker_index + 1
|
|
||||||
frame_max = 0
|
|
||||||
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_max = sorted_timeline_markers[next_marker_index].frame
|
|
||||||
if options.should_trim_timeline_marker_sequences:
|
|
||||||
nla_strips = get_nla_strips_in_timeframe(animation_data, marker.frame, frame_max)
|
|
||||||
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.
|
|
||||||
for nla_track in animation_data.nla_tracks:
|
|
||||||
if nla_track.mute:
|
|
||||||
continue
|
|
||||||
for strip in nla_track.strips:
|
|
||||||
frame_max = max(frame_max, strip.frame_end)
|
|
||||||
|
|
||||||
if frame_min > frame_max:
|
|
||||||
continue
|
|
||||||
|
|
||||||
sequence_frame_ranges[marker_name] = int(frame_min), int(frame_max)
|
|
||||||
|
|
||||||
return sequence_frame_ranges
|
|
||||||
|
|
||||||
|
|
||||||
def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa:
|
def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa:
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
|
|
||||||
if active_object.type != 'ARMATURE':
|
|
||||||
raise RuntimeError('Selected object must be an Armature')
|
|
||||||
|
|
||||||
if options.should_override_animation_data:
|
|
||||||
animation_data_object = options.animation_data_override
|
|
||||||
else:
|
|
||||||
animation_data_object = active_object
|
|
||||||
|
|
||||||
animation_data = animation_data_object.animation_data
|
|
||||||
|
|
||||||
if animation_data is None:
|
|
||||||
raise RuntimeError(f'No animation data for object \'{animation_data_object.name}\'')
|
|
||||||
|
|
||||||
# Ensure that we actually have items that we are going to be exporting.
|
|
||||||
if options.sequence_source == 'ACTIONS' and len(options.actions) == 0:
|
|
||||||
raise RuntimeError('No actions were selected for export')
|
|
||||||
elif options.sequence_source == 'TIMELINE_MARKERS' and len(options.marker_names) == 0:
|
|
||||||
raise RuntimeError('No timeline markers were selected for export')
|
|
||||||
|
|
||||||
psa = Psa()
|
psa = Psa()
|
||||||
|
|
||||||
armature_object = active_object
|
armature_object = active_object
|
||||||
@@ -177,67 +102,22 @@ def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
psa.bones.append(psa_bone)
|
psa.bones.append(psa_bone)
|
||||||
|
|
||||||
# Populate the export sequence list.
|
|
||||||
class NlaState:
|
|
||||||
def __init__(self):
|
|
||||||
self.frame_min = 0
|
|
||||||
self.frame_max = 0
|
|
||||||
self.action = None
|
|
||||||
|
|
||||||
class ExportSequence:
|
|
||||||
def __init__(self):
|
|
||||||
self.name = ''
|
|
||||||
self.nla_state = NlaState()
|
|
||||||
self.fps = 30.0
|
|
||||||
|
|
||||||
export_sequences = []
|
|
||||||
|
|
||||||
if options.sequence_source == 'ACTIONS':
|
|
||||||
for action in options.actions:
|
|
||||||
if len(action.fcurves) == 0:
|
|
||||||
continue
|
|
||||||
export_sequence = ExportSequence()
|
|
||||||
export_sequence.nla_state.action = action
|
|
||||||
export_sequence.name = get_psa_sequence_name(action, options.should_use_original_sequence_names)
|
|
||||||
frame_min, frame_max = [int(x) for x in action.frame_range]
|
|
||||||
export_sequence.nla_state.frame_min = frame_min
|
|
||||||
export_sequence.nla_state.frame_max = frame_max
|
|
||||||
export_sequence.fps = get_sequence_fps(context, options, [action])
|
|
||||||
export_sequences.append(export_sequence)
|
|
||||||
pass
|
|
||||||
elif options.sequence_source == 'TIMELINE_MARKERS':
|
|
||||||
sequence_frame_ranges = get_timeline_marker_sequence_frame_ranges(animation_data, context, options)
|
|
||||||
|
|
||||||
for name, (frame_min, frame_max) in sequence_frame_ranges.items():
|
|
||||||
export_sequence = ExportSequence()
|
|
||||||
export_sequence.name = name
|
|
||||||
export_sequence.nla_state.action = None
|
|
||||||
export_sequence.nla_state.frame_min = frame_min
|
|
||||||
export_sequence.nla_state.frame_max = frame_max
|
|
||||||
|
|
||||||
nla_strips_actions = set(
|
|
||||||
map(lambda x: x.action, get_nla_strips_in_timeframe(animation_data, frame_min, frame_max)))
|
|
||||||
export_sequence.fps = get_sequence_fps(context, options, nla_strips_actions)
|
|
||||||
export_sequences.append(export_sequence)
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Unhandled sequence source: {options.sequence_source}')
|
|
||||||
|
|
||||||
# Add prefixes and suffices to the names of the export sequences and strip whitespace.
|
# Add prefixes and suffices to the names of the export sequences and strip whitespace.
|
||||||
for export_sequence in export_sequences:
|
for export_sequence in options.sequences:
|
||||||
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
|
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
|
||||||
export_sequence.name = export_sequence.name.strip()
|
export_sequence.name = export_sequence.name.strip()
|
||||||
|
|
||||||
# Save the current action and frame so that we can restore the state once we are done.
|
# Save the current action and frame so that we can restore the state once we are done.
|
||||||
saved_frame_current = context.scene.frame_current
|
saved_frame_current = context.scene.frame_current
|
||||||
saved_action = animation_data.action
|
saved_action = options.animation_data.action
|
||||||
|
|
||||||
# Now build the PSA sequences.
|
# Now build the PSA sequences.
|
||||||
# We actually alter the timeline frame and simply record the resultant pose bone matrices.
|
# We actually alter the timeline frame and simply record the resultant pose bone matrices.
|
||||||
frame_start_index = 0
|
frame_start_index = 0
|
||||||
|
|
||||||
for export_sequence in export_sequences:
|
for export_sequence in options.sequences:
|
||||||
# Link the action to the animation data and update view layer.
|
# Link the action to the animation data and update view layer.
|
||||||
animation_data.action = export_sequence.nla_state.action
|
options.animation_data.action = export_sequence.nla_state.action
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
frame_min = export_sequence.nla_state.frame_min
|
frame_min = export_sequence.nla_state.frame_min
|
||||||
@@ -292,7 +172,7 @@ def build_psa(context: bpy.types.Context, options: PsaBuildOptions) -> Psa:
|
|||||||
psa.sequences[export_sequence.name] = psa_sequence
|
psa.sequences[export_sequence.name] = psa_sequence
|
||||||
|
|
||||||
# Restore the previous action & frame.
|
# Restore the previous action & frame.
|
||||||
animation_data.action = saved_action
|
options.animation_data.action = saved_action
|
||||||
context.scene.frame_set(saved_frame_current)
|
context.scene.frame_set(saved_frame_current)
|
||||||
|
|
||||||
return psa
|
return psa
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import fnmatch
|
import fnmatch
|
||||||
import sys
|
import sys
|
||||||
from typing import Type
|
from typing import Type, Dict
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, FloatProperty, IntProperty, PointerProperty, \
|
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, FloatProperty, IntProperty, PointerProperty, \
|
||||||
StringProperty
|
StringProperty
|
||||||
from bpy.types import Action, Operator, PropertyGroup, UIList
|
from bpy.types import Action, Operator, PropertyGroup, UIList, Context
|
||||||
from bpy_extras.io_utils import ExportHelper
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
|
||||||
from .builder import PsaBuildOptions, build_psa
|
from .builder import PsaBuildOptions, PsaExportSequence, build_psa
|
||||||
from .data import *
|
from .data import *
|
||||||
from ..helpers import *
|
from ..helpers import *
|
||||||
from ..types import BoneGroupListItem
|
from ..types import BoneGroupListItem
|
||||||
@@ -38,6 +38,9 @@ class PsaExportActionListItem(PropertyGroup):
|
|||||||
action: PointerProperty(type=Action)
|
action: PointerProperty(type=Action)
|
||||||
name: StringProperty()
|
name: StringProperty()
|
||||||
is_selected: BoolProperty(default=False)
|
is_selected: BoolProperty(default=False)
|
||||||
|
frame_start: IntProperty(options={'HIDDEN'})
|
||||||
|
frame_end: IntProperty(options={'HIDDEN'})
|
||||||
|
is_pose_marker: BoolProperty(options={'HIDDEN'})
|
||||||
|
|
||||||
|
|
||||||
class PsaExportTimelineMarkerListItem(PropertyGroup):
|
class PsaExportTimelineMarkerListItem(PropertyGroup):
|
||||||
@@ -159,6 +162,10 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
name='Show assets',
|
name='Show assets',
|
||||||
options=empty_set,
|
options=empty_set,
|
||||||
description='Show actions that belong to an asset library')
|
description='Show actions that belong to an asset library')
|
||||||
|
sequence_filter_pose_marker: BoolProperty(
|
||||||
|
default=False,
|
||||||
|
name='Show pose markers',
|
||||||
|
options=empty_set)
|
||||||
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=empty_set)
|
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=empty_set)
|
||||||
|
|
||||||
|
|
||||||
@@ -170,6 +177,69 @@ def is_bone_filter_mode_item_available(context, identifier):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_timeline_marker_sequence_frame_ranges(animation_data: AnimData, context: Context, marker_names: List[str], should_trim_timeline_marker_sequences: bool) -> Dict:
|
||||||
|
# Timeline markers need to be sorted so that we can determine the sequence start and end positions.
|
||||||
|
sequence_frame_ranges = dict()
|
||||||
|
sorted_timeline_markers = list(sorted(context.scene.timeline_markers, key=lambda x: x.frame))
|
||||||
|
sorted_timeline_marker_names = list(map(lambda x: x.name, sorted_timeline_markers))
|
||||||
|
|
||||||
|
for marker_name in marker_names:
|
||||||
|
marker = context.scene.timeline_markers[marker_name]
|
||||||
|
frame_min = marker.frame
|
||||||
|
# Determine the final frame of the sequence based on the next marker.
|
||||||
|
# If no subsequent marker exists, use the maximum frame_end from all NLA strips.
|
||||||
|
marker_index = sorted_timeline_marker_names.index(marker_name)
|
||||||
|
next_marker_index = marker_index + 1
|
||||||
|
frame_max = 0
|
||||||
|
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_max = sorted_timeline_markers[next_marker_index].frame
|
||||||
|
if should_trim_timeline_marker_sequences:
|
||||||
|
nla_strips = get_nla_strips_in_timeframe(animation_data, marker.frame, frame_max)
|
||||||
|
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.
|
||||||
|
for nla_track in animation_data.nla_tracks:
|
||||||
|
if nla_track.mute:
|
||||||
|
continue
|
||||||
|
for strip in nla_track.strips:
|
||||||
|
frame_max = max(frame_max, strip.frame_end)
|
||||||
|
|
||||||
|
if frame_min > frame_max:
|
||||||
|
continue
|
||||||
|
|
||||||
|
sequence_frame_ranges[marker_name] = int(frame_min), int(frame_max)
|
||||||
|
|
||||||
|
return sequence_frame_ranges
|
||||||
|
|
||||||
|
|
||||||
|
def get_sequence_fps(context: Context, fps_source: str, fps_custom: float, actions: Iterable[Action]) -> float:
|
||||||
|
if fps_source == 'SCENE':
|
||||||
|
return context.scene.render.fps
|
||||||
|
if fps_source == 'CUSTOM':
|
||||||
|
return fps_custom
|
||||||
|
elif fps_source == 'ACTION_METADATA':
|
||||||
|
# Get the minimum value of action metadata FPS values.
|
||||||
|
fps_list = []
|
||||||
|
for action in filter(lambda x: 'psa_sequence_fps' in x, actions):
|
||||||
|
fps = action['psa_sequence_fps']
|
||||||
|
if type(fps) == int or type(fps) == float:
|
||||||
|
fps_list.append(fps)
|
||||||
|
if len(fps_list) > 0:
|
||||||
|
return min(fps_list)
|
||||||
|
else:
|
||||||
|
# No valid action metadata to use, fallback to scene FPS
|
||||||
|
return context.scene.render.fps
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f'Invalid FPS source "{fps_source}"')
|
||||||
|
|
||||||
|
|
||||||
class PsaExportOperator(Operator, ExportHelper):
|
class PsaExportOperator(Operator, ExportHelper):
|
||||||
bl_idname = 'psa_export.operator'
|
bl_idname = 'psa_export.operator'
|
||||||
bl_label = 'Export'
|
bl_label = 'Export'
|
||||||
@@ -313,7 +383,24 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
item = pg.action_list.add()
|
item = pg.action_list.add()
|
||||||
item.action = action
|
item.action = action
|
||||||
item.name = action.name
|
item.name = action.name
|
||||||
|
item.frame_start = int(action.frame_range[0])
|
||||||
|
item.frame_end = int(action.frame_range[1])
|
||||||
item.is_selected = False
|
item.is_selected = False
|
||||||
|
item.is_pose_marker = False
|
||||||
|
# Pose markers are not guaranteed to be in frame-order, so make sure that they are.
|
||||||
|
pose_markers = sorted(action.pose_markers, key=lambda x: x.frame)
|
||||||
|
print([x.name for x in pose_markers])
|
||||||
|
for pose_marker_index, pose_marker in enumerate(pose_markers):
|
||||||
|
item = pg.action_list.add()
|
||||||
|
item.action = action
|
||||||
|
item.name = pose_marker.name
|
||||||
|
item.is_selected = False
|
||||||
|
item.is_pose_marker = True
|
||||||
|
item.frame_start = pose_marker.frame
|
||||||
|
if pose_marker_index + 1 < len(pose_markers):
|
||||||
|
item.frame_end = pose_markers[pose_marker_index + 1].frame
|
||||||
|
else:
|
||||||
|
item.frame_end = int(action.frame_range[1])
|
||||||
|
|
||||||
update_action_names(context)
|
update_action_names(context)
|
||||||
|
|
||||||
@@ -339,21 +426,69 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = getattr(context.scene, 'psa_export')
|
pg = getattr(context.scene, 'psa_export')
|
||||||
|
|
||||||
actions = [x.action for x in pg.action_list if x.is_selected]
|
# TODO: move this up the call chain
|
||||||
|
# Populate the export sequence list.
|
||||||
|
active_object = context.view_layer.objects.active
|
||||||
|
|
||||||
|
# Ensure that we actually have items that we are going to be exporting.
|
||||||
|
if pg.sequence_source == 'ACTIONS' and len(pg.action_list) == 0:
|
||||||
|
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')
|
||||||
|
|
||||||
|
if active_object.type != 'ARMATURE':
|
||||||
|
raise RuntimeError('Selected object must be an Armature')
|
||||||
|
|
||||||
|
if pg.should_override_animation_data:
|
||||||
|
animation_data_object = pg.animation_data_override
|
||||||
|
else:
|
||||||
|
animation_data_object = active_object
|
||||||
|
|
||||||
|
animation_data = animation_data_object.animation_data
|
||||||
|
|
||||||
|
if animation_data is None:
|
||||||
|
raise RuntimeError(f'No animation data for object \'{animation_data_object.name}\'')
|
||||||
|
|
||||||
|
export_sequences: List[PsaExportSequence] = []
|
||||||
|
|
||||||
|
# actions = [x.action for x in pg.action_list if x.is_selected]
|
||||||
|
# marker_names =
|
||||||
|
|
||||||
|
if pg.sequence_source == 'ACTIONS':
|
||||||
|
for action in filter(lambda x: x.is_selected, pg.action_list):
|
||||||
|
if len(action.action.fcurves) == 0:
|
||||||
|
continue
|
||||||
|
export_sequence = PsaExportSequence()
|
||||||
|
export_sequence.nla_state.action = action.action
|
||||||
|
export_sequence.name = action.name
|
||||||
|
export_sequence.nla_state.frame_min = action.frame_start
|
||||||
|
export_sequence.nla_state.frame_max = action.frame_end
|
||||||
|
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, [action.action])
|
||||||
|
export_sequences.append(export_sequence)
|
||||||
|
elif pg.sequence_source == 'TIMELINE_MARKERS':
|
||||||
marker_names = [x.name for x in pg.marker_list if x.is_selected]
|
marker_names = [x.name for x in pg.marker_list if x.is_selected]
|
||||||
|
sequence_frame_ranges = get_timeline_marker_sequence_frame_ranges(animation_data, context, marker_names, pg.should_trim_timeline_marker_sequences)
|
||||||
|
|
||||||
|
for name, (frame_min, frame_max) in sequence_frame_ranges.items():
|
||||||
|
export_sequence = PsaExportSequence()
|
||||||
|
export_sequence.name = name
|
||||||
|
export_sequence.nla_state.action = None
|
||||||
|
export_sequence.nla_state.frame_min = frame_min
|
||||||
|
export_sequence.nla_state.frame_max = frame_max
|
||||||
|
|
||||||
|
nla_strips_actions = set(
|
||||||
|
map(lambda x: x.action, get_nla_strips_in_timeframe(animation_data, frame_min, frame_max)))
|
||||||
|
export_sequence.fps = get_sequence_fps(context, pg.fps_source, pg.fps_custom, nla_strips_actions)
|
||||||
|
export_sequences.append(export_sequence)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unhandled sequence source: {pg.sequence_source}')
|
||||||
|
|
||||||
options = PsaBuildOptions()
|
options = PsaBuildOptions()
|
||||||
options.should_override_animation_data = pg.should_override_animation_data
|
options.animation_data = animation_data
|
||||||
options.animation_data_override = pg.animation_data_override
|
options.sequences = export_sequences
|
||||||
options.fps_source = pg.fps_source
|
|
||||||
options.fps_custom = pg.fps_custom
|
|
||||||
options.sequence_source = pg.sequence_source
|
|
||||||
options.actions = actions
|
|
||||||
options.marker_names = marker_names
|
|
||||||
options.bone_filter_mode = pg.bone_filter_mode
|
options.bone_filter_mode = pg.bone_filter_mode
|
||||||
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
options.bone_group_indices = [x.index for x in pg.bone_group_list if x.is_selected]
|
||||||
options.should_use_original_sequence_names = pg.should_use_original_sequence_names
|
options.should_use_original_sequence_names = pg.should_use_original_sequence_names
|
||||||
options.should_trim_timeline_marker_sequences = pg.should_trim_timeline_marker_sequences
|
|
||||||
options.should_ignore_bone_name_restrictions = pg.should_ignore_bone_name_restrictions
|
options.should_ignore_bone_name_restrictions = pg.should_ignore_bone_name_restrictions
|
||||||
options.sequence_name_prefix = pg.sequence_name_prefix
|
options.sequence_name_prefix = pg.sequence_name_prefix
|
||||||
options.sequence_name_suffix = pg.sequence_name_suffix
|
options.sequence_name_suffix = pg.sequence_name_suffix
|
||||||
@@ -391,6 +526,11 @@ def filter_sequences(pg: PsaExportPropertyGroup, sequences) -> List[int]:
|
|||||||
if hasattr(sequence, 'action') and sequence.action.asset_data is not None:
|
if hasattr(sequence, 'action') and sequence.action.asset_data is not None:
|
||||||
flt_flags[i] &= ~bitflag_filter_item
|
flt_flags[i] &= ~bitflag_filter_item
|
||||||
|
|
||||||
|
if not pg.sequence_filter_pose_marker:
|
||||||
|
for i, sequence in enumerate(sequences):
|
||||||
|
if hasattr(sequence, 'is_pose_marker') and sequence.is_pose_marker:
|
||||||
|
flt_flags[i] &= ~bitflag_filter_item
|
||||||
|
|
||||||
return flt_flags
|
return flt_flags
|
||||||
|
|
||||||
|
|
||||||
@@ -410,9 +550,14 @@ class PSA_UL_ExportSequenceList(UIList):
|
|||||||
self.use_filter_show = True
|
self.use_filter_show = True
|
||||||
|
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
|
is_pose_marker = hasattr(item, 'is_pose_marker') and item.is_pose_marker
|
||||||
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
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.asset_data is not None:
|
||||||
layout.label(text='', icon='ASSET_MANAGER')
|
layout.label(text='', icon='ASSET_MANAGER')
|
||||||
|
if is_pose_marker:
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.alignment = 'RIGHT'
|
||||||
|
row.label(text=item.action.name, icon='PMARKER')
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
pg = getattr(context.scene, 'psa_export')
|
pg = getattr(context.scene, 'psa_export')
|
||||||
@@ -425,12 +570,14 @@ class PSA_UL_ExportSequenceList(UIList):
|
|||||||
if pg.sequence_source == 'ACTIONS':
|
if pg.sequence_source == 'ACTIONS':
|
||||||
subrow = row.row(align=True)
|
subrow = row.row(align=True)
|
||||||
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
||||||
|
subrow.prop(pg, 'sequence_filter_pose_marker', icon_only=True, icon='PMARKER')
|
||||||
|
|
||||||
def filter_items(self, context, data, prop):
|
def filter_items(self, context, data, prop):
|
||||||
pg = getattr(context.scene, 'psa_export')
|
pg = getattr(context.scene, 'psa_export')
|
||||||
actions = getattr(data, prop)
|
actions = getattr(data, prop)
|
||||||
flt_flags = filter_sequences(pg, actions)
|
flt_flags = filter_sequences(pg, actions)
|
||||||
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
|
# flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
|
||||||
|
flt_neworder = list(range(len(actions)))
|
||||||
return flt_flags, flt_neworder
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user