Added automated tests (testing PSA import for now, more to come)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user