Checkpoint commit

This commit is contained in:
Colin Basnett
2026-01-06 23:07:45 -08:00
parent efe845bf4a
commit a5eba2b6d9
3 changed files with 67 additions and 68 deletions

View File

@@ -48,28 +48,16 @@ class PsaBuildOptions:
def _get_pose_bone_location_and_rotation( def _get_pose_bone_location_and_rotation(
pose_bone: PoseBone | None, pose_bone: PoseBone,
armature_object: Object | None, armature_object: Object,
export_space: str, export_space: str,
scale: Vector, scale: Vector,
coordinate_system_transform: Matrix, coordinate_system_transform: Matrix,
has_false_root_bone: bool,
) -> Tuple[Vector, Quaternion]: ) -> Tuple[Vector, Quaternion]:
is_false_root_bone = pose_bone is None and armature_object is None if pose_bone.parent is not None:
pose_bone_matrix = pose_bone.parent.matrix.inverted() @ pose_bone.matrix
# TODO: this is such a disaster; the false root bone idea needs revising.
if is_false_root_bone:
pose_bone_matrix = coordinate_system_transform
elif pose_bone is not None and pose_bone.parent is not None:
pose_bone_matrix = pose_bone.matrix
pose_bone_parent_matrix = pose_bone.parent.matrix
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
else: else:
# Root bone # Root bone
if has_false_root_bone:
pose_bone_matrix = armature_object.matrix_world @ pose_bone.matrix
else:
# Get the bone's pose matrix and transform it into the export space. # Get the bone's pose matrix and transform it into the export space.
# In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix. # In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix.
# Otherwise, it will be the identity matrix. # Otherwise, it will be the identity matrix.
@@ -90,16 +78,10 @@ def _get_pose_bone_location_and_rotation(
location = pose_bone_matrix.to_translation() location = pose_bone_matrix.to_translation()
rotation = pose_bone_matrix.to_quaternion().normalized() rotation = pose_bone_matrix.to_quaternion().normalized()
if pose_bone.parent is not None:
# Don't apply scale to the root bone of armatures if we have a false root. # 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): # TODO: probably remove this?
location *= scale location *= scale
if has_false_root_bone:
is_child_bone = not is_false_root_bone
else:
is_child_bone = pose_bone.parent is not None
if is_child_bone:
rotation.conjugate() rotation.conjugate()
return location, rotation return location, rotation
@@ -218,12 +200,12 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
psa.keys.append(key) psa.keys.append(key)
class PsaExportBone: class PsaExportBone:
def __init__(self, pose_bone: Optional[PoseBone], armature_object: Optional[Object], scale: Vector): def __init__(self, pose_bone: PoseBone | None, armature_object: Object | None, scale: Vector):
self.pose_bone = pose_bone self.pose_bone = pose_bone
self.armature_object = armature_object self.armature_object = armature_object
self.scale = scale self.scale = scale
armature_scales: Dict[Object, Vector] = {} armature_scales: dict[Object, Vector] = {}
# Extract the scale from the world matrix of the evaluated armature object. # Extract the scale from the world matrix of the evaluated armature object.
for armature_object in options.armature_objects: for armature_object in options.armature_objects:
@@ -235,7 +217,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
# Create a list of export pose bones, in the same order as the bones as they appear in the armature. # Create a list of export pose bones, in the same order as the bones as they appear in the armature.
# The object contains the pose bone, the armature object, and a pre-calculated scaling value to apply to the # The object contains the pose bone, the armature object, and a pre-calculated scaling value to apply to the
# locations. # locations.
export_bones: List[PsaExportBone] = [] export_bones: list[PsaExportBone] = []
for psx_bone, armature_object in psx_bone_create_result.bones: for psx_bone, armature_object in psx_bone_create_result.bones:
if armature_object is None: if armature_object is None:
@@ -250,11 +232,11 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
match options.sampling_mode: match options.sampling_mode:
case 'INTERPOLATED': case 'INTERPOLATED':
# Used as a store for the last frame's pose bone locations and rotations. # Used as a store for the last frame's pose bone locations and rotations.
last_frame: Optional[int] = None last_frame: int | None = None
last_frame_bone_poses: List[Tuple[Vector, Quaternion]] = [] last_frame_bone_poses: list[tuple[Vector, Quaternion]] = []
next_frame: Optional[int] = None next_frame: int | None = None
next_frame_bone_poses: List[Tuple[Vector, Quaternion]] = [] next_frame_bone_poses: list[tuple[Vector, Quaternion]] = []
for _ in range(frame_count): for _ in range(frame_count):
if last_frame is None or last_frame != int(frame): if last_frame is None or last_frame != int(frame):
@@ -275,8 +257,8 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_bone.armature_object, export_bone.armature_object,
options.export_space, options.export_space,
export_bone.scale, export_bone.scale,
coordinate_system_transform=coordinate_system_transform, coordinate_system_transform=coordinate_system_transform
has_false_root_bone=psx_bone_create_result.has_false_root_bone, # has_false_root_bone=psx_bone_create_result.has_false_root_bone,
) )
last_frame_bone_poses.append((location, rotation)) last_frame_bone_poses.append((location, rotation))
@@ -299,7 +281,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space, export_space=options.export_space,
scale=export_bone.scale, scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform, coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=psx_bone_create_result.has_false_root_bone, # has_false_root_bone=psx_bone_create_result.has_false_root_bone,
) )
next_frame_bone_poses.append((location, rotation)) next_frame_bone_poses.append((location, rotation))
@@ -326,7 +308,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
export_space=options.export_space, export_space=options.export_space,
scale=export_bone.scale, scale=export_bone.scale,
coordinate_system_transform=coordinate_system_transform, coordinate_system_transform=coordinate_system_transform,
has_false_root_bone=psx_bone_create_result.has_false_root_bone, # has_false_root_bone=psx_bone_create_result.has_false_root_bone,
) )
add_key(location, rotation) add_key(location, rotation)

View File

@@ -238,7 +238,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
case _: case _:
assert False, f'Invalid export space: {options.export_space}' assert False, f'Invalid export space: {options.export_space}'
vertex_transform_matrix = scale_matrix @ coordinate_system_matrix @ mesh_export_space_matrix vertex_transform_matrix = scale_matrix @ coordinate_system_matrix.inverted() @ mesh_export_space_matrix
point_transform_matrix = vertex_transform_matrix @ mesh_object.matrix_world point_transform_matrix = vertex_transform_matrix @ mesh_object.matrix_world
# Vertices # Vertices

View File

@@ -172,18 +172,12 @@ def convert_string_to_cp1252_bytes(string: str) -> bytes:
def create_psx_bones_from_blender_bones( def create_psx_bones_from_blender_bones(
bones: List[bpy.types.Bone], bones: List[bpy.types.Bone],
armature_object_matrix_world: Matrix, armature_object_matrix_world: Matrix,
forward_axis: str = 'X',
up_axis: str = 'Z',
) -> List[PsxBone]: ) -> List[PsxBone]:
""" """
Creates PSX bones from the given Blender bones. Creates PSX bones from the given Blender bones.
The bones are in world space based on the armature object's world matrix. The bones are in world space based on the armature object's world matrix.
""" """
coordinate_system_transform = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_transform.to_quaternion()
# Apply the scale of the armature object to the bone location. # Apply the scale of the armature object to the bone location.
_, _, armature_object_scale = armature_object_matrix_world.decompose() _, _, armature_object_scale = armature_object_matrix_world.decompose()
@@ -209,11 +203,9 @@ def create_psx_bones_from_blender_bones(
location = (parent_tail - parent_head) + bone.head location = (parent_tail - parent_head) + bone.head
else: else:
location = armature_object_matrix_world @ bone.head location = armature_object_matrix_world @ bone.head
location = coordinate_system_transform @ location
bone_rotation = bone.matrix.to_quaternion().conjugated() bone_rotation = bone.matrix.to_quaternion().conjugated()
rotation = bone_rotation @ armature_object_matrix_world.to_3x3().to_quaternion() rotation = bone_rotation @ armature_object_matrix_world.to_3x3().to_quaternion()
rotation.conjugate() rotation.conjugate()
rotation = coordinate_system_default_rotation @ rotation
location.x *= armature_object_scale.x location.x *= armature_object_scale.x
location.y *= armature_object_scale.y location.y *= armature_object_scale.y
@@ -352,9 +344,6 @@ def create_psx_bones(
raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World.\n' \ raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World.\n' \
f'The following armatures are attempting to be exported: {root_armature_names}') f'The following armatures are attempting to be exported: {root_armature_names}')
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
total_bone_count = 0 total_bone_count = 0
for armature_object in filter(lambda x: x.data is not None, armature_objects): for armature_object in filter(lambda x: x.data is not None, armature_objects):
armature_data = typing_cast(Armature, armature_object.data) armature_data = typing_cast(Armature, armature_object.data)
@@ -384,7 +373,7 @@ def create_psx_bones(
# requirement that a PSK file must have at least one bone. # requirement that a PSK file must have at least one bone.
psx_bone = PsxBone() psx_bone = PsxBone()
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name) psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation) psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
bones.append((psx_bone, None)) bones.append((psx_bone, None))
armature_object_root_bone_indices[None] = 0 armature_object_root_bone_indices[None] = 0
@@ -394,7 +383,7 @@ def create_psx_bones(
psx_bone = PsxBone() psx_bone = PsxBone()
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name) psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
psx_bone.children_count = total_bone_count psx_bone.children_count = total_bone_count
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation) psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
bones.append((psx_bone, None)) bones.append((psx_bone, None))
armature_object_root_bone_indices[None] = 0 armature_object_root_bone_indices[None] = 0
@@ -408,8 +397,6 @@ def create_psx_bones(
armature_psx_bones = create_psx_bones_from_blender_bones( armature_psx_bones = create_psx_bones_from_blender_bones(
bones=armature_bones, bones=armature_bones,
armature_object_matrix_world=armature_object.matrix_world, armature_object_matrix_world=armature_object.matrix_world,
forward_axis=forward_axis,
up_axis=up_axis,
) )
# We have the bones in world space. If we are attaching this armature to a parent bone, we need to convert # We have the bones in world space. If we are attaching this armature to a parent bone, we need to convert
@@ -541,6 +528,36 @@ def create_psx_bones(
f'' f''
raise RuntimeError(error_message) raise RuntimeError(error_message)
# Apply the scale to the bone locations.
for psx_bone, _ in bones:
psx_bone.location.x *= scale
psx_bone.location.y *= scale
psx_bone.location.z *= scale
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
# Apply the coordinate system transform to the root bone.
root_psx_bone = bones[0][0]
# Get transform matrix from root bone location and rotation.
root_bone_location = Vector((root_psx_bone.location.x, root_psx_bone.location.y, root_psx_bone.location.z))
root_bone_rotation = BpyQuaternion((root_psx_bone.rotation.w, root_psx_bone.rotation.x, root_psx_bone.rotation.y, root_psx_bone.rotation.z))
root_bone_matrix = (
Matrix.Translation(root_bone_location) @
root_bone_rotation.to_matrix().to_4x4()
)
root_bone_matrix = coordinate_system_default_rotation.inverted().to_matrix().to_4x4() @ root_bone_matrix
location, rotation, _ = root_bone_matrix.decompose()
root_psx_bone.location.x = location.x
root_psx_bone.location.y = location.y
root_psx_bone.location.z = location.z
root_psx_bone.rotation.w = rotation.w
root_psx_bone.rotation.x = rotation.x
root_psx_bone.rotation.y = rotation.y
root_psx_bone.rotation.z = rotation.z
convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation)
return PsxBoneCreateResult( return PsxBoneCreateResult(
bones=bones, bones=bones,
armature_object_root_bone_indices=armature_object_root_bone_indices, armature_object_root_bone_indices=armature_object_root_bone_indices,