Checkpoint commit
This commit is contained in:
@@ -48,58 +48,40 @@ 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:
|
# Get the bone's pose matrix and transform it into the export space.
|
||||||
pose_bone_matrix = armature_object.matrix_world @ pose_bone.matrix
|
# In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix.
|
||||||
else:
|
# Otherwise, it will be the identity matrix.
|
||||||
# Get the bone's pose matrix and transform it into the export space.
|
match export_space:
|
||||||
# In the case of an 'ARMATURE' export space, this will be the inverse of armature object's world matrix.
|
case 'ARMATURE':
|
||||||
# Otherwise, it will be the identity matrix.
|
pose_bone_matrix = pose_bone.matrix
|
||||||
match export_space:
|
case 'WORLD':
|
||||||
case 'ARMATURE':
|
pose_bone_matrix = armature_object.matrix_world @ pose_bone.matrix
|
||||||
pose_bone_matrix = pose_bone.matrix
|
case 'ROOT':
|
||||||
case 'WORLD':
|
pose_bone_matrix = Matrix.Identity(4)
|
||||||
pose_bone_matrix = armature_object.matrix_world @ pose_bone.matrix
|
case _:
|
||||||
case 'ROOT':
|
assert False, f'Invalid export space: {export_space}'
|
||||||
pose_bone_matrix = Matrix.Identity(4)
|
|
||||||
case _:
|
|
||||||
assert False, f'Invalid export space: {export_space}'
|
|
||||||
|
|
||||||
# The root bone is the only bone that should be transformed by the coordinate system transform, since all
|
# The root bone is the only bone that should be transformed by the coordinate system transform, since all
|
||||||
# other bones are relative to their parent bones.
|
# other bones are relative to their parent bones.
|
||||||
pose_bone_matrix = coordinate_system_transform @ pose_bone_matrix
|
pose_bone_matrix = coordinate_system_transform @ pose_bone_matrix
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
# Don't apply scale to the root bone of armatures if we have a false root.
|
if pose_bone.parent is not None:
|
||||||
if not has_false_root_bone or (pose_bone is None or pose_bone.parent is not None):
|
# Don't apply scale to the root bone of armatures if we have a false root.
|
||||||
location *= scale
|
# TODO: probably remove this?
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -540,6 +527,36 @@ def create_psx_bones(
|
|||||||
error_message += f' This is the name of the automatically generated root bone. Consider changing this '
|
error_message += f' This is the name of the automatically generated root bone. Consider changing this '
|
||||||
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,
|
||||||
|
|||||||
Reference in New Issue
Block a user