From 41edd61f3db76e3c56dd01d75fb8d02963f73301 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sat, 22 Jan 2022 21:44:36 -0800 Subject: [PATCH] * Considerable clean-up of existing code. * Moved the PSA import to the properties panel. --- io_export_psk_psa/__init__.py | 36 ++-------- io_export_psk_psa/helpers.py | 2 +- io_export_psk_psa/psa/exporter.py | 12 +++- io_export_psk_psa/psa/importer.py | 112 +++++++++++++++++------------- io_export_psk_psa/psk/importer.py | 7 +- 5 files changed, 88 insertions(+), 81 deletions(-) diff --git a/io_export_psk_psa/__init__.py b/io_export_psk_psa/__init__.py index d29d0e4..de6c7be 100644 --- a/io_export_psk_psa/__init__.py +++ b/io_export_psk_psa/__init__.py @@ -42,33 +42,15 @@ else: from .psa import reader as psa_reader from .psa import importer as psa_importer + import bpy from bpy.props import PointerProperty - -# TODO: have the individual files emit a __classes__ field or something we can update it locally instead of explicitly declaring it here. -classes = [] -classes.extend(psx_types.__classes__) -classes.extend(psk_exporter.__classes__) -classes.extend([ - psk_importer.PskImportOperator, - psa_importer.PsaImportOperator, - psa_importer.PsaImportFileSelectOperator, - psa_exporter.PSA_UL_ExportActionList, - # psa_exporter.PSA_UL_ExportBoneGroupList, - psa_importer.PSA_UL_ImportActionList, - psa_importer.PsaImportActionListItem, - psa_importer.PsaImportPsaBoneItem, - psa_importer.PsaImportSelectAll, - psa_importer.PsaImportDeselectAll, - psa_importer.PSA_PT_ImportPanel, - psa_importer.PsaImportPropertyGroup, - psa_exporter.PsaExportOperator, - psa_exporter.PsaExportSelectAll, - psa_exporter.PsaExportDeselectAll, - psa_exporter.PsaExportActionListItem, - psa_exporter.PsaExportPropertyGroup, -]) +classes = psx_types.__classes__ + \ + psk_importer.__classes__ + \ + psk_exporter.__classes__ + \ + psa_exporter.__classes__ + \ + psa_importer.__classes__ def psk_export_menu_func(self, context): @@ -83,17 +65,12 @@ def psa_export_menu_func(self, context): self.layout.operator(psa_exporter.PsaExportOperator.bl_idname, text='Unreal PSA (.psa)') -def psa_import_menu_func(self, context): - self.layout.operator(psa_importer.PsaImportOperator.bl_idname, text='Unreal PSA (.psa)') - - def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func) bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func) bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func) - bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func) bpy.types.Scene.psa_import = PointerProperty(type=psa_importer.PsaImportPropertyGroup) bpy.types.Scene.psa_export = PointerProperty(type=psa_exporter.PsaExportPropertyGroup) bpy.types.Scene.psk_export = PointerProperty(type=psk_exporter.PskExportPropertyGroup) @@ -105,7 +82,6 @@ def unregister(): bpy.types.TOPBAR_MT_file_export.remove(psk_export_menu_func) bpy.types.TOPBAR_MT_file_import.remove(psk_import_menu_func) bpy.types.TOPBAR_MT_file_export.remove(psa_export_menu_func) - bpy.types.TOPBAR_MT_file_import.remove(psa_import_menu_func) for cls in reversed(classes): bpy.utils.unregister_class(cls) diff --git a/io_export_psk_psa/helpers.py b/io_export_psk_psa/helpers.py index f8bcb4c..41f3c5b 100644 --- a/io_export_psk_psa/helpers.py +++ b/io_export_psk_psa/helpers.py @@ -1,7 +1,7 @@ from typing import List -def populate_bone_groups_list(armature_object, bone_group_list): +def populate_bone_group_list(armature_object, bone_group_list): bone_group_list.clear() item = bone_group_list.add() diff --git a/io_export_psk_psa/psa/exporter.py b/io_export_psk_psa/psa/exporter.py index 643a6e6..bd10552 100644 --- a/io_export_psk_psa/psa/exporter.py +++ b/io_export_psk_psa/psa/exporter.py @@ -155,7 +155,7 @@ class PsaExportOperator(Operator, ExportHelper): return {'CANCELLED'} # Populate bone groups list. - populate_bone_groups_list(self.armature, property_group.bone_group_list) + populate_bone_group_list(self.armature, property_group.bone_group_list) context.window_manager.fileselect_add(self) @@ -235,3 +235,13 @@ class PsaExportDeselectAll(bpy.types.Operator): for action in property_group.action_list: action.is_selected = False return {'FINISHED'} + + +__classes__ = [ + PsaExportActionListItem, + PsaExportPropertyGroup, + PsaExportOperator, + PSA_UL_ExportActionList, + PsaExportSelectAll, + PsaExportDeselectAll, +] diff --git a/io_export_psk_psa/psa/importer.py b/io_export_psk_psa/psa/importer.py index 996e96e..9d99131 100644 --- a/io_export_psk_psa/psa/importer.py +++ b/io_export_psk_psa/psa/importer.py @@ -1,6 +1,5 @@ import bpy import os -from math import inf import numpy as np from mathutils import Vector, Quaternion, Matrix from .data import Psa @@ -9,17 +8,14 @@ from bpy.types import Operator, Action, UIList, PropertyGroup, Panel, Armature, from bpy_extras.io_utils import ExportHelper, ImportHelper from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty from .reader import PsaReader -import datetime class PsaImporter(object): def __init__(self): pass - def import_psa(self, psa_reader: PsaReader, sequence_names: List[AnyStr], context): - property_group = context.scene.psa_import + def import_psa(self, psa_reader: PsaReader, sequence_names: List[AnyStr], armature_object): sequences = map(lambda x: psa_reader.sequences[x], sequence_names) - armature_object = property_group.armature_object armature_data = armature_object.data class ImportBone(object): @@ -107,18 +103,8 @@ class PsaImporter(object): import_bone.orig_quat = armature_bone.matrix_local.to_quaternion() import_bone.post_quat = import_bone.orig_quat.conjugated() - io_time = datetime.timedelta() - math_time = datetime.timedelta() - keyframe_time = datetime.timedelta() - total_time = datetime.timedelta() - - total_datetime_start = datetime.datetime.now() - # Create and populate the data for new sequences. for sequence in sequences: - # F-curve data buffer for all bones. This is used later on to avoid adding redundant keyframes. - next_frame_bones_fcurve_data = [(inf, inf, inf, inf, inf, inf, inf)] * len(import_bones) - # Add the action. action = bpy.data.actions.new(name=sequence.name.decode()) @@ -142,10 +128,8 @@ class PsaImporter(object): sequence_name = sequence.name.decode('windows-1252') # Read the sequence data matrix from the PSA. - start_datetime = datetime.datetime.now() sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name) keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8) - io_time += datetime.datetime.now() - start_datetime # The first step is to determine the frames at which each bone will write out a keyframe. threshold = 0.001 @@ -173,21 +157,10 @@ class PsaImporter(object): # This bone has writeable keyframes for this frame. key_data = sequence_data_matrix[frame_index, bone_index] # Calculate the local-space key data for the bone. - start_datetime = datetime.datetime.now() fcurve_data = calculate_fcurve_data(import_bone, key_data) - math_time += datetime.datetime.now() - start_datetime for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], fcurve_data): if should_write: - start_datetime = datetime.datetime.now() fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'}) - keyframe_time += datetime.datetime.now() - start_datetime - - total_time = datetime.datetime.now() - total_datetime_start - - print(f'io_time: {io_time}') - print(f'math_time: {math_time}') - print(f'keyframe_time: {keyframe_time}') - print(f'total_time: {total_time}') class PsaImportPsaBoneItem(PropertyGroup): @@ -209,6 +182,7 @@ class PsaImportActionListItem(PropertyGroup): def on_psa_file_path_updated(property, context): + print('PATH UPDATED') property_group = context.scene.psa_import property_group.action_list.clear() property_group.psa_bones.clear() @@ -242,9 +216,9 @@ def on_armature_object_updated(property, context): class PsaImportPropertyGroup(bpy.types.PropertyGroup): - psa_file_path: StringProperty(default='', subtype='FILE_PATH', update=on_psa_file_path_updated) + psa_file_path: StringProperty(default='', update=on_psa_file_path_updated) psa_bones: CollectionProperty(type=PsaImportPsaBoneItem) - armature_object: PointerProperty(name='Object', type=bpy.types.Object, update=on_armature_object_updated) + # armature_object: PointerProperty(name='Object', type=bpy.types.Object, update=on_armature_object_updated) action_list: CollectionProperty(type=PsaImportActionListItem) action_list_index: IntProperty(name='', default=0) action_filter_name: StringProperty(default='') @@ -320,35 +294,62 @@ class PsaImportDeselectAll(bpy.types.Operator): class PSA_PT_ImportPanel(Panel): - bl_space_type = 'NLA_EDITOR' - bl_region_type = 'UI' + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' bl_label = 'PSA Import' - bl_context = 'object' + bl_context = 'data' bl_category = 'PSA Import' + bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): - return context.view_layer.objects.active is not None + return context.object.type == 'ARMATURE' def draw(self, context): layout = self.layout property_group = context.scene.psa_import + row = layout.row() - row.prop(property_group, 'psa_file_path', text='PSA File') + row.prop(property_group, 'psa_file_path', text='') + row.enabled = False + # row.enabled = property_group.psa_file_path is not '' row = layout.row() - row.prop_search(property_group, 'armature_object', bpy.data, 'objects') - box = layout.box() - box.label(text=f'Actions ({len(property_group.action_list)})', icon='ACTION') - row = box.row() - rows = max(3, min(len(property_group.action_list), 10)) - row.template_list('PSA_UL_ImportActionList', '', property_group, 'action_list', property_group, 'action_list_index', rows=rows) - row = box.row(align=True) - row.label(text='Select') - row.operator('psa_import.actions_select_all', text='All') - row.operator('psa_import.actions_deselect_all', text='None') + + layout.separator() + + row.operator('psa_import.select_file', text='Select PSA File', icon='FILEBROWSER') + if len(property_group.action_list) > 0: + box = layout.box() + box.label(text=f'Actions ({len(property_group.action_list)})', icon='ACTION') + row = box.row() + rows = max(3, min(len(property_group.action_list), 10)) + row.template_list('PSA_UL_ImportActionList', '', property_group, 'action_list', property_group, 'action_list_index', rows=rows) + row = box.row(align=True) + row.label(text='Select') + row.operator('psa_import.actions_select_all', text='All') + row.operator('psa_import.actions_deselect_all', text='None') + + layout.separator() + layout.operator('psa_import.import', text=f'Import') +class PsaImportSelectFile(Operator): + bl_idname = "psa_import.select_file" + bl_label = "Select" + bl_options = {'REGISTER', 'UNDO'} + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + filter_glob: bpy.props.StringProperty(default="*.psa", options={"HIDDEN"}) + + def execute(self, context): + context.scene.psa_import.psa_file_path = self.filepath + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + class PsaImportOperator(Operator): bl_idname = 'psa_import.import' bl_label = 'Import' @@ -356,16 +357,17 @@ class PsaImportOperator(Operator): @classmethod def poll(cls, context): property_group = context.scene.psa_import + active_object = context.view_layer.objects.active action_list = property_group.action_list has_selected_actions = any(map(lambda action: action.is_selected, action_list)) - armature_object = property_group.armature_object - return has_selected_actions and armature_object is not None + return has_selected_actions and active_object is not None and active_object.type == 'ARMATURE' def execute(self, context): property_group = context.scene.psa_import psa_reader = PsaReader(property_group.psa_file_path) sequence_names = [x.action_name for x in property_group.action_list if x.is_selected] - PsaImporter().import_psa(psa_reader, sequence_names, context) + PsaImporter().import_psa(psa_reader, sequence_names, context.view_layer.objects.active) + self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)') return {'FINISHED'} @@ -389,3 +391,17 @@ class PsaImportFileSelectOperator(Operator, ImportHelper): property_group.psa_file_path = self.filepath # Load the sequence names from the selected file return {'FINISHED'} + + +__classes__ = [ + PsaImportPsaBoneItem, + PsaImportActionListItem, + PsaImportPropertyGroup, + PSA_UL_ImportActionList, + PsaImportSelectAll, + PsaImportDeselectAll, + PSA_PT_ImportPanel, + PsaImportOperator, + PsaImportFileSelectOperator, + PsaImportSelectFile, +] diff --git a/io_export_psk_psa/psk/importer.py b/io_export_psk_psa/psk/importer.py index 1de4c7c..e651b0c 100644 --- a/io_export_psk_psa/psk/importer.py +++ b/io_export_psk_psa/psk/importer.py @@ -181,4 +181,9 @@ class PskImportOperator(Operator, ImportHelper): psk = reader.read(self.filepath) name = os.path.splitext(os.path.basename(self.filepath))[0] PskImporter().import_psk(psk, name, context) - return {'FINISHED'} \ No newline at end of file + return {'FINISHED'} + + +__classes__ = [ + PskImportOperator +]