* Unified how bones are filtered for export based on export settings (bone filter mode + bone groups)

* Bone Group filtering now works properly for PSK export
* Fixed a number of bugs that broke animation export
This commit is contained in:
Colin Basnett
2022-02-12 01:45:19 -08:00
parent 7ad8f0238a
commit 5e7c2535e2
5 changed files with 123 additions and 63 deletions

View File

@@ -44,55 +44,45 @@ class PsaBuilder(object):
# armature bones.
bone_names = [x.name for x in bones]
pose_bones = [(bone_names.index(bone.name), bone) for bone in armature.pose.bones]
del bone_names
pose_bones.sort(key=lambda x: x[0])
pose_bones = [x[1] for x in pose_bones]
bone_indices = list(range(len(bones)))
# If bone groups are specified, get only the bones that are in that specified bone groups and their ancestors.
if options.bone_filter_mode == 'BONE_GROUPS':
bone_indices = get_export_bone_indices_for_bone_groups(armature, options.bone_group_indices)
# Get a list of all the bone indices and instigator bones for the bone filter settings.
bone_names = get_export_bone_names(armature, options.bone_filter_mode, options.bone_group_indices)
bone_indices = [bone_names.index(x) for x in bone_names]
# Make the bone lists contain only the bones that are going to be exported.
bones = [bones[bone_index] for bone_index in bone_indices]
pose_bones = [pose_bones[bone_index] for bone_index in bone_indices]
# No bones are going to be exported.
if len(bones) == 0:
# No bones are going to be exported.
raise RuntimeError('No bones available for export')
# Ensure that the exported hierarchy has a single root bone.
root_bones = [x for x in bones if x.parent is None]
if len(root_bones) > 1:
root_bone_names = [x.name for x in root_bones]
raise RuntimeError('Exported bone hierarchy must have a single root bone.'
f'The bone hierarchy marked for export has {len(root_bones)} root bones: {root_bone_names}')
# Build list of PSA bones.
for pose_bone in bones:
for bone in bones:
psa_bone = Psa.Bone()
psa_bone.name = bytes(pose_bone.name, encoding='utf-8')
psa_bone.name = bytes(bone.name, encoding='utf-8')
try:
parent_index = bones.index(pose_bone.parent)
parent_index = bones.index(bone.parent)
psa_bone.parent_index = parent_index
psa.bones[parent_index].children_count += 1
except ValueError:
psa_bone.parent_index = -1
if pose_bone.parent is not None:
rotation = pose_bone.matrix.to_quaternion()
if bone.parent is not None:
rotation = bone.matrix.to_quaternion()
rotation.x = -rotation.x
rotation.y = -rotation.y
rotation.z = -rotation.z
quat_parent = pose_bone.parent.matrix.to_quaternion().inverted()
parent_head = quat_parent @ pose_bone.parent.head
parent_tail = quat_parent @ pose_bone.parent.tail
location = (parent_tail - parent_head) + pose_bone.head
quat_parent = bone.parent.matrix.to_quaternion().inverted()
parent_head = quat_parent @ bone.parent.head
parent_tail = quat_parent @ bone.parent.tail
location = (parent_tail - parent_head) + bone.head
else:
location = armature.matrix_local @ pose_bone.head
rot_matrix = pose_bone.matrix @ armature.matrix_local.to_3x3()
location = armature.matrix_local @ bone.head
rot_matrix = bone.matrix @ armature.matrix_local.to_3x3()
rotation = rot_matrix.to_quaternion()
psa_bone.location.x = location.x
@@ -195,8 +185,8 @@ class PsaBuilder(object):
psa.keys.append(key)
export_sequence.bone_count = len(pose_bones)
export_sequence.track_time = frame_count
psa_sequence.bone_count = len(pose_bones)
psa_sequence.track_time = frame_count
# Restore the mute state of the NLA strips we muted beforehand.
for nla_strip, mute in nla_strip_mute_statuses.items():

View File

@@ -129,8 +129,8 @@ class PsaExportOperator(Operator, ExportHelper):
layout.label(text='Actions', icon='ACTION')
row = layout.row(align=True)
row.label(text='Select')
row.operator(PsaExportSelectAll.bl_idname, text='All')
row.operator(PsaExportDeselectAll.bl_idname, text='None')
row.operator(PsaExportActionsSelectAll.bl_idname, text='All')
row.operator(PsaExportActionsDeselectAll.bl_idname, text='None')
row = layout.row()
rows = max(3, min(len(pg.action_list), 10))
row.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
@@ -172,6 +172,10 @@ class PsaExportOperator(Operator, ExportHelper):
if pg.bone_filter_mode == 'BONE_GROUPS':
rows = max(3, min(len(pg.bone_group_list), 10))
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows)
row = layout.row(align=True)
row.label(text='Select')
row.operator(PsaExportBoneGroupsSelectAll.bl_idname, text='All')
row.operator(PsaExportBoneGroupsDeselectAll.bl_idname, text='None')
def is_action_for_armature(self, action):
if len(action.fcurves) == 0:
@@ -297,7 +301,7 @@ class PSA_UL_ExportActionList(UIList):
return flt_flags, flt_neworder
class PsaExportSelectAll(Operator):
class PsaExportActionsSelectAll(Operator):
bl_idname = 'psa_export.actions_select_all'
bl_label = 'Select All'
bl_description = 'Select all actions'
@@ -307,17 +311,17 @@ class PsaExportSelectAll(Operator):
def poll(cls, context):
pg = context.scene.psa_export
item_list = pg.action_list
has_unselected_actions = any(map(lambda action: not action.is_selected, item_list))
return len(item_list) > 0 and has_unselected_actions
has_unselected_items = any(map(lambda item: not item.is_selected, item_list))
return len(item_list) > 0 and has_unselected_items
def execute(self, context):
pg = context.scene.psa_export
for action in pg.action_list:
action.is_selected = True
for item in pg.action_list:
item.is_selected = True
return {'FINISHED'}
class PsaExportDeselectAll(Operator):
class PsaExportActionsDeselectAll(Operator):
bl_idname = 'psa_export.actions_deselect_all'
bl_label = 'Deselect All'
bl_description = 'Deselect all actions'
@@ -327,12 +331,52 @@ class PsaExportDeselectAll(Operator):
def poll(cls, context):
pg = context.scene.psa_export
item_list = pg.action_list
has_selected_items = any(map(lambda item: item.is_selected, item_list))
return len(item_list) > 0 and has_selected_items
def execute(self, context):
pg = context.scene.psa_export
for item in pg.action_list:
item.is_selected = False
return {'FINISHED'}
class PsaExportBoneGroupsSelectAll(Operator):
bl_idname = 'psa_export.bone_groups_select_all'
bl_label = 'Select All'
bl_description = 'Select all bone groups'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_export
item_list = pg.bone_group_list
has_unselected_items = any(map(lambda action: not action.is_selected, item_list))
return len(item_list) > 0 and has_unselected_items
def execute(self, context):
pg = context.scene.psa_export
for item in pg.bone_group_list:
item.is_selected = True
return {'FINISHED'}
class PsaExportBoneGroupsDeselectAll(Operator):
bl_idname = 'psa_export.bone_groups_deselect_all'
bl_label = 'Deselect All'
bl_description = 'Deselect all bone groups'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_export
item_list = pg.bone_group_list
has_selected_actions = any(map(lambda action: action.is_selected, item_list))
return len(item_list) > 0 and has_selected_actions
def execute(self, context):
pg = context.scene.psa_export
for action in pg.action_list:
for action in pg.bone_group_list:
action.is_selected = False
return {'FINISHED'}
@@ -344,6 +388,8 @@ classes = (
PsaExportOperator,
PSA_UL_ExportActionList,
PSA_UL_ExportTimelineMarkerList,
PsaExportSelectAll,
PsaExportDeselectAll,
PsaExportActionsSelectAll,
PsaExportActionsDeselectAll,
PsaExportBoneGroupsSelectAll,
PsaExportBoneGroupsDeselectAll,
)