mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2024-12-02 18:27:15 +01:00
227 lines
8.8 KiB
Python
227 lines
8.8 KiB
Python
import re
|
|
import sys
|
|
from fnmatch import fnmatch
|
|
from typing import List, Optional
|
|
|
|
from bpy.props import BoolProperty, PointerProperty, EnumProperty, FloatProperty, CollectionProperty, IntProperty, \
|
|
StringProperty
|
|
from bpy.types import PropertyGroup, Object, Action, AnimData, Context
|
|
|
|
from ...shared.types import PSX_PG_bone_collection_list_item
|
|
|
|
|
|
def psa_export_property_group_animation_data_override_poll(_context, obj):
|
|
return obj.animation_data is not None
|
|
|
|
|
|
empty_set = set()
|
|
|
|
|
|
class PSA_PG_export_action_list_item(PropertyGroup):
|
|
action: PointerProperty(type=Action)
|
|
name: StringProperty()
|
|
is_selected: BoolProperty(default=True)
|
|
frame_start: IntProperty(options={'HIDDEN'})
|
|
frame_end: IntProperty(options={'HIDDEN'})
|
|
is_pose_marker: BoolProperty(options={'HIDDEN'})
|
|
|
|
|
|
class PSA_PG_export_timeline_markers(PropertyGroup): # TODO: rename this to singular
|
|
marker_index: IntProperty()
|
|
name: StringProperty()
|
|
is_selected: BoolProperty(default=True)
|
|
frame_start: IntProperty(options={'HIDDEN'})
|
|
frame_end: IntProperty(options={'HIDDEN'})
|
|
|
|
|
|
class PSA_PG_export_nla_strip_list_item(PropertyGroup):
|
|
name: StringProperty()
|
|
action: PointerProperty(type=Action)
|
|
frame_start: FloatProperty()
|
|
frame_end: FloatProperty()
|
|
is_selected: BoolProperty(default=True)
|
|
|
|
|
|
def nla_track_update_cb(self: 'PSA_PG_export', context: Context) -> None:
|
|
self.nla_strip_list.clear()
|
|
match = re.match(r'^(\d+).+$', self.nla_track)
|
|
self.nla_track_index = int(match.group(1)) if match else -1
|
|
if self.nla_track_index >= 0:
|
|
animation_data = get_animation_data(self, context)
|
|
if animation_data is None:
|
|
return
|
|
nla_track = animation_data.nla_tracks[self.nla_track_index]
|
|
for nla_strip in nla_track.strips:
|
|
strip: PSA_PG_export_nla_strip_list_item = self.nla_strip_list.add()
|
|
strip.action = nla_strip.action
|
|
strip.name = nla_strip.name
|
|
strip.frame_start = nla_strip.frame_start
|
|
strip.frame_end = nla_strip.frame_end
|
|
|
|
|
|
def get_animation_data(pg: 'PSA_PG_export', context: Context) -> Optional[AnimData]:
|
|
animation_data_object = context.object
|
|
if pg.should_override_animation_data:
|
|
animation_data_object = pg.animation_data_override
|
|
return animation_data_object.animation_data if animation_data_object else None
|
|
|
|
|
|
def nla_track_search_cb(self, context: Context, edit_text: str):
|
|
pg = getattr(context.scene, 'psa_export')
|
|
animation_data = get_animation_data(pg, context)
|
|
if animation_data is None:
|
|
return
|
|
for index, nla_track in enumerate(animation_data.nla_tracks):
|
|
yield f'{index} - {nla_track.name}'
|
|
|
|
|
|
def animation_data_override_update_cb(self: 'PSA_PG_export', context: Context):
|
|
# Reset NLA track selection
|
|
self.nla_track = ''
|
|
|
|
|
|
class PSA_PG_export(PropertyGroup):
|
|
root_motion: BoolProperty(
|
|
name='Root Motion',
|
|
options=empty_set,
|
|
default=False,
|
|
description='When enabled, the root bone will be transformed as it appears in the scene.\n\n'
|
|
'You might want to disable this if you are exporting an animation for an armature that is '
|
|
'attached to another object, such as a weapon or a shield',
|
|
)
|
|
should_override_animation_data: BoolProperty(
|
|
name='Override Animation Data',
|
|
options=empty_set,
|
|
default=False,
|
|
description='Use the animation data from a different object instead of the selected object',
|
|
update=animation_data_override_update_cb,
|
|
)
|
|
animation_data_override: PointerProperty(
|
|
type=Object,
|
|
update=animation_data_override_update_cb,
|
|
poll=psa_export_property_group_animation_data_override_poll
|
|
)
|
|
sequence_source: EnumProperty(
|
|
name='Source',
|
|
options=empty_set,
|
|
description='',
|
|
items=(
|
|
('ACTIONS', 'Actions', 'Sequences will be exported using actions', 'ACTION', 0),
|
|
('TIMELINE_MARKERS', 'Timeline Markers', 'Sequences are delineated by scene timeline markers', 'MARKER_HLT', 1),
|
|
('NLA_TRACK_STRIPS', 'NLA Track Strips', 'Sequences are delineated by the start & end times of strips on the selected NLA track', 'NLA', 2)
|
|
)
|
|
)
|
|
nla_track: StringProperty(
|
|
name='NLA Track',
|
|
options=empty_set,
|
|
description='',
|
|
search=nla_track_search_cb,
|
|
update=nla_track_update_cb
|
|
)
|
|
nla_track_index: IntProperty(name='NLA Track Index', default=-1)
|
|
fps_source: EnumProperty(
|
|
name='FPS Source',
|
|
options=empty_set,
|
|
description='',
|
|
items=(
|
|
('SCENE', 'Scene', '', 'SCENE_DATA', 0),
|
|
('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)
|
|
)
|
|
)
|
|
fps_custom: FloatProperty(default=30.0, min=sys.float_info.epsilon, soft_min=1.0, options=empty_set, step=100,
|
|
soft_max=60.0)
|
|
action_list: CollectionProperty(type=PSA_PG_export_action_list_item)
|
|
action_list_index: IntProperty(default=0)
|
|
marker_list: CollectionProperty(type=PSA_PG_export_timeline_markers)
|
|
marker_list_index: IntProperty(default=0)
|
|
nla_strip_list: CollectionProperty(type=PSA_PG_export_nla_strip_list_item)
|
|
nla_strip_list_index: IntProperty(default=0)
|
|
bone_filter_mode: EnumProperty(
|
|
name='Bone Filter',
|
|
options=empty_set,
|
|
description='',
|
|
items=(
|
|
('ALL', 'All', 'All bones will be exported.'),
|
|
('BONE_COLLECTIONS', 'Bone Collections', 'Only bones belonging to the selected bone collections and their '
|
|
'ancestors will be exported.'),
|
|
)
|
|
)
|
|
bone_collection_list: CollectionProperty(type=PSX_PG_bone_collection_list_item)
|
|
bone_collection_list_index: IntProperty(default=0, name='', description='')
|
|
should_enforce_bone_name_restrictions: BoolProperty(
|
|
default=False,
|
|
name='Enforce Bone Name Restrictions',
|
|
description='Bone names restrictions will be enforced. Note that bone names without properly formatted names '
|
|
'may not be able to be referenced in-engine'
|
|
)
|
|
sequence_name_prefix: StringProperty(name='Prefix', options=empty_set)
|
|
sequence_name_suffix: StringProperty(name='Suffix', options=empty_set)
|
|
sequence_filter_name: StringProperty(
|
|
default='',
|
|
name='Filter by Name',
|
|
options={'TEXTEDIT_UPDATE'},
|
|
description='Only show items matching this name (use \'*\' as wildcard)')
|
|
sequence_use_filter_invert: BoolProperty(
|
|
default=False,
|
|
name='Invert',
|
|
options=empty_set,
|
|
description='Invert filtering (show hidden items, and vice versa)')
|
|
sequence_filter_asset: BoolProperty(
|
|
default=False,
|
|
name='Show assets',
|
|
options=empty_set,
|
|
description='Show actions that belong to an asset library')
|
|
sequence_filter_pose_marker: BoolProperty(
|
|
default=True,
|
|
name='Show pose markers',
|
|
options=empty_set)
|
|
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=empty_set)
|
|
sequence_filter_reversed: BoolProperty(
|
|
default=True,
|
|
options=empty_set,
|
|
name='Show Reversed',
|
|
description='Show reversed sequences'
|
|
)
|
|
|
|
|
|
def filter_sequences(pg: PSA_PG_export, sequences) -> List[int]:
|
|
bitflag_filter_item = 1 << 30
|
|
flt_flags = [bitflag_filter_item] * len(sequences)
|
|
|
|
if pg.sequence_filter_name:
|
|
# Filter name is non-empty.
|
|
for i, sequence in enumerate(sequences):
|
|
if not fnmatch(sequence.name, f'*{pg.sequence_filter_name}*'):
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
|
|
# Invert filter flags for all items.
|
|
if pg.sequence_use_filter_invert:
|
|
for i, sequence in enumerate(sequences):
|
|
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 is not None and sequence.action.asset_data is not None:
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
|
|
if not pg.sequence_filter_pose_marker:
|
|
for i, sequence in enumerate(sequences):
|
|
if hasattr(sequence, 'is_pose_marker') and sequence.is_pose_marker:
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
|
|
if not pg.sequence_filter_reversed:
|
|
for i, sequence in enumerate(sequences):
|
|
if sequence.frame_start > sequence.frame_end:
|
|
flt_flags[i] &= ~bitflag_filter_item
|
|
|
|
return flt_flags
|
|
|
|
|
|
classes = (
|
|
PSA_PG_export_action_list_item,
|
|
PSA_PG_export_timeline_markers,
|
|
PSA_PG_export_nla_strip_list_item,
|
|
PSA_PG_export,
|
|
)
|