* Considerable clean-up of existing code.
* Moved the PSA import to the properties panel.
This commit is contained in:
@@ -42,33 +42,15 @@ else:
|
|||||||
from .psa import reader as psa_reader
|
from .psa import reader as psa_reader
|
||||||
from .psa import importer as psa_importer
|
from .psa import importer as psa_importer
|
||||||
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import PointerProperty
|
from bpy.props import PointerProperty
|
||||||
|
|
||||||
|
classes = psx_types.__classes__ + \
|
||||||
# TODO: have the individual files emit a __classes__ field or something we can update it locally instead of explicitly declaring it here.
|
psk_importer.__classes__ + \
|
||||||
classes = []
|
psk_exporter.__classes__ + \
|
||||||
classes.extend(psx_types.__classes__)
|
psa_exporter.__classes__ + \
|
||||||
classes.extend(psk_exporter.__classes__)
|
psa_importer.__classes__
|
||||||
classes.extend([
|
|
||||||
psk_importer.PskImportOperator,
|
|
||||||
psa_importer.PsaImportOperator,
|
|
||||||
psa_importer.PsaImportFileSelectOperator,
|
|
||||||
psa_exporter.PSA_UL_ExportActionList,
|
|
||||||
# psa_exporter.PSA_UL_ExportBoneGroupList,
|
|
||||||
psa_importer.PSA_UL_ImportActionList,
|
|
||||||
psa_importer.PsaImportActionListItem,
|
|
||||||
psa_importer.PsaImportPsaBoneItem,
|
|
||||||
psa_importer.PsaImportSelectAll,
|
|
||||||
psa_importer.PsaImportDeselectAll,
|
|
||||||
psa_importer.PSA_PT_ImportPanel,
|
|
||||||
psa_importer.PsaImportPropertyGroup,
|
|
||||||
psa_exporter.PsaExportOperator,
|
|
||||||
psa_exporter.PsaExportSelectAll,
|
|
||||||
psa_exporter.PsaExportDeselectAll,
|
|
||||||
psa_exporter.PsaExportActionListItem,
|
|
||||||
psa_exporter.PsaExportPropertyGroup,
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def psk_export_menu_func(self, context):
|
def psk_export_menu_func(self, context):
|
||||||
@@ -83,17 +65,12 @@ def psa_export_menu_func(self, context):
|
|||||||
self.layout.operator(psa_exporter.PsaExportOperator.bl_idname, text='Unreal PSA (.psa)')
|
self.layout.operator(psa_exporter.PsaExportOperator.bl_idname, text='Unreal PSA (.psa)')
|
||||||
|
|
||||||
|
|
||||||
def psa_import_menu_func(self, context):
|
|
||||||
self.layout.operator(psa_importer.PsaImportOperator.bl_idname, text='Unreal PSA (.psa)')
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func)
|
bpy.types.TOPBAR_MT_file_import.append(psk_import_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.append(psa_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func)
|
|
||||||
bpy.types.Scene.psa_import = PointerProperty(type=psa_importer.PsaImportPropertyGroup)
|
bpy.types.Scene.psa_import = PointerProperty(type=psa_importer.PsaImportPropertyGroup)
|
||||||
bpy.types.Scene.psa_export = PointerProperty(type=psa_exporter.PsaExportPropertyGroup)
|
bpy.types.Scene.psa_export = PointerProperty(type=psa_exporter.PsaExportPropertyGroup)
|
||||||
bpy.types.Scene.psk_export = PointerProperty(type=psk_exporter.PskExportPropertyGroup)
|
bpy.types.Scene.psk_export = PointerProperty(type=psk_exporter.PskExportPropertyGroup)
|
||||||
@@ -105,7 +82,6 @@ def unregister():
|
|||||||
bpy.types.TOPBAR_MT_file_export.remove(psk_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.remove(psk_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(psk_import_menu_func)
|
bpy.types.TOPBAR_MT_file_import.remove(psk_import_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_export.remove(psa_export_menu_func)
|
bpy.types.TOPBAR_MT_file_export.remove(psa_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(psa_import_menu_func)
|
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def populate_bone_groups_list(armature_object, bone_group_list):
|
def populate_bone_group_list(armature_object, bone_group_list):
|
||||||
bone_group_list.clear()
|
bone_group_list.clear()
|
||||||
|
|
||||||
item = bone_group_list.add()
|
item = bone_group_list.add()
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# Populate bone groups list.
|
# Populate bone groups list.
|
||||||
populate_bone_groups_list(self.armature, property_group.bone_group_list)
|
populate_bone_group_list(self.armature, property_group.bone_group_list)
|
||||||
|
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
|
|
||||||
@@ -235,3 +235,13 @@ class PsaExportDeselectAll(bpy.types.Operator):
|
|||||||
for action in property_group.action_list:
|
for action in property_group.action_list:
|
||||||
action.is_selected = False
|
action.is_selected = False
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
__classes__ = [
|
||||||
|
PsaExportActionListItem,
|
||||||
|
PsaExportPropertyGroup,
|
||||||
|
PsaExportOperator,
|
||||||
|
PSA_UL_ExportActionList,
|
||||||
|
PsaExportSelectAll,
|
||||||
|
PsaExportDeselectAll,
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import os
|
import os
|
||||||
from math import inf
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from mathutils import Vector, Quaternion, Matrix
|
from mathutils import Vector, Quaternion, Matrix
|
||||||
from .data import Psa
|
from .data import Psa
|
||||||
@@ -9,17 +8,14 @@ from bpy.types import Operator, Action, UIList, PropertyGroup, Panel, Armature,
|
|||||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty
|
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty
|
||||||
from .reader import PsaReader
|
from .reader import PsaReader
|
||||||
import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImporter(object):
|
class PsaImporter(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def import_psa(self, psa_reader: PsaReader, sequence_names: List[AnyStr], context):
|
def import_psa(self, psa_reader: PsaReader, sequence_names: List[AnyStr], armature_object):
|
||||||
property_group = context.scene.psa_import
|
|
||||||
sequences = map(lambda x: psa_reader.sequences[x], sequence_names)
|
sequences = map(lambda x: psa_reader.sequences[x], sequence_names)
|
||||||
armature_object = property_group.armature_object
|
|
||||||
armature_data = armature_object.data
|
armature_data = armature_object.data
|
||||||
|
|
||||||
class ImportBone(object):
|
class ImportBone(object):
|
||||||
@@ -107,18 +103,8 @@ class PsaImporter(object):
|
|||||||
import_bone.orig_quat = armature_bone.matrix_local.to_quaternion()
|
import_bone.orig_quat = armature_bone.matrix_local.to_quaternion()
|
||||||
import_bone.post_quat = import_bone.orig_quat.conjugated()
|
import_bone.post_quat = import_bone.orig_quat.conjugated()
|
||||||
|
|
||||||
io_time = datetime.timedelta()
|
|
||||||
math_time = datetime.timedelta()
|
|
||||||
keyframe_time = datetime.timedelta()
|
|
||||||
total_time = datetime.timedelta()
|
|
||||||
|
|
||||||
total_datetime_start = datetime.datetime.now()
|
|
||||||
|
|
||||||
# Create and populate the data for new sequences.
|
# Create and populate the data for new sequences.
|
||||||
for sequence in sequences:
|
for sequence in sequences:
|
||||||
# F-curve data buffer for all bones. This is used later on to avoid adding redundant keyframes.
|
|
||||||
next_frame_bones_fcurve_data = [(inf, inf, inf, inf, inf, inf, inf)] * len(import_bones)
|
|
||||||
|
|
||||||
# Add the action.
|
# Add the action.
|
||||||
action = bpy.data.actions.new(name=sequence.name.decode())
|
action = bpy.data.actions.new(name=sequence.name.decode())
|
||||||
|
|
||||||
@@ -142,10 +128,8 @@ class PsaImporter(object):
|
|||||||
sequence_name = sequence.name.decode('windows-1252')
|
sequence_name = sequence.name.decode('windows-1252')
|
||||||
|
|
||||||
# Read the sequence data matrix from the PSA.
|
# Read the sequence data matrix from the PSA.
|
||||||
start_datetime = datetime.datetime.now()
|
|
||||||
sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name)
|
sequence_data_matrix = psa_reader.read_sequence_data_matrix(sequence_name)
|
||||||
keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8)
|
keyframe_write_matrix = np.ones(sequence_data_matrix.shape, dtype=np.int8)
|
||||||
io_time += datetime.datetime.now() - start_datetime
|
|
||||||
|
|
||||||
# The first step is to determine the frames at which each bone will write out a keyframe.
|
# The first step is to determine the frames at which each bone will write out a keyframe.
|
||||||
threshold = 0.001
|
threshold = 0.001
|
||||||
@@ -173,21 +157,10 @@ class PsaImporter(object):
|
|||||||
# 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]
|
||||||
# Calculate the local-space key data for the bone.
|
# Calculate the local-space key data for the bone.
|
||||||
start_datetime = datetime.datetime.now()
|
|
||||||
fcurve_data = calculate_fcurve_data(import_bone, key_data)
|
fcurve_data = calculate_fcurve_data(import_bone, key_data)
|
||||||
math_time += datetime.datetime.now() - start_datetime
|
|
||||||
for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], fcurve_data):
|
for fcurve, should_write, datum in zip(import_bone.fcurves, keyframe_write_matrix[frame_index, bone_index], fcurve_data):
|
||||||
if should_write:
|
if should_write:
|
||||||
start_datetime = datetime.datetime.now()
|
|
||||||
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
fcurve.keyframe_points.insert(frame_index, datum, options={'FAST'})
|
||||||
keyframe_time += datetime.datetime.now() - start_datetime
|
|
||||||
|
|
||||||
total_time = datetime.datetime.now() - total_datetime_start
|
|
||||||
|
|
||||||
print(f'io_time: {io_time}')
|
|
||||||
print(f'math_time: {math_time}')
|
|
||||||
print(f'keyframe_time: {keyframe_time}')
|
|
||||||
print(f'total_time: {total_time}')
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportPsaBoneItem(PropertyGroup):
|
class PsaImportPsaBoneItem(PropertyGroup):
|
||||||
@@ -209,6 +182,7 @@ class PsaImportActionListItem(PropertyGroup):
|
|||||||
|
|
||||||
|
|
||||||
def on_psa_file_path_updated(property, context):
|
def on_psa_file_path_updated(property, context):
|
||||||
|
print('PATH UPDATED')
|
||||||
property_group = context.scene.psa_import
|
property_group = context.scene.psa_import
|
||||||
property_group.action_list.clear()
|
property_group.action_list.clear()
|
||||||
property_group.psa_bones.clear()
|
property_group.psa_bones.clear()
|
||||||
@@ -242,9 +216,9 @@ def on_armature_object_updated(property, context):
|
|||||||
|
|
||||||
|
|
||||||
class PsaImportPropertyGroup(bpy.types.PropertyGroup):
|
class PsaImportPropertyGroup(bpy.types.PropertyGroup):
|
||||||
psa_file_path: StringProperty(default='', subtype='FILE_PATH', update=on_psa_file_path_updated)
|
psa_file_path: StringProperty(default='', update=on_psa_file_path_updated)
|
||||||
psa_bones: CollectionProperty(type=PsaImportPsaBoneItem)
|
psa_bones: CollectionProperty(type=PsaImportPsaBoneItem)
|
||||||
armature_object: PointerProperty(name='Object', type=bpy.types.Object, update=on_armature_object_updated)
|
# armature_object: PointerProperty(name='Object', type=bpy.types.Object, update=on_armature_object_updated)
|
||||||
action_list: CollectionProperty(type=PsaImportActionListItem)
|
action_list: CollectionProperty(type=PsaImportActionListItem)
|
||||||
action_list_index: IntProperty(name='', default=0)
|
action_list_index: IntProperty(name='', default=0)
|
||||||
action_filter_name: StringProperty(default='')
|
action_filter_name: StringProperty(default='')
|
||||||
@@ -320,23 +294,31 @@ class PsaImportDeselectAll(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
class PSA_PT_ImportPanel(Panel):
|
class PSA_PT_ImportPanel(Panel):
|
||||||
bl_space_type = 'NLA_EDITOR'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'WINDOW'
|
||||||
bl_label = 'PSA Import'
|
bl_label = 'PSA Import'
|
||||||
bl_context = 'object'
|
bl_context = 'data'
|
||||||
bl_category = 'PSA Import'
|
bl_category = 'PSA Import'
|
||||||
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return context.view_layer.objects.active is not None
|
return context.object.type == 'ARMATURE'
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
property_group = context.scene.psa_import
|
property_group = context.scene.psa_import
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(property_group, 'psa_file_path', text='PSA File')
|
row.prop(property_group, 'psa_file_path', text='')
|
||||||
|
row.enabled = False
|
||||||
|
# row.enabled = property_group.psa_file_path is not ''
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop_search(property_group, 'armature_object', bpy.data, 'objects')
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
row.operator('psa_import.select_file', text='Select PSA File', icon='FILEBROWSER')
|
||||||
|
if len(property_group.action_list) > 0:
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text=f'Actions ({len(property_group.action_list)})', icon='ACTION')
|
box.label(text=f'Actions ({len(property_group.action_list)})', icon='ACTION')
|
||||||
row = box.row()
|
row = box.row()
|
||||||
@@ -346,9 +328,28 @@ class PSA_PT_ImportPanel(Panel):
|
|||||||
row.label(text='Select')
|
row.label(text='Select')
|
||||||
row.operator('psa_import.actions_select_all', text='All')
|
row.operator('psa_import.actions_select_all', text='All')
|
||||||
row.operator('psa_import.actions_deselect_all', text='None')
|
row.operator('psa_import.actions_deselect_all', text='None')
|
||||||
|
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
layout.operator('psa_import.import', text=f'Import')
|
layout.operator('psa_import.import', text=f'Import')
|
||||||
|
|
||||||
|
|
||||||
|
class PsaImportSelectFile(Operator):
|
||||||
|
bl_idname = "psa_import.select_file"
|
||||||
|
bl_label = "Select"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||||
|
filter_glob: bpy.props.StringProperty(default="*.psa", options={"HIDDEN"})
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
context.scene.psa_import.psa_file_path = self.filepath
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
context.window_manager.fileselect_add(self)
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
|
||||||
class PsaImportOperator(Operator):
|
class PsaImportOperator(Operator):
|
||||||
bl_idname = 'psa_import.import'
|
bl_idname = 'psa_import.import'
|
||||||
bl_label = 'Import'
|
bl_label = 'Import'
|
||||||
@@ -356,16 +357,17 @@ class PsaImportOperator(Operator):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
property_group = context.scene.psa_import
|
property_group = context.scene.psa_import
|
||||||
|
active_object = context.view_layer.objects.active
|
||||||
action_list = property_group.action_list
|
action_list = property_group.action_list
|
||||||
has_selected_actions = any(map(lambda action: action.is_selected, action_list))
|
has_selected_actions = any(map(lambda action: action.is_selected, action_list))
|
||||||
armature_object = property_group.armature_object
|
return has_selected_actions and active_object is not None and active_object.type == 'ARMATURE'
|
||||||
return has_selected_actions and armature_object is not None
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
property_group = context.scene.psa_import
|
property_group = context.scene.psa_import
|
||||||
psa_reader = PsaReader(property_group.psa_file_path)
|
psa_reader = PsaReader(property_group.psa_file_path)
|
||||||
sequence_names = [x.action_name for x in property_group.action_list if x.is_selected]
|
sequence_names = [x.action_name for x in property_group.action_list if x.is_selected]
|
||||||
PsaImporter().import_psa(psa_reader, sequence_names, context)
|
PsaImporter().import_psa(psa_reader, sequence_names, context.view_layer.objects.active)
|
||||||
|
self.report({'INFO'}, f'Imported {len(sequence_names)} action(s)')
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@@ -389,3 +391,17 @@ class PsaImportFileSelectOperator(Operator, ImportHelper):
|
|||||||
property_group.psa_file_path = self.filepath
|
property_group.psa_file_path = self.filepath
|
||||||
# Load the sequence names from the selected file
|
# Load the sequence names from the selected file
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
__classes__ = [
|
||||||
|
PsaImportPsaBoneItem,
|
||||||
|
PsaImportActionListItem,
|
||||||
|
PsaImportPropertyGroup,
|
||||||
|
PSA_UL_ImportActionList,
|
||||||
|
PsaImportSelectAll,
|
||||||
|
PsaImportDeselectAll,
|
||||||
|
PSA_PT_ImportPanel,
|
||||||
|
PsaImportOperator,
|
||||||
|
PsaImportFileSelectOperator,
|
||||||
|
PsaImportSelectFile,
|
||||||
|
]
|
||||||
|
|||||||
@@ -182,3 +182,8 @@ class PskImportOperator(Operator, ImportHelper):
|
|||||||
name = os.path.splitext(os.path.basename(self.filepath))[0]
|
name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||||
PskImporter().import_psk(psk, name, context)
|
PskImporter().import_psk(psk, name, context)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
__classes__ = [
|
||||||
|
PskImportOperator
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user