From 04503ed2825d5a7ce3de7dfe639cf706cb8ea761 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sun, 13 Feb 2022 16:08:17 -0800 Subject: [PATCH] Added the ability to prefix and suffix sequence names on PSA export --- io_scene_psk_psa/psa/builder.py | 8 ++- io_scene_psk_psa/psa/exporter.py | 108 ++++++++++++++++++------------- io_scene_psk_psa/psa/importer.py | 2 +- 3 files changed, 71 insertions(+), 47 deletions(-) diff --git a/io_scene_psk_psa/psa/builder.py b/io_scene_psk_psa/psa/builder.py index 1578796..d093206 100644 --- a/io_scene_psk_psa/psa/builder.py +++ b/io_scene_psk_psa/psa/builder.py @@ -12,6 +12,8 @@ class PsaBuilderOptions(object): self.bone_group_indices = [] self.should_use_original_sequence_names = False self.should_trim_timeline_marker_sequences = True + self.sequence_name_prefix = '' + self.sequence_name_suffix = '' class PsaBuilder(object): @@ -129,10 +131,14 @@ class PsaBuilder(object): else: raise ValueError(f'Unhandled sequence source: {options.sequence_source}') - frame_start_index = 0 + # Add prefixes and suffices to the names of the export sequences and strip whitespace. + for export_sequence in export_sequences: + export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'.strip() # Now build the PSA sequences. # We actually alter the timeline frame and simply record the resultant pose bone matrices. + frame_start_index = 0 + for export_sequence in export_sequences: armature.animation_data.action = export_sequence.action context.view_layer.update() diff --git a/io_scene_psk_psa/psa/exporter.py b/io_scene_psk_psa/psa/exporter.py index e03bb4a..9686a6d 100644 --- a/io_scene_psk_psa/psa/exporter.py +++ b/io_scene_psk_psa/psa/exporter.py @@ -91,6 +91,9 @@ class PsaExportPropertyGroup(PropertyGroup): bone_group_list: CollectionProperty(type=BoneGroupListItem) bone_group_list_index: IntProperty(default=0, name='', description='') should_use_original_sequence_names: BoolProperty(default=False, name='Original Names', description='If the action was imported from the PSA Import panel, the original name of the sequence will be used instead of the Blender action name', update=should_use_original_sequence_names_updated) + should_trim_timeline_marker_sequences: BoolProperty(default=True, name='Trim Sequences', description='Frames without NLA track information at the boundaries of timeline markers will be excluded from the exported sequences') + sequence_name_prefix: StringProperty(name='Prefix') + sequence_name_suffix: StringProperty(name='Suffix') def is_bone_filter_mode_item_available(context, identifier): @@ -122,29 +125,36 @@ class PsaExportOperator(Operator, ExportHelper): pg = context.scene.psa_export # SOURCE - layout.prop(pg, 'sequence_source', text='Source') + layout.prop(pg, 'sequence_source', text='Source', icon='ACTION' if pg.sequence_source == 'ACTIONS' else 'MARKER') + + # SELECT ALL/NONE + row = layout.row(align=True) + row.label(text='Select') + row.operator(PsaExportActionsSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT') + row.operator(PsaExportActionsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT') # ACTIONS if pg.sequence_source == 'ACTIONS': - layout.label(text='Actions', icon='ACTION') - row = layout.row(align=True) - row.label(text='Select') - row.operator(PsaExportActionsSelectAll.bl_idname, text='All') - row.operator(PsaExportActionsDeselectAll.bl_idname, text='None') - row = layout.row() rows = max(3, min(len(pg.action_list), 10)) - row.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows) + layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows) - col = layout.column(heading="Options") + col = layout.column() col.use_property_split = True col.use_property_decorate = False col.prop(pg, 'should_use_original_sequence_names') - elif pg.sequence_source == 'TIMELINE_MARKERS': - layout.label(text='Markers', icon='MARKER') + col.prop(pg, 'sequence_name_prefix') + col.prop(pg, 'sequence_name_suffix') - row = layout.row() + elif pg.sequence_source == 'TIMELINE_MARKERS': rows = max(3, min(len(pg.marker_list), 10)) - row.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows) + layout.template_list('PSA_UL_ExportTimelineMarkerList', '', pg, 'marker_list', pg, 'marker_list_index', rows=rows) + + col = layout.column() + col.use_property_split = True + col.use_property_decorate = False + col.prop(pg, 'should_trim_timeline_marker_sequences') + col.prop(pg, 'sequence_name_prefix') + col.prop(pg, 'sequence_name_suffix') # Determine if there is going to be a naming conflict and display an error, if so. selected_items = [x for x in pg.action_list if x.is_selected] @@ -158,24 +168,16 @@ class PsaExportOperator(Operator, ExportHelper): layout.separator() # BONES - box = layout.row() - box.label(text='Bones', icon='BONE_DATA') - bone_filter_mode_items = pg.bl_rna.properties['bone_filter_mode'].enum_items_static - row = box.row(align=True) - - for item in bone_filter_mode_items: - identifier = item.identifier - item_layout = row.row(align=True) - item_layout.prop_enum(pg, 'bone_filter_mode', item.identifier) - item_layout.enabled = is_bone_filter_mode_item_available(context, identifier) + row = layout.row(align=True) + row.prop(pg, 'bone_filter_mode', text='Bones') if pg.bone_filter_mode == 'BONE_GROUPS': - rows = max(3, min(len(pg.bone_group_list), 10)) - layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows) row = layout.row(align=True) row.label(text='Select') - row.operator(PsaExportBoneGroupsSelectAll.bl_idname, text='All') - row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None') + row.operator(PsaExportBoneGroupsSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT') + row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT') + rows = max(3, min(len(pg.bone_group_list), 10)) + layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows) def is_action_for_armature(self, action): if len(action.fcurves) == 0: @@ -246,6 +248,10 @@ class PsaExportOperator(Operator, ExportHelper): 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.should_use_original_sequence_names = pg.should_use_original_sequence_names + 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 + builder = PsaBuilder() try: @@ -261,9 +267,7 @@ class PsaExportOperator(Operator, ExportHelper): class PSA_UL_ExportTimelineMarkerList(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - layout.alignment = 'LEFT' - layout.prop(item, 'is_selected', icon_only=True) - layout.label(text=item.marker_name) + layout.prop(item, 'is_selected', icon_only=True, text=item.marker_name) def filter_items(self, context, data, property): actions = getattr(data, property) @@ -282,9 +286,7 @@ class PSA_UL_ExportTimelineMarkerList(UIList): class PSA_UL_ExportActionList(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): - layout.alignment = 'LEFT' - layout.prop(item, 'is_selected', icon_only=True) - layout.label(text=item.action_name) + layout.prop(item, 'is_selected', icon_only=True, text=item.action_name) def filter_items(self, context, data, property): actions = getattr(data, property) @@ -302,41 +304,57 @@ class PSA_UL_ExportActionList(UIList): class PsaExportActionsSelectAll(Operator): - bl_idname = 'psa_export.actions_select_all' + bl_idname = 'psa_export.sequences_select_all' bl_label = 'Select All' - bl_description = 'Select all actions' + bl_description = 'Select all sequences' bl_options = {'INTERNAL'} @classmethod - def poll(cls, context): + def get_item_list(cls, context): pg = context.scene.psa_export - item_list = pg.action_list + if pg.sequence_source == 'ACTIONS': + return pg.action_list + elif pg.sequence_source == 'TIMELINE_MARKERS': + return pg.marker_list + return None + + @classmethod + def poll(cls, context): + item_list = cls.get_item_list(context) has_unselected_items = any(map(lambda item: not item.is_selected, item_list)) return len(item_list) > 0 and has_unselected_items def execute(self, context): - pg = context.scene.psa_export - for item in pg.action_list: + item_list = self.get_item_list(context) + for item in item_list: item.is_selected = True return {'FINISHED'} class PsaExportActionsDeselectAll(Operator): - bl_idname = 'psa_export.actions_deselect_all' + bl_idname = 'psa_export.sequences_deselect_all' bl_label = 'Deselect All' - bl_description = 'Deselect all actions' + bl_description = 'Deselect all sequences' bl_options = {'INTERNAL'} @classmethod - def poll(cls, context): + def get_item_list(cls, context): pg = context.scene.psa_export - item_list = pg.action_list + if pg.sequence_source == 'ACTIONS': + return pg.action_list + elif pg.sequence_source == 'TIMELINE_MARKERS': + return pg.marker_list + return None + + @classmethod + def poll(cls, context): + item_list = cls.get_item_list(context) has_selected_items = any(map(lambda item: item.is_selected, item_list)) return len(item_list) > 0 and has_selected_items def execute(self, context): - pg = context.scene.psa_export - for item in pg.action_list: + item_list = self.get_item_list(context) + for item in item_list: item.is_selected = False return {'FINISHED'} diff --git a/io_scene_psk_psa/psa/importer.py b/io_scene_psk_psa/psa/importer.py index 871a22a..3a4093d 100644 --- a/io_scene_psk_psa/psa/importer.py +++ b/io_scene_psk_psa/psa/importer.py @@ -153,7 +153,7 @@ class PsaImporter(object): sequence_data_matrix[frame_index, bone_index] = calculate_fcurve_data(import_bone, key_data) # Clean the keyframe data. This is accomplished by writing zeroes to the write matrix when there is an - # insufficiently large change in the data from frame-to-frame. + # insufficiently large change in the data from the last written frame. if options.should_clean_keys: threshold = 0.001 for bone_index, import_bone in enumerate(import_bones):