build_psk now creates an armature tree and uses that instead of a flat armature list

This commit is contained in:
Colin Basnett
2026-01-04 16:13:57 -08:00
parent f6625d983a
commit 12025452d0
2 changed files with 36 additions and 24 deletions

View File

@@ -8,6 +8,8 @@ from psk_psa_py.shared.data import Vector3
from psk_psa_py.psk.data import Psk from psk_psa_py.psk.data import Psk
from .properties import triangle_type_and_bit_flags_to_poly_flags from .properties import triangle_type_and_bit_flags_to_poly_flags
from ..shared.helpers import ( from ..shared.helpers import (
ObjectNode,
ObjectTree,
PskInputObjects, PskInputObjects,
PsxBoneCollection, PsxBoneCollection,
convert_string_to_cp1252_bytes, convert_string_to_cp1252_bytes,
@@ -38,11 +40,13 @@ class PskBuildResult(object):
self.warnings: List[str] = warnings self.warnings: List[str] = warnings
def _get_mesh_export_space_matrix(armature_object: Object | None, export_space: str, root_armature_object: Object | None) -> Matrix: def _get_mesh_export_space_matrix(node: ObjectNode | None, export_space: str) -> Matrix:
# TODO: this should be a bundle of armature objects. otherwise this creates a scenario where you can have if node is None:
if armature_object is None or root_armature_object is None:
return Matrix.Identity(4) return Matrix.Identity(4)
armature_object = node.object
root_armature_object = node.root.object
def get_object_space_matrix(obj: Object) -> Matrix: def get_object_space_matrix(obj: Object) -> Matrix:
translation, rotation, _ = obj.matrix_world.decompose() translation, rotation, _ = obj.matrix_world.decompose()
# 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.
@@ -86,6 +90,7 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
assert context.window_manager assert context.window_manager
armature_objects = list(input_objects.armature_objects) armature_objects = list(input_objects.armature_objects)
armature_object_tree = ObjectTree(input_objects.armature_objects)
warnings: list[str] = [] warnings: list[str] = []
psk = Psk() psk = Psk()
@@ -138,18 +143,19 @@ def build_psk(context: Context, input_objects: PskInputObjects, options: PskBuil
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) root_armature_object = next(iter(armature_object_tree), 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[Object | None, 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(root_armature_object, options.export_space, root_armature_object) armature_mesh_export_space_matrices[None] = _get_mesh_export_space_matrix(root_armature_object, options.export_space)
for armature_object in armature_objects: # TODO: also handle the case of multiple roots; dont' just assume we have one!
# TODO: also handle the case of multiple roots; dont' just assume we have one! for armature_node in iter(armature_object_tree):
armature_mesh_export_space_matrices[armature_object] = _get_mesh_export_space_matrix(armature_object, options.export_space, root_armature_object) armature_mesh_export_space_matrices[armature_node.object] = _get_mesh_export_space_matrix(armature_node, options.export_space)
# Temporarily force the armature into the rest position. # Temporarily force the armature into the rest position.
# The original pose position setting will be restored at the end. # The original pose position setting will be restored at the end.

View File

@@ -334,41 +334,47 @@ class PsxBoneCollection:
class ObjectNode: class ObjectNode:
def __init__(self, obj: Object): def __init__(self, obj: Object):
self.object = obj self.object = obj
self.children: List['ObjectNode'] = [] self.parent: ObjectNode | None = None
self.children: List[ObjectNode] = []
@property
def root(self):
"""
Gets the root in the object hierarchy. This can return itself if this node has no parent.
"""
n = self
while n.parent is not None:
n = n.parent
return n
class ObjectTree: class ObjectTree:
def __init__(self) -> None: '''
A tree of the armature objects based on their hierarchy.
'''
def __init__(self, objects: Iterable[Object]):
self.root_nodes: List[ObjectNode] = [] self.root_nodes: List[ObjectNode] = []
@staticmethod
def from_objects(objects: Iterable[Object]) -> 'ObjectTree':
'''
Make a tree of the armature objects based on their hierarchy.
'''
tree = ObjectTree()
object_node_map: Dict[Object, ObjectNode] = {x: ObjectNode(x) for x in objects} object_node_map: Dict[Object, ObjectNode] = {x: ObjectNode(x) for x in objects}
for obj, object_node in object_node_map.items(): for obj, object_node in object_node_map.items():
if obj.parent in object_node_map: if obj.parent in object_node_map:
parent_node = object_node_map[obj.parent] parent_node = object_node_map[obj.parent]
object_node.parent = parent_node
parent_node.children.append(object_node) parent_node.children.append(object_node)
else: else:
tree.root_nodes.append(object_node) self.root_nodes.append(object_node)
return tree
def __iter__(self): def __iter__(self):
""" """
An depth-first iterator over the armature tree. An depth-first iterator over the armature tree.
""" """
node_stack = self.root_nodes node_stack = [] + self.root_nodes
while node_stack: while node_stack:
node = node_stack.pop() node = node_stack.pop()
yield node yield node
node_stack = node.children + node_stack node_stack = node.children + node_stack
def objects_dfs(self): def objects_iterator(self):
for node in self: for node in self:
yield node.object yield node.object
@@ -401,7 +407,7 @@ def create_psx_bones(
if bone_collection_indices is None: if bone_collection_indices is None:
bone_collection_indices = [] bone_collection_indices = []
armature_tree = ObjectTree.from_objects(armature_objects) armature_tree = ObjectTree(armature_objects)
# Check that there is only one root bone. If there are multiple armature objects, the export space must be WORLD. # Check that there is only one root bone. If there are multiple armature objects, the export space must be WORLD.
if len(armature_tree.root_nodes) >= 2 and export_space != 'WORLD': if len(armature_tree.root_nodes) >= 2 and export_space != 'WORLD':
@@ -655,7 +661,7 @@ def _get_psk_input_objects(mesh_dfs_objects: Iterable[DfsObject]) -> PskInputObj
# Get the armature objects used on all the meshes being exported. # Get the armature objects used on all the meshes being exported.
armature_objects = get_armatures_for_mesh_objects(map(lambda x: x.obj, mesh_dfs_objects)) armature_objects = get_armatures_for_mesh_objects(map(lambda x: x.obj, mesh_dfs_objects))
# Sort them in hierarchy order. # Sort them in hierarchy order.
input_objects.armature_objects = list(ObjectTree.from_objects(armature_objects).objects_dfs()) input_objects.armature_objects = list(ObjectTree(armature_objects).objects_iterator())
return input_objects return input_objects