Fixed a load of PEP8 warnings

This commit is contained in:
Colin Basnett
2022-08-06 23:52:18 -07:00
parent 96001651c6
commit cd490af431
9 changed files with 181 additions and 163 deletions

View File

@@ -1,10 +1,10 @@
import datetime import datetime
from collections import Counter
import re import re
from collections import Counter
from typing import List, Iterable from typing import List, Iterable
import bpy.types
from bpy.types import NlaStrip, Object from bpy.types import NlaStrip, Object
from .types import BoneGroupListItem
class Timer: class Timer:
@@ -47,7 +47,7 @@ def get_nla_strips_in_timeframe(animation_data, frame_min, frame_max) -> List[Nl
return strips return strips
def populate_bone_group_list(armature_object: Object, bone_group_list: Iterable[BoneGroupListItem]) -> None: def populate_bone_group_list(armature_object: Object, bone_group_list: bpy.types.Collection) -> None:
""" """
Updates the bone group collection. Updates the bone group collection.

View File

@@ -1,4 +1,4 @@
from typing import Dict, Iterable from typing import Dict
from bpy.types import Action from bpy.types import Action
@@ -217,7 +217,8 @@ def build_psa(context, options: PsaBuildOptions) -> Psa:
# Add prefixes and suffices to the names of the export sequences and strip whitespace. # Add prefixes and suffices to the names of the export sequences and strip whitespace.
for export_sequence in export_sequences: for export_sequence in export_sequences:
export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'.strip() export_sequence.name = f'{options.sequence_name_prefix}{export_sequence.name}{options.sequence_name_suffix}'
export_sequence.name = export_sequence.name.strip()
# Now build the PSA sequences. # Now build the PSA sequences.
# We actually alter the timeline frame and simply record the resultant pose bone matrices. # We actually alter the timeline frame and simply record the resultant pose bone matrices.

View File

@@ -1,7 +1,5 @@
import fnmatch import fnmatch
import re
import sys import sys
from collections import Counter
from typing import Type from typing import Type
import bpy import bpy
@@ -16,7 +14,6 @@ from ..helpers import *
from ..types import BoneGroupListItem from ..types import BoneGroupListItem
def export_psa(psa: Psa, path: str):
def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None): def write_section(fp, name: bytes, data_type: Type[Structure] = None, data: list = None):
section = Section() section = Section()
section.name = name section.name = name
@@ -27,6 +24,9 @@ def export_psa(psa: Psa, path: str):
if data is not None: if data is not None:
for datum in data: for datum in data:
fp.write(datum) fp.write(datum)
def export_psa(psa: Psa, path: str):
with open(path, 'wb') as fp: with open(path, 'wb') as fp:
write_section(fp, b'ANIMHEAD') write_section(fp, b'ANIMHEAD')
write_section(fp, b'BONENAMES', Psa.Bone, psa.bones) write_section(fp, b'BONENAMES', Psa.Bone, psa.bones)
@@ -61,16 +61,19 @@ def psa_export_property_group_animation_data_override_poll(_context, obj):
return obj.animation_data is not None return obj.animation_data is not None
empty_set = set()
class PsaExportPropertyGroup(PropertyGroup): class PsaExportPropertyGroup(PropertyGroup):
root_motion: BoolProperty( root_motion: BoolProperty(
name='Root Motion', name='Root Motion',
options=set(), options=empty_set,
default=False, default=False,
description='The root bone will be transformed as it appears in the scene', description='The root bone will be transformed as it appears in the scene',
) )
should_override_animation_data: BoolProperty( should_override_animation_data: BoolProperty(
name='Override Animation Data', name='Override Animation Data',
options=set(), options=empty_set,
default=False, default=False,
description='Use the animation data from a different object instead of the selected object' description='Use the animation data from a different object instead of the selected object'
) )
@@ -80,7 +83,7 @@ class PsaExportPropertyGroup(PropertyGroup):
) )
sequence_source: EnumProperty( sequence_source: EnumProperty(
name='Source', name='Source',
options=set(), options=empty_set,
description='', description='',
items=( items=(
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0), ('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
@@ -90,7 +93,7 @@ class PsaExportPropertyGroup(PropertyGroup):
) )
fps_source: EnumProperty( fps_source: EnumProperty(
name='FPS Source', name='FPS Source',
options=set(), options=empty_set,
description='', description='',
items=( items=(
('SCENE', 'Scene', '', 'SCENE_DATA', 0), ('SCENE', 'Scene', '', 'SCENE_DATA', 0),
@@ -100,7 +103,7 @@ class PsaExportPropertyGroup(PropertyGroup):
('CUSTOM', 'Custom', '', 2) ('CUSTOM', 'Custom', '', 2)
) )
) )
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100, fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=empty_set, step=100,
soft_max=60.0) soft_max=60.0)
action_list: CollectionProperty(type=PsaExportActionListItem) action_list: CollectionProperty(type=PsaExportActionListItem)
action_list_index: IntProperty(default=0) action_list_index: IntProperty(default=0)
@@ -108,7 +111,7 @@ class PsaExportPropertyGroup(PropertyGroup):
marker_list_index: IntProperty(default=0) marker_list_index: IntProperty(default=0)
bone_filter_mode: EnumProperty( bone_filter_mode: EnumProperty(
name='Bone Filter', name='Bone Filter',
options=set(), options=empty_set,
description='', description='',
items=( items=(
('ALL', 'All', 'All bones will be exported.'), ('ALL', 'All', 'All bones will be exported.'),
@@ -121,7 +124,7 @@ class PsaExportPropertyGroup(PropertyGroup):
should_use_original_sequence_names: BoolProperty( should_use_original_sequence_names: BoolProperty(
default=False, default=False,
name='Original Names', name='Original Names',
options=set(), options=empty_set,
update=should_use_original_sequence_names_updated, update=should_use_original_sequence_names_updated,
description='If the action was imported from the PSA Import panel, the original name of the sequence will be ' description='If the action was imported from the PSA Import panel, the original name of the sequence will be '
'used instead of the Blender action name', 'used instead of the Blender action name',
@@ -129,12 +132,12 @@ class PsaExportPropertyGroup(PropertyGroup):
should_trim_timeline_marker_sequences: BoolProperty( should_trim_timeline_marker_sequences: BoolProperty(
default=True, default=True,
name='Trim Sequences', name='Trim Sequences',
options=set(), options=empty_set,
description='Frames without NLA track information at the boundaries of timeline markers will be excluded from ' description='Frames without NLA track information at the boundaries of timeline markers will be excluded from '
'the exported sequences ' 'the exported sequences '
) )
sequence_name_prefix: StringProperty(name='Prefix', options=set()) sequence_name_prefix: StringProperty(name='Prefix', options=empty_set)
sequence_name_suffix: StringProperty(name='Suffix', options=set()) sequence_name_suffix: StringProperty(name='Suffix', options=empty_set)
sequence_filter_name: StringProperty( sequence_filter_name: StringProperty(
default='', default='',
name='Filter by Name', name='Filter by Name',
@@ -143,14 +146,14 @@ class PsaExportPropertyGroup(PropertyGroup):
sequence_use_filter_invert: BoolProperty( sequence_use_filter_invert: BoolProperty(
default=False, default=False,
name='Invert', name='Invert',
options=set(), options=empty_set,
description='Invert filtering (show hidden items, and vice versa)') description='Invert filtering (show hidden items, and vice versa)')
sequence_filter_asset: BoolProperty( sequence_filter_asset: BoolProperty(
default=False, default=False,
name='Show assets', name='Show assets',
options=set(), options=empty_set,
description='Show actions that belong to an asset library') description='Show actions that belong to an asset library')
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set()) sequence_use_filter_sort_reverse: BoolProperty(default=True, options=empty_set)
def is_bone_filter_mode_item_available(context, identifier): def is_bone_filter_mode_item_available(context, identifier):
@@ -182,13 +185,13 @@ class PsaExportOperator(Operator, ExportHelper):
try: try:
cls._check_context(context) cls._check_context(context)
except RuntimeError as e: except RuntimeError as e:
cls.poll_message_set((str(e))) cls.poll_message_set(str(e))
return False return False
return True return True
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
# FPS # FPS
layout.prop(pg, 'fps_source', text='FPS') layout.prop(pg, 'fps_source', text='FPS')
@@ -291,7 +294,7 @@ class PsaExportOperator(Operator, ExportHelper):
except RuntimeError as e: except RuntimeError as e:
self.report({'ERROR_INVALID_CONTEXT'}, str(e)) self.report({'ERROR_INVALID_CONTEXT'}, str(e))
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
self.armature = context.view_layer.objects.active self.armature = context.view_layer.objects.active
# Populate actions list. # Populate actions list.
@@ -326,7 +329,7 @@ class PsaExportOperator(Operator, ExportHelper):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def execute(self, context): def execute(self, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
actions = [x.action for x in pg.action_list if x.is_selected] actions = [x.action for x in pg.action_list if x.is_selected]
marker_names = [x.name for x in pg.marker_list if x.is_selected] marker_names = [x.name for x in pg.marker_list if x.is_selected]
@@ -349,6 +352,7 @@ class PsaExportOperator(Operator, ExportHelper):
try: try:
psa = build_psa(context, options) psa = build_psa(context, options)
self.report({'INFO'}, f'PSA export successful')
except RuntimeError as e: except RuntimeError as e:
self.report({'ERROR_INVALID_CONTEXT'}, str(e)) self.report({'ERROR_INVALID_CONTEXT'}, str(e))
return {'CANCELLED'} return {'CANCELLED'}
@@ -358,7 +362,7 @@ class PsaExportOperator(Operator, ExportHelper):
return {'FINISHED'} return {'FINISHED'}
def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]: def filter_sequences(pg: PsaExportPropertyGroup, sequences) -> List[int]:
bitflag_filter_item = 1 << 30 bitflag_filter_item = 1 << 30
flt_flags = [bitflag_filter_item] * len(sequences) flt_flags = [bitflag_filter_item] * len(sequences)
@@ -381,7 +385,7 @@ def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_c
return flt_flags return flt_flags
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]: def get_visible_sequences(pg: PsaExportPropertyGroup, sequences) -> List[PsaExportActionListItem]:
visible_sequences = [] visible_sequences = []
for i, flag in enumerate(filter_sequences(pg, sequences)): for i, flag in enumerate(filter_sequences(pg, sequences)):
if bool(flag & (1 << 30)): if bool(flag & (1 << 30)):
@@ -402,7 +406,7 @@ class PSA_UL_ExportSequenceList(UIList):
layout.label(text='', icon='ASSET_MANAGER') layout.label(text='', icon='ASSET_MANAGER')
def draw_filter(self, context, layout): def draw_filter(self, context, layout):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
row = layout.row() row = layout.row()
subrow = row.row(align=True) subrow = row.row(align=True)
subrow.prop(pg, 'sequence_filter_name', text="") subrow.prop(pg, 'sequence_filter_name', text="")
@@ -414,7 +418,7 @@ class PSA_UL_ExportSequenceList(UIList):
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER') subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
def filter_items(self, context, data, prop): def filter_items(self, context, data, prop):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
actions = getattr(data, prop) actions = getattr(data, prop)
flt_flags = filter_sequences(pg, actions) flt_flags = filter_sequences(pg, actions)
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name') flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
@@ -438,14 +442,14 @@ class PsaExportActionsSelectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
item_list = cls.get_item_list(context) item_list = cls.get_item_list(context)
visible_sequences = get_visible_sequences(pg, item_list) visible_sequences = get_visible_sequences(pg, item_list)
has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences)) has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences))
return has_unselected_sequences return has_unselected_sequences
def execute(self, context): def execute(self, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
sequences = self.get_item_list(context) sequences = self.get_item_list(context)
for sequence in get_visible_sequences(pg, sequences): for sequence in get_visible_sequences(pg, sequences):
sequence.is_selected = True sequence.is_selected = True
@@ -474,7 +478,7 @@ class PsaExportActionsDeselectAll(Operator):
return len(item_list) > 0 and has_selected_items return len(item_list) > 0 and has_selected_items
def execute(self, context): def execute(self, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
item_list = self.get_item_list(context) item_list = self.get_item_list(context)
for sequence in get_visible_sequences(pg, item_list): for sequence in get_visible_sequences(pg, item_list):
sequence.is_selected = False sequence.is_selected = False
@@ -489,13 +493,13 @@ class PsaExportBoneGroupsSelectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
item_list = pg.bone_group_list item_list = pg.bone_group_list
has_unselected_items = any(map(lambda action: not action.is_selected, item_list)) has_unselected_items = any(map(lambda action: not action.is_selected, item_list))
return len(item_list) > 0 and has_unselected_items return len(item_list) > 0 and has_unselected_items
def execute(self, context): def execute(self, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
for item in pg.bone_group_list: for item in pg.bone_group_list:
item.is_selected = True item.is_selected = True
return {'FINISHED'} return {'FINISHED'}
@@ -509,13 +513,13 @@ class PsaExportBoneGroupsDeselectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
item_list = pg.bone_group_list item_list = pg.bone_group_list
has_selected_actions = any(map(lambda action: action.is_selected, item_list)) has_selected_actions = any(map(lambda action: action.is_selected, item_list))
return len(item_list) > 0 and has_selected_actions return len(item_list) > 0 and has_selected_actions
def execute(self, context): def execute(self, context):
pg = context.scene.psa_export pg = getattr(context.scene, 'psa_export')
for action in pg.bone_group_list: for action in pg.bone_group_list:
action.is_selected = False action.is_selected = False
return {'FINISHED'} return {'FINISHED'}

View File

@@ -26,10 +26,6 @@ class PsaImportOptions(object):
self.action_name_prefix = '' self.action_name_prefix = ''
def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions):
sequences = map(lambda x: psa_reader.sequences[x], options.sequence_names)
armature_data = armature_object.data
class ImportBone(object): class ImportBone(object):
def __init__(self, psa_bone: Psa.Bone): def __init__(self, psa_bone: Psa.Bone):
self.psa_bone: Psa.Bone = psa_bone self.psa_bone: Psa.Bone = psa_bone
@@ -41,6 +37,7 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
self.post_quat: Quaternion = Quaternion() self.post_quat: Quaternion = Quaternion()
self.fcurves = [] self.fcurves = []
def calculate_fcurve_data(import_bone: ImportBone, key_data: []): def calculate_fcurve_data(import_bone: ImportBone, key_data: []):
# Convert world-space transforms to local-space transforms. # Convert world-space transforms to local-space transforms.
key_rotation = Quaternion(key_data[0:4]) key_rotation = Quaternion(key_data[0:4])
@@ -58,6 +55,11 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
loc.rotate(import_bone.post_quat.conjugated()) loc.rotate(import_bone.post_quat.conjugated())
return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z return quat.w, quat.x, quat.y, quat.z, loc.x, loc.y, loc.z
def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions):
sequences = map(lambda x: psa_reader.sequences[x], options.sequence_names)
armature_data = armature_object.data
# Create an index mapping from bones in the PSA to bones in the target armature. # Create an index mapping from bones in the PSA to bones in the target armature.
psa_to_armature_bone_indices = {} psa_to_armature_bone_indices = {}
armature_bone_names = [x.name for x in armature_data.bones] armature_bone_names = [x.name for x in armature_data.bones]
@@ -176,7 +178,8 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index] fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
last_written_datum = 0 last_written_datum = 0
for frame_index, datum in enumerate(fcurve_frame_data): for frame_index, datum in enumerate(fcurve_frame_data):
# If the f-curve data is not different enough to the last written frame, un-mark this data for writing. # If the f-curve data is not different enough to the last written frame,
# un-mark this data for writing.
if frame_index > 0 and abs(datum - last_written_datum) < threshold: if frame_index > 0 and abs(datum - last_written_datum) < threshold:
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0 keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
else: else:
@@ -217,9 +220,12 @@ def import_psa(psa_reader: PsaReader, armature_object, options: PsaImportOptions
nla_track.strips.new(name=action.name, start=0, action=action) nla_track.strips.new(name=action.name, start=0, action=action)
empty_set = set()
class PsaImportActionListItem(PropertyGroup): class PsaImportActionListItem(PropertyGroup):
action_name: StringProperty(options=set()) action_name: StringProperty(options=empty_set)
is_selected: BoolProperty(default=False, options=set()) is_selected: BoolProperty(default=False, options=empty_set)
def load_psa_file(context): def load_psa_file(context):
@@ -246,7 +252,7 @@ def on_psa_file_path_updated(property, context):
class PsaBonePropertyGroup(PropertyGroup): class PsaBonePropertyGroup(PropertyGroup):
bone_name: StringProperty(options=set()) bone_name: StringProperty(options=empty_set)
class PsaDataPropertyGroup(PropertyGroup): class PsaDataPropertyGroup(PropertyGroup):
@@ -255,37 +261,37 @@ class PsaDataPropertyGroup(PropertyGroup):
class PsaImportPropertyGroup(PropertyGroup): class PsaImportPropertyGroup(PropertyGroup):
psa_file_path: StringProperty(default='', options=set(), update=on_psa_file_path_updated, name='PSA File Path') psa_file_path: StringProperty(default='', options=empty_set, update=on_psa_file_path_updated, name='PSA File Path')
psa_error: StringProperty(default='') psa_error: StringProperty(default='')
psa: PointerProperty(type=PsaDataPropertyGroup) psa: PointerProperty(type=PsaDataPropertyGroup)
sequence_list: CollectionProperty(type=PsaImportActionListItem) sequence_list: CollectionProperty(type=PsaImportActionListItem)
sequence_list_index: IntProperty(name='', default=0) sequence_list_index: IntProperty(name='', default=0)
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', should_clean_keys: BoolProperty(default=True, name='Clean Keyframes',
description='Exclude unnecessary keyframes from being written to the actions', description='Exclude unnecessary keyframes from being written to the actions',
options=set()) options=empty_set)
should_use_fake_user: BoolProperty(default=True, name='Fake User', should_use_fake_user: BoolProperty(default=True, name='Fake User',
description='Assign each imported action a fake user so that the data block is saved even it has no users', description='Assign each imported action a fake user so that the data block is saved even it has no users',
options=set()) options=empty_set)
should_stash: BoolProperty(default=False, name='Stash', should_stash: BoolProperty(default=False, name='Stash',
description='Stash each imported action as a strip on a new non-contributing NLA track', description='Stash each imported action as a strip on a new non-contributing NLA track',
options=set()) options=empty_set)
should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=set()) should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=empty_set)
action_name_prefix: StringProperty(default='', name='Prefix', options=set()) action_name_prefix: StringProperty(default='', name='Prefix', options=empty_set)
should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=set(), should_overwrite: BoolProperty(default=False, name='Reuse Existing Actions', options=empty_set,
description='If an action with a matching name already exists, the existing action will have it\'s data overwritten instead of a new action being created') description='If an action with a matching name already exists, the existing action will have it\'s data overwritten instead of a new action being created')
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set()) should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=empty_set)
should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(), should_write_metadata: BoolProperty(default=True, name='Metadata', options=empty_set,
description='Additional data will be written to the custom properties of the Action (e.g., frame rate)') description='Additional data will be written to the custom properties of the Action (e.g., frame rate)')
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'}) sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
sequence_filter_is_selected: BoolProperty(default=False, options=set(), name='Only Show Selected', sequence_filter_is_selected: BoolProperty(default=False, options=empty_set, name='Only Show Selected',
description='Only show selected sequences') description='Only show selected sequences')
sequence_use_filter_invert: BoolProperty(default=False, options=set()) sequence_use_filter_invert: BoolProperty(default=False, options=empty_set)
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression', sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression',
description='Filter using regular expressions', options=set()) description='Filter using regular expressions', options=empty_set)
select_text: PointerProperty(type=bpy.types.Text) select_text: PointerProperty(type=bpy.types.Text)
def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]: def filter_sequences(pg: PsaImportPropertyGroup, sequences) -> List[int]:
bitflag_filter_item = 1 << 30 bitflag_filter_item = 1 << 30
flt_flags = [bitflag_filter_item] * len(sequences) flt_flags = [bitflag_filter_item] * len(sequences)
@@ -319,8 +325,7 @@ def filter_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_c
return flt_flags return flt_flags
def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[ def get_visible_sequences(pg: PsaImportPropertyGroup, sequences) -> List[PsaImportActionListItem]:
PsaImportActionListItem]:
bitflag_filter_item = 1 << 30 bitflag_filter_item = 1 << 30
visible_sequences = [] visible_sequences = []
for i, flag in enumerate(filter_sequences(pg, sequences)): for i, flag in enumerate(filter_sequences(pg, sequences)):
@@ -330,26 +335,25 @@ def get_visible_sequences(pg: PsaImportPropertyGroup, sequences: bpy.types.bpy_p
class PSA_UL_SequenceList(UIList): class PSA_UL_SequenceList(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index, flt_flag):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.row(align=True) row = layout.row(align=True)
split = row.split(align=True, factor=0.75) split = row.split(align=True, factor=0.75)
column = split.row(align=True) column = split.row(align=True)
column.alignment = 'LEFT' column.alignment = 'LEFT'
column.prop(item, 'is_selected', icon_only=True) column.prop(item, 'is_selected', icon_only=True)
column.label(text=item.action_name) column.label(text=getattr(item, 'action_name'))
def draw_filter(self, context, layout): def draw_filter(self, context, layout):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
row = layout.row() row = layout.row()
subrow = row.row(align=True) sub_row = row.row(align=True)
subrow.prop(pg, 'sequence_filter_name', text="") sub_row.prop(pg, 'sequence_filter_name', text="")
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT') sub_row.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
subrow.prop(pg, 'sequence_use_filter_regex', text="", icon='SORTBYEXT') sub_row.prop(pg, 'sequence_use_filter_regex', text="", icon='SORTBYEXT')
subrow.prop(pg, 'sequence_filter_is_selected', text="", icon='CHECKBOX_HLT') sub_row.prop(pg, 'sequence_filter_is_selected', text="", icon='CHECKBOX_HLT')
def filter_items(self, context, data, property): def filter_items(self, context, data, property):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
sequences = getattr(data, property) sequences = getattr(data, property)
flt_flags = filter_sequences(pg, sequences) flt_flags = filter_sequences(pg, sequences)
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'action_name') flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'action_name')
@@ -372,7 +376,7 @@ class PsaImportSequencesFromText(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
return len(pg.sequence_list) > 0 return len(pg.sequence_list) > 0
def invoke(self, context, event): def invoke(self, context, event):
@@ -380,12 +384,12 @@ class PsaImportSequencesFromText(Operator):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
layout.label(icon='INFO', text='Each sequence name should be on a new line.') layout.label(icon='INFO', text='Each sequence name should be on a new line.')
layout.prop(pg, 'select_text', text='') layout.prop(pg, 'select_text', text='')
def execute(self, context): def execute(self, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
if pg.select_text is None: if pg.select_text is None:
self.report({'ERROR_INVALID_CONTEXT'}, 'No text block selected') self.report({'ERROR_INVALID_CONTEXT'}, 'No text block selected')
return {'CANCELLED'} return {'CANCELLED'}
@@ -408,13 +412,13 @@ class PsaImportSequencesSelectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
visible_sequences = get_visible_sequences(pg, pg.sequence_list) visible_sequences = get_visible_sequences(pg, pg.sequence_list)
has_unselected_actions = any(map(lambda action: not action.is_selected, visible_sequences)) has_unselected_actions = any(map(lambda action: not action.is_selected, visible_sequences))
return len(visible_sequences) > 0 and has_unselected_actions return len(visible_sequences) > 0 and has_unselected_actions
def execute(self, context): def execute(self, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
visible_sequences = get_visible_sequences(pg, pg.sequence_list) visible_sequences = get_visible_sequences(pg, pg.sequence_list)
for sequence in visible_sequences: for sequence in visible_sequences:
sequence.is_selected = True sequence.is_selected = True
@@ -429,13 +433,13 @@ class PsaImportSequencesDeselectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
visible_sequences = get_visible_sequences(pg, pg.sequence_list) visible_sequences = get_visible_sequences(pg, pg.sequence_list)
has_selected_sequences = any(map(lambda sequence: sequence.is_selected, visible_sequences)) has_selected_sequences = any(map(lambda sequence: sequence.is_selected, visible_sequences))
return len(visible_sequences) > 0 and has_selected_sequences return len(visible_sequences) > 0 and has_selected_sequences
def execute(self, context): def execute(self, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
visible_sequences = get_visible_sequences(pg, pg.sequence_list) visible_sequences = get_visible_sequences(pg, pg.sequence_list)
for sequence in visible_sequences: for sequence in visible_sequences:
sequence.is_selected = False sequence.is_selected = False
@@ -451,7 +455,7 @@ class PSA_PT_ImportPanel_Advanced(Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
col = layout.column(heading="Options") col = layout.column(heading="Options")
col.use_property_split = True col.use_property_split = True
@@ -476,11 +480,11 @@ class PSA_PT_ImportPanel(Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.object.type == 'ARMATURE' return context.view_layer.objects.active.type == 'ARMATURE'
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
row = layout.row(align=True) row = layout.row(align=True)
row.operator(PsaImportSelectFile.bl_idname, text='', icon='FILEBROWSER') row.operator(PsaImportSelectFile.bl_idname, text='', icon='FILEBROWSER')
@@ -552,7 +556,7 @@ class PsaImportSelectFile(Operator):
filter_glob: bpy.props.StringProperty(default="*.psa", options={'HIDDEN'}) filter_glob: bpy.props.StringProperty(default="*.psa", options={'HIDDEN'})
def execute(self, context): def execute(self, context):
context.scene.psa_import.psa_file_path = self.filepath getattr(context.scene, 'psa_import').psa_file_path = self.filepath
return {"FINISHED"} return {"FINISHED"}
def invoke(self, context, event): def invoke(self, context, event):
@@ -568,14 +572,14 @@ class PsaImportOperator(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
active_object = context.view_layer.objects.active active_object = context.view_layer.objects.active
if active_object is None or active_object.type != 'ARMATURE': if active_object is None or active_object.type != 'ARMATURE':
return False return False
return any(map(lambda x: x.is_selected, pg.sequence_list)) return any(map(lambda x: x.is_selected, pg.sequence_list))
def execute(self, context): def execute(self, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
psa_reader = PsaReader(pg.psa_file_path) psa_reader = PsaReader(pg.psa_file_path)
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected] sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
@@ -613,7 +617,7 @@ class PsaImportFileSelectOperator(Operator, ImportHelper):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
def execute(self, context): def execute(self, context):
pg = context.scene.psa_import pg = getattr(context.scene, 'psa_import')
pg.psa_file_path = self.filepath pg.psa_file_path = self.filepath
return {'FINISHED'} return {'FINISHED'}

View File

@@ -8,8 +8,8 @@ from .data import *
class PsaReader(object): class PsaReader(object):
""" """
This class reads the sequences and bone information immediately upon instantiation and holds onto a file handle. This class reads the sequences and bone information immediately upon instantiation and holds onto a file handle.
The keyframe data is not read into memory upon instantiation due to it's potentially very large size. The keyframe data is not read into memory upon instantiation due to its potentially very large size.
To read the key data for a particular sequence, call `read_sequence_keys`. To read the key data for a particular sequence, call :read_sequence_keys.
""" """
def __init__(self, path): def __init__(self, path):
@@ -38,7 +38,8 @@ class PsaReader(object):
return matrix return matrix
def read_sequence_keys(self, sequence_name: str) -> List[Psa.Key]: def read_sequence_keys(self, sequence_name: str) -> List[Psa.Key]:
""" Reads and returns the key data for a sequence. """
Reads and returns the key data for a sequence.
:param sequence_name: The name of the sequence. :param sequence_name: The name of the sequence.
:return: A list of Psa.Keys. :return: A list of Psa.Keys.
@@ -60,7 +61,7 @@ class PsaReader(object):
return keys return keys
@staticmethod @staticmethod
def _read_types(fp, data_class: ctypes.Structure, section: Section, data): def _read_types(fp, data_class, section: Section, data):
buffer_length = section.data_size * section.data_count buffer_length = section.data_size * section.data_count
buffer = fp.read(buffer_length) buffer = fp.read(buffer_length)
offset = 0 offset = 0
@@ -86,7 +87,7 @@ class PsaReader(object):
# Skip keys on this pass. We will keep this file open and read from it as needed. # Skip keys on this pass. We will keep this file open and read from it as needed.
self.keys_data_offset = fp.tell() self.keys_data_offset = fp.tell()
fp.seek(section.data_size * section.data_count, 1) fp.seek(section.data_size * section.data_count, 1)
elif section.name in [b'SCALEKEYS']: elif section.name == b'SCALEKEYS':
fp.seek(section.data_size * section.data_count, 1) fp.seek(section.data_size * section.data_count, 1)
else: else:
raise RuntimeError(f'Unrecognized section "{section.name}"') raise RuntimeError(f'Unrecognized section "{section.name}"')

View File

@@ -1,7 +1,7 @@
from typing import Type from typing import Type
from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty, PointerProperty from bpy.props import BoolProperty, StringProperty, CollectionProperty, IntProperty, EnumProperty
from bpy.types import Operator, PropertyGroup, UIList, Material from bpy.types import Operator, PropertyGroup, UIList
from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ExportHelper
from .builder import build_psk, PskBuildOptions, get_psk_input_objects from .builder import build_psk, PskBuildOptions, get_psk_input_objects
@@ -70,7 +70,7 @@ def is_bone_filter_mode_item_available(context, identifier):
class PSK_UL_MaterialList(UIList): class PSK_UL_MaterialList(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.row() row = layout.row()
row.label(text=str(item.material_name), icon='MATERIAL') row.label(text=str(getattr(item, 'material_name')), icon='MATERIAL')
class MaterialListItem(PropertyGroup): class MaterialListItem(PropertyGroup):
@@ -108,11 +108,11 @@ class PskMaterialListItemMoveUp(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psk_export pg = getattr(context.scene, 'psk_export')
return pg.material_list_index > 0 return pg.material_list_index > 0
def execute(self, context): def execute(self, context):
pg = context.scene.psk_export pg = getattr(context.scene, 'psk_export')
pg.material_list.move(pg.material_list_index, pg.material_list_index - 1) pg.material_list.move(pg.material_list_index, pg.material_list_index - 1)
pg.material_list_index -= 1 pg.material_list_index -= 1
return {"FINISHED"} return {"FINISHED"}
@@ -126,11 +126,11 @@ class PskMaterialListItemMoveDown(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psk_export pg = getattr(context.scene, 'psk_export')
return pg.material_list_index < len(pg.material_list) - 1 return pg.material_list_index < len(pg.material_list) - 1
def execute(self, context): def execute(self, context):
pg = context.scene.psk_export pg = getattr(context.scene, 'psk_export')
pg.material_list.move(pg.material_list_index, pg.material_list_index + 1) pg.material_list.move(pg.material_list_index, pg.material_list_index + 1)
pg.material_list_index += 1 pg.material_list_index += 1
return {"FINISHED"} return {"FINISHED"}
@@ -157,7 +157,7 @@ class PskExportOperator(Operator, ExportHelper):
self.report({'ERROR_INVALID_CONTEXT'}, str(e)) self.report({'ERROR_INVALID_CONTEXT'}, str(e))
return {'CANCELLED'} return {'CANCELLED'}
pg = context.scene.psk_export pg = getattr(context.scene, 'psk_export')
# Populate bone groups list. # Populate bone groups list.
populate_bone_group_list(input_objects.armature_object, pg.bone_group_list) populate_bone_group_list(input_objects.armature_object, pg.bone_group_list)
@@ -178,8 +178,7 @@ class PskExportOperator(Operator, ExportHelper):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
scene = context.scene pg = getattr(context.scene, 'psk_export')
pg = scene.psk_export
layout.prop(pg, 'use_raw_mesh_data') layout.prop(pg, 'use_raw_mesh_data')
@@ -220,6 +219,7 @@ class PskExportOperator(Operator, ExportHelper):
try: try:
psk = build_psk(context, options) psk = build_psk(context, options)
export_psk(psk, self.filepath) export_psk(psk, self.filepath)
self.report({'INFO'}, f'PSK export successful')
except RuntimeError as e: except RuntimeError as e:
self.report({'ERROR_INVALID_CONTEXT'}, str(e)) self.report({'ERROR_INVALID_CONTEXT'}, str(e))
return {'CANCELLED'} return {'CANCELLED'}

View File

@@ -1,13 +1,13 @@
import os import os
import sys import sys
from math import inf from math import inf
from typing import Optional from typing import Optional, List
import bmesh import bmesh
import bpy import bpy
import numpy as np import numpy as np
from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty from bpy.props import BoolProperty, EnumProperty, FloatProperty, StringProperty
from bpy.types import Operator, PropertyGroup from bpy.types import Operator, PropertyGroup, VertexGroup
from bpy_extras.io_utils import ImportHelper from bpy_extras.io_utils import ImportHelper
from mathutils import Quaternion, Vector, Matrix from mathutils import Quaternion, Vector, Matrix
@@ -28,6 +28,24 @@ class PskImportOptions(object):
self.bone_length = 1.0 self.bone_length = 1.0
class ImportBone(object):
"""
Intermediate bone type for the purpose of construction.
"""
def __init__(self, index: int, psk_bone: Psk.Bone):
self.index: int = index
self.psk_bone: Psk.Bone = psk_bone
self.parent: Optional[ImportBone] = None
self.local_rotation: Quaternion = Quaternion()
self.local_translation: Vector = Vector()
self.world_rotation_matrix: Matrix = Matrix()
self.world_matrix: Matrix = Matrix()
self.vertex_group = None
self.orig_quat: Quaternion = Quaternion()
self.orig_loc: Vector = Vector()
self.post_quat: Quaternion = Quaternion()
def import_psk(psk: Psk, context, options: PskImportOptions): def import_psk(psk: Psk, context, options: PskImportOptions):
armature_object = None armature_object = None
@@ -49,21 +67,6 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
# Intermediate bone type for the purpose of construction.
class ImportBone(object):
def __init__(self, index: int, psk_bone: Psk.Bone):
self.index: int = index
self.psk_bone: Psk.Bone = psk_bone
self.parent: Optional[ImportBone] = None
self.local_rotation: Quaternion = Quaternion()
self.local_translation: Vector = Vector()
self.world_rotation_matrix: Matrix = Matrix()
self.world_matrix: Matrix = Matrix()
self.vertex_group = None
self.orig_quat: Quaternion = Quaternion()
self.orig_loc: Vector = Vector()
self.post_quat: Quaternion = Quaternion()
import_bones = [] import_bones = []
for bone_index, psk_bone in enumerate(psk.bones): for bone_index, psk_bone in enumerate(psk.bones):
@@ -213,7 +216,7 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
# Get a list of all bones that have weights associated with them. # Get a list of all bones that have weights associated with them.
vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights)) vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights))
vertex_groups = [None] * len(psk.bones) vertex_groups: List[Optional[VertexGroup]] = [None] * len(psk.bones)
for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices): for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices):
vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=psk_bone.name.decode('windows-1252')) vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=psk_bone.name.decode('windows-1252'))
@@ -234,16 +237,19 @@ def import_psk(psk: Psk, context, options: PskImportOptions):
pass pass
empty_set = set()
class PskImportPropertyGroup(PropertyGroup): class PskImportPropertyGroup(PropertyGroup):
should_import_vertex_colors: BoolProperty( should_import_vertex_colors: BoolProperty(
default=True, default=True,
options=set(), options=empty_set,
name='Vertex Colors', name='Vertex Colors',
description='Import vertex colors from PSKX files, if available' description='Import vertex colors from PSKX files, if available'
) )
vertex_color_space: EnumProperty( vertex_color_space: EnumProperty(
name='Vertex Color Space', name='Vertex Color Space',
options=set(), options=empty_set,
description='The source vertex color space', description='The source vertex color space',
default='SRGBA', default='SRGBA',
items=( items=(
@@ -254,25 +260,25 @@ class PskImportPropertyGroup(PropertyGroup):
should_import_vertex_normals: BoolProperty( should_import_vertex_normals: BoolProperty(
default=True, default=True,
name='Vertex Normals', name='Vertex Normals',
options=set(), options=empty_set,
description='Import vertex normals from PSKX files, if available' description='Import vertex normals from PSKX files, if available'
) )
should_import_extra_uvs: BoolProperty( should_import_extra_uvs: BoolProperty(
default=True, default=True,
name='Extra UVs', name='Extra UVs',
options=set(), options=empty_set,
description='Import extra UV maps from PSKX files, if available' description='Import extra UV maps from PSKX files, if available'
) )
should_import_mesh: BoolProperty( should_import_mesh: BoolProperty(
default=True, default=True,
name='Import Mesh', name='Import Mesh',
options=set(), options=empty_set,
description='Import mesh' description='Import mesh'
) )
should_import_skeleton: BoolProperty( should_import_skeleton: BoolProperty(
default=True, default=True,
name='Import Skeleton', name='Import Skeleton',
options=set(), options=empty_set,
description='Import skeleton' description='Import skeleton'
) )
bone_length: FloatProperty( bone_length: FloatProperty(
@@ -281,7 +287,7 @@ class PskImportPropertyGroup(PropertyGroup):
step=100, step=100,
soft_min=1.0, soft_min=1.0,
name='Bone Length', name='Bone Length',
options=set(), options=empty_set,
description='Length of the bones' description='Length of the bones'
) )
@@ -300,7 +306,7 @@ class PskImportOperator(Operator, ImportHelper):
default='') default='')
def execute(self, context): def execute(self, context):
pg = context.scene.psk_import pg = getattr(context.scene, 'psk_import')
psk = read_psk(self.filepath) psk = read_psk(self.filepath)
@@ -319,7 +325,7 @@ class PskImportOperator(Operator, ImportHelper):
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
pg = context.scene.psk_import pg = getattr(context.scene, 'psk_import')
layout = self.layout layout = self.layout
layout.prop(pg, 'should_import_mesh') layout.prop(pg, 'should_import_mesh')
row = layout.column() row = layout.column()

View File

@@ -3,7 +3,7 @@ import ctypes
from .data import * from .data import *
def _read_types(fp, data_class: ctypes.Structure, section: Section, data): def _read_types(fp, data_class, section: Section, data):
buffer_length = section.data_size * section.data_count buffer_length = section.data_size * section.data_count
buffer = fp.read(buffer_length) buffer = fp.read(buffer_length)
offset = 0 offset = 0

View File

@@ -1,12 +1,14 @@
from bpy.props import StringProperty, IntProperty, BoolProperty from bpy.props import StringProperty, IntProperty, BoolProperty
from bpy.types import PropertyGroup, UIList from bpy.types import PropertyGroup, UIList, UILayout, Context, AnyType
class PSX_UL_BoneGroupList(UIList): class PSX_UL_BoneGroupList(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
def draw_item(self, context: Context, layout: UILayout, data: AnyType, item: AnyType, icon: int,
active_data: AnyType, active_property: str, index: int = 0, flt_flag: int = 0):
row = layout.row() row = layout.row()
row.prop(item, 'is_selected', text=item.name) row.prop(item, 'is_selected', text=getattr(item, 'name'))
row.label(text=str(item.count), icon='BONE_DATA') row.label(text=str(getattr(item, 'count')), icon='BONE_DATA')
class BoneGroupListItem(PropertyGroup): class BoneGroupListItem(PropertyGroup):