Added automated tests (testing PSA import for now, more to come)

This commit is contained in:
Colin Basnett
2025-04-02 15:12:23 -07:00
parent 977153e4ad
commit 2b347bf064
15 changed files with 387 additions and 86 deletions

View File

@@ -70,18 +70,18 @@ else:
import bpy
from bpy.props import PointerProperty
classes = shared_types.classes +\
psk_properties.classes +\
psk_ui.classes +\
psk_import_operators.classes +\
psk_export_properties.classes +\
psk_export_operators.classes +\
classes = shared_types.classes + \
psk_properties.classes + \
psk_ui.classes + \
psk_import_operators.classes + \
psk_export_properties.classes + \
psk_export_operators.classes + \
psk_export_ui.classes + \
psa_export_properties.classes +\
psa_export_operators.classes +\
psa_export_properties.classes + \
psa_export_operators.classes + \
psa_export_ui.classes + \
psa_import_properties.classes +\
psa_import_operators.classes +\
psa_import_properties.classes + \
psa_import_operators.classes + \
psa_import_ui.classes

View File

@@ -49,8 +49,8 @@ def _get_pose_bone_location_and_rotation(
coordinate_system_transform: Matrix,
has_false_root_bone: bool,
) -> Tuple[Vector, Quaternion]:
# TODO: my kingdom for a Rust monad.
is_false_root_bone = pose_bone is None and armature_object is None
if is_false_root_bone:
pose_bone_matrix = coordinate_system_transform
elif pose_bone.parent is not None:
@@ -82,7 +82,6 @@ def _get_pose_bone_location_and_rotation(
location = pose_bone_matrix.to_translation()
rotation = pose_bone_matrix.to_quaternion().normalized()
# TODO: this has gotten way more complicated than it needs to be.
# Don't apply scale to the root bone of armatures if we have a false root.
if not has_false_root_bone or (pose_bone is None or pose_bone.parent is not None):
location *= scale
@@ -112,12 +111,9 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
bone_collection_indices=options.bone_collection_indices,
)
# TODO: technically wrong, might not necessarily be true (i.e., if the armature object has no contributing bones).
has_false_root_bone = len(options.armature_objects) > 1
# Build list of PSA bones.
# Note that the PSA bones are just here to validate the hierarchy. The bind pose information is not used by the
# engine.
# Note that the PSA bones are just here to validate the hierarchy.
# The bind pose information is not used by the engine.
psa.bones = [psx_bone for psx_bone, _ in psx_bone_create_result.bones]
# No bones are going to be exported.
@@ -129,11 +125,10 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
export_sequence.name = export_sequence.name.strip()
# Save the current action and frame so that we can restore the state once we are done.
# Save each armature object's current action and frame so that we can restore the state once we are done.
saved_armature_object_actions = {o: o.animation_data.action for o in options.armature_objects}
saved_frame_current = context.scene.frame_current
saved_action = options.animation_data.action
# Now build the PSA sequences.
# We actually alter the timeline frame and simply record the resultant pose bone matrices.
frame_start_index = 0
@@ -179,7 +174,6 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
# Link the action to the animation data and update view layer.
for armature_object in options.armature_objects:
# TODO: change this to assign it to each armature object's animation data.
armature_object.animation_data.action = export_sequence.nla_state.action
context.view_layer.update()
@@ -217,13 +211,10 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_bones: List[PsaExportBone] = []
for psx_bone, armature_object in psx_bone_create_result.bones:
print(psx_bone, armature_object)
# TODO: look up the pose bone from the name in the PSX bone.
if armature_object is None:
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
continue
# TODO: we need to look up the pose bones using the name.
pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')]
export_bones.append(PsaExportBone(pose_bone, armature_object, armature_scales[armature_object]))
@@ -260,7 +251,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
options.export_space,
export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=has_false_root_bone,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
last_frame_bone_poses.append((location, rotation))
@@ -283,7 +274,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space,
scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=has_false_root_bone,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
next_frame_bone_poses.append((location, rotation))
@@ -310,7 +301,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space,
scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=has_false_root_bone,
has_false_root_bone=psx_bone_create_result.has_false_root_bone,
)
add_key(location, rotation)
@@ -322,9 +313,9 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
context.window_manager.progress_update(export_sequence_index)
# Restore the previous action & frame.
# TODO: store each armature object's previous action
options.animation_data.action = saved_action
# Restore the previous actions & frame.
for armature_object, action in saved_armature_object_actions.items():
armature_object.animation_data.action = action
context.scene.frame_set(saved_frame_current)

View File

@@ -16,7 +16,7 @@ def get_psk_import_options_from_properties(property_group: PskImportMixin):
options.should_import_vertex_colors = property_group.should_import_vertex_colors
options.should_import_vertex_normals = property_group.should_import_vertex_normals
options.vertex_color_space = property_group.vertex_color_space
options.should_import_skeleton = property_group.should_import_skeleton
options.should_import_armature = property_group.should_import_armature
options.bone_length = property_group.bone_length
options.should_import_materials = property_group.should_import_materials
options.should_import_shape_keys = property_group.should_import_shape_keys
@@ -34,7 +34,7 @@ def psk_import_draw(layout: UILayout, props: PskImportMixin):
col = row.column()
col.use_property_split = True
col.use_property_decorate = False
col.prop(props, 'import_components')
col.prop(props, 'components')
if props.should_import_mesh:
mesh_header, mesh_panel = layout.panel('mesh_panel_id', default_closed=False)
@@ -45,20 +45,21 @@ def psk_import_draw(layout: UILayout, props: PskImportMixin):
col = row.column()
col.use_property_split = True
col.use_property_decorate = False
col.prop(props, 'should_import_materials', text='Materials')
col.prop(props, 'should_import_vertex_normals', text='Vertex Normals')
col.prop(props, 'should_import_extra_uvs', text='Extra UVs')
col.prop(props, 'should_import_materials', text='Materials')
col.prop(props, 'should_import_vertex_colors', text='Vertex Colors')
if props.should_import_vertex_colors:
col.prop(props, 'vertex_color_space')
col.separator()
col.prop(props, 'should_import_vertex_normals', text='Vertex Normals')
col.prop(props, 'should_import_shape_keys', text='Shape Keys')
if props.should_import_skeleton:
skeleton_header, skeleton_panel = layout.panel('skeleton_panel_id', default_closed=False)
skeleton_header.label(text='Skeleton', icon='OUTLINER_DATA_ARMATURE')
if props.should_import_armature:
armature_header, armature_panel = layout.panel('armature_panel_id', default_closed=False)
armature_header.label(text='Armature', icon='OUTLINER_DATA_ARMATURE')
if skeleton_panel:
row = skeleton_panel.row()
if armature_panel:
row = armature_panel.row()
col = row.column()
col.use_property_split = True
col.use_property_decorate = False

View File

@@ -20,7 +20,7 @@ class PskImportOptions:
self.vertex_color_space = 'SRGB'
self.should_import_vertex_normals = True
self.should_import_extra_uvs = True
self.should_import_skeleton = True
self.should_import_armature = True
self.should_import_shape_keys = True
self.bone_length = 1.0
self.should_import_materials = True
@@ -62,7 +62,7 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions)
armature_object = None
mesh_object = None
if options.should_import_skeleton:
if options.should_import_armature:
# Armature
armature_data = bpy.data.armatures.new(name)
armature_object = bpy.data.objects.new(name, armature_data)
@@ -213,12 +213,9 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions)
psk_vertex_colors = np.zeros((len(psk.vertex_colors), 4))
for vertex_color_index in range(len(psk.vertex_colors)):
psk_vertex_colors[vertex_color_index,:] = psk.vertex_colors[vertex_color_index].normalized()
match options.vertex_color_space:
case 'SRGBA':
for i in range(psk_vertex_colors.shape[0]):
psk_vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), psk_vertex_colors[i, :3]))
case _:
pass
if options.vertex_color_space == 'SRGBA':
for i in range(psk_vertex_colors.shape[0]):
psk_vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), psk_vertex_colors[i, :3]))
# Map the PSK vertex colors to the face corners.
face_count = len(psk.faces) - len(invalid_face_indices)
@@ -276,12 +273,12 @@ def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions)
context.scene.collection.objects.link(mesh_object)
# Add armature modifier to our mesh object.
if options.should_import_skeleton:
if options.should_import_armature:
armature_modifier = mesh_object.modifiers.new(name='Armature', type='ARMATURE')
armature_modifier.object = armature_object
mesh_object.parent = armature_object
root_object = armature_object if options.should_import_skeleton else mesh_object
root_object = armature_object if options.should_import_armature else mesh_object
root_object.scale = (options.scale, options.scale, options.scale)
try:

View File

@@ -46,10 +46,11 @@ def poly_flags_to_triangle_type_and_bit_flags(poly_flags: int) -> (str, set[str]
def should_import_mesh_get(self):
return self.import_components in {'ALL', 'MESH'}
return self.components in {'ALL', 'MESH'}
def should_import_skleton_get(self):
return self.import_components in {'ALL', 'SKELETON'}
return self.components in {'ALL', 'ARMATURE'}
class PskImportMixin:
@@ -73,7 +74,7 @@ class PskImportMixin:
default=True,
name='Import Vertex Normals',
options=set(),
description='Import vertex normals, if available'
description='Import vertex normals, if available.\n\nThis is only supported for PSKX files'
)
should_import_extra_uvs: BoolProperty(
default=True,
@@ -81,14 +82,14 @@ class PskImportMixin:
options=set(),
description='Import extra UV maps, if available'
)
import_components: EnumProperty(
name='Import Components',
components: EnumProperty(
name='Components',
options=set(),
description='Determine which components to import',
description='Which components to import',
items=(
('ALL', 'Mesh & Skeleton', 'Import mesh and skeleton'),
('ALL', 'Mesh & Armature', 'Import mesh and armature'),
('MESH', 'Mesh Only', 'Import mesh only'),
('SKELETON', 'Skeleton Only', 'Import skeleton only'),
('ARMATURE', 'Armature Only', 'Import armature only'),
),
default='ALL'
)
@@ -101,7 +102,7 @@ class PskImportMixin:
name='Import Materials',
options=set(),
)
should_import_skeleton: BoolProperty(
should_import_armature: BoolProperty(
name='Import Skeleton',
get=should_import_skleton_get,
)
@@ -119,7 +120,7 @@ class PskImportMixin:
default=True,
name='Import Shape Keys',
options=set(),
description='Import shape keys, if available'
description='Import shape keys, if available.\n\nThis is only supported for PSKX files'
)
scale: FloatProperty(
name='Scale',

View File

@@ -271,6 +271,10 @@ class PsxBoneCreateResult:
self.bones = bones
self.armature_object_root_bone_indices = armature_object_root_bone_indices
self.armature_object_bone_names = armature_object_bone_names
@property
def has_false_root_bone(self) -> bool:
return len(self.bones) > 0 and self.bones[0][1] is None
def create_psx_bones(