Fixed an issue where ACTIVE_ACTION sequence source was not working

Also added a scene-level transform source.
This commit is contained in:
Colin Basnett
2025-05-18 00:46:50 -07:00
parent ff2fe35846
commit 00d9e3996c
16 changed files with 324 additions and 169 deletions

View File

@@ -1,8 +1,7 @@
import bpy
from collections import Counter
from typing import List, Iterable, Optional, Dict, Tuple, cast as typing_cast
from bpy.props import CollectionProperty
from bpy.types import Armature, AnimData, Object
from bpy.types import Armature, AnimData, Object, ArmatureModifier
from mathutils import Matrix, Vector, Quaternion as BpyQuaternion
from .data import Vector3, Quaternion
from ..shared.data import PsxBone
@@ -28,16 +27,27 @@ def get_nla_strips_in_frame_range(animation_data: AnimData, frame_min: float, fr
yield strip
def populate_bone_collection_list(armature_objects: Iterable[Object], bone_collection_list: CollectionProperty) -> None:
def populate_bone_collection_list(bone_collection_list, armature_objects: Iterable[Object], primary_key: str = 'OBJECT'):
"""
Updates the bone collections collection.
Updates the bone collection list.
Bone collection selections are preserved between updates unless none of the groups were previously selected;
otherwise, all collections are selected by default.
Selection is preserved between updates unless none of the groups were previously selected.
Otherwise, all collections are selected by default.
The primary key is used to determine how to group the armature objects. For example, if the primary key is
'DATA', then all bone collections with the same armature data-block will be under one entry.
:param bone_collection_list: The list to update.
:param armature_objects: The armature objects to populate the collection with.
:param primary_key: The primary key to use for the collection (one of 'OBJECT' or 'DATA').
:return: None
"""
has_selected_collections = any([g.is_selected for g in bone_collection_list])
unassigned_collection_is_selected, selected_assigned_collection_names = True, []
if primary_key not in ('OBJECT', 'DATA'):
assert False, f'Invalid primary key: {primary_key}'
if not armature_objects:
return
@@ -51,16 +61,26 @@ def populate_bone_collection_list(armature_objects: Iterable[Object], bone_colle
selected_assigned_collection_names = [
g.name for i, g in enumerate(bone_collection_list) if i != unassigned_collection_idx and g.is_selected]
bone_collection_list.clear()
unique_armature_data = set()
for armature_object in armature_objects:
armature = typing_cast(Armature, armature_object.data)
if armature is None:
return
continue
if primary_key == 'DATA' and armature_object.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_object.data)
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.index = -1
# Count the number of bones without an assigned bone collection
@@ -70,6 +90,7 @@ def populate_bone_collection_list(armature_objects: Iterable[Object], bone_colle
for bone_collection_index, bone_collection in enumerate(armature.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.name = bone_collection.name
item.index = bone_collection_index
item.count = len(bone_collection.bones)
@@ -286,6 +307,16 @@ def convert_bpy_quaternion_to_psx_quaternion(other: BpyQuaternion) -> Quaternion
return quaternion
class PsxBoneCollection:
"""
Stores the armature's object name, data-block name and bone collection index.
"""
def __init__(self, armature_object_name: str, armature_data_name: str, index: int):
self.armature_object_name = armature_object_name
self.armature_data_name = armature_data_name
self.index = index
def create_psx_bones(
armature_objects: List[Object],
export_space: str = 'WORLD',
@@ -294,7 +325,8 @@ def create_psx_bones(
up_axis: str = 'Z',
scale: float = 1.0,
bone_filter_mode: str = 'ALL',
bone_collection_indices: Optional[List[Tuple[str, int]]] = None,
bone_collection_indices: Optional[List[PsxBoneCollection]] = None,
bone_collection_primary_key: str = 'OBJECT',
) -> PsxBoneCreateResult:
"""
Creates a list of PSX bones from the given armature objects and options.
@@ -306,20 +338,27 @@ def create_psx_bones(
bones: List[Tuple[PsxBone, Optional[Object]]] = []
if export_space != 'WORLD' and len(armature_objects) > 1:
if export_space != 'WORLD' and len(armature_objects) >= 2:
armature_object_names = [armature_object.name for armature_object in armature_objects]
raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World. The following armatures are attempting to be exported: {armature_object_names}')
raise RuntimeError(f'When exporting multiple armatures, the Export Space must be World.\n' \
f'The following armatures are attempting to be exported: {armature_object_names}')
coordinate_system_matrix = get_coordinate_system_transform(forward_axis, up_axis)
coordinate_system_default_rotation = coordinate_system_matrix.to_quaternion()
total_bone_count = sum(len(armature_object.data.bones) for armature_object in armature_objects)
# Store the bone names to be exported for each armature object.
armature_object_bone_names: Dict[Object, List[str]] = dict()
for armature_object in armature_objects:
armature_bone_collection_indices = [x[1] for x in bone_collection_indices if x[0] == armature_object.name]
armature_bone_collection_indices: List[int] = []
match bone_collection_primary_key:
case 'OBJECT':
armature_bone_collection_indices.extend([x.index for x in bone_collection_indices if x.armature_object_name == armature_object.name])
case 'DATA':
armature_bone_collection_indices.extend([x.index for x in bone_collection_indices if armature_object.data and x.armature_data_name == armature_object.data.name])
case _:
assert False, f'Invalid primary key: {bone_collection_primary_key}'
bone_names = get_export_bone_names(armature_object, bone_filter_mode, armature_bone_collection_indices)
armature_object_bone_names[armature_object] = bone_names
@@ -429,3 +468,16 @@ def get_coordinate_system_transform(forward_axis: str = 'X', up_axis: str = 'Z')
(up.x, up.y, up.z, 0.0),
(0.0, 0.0, 0.0, 1.0)
))
def get_armatures_for_mesh_objects(mesh_objects: Iterable[Object]):
"""
Returns a generator of unique armature objects that are used by the given mesh objects.
"""
armature_objects: set[Object] = set()
for mesh_object in mesh_objects:
armature_modifiers = [typing_cast(ArmatureModifier, x) for x in mesh_object.modifiers if x.type == 'ARMATURE']
for armature_object in map(lambda x: x.object, armature_modifiers):
if armature_object is not None:
armature_objects.add(armature_object)
yield from armature_objects