Lots of interface tweaks
This commit is contained in:
@@ -55,6 +55,10 @@ class Vector3(Structure):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(tuple(self))
|
return repr(tuple(self))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zero(cls):
|
||||||
|
return Vector3(0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
class Quaternion(Structure):
|
class Quaternion(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
@@ -73,6 +77,10 @@ class Quaternion(Structure):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(tuple(self))
|
return repr(tuple(self))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def identity(cls):
|
||||||
|
return Quaternion(0, 0, 0, 1)
|
||||||
|
|
||||||
|
|
||||||
class Section(Structure):
|
class Section(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
from bpy.types import NlaStrip
|
from bpy.types import NlaStrip
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __enter__(self):
|
||||||
|
self.start = datetime.datetime.now()
|
||||||
|
self.interval = None
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.end = datetime.datetime.now()
|
||||||
|
self.interval = self.end - self.start
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self):
|
||||||
|
if self.interval is not None:
|
||||||
|
return self.interval
|
||||||
|
else:
|
||||||
|
return datetime.datetime.now() - self.start
|
||||||
|
|
||||||
|
|
||||||
def rgb_to_srgb(c):
|
def rgb_to_srgb(c):
|
||||||
|
|||||||
@@ -16,17 +16,25 @@ class PsaBuilderOptions(object):
|
|||||||
self.sequence_name_suffix = ''
|
self.sequence_name_suffix = ''
|
||||||
|
|
||||||
|
|
||||||
|
class PsaBuilderPerformance:
|
||||||
|
def __init__(self):
|
||||||
|
self.frame_set_duration = datetime.timedelta()
|
||||||
|
self.key_build_duration = datetime.timedelta()
|
||||||
|
self.key_add_duration = datetime.timedelta()
|
||||||
|
|
||||||
|
|
||||||
class PsaBuilder(object):
|
class PsaBuilder(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def build(self, context, options: PsaBuilderOptions) -> Psa:
|
def build(self, context, options: PsaBuilderOptions) -> Psa:
|
||||||
object = context.view_layer.objects.active
|
performance = PsaBuilderPerformance()
|
||||||
|
active_object = context.view_layer.objects.active
|
||||||
|
|
||||||
if object.type != 'ARMATURE':
|
if active_object.type != 'ARMATURE':
|
||||||
raise RuntimeError('Selected object must be an Armature')
|
raise RuntimeError('Selected object must be an Armature')
|
||||||
|
|
||||||
armature = object
|
armature = active_object
|
||||||
|
|
||||||
if armature.animation_data is None:
|
if armature.animation_data is None:
|
||||||
raise RuntimeError('No animation data for armature')
|
raise RuntimeError('No animation data for armature')
|
||||||
@@ -99,13 +107,17 @@ class PsaBuilder(object):
|
|||||||
psa.bones.append(psa_bone)
|
psa.bones.append(psa_bone)
|
||||||
|
|
||||||
# Populate the export sequence list.
|
# Populate the export sequence list.
|
||||||
class ExportSequence:
|
class NlaState:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.name = ''
|
|
||||||
self.frame_min = 0
|
self.frame_min = 0
|
||||||
self.frame_max = 0
|
self.frame_max = 0
|
||||||
self.action = None
|
self.action = None
|
||||||
|
|
||||||
|
class ExportSequence:
|
||||||
|
def __init__(self):
|
||||||
|
self.name = ''
|
||||||
|
self.nla_state = NlaState()
|
||||||
|
|
||||||
export_sequences = []
|
export_sequences = []
|
||||||
|
|
||||||
if options.sequence_source == 'ACTIONS':
|
if options.sequence_source == 'ACTIONS':
|
||||||
@@ -113,9 +125,11 @@ class PsaBuilder(object):
|
|||||||
if len(action.fcurves) == 0:
|
if len(action.fcurves) == 0:
|
||||||
continue
|
continue
|
||||||
export_sequence = ExportSequence()
|
export_sequence = ExportSequence()
|
||||||
export_sequence.action = action
|
export_sequence.nla_state.action = action
|
||||||
export_sequence.name = get_psa_sequence_name(action, options.should_use_original_sequence_names)
|
export_sequence.name = get_psa_sequence_name(action, options.should_use_original_sequence_names)
|
||||||
export_sequence.frame_min, export_sequence.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_max = frame_max
|
||||||
export_sequences.append(export_sequence)
|
export_sequences.append(export_sequence)
|
||||||
pass
|
pass
|
||||||
elif options.sequence_source == 'TIMELINE_MARKERS':
|
elif options.sequence_source == 'TIMELINE_MARKERS':
|
||||||
@@ -123,10 +137,10 @@ class PsaBuilder(object):
|
|||||||
|
|
||||||
for name, (frame_min, frame_max) in sequence_frame_ranges.items():
|
for name, (frame_min, frame_max) in sequence_frame_ranges.items():
|
||||||
export_sequence = ExportSequence()
|
export_sequence = ExportSequence()
|
||||||
export_sequence.action = None
|
|
||||||
export_sequence.name = name
|
export_sequence.name = name
|
||||||
export_sequence.frame_min = frame_min
|
export_sequence.nla_state.action = None
|
||||||
export_sequence.frame_max = frame_max
|
export_sequence.nla_state.frame_min = frame_min
|
||||||
|
export_sequence.nla_state.frame_max = frame_max
|
||||||
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}')
|
||||||
@@ -140,13 +154,13 @@ class PsaBuilder(object):
|
|||||||
frame_start_index = 0
|
frame_start_index = 0
|
||||||
|
|
||||||
for export_sequence in export_sequences:
|
for export_sequence in export_sequences:
|
||||||
armature.animation_data.action = export_sequence.action
|
armature.animation_data.action = export_sequence.nla_state.action
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
|
|
||||||
psa_sequence = Psa.Sequence()
|
psa_sequence = Psa.Sequence()
|
||||||
|
|
||||||
frame_min = export_sequence.frame_min
|
frame_min = export_sequence.nla_state.frame_min
|
||||||
frame_max = export_sequence.frame_max
|
frame_max = export_sequence.nla_state.frame_max
|
||||||
frame_count = frame_max - frame_min + 1
|
frame_count = frame_max - frame_min + 1
|
||||||
|
|
||||||
psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252')
|
psa_sequence.name = bytes(export_sequence.name, encoding='windows-1252')
|
||||||
@@ -157,34 +171,40 @@ class PsaBuilder(object):
|
|||||||
frame_count = frame_max - frame_min + 1
|
frame_count = frame_max - frame_min + 1
|
||||||
|
|
||||||
for frame in range(frame_count):
|
for frame in range(frame_count):
|
||||||
context.scene.frame_set(frame_min + frame)
|
with Timer() as t:
|
||||||
|
context.scene.frame_set(frame_min + frame)
|
||||||
|
performance.frame_set_duration += t.duration
|
||||||
|
|
||||||
for pose_bone in pose_bones:
|
for pose_bone in pose_bones:
|
||||||
key = Psa.Key()
|
with Timer() as t:
|
||||||
pose_bone_matrix = pose_bone.matrix
|
key = Psa.Key()
|
||||||
|
pose_bone_matrix = pose_bone.matrix
|
||||||
|
|
||||||
if pose_bone.parent is not None:
|
if pose_bone.parent is not None:
|
||||||
pose_bone_parent_matrix = pose_bone.parent.matrix
|
pose_bone_parent_matrix = pose_bone.parent.matrix
|
||||||
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
pose_bone_matrix = pose_bone_parent_matrix.inverted() @ pose_bone_matrix
|
||||||
|
|
||||||
location = pose_bone_matrix.to_translation()
|
location = pose_bone_matrix.to_translation()
|
||||||
rotation = pose_bone_matrix.to_quaternion().normalized()
|
rotation = pose_bone_matrix.to_quaternion().normalized()
|
||||||
|
|
||||||
if pose_bone.parent is not None:
|
if pose_bone.parent is not None:
|
||||||
rotation.x = -rotation.x
|
rotation.x = -rotation.x
|
||||||
rotation.y = -rotation.y
|
rotation.y = -rotation.y
|
||||||
rotation.z = -rotation.z
|
rotation.z = -rotation.z
|
||||||
|
|
||||||
key.location.x = location.x
|
key.location.x = location.x
|
||||||
key.location.y = location.y
|
key.location.y = location.y
|
||||||
key.location.z = location.z
|
key.location.z = location.z
|
||||||
key.rotation.x = rotation.x
|
key.rotation.x = rotation.x
|
||||||
key.rotation.y = rotation.y
|
key.rotation.y = rotation.y
|
||||||
key.rotation.z = rotation.z
|
key.rotation.z = rotation.z
|
||||||
key.rotation.w = rotation.w
|
key.rotation.w = rotation.w
|
||||||
key.time = 1.0 / psa_sequence.fps
|
key.time = 1.0 / psa_sequence.fps
|
||||||
|
performance.key_build_duration += t.duration
|
||||||
|
|
||||||
psa.keys.append(key)
|
with Timer() as t:
|
||||||
|
psa.keys.append(key)
|
||||||
|
performance.key_add_duration += t.duration
|
||||||
|
|
||||||
psa_sequence.bone_count = len(pose_bones)
|
psa_sequence.bone_count = len(pose_bones)
|
||||||
psa_sequence.track_time = frame_count
|
psa_sequence.track_time = frame_count
|
||||||
@@ -193,6 +213,10 @@ 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:
|
||||||
@@ -214,8 +238,8 @@ class PsaBuilder(object):
|
|||||||
frame_max = sorted_timeline_markers[next_marker_index].frame
|
frame_max = sorted_timeline_markers[next_marker_index].frame
|
||||||
if options.should_trim_timeline_marker_sequences:
|
if options.should_trim_timeline_marker_sequences:
|
||||||
nla_strips = get_nla_strips_in_timeframe(object, marker.frame, frame_max)
|
nla_strips = get_nla_strips_in_timeframe(object, marker.frame, frame_max)
|
||||||
frame_max = min(frame_max, max(map(lambda x: x.frame_end, nla_strips)))
|
frame_max = min(frame_max, max(map(lambda nla_strip: nla_strip.frame_end, nla_strips)))
|
||||||
frame_min = max(frame_min, min(map(lambda x: x.frame_start, nla_strips)))
|
frame_min = max(frame_min, min(map(lambda nla_strip: nla_strip.frame_start, nla_strips)))
|
||||||
else:
|
else:
|
||||||
# There is no next marker.
|
# There is no next marker.
|
||||||
# Find the final frame of all the NLA strips and use that as the last frame of this sequence.
|
# Find the final frame of all the NLA strips and use that as the last frame of this sequence.
|
||||||
|
|||||||
@@ -38,23 +38,15 @@ class PsaExporter(object):
|
|||||||
|
|
||||||
class PsaExportActionListItem(PropertyGroup):
|
class PsaExportActionListItem(PropertyGroup):
|
||||||
action: PointerProperty(type=Action)
|
action: PointerProperty(type=Action)
|
||||||
action_name: StringProperty()
|
name: StringProperty()
|
||||||
is_selected: BoolProperty(default=False)
|
is_selected: BoolProperty(default=False)
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.action.name
|
|
||||||
|
|
||||||
|
|
||||||
class PsaExportTimelineMarkerListItem(PropertyGroup):
|
class PsaExportTimelineMarkerListItem(PropertyGroup):
|
||||||
marker_index: IntProperty()
|
marker_index: IntProperty()
|
||||||
marker_name: StringProperty()
|
name: StringProperty()
|
||||||
is_selected: BoolProperty(default=True)
|
is_selected: BoolProperty(default=True)
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.marker_name
|
|
||||||
|
|
||||||
|
|
||||||
def update_action_names(context):
|
def update_action_names(context):
|
||||||
pg = context.scene.psa_export
|
pg = context.scene.psa_export
|
||||||
@@ -110,6 +102,10 @@ class PsaExportPropertyGroup(PropertyGroup):
|
|||||||
)
|
)
|
||||||
sequence_name_prefix: StringProperty(name='Prefix', options=set())
|
sequence_name_prefix: StringProperty(name='Prefix', options=set())
|
||||||
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
sequence_name_suffix: StringProperty(name='Suffix', options=set())
|
||||||
|
sequence_filter_name: StringProperty(default='', options={'TEXTEDIT_UPDATE'})
|
||||||
|
sequence_use_filter_invert: BoolProperty(default=False, options=set())
|
||||||
|
sequence_filter_asset: BoolProperty(default=False, name='Show assets', description='Show actions that belong to an asset library', options=set())
|
||||||
|
sequence_use_filter_sort_reverse: BoolProperty(default=True, options=set())
|
||||||
|
|
||||||
|
|
||||||
def is_bone_filter_mode_item_available(context, identifier):
|
def is_bone_filter_mode_item_available(context, identifier):
|
||||||
@@ -152,6 +148,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
# ACTIONS
|
# ACTIONS
|
||||||
if pg.sequence_source == 'ACTIONS':
|
if pg.sequence_source == 'ACTIONS':
|
||||||
rows = max(3, min(len(pg.action_list), 10))
|
rows = max(3, min(len(pg.action_list), 10))
|
||||||
|
|
||||||
layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
layout.template_list('PSA_UL_ExportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
||||||
|
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
@@ -174,7 +171,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
# Determine if there is going to be a naming conflict and display an error, if so.
|
# Determine if there is going to be a naming conflict and display an error, if so.
|
||||||
selected_items = [x for x in pg.action_list if x.is_selected]
|
selected_items = [x for x in pg.action_list if x.is_selected]
|
||||||
action_names = [x.action_name for x in selected_items]
|
action_names = [x.name for x in selected_items]
|
||||||
action_name_counts = Counter(action_names)
|
action_name_counts = Counter(action_names)
|
||||||
for action_name, count in action_name_counts.items():
|
for action_name, count in action_name_counts.items():
|
||||||
if count > 1:
|
if count > 1:
|
||||||
@@ -195,6 +192,9 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
rows = max(3, min(len(pg.bone_group_list), 10))
|
rows = max(3, min(len(pg.bone_group_list), 10))
|
||||||
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows)
|
layout.template_list('PSX_UL_BoneGroupList', '', pg, 'bone_group_list', pg, 'bone_group_list_index', rows=rows)
|
||||||
|
|
||||||
|
def should_action_be_selected_by_default(self, action):
|
||||||
|
return action is not None and action.asset_data is None
|
||||||
|
|
||||||
def is_action_for_armature(self, action):
|
def is_action_for_armature(self, action):
|
||||||
if len(action.fcurves) == 0:
|
if len(action.fcurves) == 0:
|
||||||
return False
|
return False
|
||||||
@@ -228,8 +228,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
continue
|
continue
|
||||||
item = pg.action_list.add()
|
item = pg.action_list.add()
|
||||||
item.action = action
|
item.action = action
|
||||||
item.action_name = action.name
|
item.name = action.name
|
||||||
item.is_selected = True
|
item.is_selected = self.should_action_be_selected_by_default(action)
|
||||||
|
|
||||||
update_action_names(context)
|
update_action_names(context)
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
pg.marker_list.clear()
|
pg.marker_list.clear()
|
||||||
for marker in context.scene.timeline_markers:
|
for marker in context.scene.timeline_markers:
|
||||||
item = pg.marker_list.add()
|
item = pg.marker_list.add()
|
||||||
item.marker_name = marker.name
|
item.name = marker.name
|
||||||
|
|
||||||
if len(pg.action_list) == 0 and len(pg.marker_names) == 0:
|
if len(pg.action_list) == 0 and len(pg.marker_names) == 0:
|
||||||
# If there are no actions at all, we have nothing to export, so just cancel the operation.
|
# If there are no actions at all, we have nothing to export, so just cancel the operation.
|
||||||
@@ -255,7 +255,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
pg = context.scene.psa_export
|
pg = context.scene.psa_export
|
||||||
|
|
||||||
actions = [x.action for x in pg.action_list if x.is_selected]
|
actions = [x.action for x in pg.action_list if x.is_selected]
|
||||||
marker_names = [x.marker_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.sequence_source = pg.sequence_source
|
options.sequence_source = pg.sequence_source
|
||||||
@@ -283,39 +283,75 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
|
|
||||||
class PSA_UL_ExportTimelineMarkerList(UIList):
|
class PSA_UL_ExportTimelineMarkerList(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):
|
||||||
layout.prop(item, 'is_selected', icon_only=True, text=item.marker_name)
|
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
||||||
|
|
||||||
def filter_items(self, context, data, property):
|
def filter_items(self, context, data, property):
|
||||||
actions = getattr(data, property)
|
pg = context.scene.psa_export
|
||||||
flt_flags = []
|
sequences = getattr(data, property)
|
||||||
flt_neworder = []
|
flt_flags = filter_sequences(pg, sequences)
|
||||||
if self.filter_name:
|
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(sequences, 'name')
|
||||||
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
|
|
||||||
self.filter_name,
|
|
||||||
self.bitflag_filter_item,
|
|
||||||
actions,
|
|
||||||
'marker_name',
|
|
||||||
reverse=self.use_filter_invert
|
|
||||||
)
|
|
||||||
return flt_flags, flt_neworder
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
|
||||||
|
def filter_sequences(pg: PsaExportPropertyGroup, 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.name, f'*{pg.sequence_filter_name}*'):
|
||||||
|
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.asset_data is not None:
|
||||||
|
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: PsaExportPropertyGroup, sequences: bpy.types.bpy_prop_collection) -> List[PsaExportActionListItem]:
|
||||||
|
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_ExportActionList(UIList):
|
class PSA_UL_ExportActionList(UIList):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PSA_UL_ExportActionList, self).__init__()
|
||||||
|
# Show the filtering options by default.
|
||||||
|
self.use_filter_show = True
|
||||||
|
|
||||||
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):
|
||||||
layout.prop(item, 'is_selected', icon_only=True, text=item.action_name)
|
layout.prop(item, 'is_selected', icon_only=True, text=item.name)
|
||||||
|
if item.action.asset_data is not None:
|
||||||
|
layout.label(text='', icon='ASSET_MANAGER')
|
||||||
|
|
||||||
|
def draw_filter(self, context, layout):
|
||||||
|
pg = context.scene.psa_export
|
||||||
|
row = layout.row()
|
||||||
|
subrow = row.row(align=True)
|
||||||
|
subrow.prop(pg, 'sequence_filter_name', text="")
|
||||||
|
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
||||||
|
subrow = row.row(align=True)
|
||||||
|
subrow.prop(pg, 'sequence_filter_asset', icon_only=True, icon='ASSET_MANAGER')
|
||||||
|
# subrow.prop(pg, 'sequence_use_filter_sort_reverse', text='', icon='SORT_ASC')
|
||||||
|
|
||||||
def filter_items(self, context, data, property):
|
def filter_items(self, context, data, property):
|
||||||
|
pg = context.scene.psa_export
|
||||||
actions = getattr(data, property)
|
actions = getattr(data, property)
|
||||||
flt_flags = []
|
flt_flags = filter_sequences(pg, actions)
|
||||||
flt_neworder = []
|
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, 'name')
|
||||||
if self.filter_name:
|
|
||||||
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
|
|
||||||
self.filter_name,
|
|
||||||
self.bitflag_filter_item,
|
|
||||||
actions,
|
|
||||||
'action_name',
|
|
||||||
reverse=self.use_filter_invert
|
|
||||||
)
|
|
||||||
return flt_flags, flt_neworder
|
return flt_flags, flt_neworder
|
||||||
|
|
||||||
|
|
||||||
@@ -336,14 +372,17 @@ class PsaExportActionsSelectAll(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
pg = context.scene.psa_export
|
||||||
item_list = cls.get_item_list(context)
|
item_list = cls.get_item_list(context)
|
||||||
has_unselected_items = any(map(lambda item: not item.is_selected, item_list))
|
visible_sequences = get_visible_sequences(pg, item_list)
|
||||||
return len(item_list) > 0 and has_unselected_items
|
has_unselected_sequences = any(map(lambda item: not item.is_selected, visible_sequences))
|
||||||
|
return has_unselected_sequences
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
item_list = self.get_item_list(context)
|
pg = context.scene.psa_export
|
||||||
for item in item_list:
|
sequences = self.get_item_list(context)
|
||||||
item.is_selected = True
|
for sequence in get_visible_sequences(pg, sequences):
|
||||||
|
sequence.is_selected = True
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@@ -369,9 +408,10 @@ class PsaExportActionsDeselectAll(Operator):
|
|||||||
return len(item_list) > 0 and has_selected_items
|
return len(item_list) > 0 and has_selected_items
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
pg = context.scene.psa_export
|
||||||
item_list = self.get_item_list(context)
|
item_list = self.get_item_list(context)
|
||||||
for item in item_list:
|
for sequence in get_visible_sequences(pg, item_list):
|
||||||
item.is_selected = False
|
sequence.is_selected = False
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ def load_psa_file(context):
|
|||||||
pg = context.scene.psa_import
|
pg = context.scene.psa_import
|
||||||
pg.sequence_list.clear()
|
pg.sequence_list.clear()
|
||||||
pg.psa.bones.clear()
|
pg.psa.bones.clear()
|
||||||
pg.action_list.clear()
|
|
||||||
pg.psa_error = ''
|
pg.psa_error = ''
|
||||||
try:
|
try:
|
||||||
# Read the file and populate the action list.
|
# Read the file and populate the action list.
|
||||||
@@ -243,13 +242,13 @@ class PsaImportPropertyGroup(PropertyGroup):
|
|||||||
psa: PointerProperty(type=PsaDataPropertyGroup)
|
psa: PointerProperty(type=PsaDataPropertyGroup)
|
||||||
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
||||||
sequence_list_index: IntProperty(name='', default=0)
|
sequence_list_index: IntProperty(name='', default=0)
|
||||||
action_list: CollectionProperty(type=PsaImportActionListItem)
|
|
||||||
action_list_index: IntProperty(name='', default=0)
|
|
||||||
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', options=set())
|
should_clean_keys: BoolProperty(default=True, name='Clean Keyframes', description='Exclude unnecessary keyframes from being written to the actions.', 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_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())
|
||||||
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_use_filter_invert: BoolProperty(default=False, options=set())
|
||||||
|
|
||||||
|
|
||||||
class PSA_UL_SequenceList(UIList):
|
class PSA_UL_SequenceList(UIList):
|
||||||
@@ -263,25 +262,26 @@ class PSA_UL_SequenceList(UIList):
|
|||||||
column.label(text=item.action_name)
|
column.label(text=item.action_name)
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
|
pg = context.scene.psa_import
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
subrow = row.row(align=True)
|
subrow = row.row(align=True)
|
||||||
subrow.prop(self, 'filter_name', text="")
|
# TODO: current used for both, not good!
|
||||||
subrow.prop(self, 'use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
subrow.prop(pg, 'sequence_filter_name', text="")
|
||||||
subrow = row.row(align=True)
|
subrow.prop(pg, 'sequence_use_filter_invert', text="", icon='ARROW_LEFTRIGHT')
|
||||||
subrow.prop(self, 'use_filter_sort_reverse', text='', icon='SORT_ASC')
|
|
||||||
|
|
||||||
def filter_items(self, context, data, property):
|
def filter_items(self, context, data, property):
|
||||||
actions = getattr(data, property)
|
pg = context.scene.psa_import
|
||||||
|
sequences = getattr(data, property)
|
||||||
flt_flags = []
|
flt_flags = []
|
||||||
if self.filter_name:
|
if pg.sequence_filter_name:
|
||||||
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
|
flt_flags = bpy.types.UI_UL_list.filter_items_by_name(
|
||||||
self.filter_name,
|
pg.sequence_filter_name,
|
||||||
self.bitflag_filter_item,
|
self.bitflag_filter_item,
|
||||||
actions,
|
sequences,
|
||||||
'action_name',
|
'action_name',
|
||||||
reverse=self.use_filter_invert
|
reverse=pg.sequence_use_filter_invert
|
||||||
)
|
)
|
||||||
flt_neworder = bpy.types.UI_UL_list.sort_items_by_name(actions, '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
|
||||||
|
|
||||||
|
|
||||||
@@ -313,26 +313,6 @@ class PsaImportSequencesSelectAll(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class PsaImportActionsSelectAll(Operator):
|
|
||||||
bl_idname = 'psa_import.actions_select_all'
|
|
||||||
bl_label = 'All'
|
|
||||||
bl_description = 'Select all actions'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
action_list = pg.action_list
|
|
||||||
has_unselected_actions = any(map(lambda action: not action.is_selected, action_list))
|
|
||||||
return len(action_list) > 0 and has_unselected_actions
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
for action in pg.action_list:
|
|
||||||
action.is_selected = True
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportSequencesDeselectAll(Operator):
|
class PsaImportSequencesDeselectAll(Operator):
|
||||||
bl_idname = 'psa_import.sequences_deselect_all'
|
bl_idname = 'psa_import.sequences_deselect_all'
|
||||||
bl_label = 'None'
|
bl_label = 'None'
|
||||||
@@ -353,26 +333,6 @@ class PsaImportSequencesDeselectAll(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class PsaImportActionsDeselectAll(Operator):
|
|
||||||
bl_idname = 'psa_import.actions_deselect_all'
|
|
||||||
bl_label = 'None'
|
|
||||||
bl_description = 'Deselect all actions'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
action_list = pg.action_list
|
|
||||||
has_selected_actions = any(map(lambda action: action.is_selected, action_list))
|
|
||||||
return len(action_list) > 0 and has_selected_actions
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
for action in pg.action_list:
|
|
||||||
action.is_selected = False
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
class PSA_PT_ImportPanel_Advanced(Panel):
|
class PSA_PT_ImportPanel_Advanced(Panel):
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
@@ -443,31 +403,21 @@ class PSA_PT_ImportPanel(Panel):
|
|||||||
box.label(text=f'Sequences', icon='ARMATURE_DATA')
|
box.label(text=f'Sequences', icon='ARMATURE_DATA')
|
||||||
|
|
||||||
# select
|
# select
|
||||||
rows = max(3, min(len(pg.sequence_list) + len(pg.action_list), 10))
|
rows = max(3, max(len(pg.sequence_list), 10))
|
||||||
|
|
||||||
row = box.row()
|
row = box.row()
|
||||||
col = row.column()
|
col = row.column()
|
||||||
|
|
||||||
row2 = col.row(align=True)
|
row2 = col.row(align=True)
|
||||||
row2.label(text='Select')
|
row2.label(text='Select')
|
||||||
row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All')
|
row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
|
||||||
row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None')
|
row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
col = row.column(align=True)
|
row = box.row()
|
||||||
col.operator(PsaImportPushToActions.bl_idname, icon='TRIA_RIGHT', text='')
|
row.operator(PsaImportOperator.bl_idname, text=f'Import')
|
||||||
col.operator(PsaImportPopFromActions.bl_idname, icon='TRIA_LEFT', text='')
|
|
||||||
|
|
||||||
col = row.column()
|
|
||||||
row2 = col.row(align=True)
|
|
||||||
row2.label(text='Select')
|
|
||||||
row2.operator(PsaImportActionsSelectAll.bl_idname, text='All')
|
|
||||||
row2.operator(PsaImportActionsDeselectAll.bl_idname, text='None')
|
|
||||||
col.template_list('PSA_UL_ImportActionList', '', pg, 'action_list', pg, 'action_list_index', rows=rows)
|
|
||||||
col.separator()
|
|
||||||
col.operator(PsaImportOperator.bl_idname, text=f'Import')
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportFileReload(Operator):
|
class PsaImportFileReload(Operator):
|
||||||
@@ -508,69 +458,26 @@ class PsaImportOperator(Operator):
|
|||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = context.scene.psa_import
|
pg = context.scene.psa_import
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
action_list = pg.action_list
|
if active_object is None or active_object.type != 'ARMATURE':
|
||||||
return len(action_list) and active_object is not None and active_object.type == 'ARMATURE'
|
return False
|
||||||
|
return any(map(lambda x: x.is_selected, pg.sequence_list))
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = context.scene.psa_import
|
pg = context.scene.psa_import
|
||||||
psa_reader = PsaReader(pg.psa_file_path)
|
psa_reader = PsaReader(pg.psa_file_path)
|
||||||
sequence_names = [x.action_name for x in pg.action_list]
|
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
|
||||||
|
|
||||||
options = PsaImportOptions()
|
options = PsaImportOptions()
|
||||||
options.sequence_names = sequence_names
|
options.sequence_names = sequence_names
|
||||||
options.should_clean_keys = pg.should_clean_keys
|
options.should_clean_keys = pg.should_clean_keys
|
||||||
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
|
||||||
|
|
||||||
PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options)
|
PsaImporter().import_psa(psa_reader, context.view_layer.objects.active, options)
|
||||||
|
|
||||||
self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)')
|
self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)')
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportPushToActions(Operator):
|
|
||||||
bl_idname = 'psa_import.push_to_actions'
|
|
||||||
bl_label = 'Push to Actions'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
has_sequences_selected = any(map(lambda x: x.is_selected, pg.sequence_list))
|
|
||||||
return has_sequences_selected
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
indices_to_remove = []
|
|
||||||
for sequence_index, item in enumerate(pg.sequence_list):
|
|
||||||
if item.is_selected:
|
|
||||||
indices_to_remove.append(sequence_index)
|
|
||||||
action = pg.action_list.add()
|
|
||||||
action.action_name = item.action_name
|
|
||||||
for index in reversed(indices_to_remove):
|
|
||||||
pg.sequence_list.remove(index)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportPopFromActions(Operator):
|
|
||||||
bl_idname = 'psa_import.pop_from_actions'
|
|
||||||
bl_label = 'Pop From Actions'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
has_actions_selected = any(map(lambda x: x.is_selected, pg.action_list))
|
|
||||||
return has_actions_selected
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
pg = context.scene.psa_import
|
|
||||||
indices_to_remove = []
|
|
||||||
for action_index, item in enumerate(pg.action_list):
|
|
||||||
if item.is_selected:
|
|
||||||
indices_to_remove.append(action_index)
|
|
||||||
sequence = pg.sequence_list.add()
|
|
||||||
sequence.action_name = item.action_name
|
|
||||||
for index in reversed(indices_to_remove):
|
|
||||||
pg.action_list.remove(index)
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@@ -606,8 +513,6 @@ classes = (
|
|||||||
PSA_UL_ImportActionList,
|
PSA_UL_ImportActionList,
|
||||||
PsaImportSequencesSelectAll,
|
PsaImportSequencesSelectAll,
|
||||||
PsaImportSequencesDeselectAll,
|
PsaImportSequencesDeselectAll,
|
||||||
PsaImportActionsSelectAll,
|
|
||||||
PsaImportActionsDeselectAll,
|
|
||||||
PsaImportFileReload,
|
PsaImportFileReload,
|
||||||
PSA_PT_ImportPanel,
|
PSA_PT_ImportPanel,
|
||||||
PSA_PT_ImportPanel_Advanced,
|
PSA_PT_ImportPanel_Advanced,
|
||||||
@@ -615,6 +520,4 @@ classes = (
|
|||||||
PsaImportOperator,
|
PsaImportOperator,
|
||||||
PsaImportFileSelectOperator,
|
PsaImportFileSelectOperator,
|
||||||
PsaImportSelectFile,
|
PsaImportSelectFile,
|
||||||
PsaImportPushToActions,
|
|
||||||
PsaImportPopFromActions,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,12 +70,12 @@ class PskBuilder(object):
|
|||||||
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement
|
# If the mesh has no armature object, simply assign it a dummy bone at the root to satisfy the requirement
|
||||||
# that a PSK file must have at least one bone.
|
# that a PSK file must have at least one bone.
|
||||||
psk_bone = Psk.Bone()
|
psk_bone = Psk.Bone()
|
||||||
psk_bone.name = bytes('static', encoding='windows-1252')
|
psk_bone.name = bytes('root', encoding='windows-1252')
|
||||||
psk_bone.flags = 0
|
psk_bone.flags = 0
|
||||||
psk_bone.children_count = 0
|
psk_bone.children_count = 0
|
||||||
psk_bone.parent_index = 0
|
psk_bone.parent_index = 0
|
||||||
psk_bone.location = Vector3(0, 0, 0)
|
psk_bone.location = Vector3.zero()
|
||||||
psk_bone.rotation = Quaternion(0, 0, 0, 1)
|
psk_bone.rotation = Quaternion.identity()
|
||||||
psk.bones.append(psk_bone)
|
psk.bones.append(psk_bone)
|
||||||
else:
|
else:
|
||||||
bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices)
|
bone_names = get_export_bone_names(armature_object, options.bone_filter_mode, options.bone_group_indices)
|
||||||
|
|||||||
Reference in New Issue
Block a user