The PSA import functionality has been moved to a file import dialog.
As a result, the "PSA Import" panel in the Armature Data tab has been removed as it is now redundant. This was made possible by https://developer.blender.org/D15543. As a result, the minimum Blender version has now been bumped to 3.4. The 4.2.0 version is now in LTS mode and will not be receiving new features.
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "PSK/PSA Importer/Exporter",
|
"name": "PSK/PSA Importer/Exporter",
|
||||||
"author": "Colin Basnett, Yurii Ti",
|
"author": "Colin Basnett, Yurii Ti",
|
||||||
"version": (4, 2, 0),
|
"version": (5, 0, 0),
|
||||||
"blender": (2, 80, 0),
|
"blender": (3, 4, 0),
|
||||||
# "location": "File > Export > PSK Export (.psk)",
|
|
||||||
"description": "PSK/PSA Import/Export (.psk/.psa)",
|
"description": "PSK/PSA Import/Export (.psk/.psa)",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"doc_url": "https://github.com/DarklightGames/io_scene_psk_psa",
|
"doc_url": "https://github.com/DarklightGames/io_scene_psk_psa",
|
||||||
@@ -71,6 +70,7 @@ def register():
|
|||||||
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.psk_import = PointerProperty(type=psk_importer.PskImportPropertyGroup)
|
bpy.types.Scene.psk_import = PointerProperty(type=psk_importer.PskImportPropertyGroup)
|
||||||
bpy.types.Scene.psa_export = PointerProperty(type=psa_exporter.PsaExportPropertyGroup)
|
bpy.types.Scene.psa_export = PointerProperty(type=psa_exporter.PsaExportPropertyGroup)
|
||||||
@@ -85,6 +85,7 @@ 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)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import List, Optional
|
|||||||
import bpy
|
import bpy
|
||||||
import numpy
|
import numpy
|
||||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty, EnumProperty
|
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty, IntProperty, EnumProperty
|
||||||
from bpy.types import Operator, UIList, PropertyGroup, Panel, FCurve
|
from bpy.types import Operator, UIList, PropertyGroup, FCurve
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
from mathutils import Vector, Quaternion
|
from mathutils import Vector, Quaternion
|
||||||
|
|
||||||
@@ -243,14 +243,14 @@ class PsaImportActionListItem(PropertyGroup):
|
|||||||
is_selected: BoolProperty(default=False, options=empty_set)
|
is_selected: BoolProperty(default=False, options=empty_set)
|
||||||
|
|
||||||
|
|
||||||
def load_psa_file(context):
|
def load_psa_file(context, filepath: str):
|
||||||
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.psa_error = ''
|
pg.psa_error = ''
|
||||||
try:
|
try:
|
||||||
# Read the file and populate the action list.
|
# Read the file and populate the action list.
|
||||||
p = os.path.abspath(pg.psa_file_path)
|
p = os.path.abspath(filepath)
|
||||||
psa_reader = PsaReader(p)
|
psa_reader = PsaReader(p)
|
||||||
for sequence in psa_reader.sequences.values():
|
for sequence in psa_reader.sequences.values():
|
||||||
item = pg.sequence_list.add()
|
item = pg.sequence_list.add()
|
||||||
@@ -262,8 +262,8 @@ def load_psa_file(context):
|
|||||||
pg.psa_error = str(e)
|
pg.psa_error = str(e)
|
||||||
|
|
||||||
|
|
||||||
def on_psa_file_path_updated(property_, context):
|
def on_psa_file_path_updated(cls, context):
|
||||||
load_psa_file(context)
|
load_psa_file(context, cls.filepath)
|
||||||
|
|
||||||
|
|
||||||
class PsaBonePropertyGroup(PropertyGroup):
|
class PsaBonePropertyGroup(PropertyGroup):
|
||||||
@@ -276,7 +276,6 @@ class PsaDataPropertyGroup(PropertyGroup):
|
|||||||
|
|
||||||
|
|
||||||
class PsaImportPropertyGroup(PropertyGroup):
|
class PsaImportPropertyGroup(PropertyGroup):
|
||||||
psa_file_path: StringProperty(default='', options=empty_set, update=on_psa_file_path_updated, name='PSA File Path')
|
|
||||||
psa_error: StringProperty(default='')
|
psa_error: StringProperty(default='')
|
||||||
psa: PointerProperty(type=PsaDataPropertyGroup)
|
psa: PointerProperty(type=PsaDataPropertyGroup)
|
||||||
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
sequence_list: CollectionProperty(type=PsaImportActionListItem)
|
||||||
@@ -477,117 +476,6 @@ class PsaImportSequencesDeselectAll(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class PSA_PT_ImportPanel_Advanced(Panel):
|
|
||||||
bl_space_type = 'PROPERTIES'
|
|
||||||
bl_region_type = 'WINDOW'
|
|
||||||
bl_label = 'Advanced'
|
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
|
||||||
bl_parent_id = 'PSA_PT_ImportPanel'
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
pg = getattr(context.scene, 'psa_import')
|
|
||||||
|
|
||||||
col = layout.column()
|
|
||||||
col.use_property_split = True
|
|
||||||
col.use_property_decorate = False
|
|
||||||
col.prop(pg, 'bone_mapping_mode')
|
|
||||||
|
|
||||||
if pg.should_write_keyframes:
|
|
||||||
col = layout.column(heading='Keyframes')
|
|
||||||
col.use_property_split = True
|
|
||||||
col.use_property_decorate = False
|
|
||||||
col.prop(pg, 'should_convert_to_samples')
|
|
||||||
col.separator()
|
|
||||||
|
|
||||||
col = layout.column(heading='Options')
|
|
||||||
col.use_property_split = True
|
|
||||||
col.use_property_decorate = False
|
|
||||||
col.prop(pg, 'should_use_fake_user')
|
|
||||||
col.prop(pg, 'should_stash')
|
|
||||||
col.prop(pg, 'should_use_action_name_prefix')
|
|
||||||
|
|
||||||
if pg.should_use_action_name_prefix:
|
|
||||||
col.prop(pg, 'action_name_prefix')
|
|
||||||
|
|
||||||
|
|
||||||
class PSA_PT_ImportPanel(Panel):
|
|
||||||
bl_space_type = 'PROPERTIES'
|
|
||||||
bl_region_type = 'WINDOW'
|
|
||||||
bl_label = 'PSA Import'
|
|
||||||
bl_context = 'data'
|
|
||||||
bl_category = 'PSA Import'
|
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.view_layer.objects.active.type == 'ARMATURE'
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
pg = getattr(context.scene, 'psa_import')
|
|
||||||
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.operator(PsaImportSelectFile.bl_idname, text='', icon='FILEBROWSER')
|
|
||||||
row.prop(pg, 'psa_file_path', text='')
|
|
||||||
row.operator(PsaImportFileReload.bl_idname, text='', icon='FILE_REFRESH')
|
|
||||||
|
|
||||||
if pg.psa_error != '':
|
|
||||||
row = layout.row()
|
|
||||||
row.label(text='File could not be read', icon='ERROR')
|
|
||||||
|
|
||||||
box = layout.box()
|
|
||||||
|
|
||||||
box.label(text=f'Sequences ({len(pg.sequence_list)})', icon='ARMATURE_DATA')
|
|
||||||
|
|
||||||
# select
|
|
||||||
rows = max(3, min(len(pg.sequence_list), 10))
|
|
||||||
|
|
||||||
row = box.row()
|
|
||||||
col = row.column()
|
|
||||||
|
|
||||||
row2 = col.row(align=True)
|
|
||||||
row2.label(text='Select')
|
|
||||||
row2.operator(PsaImportSequencesFromText.bl_idname, text='', icon='TEXT')
|
|
||||||
row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
|
|
||||||
row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
|
||||||
|
|
||||||
col = col.row()
|
|
||||||
col.template_list('PSA_UL_ImportSequenceList', '', pg, 'sequence_list', pg, 'sequence_list_index', rows=rows)
|
|
||||||
|
|
||||||
col = layout.column(heading='')
|
|
||||||
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):
|
|
||||||
bl_idname = 'psa_import.file_reload'
|
|
||||||
bl_label = 'Refresh'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
bl_description = 'Refresh the PSA file'
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
load_psa_file(context)
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
class PsaImportSelectFile(Operator):
|
class PsaImportSelectFile(Operator):
|
||||||
bl_idname = 'psa_import.select_file'
|
bl_idname = 'psa_import.select_file'
|
||||||
bl_label = 'Select'
|
bl_label = 'Select'
|
||||||
@@ -605,23 +493,32 @@ class PsaImportSelectFile(Operator):
|
|||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
|
||||||
class PsaImportOperator(Operator):
|
class PsaImportOperator(Operator, ImportHelper):
|
||||||
bl_idname = 'psa_import.import'
|
bl_idname = 'psa_import.import'
|
||||||
bl_label = 'Import'
|
bl_label = 'Import'
|
||||||
bl_description = 'Import the selected animations into the scene as actions'
|
bl_description = 'Import the selected animations into the scene as actions'
|
||||||
bl_options = {'INTERNAL', 'UNDO'}
|
bl_options = {'INTERNAL', 'UNDO'}
|
||||||
|
|
||||||
|
filename_ext = '.psa'
|
||||||
|
filter_glob: StringProperty(default='*.psa', options={'HIDDEN'})
|
||||||
|
filepath: StringProperty(
|
||||||
|
name='File Path',
|
||||||
|
description='File path used for importing the PSA file',
|
||||||
|
maxlen=1024,
|
||||||
|
default='',
|
||||||
|
update=on_psa_file_path_updated)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
pg = getattr(context.scene, 'psa_import')
|
|
||||||
active_object = context.view_layer.objects.active
|
active_object = context.view_layer.objects.active
|
||||||
if active_object is None or active_object.type != 'ARMATURE':
|
if active_object is None or active_object.type != 'ARMATURE':
|
||||||
|
cls.poll_message_set('The active object must be an armature')
|
||||||
return False
|
return False
|
||||||
return any(map(lambda x: x.is_selected, pg.sequence_list))
|
return True
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
pg = getattr(context.scene, 'psa_import')
|
pg = getattr(context.scene, 'psa_import')
|
||||||
psa_reader = PsaReader(pg.psa_file_path)
|
psa_reader = PsaReader(self.filepath)
|
||||||
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
|
sequence_names = [x.action_name for x in pg.sequence_list if x.is_selected]
|
||||||
|
|
||||||
options = PsaImportOptions()
|
options = PsaImportOptions()
|
||||||
@@ -646,27 +543,72 @@ class PsaImportOperator(Operator):
|
|||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context: bpy.types.Context, event: bpy.types.Event):
|
||||||
|
# Attempt to load the PSA file for the pre-selected file.
|
||||||
|
load_psa_file(context, self.filepath)
|
||||||
|
|
||||||
class PsaImportFileSelectOperator(Operator, ImportHelper):
|
|
||||||
bl_idname = 'psa_import.file_select'
|
|
||||||
bl_label = 'File Select'
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
filename_ext = '.psa'
|
|
||||||
filter_glob: StringProperty(default='*.psa', options={'HIDDEN'})
|
|
||||||
filepath: StringProperty(
|
|
||||||
name='File Path',
|
|
||||||
description='File path used for importing the PSA file',
|
|
||||||
maxlen=1024,
|
|
||||||
default='')
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
context.window_manager.fileselect_add(self)
|
context.window_manager.fileselect_add(self)
|
||||||
return {'RUNNING_MODAL'}
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
def execute(self, context):
|
def draw(self, context: bpy.types.Context):
|
||||||
|
layout = self.layout
|
||||||
pg = getattr(context.scene, 'psa_import')
|
pg = getattr(context.scene, 'psa_import')
|
||||||
pg.psa_file_path = self.filepath
|
|
||||||
return {'FINISHED'}
|
if pg.psa_error:
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text='Select a PSA file', icon='ERROR')
|
||||||
|
else:
|
||||||
|
box = layout.box()
|
||||||
|
|
||||||
|
box.label(text=f'Sequences ({len(pg.sequence_list)})', icon='ARMATURE_DATA')
|
||||||
|
|
||||||
|
# Select buttons.
|
||||||
|
rows = max(3, min(len(pg.sequence_list), 10))
|
||||||
|
|
||||||
|
row = box.row()
|
||||||
|
col = row.column()
|
||||||
|
|
||||||
|
row2 = col.row(align=True)
|
||||||
|
row2.label(text='Select')
|
||||||
|
row2.operator(PsaImportSequencesFromText.bl_idname, text='', icon='TEXT')
|
||||||
|
row2.operator(PsaImportSequencesSelectAll.bl_idname, text='All', icon='CHECKBOX_HLT')
|
||||||
|
row2.operator(PsaImportSequencesDeselectAll.bl_idname, text='None', icon='CHECKBOX_DEHLT')
|
||||||
|
|
||||||
|
col = col.row()
|
||||||
|
col.template_list('PSA_UL_ImportSequenceList', '', pg, 'sequence_list', pg, 'sequence_list_index', rows=rows)
|
||||||
|
|
||||||
|
col = layout.column(heading='')
|
||||||
|
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')
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(pg, 'bone_mapping_mode')
|
||||||
|
|
||||||
|
if pg.should_write_keyframes:
|
||||||
|
col = layout.column(heading='Keyframes')
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(pg, 'should_convert_to_samples')
|
||||||
|
col.separator()
|
||||||
|
|
||||||
|
col = layout.column(heading='Options')
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(pg, 'should_use_fake_user')
|
||||||
|
col.prop(pg, 'should_stash')
|
||||||
|
col.prop(pg, 'should_use_action_name_prefix')
|
||||||
|
|
||||||
|
if pg.should_use_action_name_prefix:
|
||||||
|
col.prop(pg, 'action_name_prefix')
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
@@ -680,10 +622,6 @@ classes = (
|
|||||||
PsaImportSequencesSelectAll,
|
PsaImportSequencesSelectAll,
|
||||||
PsaImportSequencesDeselectAll,
|
PsaImportSequencesDeselectAll,
|
||||||
PsaImportSequencesFromText,
|
PsaImportSequencesFromText,
|
||||||
PsaImportFileReload,
|
|
||||||
PSA_PT_ImportPanel,
|
|
||||||
PSA_PT_ImportPanel_Advanced,
|
|
||||||
PsaImportOperator,
|
PsaImportOperator,
|
||||||
PsaImportFileSelectOperator,
|
|
||||||
PsaImportSelectFile,
|
PsaImportSelectFile,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user