mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2025-01-31 11:53:47 +01:00
New features for handling FPS
* An action's export FPS is now stored in a property group instead of a loose custom property value. * New options for handling FPS when importing sequences.
This commit is contained in:
parent
c4c00ca49e
commit
5bbb1512e0
@ -1,3 +1,5 @@
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
bl_info = {
|
||||
"name": "PSK/PSA Importer/Exporter",
|
||||
"author": "Colin Basnett, Yurii Ti",
|
||||
@ -124,3 +126,17 @@ def unregister():
|
||||
|
||||
if __name__ == '__main__':
|
||||
register()
|
||||
|
||||
|
||||
@persistent
|
||||
def load_handler(dummy):
|
||||
print('RUNNING LOAD HANDLER')
|
||||
# Convert old `psa_sequence_fps` property to new `psa_export.fps` property.
|
||||
# This is only needed for backwards compatibility with older versions of the addon.
|
||||
for action in bpy.data.actions:
|
||||
if 'psa_sequence_fps' in action:
|
||||
action.psa_export.fps = action['psa_sequence_fps']
|
||||
del action['psa_sequence_fps']
|
||||
|
||||
|
||||
bpy.app.handlers.load_post.append(load_handler)
|
||||
|
@ -95,16 +95,7 @@ def get_sequence_fps(context: Context, fps_source: str, fps_custom: float, actio
|
||||
return fps_custom
|
||||
elif fps_source == 'ACTION_METADATA':
|
||||
# Get the minimum value of action metadata FPS values.
|
||||
fps_list = []
|
||||
for action in filter(lambda x: 'psa_sequence_fps' in x, actions):
|
||||
fps = action['psa_sequence_fps']
|
||||
if type(fps) == int or type(fps) == float:
|
||||
fps_list.append(fps)
|
||||
if len(fps_list) > 0:
|
||||
return min(fps_list)
|
||||
else:
|
||||
# No valid action metadata to use, fallback to scene FPS
|
||||
return context.scene.render.fps
|
||||
return min([action.psa_export.fps for action in actions])
|
||||
else:
|
||||
raise RuntimeError(f'Invalid FPS source "{fps_source}"')
|
||||
|
||||
|
@ -124,9 +124,7 @@ class PSA_PG_export(PropertyGroup):
|
||||
description='',
|
||||
items=(
|
||||
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
||||
('ACTION_METADATA', 'Action Metadata',
|
||||
'The frame rate will be determined by action\'s "psa_sequence_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),
|
||||
('ACTION_METADATA', 'Action Metadata', 'The frame rate will be determined by action\'s FPS property found in the PSA Export panel.\n\nIf the Sequence Source is Timeline Markers, the lowest value of all contributing actions will be used', 'PROPERTIES', 1),
|
||||
('CUSTOM', 'Custom', '', 2)
|
||||
)
|
||||
)
|
||||
|
@ -166,6 +166,8 @@ class PSA_OT_import(Operator, ImportHelper):
|
||||
options.should_write_keyframes = pg.should_write_keyframes
|
||||
options.should_convert_to_samples = pg.should_convert_to_samples
|
||||
options.bone_mapping_mode = pg.bone_mapping_mode
|
||||
options.fps_source = pg.fps_source
|
||||
options.fps_custom = pg.fps_custom
|
||||
|
||||
result = import_psa(context, psa_reader, context.view_layer.objects.active, options)
|
||||
|
||||
@ -234,6 +236,10 @@ class PSA_OT_import(Operator, ImportHelper):
|
||||
col.use_property_decorate = False
|
||||
col.prop(pg, 'should_convert_to_samples')
|
||||
col.separator()
|
||||
# FPS
|
||||
col.prop(pg, 'fps_source')
|
||||
if pg.fps_source == 'CUSTOM':
|
||||
col.prop(pg, 'fps_custom')
|
||||
|
||||
col = layout.column(heading='Options')
|
||||
col.use_property_split = True
|
||||
|
@ -2,7 +2,8 @@ import re
|
||||
from fnmatch import fnmatch
|
||||
from typing import List
|
||||
|
||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, IntProperty, PointerProperty, EnumProperty
|
||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, IntProperty, PointerProperty, EnumProperty, \
|
||||
FloatProperty
|
||||
from bpy.types import PropertyGroup, Text
|
||||
|
||||
empty_set = set()
|
||||
@ -66,6 +67,21 @@ class PSA_PG_import(PropertyGroup):
|
||||
'\'root\' can be mapped to the armature bone \'Root\')', 'CASE_INSENSITIVE', 1),
|
||||
)
|
||||
)
|
||||
fps_source: EnumProperty(name='FPS Source', items=(
|
||||
('SEQUENCE', 'Sequence', 'The sequence frame rate matches the original frame rate', 'ACTION', 0),
|
||||
('SCENE', 'Scene', 'The sequence frame rate dilates to match that of the scene', 'SCENE_DATA', 1),
|
||||
('CUSTOM', 'Custom', 'The sequence frame rate dilates to match a custom frame rate', 2),
|
||||
))
|
||||
fps_custom: FloatProperty(
|
||||
default=30.0,
|
||||
name='Custom FPS',
|
||||
description='The frame rate to which the imported actions will be converted',
|
||||
options=empty_set,
|
||||
min=1.0,
|
||||
soft_min=1.0,
|
||||
soft_max=60.0,
|
||||
step=100,
|
||||
)
|
||||
|
||||
|
||||
def filter_sequences(pg: PSA_PG_import, sequences) -> List[int]:
|
||||
|
@ -21,6 +21,8 @@ class PsaImportOptions(object):
|
||||
self.action_name_prefix = ''
|
||||
self.should_convert_to_samples = False
|
||||
self.bone_mapping_mode = 'CASE_INSENSITIVE'
|
||||
self.fps_source = 'SEQUENCE'
|
||||
self.fps_custom: float = 30.0
|
||||
|
||||
|
||||
class ImportBone(object):
|
||||
@ -172,6 +174,19 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
||||
else:
|
||||
action = bpy.data.actions.new(name=action_name)
|
||||
|
||||
# Calculate the target FPS.
|
||||
target_fps = sequence.fps
|
||||
if options.fps_source == 'CUSTOM':
|
||||
target_fps = options.fps_custom
|
||||
elif options.fps_source == 'SCENE':
|
||||
target_fps = context.scene.render.fps
|
||||
elif options.fps_source == 'SEQUENCE':
|
||||
target_fps = sequence.fps
|
||||
else:
|
||||
raise ValueError(f'Unknown FPS source: {options.fps_source}')
|
||||
|
||||
keyframe_time_dilation = target_fps / sequence.fps
|
||||
|
||||
if options.should_write_keyframes:
|
||||
# Remove existing f-curves (replace with action.fcurves.clear() in Blender 3.2)
|
||||
while len(action.fcurves) > 0:
|
||||
@ -208,7 +223,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
||||
|
||||
# Write the keyframes out.
|
||||
fcurve_data = numpy.zeros(2 * sequence.frame_count, dtype=float)
|
||||
fcurve_data[0::2] = range(sequence.frame_count)
|
||||
fcurve_data[0::2] = [x * keyframe_time_dilation for x in range(sequence.frame_count)]
|
||||
for bone_index, import_bone in enumerate(import_bones):
|
||||
if import_bone is None:
|
||||
continue
|
||||
@ -216,6 +231,8 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
||||
fcurve_data[1::2] = sequence_data_matrix[:, bone_index, fcurve_index]
|
||||
fcurve.keyframe_points.add(sequence.frame_count)
|
||||
fcurve.keyframe_points.foreach_set('co', fcurve_data)
|
||||
for fcurve_keyframe in fcurve.keyframe_points:
|
||||
fcurve_keyframe.interpolation = 'LINEAR'
|
||||
|
||||
if options.should_convert_to_samples:
|
||||
# Bake the curve to samples.
|
||||
@ -224,7 +241,7 @@ def import_psa(context: Context, psa_reader: PsaReader, armature_object: Object,
|
||||
|
||||
# Write meta-data.
|
||||
if options.should_write_metadata:
|
||||
action['psa_sequence_fps'] = sequence.fps
|
||||
action.psa_export.fps = target_fps
|
||||
|
||||
action.use_fake_user = options.should_use_fake_user
|
||||
|
||||
|
@ -21,6 +21,7 @@ class PSX_PG_bone_group_list_item(PropertyGroup):
|
||||
class PSX_PG_action_export(PropertyGroup):
|
||||
compression_ratio: FloatProperty(name='Compression Ratio', default=1.0, min=0.0, max=1.0, subtype='FACTOR', description='The key sampling ratio of the exported sequence.\n\nA compression ratio of 1.0 will export all frames, while a compression ratio of 0.5 will export half of the frames')
|
||||
key_quota: IntProperty(name='Key Quota', default=0, min=1, description='The minimum number of frames to be exported')
|
||||
fps: FloatProperty(name='FPS', default=30.0, min=0.0, description='The frame rate of the exported sequence')
|
||||
|
||||
|
||||
class PSX_PT_action(Panel):
|
||||
@ -38,8 +39,12 @@ class PSX_PT_action(Panel):
|
||||
def draw(self, context: 'Context'):
|
||||
action = context.active_action
|
||||
layout = self.layout
|
||||
layout.prop(action.psa_export, 'compression_ratio')
|
||||
layout.prop(action.psa_export, 'key_quota')
|
||||
flow = layout.grid_flow(columns=1)
|
||||
flow.use_property_split = True
|
||||
flow.use_property_decorate = False
|
||||
flow.prop(action.psa_export, 'compression_ratio')
|
||||
flow.prop(action.psa_export, 'key_quota')
|
||||
flow.prop(action.psa_export, 'fps')
|
||||
|
||||
|
||||
classes = (
|
||||
|
Loading…
x
Reference in New Issue
Block a user