* 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:
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user