Lots of interface tweaks

This commit is contained in:
Colin Basnett
2022-04-04 00:32:12 -07:00
parent eb7a497010
commit a5955bf09b
6 changed files with 200 additions and 206 deletions

View File

@@ -55,6 +55,10 @@ class Vector3(Structure):
def __repr__(self): def __repr__(self):
return repr(tuple(self)) return repr(tuple(self))
@classmethod
def zero(cls):
return Vector3(0, 0, 0)
class Quaternion(Structure): class Quaternion(Structure):
_fields_ = [ _fields_ = [
@@ -73,6 +77,10 @@ class Quaternion(Structure):
def __repr__(self): def __repr__(self):
return repr(tuple(self)) return repr(tuple(self))
@classmethod
def identity(cls):
return Quaternion(0, 0, 0, 1)
class Section(Structure): class Section(Structure):
_fields_ = [ _fields_ = [

View File

@@ -1,6 +1,25 @@
from bpy.types import NlaStrip from bpy.types import NlaStrip
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from collections import Counter from collections import Counter
import datetime
class Timer:
def __enter__(self):
self.start = datetime.datetime.now()
self.interval = None
return self
def __exit__(self, *args):
self.end = datetime.datetime.now()
self.interval = self.end - self.start
@property
def duration(self):
if self.interval is not None:
return self.interval
else:
return datetime.datetime.now() - self.start
def rgb_to_srgb(c): def rgb_to_srgb(c):

View File

@@ -16,17 +16,25 @@ class PsaBuilderOptions(object):
self.sequence_name_suffix = '' self.sequence_name_suffix = ''
class PsaBuilderPerformance:
def __init__(self):
self.frame_set_duration = datetime.timedelta()
self.key_build_duration = datetime.timedelta()
self.key_add_duration = datetime.timedelta()
class PsaBuilder(object): class PsaBuilder(object):
def __init__(self): def __init__(self):
pass pass
def build(self, context, options: PsaBuilderOptions) -> Psa: def build(self, context, options: PsaBuilderOptions) -> Psa:
object = context.view_layer.objects.active performance = PsaBuilderPerformance()
active_object = context.view_layer.objects.active
if object.type != 'ARMATURE': if active_object.type != 'ARMATURE':
raise RuntimeError('Selected object must be an Armature') raise RuntimeError('Selected object must be an Armature')
armature = object armature = active_object
if armature.animation_data is None: if armature.animation_data is None:
raise RuntimeError('No animation data for armature') raise RuntimeError('No animation data for armature')
@@ -99,13 +107,17 @@ class PsaBuilder(object):
psa.bones.append(psa_bone) psa.bones.append(psa_bone)
# Populate the export sequence list. # Populate the export sequence list.
class ExportSequence: class NlaState:
def __init__(self): def __init__(self):
self.name = ''
self.frame_min = 0 self.frame_min = 0
self.frame_max = 0 self.frame_max = 0
self.action = None self.action = None
class ExportSequence:
def __init__(self):
self.name = ''
self.nla_state = NlaState()
export_sequences = [] export_sequences = []
if options.sequence_source == 'ACTIONS': if options.sequence_source == 'ACTIONS':
@@ -113,9 +125,11 @@ class PsaBuilder(object):
if len(action.fcurves) == 0: if len(action.fcurves) == 0:
continue continue
export_sequence = ExportSequence() export_sequence = ExportSequence()
export_sequence.action = action export_sequence.nla_state.action = action
export_sequence.name = get_psa_sequence_name(action, options.should_use_original_sequence_names) export_sequence.name = get_psa_sequence_name(action, options.should_use_original_sequence_names)
export_sequence.frame_min, export_sequence.frame_max = [int(x) for x in action.frame_range] frame_min, frame_max = [int(x) for x in action.frame_range]
export_sequence.nla_state.frame_min = frame_min
export_sequence.nla_state.frame_max = frame_max
export_sequences.append(export_sequence) export_sequences.append(export_sequence)
pass pass
elif options.sequence_source == 'TIMELINE_MARKERS': elif options.sequence_source == 'TIMELINE_MARKERS':
@@ -123,10 +137,10 @@ class PsaBuilder(object):
for name, (frame_min, frame_max) in sequence_frame_ranges.items(): for name, (frame_min, frame_max) in sequence_frame_ranges.items():
export_sequence = ExportSequence() export_sequence = ExportSequence()
export_sequence.action = None
export_sequence.name = name export_sequence.name = name
export_sequence.frame_min = frame_min export_sequence.nla_state.action = None
export_sequence.frame_max = frame_max export_sequence.nla_state.frame_min = frame_min
export_sequence.nla_state.frame_max = frame_max
export_sequences.append(export_sequence) export_sequences.append(export_sequence)
else: else:
raise ValueError(f'Unhandled sequence source: {options.sequence_source}') raise ValueError(f'Unhandled sequence source: {options.sequence_source}')
@@ -140,13 +154,13 @@ class PsaBuilder(object):
frame_start_index = 0 frame_start_index = 0
for export_sequence in export_sequences: for export_sequence in export_sequences:
armature.animation_data.action = export_sequence.action armature.animation_data.action = export_sequence.nla_state.action
context.view_layer.update() context.view_layer.update()
psa_sequence = Psa.Sequence() psa_sequence = Psa.Sequence()
frame_min = export_sequence.frame_min frame_min = export_sequence.nla_state.frame_min
frame_max = export_sequence.frame_max frame_max = export_sequence.nla_state.frame_max
frame_count = frame_max - frame_min + 1 frame_count = frame_max - frame_min + 1
psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252') psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252')
@@ -157,34 +171,40 @@ class PsaBuilder(object):
frame_count = frame_max - frame_min + 1 frame_count = frame_max - frame_min + 1
for frame in range(frame_count): for frame in range(frame_count):
context.scene.frame_set(frame_min + frame) with Timer() as t:
context.scene.frame_set(frame_min + frame)
performance.frame_set_duration += t.duration
for pose_bone in pose_bones: for pose_bone in pose_bones:
key = Psa.Key() with Timer() as t:
pose_bone_matrix = pose_bone.matrix key = Psa.Key()
pose_bone_matrix = pose_bone.matrix
if pose_bone.parent is not None: if pose_bone.parent is not None:
pose_bone_parent_matrix = pose_bone.parent.matrix pose_bone_parent_matrix = pose_bone.parent.matrix
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
location = pose_bone_matrix.to_translation() location = pose_bone_matrix.to_translation()
rotation = pose_bone_matrix.to_quaternion().normalized() rotation = pose_bone_matrix.to_quaternion().normalized()
if pose_bone.parent is not None: if pose_bone.parent is not None:
rotation.x = -rotation.x rotation.x = -rotation.x
rotation.y = -rotation.y rotation.y = -rotation.y
rotation.z = -rotation.z rotation.z = -rotation.z
key.location.x = location.x key.location.x = location.x
key.location.y = location.y key.location.y = location.y
key.location.z = location.z key.location.z = location.z
key.rotation.x = rotation.x key.rotation.x = rotation.x
key.rotation.y = rotation.y key.rotation.y = rotation.y
key.rotation.z = rotation.z key.rotation.z = rotation.z
key.rotation.w = rotation.w key.rotation.w = rotation.w
key.time = 1.0 / psa_sequence.fps key.time = 1.0 / psa_sequence.fps
performance.key_build_duration += t.duration
psa.keys.append(key) with Timer() as t:
psa.keys.append(key)
performance.key_add_duration += t.duration
psa_sequence.bone_count = len(pose_bones) psa_sequence.bone_count = len(pose_bones)
psa_sequence.track_time = frame_count psa_sequence.track_time = frame_count
@@ -193,6 +213,10 @@ class PsaBuilder(object):
psa.sequences[export_sequence.name] = psa_sequence psa.sequences[export_sequence.name] = psa_sequence
print(f'frame set duration: {performance.frame_set_duration}')
print(f'key build duration: {performance.key_build_duration}')
print(f'key add duration: {performance.key_add_duration}')
return psa return psa
def get_timeline_marker_sequence_frame_ranges(self, object, context, options: PsaBuilderOptions) -> Dict: def get_timeline_marker_sequence_frame_ranges(self, object, context, options: PsaBuilderOptions) -> Dict:
@@ -214,8 +238,8 @@ class PsaBuilder(object):
frame_max = sorted_timeline_markers[next_marker_index].frame frame_max = sorted_timeline_markers[next_marker_index].frame
if options.should_trim_timeline_marker_sequences: if options.should_trim_timeline_marker_sequences:
nla_strips = get_nla_strips_in_timeframe(object, marker.frame, frame_max) nla_strips = get_nla_strips_in_timeframe(object, marker.frame, frame_max)
frame_max = min(frame_max, max(map(lambda x: x.frame_end, nla_strips))) frame_max = min(frame_max, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips)))
frame_min = max(frame_min, min(map(lambda x: x.frame_start, nla_strips))) frame_min = max(frame_min, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips)))
else: else:
# There is no next marker. # There is no next marker.
# Find the final frame of all the NLA strips and use that as the last frame of this sequence. # Find the final frame of all the NLA strips and use that as the last frame of this sequence.

View File

@@ -38,23 +38,15 @@ class PsaExporter(object):
class PsaExportActionListItem(PropertyGroup): class PsaExportActionListItem(PropertyGroup):
action: PointerProperty(type=Action) action: PointerProperty(type=Action)
action_name: StringProperty() name: StringProperty()
is_selected: BoolProperty(default=False) is_selected: BoolProperty(default=False)
@property
def name(self):
return self.action.name
class PsaExportTimelineMarkerListItem(PropertyGroup): class PsaExportTimelineMarkerListItem(PropertyGroup):
marker_index: IntProperty() marker_index: IntProperty()
marker_name: StringProperty() name: StringProperty()
is_selected: BoolProperty(default=True) is_selected: BoolProperty(default=True)
@property
def name(self):
return self.marker_name
def update_action_names(context): def update_action_names(context):
pg = context.scene.psa_export pg = context.scene.psa_export
@@ -110,6 +102,10 @@ class PsaExportPropertyGroup(PropertyGroup):
) )
sequence_name_prefix: StringProperty(name='Prefix', options=set()) sequence_name_prefix: StringProperty(name='Prefix', options=set())
sequence_name_suffix: StringProperty(name='Suffix', options=set()) sequence_name_suffix: StringProperty(name='Suffix', options=set())
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
sequence_use_filter_invert: BoolProperty(default=False, options=set())
sequence_filter_asset: BoolProperty(default=False, name='Show assets', description='Show actions that belong to an asset library', options=set())
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
def is_bone_filter_mode_item_available(context, identifier): def is_bone_filter_mode_item_available(context, identifier):
@@ -152,6 +148,7 @@ class PsaExportOperator(Operator, ExportHelper):
# ACTIONS # ACTIONS
if pg.sequence_source == 'ACTIONS': if pg.sequence_source == 'ACTIONS':
rows = max(3, min(len(pg.action_list), 10)) rows = max(3, min(len(pg.action_list), 10))
layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows) layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
col = layout.column() col = layout.column()
@@ -174,7 +171,7 @@ class PsaExportOperator(Operator, ExportHelper):
# Determine if there is going to be a naming conflict and display an error, if so. # Determine if there is going to be a naming conflict and display an error, if so.
selected_items = [x for x in pg.action_list if x.is_selected] selected_items = [x for x in pg.action_list if x.is_selected]
action_names = [x.action_name for x in selected_items] action_names = [x.name for x in selected_items]
action_name_counts = Counter(action_names) action_name_counts = Counter(action_names)
for action_name, count in action_name_counts.items(): for action_name, count in action_name_counts.items():
if count > 1: if count > 1:
@@ -195,6 +192,9 @@ class PsaExportOperator(Operator, ExportHelper):
rows = max(3, min(len(pg.bone_group_list), 10)) 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) layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows)
def should_action_be_selected_by_default(self, action):
return action is not None and action.asset_data is None
def is_action_for_armature(self, action): def is_action_for_armature(self, action):
if len(action.fcurves) == 0: if len(action.fcurves) == 0:
return False return False
@@ -228,8 +228,8 @@ class PsaExportOperator(Operator, ExportHelper):
continue continue
item = pg.action_list.add() item = pg.action_list.add()
item.action = action item.action = action
item.action_name = action.name item.name = action.name
item.is_selected = True item.is_selected = self.should_action_be_selected_by_default(action)
update_action_names(context) update_action_names(context)
@@ -237,7 +237,7 @@ class PsaExportOperator(Operator, ExportHelper):
pg.marker_list.clear() pg.marker_list.clear()
for marker in context.scene.timeline_markers: for marker in context.scene.timeline_markers:
item = pg.marker_list.add() item = pg.marker_list.add()
item.marker_name = marker.name item.name = marker.name
if len(pg.action_list) == 0 and len(pg.marker_names) == 0: if len(pg.action_list) == 0 and len(pg.marker_names) == 0:
# If there are no actions at all, we have nothing to export, so just cancel the operation. # If there are no actions at all, we have nothing to export, so just cancel the operation.
@@ -255,7 +255,7 @@ class PsaExportOperator(Operator, ExportHelper):
pg = context.scene.psa_export pg = 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.marker_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]
options = PsaBuilderOptions() options = PsaBuilderOptions()
options.sequence_source = pg.sequence_source options.sequence_source = pg.sequence_source
@@ -283,39 +283,75 @@ class PsaExportOperator(Operator, ExportHelper):
class PSA_UL_ExportTimelineMarkerList(UIList): class PSA_UL_ExportTimelineMarkerList(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):
layout.prop(item, 'is_selected', icon_only=True, text=item.marker_name) layout.prop(item, 'is_selected', icon_only=True, text=item.name)
def filter_items(self, context, data, property): def filter_items(self, context, data, property):
actions = getattr(data, property) pg = context.scene.psa_export
flt_flags = [] sequences = getattr(data, property)
flt_neworder = [] flt_flags = filter_sequences(pg, sequences)
if self.filter_name: flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'name')
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name,
self.bitflag_filter_item,
actions,
'marker_name',
reverse=self.use_filter_invert
)
return flt_flags, flt_neworder return flt_flags, flt_neworder
def filter_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[int]:
bitflag_filter_item = 1 << 30
flt_flags = [bitflag_filter_item] * len(sequences)
if pg.sequence_filter_name is not None:
# Filter name is non-empty.
import fnmatch
for i, sequence in enumerate(sequences):
if not fnmatch.fnmatch(sequence.name, f'*{pg.sequence_filter_name}*'):
flt_flags[i] &= ~bitflag_filter_item
if not pg.sequence_filter_asset:
for i, sequence in enumerate(sequences):
if hasattr(sequence, 'action') and sequence.action.asset_data is not None:
flt_flags[i] &= ~bitflag_filter_item
if pg.sequence_use_filter_invert:
# Invert filter flags for all items.
for i, sequence in enumerate(sequences):
flt_flags[i] ^= ~bitflag_filter_item
return flt_flags
def get_visible_sequences(pg: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]:
visible_sequences = []
for i, flag in enumerate(filter_sequences(pg, sequences)):
if bool(flag & (1 << 30)):
visible_sequences.append(sequences[i])
return visible_sequences
class PSA_UL_ExportActionList(UIList): class PSA_UL_ExportActionList(UIList):
def __init__(self):
super(PSA_UL_ExportActionList, self).__init__()
# Show the filtering options by default.
self.use_filter_show = True
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):
layout.prop(item, 'is_selected', icon_only=True, text=item.action_name) layout.prop(item, 'is_selected', icon_only=True, text=item.name)
if item.action.asset_data is not None:
layout.label(text='', icon='ASSET_MANAGER')
def draw_filter(self, context, layout):
pg = context.scene.psa_export
row = layout.row()
subrow = row.row(align=True)
subrow.prop(pg, 'sequence_filter_name', text="")
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
subrow = row.row(align=True)
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
# subrow.prop(pg, 'sequence_use_filter_sort_reverse', text='', icon='SORT_ASC')
def filter_items(self, context, data, property): def filter_items(self, context, data, property):
pg = context.scene.psa_export
actions = getattr(data, property) actions = getattr(data, property)
flt_flags = [] flt_flags = filter_sequences(pg, actions)
flt_neworder = [] flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
if self.filter_name:
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name,
self.bitflag_filter_item,
actions,
'action_name',
reverse=self.use_filter_invert
)
return flt_flags, flt_neworder return flt_flags, flt_neworder
@@ -336,14 +372,17 @@ class PsaExportActionsSelectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_export
item_list = cls.get_item_list(context) item_list = cls.get_item_list(context)
has_unselected_items = any(map(lambda item: not item.is_selected, item_list)) visible_sequences = get_visible_sequences(pg, item_list)
return len(item_list) > 0 and has_unselected_items has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences))
return has_unselected_sequences
def execute(self, context): def execute(self, context):
item_list = self.get_item_list(context) pg = context.scene.psa_export
for item in item_list: sequences = self.get_item_list(context)
item.is_selected = True for sequence in get_visible_sequences(pg, sequences):
sequence.is_selected = True
return {'FINISHED'} return {'FINISHED'}
@@ -369,9 +408,10 @@ 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
item_list = self.get_item_list(context) item_list = self.get_item_list(context)
for item in item_list: for sequence in get_visible_sequences(pg, item_list):
item.is_selected = False sequence.is_selected = False
return {'FINISHED'} return {'FINISHED'}

View File

@@ -208,7 +208,6 @@ def load_psa_file(context):
pg = context.scene.psa_import pg = context.scene.psa_import
pg.sequence_list.clear() pg.sequence_list.clear()
pg.psa.bones.clear() pg.psa.bones.clear()
pg.action_list.clear()
pg.psa_error = '' pg.psa_error = ''
try: try:
# Read the file and populate the action list. # Read the file and populate the action list.
@@ -243,13 +242,13 @@ class PsaImportPropertyGroup(PropertyGroup):
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)
action_list: CollectionProperty(type=PsaImportActionListItem)
action_list_index: IntProperty(name='', default=0)
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', options=set()) should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', options=set())
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.', options=set()) 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.', options=set())
should_stash: BoolProperty(default=False, name='Stash', description='Stash each imported action as a strip on a new non-contributing NLA track', options=set()) should_stash: BoolProperty(default=False, name='Stash', description='Stash each imported action as a strip on a new non-contributing NLA track', options=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=set())
action_name_prefix: StringProperty(default='', name='Prefix', options=set()) action_name_prefix: StringProperty(default='', name='Prefix', options=set())
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
sequence_use_filter_invert: BoolProperty(default=False, options=set())
class PSA_UL_SequenceList(UIList): class PSA_UL_SequenceList(UIList):
@@ -263,25 +262,26 @@ class PSA_UL_SequenceList(UIList):
column.label(text=item.action_name) column.label(text=item.action_name)
def draw_filter(self, context, layout): def draw_filter(self, context, layout):
pg = context.scene.psa_import
row = layout.row() row = layout.row()
subrow = row.row(align=True) subrow = row.row(align=True)
subrow.prop(self, 'filter_name', text="") # TODO: current used for both, not good!
subrow.prop(self, 'use_filter_invert', text="", icon='ARROW_LEFTRIGHT') subrow.prop(pg, 'sequence_filter_name', text="")
subrow = row.row(align=True) subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
subrow.prop(self, 'use_filter_sort_reverse', text='', icon='SORT_ASC')
def filter_items(self, context, data, property): def filter_items(self, context, data, property):
actions = getattr(data, property) pg = context.scene.psa_import
sequences = getattr(data, property)
flt_flags = [] flt_flags = []
if self.filter_name: if pg.sequence_filter_name:
flt_flags = bpy.types.UI_UL_list.filter_items_by_name( flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
self.filter_name, pg.sequence_filter_name,
self.bitflag_filter_item, self.bitflag_filter_item,
actions, sequences,
'action_name', 'action_name',
reverse=self.use_filter_invert reverse=pg.sequence_use_filter_invert
) )
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'action_name') flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'action_name')
return flt_flags, flt_neworder return flt_flags, flt_neworder
@@ -313,26 +313,6 @@ class PsaImportSequencesSelectAll(Operator):
return {'FINISHED'} return {'FINISHED'}
class PsaImportActionsSelectAll(Operator):
bl_idname = 'psa_import.actions_select_all'
bl_label = 'All'
bl_description = 'Select all actions'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_import
action_list = pg.action_list
has_unselected_actions = any(map(lambda action: not action.is_selected, action_list))
return len(action_list) > 0 and has_unselected_actions
def execute(self, context):
pg = context.scene.psa_import
for action in pg.action_list:
action.is_selected = True
return {'FINISHED'}
class PsaImportSequencesDeselectAll(Operator): class PsaImportSequencesDeselectAll(Operator):
bl_idname = 'psa_import.sequences_deselect_all' bl_idname = 'psa_import.sequences_deselect_all'
bl_label = 'None' bl_label = 'None'
@@ -353,26 +333,6 @@ class PsaImportSequencesDeselectAll(Operator):
return {'FINISHED'} return {'FINISHED'}
class PsaImportActionsDeselectAll(Operator):
bl_idname = 'psa_import.actions_deselect_all'
bl_label = 'None'
bl_description = 'Deselect all actions'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_import
action_list = pg.action_list
has_selected_actions = any(map(lambda action: action.is_selected, action_list))
return len(action_list) > 0 and has_selected_actions
def execute(self, context):
pg = context.scene.psa_import
for action in pg.action_list:
action.is_selected = False
return {'FINISHED'}
class PSA_PT_ImportPanel_Advanced(Panel): class PSA_PT_ImportPanel_Advanced(Panel):
bl_space_type = 'PROPERTIES' bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW' bl_region_type = 'WINDOW'
@@ -443,31 +403,21 @@ class PSA_PT_ImportPanel(Panel):
box.label(text=f'Sequences', icon='ARMATURE_DATA') box.label(text=f'Sequences', icon='ARMATURE_DATA')
# select # select
rows = max(3, min(len(pg.sequence_list) + len(pg.action_list), 10)) rows = max(3, max(len(pg.sequence_list), 10))
row = box.row() row = box.row()
col = row.column() col = row.column()
row2 = col.row(align=True) row2 = col.row(align=True)
row2.label(text='Select') row2.label(text='Select')
row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All') row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None') row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
col = col.row() col = col.row()
col.template_list('PSA_UL_ImportSequenceList', '', pg, 'sequence_list', pg, 'sequence_list_index', rows=rows) col.template_list('PSA_UL_ImportSequenceList', '', pg, 'sequence_list', pg, 'sequence_list_index', rows=rows)
col = row.column(align=True) row = box.row()
col.operator(PsaImportPushToActions.bl_idname, icon='TRIA_RIGHT', text='') row.operator(PsaImportOperator.bl_idname, text=f'Import')
col.operator(PsaImportPopFromActions.bl_idname, icon='TRIA_LEFT', text='')
col = row.column()
row2 = col.row(align=True)
row2.label(text='Select')
row2.operator(PsaImportActionsSelectAll.bl_idname, text='All')
row2.operator(PsaImportActionsDeselectAll.bl_idname, text='None')
col.template_list('PSA_UL_ImportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
col.separator()
col.operator(PsaImportOperator.bl_idname, text=f'Import')
class PsaImportFileReload(Operator): class PsaImportFileReload(Operator):
@@ -508,69 +458,26 @@ class PsaImportOperator(Operator):
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = context.scene.psa_import
active_object = context.view_layer.objects.active active_object = context.view_layer.objects.active
action_list = pg.action_list if active_object is None or active_object.type != 'ARMATURE':
return len(action_list) and active_object is not None and active_object.type == 'ARMATURE' return False
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 = 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.action_list] sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
options = PsaImportOptions() options = PsaImportOptions()
options.sequence_names = sequence_names options.sequence_names = sequence_names
options.should_clean_keys = pg.should_clean_keys options.should_clean_keys = pg.should_clean_keys
options.should_use_fake_user = pg.should_use_fake_user options.should_use_fake_user = pg.should_use_fake_user
options.should_stash = pg.should_stash options.should_stash = pg.should_stash
options.action_name_prefix = pg.action_name_prefix options.action_name_prefix = pg.action_name_prefix
PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options) PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options)
self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)') self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)')
return {'FINISHED'}
class PsaImportPushToActions(Operator):
bl_idname = 'psa_import.push_to_actions'
bl_label = 'Push to Actions'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_import
has_sequences_selected = any(map(lambda x: x.is_selected, pg.sequence_list))
return has_sequences_selected
def execute(self, context):
pg = context.scene.psa_import
indices_to_remove = []
for sequence_index, item in enumerate(pg.sequence_list):
if item.is_selected:
indices_to_remove.append(sequence_index)
action = pg.action_list.add()
action.action_name = item.action_name
for index in reversed(indices_to_remove):
pg.sequence_list.remove(index)
return {'FINISHED'}
class PsaImportPopFromActions(Operator):
bl_idname = 'psa_import.pop_from_actions'
bl_label = 'Pop From Actions'
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
pg = context.scene.psa_import
has_actions_selected = any(map(lambda x: x.is_selected, pg.action_list))
return has_actions_selected
def execute(self, context):
pg = context.scene.psa_import
indices_to_remove = []
for action_index, item in enumerate(pg.action_list):
if item.is_selected:
indices_to_remove.append(action_index)
sequence = pg.sequence_list.add()
sequence.action_name = item.action_name
for index in reversed(indices_to_remove):
pg.action_list.remove(index)
return {'FINISHED'} return {'FINISHED'}
@@ -606,8 +513,6 @@ classes = (
PSA_UL_ImportActionList, PSA_UL_ImportActionList,
PsaImportSequencesSelectAll, PsaImportSequencesSelectAll,
PsaImportSequencesDeselectAll, PsaImportSequencesDeselectAll,
PsaImportActionsSelectAll,
PsaImportActionsDeselectAll,
PsaImportFileReload, PsaImportFileReload,
PSA_PT_ImportPanel, PSA_PT_ImportPanel,
PSA_PT_ImportPanel_Advanced, PSA_PT_ImportPanel_Advanced,
@@ -615,6 +520,4 @@ classes = (
PsaImportOperator, PsaImportOperator,
PsaImportFileSelectOperator, PsaImportFileSelectOperator,
PsaImportSelectFile, PsaImportSelectFile,
PsaImportPushToActions,
PsaImportPopFromActions,
) )

View File

@@ -70,12 +70,12 @@ class PskBuilder(object):
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement # If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement
# that a PSK file must have at least one bone. # that a PSK file must have at least one bone.
psk_bone = Psk.Bone() psk_bone = Psk.Bone()
psk_bone.name = bytes('static', encoding='windows-1252') psk_bone.name = bytes('root', encoding='windows-1252')
psk_bone.flags = 0 psk_bone.flags = 0
psk_bone.children_count = 0 psk_bone.children_count = 0
psk_bone.parent_index = 0 psk_bone.parent_index = 0
psk_bone.location = Vector3(0, 0, 0) psk_bone.location = Vector3.zero()
psk_bone.rotation = Quaternion(0, 0, 0, 1) psk_bone.rotation = Quaternion.identity()
psk.bones.append(psk_bone) psk.bones.append(psk_bone)
else: else:
bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices) bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices)