Meshes are now exported correctly when using armature trees.

We need to add thorough testing for all the various settings and manually verify their correctness.
This commit is contained in:
Colin Basnett
2026-01-04 15:14:51 -08:00
parent 0a783bb89d
commit f6625d983a

View File

@@ -38,8 +38,9 @@ class PskBuildResult(object):
self.warnings: List[str] = warnings self.warnings: List[str] = warnings
def _get_mesh_export_space_matrix(armature_object: Optional[Object], export_space: str) -> Matrix: def _get_mesh_export_space_matrix(armature_object: Object | None, export_space: str, root_armature_object: Object | None) -> Matrix:
if armature_object is None: # TODO: this should be a bundle of armature objects. otherwise this creates a scenario where you can have
if armature_object is None or root_armature_object is None:
return Matrix.Identity(4) return Matrix.Identity(4)
def get_object_space_matrix(obj: Object) -> Matrix: def get_object_space_matrix(obj: Object) -> Matrix:
@@ -47,15 +48,20 @@ def _get_mesh_export_space_matrix(armature_object: Optional[Object], export_spac
# We neutralize the scale here because the scale is already applied to the mesh objects implicitly. # We neutralize the scale here because the scale is already applied to the mesh objects implicitly.
return Matrix.Translation(translation) @ rotation.to_matrix().to_4x4() return Matrix.Translation(translation) @ rotation.to_matrix().to_4x4()
armature_space_matrix = get_object_space_matrix(armature_object)
root_armature_space_matrix = get_object_space_matrix(root_armature_object)
relative_matrix = root_armature_space_matrix @ armature_space_matrix.inverted()
match export_space: match export_space:
case 'WORLD': case 'WORLD':
return Matrix.Identity(4) return Matrix.Identity(4)
case 'ARMATURE': case 'ARMATURE':
return get_object_space_matrix(armature_object).inverted() return (armature_space_matrix @ relative_matrix).inverted()
case 'ROOT': case 'ROOT':
armature_data = typing_cast(Armature, armature_object.data) root_armature_data = typing_cast(Armature, root_armature_object.data)
armature_space_matrix = get_object_space_matrix(armature_object) @ armature_data.bones[0].matrix_local if len(root_armature_data.bones) == 0:
return armature_space_matrix.inverted() raise RuntimeError(f'Armature {root_armature_data.name} has no bones')
return (armature_space_matrix @ relative_matrix @ root_armature_data.bones[0].matrix_local).inverted()
case _: case _:
assert False, f'Invalid export space: {export_space}' assert False, f'Invalid export space: {export_space}'
@@ -131,26 +137,23 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
psk.materials.append(psk_material) psk.materials.append(psk_material)
context.window_manager.progress_begin(0, len(input_objects.mesh_dfs_objects)) context.window_manager.progress_begin(0, len(input_objects.mesh_dfs_objects))
coordinate_system_matrix = get_coordinate_system_transform(options.forward_axis, options.up_axis) coordinate_system_matrix = get_coordinate_system_transform(options.forward_axis, options.up_axis)
root_armature_object = next(iter(input_objects.armature_objects), None)
# Calculate the export spaces for the armature objects. # Calculate the export spaces for the armature objects.
# This is used later to transform the mesh object geometry into the export space. # This is used later to transform the mesh object geometry into the export space.
armature_mesh_export_space_matrices: Dict[Optional[Object], Matrix] = {None: Matrix.Identity(4)} armature_mesh_export_space_matrices: dict[Object | None, Matrix] = {None: Matrix.Identity(4)}
if options.export_space == 'ARMATURE': if options.export_space == 'ARMATURE':
# For meshes without an armature modifier, we need to set the export space to the first armature object. # For meshes without an armature modifier, we need to set the export space to the first armature object.
armature_mesh_export_space_matrices[None] = _get_mesh_export_space_matrix(next(iter(input_objects.armature_objects), None), options.export_space) armature_mesh_export_space_matrices[None] = _get_mesh_export_space_matrix(root_armature_object, options.export_space, root_armature_object)
for armature_object in armature_objects: for armature_object in armature_objects:
armature_mesh_export_space_matrices[armature_object] = _get_mesh_export_space_matrix(armature_object, options.export_space) # TODO: also handle the case of multiple roots; dont' just assume we have one!
armature_mesh_export_space_matrices[armature_object] = _get_mesh_export_space_matrix(armature_object, options.export_space, root_armature_object)
# TODO: we need to handle armature hierarchies here. if an object is parented to another armature,
# we need to take that into account when calculating the export space matrix.
original_armature_object_pose_positions = {a: a.data.pose_position for a in armature_objects}
# Temporarily force the armature into the rest position. # Temporarily force the armature into the rest position.
# We will undo this later. # The original pose position setting will be restored at the end.
original_armature_object_pose_positions = {a: a.data.pose_position for a in armature_objects}
for armature_object in armature_objects: for armature_object in armature_objects:
armature_data = typing_cast(Armature, armature_object.data) armature_data = typing_cast(Armature, armature_object.data)
armature_data.pose_position = 'REST' armature_data.pose_position = 'REST'