* 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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user