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 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:
|
||||
@@ -47,9 +47,54 @@ class PsaBuildOptions:
|
||||
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(
|
||||
pose_bone: PoseBone,
|
||||
armature_object: Object,
|
||||
export_bone: PsaExportBone,
|
||||
export_space: str,
|
||||
scale: Vector,
|
||||
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.
|
||||
# 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.
|
||||
|
||||
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:
|
||||
case 'ARMATURE':
|
||||
pose_bone_matrix = pose_bone.matrix
|
||||
@@ -70,7 +129,6 @@ def _get_pose_bone_location_and_rotation(
|
||||
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
|
||||
# other bones are relative to their parent bones.
|
||||
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.
|
||||
# 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]
|
||||
psa.bones = [bone.psx_bone for bone in psx_bone_create_result.bones]
|
||||
|
||||
# No bones are going to be exported.
|
||||
if len(psa.bones) == 0:
|
||||
@@ -189,22 +247,11 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
||||
|
||||
def add_key(location: Vector, rotation: Quaternion):
|
||||
key = Psa.Key()
|
||||
key.location.x = location.x
|
||||
key.location.y = location.y
|
||||
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.location = convert_vector_to_vector3(location)
|
||||
key.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||
key.time = 1.0 / psa_sequence.fps
|
||||
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] = {}
|
||||
|
||||
# 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.
|
||||
export_bones: list[PsaExportBone] = []
|
||||
|
||||
for psx_bone, armature_object in psx_bone_create_result.bones:
|
||||
if armature_object is None:
|
||||
export_bones.append(PsaExportBone(None, None, Vector((1.0, 1.0, 1.0))))
|
||||
for bone in psx_bone_create_result.bones:
|
||||
if bone.armature_object is None:
|
||||
export_bone = PsaExportBone(None, None, Vector((1.0, 1.0, 1.0)))
|
||||
export_bones.append(export_bone)
|
||||
continue
|
||||
|
||||
assert armature_object.pose
|
||||
pose_bone = armature_object.pose.bones[psx_bone.name.decode('windows-1252')]
|
||||
assert bone.armature_object.pose
|
||||
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:
|
||||
case 'INTERPOLATED':
|
||||
@@ -255,6 +303,7 @@ def build_psa(context: Context, options: PsaBuildOptions) -> Psa:
|
||||
location, rotation = _get_pose_bone_location_and_rotation(
|
||||
export_bone.pose_bone,
|
||||
export_bone.armature_object,
|
||||
export_bone,
|
||||
options.export_space,
|
||||
export_bone.scale,
|
||||
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(
|
||||
pose_bone=export_bone.pose_bone,
|
||||
armature_object=export_bone.armature_object,
|
||||
export_bone=export_bone,
|
||||
export_space=options.export_space,
|
||||
scale=export_bone.scale,
|
||||
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(
|
||||
pose_bone=export_bone.pose_bone,
|
||||
armature_object=export_bone.armature_object,
|
||||
export_bone=export_bone,
|
||||
export_space=options.export_space,
|
||||
scale=export_bone.scale,
|
||||
coordinate_system_transform=coordinate_system_transform,
|
||||
|
||||
@@ -847,7 +847,12 @@ class PSA_OT_export_collection_populate_sequences(Operator):
|
||||
if collection is None:
|
||||
self.report({'ERROR'}, 'No collection found in context')
|
||||
return {'CANCELLED'}
|
||||
|
||||
try:
|
||||
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.
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
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 bpy.types import Armature, AnimData, Collection, Context, Object, ArmatureModifier, SpaceProperties, PropertyGroup
|
||||
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
|
||||
|
||||
@@ -69,30 +69,33 @@ def populate_bone_collection_list(
|
||||
unique_armature_data = set()
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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.armature_object_name = armature_object.name
|
||||
item.armature_data_name = armature_object.data.name if armature_object.data else ''
|
||||
item.name = 'Unassigned' # TODO: localize
|
||||
item.armature_data_name = armature_data.name if armature_data else ''
|
||||
item.name = 'Unassigned'
|
||||
item.index = -1
|
||||
# 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
|
||||
|
||||
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.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.index = bone_collection_index
|
||||
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
|
||||
|
||||
|
||||
# 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(
|
||||
bones: List[bpy.types.Bone],
|
||||
armature_object_matrix_world: Matrix,
|
||||
@@ -212,23 +214,23 @@ def create_psx_bones_from_blender_bones(
|
||||
location.z *= armature_object_scale.z
|
||||
|
||||
# Copy the calculated location and rotation to the bone.
|
||||
psx_bone.location.x = location.x
|
||||
psx_bone.location.y = location.y
|
||||
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_bone.location = convert_vector_to_vector3(location)
|
||||
psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||
|
||||
psx_bones.append(psx_bone)
|
||||
|
||||
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:
|
||||
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_bone_names: dict[Object, list[str]],
|
||||
):
|
||||
@@ -238,16 +240,30 @@ class PsxBoneCreateResult:
|
||||
|
||||
@property
|
||||
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:
|
||||
quaternion = Quaternion()
|
||||
quaternion.x = other.x
|
||||
quaternion.y = other.y
|
||||
quaternion.z = other.z
|
||||
quaternion.w = other.w
|
||||
return quaternion
|
||||
def convert_vector_to_vector3(vector: Vector) -> Vector3:
|
||||
"""
|
||||
Convert a Blender mathutils.Vector to a psk_psa_py Vector3.
|
||||
"""
|
||||
vector3 = Vector3()
|
||||
vector3.x = vector.x
|
||||
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:
|
||||
@@ -366,27 +382,7 @@ def create_psx_bones(
|
||||
# Store the index of the root bone for each armature object.
|
||||
# We will need this later to correctly assign vertex weights.
|
||||
armature_object_root_bone_indices: dict[Object | None, int] = dict()
|
||||
bones: list[tuple[PsxBone, Object | None]] = []
|
||||
|
||||
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
|
||||
bones: list[PsxBoneResult] = []
|
||||
|
||||
# Iterate through all the armature objects.
|
||||
for armature_object in armature_objects:
|
||||
@@ -423,16 +419,11 @@ def create_psx_bones(
|
||||
root_bone = armature_psx_bones[0]
|
||||
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)
|
||||
root_bone.location.x = relative_location.x
|
||||
root_bone.location.y = relative_location.y
|
||||
root_bone.location.z = relative_location.z
|
||||
root_bone.location = convert_vector_to_vector3(relative_location)
|
||||
# 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))
|
||||
relative_rotation = parent_bone_world_rotation.inverted() @ root_bone_rotation
|
||||
root_bone.rotation.w = relative_rotation.w
|
||||
root_bone.rotation.x = relative_rotation.x
|
||||
root_bone.rotation.y = relative_rotation.y
|
||||
root_bone.rotation.z = relative_rotation.z
|
||||
root_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(relative_rotation)
|
||||
|
||||
case 'OBJECT':
|
||||
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)
|
||||
|
||||
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.
|
||||
# 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:
|
||||
case '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':
|
||||
# Parent this armature's root bone to the specified bone in the parent.
|
||||
new_parent_index = None
|
||||
for bone_index, (bone, bone_armature_object) in enumerate(bones):
|
||||
if bone.name == convert_string_to_cp1252_bytes(armature_object.parent_bone) and bone_armature_object == armature_object.parent:
|
||||
for bone_index, bone in enumerate(bones):
|
||||
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
|
||||
break
|
||||
if new_parent_index == None:
|
||||
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 _:
|
||||
raise RuntimeError(f'Unhandled parent type ({armature_object.parent_type}) for object {armature_object.name}.\n'
|
||||
f'Parent type must be \'Object\' or \'Bone\'.'
|
||||
@@ -489,35 +480,25 @@ def create_psx_bones(
|
||||
case 'ARMATURE':
|
||||
# The bone is in world-space. We need to convert it to armature (object) space.
|
||||
# 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:
|
||||
raise RuntimeError('Cannot export to Armature space when multiple armatures are being exported.')
|
||||
|
||||
armature_data = typing_cast(Armature, root_bone_armature_object.data)
|
||||
matrix_local = armature_data.bones[root_bone.name.decode('windows-1252')].matrix_local
|
||||
location, rotation, _ = matrix_local.decompose()
|
||||
root_bone.location.x = location.x
|
||||
root_bone.location.y = location.y
|
||||
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
|
||||
root_bone.location = convert_vector_to_vector3(location)
|
||||
root_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(rotation)
|
||||
case 'ROOT':
|
||||
# Zero out the root bone transforms.
|
||||
root_bone = bones[0][0]
|
||||
root_bone.location.x = 0.0
|
||||
root_bone.location.y = 0.0
|
||||
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
|
||||
root_bone = bones[0].psx_bone
|
||||
root_bone.location = Vector3.zero()
|
||||
root_bone.rotation = Quaternion.identity()
|
||||
case _:
|
||||
assert False, f'Invalid export space: {export_space}'
|
||||
|
||||
# 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():
|
||||
if count > 1:
|
||||
error_message = f'Found {count} bones with the name "{bone_name}". '
|
||||
@@ -529,16 +510,16 @@ def create_psx_bones(
|
||||
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
|
||||
for bone in bones:
|
||||
bone.psx_bone.location.x *= scale
|
||||
bone.psx_bone.location.y *= scale
|
||||
bone.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]
|
||||
root_psx_bone = bones[0].psx_bone
|
||||
# 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))
|
||||
@@ -548,13 +529,8 @@ def create_psx_bones(
|
||||
)
|
||||
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
|
||||
root_psx_bone.location = convert_vector_to_vector3(location)
|
||||
root_psx_bone.rotation = convert_bpy_quaternion_to_psx_quaternion(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:
|
||||
mesh_dfs_objects = list(mesh_dfs_objects)
|
||||
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.mesh_dfs_objects = mesh_dfs_objects
|
||||
# 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)
|
||||
|
||||
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'}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user