* Added the ability to import the sequence FPS as a custom property to the Action (psa_fps)

* Added additional options for exporting sequence FPS values (scene, action metadata (custom data), custom,)
* The user can now choose to reuse existing action data blocks when importing sequence data.
* The user can choose whether or not to import keyframe data and metadata
This commit is contained in:
Colin Basnett
2022-04-15 16:50:58 -07:00
parent 7af97d53bd
commit 37f14a2a19
5 changed files with 192 additions and 90 deletions

View File

@@ -76,8 +76,8 @@ def populate_bone_group_list(armature_object, bone_group_list):
def get_psa_sequence_name(action, should_use_original_sequence_name): def get_psa_sequence_name(action, should_use_original_sequence_name):
if should_use_original_sequence_name and 'original_sequence_name' in action: if should_use_original_sequence_name and 'psa_sequence_name' in action:
return action['original_sequence_name'] return action['psa_sequence_name']
else: else:
return action.name return action.name

View File

@@ -1,10 +1,13 @@
from .data import * from .data import *
from ..helpers import * from ..helpers import *
from typing import Dict from typing import Dict, Iterable
from bpy.types import Action
class PsaBuilderOptions(object): class PsaBuilderOptions(object):
def __init__(self): def __init__(self):
self.fps_source = 'SCENE'
self.fps_custom = 30.0
self.sequence_source = 'ACTIONS' self.sequence_source = 'ACTIONS'
self.actions = [] self.actions = []
self.marker_names = [] self.marker_names = []
@@ -117,9 +120,30 @@ class PsaBuilder(object):
def __init__(self): def __init__(self):
self.name = '' self.name = ''
self.nla_state = NlaState() self.nla_state = NlaState()
self.fps = 30.0
export_sequences = [] export_sequences = []
def get_sequence_fps(context, options: PsaBuilderOptions, actions: Iterable[Action]) -> float:
if options.fps_source == 'SCENE':
return context.scene.render.fps
if options.fps_source == 'CUSTOM':
return options.fps_custom
elif options.fps_source == 'ACTION_METADATA':
# Get the minimum value of action metadata FPS values.
psa_fps_list = []
for action in filter(lambda x: 'psa_fps' in x, actions):
psa_fps = action['psa_fps']
if type(psa_fps) == int or type(psa_fps) == float:
psa_fps_list.append(psa_fps)
if len(psa_fps_list) > 0:
return min(psa_fps_list)
else:
# No valid action metadata to use, fallback to scene FPS
return context.scene.render.fps
else:
raise RuntimeError(f'Invalid FPS source "{options.fps_source}"')
if options.sequence_source == 'ACTIONS': if options.sequence_source == 'ACTIONS':
for action in options.actions: for action in options.actions:
if len(action.fcurves) == 0: if len(action.fcurves) == 0:
@@ -130,6 +154,7 @@ class PsaBuilder(object):
frame_min, 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_min = frame_min
export_sequence.nla_state.frame_max = frame_max export_sequence.nla_state.frame_max = frame_max
export_sequence.fps = get_sequence_fps(context, options, [action])
export_sequences.append(export_sequence) export_sequences.append(export_sequence)
pass pass
elif options.sequence_source == 'TIMELINE_MARKERS': elif options.sequence_source == 'TIMELINE_MARKERS':
@@ -141,6 +166,8 @@ class PsaBuilder(object):
export_sequence.nla_state.action = None export_sequence.nla_state.action = None
export_sequence.nla_state.frame_min = frame_min export_sequence.nla_state.frame_min = frame_min
export_sequence.nla_state.frame_max = frame_max export_sequence.nla_state.frame_max = frame_max
nla_strips_actions = set(map(lambda x: x.action, get_nla_strips_in_timeframe(active_object, frame_min, frame_max)))
export_sequence.fps = get_sequence_fps(context, options, nla_strips_actions)
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}')
@@ -166,7 +193,7 @@ class PsaBuilder(object):
psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252') psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252')
psa_sequence.frame_count = frame_count psa_sequence.frame_count = frame_count
psa_sequence.frame_start_index = frame_start_index psa_sequence.frame_start_index = frame_start_index
psa_sequence.fps = context.scene.render.fps psa_sequence.fps = export_sequence.fps
frame_count = frame_max - frame_min + 1 frame_count = frame_max - frame_min + 1
@@ -213,10 +240,6 @@ 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:

View File

@@ -1,6 +1,6 @@
import bpy import bpy
from bpy.types import Operator, PropertyGroup, Action, UIList, BoneGroup, Panel, TimelineMarker from bpy.types import Operator, PropertyGroup, Action, UIList, BoneGroup, Panel, TimelineMarker
from bpy.props import CollectionProperty, IntProperty, PointerProperty, StringProperty, BoolProperty, EnumProperty from bpy.props import CollectionProperty, IntProperty, FloatProperty, PointerProperty, StringProperty, BoolProperty, EnumProperty
from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ExportHelper
from typing import Type from typing import Type
from .builder import PsaBuilder, PsaBuilderOptions from .builder import PsaBuilder, PsaBuilderOptions
@@ -9,6 +9,7 @@ from ..types import BoneGroupListItem
from ..helpers import * from ..helpers import *
from collections import Counter from collections import Counter
import re import re
import sys
class PsaExporter(object): class PsaExporter(object):
@@ -65,10 +66,21 @@ class PsaExportPropertyGroup(PropertyGroup):
options=set(), options=set(),
description='', description='',
items=( items=(
('ACTIONS', 'Actions', 'Sequences will be exported using actions'), ('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers'), ('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences will be exported using timeline markers', 'MARKER_HLT', 1),
) )
) )
fps_source: EnumProperty(
name='FPS Source',
options=set(),
description='',
items=(
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
('ACTION_METADATA', 'Action Metadata', 'The frame rate will be determined by action\'s "psa_fps" custom property, if it exists. If the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used. If no metadata is available, the scene\'s frame rate will be used.', 'PROPERTIES', 1),
('CUSTOM', 'Custom', '', 2)
)
)
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=set(), step=100, 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)
marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem) marker_list: CollectionProperty(type=PsaExportTimelineMarkerListItem)
@@ -136,8 +148,13 @@ class PsaExportOperator(Operator, ExportHelper):
layout = self.layout layout = self.layout
pg = context.scene.psa_export pg = context.scene.psa_export
# FPS
layout.prop(pg, 'fps_source', text='FPS')
if pg.fps_source == 'CUSTOM':
layout.prop(pg, 'fps_custom', text='Custom')
# SOURCE # SOURCE
layout.prop(pg, 'sequence_source', text='Source', icon='ACTION' if pg.sequence_source == 'ACTIONS' else 'MARKER') layout.prop(pg, 'sequence_source', text='Source')
# SELECT ALL/NONE # SELECT ALL/NONE
row = layout.row(align=True) row = layout.row(align=True)
@@ -258,6 +275,8 @@ class PsaExportOperator(Operator, ExportHelper):
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]
options = PsaBuilderOptions() options = PsaBuilderOptions()
options.fps_source = pg.fps_source
options.fps_custom = pg.fps_custom
options.sequence_source = pg.sequence_source options.sequence_source = pg.sequence_source
options.actions = actions options.actions = actions
options.marker_names = marker_names options.marker_names = marker_names

View File

@@ -17,6 +17,9 @@ class PsaImportOptions(object):
self.should_stash = False self.should_stash = False
self.sequence_names = [] self.sequence_names = []
self.should_use_action_name_prefix = False self.should_use_action_name_prefix = False
self.should_overwrite = False
self.should_write_keyframes = True
self.should_write_metadata = True
self.action_name_prefix = '' self.action_name_prefix = ''
@@ -119,75 +122,87 @@ class PsaImporter(object):
# Add the action. # Add the action.
sequence_name = sequence.name.decode('windows-1252') sequence_name = sequence.name.decode('windows-1252')
action_name = options.action_name_prefix + sequence_name action_name = options.action_name_prefix + sequence_name
action = bpy.data.actions.new(name=action_name)
action.use_fake_user = options.should_use_fake_user
# Create f-curves for the rotation and location of each bone. if options.should_overwrite and action_name in bpy.data.actions:
for psa_bone_index, armature_bone_index in psa_to_armature_bone_indices.items(): action = bpy.data.actions[action_name]
import_bone = import_bones[psa_bone_index] else:
pose_bone = import_bone.pose_bone action = bpy.data.actions.new(name=action_name)
rotation_data_path = pose_bone.path_from_id('rotation_quaternion')
location_data_path = pose_bone.path_from_id('location')
import_bone.fcurves = [
action.fcurves.new(rotation_data_path, index=0), # Qw
action.fcurves.new(rotation_data_path, index=1), # Qx
action.fcurves.new(rotation_data_path, index=2), # Qy
action.fcurves.new(rotation_data_path, index=3), # Qz
action.fcurves.new(location_data_path, index=0), # Lx
action.fcurves.new(location_data_path, index=1), # Ly
action.fcurves.new(location_data_path, index=2), # Lz
]
# Read the sequence data matrix from the PSA. if options.should_write_keyframes:
sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name) # Remove existing f-curves (replace with action.fcurves.clear() in Blender 3.2)
keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8) while len(action.fcurves) > 0:
action.fcurves.remove(action.fcurves[-1])
# Convert the sequence's data from world-space to local-space. # Create f-curves for the rotation and location of each bone.
for bone_index, import_bone in enumerate(import_bones): for psa_bone_index, armature_bone_index in psa_to_armature_bone_indices.items():
if import_bone is None: import_bone = import_bones[psa_bone_index]
continue pose_bone = import_bone.pose_bone
for frame_index in range(sequence.frame_count): rotation_data_path = pose_bone.path_from_id('rotation_quaternion')
# This bone has writeable keyframes for this frame. location_data_path = pose_bone.path_from_id('location')
key_data = sequence_data_matrix[frame_index, bone_index] import_bone.fcurves = [
# Calculate the local-space key data for the bone. action.fcurves.new(rotation_data_path, index=0, action_group=pose_bone.name), # Qw
sequence_data_matrix[frame_index, bone_index] = calculate_fcurve_data(import_bone, key_data) action.fcurves.new(rotation_data_path, index=1, action_group=pose_bone.name), # Qx
action.fcurves.new(rotation_data_path, index=2, action_group=pose_bone.name), # Qy
action.fcurves.new(rotation_data_path, index=3, action_group=pose_bone.name), # Qz
action.fcurves.new(location_data_path, index=0, action_group=pose_bone.name), # Lx
action.fcurves.new(location_data_path, index=1, action_group=pose_bone.name), # Ly
action.fcurves.new(location_data_path, index=2, action_group=pose_bone.name), # Lz
]
# Clean the keyframe data. This is accomplished by writing zeroes to the write matrix when there is an # Read the sequence data matrix from the PSA.
# insufficiently large change in the data from the last written frame. sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name)
if options.should_clean_keys: keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8)
threshold = 0.001
# Convert the sequence's data from world-space to local-space.
for bone_index, import_bone in enumerate(import_bones): for bone_index, import_bone in enumerate(import_bones):
if import_bone is None: if import_bone is None:
continue continue
for fcurve_index in range(len(import_bone.fcurves)): for frame_index in range(sequence.frame_count):
# Get all the keyframe data for the bone's f-curve data from the sequence data matrix.
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
last_written_datum = 0
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 frame_index > 0 and abs(datum - last_written_datum) < threshold:
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
else:
last_written_datum = datum
# Write the keyframes out!
for frame_index in range(sequence.frame_count):
for bone_index, import_bone in enumerate(import_bones):
if import_bone is None:
continue
bone_has_writeable_keyframes = any(keyframe_write_matrix[frame_index, bone_index])
if bone_has_writeable_keyframes:
# This bone has writeable keyframes for this frame. # This bone has writeable keyframes for this frame.
key_data = sequence_data_matrix[frame_index, bone_index] key_data = sequence_data_matrix[frame_index, bone_index]
for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], key_data): # Calculate the local-space key data for the bone.
if should_write: sequence_data_matrix[frame_index, bone_index] = calculate_fcurve_data(import_bone, key_data)
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
# Store the original sequence name for use when exporting this same action using the PSA exporter. # Clean the keyframe data. This is accomplished by writing zeroes to the write matrix when there is an
action['original_sequence_name'] = sequence_name # insufficiently large change in the data from the last written frame.
if options.should_clean_keys:
threshold = 0.001
for bone_index, import_bone in enumerate(import_bones):
if import_bone is None:
continue
for fcurve_index in range(len(import_bone.fcurves)):
# Get all the keyframe data for the bone's f-curve data from the sequence data matrix.
fcurve_frame_data = sequence_data_matrix[:, bone_index, fcurve_index]
last_written_datum = 0
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 frame_index > 0 and abs(datum - last_written_datum) < threshold:
keyframe_write_matrix[frame_index, bone_index, fcurve_index] = 0
else:
last_written_datum = datum
# Write the keyframes out!
for frame_index in range(sequence.frame_count):
for bone_index, import_bone in enumerate(import_bones):
if import_bone is None:
continue
bone_has_writeable_keyframes = any(keyframe_write_matrix[frame_index, bone_index])
if bone_has_writeable_keyframes:
# This bone has writeable keyframes for this frame.
key_data = sequence_data_matrix[frame_index, bone_index]
for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], key_data):
if should_write:
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
# Write
if options.should_write_metadata:
action['psa_sequence_name'] = sequence_name
action['psa_fps'] = sequence.fps
actions.append(action) actions.append(action)
action.use_fake_user = options.should_use_fake_user
# If the user specifies, store the new animations as strips on a non-contributing NLA track. # If the user specifies, store the new animations as strips on a non-contributing NLA track.
if options.should_stash: if options.should_stash:
if armature_object.animation_data is None: if armature_object.animation_data is None:
@@ -246,11 +261,41 @@ class PsaImportPropertyGroup(PropertyGroup):
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())
should_overwrite: BoolProperty(default=False, name='Reuse Existing Datablocks', options=set())
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=set())
should_write_metadata: BoolProperty(default=True, name='Metadata', options=set(), description='Additional data will be written to the custom properties of the Action (e.g., frame rate)')
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_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
sequence_use_filter_invert: BoolProperty(default=False, options=set()) sequence_use_filter_invert: BoolProperty(default=False, options=set())
def filter_sequences(pg: PsaImportPropertyGroup, 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.action_name, f'*{pg.sequence_filter_name}*'):
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: PsaImportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaImportActionListItem]:
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_SequenceList(UIList): class PSA_UL_SequenceList(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):
@@ -265,7 +310,6 @@ class PSA_UL_SequenceList(UIList):
pg = context.scene.psa_import pg = context.scene.psa_import
row = layout.row() row = layout.row()
subrow = row.row(align=True) subrow = row.row(align=True)
# TODO: current used for both, not good!
subrow.prop(pg, 'sequence_filter_name', text="") subrow.prop(pg, 'sequence_filter_name', text="")
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT') subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
@@ -274,13 +318,7 @@ class PSA_UL_SequenceList(UIList):
sequences = getattr(data, property) sequences = getattr(data, property)
flt_flags = [] flt_flags = []
if pg.sequence_filter_name: if pg.sequence_filter_name:
flt_flags = bpy.types.UI_UL_list.filter_items_by_name( flt_flags = filter_sequences(pg, sequences)
pg.sequence_filter_name,
self.bitflag_filter_item,
sequences,
'action_name',
reverse=pg.sequence_use_filter_invert
)
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')
return flt_flags, flt_neworder return flt_flags, flt_neworder
@@ -302,14 +340,15 @@ class PsaImportSequencesSelectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = context.scene.psa_import
sequence_list = pg.sequence_list visible_sequences = get_visible_sequences(pg, pg.sequence_list)
has_unselected_actions = any(map(lambda action: not action.is_selected, sequence_list)) has_unselected_actions = any(map(lambda action: not action.is_selected, visible_sequences))
return len(sequence_list) > 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 = context.scene.psa_import
for action in pg.sequence_list: visible_sequences = get_visible_sequences(pg, pg.sequence_list)
action.is_selected = True for sequence in visible_sequences:
sequence.is_selected = True
return {'FINISHED'} return {'FINISHED'}
@@ -322,14 +361,15 @@ class PsaImportSequencesDeselectAll(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
pg = context.scene.psa_import pg = context.scene.psa_import
sequence_list = pg.sequence_list visible_sequences = get_visible_sequences(pg, pg.sequence_list)
has_selected_sequences = any(map(lambda action: action.is_selected, sequence_list)) has_selected_sequences = any(map(lambda sequence: sequence.is_selected, visible_sequences))
return len(sequence_list) > 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 = context.scene.psa_import
for action in pg.sequence_list: visible_sequences = get_visible_sequences(pg, pg.sequence_list)
action.is_selected = False for sequence in visible_sequences:
sequence.is_selected = False
return {'FINISHED'} return {'FINISHED'}
@@ -351,7 +391,6 @@ class PSA_PT_ImportPanel_Advanced(Panel):
col.separator() col.separator()
col.prop(pg, 'should_use_fake_user') col.prop(pg, 'should_use_fake_user')
col.prop(pg, 'should_stash') col.prop(pg, 'should_stash')
col.separator()
col.prop(pg, 'should_use_action_name_prefix') col.prop(pg, 'should_use_action_name_prefix')
if pg.should_use_action_name_prefix: if pg.should_use_action_name_prefix:
@@ -416,8 +455,26 @@ class PSA_PT_ImportPanel(Panel):
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)
row = box.row() col = layout.column(heading='')
row.operator(PsaImportOperator.bl_idname, text=f'Import') col.use_property_split = True
col.use_property_decorate = False
col.prop(pg, 'should_overwrite')
col = layout.column(heading='Write')
col.use_property_split = True
col.use_property_decorate = False
col.prop(pg, 'should_write_keyframes')
col.prop(pg, 'should_write_metadata')
selected_sequence_count = sum(map(lambda x: x.is_selected, pg.sequence_list))
row = layout.row()
import_button_text = 'Import'
if selected_sequence_count > 0:
import_button_text = f'Import ({selected_sequence_count})'
row.operator(PsaImportOperator.bl_idname, text=import_button_text)
class PsaImportFileReload(Operator): class PsaImportFileReload(Operator):
@@ -473,6 +530,9 @@ class PsaImportOperator(Operator):
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
options.should_overwrite = pg.should_overwrite
options.should_write_metadata = pg.should_write_metadata
options.should_write_keyframes = pg.should_write_keyframes
PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options) PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options)

View File

@@ -19,7 +19,7 @@ class PsaReader(object):
return self.psa.bones return self.psa.bones
@property @property
def sequences(self): def sequences(self) -> OrderedDict[Psa.Sequence]:
return self.psa.sequences return self.psa.sequences
@staticmethod @staticmethod