Technically got the armature-attachments working for animations as well; lots of clean up and testing needed though
This commit is contained in:
@@ -4,7 +4,7 @@ from psk_psa_py.psa.data import Psa
|
|||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
from mathutils import Matrix, Quaternion, Vector
|
from mathutils import Matrix, Quaternion, Vector
|
||||||
|
|
||||||
from ..shared.helpers import PsxBoneCollection, create_psx_bones, get_coordinate_system_transform
|
from ..shared.helpers import PsxBoneCollection, convert_bpy_quaternion_to_psx_quaternion, convert_vector_to_vector3, create_psx_bones, get_coordinate_system_transform
|
||||||
|
|
||||||
|
|
||||||
class PsaBuildSequence:
|
class PsaBuildSequence:
|
||||||
@@ -47,9 +47,54 @@ class PsaBuildOptions:
|
|||||||
return 'DATA' if self.sequence_source == 'ACTIVE_ACTION' else 'OBJECT'
|
return 'DATA' if self.sequence_source == 'ACTIVE_ACTION' else 'OBJECT'
|
||||||
|
|
||||||
|
|
||||||
|
class PsaExportBone:
|
||||||
|
def __init__(self,
|
||||||
|
pose_bone: PoseBone | None,
|
||||||
|
armature_object: Object | None,
|
||||||
|
scale: Vector):
|
||||||
|
self.pose_bone = pose_bone
|
||||||
|
self.armature_object = armature_object
|
||||||
|
self.scale = scale
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_armature_root_bone(self) -> bool:
|
||||||
|
return self.pose_bone is not None and self.pose_bone.parent is None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_attached_to_armature(self) -> bool:
|
||||||
|
return self.get_attached_armature() is not None
|
||||||
|
|
||||||
|
def get_attached_armature(self) -> tuple[Object, PoseBone] | None:
|
||||||
|
if not self.is_armature_root_bone:
|
||||||
|
return None
|
||||||
|
assert self.armature_object is not None
|
||||||
|
match self.armature_object.parent_type:
|
||||||
|
case 'BONE':
|
||||||
|
parent_bone_name = self.armature_object.parent_bone
|
||||||
|
assert self.armature_object.parent is not None
|
||||||
|
parent_armature_object = self.armature_object.parent
|
||||||
|
assert parent_armature_object.pose is not None
|
||||||
|
parent_pose_bone = parent_armature_object.pose.bones.get(parent_bone_name)
|
||||||
|
if parent_pose_bone is None:
|
||||||
|
return None
|
||||||
|
return (parent_armature_object, parent_pose_bone)
|
||||||
|
case _:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_attached_armature_transform(self) -> Matrix:
|
||||||
|
attached_armature, attached_pose_bone = self.get_attached_armature() or (None, None)
|
||||||
|
if attached_armature is None or attached_pose_bone is None:
|
||||||
|
return Matrix.Identity(4)
|
||||||
|
if attached_pose_bone.parent is not None:
|
||||||
|
attached_bone_matrix = attached_pose_bone.parent.matrix.inverted() @ attached_pose_bone.matrix
|
||||||
|
else:
|
||||||
|
attached_bone_matrix = attached_armature.matrix_world @ attached_pose_bone.matrix
|
||||||
|
return attached_bone_matrix
|
||||||
|
|
||||||
def _get_pose_bone_location_and_rotation(
|
def _get_pose_bone_location_and_rotation(
|
||||||
pose_bone: PoseBone,
|
pose_bone: PoseBone,
|
||||||
armature_object: Object,
|
armature_object: Object,
|
||||||
|
export_bone: PsaExportBone,
|
||||||
export_space: str,
|
export_space: str,
|
||||||
scale: Vector,
|
scale: Vector,
|
||||||
coordinate_system_transform: Matrix,
|
coordinate_system_transform: Matrix,
|
||||||
@@ -61,6 +106,20 @@ def _get_pose_bone_location_and_rotation(
|
|||||||
# 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.
|
||||||
|
|
||||||
|
if export_bone.is_attached_to_armature:
|
||||||
|
# Get the world space matrix of both this bone and the bone that we're attached to,
|
||||||
|
# then calculate a matrix relative to the attached bone.
|
||||||
|
world_matrix = armature_object.matrix_world @ pose_bone.matrix
|
||||||
|
assert export_bone.armature_object
|
||||||
|
my_parent = export_bone.armature_object.parent
|
||||||
|
assert my_parent
|
||||||
|
my_parent_bone = export_bone.armature_object.parent_bone
|
||||||
|
assert my_parent.pose
|
||||||
|
parent_pose_bone = my_parent.pose.bones[my_parent_bone]
|
||||||
|
parent_world_matrix = my_parent.matrix_world @ parent_pose_bone.matrix
|
||||||
|
pose_bone_matrix = parent_world_matrix.inverted() @ world_matrix
|
||||||
|
else:
|
||||||
match export_space:
|
match export_space:
|
||||||
case 'ARMATURE':
|
case 'ARMATURE':
|
||||||
pose_bone_matrix = pose_bone.matrix
|
pose_bone_matrix = pose_bone.matrix
|
||||||
@@ -70,7 +129,6 @@ def _get_pose_bone_location_and_rotation(
|
|||||||
pose_bone_matrix = Matrix.Identity(4)
|
pose_bone_matrix = Matrix.Identity(4)
|
||||||
case _:
|
case _:
|
||||||
assert False, f'Invalid export space: {export_space}'
|
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
|
||||||
@@ -116,7 +174,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
# Build list of PSA bones.
|
# Build list of PSA bones.
|
||||||
# Note that the PSA bones are just here to validate the hierarchy.
|
# Note that the PSA bones are just here to validate the hierarchy.
|
||||||
# The bind pose information is not used by the engine.
|
# The bind pose information is not used by the engine.
|
||||||
psa.bones = [psx_bone for psx_bone, _ in psx_bone_create_result.bones]
|
psa.bones = [bone.psx_bone for bone in psx_bone_create_result.bones]
|
||||||
|
|
||||||
# No bones are going to be exported.
|
# No bones are going to be exported.
|
||||||
if len(psa.bones) == 0:
|
if len(psa.bones) == 0:
|
||||||
@@ -189,22 +247,11 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
|
|
||||||
def add_key(location: Vector, rotation: Quaternion):
|
def add_key(location: Vector, rotation: Quaternion):
|
||||||
key = Psa.Key()
|
key = Psa.Key()
|
||||||
key.location.x = location.x
|
key.location = convert_vector_to_vector3(location)
|
||||||
key.location.y = location.y
|
key.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||||
key.location.z = location.z
|
|
||||||
key.rotation.x = rotation.x
|
|
||||||
key.rotation.y = rotation.y
|
|
||||||
key.rotation.z = rotation.z
|
|
||||||
key.rotation.w = rotation.w
|
|
||||||
key.time = 1.0 / psa_sequence.fps
|
key.time = 1.0 / psa_sequence.fps
|
||||||
psa.keys.append(key)
|
psa.keys.append(key)
|
||||||
|
|
||||||
class PsaExportBone:
|
|
||||||
def __init__(self, pose_bone: PoseBone | None, armature_object: Object | None, scale: Vector):
|
|
||||||
self.pose_bone = pose_bone
|
|
||||||
self.armature_object = armature_object
|
|
||||||
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.
|
||||||
@@ -219,15 +266,16 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
# locations.
|
# locations.
|
||||||
export_bones: list[PsaExportBone] = []
|
export_bones: list[PsaExportBone] = []
|
||||||
|
|
||||||
for psx_bone, armature_object in psx_bone_create_result.bones:
|
for bone in psx_bone_create_result.bones:
|
||||||
if armature_object is None:
|
if bone.armature_object is None:
|
||||||
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
export_bone = PsaExportBone(None, None, Vector((1.0, 1.0, 1.0)))
|
||||||
|
export_bones.append(export_bone)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert armature_object.pose
|
assert bone.armature_object.pose
|
||||||
pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')]
|
pose_bone = bone.armature_object.pose.bones[bone.psx_bone.name.decode('windows-1252')]
|
||||||
|
|
||||||
export_bones.append(PsaExportBone(pose_bone, armature_object, armature_scales[armature_object]))
|
export_bones.append(PsaExportBone(pose_bone, bone.armature_object, armature_scales[bone.armature_object]))
|
||||||
|
|
||||||
match options.sampling_mode:
|
match options.sampling_mode:
|
||||||
case 'INTERPOLATED':
|
case 'INTERPOLATED':
|
||||||
@@ -255,6 +303,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
location, rotation = _get_pose_bone_location_and_rotation(
|
location, rotation = _get_pose_bone_location_and_rotation(
|
||||||
export_bone.pose_bone,
|
export_bone.pose_bone,
|
||||||
export_bone.armature_object,
|
export_bone.armature_object,
|
||||||
|
export_bone,
|
||||||
options.export_space,
|
options.export_space,
|
||||||
export_bone.scale,
|
export_bone.scale,
|
||||||
coordinate_system_transform=coordinate_system_transform
|
coordinate_system_transform=coordinate_system_transform
|
||||||
@@ -278,6 +327,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
location, rotation = _get_pose_bone_location_and_rotation(
|
location, rotation = _get_pose_bone_location_and_rotation(
|
||||||
pose_bone=export_bone.pose_bone,
|
pose_bone=export_bone.pose_bone,
|
||||||
armature_object=export_bone.armature_object,
|
armature_object=export_bone.armature_object,
|
||||||
|
export_bone=export_bone,
|
||||||
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,
|
||||||
@@ -305,6 +355,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
|||||||
location, rotation = _get_pose_bone_location_and_rotation(
|
location, rotation = _get_pose_bone_location_and_rotation(
|
||||||
pose_bone=export_bone.pose_bone,
|
pose_bone=export_bone.pose_bone,
|
||||||
armature_object=export_bone.armature_object,
|
armature_object=export_bone.armature_object,
|
||||||
|
export_bone=export_bone,
|
||||||
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,
|
||||||
|
|||||||
@@ -847,7 +847,12 @@ class PSA_OT_export_collection_populate_sequences(Operator):
|
|||||||
if collection is None:
|
if collection is None:
|
||||||
self.report({'ERROR'}, 'No collection found in context')
|
self.report({'ERROR'}, 'No collection found in context')
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
try:
|
||||||
input_objects = get_psk_input_objects_for_collection(collection)
|
input_objects = get_psk_input_objects_for_collection(collection)
|
||||||
|
except RuntimeError as e:
|
||||||
|
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# Keep track of what sequences were selected, then restore the selected status after we have updated the lists.
|
# Keep track of what sequences were selected, then restore the selected status after we have updated the lists.
|
||||||
def store_is_selected_for_sequence_list(sequences: Iterable[PsaExportSequenceMixin]) -> dict[int, bool]:
|
def store_is_selected_for_sequence_list(sequences: Iterable[PsaExportSequenceMixin]) -> dict[int, bool]:
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
|
|||||||
bone_collection_indices=options.bone_collection_indices
|
bone_collection_indices=options.bone_collection_indices
|
||||||
)
|
)
|
||||||
|
|
||||||
psk.bones = [psx_bone for psx_bone, _ in psx_bone_create_result.bones]
|
psk.bones = [bone.psx_bone for bone in psx_bone_create_result.bones]
|
||||||
|
|
||||||
# Materials
|
# Materials
|
||||||
match options.material_order_mode:
|
match options.material_order_mode:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from collections import Counter
|
|||||||
from typing import List, Iterable, Optional, Dict, Tuple, cast as typing_cast
|
from typing import List, Iterable, Optional, Dict, Tuple, cast as typing_cast
|
||||||
from bpy.types import Armature, AnimData, Collection, Context, Object, ArmatureModifier, SpaceProperties, PropertyGroup
|
from bpy.types import Armature, AnimData, Collection, Context, Object, ArmatureModifier, SpaceProperties, PropertyGroup
|
||||||
from mathutils import Matrix, Vector, Quaternion as BpyQuaternion
|
from mathutils import Matrix, Vector, Quaternion as BpyQuaternion
|
||||||
from psk_psa_py.shared.data import PsxBone, Quaternion
|
from psk_psa_py.shared.data import PsxBone, Quaternion, Vector3
|
||||||
|
|
||||||
from ..shared.types import BpyCollectionProperty, PSX_PG_bone_collection_list_item
|
from ..shared.types import BpyCollectionProperty, PSX_PG_bone_collection_list_item
|
||||||
|
|
||||||
@@ -69,30 +69,33 @@ def populate_bone_collection_list(
|
|||||||
unique_armature_data = set()
|
unique_armature_data = set()
|
||||||
|
|
||||||
for armature_object in armature_objects:
|
for armature_object in armature_objects:
|
||||||
armature = typing_cast(Armature, armature_object.data)
|
armature_data = typing_cast(Armature, armature_object.data)
|
||||||
|
|
||||||
if armature is None:
|
if armature_data is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if primary_key == 'DATA' and armature_object.data in unique_armature_data:
|
if primary_key == 'DATA':
|
||||||
|
if armature_data in unique_armature_data:
|
||||||
# Skip this armature since we have already added an entry for it and we are using the data as the key.
|
# Skip this armature since we have already added an entry for it and we are using the data as the key.
|
||||||
continue
|
continue
|
||||||
|
unique_armature_data.add(armature_data)
|
||||||
|
|
||||||
unique_armature_data.add(armature_object.data)
|
unassigned_bone_count = sum(map(lambda bone: 1 if len(bone.collections) == 0 else 0, armature_data.bones))
|
||||||
|
|
||||||
|
if unassigned_bone_count > 0:
|
||||||
item = bone_collection_list.add()
|
item = bone_collection_list.add()
|
||||||
item.armature_object_name = armature_object.name
|
item.armature_object_name = armature_object.name
|
||||||
item.armature_data_name = armature_object.data.name if armature_object.data else ''
|
item.armature_data_name = armature_data.name if armature_data else ''
|
||||||
item.name = 'Unassigned' # TODO: localize
|
item.name = 'Unassigned'
|
||||||
item.index = -1
|
item.index = -1
|
||||||
# Count the number of bones without an assigned bone collection
|
# Count the number of bones without an assigned bone collection
|
||||||
item.count = sum(map(lambda bone: 1 if len(bone.collections) == 0 else 0, armature.bones))
|
item.count = unassigned_bone_count
|
||||||
item.is_selected = unassigned_collection_is_selected
|
item.is_selected = unassigned_collection_is_selected
|
||||||
|
|
||||||
for bone_collection_index, bone_collection in enumerate(armature.collections_all):
|
for bone_collection_index, bone_collection in enumerate(armature_data.collections_all):
|
||||||
item = bone_collection_list.add()
|
item = bone_collection_list.add()
|
||||||
item.armature_object_name = armature_object.name
|
item.armature_object_name = armature_object.name
|
||||||
item.armature_data_name = armature_object.data.name if armature_object.data else ''
|
item.armature_data_name = armature_data.name if armature_data else ''
|
||||||
item.name = bone_collection.name
|
item.name = bone_collection.name
|
||||||
item.index = bone_collection_index
|
item.index = bone_collection_index
|
||||||
item.count = len(bone_collection.bones)
|
item.count = len(bone_collection.bones)
|
||||||
@@ -168,7 +171,6 @@ def convert_string_to_cp1252_bytes(string: str) -> bytes:
|
|||||||
raise RuntimeError(f'The string "{string}" contains characters that cannot be encoded in the Windows-1252 codepage') from e
|
raise RuntimeError(f'The string "{string}" contains characters that cannot be encoded in the Windows-1252 codepage') from e
|
||||||
|
|
||||||
|
|
||||||
# TODO: Perhaps export space should just be a transform matrix, since the below is not actually used unless we're using WORLD space.
|
|
||||||
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,
|
||||||
@@ -212,23 +214,23 @@ def create_psx_bones_from_blender_bones(
|
|||||||
location.z *= armature_object_scale.z
|
location.z *= armature_object_scale.z
|
||||||
|
|
||||||
# Copy the calculated location and rotation to the bone.
|
# Copy the calculated location and rotation to the bone.
|
||||||
psx_bone.location.x = location.x
|
psx_bone.location = convert_vector_to_vector3(location)
|
||||||
psx_bone.location.y = location.y
|
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||||
psx_bone.location.z = location.z
|
|
||||||
|
|
||||||
psx_bone.rotation.w = rotation.w
|
|
||||||
psx_bone.rotation.x = rotation.x
|
|
||||||
psx_bone.rotation.y = rotation.y
|
|
||||||
psx_bone.rotation.z = rotation.z
|
|
||||||
|
|
||||||
psx_bones.append(psx_bone)
|
psx_bones.append(psx_bone)
|
||||||
|
|
||||||
return psx_bones
|
return psx_bones
|
||||||
|
|
||||||
|
|
||||||
|
class PsxBoneResult:
|
||||||
|
def __init__(self, psx_bone: PsxBone, armature_object: Object | None) -> None:
|
||||||
|
self.psx_bone: PsxBone = psx_bone
|
||||||
|
self.armature_object: Object | None = armature_object
|
||||||
|
|
||||||
|
|
||||||
class PsxBoneCreateResult:
|
class PsxBoneCreateResult:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
bones: list[tuple[PsxBone, Object | None]], # List of tuples of (psx_bone, armature_object)
|
bones: list[PsxBoneResult], # List of tuples of (psx_bone, armature_object)
|
||||||
armature_object_root_bone_indices: dict[Object, int],
|
armature_object_root_bone_indices: dict[Object, int],
|
||||||
armature_object_bone_names: dict[Object, list[str]],
|
armature_object_bone_names: dict[Object, list[str]],
|
||||||
):
|
):
|
||||||
@@ -238,16 +240,30 @@ class PsxBoneCreateResult:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_false_root_bone(self) -> bool:
|
def has_false_root_bone(self) -> bool:
|
||||||
return len(self.bones) > 0 and self.bones[0][1] is None
|
return len(self.bones) > 0 and self.bones[0].armature_object is None
|
||||||
|
|
||||||
|
|
||||||
def convert_bpy_quaternion_to_psx_quaternion(other: BpyQuaternion) -> Quaternion:
|
def convert_vector_to_vector3(vector: Vector) -> Vector3:
|
||||||
quaternion = Quaternion()
|
"""
|
||||||
quaternion.x = other.x
|
Convert a Blender mathutils.Vector to a psk_psa_py Vector3.
|
||||||
quaternion.y = other.y
|
"""
|
||||||
quaternion.z = other.z
|
vector3 = Vector3()
|
||||||
quaternion.w = other.w
|
vector3.x = vector.x
|
||||||
return quaternion
|
vector3.y = vector.y
|
||||||
|
vector3.z = vector.z
|
||||||
|
return vector3
|
||||||
|
|
||||||
|
|
||||||
|
def convert_bpy_quaternion_to_psx_quaternion(quaternion: BpyQuaternion) -> Quaternion:
|
||||||
|
"""
|
||||||
|
Convert a Blender mathutils.Quaternion to a psk_psa_py Quaternion.
|
||||||
|
"""
|
||||||
|
psx_quaternion = Quaternion()
|
||||||
|
psx_quaternion.x = quaternion.x
|
||||||
|
psx_quaternion.y = quaternion.y
|
||||||
|
psx_quaternion.z = quaternion.z
|
||||||
|
psx_quaternion.w = quaternion.w
|
||||||
|
return psx_quaternion
|
||||||
|
|
||||||
|
|
||||||
class PsxBoneCollection:
|
class PsxBoneCollection:
|
||||||
@@ -366,27 +382,7 @@ def create_psx_bones(
|
|||||||
# Store the index of the root bone for each armature object.
|
# Store the index of the root bone for each armature object.
|
||||||
# We will need this later to correctly assign vertex weights.
|
# We will need this later to correctly assign vertex weights.
|
||||||
armature_object_root_bone_indices: dict[Object | None, int] = dict()
|
armature_object_root_bone_indices: dict[Object | None, int] = dict()
|
||||||
bones: list[tuple[PsxBone, Object | None]] = []
|
bones: list[PsxBoneResult] = []
|
||||||
|
|
||||||
if len(armature_objects) == 0 or total_bone_count == 0:
|
|
||||||
# If the mesh has no armature object or no bones, simply assign it a dummy bone at the root to satisfy the
|
|
||||||
# requirement that a PSK file must have at least one bone.
|
|
||||||
psx_bone = PsxBone()
|
|
||||||
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
|
|
||||||
psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
|
|
||||||
bones.append((psx_bone, None))
|
|
||||||
|
|
||||||
armature_object_root_bone_indices[None] = 0
|
|
||||||
else:
|
|
||||||
# If we have multiple root armature objects, create a root bone at the world origin.
|
|
||||||
if len(armature_tree.root_nodes) > 1:
|
|
||||||
psx_bone = PsxBone()
|
|
||||||
psx_bone.name = convert_string_to_cp1252_bytes(root_bone_name)
|
|
||||||
psx_bone.children_count = total_bone_count
|
|
||||||
psx_bone.rotation = Quaternion(0.0, 0.0, 0.0, 1.0)
|
|
||||||
bones.append((psx_bone, None))
|
|
||||||
|
|
||||||
armature_object_root_bone_indices[None] = 0
|
|
||||||
|
|
||||||
# Iterate through all the armature objects.
|
# Iterate through all the armature objects.
|
||||||
for armature_object in armature_objects:
|
for armature_object in armature_objects:
|
||||||
@@ -423,16 +419,11 @@ def create_psx_bones(
|
|||||||
root_bone = armature_psx_bones[0]
|
root_bone = armature_psx_bones[0]
|
||||||
root_bone_location = Vector((root_bone.location.x, root_bone.location.y, root_bone.location.z))
|
root_bone_location = Vector((root_bone.location.x, root_bone.location.y, root_bone.location.z))
|
||||||
relative_location = parent_bone_world_rotation.inverted() @ (root_bone_location - parent_bone_world_location)
|
relative_location = parent_bone_world_rotation.inverted() @ (root_bone_location - parent_bone_world_location)
|
||||||
root_bone.location.x = relative_location.x
|
root_bone.location = convert_vector_to_vector3(relative_location)
|
||||||
root_bone.location.y = relative_location.y
|
|
||||||
root_bone.location.z = relative_location.z
|
|
||||||
# Convert the root bone rotation to be relative to the parent bone.
|
# Convert the root bone rotation to be relative to the parent bone.
|
||||||
root_bone_rotation = BpyQuaternion((root_bone.rotation.w, root_bone.rotation.x, root_bone.rotation.y, root_bone.rotation.z))
|
root_bone_rotation = BpyQuaternion((root_bone.rotation.w, root_bone.rotation.x, root_bone.rotation.y, root_bone.rotation.z))
|
||||||
relative_rotation = parent_bone_world_rotation.inverted() @ root_bone_rotation
|
relative_rotation = parent_bone_world_rotation.inverted() @ root_bone_rotation
|
||||||
root_bone.rotation.w = relative_rotation.w
|
root_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(relative_rotation)
|
||||||
root_bone.rotation.x = relative_rotation.x
|
|
||||||
root_bone.rotation.y = relative_rotation.y
|
|
||||||
root_bone.rotation.z = relative_rotation.z
|
|
||||||
|
|
||||||
case 'OBJECT':
|
case 'OBJECT':
|
||||||
raise NotImplementedError('Parenting armature objects to other armature objects is not yet implemented.')
|
raise NotImplementedError('Parenting armature objects to other armature objects is not yet implemented.')
|
||||||
@@ -450,7 +441,7 @@ def create_psx_bones(
|
|||||||
|
|
||||||
armature_object_root_bone_indices[armature_object] = len(bones)
|
armature_object_root_bone_indices[armature_object] = len(bones)
|
||||||
|
|
||||||
bones.extend((psx_bone, armature_object) for psx_bone in armature_psx_bones)
|
bones.extend(PsxBoneResult(psx_bone, armature_object) for psx_bone in armature_psx_bones)
|
||||||
|
|
||||||
# Check if any of the armatures are parented to one another.
|
# Check if any of the armatures are parented to one another.
|
||||||
# If so, adjust the hierarchy as though they are part of the same armature object.
|
# If so, adjust the hierarchy as though they are part of the same armature object.
|
||||||
@@ -466,17 +457,17 @@ def create_psx_bones(
|
|||||||
match armature_object.parent_type:
|
match armature_object.parent_type:
|
||||||
case 'OBJECT':
|
case 'OBJECT':
|
||||||
# Parent this armature's root bone to the root bone of the parent object.
|
# Parent this armature's root bone to the root bone of the parent object.
|
||||||
bones[root_bone_index][0].parent_index = parent_root_bone_index
|
bones[root_bone_index].psx_bone.parent_index = parent_root_bone_index
|
||||||
case 'BONE':
|
case 'BONE':
|
||||||
# Parent this armature's root bone to the specified bone in the parent.
|
# Parent this armature's root bone to the specified bone in the parent.
|
||||||
new_parent_index = None
|
new_parent_index = None
|
||||||
for bone_index, (bone, bone_armature_object) in enumerate(bones):
|
for bone_index, bone in enumerate(bones):
|
||||||
if bone.name == convert_string_to_cp1252_bytes(armature_object.parent_bone) and bone_armature_object == armature_object.parent:
|
if bone.psx_bone.name == convert_string_to_cp1252_bytes(armature_object.parent_bone) and bone.armature_object == armature_object.parent:
|
||||||
new_parent_index = bone_index
|
new_parent_index = bone_index
|
||||||
break
|
break
|
||||||
if new_parent_index == None:
|
if new_parent_index == None:
|
||||||
raise RuntimeError(f'Bone \'{armature_object.parent_bone}\' could not be found in armature \'{armature_object.parent.name}\'.')
|
raise RuntimeError(f'Bone \'{armature_object.parent_bone}\' could not be found in armature \'{armature_object.parent.name}\'.')
|
||||||
bones[root_bone_index][0].parent_index = new_parent_index
|
bones[root_bone_index].psx_bone.parent_index = new_parent_index
|
||||||
case _:
|
case _:
|
||||||
raise RuntimeError(f'Unhandled parent type ({armature_object.parent_type}) for object {armature_object.name}.\n'
|
raise RuntimeError(f'Unhandled parent type ({armature_object.parent_type}) for object {armature_object.name}.\n'
|
||||||
f'Parent type must be \'Object\' or \'Bone\'.'
|
f'Parent type must be \'Object\' or \'Bone\'.'
|
||||||
@@ -489,35 +480,25 @@ def create_psx_bones(
|
|||||||
case 'ARMATURE':
|
case 'ARMATURE':
|
||||||
# The bone is in world-space. We need to convert it to armature (object) space.
|
# The bone is in world-space. We need to convert it to armature (object) space.
|
||||||
# Get this from matrix_local.
|
# Get this from matrix_local.
|
||||||
root_bone, root_bone_armature_object = bones[0]
|
root_bone, root_bone_armature_object = bones[0].psx_bone, bones[0].armature_object
|
||||||
if root_bone_armature_object is None:
|
if root_bone_armature_object is None:
|
||||||
raise RuntimeError('Cannot export to Armature space when multiple armatures are being exported.')
|
raise RuntimeError('Cannot export to Armature space when multiple armatures are being exported.')
|
||||||
|
|
||||||
armature_data = typing_cast(Armature, root_bone_armature_object.data)
|
armature_data = typing_cast(Armature, root_bone_armature_object.data)
|
||||||
matrix_local = armature_data.bones[root_bone.name.decode('windows-1252')].matrix_local
|
matrix_local = armature_data.bones[root_bone.name.decode('windows-1252')].matrix_local
|
||||||
location, rotation, _ = matrix_local.decompose()
|
location, rotation, _ = matrix_local.decompose()
|
||||||
root_bone.location.x = location.x
|
root_bone.location = convert_vector_to_vector3(location)
|
||||||
root_bone.location.y = location.y
|
root_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||||
root_bone.location.z = location.z
|
|
||||||
root_bone.rotation.w = rotation.w
|
|
||||||
root_bone.rotation.x = rotation.x
|
|
||||||
root_bone.rotation.y = rotation.y
|
|
||||||
root_bone.rotation.z = rotation.z
|
|
||||||
case 'ROOT':
|
case 'ROOT':
|
||||||
# Zero out the root bone transforms.
|
# Zero out the root bone transforms.
|
||||||
root_bone = bones[0][0]
|
root_bone = bones[0].psx_bone
|
||||||
root_bone.location.x = 0.0
|
root_bone.location = Vector3.zero()
|
||||||
root_bone.location.y = 0.0
|
root_bone.rotation = Quaternion.identity()
|
||||||
root_bone.location.z = 0.0
|
|
||||||
root_bone.rotation.w = 1.0
|
|
||||||
root_bone.rotation.x = 0.0
|
|
||||||
root_bone.rotation.y = 0.0
|
|
||||||
root_bone.rotation.z = 0.0
|
|
||||||
case _:
|
case _:
|
||||||
assert False, f'Invalid export space: {export_space}'
|
assert False, f'Invalid export space: {export_space}'
|
||||||
|
|
||||||
# Check if there are bone name conflicts between armatures.
|
# Check if there are bone name conflicts between armatures.
|
||||||
bone_name_counts = Counter(bone[0].name.decode('windows-1252').upper() for bone in bones)
|
bone_name_counts = Counter(bone.psx_bone.name.decode('windows-1252').upper() for bone in bones)
|
||||||
for bone_name, count in bone_name_counts.items():
|
for bone_name, count in bone_name_counts.items():
|
||||||
if count > 1:
|
if count > 1:
|
||||||
error_message = f'Found {count} bones with the name "{bone_name}". '
|
error_message = f'Found {count} bones with the name "{bone_name}". '
|
||||||
@@ -529,16 +510,16 @@ def create_psx_bones(
|
|||||||
raise RuntimeError(error_message)
|
raise RuntimeError(error_message)
|
||||||
|
|
||||||
# Apply the scale to the bone locations.
|
# Apply the scale to the bone locations.
|
||||||
for psx_bone, _ in bones:
|
for bone in bones:
|
||||||
psx_bone.location.x *= scale
|
bone.psx_bone.location.x *= scale
|
||||||
psx_bone.location.y *= scale
|
bone.psx_bone.location.y *= scale
|
||||||
psx_bone.location.z *= scale
|
bone.psx_bone.location.z *= scale
|
||||||
|
|
||||||
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
|
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
|
||||||
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
|
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
|
||||||
|
|
||||||
# Apply the coordinate system transform to the root bone.
|
# Apply the coordinate system transform to the root bone.
|
||||||
root_psx_bone = bones[0][0]
|
root_psx_bone = bones[0].psx_bone
|
||||||
# Get transform matrix from root bone location and rotation.
|
# 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_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_rotation = BpyQuaternion((root_psx_bone.rotation.w, root_psx_bone.rotation.x, root_psx_bone.rotation.y, root_psx_bone.rotation.z))
|
||||||
@@ -548,13 +529,8 @@ def create_psx_bones(
|
|||||||
)
|
)
|
||||||
root_bone_matrix = coordinate_system_default_rotation.inverted().to_matrix().to_4x4() @ root_bone_matrix
|
root_bone_matrix = coordinate_system_default_rotation.inverted().to_matrix().to_4x4() @ root_bone_matrix
|
||||||
location, rotation, _ = root_bone_matrix.decompose()
|
location, rotation, _ = root_bone_matrix.decompose()
|
||||||
root_psx_bone.location.x = location.x
|
root_psx_bone.location = convert_vector_to_vector3(location)
|
||||||
root_psx_bone.location.y = location.y
|
root_psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||||
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)
|
convert_bpy_quaternion_to_psx_quaternion(coordinate_system_default_rotation)
|
||||||
|
|
||||||
@@ -678,7 +654,7 @@ def get_armature_for_mesh_object(mesh_object: Object) -> Optional[Object]:
|
|||||||
def _get_psk_input_objects(mesh_dfs_objects: Iterable[DfsObject]) -> PskInputObjects:
|
def _get_psk_input_objects(mesh_dfs_objects: Iterable[DfsObject]) -> PskInputObjects:
|
||||||
mesh_dfs_objects = list(mesh_dfs_objects)
|
mesh_dfs_objects = list(mesh_dfs_objects)
|
||||||
if len(mesh_dfs_objects) == 0:
|
if len(mesh_dfs_objects) == 0:
|
||||||
raise RuntimeError('At least one mesh must be selected')
|
raise RuntimeError('No mesh objects were found to export.')
|
||||||
input_objects = PskInputObjects()
|
input_objects = PskInputObjects()
|
||||||
input_objects.mesh_dfs_objects = mesh_dfs_objects
|
input_objects.mesh_dfs_objects = mesh_dfs_objects
|
||||||
# Get the armature objects used on all the meshes being exported.
|
# Get the armature objects used on all the meshes being exported.
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class PSK_OT_bone_collection_list_populate(Operator):
|
|||||||
populate_bone_collection_list(export_operator.bone_collection_list, input_objects.armature_objects)
|
populate_bone_collection_list(export_operator.bone_collection_list, input_objects.armature_objects)
|
||||||
|
|
||||||
for bone_collection in export_operator.bone_collection_list:
|
for bone_collection in export_operator.bone_collection_list:
|
||||||
bone_collection.is_selected = selected_status[hash(bone_collection)]
|
bone_collection.is_selected = selected_status.get(hash(bone_collection), False)
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user