Impemented #120: Multiple PSKs can be imported at once with drag-and-drop
This commit is contained in:
@@ -455,8 +455,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
|
|
||||||
export_sequences: List[PsaBuildSequence] = []
|
export_sequences: List[PsaBuildSequence] = []
|
||||||
|
|
||||||
selected_armature_objects = [obj for obj in context.selected_objects if obj.type == 'ARMATURE']
|
|
||||||
|
|
||||||
match pg.sequence_source:
|
match pg.sequence_source:
|
||||||
case 'ACTIONS':
|
case 'ACTIONS':
|
||||||
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
||||||
@@ -507,6 +505,10 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
case _:
|
case _:
|
||||||
raise ValueError(f'Unhandled sequence source: {pg.sequence_source}')
|
raise ValueError(f'Unhandled sequence source: {pg.sequence_source}')
|
||||||
|
|
||||||
|
if len(export_sequences) == 0:
|
||||||
|
self.report({'ERROR'}, 'No sequences were selected for export')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
options = PsaBuildOptions()
|
options = PsaBuildOptions()
|
||||||
options.animation_data = animation_data
|
options.animation_data = animation_data
|
||||||
options.sequences = export_sequences
|
options.sequences = export_sequences
|
||||||
@@ -530,9 +532,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
|||||||
|
|
||||||
write_psa(psa, self.filepath)
|
write_psa(psa, self.filepath)
|
||||||
|
|
||||||
if len(psa.sequences) == 0:
|
|
||||||
self.report({'WARNING'}, 'No sequences were selected for export')
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from bpy.props import StringProperty
|
from bpy.props import StringProperty, CollectionProperty
|
||||||
from bpy.types import Operator, FileHandler, Context
|
from bpy.types import Operator, FileHandler, Context, OperatorFileListElement, UILayout
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
|
||||||
from ..importer import PskImportOptions, import_psk
|
from ..importer import PskImportOptions, import_psk
|
||||||
@@ -10,6 +11,70 @@ from ..reader import read_psk
|
|||||||
|
|
||||||
empty_set = set()
|
empty_set = set()
|
||||||
|
|
||||||
|
def get_psk_import_options_from_properties(property_group: PskImportMixin):
|
||||||
|
options = PskImportOptions()
|
||||||
|
options.should_import_mesh = property_group.should_import_mesh
|
||||||
|
options.should_import_extra_uvs = property_group.should_import_extra_uvs
|
||||||
|
options.should_import_vertex_colors = property_group.should_import_vertex_colors
|
||||||
|
options.should_import_vertex_normals = property_group.should_import_vertex_normals
|
||||||
|
options.vertex_color_space = property_group.vertex_color_space
|
||||||
|
options.should_import_skeleton = property_group.should_import_skeleton
|
||||||
|
options.bone_length = property_group.bone_length
|
||||||
|
options.should_import_materials = property_group.should_import_materials
|
||||||
|
options.should_import_shape_keys = property_group.should_import_shape_keys
|
||||||
|
options.scale = property_group.scale
|
||||||
|
|
||||||
|
if property_group.bdk_repository_id:
|
||||||
|
options.bdk_repository_id = property_group.bdk_repository_id
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def psk_import_draw(layout: UILayout, props: PskImportMixin):
|
||||||
|
row = layout.row()
|
||||||
|
|
||||||
|
col = row.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(props, 'import_components')
|
||||||
|
|
||||||
|
if props.should_import_mesh:
|
||||||
|
mesh_header, mesh_panel = layout.panel('mesh_panel_id', default_closed=False)
|
||||||
|
mesh_header.label(text='Mesh', icon='MESH_DATA')
|
||||||
|
|
||||||
|
if mesh_panel:
|
||||||
|
row = mesh_panel.row()
|
||||||
|
col = row.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(props, 'should_import_materials', text='Materials')
|
||||||
|
col.prop(props, 'should_import_vertex_normals', text='Vertex Normals')
|
||||||
|
col.prop(props, 'should_import_extra_uvs', text='Extra UVs')
|
||||||
|
col.prop(props, 'should_import_vertex_colors', text='Vertex Colors')
|
||||||
|
if props.should_import_vertex_colors:
|
||||||
|
col.prop(props, 'vertex_color_space')
|
||||||
|
col.prop(props, 'should_import_shape_keys', text='Shape Keys')
|
||||||
|
|
||||||
|
if props.should_import_skeleton:
|
||||||
|
skeleton_header, skeleton_panel = layout.panel('skeleton_panel_id', default_closed=False)
|
||||||
|
skeleton_header.label(text='Skeleton', icon='OUTLINER_DATA_ARMATURE')
|
||||||
|
|
||||||
|
if skeleton_panel:
|
||||||
|
row = skeleton_panel.row()
|
||||||
|
col = row.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(props, 'bone_length')
|
||||||
|
|
||||||
|
transform_header, transform_panel = layout.panel('transform_panel_id', default_closed=False)
|
||||||
|
transform_header.label(text='Transform')
|
||||||
|
if transform_panel:
|
||||||
|
row = transform_panel.row()
|
||||||
|
col = row.column()
|
||||||
|
col.use_property_split = True
|
||||||
|
col.use_property_decorate = False
|
||||||
|
col.prop(props, 'scale')
|
||||||
|
|
||||||
|
|
||||||
class PSK_OT_import(Operator, ImportHelper, PskImportMixin):
|
class PSK_OT_import(Operator, ImportHelper, PskImportMixin):
|
||||||
bl_idname = 'psk.import'
|
bl_idname = 'psk.import'
|
||||||
@@ -26,80 +91,69 @@ class PSK_OT_import(Operator, ImportHelper, PskImportMixin):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
psk = read_psk(self.filepath)
|
psk = read_psk(self.filepath)
|
||||||
|
name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||||
options = PskImportOptions()
|
options = get_psk_import_options_from_properties(self)
|
||||||
options.name = os.path.splitext(os.path.basename(self.filepath))[0]
|
result = import_psk(psk, context, name, options)
|
||||||
options.should_import_mesh = self.should_import_mesh
|
|
||||||
options.should_import_extra_uvs = self.should_import_extra_uvs
|
|
||||||
options.should_import_vertex_colors = self.should_import_vertex_colors
|
|
||||||
options.should_import_vertex_normals = self.should_import_vertex_normals
|
|
||||||
options.vertex_color_space = self.vertex_color_space
|
|
||||||
options.should_import_skeleton = self.should_import_skeleton
|
|
||||||
options.bone_length = self.bone_length
|
|
||||||
options.should_import_materials = self.should_import_materials
|
|
||||||
options.should_import_shape_keys = self.should_import_shape_keys
|
|
||||||
options.scale = self.scale
|
|
||||||
|
|
||||||
if self.bdk_repository_id:
|
|
||||||
options.bdk_repository_id = self.bdk_repository_id
|
|
||||||
|
|
||||||
if not options.should_import_mesh and not options.should_import_skeleton:
|
|
||||||
self.report({'ERROR'}, 'Nothing to import')
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
result = import_psk(psk, context, options)
|
|
||||||
|
|
||||||
if len(result.warnings):
|
if len(result.warnings):
|
||||||
message = f'PSK imported with {len(result.warnings)} warning(s)\n'
|
message = f'PSK imported as "{result.root_object.name}" with {len(result.warnings)} warning(s)\n'
|
||||||
message += '\n'.join(result.warnings)
|
message += '\n'.join(result.warnings)
|
||||||
self.report({'WARNING'}, message)
|
self.report({'WARNING'}, message)
|
||||||
else:
|
else:
|
||||||
self.report({'INFO'}, f'PSK imported ({options.name})')
|
self.report({'INFO'}, f'PSK imported as "{result.root_object.name}"')
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
psk_import_draw(self.layout, self)
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
|
|
||||||
col = row.column()
|
class PSK_OT_import_drag_and_drop(Operator, PskImportMixin):
|
||||||
col.use_property_split = True
|
bl_idname = 'psk.import_drag_and_drop'
|
||||||
col.use_property_decorate = False
|
bl_label = 'Import Drag and Drop'
|
||||||
col.prop(self, 'scale')
|
bl_options = {'INTERNAL', 'UNDO', 'PRESET'}
|
||||||
|
bl_description = 'Import a PSK file by dragging and dropping it onto the 3D view'
|
||||||
|
|
||||||
mesh_header, mesh_panel = layout.panel('mesh_panel_id', default_closed=False)
|
directory: StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE', 'HIDDEN'})
|
||||||
mesh_header.prop(self, 'should_import_mesh')
|
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
||||||
|
|
||||||
if mesh_panel and self.should_import_mesh:
|
@classmethod
|
||||||
row = mesh_panel.row()
|
def poll(cls, context):
|
||||||
col = row.column()
|
return context.area and context.area.type == 'VIEW_3D'
|
||||||
col.use_property_split = True
|
|
||||||
col.use_property_decorate = False
|
|
||||||
col.prop(self, 'should_import_materials', text='Materials')
|
|
||||||
col.prop(self, 'should_import_vertex_normals', text='Vertex Normals')
|
|
||||||
col.prop(self, 'should_import_extra_uvs', text='Extra UVs')
|
|
||||||
col.prop(self, 'should_import_vertex_colors', text='Vertex Colors')
|
|
||||||
if self.should_import_vertex_colors:
|
|
||||||
col.prop(self, 'vertex_color_space')
|
|
||||||
col.prop(self, 'should_import_shape_keys', text='Shape Keys')
|
|
||||||
|
|
||||||
skeleton_header, skeleton_panel = layout.panel('skeleton_panel_id', default_closed=False)
|
def draw(self, context):
|
||||||
skeleton_header.prop(self, 'should_import_skeleton')
|
psk_import_draw(self.layout, self)
|
||||||
|
|
||||||
if skeleton_panel and self.should_import_skeleton:
|
def invoke(self, context, event):
|
||||||
row = skeleton_panel.row()
|
context.window_manager.invoke_props_dialog(self)
|
||||||
col = row.column()
|
return {'RUNNING_MODAL'}
|
||||||
col.use_property_split = True
|
|
||||||
col.use_property_decorate = False
|
def execute(self, context):
|
||||||
col.prop(self, 'bone_length')
|
warning_count = 0
|
||||||
|
|
||||||
|
options = get_psk_import_options_from_properties(self)
|
||||||
|
|
||||||
|
for file in self.files:
|
||||||
|
filepath = Path(self.directory) / file.name
|
||||||
|
psk = read_psk(filepath)
|
||||||
|
name = os.path.splitext(file.name)[0]
|
||||||
|
result = import_psk(psk, context, name, options)
|
||||||
|
if result.warnings:
|
||||||
|
warning_count += len(result.warnings)
|
||||||
|
|
||||||
|
if warning_count > 0:
|
||||||
|
self.report({'WARNING'}, f'Imported {len(self.files)} PSK file(s) with {warning_count} warning(s)')
|
||||||
|
else:
|
||||||
|
self.report({'INFO'}, f'Imported {len(self.files)} PSK file(s)')
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
# TODO: move to another file
|
# TODO: move to another file
|
||||||
class PSK_FH_import(FileHandler):
|
class PSK_FH_import(FileHandler):
|
||||||
bl_idname = 'PSK_FH_import'
|
bl_idname = 'PSK_FH_import'
|
||||||
bl_label = 'Unreal PSK'
|
bl_label = 'Unreal PSK'
|
||||||
bl_import_operator = PSK_OT_import.bl_idname
|
bl_import_operator = PSK_OT_import_drag_and_drop.bl_idname
|
||||||
bl_export_operator = 'psk.export_collection'
|
bl_export_operator = 'psk.export_collection'
|
||||||
bl_file_extensions = '.psk;.pskx'
|
bl_file_extensions = '.psk;.pskx'
|
||||||
|
|
||||||
@@ -107,7 +161,9 @@ class PSK_FH_import(FileHandler):
|
|||||||
def poll_drop(cls, context: Context):
|
def poll_drop(cls, context: Context):
|
||||||
return context.area and context.area.type == 'VIEW_3D'
|
return context.area and context.area.type == 'VIEW_3D'
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
PSK_OT_import,
|
PSK_OT_import,
|
||||||
|
PSK_OT_import_drag_and_drop,
|
||||||
PSK_FH_import,
|
PSK_FH_import,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Optional, List
|
|||||||
import bmesh
|
import bmesh
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bpy.types import VertexGroup
|
from bpy.types import VertexGroup, Context, Object
|
||||||
from mathutils import Quaternion, Vector, Matrix
|
from mathutils import Quaternion, Vector, Matrix
|
||||||
|
|
||||||
from .data import Psk
|
from .data import Psk
|
||||||
@@ -13,7 +13,6 @@ from ..shared.helpers import rgb_to_srgb, is_bdk_addon_loaded
|
|||||||
|
|
||||||
class PskImportOptions:
|
class PskImportOptions:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.name = ''
|
|
||||||
self.should_import_mesh = True
|
self.should_import_mesh = True
|
||||||
self.should_reuse_materials = True
|
self.should_reuse_materials = True
|
||||||
self.should_import_vertex_colors = True
|
self.should_import_vertex_colors = True
|
||||||
@@ -49,17 +48,23 @@ class ImportBone:
|
|||||||
class PskImportResult:
|
class PskImportResult:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = []
|
||||||
|
self.armature_object: Optional[Object] = None
|
||||||
|
self.mesh_object: Optional[Object] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def root_object(self) -> Object:
|
||||||
|
return self.armature_object if self.armature_object is not None else self.mesh_object
|
||||||
|
|
||||||
|
|
||||||
def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
|
def import_psk(psk: Psk, context: Context, name: str, options: PskImportOptions) -> PskImportResult:
|
||||||
result = PskImportResult()
|
result = PskImportResult()
|
||||||
armature_object = None
|
armature_object = None
|
||||||
mesh_object = None
|
mesh_object = None
|
||||||
|
|
||||||
if options.should_import_skeleton:
|
if options.should_import_skeleton:
|
||||||
# ARMATURE
|
# ARMATURE
|
||||||
armature_data = bpy.data.armatures.new(options.name)
|
armature_data = bpy.data.armatures.new(name)
|
||||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
armature_object = bpy.data.objects.new(name, armature_data)
|
||||||
armature_object.show_in_front = True
|
armature_object.show_in_front = True
|
||||||
|
|
||||||
context.scene.collection.objects.link(armature_object)
|
context.scene.collection.objects.link(armature_object)
|
||||||
@@ -116,8 +121,8 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
|
|||||||
|
|
||||||
# MESH
|
# MESH
|
||||||
if options.should_import_mesh:
|
if options.should_import_mesh:
|
||||||
mesh_data = bpy.data.meshes.new(options.name)
|
mesh_data = bpy.data.meshes.new(name)
|
||||||
mesh_object = bpy.data.objects.new(options.name, mesh_data)
|
mesh_object = bpy.data.objects.new(name, mesh_data)
|
||||||
|
|
||||||
# MATERIALS
|
# MATERIALS
|
||||||
if options.should_import_materials:
|
if options.should_import_materials:
|
||||||
@@ -280,4 +285,7 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
result.armature_object = armature_object
|
||||||
|
result.mesh_object = mesh_object
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ def poly_flags_to_triangle_type_and_bit_flags(poly_flags: int) -> (str, set[str]
|
|||||||
empty_set = set()
|
empty_set = set()
|
||||||
|
|
||||||
|
|
||||||
|
def should_import_mesh_get(self):
|
||||||
|
return self.import_components in {'ALL', 'MESH'}
|
||||||
|
|
||||||
|
def should_import_skleton_get(self):
|
||||||
|
return self.import_components in {'ALL', 'SKELETON'}
|
||||||
|
|
||||||
|
|
||||||
class PskImportMixin:
|
class PskImportMixin:
|
||||||
should_import_vertex_colors: BoolProperty(
|
should_import_vertex_colors: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
@@ -76,11 +83,20 @@ class PskImportMixin:
|
|||||||
options=empty_set,
|
options=empty_set,
|
||||||
description='Import extra UV maps, if available'
|
description='Import extra UV maps, if available'
|
||||||
)
|
)
|
||||||
should_import_mesh: BoolProperty(
|
import_components: EnumProperty(
|
||||||
default=True,
|
name='Import Components',
|
||||||
name='Import Mesh',
|
|
||||||
options=empty_set,
|
options=empty_set,
|
||||||
description='Import mesh'
|
description='Determine which components to import',
|
||||||
|
items=(
|
||||||
|
('ALL', 'Mesh & Skeleton', 'Import mesh and skeleton'),
|
||||||
|
('MESH', 'Mesh Only', 'Import mesh only'),
|
||||||
|
('SKELETON', 'Skeleton Only', 'Import skeleton only'),
|
||||||
|
),
|
||||||
|
default='ALL'
|
||||||
|
)
|
||||||
|
should_import_mesh: BoolProperty(
|
||||||
|
name='Import Mesh',
|
||||||
|
get=should_import_mesh_get,
|
||||||
)
|
)
|
||||||
should_import_materials: BoolProperty(
|
should_import_materials: BoolProperty(
|
||||||
default=True,
|
default=True,
|
||||||
@@ -88,10 +104,8 @@ class PskImportMixin:
|
|||||||
options=empty_set,
|
options=empty_set,
|
||||||
)
|
)
|
||||||
should_import_skeleton: BoolProperty(
|
should_import_skeleton: BoolProperty(
|
||||||
default=True,
|
|
||||||
name='Import Skeleton',
|
name='Import Skeleton',
|
||||||
options=empty_set,
|
get=should_import_skleton_get,
|
||||||
description='Import skeleton'
|
|
||||||
)
|
)
|
||||||
bone_length: FloatProperty(
|
bone_length: FloatProperty(
|
||||||
default=1.0,
|
default=1.0,
|
||||||
|
|||||||
Reference in New Issue
Block a user