Fix #139: PSA import does not work on Blender 5.0

This commit is contained in:
Colin Basnett
2025-11-27 12:52:40 -08:00
parent 3153be3cdf
commit 1bac8b2b30
6 changed files with 44 additions and 18 deletions

View File

@@ -1,7 +1,5 @@
FROM ubuntu:22.04
ARG BLENDER_VERSION=4.4
RUN apt-get update -y && \
apt-get install -y libxxf86vm-dev libxfixes3 libxi-dev libxkbcommon-x11-0 libgl1 libglx-mesa0 python3 python3-pip \
libxrender1 libsm6
@@ -10,6 +8,8 @@ RUN pip install --upgrade pip
RUN pip install pytest-blender
RUN pip install blender-downloader
ARG BLENDER_VERSION=5.0
# Set BLENDER_EXECUTABLE and BLENDER_PYTHON as environment variables
RUN BLENDER_EXECUTABLE=$(blender-downloader $BLENDER_VERSION --extract --remove-compressed --print-blender-executable) && \
BLENDER_PYTHON=$(pytest-blender --blender-executable "${BLENDER_EXECUTABLE}") && \

View File

@@ -1,13 +1,13 @@
schema_version = "1.0.0"
id = "io_scene_psk_psa"
version = "8.2.4"
version = "9.0.0"
name = "Unreal PSK/PSA (.psk/.psa)"
tagline = "Import and export PSK and PSA files used in Unreal Engine"
maintainer = "Colin Basnett <cmbasnett@gmail.com>"
type = "add-on"
website = "https://github.com/DarklightGames/io_scene_psk_psa/"
tags = ["Game Engine", "Import-Export"]
blender_version_min = "4.4.0"
blender_version_min = "5.0.0"
# Optional: maximum supported Blender version
# blender_version_max = "5.1.0"
license = [

View File

@@ -4,7 +4,7 @@ from .data import Psa
from typing import Dict, List, Optional, Tuple
from mathutils import Matrix, Quaternion, Vector
from ..shared.helpers import create_psx_bones, get_coordinate_system_transform
from ..shared.helpers import PsxBoneCollection, create_psx_bones, get_coordinate_system_transform
class PsaBuildSequence:
@@ -30,7 +30,7 @@ class PsaBuildOptions:
self.animation_data: Optional[AnimData] = None
self.sequences: List[PsaBuildSequence] = []
self.bone_filter_mode: str = 'ALL'
self.bone_collection_indices: List[PsaBoneCollectionIndex] = []
self.bone_collection_indices: List[PsxBoneCollection] = []
self.sequence_name_prefix: str = ''
self.sequence_name_suffix: str = ''
self.scale = 1.0

View File

@@ -18,6 +18,7 @@ from ..builder import build_psa, PsaBuildSequence, PsaBuildOptions
from ..writer import write_psa
from ...shared.helpers import populate_bone_collection_list, get_nla_strips_in_frame_range, PsxBoneCollection
from ...shared.ui import draw_bone_filter_mode
from ...shared.types import PSX_PG_action_export, PSX_PG_scene_export
def get_sequences_propnames_from_source(sequence_source: str) -> Tuple[str, str]:
@@ -453,7 +454,7 @@ class PSA_OT_export(Operator, ExportHelper):
return {'RUNNING_MODAL'}
def execute(self, context):
pg = getattr(context.scene, 'psa_export')
pg = typing_cast(PSA_PG_export, getattr(context.scene, 'psa_export'))
# Populate the export sequence list.
animation_data_object = get_animation_data_object(context)

View File

@@ -214,6 +214,8 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
)
del armature_bone_names
assert armature_object.pose
# Create intermediate bone data for import operations.
import_bones = []
psa_bone_names_to_import_bones = dict()
@@ -277,7 +279,12 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
action = bpy.data.actions[action_name]
else:
action = bpy.data.actions.new(name=action_name)
action.slots.new('OBJECT', armature_object.name)
action_slot = action.slots.new('OBJECT', armature_object.name)
# TODO: Wish there was a better way to do this.
action_slot = action.slots[f'OB{armature_object.name}']
assert action_slot
# Calculate the target FPS.
match options.fps_source:
@@ -293,7 +300,22 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
if options.should_write_keyframes:
# Remove existing f-curves.
action.fcurves.clear()
if len(action.layers) == 0:
layer = action.layers.new(armature_object.name)
else:
layer = action.layers[0]
if len(layer.strips) == 0:
action_strip = layer.strips.new()
else:
action_strip = layer.strips[0]
if len(action_strip.channelbags) == 0:
channelbag = action_strip.channelbags.new(action_slot)
else:
channelbag = action_strip.channelbags[0]
channelbag.fcurves.clear()
# Create f-curves for the rotation and location of each bone.
for psa_bone_index, armature_bone_index in psa_to_armature_bone_indices.items():
@@ -305,13 +327,13 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
add_rotation_fcurves = (bone_track_flags & REMOVE_TRACK_ROTATION) == 0
add_location_fcurves = (bone_track_flags & REMOVE_TRACK_LOCATION) == 0
import_bone.fcurves = [
action.fcurves.new(rotation_data_path, index=0, action_group=pose_bone.name) if add_rotation_fcurves else None, # Qw
action.fcurves.new(rotation_data_path, index=1, action_group=pose_bone.name) if add_rotation_fcurves else None, # Qx
action.fcurves.new(rotation_data_path, index=2, action_group=pose_bone.name) if add_rotation_fcurves else None, # Qy
action.fcurves.new(rotation_data_path, index=3, action_group=pose_bone.name) if add_rotation_fcurves else None, # Qz
action.fcurves.new(location_data_path, index=0, action_group=pose_bone.name) if add_location_fcurves else None, # Lx
action.fcurves.new(location_data_path, index=1, action_group=pose_bone.name) if add_location_fcurves else None, # Ly
action.fcurves.new(location_data_path, index=2, action_group=pose_bone.name) if add_location_fcurves else None, # Lz
channelbag.fcurves.new(rotation_data_path, index=0, group_name=pose_bone.name) if add_rotation_fcurves else None, # Qw
channelbag.fcurves.new(rotation_data_path, index=1, group_name=pose_bone.name) if add_rotation_fcurves else None, # Qx
channelbag.fcurves.new(rotation_data_path, index=2, group_name=pose_bone.name) if add_rotation_fcurves else None, # Qy
channelbag.fcurves.new(rotation_data_path, index=3, group_name=pose_bone.name) if add_rotation_fcurves else None, # Qz
channelbag.fcurves.new(location_data_path, index=0, group_name=pose_bone.name) if add_location_fcurves else None, # Lx
channelbag.fcurves.new(location_data_path, index=1, group_name=pose_bone.name) if add_location_fcurves else None, # Ly
channelbag.fcurves.new(location_data_path, index=2, group_name=pose_bone.name) if add_location_fcurves else None, # Lz
]
# Read the sequence data matrix from the PSA.
@@ -375,7 +397,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
if animation_data is None:
animation_data = armature_object.animation_data_create()
for action in actions:
nla_track = armature_object.animation_data.nla_tracks.new()
nla_track = animation_data.nla_tracks.new()
nla_track.name = action.name
nla_track.mute = True
nla_track.strips.new(name=action.name, start=0, action=action)

View File

@@ -345,7 +345,10 @@ def create_psx_bones(
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
total_bone_count = sum(len(armature_object.data.bones) for armature_object in armature_objects)
total_bone_count = 0
for armature_object in filter(lambda x: x.data is not None, armature_objects):
armature_data = typing_cast(Armature, armature_object.data)
total_bone_count += len(armature_data.bones)
# Store the bone names to be exported for each armature object.
armature_object_bone_names: Dict[Object, List[str]] = dict()