mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2024-12-01 01:37:19 +01:00
157 lines
7.2 KiB
Python
157 lines
7.2 KiB
Python
import re
|
|
from fnmatch import fnmatch
|
|
from typing import List
|
|
|
|
from bpy.props import StringProperty, BoolProperty, CollectionProperty, IntProperty, PointerProperty, EnumProperty, \
|
|
FloatProperty
|
|
from bpy.types import PropertyGroup, Text
|
|
|
|
empty_set = set()
|
|
|
|
|
|
class PSA_PG_import_action_list_item(PropertyGroup):
|
|
action_name: StringProperty(options=empty_set)
|
|
is_selected: BoolProperty(default=True, options=empty_set)
|
|
|
|
|
|
class PSA_PG_bone(PropertyGroup):
|
|
bone_name: StringProperty(options=empty_set)
|
|
|
|
|
|
class PSA_PG_data(PropertyGroup):
|
|
bones: CollectionProperty(type=PSA_PG_bone)
|
|
sequence_count: IntProperty(default=0)
|
|
|
|
|
|
class PSA_PG_import(PropertyGroup):
|
|
psa_error: StringProperty(default='')
|
|
psa: PointerProperty(type=PSA_PG_data)
|
|
sequence_list: CollectionProperty(type=PSA_PG_import_action_list_item)
|
|
sequence_list_index: IntProperty(name='', default=0)
|
|
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=empty_set)
|
|
should_use_config_file: BoolProperty(default=True, name='Use Config File',
|
|
description='Use the .config file that is sometimes generated when the PSA '
|
|
'file is exported from UEViewer. This file contains '
|
|
'options that can be used to filter out certain bones tracks '
|
|
'from the imported actions',
|
|
options=empty_set)
|
|
should_stash: BoolProperty(default=False, name='Stash',
|
|
description='Stash each imported action as a strip on a new non-contributing NLA track',
|
|
options=empty_set)
|
|
should_use_action_name_prefix: BoolProperty(default=False, name='Prefix Action Name', options=empty_set)
|
|
action_name_prefix: StringProperty(default='', name='Prefix', options=empty_set)
|
|
should_overwrite: BoolProperty(default=False, name='Overwrite', 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')
|
|
should_write_keyframes: BoolProperty(default=True, name='Keyframes', options=empty_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)')
|
|
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
|
sequence_filter_is_selected: BoolProperty(default=False, options=empty_set, name='Only Show Selected',
|
|
description='Only show selected sequences')
|
|
sequence_use_filter_invert: BoolProperty(default=False, options=empty_set)
|
|
sequence_use_filter_regex: BoolProperty(default=False, name='Regular Expression',
|
|
description='Filter using regular expressions', options=empty_set)
|
|
select_text: PointerProperty(type=Text)
|
|
should_convert_to_samples: BoolProperty(
|
|
default=False,
|
|
name='Convert to Samples',
|
|
description='Convert keyframes to read-only samples. '
|
|
'Recommended if you do not plan on editing the actions directly'
|
|
)
|
|
bone_mapping_mode: EnumProperty(
|
|
name='Bone Mapping',
|
|
options=empty_set,
|
|
description='The method by which bones from the incoming PSA file are mapped to the armature',
|
|
items=(
|
|
('EXACT', 'Exact', 'Bone names must match exactly.', 'EXACT', 0),
|
|
('CASE_INSENSITIVE', 'Case Insensitive', 'Bones names must match, ignoring case (e.g., the bone PSA bone '
|
|
'\'root\' can be mapped to the armature bone \'Root\')', 'CASE_INSENSITIVE', 1),
|
|
),
|
|
default='CASE_INSENSITIVE'
|
|
)
|
|
fps_source: EnumProperty(name='FPS Source', items=(
|
|
('SEQUENCE', 'Sequence', 'The sequence frame rate matches the original frame rate', 'ACTION', 0),
|
|
('SCENE', 'Scene', 'The sequence is resampled to the frame rate of the scene', 'SCENE_DATA', 1),
|
|
('CUSTOM', 'Custom', 'The sequence is resampled to a custom frame rate', 2),
|
|
))
|
|
fps_custom: FloatProperty(
|
|
default=30.0,
|
|
name='Custom FPS',
|
|
description='The frame rate to which the imported sequences will be resampled to',
|
|
options=empty_set,
|
|
min=1.0,
|
|
soft_min=1.0,
|
|
soft_max=60.0,
|
|
step=100,
|
|
)
|
|
compression_ratio_source: EnumProperty(name='Compression Ratio Source', items=(
|
|
('ACTION', 'Action', 'The compression ratio is sourced from the action metadata', 'ACTION', 0),
|
|
('CUSTOM', 'Custom', 'The compression ratio is set to a custom value', 1),
|
|
))
|
|
compression_ratio_custom: FloatProperty(
|
|
default=1.0,
|
|
name='Custom Compression Ratio',
|
|
description='The compression ratio to apply to the imported sequences',
|
|
options=empty_set,
|
|
min=0.0,
|
|
soft_min=0.0,
|
|
soft_max=1.0,
|
|
step=0.0625,
|
|
)
|
|
|
|
|
|
def filter_sequences(pg: PSA_PG_import, sequences) -> 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.
|
|
if pg.sequence_use_filter_regex:
|
|
# Use regular expression. If regex pattern doesn't compile, just ignore it.
|
|
try:
|
|
regex = re.compile(pg.sequence_filter_name)
|
|
for i, sequence in enumerate(sequences):
|
|
if not regex.match(sequence.action_name):
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
except re.error:
|
|
pass
|
|
else:
|
|
# User regular text matching.
|
|
for i, sequence in enumerate(sequences):
|
|
if not fnmatch(sequence.action_name, f'*{pg.sequence_filter_name}*'):
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
|
|
if pg.sequence_filter_is_selected:
|
|
for i, sequence in enumerate(sequences):
|
|
if not sequence.is_selected:
|
|
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: PSA_PG_import, sequences) -> List[PSA_PG_import_action_list_item]:
|
|
bitflag_filter_item = 1 << 30
|
|
visible_sequences = []
|
|
for i, flag in enumerate(filter_sequences(pg, sequences)):
|
|
if bool(flag & bitflag_filter_item):
|
|
visible_sequences.append(sequences[i])
|
|
return visible_sequences
|
|
|
|
|
|
classes = (
|
|
PSA_PG_import_action_list_item,
|
|
PSA_PG_bone,
|
|
PSA_PG_data,
|
|
PSA_PG_import,
|
|
)
|