From 4e625e37d1b1386ff1d1f1f4462ae9c245998457 Mon Sep 17 00:00:00 2001 From: Colin Basnett Date: Sat, 15 Jan 2022 20:52:02 -0800 Subject: [PATCH] Initial working commit for full PSK/PSA import/export cycle. --- io_export_psk_psa/psa/builder.py | 2 +- io_export_psk_psa/psa/data.py | 7 ++-- io_export_psk_psa/psa/exporter.py | 4 +-- io_export_psk_psa/psa/importer.py | 55 +++++++++++++++++-------------- io_export_psk_psa/psk/importer.py | 28 +++++++++++----- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/io_export_psk_psa/psa/builder.py b/io_export_psk_psa/psa/builder.py index e1846f9..38f9c47 100644 --- a/io_export_psk_psa/psa/builder.py +++ b/io_export_psk_psa/psa/builder.py @@ -124,6 +124,6 @@ class PsaBuilder(object): sequence.bone_count = len(pose_bones) sequence.track_time = frame_count - psa.sequences.append(sequence) + psa.sequences[action.name] = sequence return psa diff --git a/io_export_psk_psa/psa/data.py b/io_export_psk_psa/psa/data.py index 819d81c..1ebef63 100644 --- a/io_export_psk_psa/psa/data.py +++ b/io_export_psk_psa/psa/data.py @@ -1,4 +1,6 @@ -from typing import List, Dict +import typing +from typing import List +from collections import OrderedDict from ..data import * """ @@ -47,4 +49,5 @@ class Psa(object): def __init__(self): self.bones: List[Psa.Bone] = [] - self.sequences: Dict[Psa.Sequence] = {} + self.sequences: typing.OrderedDict[Psa.Sequence] = OrderedDict() + self.keys: List[Psa.Key] = [] diff --git a/io_export_psk_psa/psa/exporter.py b/io_export_psk_psa/psa/exporter.py index eb81509..57b312b 100644 --- a/io_export_psk_psa/psa/exporter.py +++ b/io_export_psk_psa/psa/exporter.py @@ -29,7 +29,7 @@ class PsaExporter(object): with open(path, 'wb') as fp: self.write_section(fp, b'ANIMHEAD') self.write_section(fp, b'BONENAMES', Psa.Bone, self.psa.bones) - self.write_section(fp, b'ANIMINFO', Psa.Sequence, self.psa.sequences) + self.write_section(fp, b'ANIMINFO', Psa.Sequence, list(self.psa.sequences.values())) self.write_section(fp, b'ANIMKEYS', Psa.Key, self.psa.keys) @@ -70,7 +70,7 @@ class PsaExportOperator(Operator, ExportHelper): box = layout.box() box.label(text='Actions', icon='ACTION') row = box.row() - row.template_list('PSA_UL_ActionList', 'asd', scene, 'psa_export.action_list', scene, 'psa_export.action_list_index', rows=len(context.scene.psa_export.action_list)) + row.template_list('PSA_UL_ActionList', 'asd', scene.psa_export, 'action_list', scene.psa_export, 'action_list_index', rows=10) def is_action_for_armature(self, action): if len(action.fcurves) == 0: diff --git a/io_export_psk_psa/psa/importer.py b/io_export_psk_psa/psa/importer.py index 13be09e..dfe1538 100644 --- a/io_export_psk_psa/psa/importer.py +++ b/io_export_psk_psa/psa/importer.py @@ -60,28 +60,44 @@ class PsaImporter(object): # Create intermediate bone data for import operations. import_bones = [] + import_bones_dict = dict() for psa_bone_index, psa_bone in enumerate(psa.bones): bone_name = psa_bone.name.decode('windows-1252') - if psa_bone_index not in psa_to_armature_bone_indices: + print(bone_name) + if psa_bone_index not in psa_to_armature_bone_indices: # TODO: replace with bone_name in armature_data.bones # PSA bone does not map to armature bone, skip it and leave an empty bone in its place. import_bones.append(None) continue + import_bone = ImportBone(psa_bone) armature_bone = armature_data.bones[bone_name] import_bone.pose_bone = armature_object.pose.bones[bone_name] - if psa_bone_index > 0: - import_bone.parent = import_bones[psa_bone.parent_index] + + if armature_bone.parent is not None: + if armature_bone.parent.name in psa_bone_names: + import_bone.parent = import_bones_dict[armature_bone.parent.name] + else: + import_bone.parent = None + # Calculate the original location & rotation of each bone (in world space maybe?) - if import_bone.parent is not None: - import_bone.orig_loc = armature_bone.matrix_local.translation - armature_bone.parent.matrix_local.translation - import_bone.orig_loc.rotate(armature_bone.parent.matrix_local.to_quaternion().conjugated()) - import_bone.orig_quat = armature_bone.matrix_local.to_quaternion() - import_bone.orig_quat.rotate(armature_bone.parent.matrix_local.to_quaternion().conjugated()) - import_bone.orig_quat.conjugate() + # TODO: check if the armature bones have custom data written to them and use that instead. + if armature_bone.get('orig_quat') is not None: + # TODO: ideally we don't rely on bone auxiliary data like this, the non-aux data path is incorrect (animations are flipped 180) + import_bone.orig_quat = Quaternion(armature_bone['orig_quat']) + import_bone.orig_loc = Vector(armature_bone['orig_loc']) + import_bone.post_quat = Quaternion(armature_bone['post_quat']) else: - import_bone.orig_loc = armature_bone.matrix_local.translation.copy() - import_bone.orig_quat = armature_bone.matrix_local.to_quaternion() - import_bone.post_quat = import_bone.orig_quat.conjugated() + if import_bone.parent is not None: + import_bone.orig_loc = armature_bone.matrix_local.translation - armature_bone.parent.matrix_local.translation + import_bone.orig_loc.rotate(armature_bone.parent.matrix_local.to_quaternion().conjugated()) + import_bone.orig_quat = armature_bone.matrix_local.to_quaternion() + import_bone.orig_quat.rotate(armature_bone.parent.matrix_local.to_quaternion().conjugated()) + import_bone.orig_quat.conjugate() + else: + import_bone.orig_loc = armature_bone.matrix_local.translation.copy() + import_bone.orig_quat = armature_bone.matrix_local.to_quaternion() + import_bone.post_quat = import_bone.orig_quat.conjugated() + import_bones_dict[bone_name] = import_bone import_bones.append(import_bone) # Create and populate the data for new sequences. @@ -114,9 +130,7 @@ class PsaImporter(object): import_bone.fcurve_location_y.keyframe_points.add(sequence.frame_count) import_bone.fcurve_location_z.keyframe_points.add(sequence.frame_count) - fcurve_interpolation = 'LINEAR' should_invert_root = False - key_index = 0 sequence_name = sequence.name.decode('windows-1252') sequence_keys = psa_reader.get_sequence_keys(sequence_name) @@ -131,13 +145,12 @@ class PsaImporter(object): key_location = Vector(tuple(sequence_keys[key_index].location)) key_rotation = Quaternion(tuple(sequence_keys[key_index].rotation)) - # TODO: what is this doing exactly? q = import_bone.post_quat.copy() q.rotate(import_bone.orig_quat) quat = q q = import_bone.post_quat.copy() - if import_bone.parent is None and should_invert_root: - q.rotate(import_bone.orig_quat) + if import_bone.parent is None and not should_invert_root: + q.rotate(key_rotation.conjugated()) else: q.rotate(key_rotation) quat.rotate(q.conjugated()) @@ -153,14 +166,6 @@ class PsaImporter(object): import_bone.fcurve_location_y.keyframe_points[frame_index].co = frame_index, loc.y import_bone.fcurve_location_z.keyframe_points[frame_index].co = frame_index, loc.z - import_bone.fcurve_quat_w.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_quat_x.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_quat_y.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_quat_z.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_location_x.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_location_z.keyframe_points[frame_index].interpolation = fcurve_interpolation - import_bone.fcurve_location_z.keyframe_points[frame_index].interpolation = fcurve_interpolation - key_index += 1 diff --git a/io_export_psk_psa/psk/importer.py b/io_export_psk_psa/psk/importer.py index 19af075..a124887 100644 --- a/io_export_psk_psa/psk/importer.py +++ b/io_export_psk_psa/psk/importer.py @@ -43,6 +43,9 @@ class PskImporter(object): self.world_rotation_matrix: Matrix = Matrix() self.world_matrix: Matrix = Matrix() self.vertex_group = None + self.orig_quat: Quaternion = Quaternion() + self.orig_loc: Vector = Vector() + self.post_quat: Quaternion = Quaternion() import_bones = [] should_invert_root = False @@ -73,19 +76,28 @@ class PskImporter(object): bone.world_rotation_matrix = bone.local_rotation.conjugated().to_matrix() bone.world_rotation_matrix.rotate(parent.world_rotation_matrix) - for bone in import_bones: - edit_bone = armature_data.edit_bones.new(bone.psk_bone.name.decode('utf-8')) - if bone.parent is not None: - edit_bone.parent = armature_data.edit_bones[bone.psk_bone.parent_index] + for import_bone in import_bones: + bone_name = import_bone.psk_bone.name.decode('utf-8') + edit_bone = armature_data.edit_bones.new(bone_name) + + if import_bone.parent is not None: + edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index] elif not should_invert_root: - bone.local_rotation.conjugate() + import_bone.local_rotation.conjugate() + edit_bone.tail = Vector((0.0, new_bone_size, 0.0)) - edit_bone_matrix = bone.local_rotation.conjugated() - edit_bone_matrix.rotate(bone.world_matrix) + edit_bone_matrix = import_bone.local_rotation.conjugated() + edit_bone_matrix.rotate(import_bone.world_matrix) edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4() - edit_bone_matrix.translation = bone.world_matrix.translation + edit_bone_matrix.translation = import_bone.world_matrix.translation edit_bone.matrix = edit_bone_matrix + # Store bind pose information in the bone's custom properties. + # This information is used when importing animations from PSA files. + edit_bone['orig_quat'] = import_bone.local_rotation + edit_bone['orig_loc'] = import_bone.local_translation + edit_bone['post_quat'] = import_bone.local_rotation.conjugated() + # MESH mesh_data = bpy.data.meshes.new(name) mesh_object = bpy.data.objects.new(name, mesh_data)