From 69fc70239365e1c4f87ad880139d163e02ee37c2 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Tue, 20 Jan 2026 00:53:11 -0800 Subject: [PATCH] Refactor of disparate export sequences types to use a unified type --- io_scene_psk_psa/psa/export/operators.py | 4 +- io_scene_psk_psa/psa/export/properties.py | 72 ++++++++++------------ io_scene_psk_psa/psa/export/properties.pyi | 35 +++++------ io_scene_psk_psa/psa/export/ui.py | 8 +-- 4 files changed, 50 insertions(+), 69 deletions(-) diff --git a/io_scene_psk_psa/psa/export/operators.py b/io_scene_psk_psa/psa/export/operators.py index 6ad4d0f..02a84f9 100644 --- a/io_scene_psk_psa/psa/export/operators.py +++ b/io_scene_psk_psa/psa/export/operators.py @@ -10,10 +10,8 @@ from bpy_extras.io_utils import ExportHelper from .properties import ( PSA_PG_export, - PSA_PG_export_action_list_item, PsaExportMixin, PsaExportSequenceMixin, - PsaExportSequenceWithActionMixin, filter_sequences, get_sequences_from_name_and_frame_range, ) @@ -261,7 +259,7 @@ def get_sequences_from_action_pose_markers( yield from get_sequences_from_name_and_frame_range(sequence_name, frame_start, frame_end) -def get_visible_sequences(pg: PsaExportMixin, sequences) -> List[PSA_PG_export_action_list_item]: +def get_visible_sequences(pg: PsaExportMixin, sequences) -> List[PsaExportSequenceMixin]: visible_sequences = [] for i, flag in enumerate(filter_sequences(pg, sequences)): if bool(flag & (1 << 30)): diff --git a/io_scene_psk_psa/psa/export/properties.py b/io_scene_psk_psa/psa/export/properties.py index 6116fd8..cf1cc9d 100644 --- a/io_scene_psk_psa/psa/export/properties.py +++ b/io_scene_psk_psa/psa/export/properties.py @@ -21,43 +21,38 @@ from ...shared.types import TransformMixin, ExportSpaceMixin, PsxBoneExportMixin def psa_export_property_group_animation_data_override_poll(_context, obj): return obj.animation_data is not None + class PsaExportSequenceMixin(PropertyGroup): name: StringProperty(name='Name') is_selected: BoolProperty(name='Selected', default=True) frame_start: IntProperty(name='Start Frame', options={'HIDDEN'}) frame_end: IntProperty(name='End Frame', options={'HIDDEN'}) group: StringProperty(name='Group') + action_name: StringProperty(name='Action Name',default='', options={'HIDDEN'}) + armature_object_name: StringProperty(name='Armature Object Name',default='', options={'HIDDEN'}) + marker_index: IntProperty(name='Marker Index',default=-1, options={'HIDDEN'}) + is_pose_marker: BoolProperty(name='Is Pose Marker',default=False, options={'HIDDEN'}) + + @property + def action(self) -> Action | None: + """Get the action associated with this sequence (if any).""" + return bpy.data.actions.get(self.action_name) if self.action_name else None + + @property + def armature_object(self) -> Object | None: + """Get the armature object associated with this sequence (if any).""" + return bpy.data.objects.get(self.armature_object_name) if self.armature_object_name else None + + @property + def is_reversed(self) -> bool: + """Check if the sequence is reversed (end frame before start frame).""" + return self.frame_end < self.frame_start def __hash__(self) -> int: return hash(self.name) -class PsaExportSequenceWithActionMixin(PsaExportSequenceMixin): - action_name: StringProperty() - @property - def action(self) -> Optional[Action]: - return bpy.data.actions.get(self.action_name) - -class PSA_PG_export_action_list_item(PsaExportSequenceWithActionMixin): - is_pose_marker: BoolProperty(options={'HIDDEN'}) - - -class PSA_PG_export_active_action_list_item(PsaExportSequenceWithActionMixin): - armature_object_name: StringProperty() - - @property - def armature_object(self) -> Optional[Object]: - return bpy.data.objects.get(self.armature_object_name) - - def __hash__(self) -> int: - return super().__hash__() - - -class PSA_PG_export_timeline_marker(PsaExportSequenceMixin): - marker_index: IntProperty() - - -class PSA_PG_export_nla_strip_list_item(PsaExportSequenceWithActionMixin): +class PSA_PG_export_sequence(PsaExportSequenceMixin): pass @@ -105,7 +100,7 @@ def nla_track_update_cb(self: 'PSA_PG_export', context: Context) -> None: nla_track = animation_data.nla_tracks[self.nla_track_index] for nla_strip in nla_track.strips: for sequence_name, frame_start, frame_end in get_sequences_from_name_and_frame_range(nla_strip.name, nla_strip.frame_start, nla_strip.frame_end): - strip: PSA_PG_export_nla_strip_list_item = self.nla_strip_list.add() + strip: PSA_PG_export_sequence = self.nla_strip_list.add() strip.action_name = nla_strip.action strip.name = sequence_name strip.frame_start = frame_start @@ -203,13 +198,13 @@ class PsaExportMixin(PropertyGroup, TransformMixin, ExportSpaceMixin, PsxBoneExp ) compression_ratio_custom: FloatProperty(default=1.0, min=0.0, max=1.0, subtype='FACTOR', description='The key sampling ratio of the exported sequence.\n\nA compression ratio of 1.0 will export all frames, while a compression ratio of 0.5 will export half of the frames') - action_list: CollectionProperty(type=PSA_PG_export_action_list_item) + action_list: CollectionProperty(type=PSA_PG_export_sequence) action_list_index: IntProperty(default=0) - marker_list: CollectionProperty(type=PSA_PG_export_timeline_marker) + marker_list: CollectionProperty(type=PSA_PG_export_sequence) marker_list_index: IntProperty(default=0) - nla_strip_list: CollectionProperty(type=PSA_PG_export_nla_strip_list_item) + nla_strip_list: CollectionProperty(type=PSA_PG_export_sequence) nla_strip_list_index: IntProperty(default=0) - active_action_list: CollectionProperty(type=PSA_PG_export_active_action_list_item) + active_action_list: CollectionProperty(type=PSA_PG_export_sequence) active_action_list_index: IntProperty(default=0) sequence_name_prefix: StringProperty(name='Prefix', options=set()) @@ -264,7 +259,7 @@ class PSA_PG_export(PsaExportMixin): pass -def filter_sequences(pg: PsaExportMixin, sequences: Sequence[PsaExportSequenceMixin]) -> List[int]: +def filter_sequences(pg: PsaExportMixin, sequences: Sequence[PsaExportSequenceMixin]) -> list[int]: bitflag_filter_item = 1 << 30 flt_flags = [bitflag_filter_item] * len(sequences) @@ -279,31 +274,26 @@ def filter_sequences(pg: PsaExportMixin, sequences: Sequence[PsaExportSequenceMi for i, sequence in enumerate(sequences): flt_flags[i] ^= bitflag_filter_item - # TODO: perhaps just make one type that has all of the possible data types? hasattr is very flakey. - # we could just add the "type" as a variable and switch on that for different behaviors. if not pg.sequence_filter_asset: for i, sequence in enumerate(sequences): - if hasattr(sequence, 'action') and sequence.action is not None and sequence.action.asset_data is not None: + if 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: for i, sequence in enumerate(sequences): - if hasattr(sequence, 'is_pose_marker') and sequence.is_pose_marker: + if sequence.is_pose_marker: flt_flags[i] &= ~bitflag_filter_item if not pg.sequence_filter_reversed: for i, sequence in enumerate(sequences): - if sequence.frame_start > sequence.frame_end: + if sequence.is_reversed: flt_flags[i] &= ~bitflag_filter_item return flt_flags _classes = ( - PSA_PG_export_action_list_item, - PSA_PG_export_timeline_marker, - PSA_PG_export_nla_strip_list_item, - PSA_PG_export_active_action_list_item, + PSA_PG_export_sequence, PSA_PG_export, ) diff --git a/io_scene_psk_psa/psa/export/properties.pyi b/io_scene_psk_psa/psa/export/properties.pyi index ce4d616..20149ae 100644 --- a/io_scene_psk_psa/psa/export/properties.pyi +++ b/io_scene_psk_psa/psa/export/properties.pyi @@ -8,30 +8,23 @@ class PsaExportSequenceMixin(PropertyGroup): frame_start: int frame_end: int group: str - -class PsaExportSequenceWithActionMixin(PsaExportSequenceMixin): action_name: str - - @property - def action(self) -> Action | None: - pass - -class PSA_PG_export_action_list_item(PsaExportSequenceWithActionMixin): + armature_object_name: str + marker_index: int is_pose_marker: bool - -class PSA_PG_export_active_action_list_item(PsaExportSequenceWithActionMixin): - armature_object_name: str + @property + def action(self) -> Action | None: ... @property - def armature_object(self) -> Object | None: - pass + def armature_object(self) -> Object | None: ... + + @property + def is_reversed(self) -> bool: ... -class PSA_PG_export_timeline_marker(PsaExportSequenceMixin): - marker_index: int - -class PSA_PG_export_nla_strip_list_item(PsaExportSequenceWithActionMixin): +class PSA_PG_export_sequence(PsaExportSequenceMixin): + """Concrete type for PSA export sequences.""" pass @@ -43,13 +36,13 @@ class PsaExportMixin(PropertyGroup, TransformMixin, ExportSpaceMixin, PsxBoneExp fps_custom: float compression_ratio_source: str compression_ratio_custom: float - action_list: BpyCollectionProperty[PSA_PG_export_action_list_item] + action_list: BpyCollectionProperty[PSA_PG_export_sequence] action_list_index: int - marker_list: BpyCollectionProperty[PSA_PG_export_timeline_marker] + marker_list: BpyCollectionProperty[PSA_PG_export_sequence] marker_list_index: int - nla_strip_list: BpyCollectionProperty[PSA_PG_export_nla_strip_list_item] + nla_strip_list: BpyCollectionProperty[PSA_PG_export_sequence] nla_strip_list_index: int - active_action_list: BpyCollectionProperty[PSA_PG_export_active_action_list_item] + active_action_list: BpyCollectionProperty[PSA_PG_export_sequence] active_action_list_index: int sequence_name_prefix: str sequence_name_suffix: str diff --git a/io_scene_psk_psa/psa/export/ui.py b/io_scene_psk_psa/psa/export/ui.py index 62e4121..376e441 100644 --- a/io_scene_psk_psa/psa/export/ui.py +++ b/io_scene_psk_psa/psa/export/ui.py @@ -2,7 +2,7 @@ from typing import cast as typing_cast from bpy.types import UIList -from .properties import PSA_PG_export_action_list_item, filter_sequences +from .properties import PsaExportSequenceMixin, filter_sequences class PSA_UL_export_sequences(UIList): @@ -14,7 +14,7 @@ class PSA_UL_export_sequences(UIList): self.use_filter_show = True def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - item = typing_cast(PSA_PG_export_action_list_item, item) + item = typing_cast(PsaExportSequenceMixin, 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) @@ -24,9 +24,9 @@ class PSA_UL_export_sequences(UIList): row = layout.row(align=True) row.alignment = 'RIGHT' - row.label(text=str(abs(item.frame_end - item.frame_start) + 1), icon='FRAME_PREV' if item.frame_end < item.frame_start else 'KEYFRAME') + row.label(text=str(abs(item.frame_end - item.frame_start) + 1), icon='FRAME_PREV' if item.is_reversed else 'KEYFRAME') - if hasattr(item, 'armature_object') and item.armature_object is not None: + if item.armature_object is not None: row.label(text=item.armature_object.name, icon='ARMATURE_DATA') # row.label(text=item.action.name, icon='PMARKER' if is_pose_marker else 'ACTION_DATA')