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] = []
|
||||
|
||||
selected_armature_objects = [obj for obj in context.selected_objects if obj.type == 'ARMATURE']
|
||||
|
||||
match pg.sequence_source:
|
||||
case 'ACTIONS':
|
||||
for action_item in filter(lambda x: x.is_selected, pg.action_list):
|
||||
@@ -507,6 +505,10 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
case _:
|
||||
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.animation_data = animation_data
|
||||
options.sequences = export_sequences
|
||||
@@ -530,9 +532,6 @@ class PSA_OT_export(Operator, ExportHelper):
|
||||
|
||||
write_psa(psa, self.filepath)
|
||||
|
||||
if len(psa.sequences) == 0:
|
||||
self.report({'WARNING'}, 'No sequences were selected for export')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from bpy.props import StringProperty
|
||||
from bpy.types import Operator, FileHandler, Context
|
||||
from bpy.props import StringProperty, CollectionProperty
|
||||
from bpy.types import Operator, FileHandler, Context, OperatorFileListElement, UILayout
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
from ..importer import PskImportOptions, import_psk
|
||||
@@ -10,6 +11,70 @@ from ..reader import read_psk
|
||||
|
||||
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):
|
||||
bl_idname = 'psk.import'
|
||||
@@ -26,80 +91,69 @@ class PSK_OT_import(Operator, ImportHelper, PskImportMixin):
|
||||
|
||||
def execute(self, context):
|
||||
psk = read_psk(self.filepath)
|
||||
|
||||
options = PskImportOptions()
|
||||
options.name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||
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)
|
||||
name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||
options = get_psk_import_options_from_properties(self)
|
||||
result = import_psk(psk, context, name, options)
|
||||
|
||||
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)
|
||||
self.report({'WARNING'}, message)
|
||||
else:
|
||||
self.report({'INFO'}, f'PSK imported ({options.name})')
|
||||
self.report({'INFO'}, f'PSK imported as "{result.root_object.name}"')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
psk_import_draw(self.layout, self)
|
||||
|
||||
row = layout.row()
|
||||
|
||||
col = row.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
col.prop(self, 'scale')
|
||||
class PSK_OT_import_drag_and_drop(Operator, PskImportMixin):
|
||||
bl_idname = 'psk.import_drag_and_drop'
|
||||
bl_label = 'Import Drag and Drop'
|
||||
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)
|
||||
mesh_header.prop(self, 'should_import_mesh')
|
||||
directory: StringProperty(subtype='FILE_PATH', options={'SKIP_SAVE', 'HIDDEN'})
|
||||
files: CollectionProperty(type=OperatorFileListElement, options={'SKIP_SAVE', 'HIDDEN'})
|
||||
|
||||
if mesh_panel and self.should_import_mesh:
|
||||
row = mesh_panel.row()
|
||||
col = row.column()
|
||||
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')
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.area and context.area.type == 'VIEW_3D'
|
||||
|
||||
skeleton_header, skeleton_panel = layout.panel('skeleton_panel_id', default_closed=False)
|
||||
skeleton_header.prop(self, 'should_import_skeleton')
|
||||
def draw(self, context):
|
||||
psk_import_draw(self.layout, self)
|
||||
|
||||
if skeleton_panel and self.should_import_skeleton:
|
||||
row = skeleton_panel.row()
|
||||
col = row.column()
|
||||
col.use_property_split = True
|
||||
col.use_property_decorate = False
|
||||
col.prop(self, 'bone_length')
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.invoke_props_dialog(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
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
|
||||
class PSK_FH_import(FileHandler):
|
||||
bl_idname = 'PSK_FH_import'
|
||||
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_file_extensions = '.psk;.pskx'
|
||||
|
||||
@@ -107,7 +161,9 @@ class PSK_FH_import(FileHandler):
|
||||
def poll_drop(cls, context: Context):
|
||||
return context.area and context.area.type == 'VIEW_3D'
|
||||
|
||||
|
||||
classes = (
|
||||
PSK_OT_import,
|
||||
PSK_OT_import_drag_and_drop,
|
||||
PSK_FH_import,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Optional, List
|
||||
import bmesh
|
||||
import bpy
|
||||
import numpy as np
|
||||
from bpy.types import VertexGroup
|
||||
from bpy.types import VertexGroup, Context, Object
|
||||
from mathutils import Quaternion, Vector, Matrix
|
||||
|
||||
from .data import Psk
|
||||
@@ -13,7 +13,6 @@ from ..shared.helpers import rgb_to_srgb, is_bdk_addon_loaded
|
||||
|
||||
class PskImportOptions:
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.should_import_mesh = True
|
||||
self.should_reuse_materials = True
|
||||
self.should_import_vertex_colors = True
|
||||
@@ -49,17 +48,23 @@ class ImportBone:
|
||||
class PskImportResult:
|
||||
def __init__(self):
|
||||
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()
|
||||
armature_object = None
|
||||
mesh_object = None
|
||||
|
||||
if options.should_import_skeleton:
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new(options.name)
|
||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
||||
armature_data = bpy.data.armatures.new(name)
|
||||
armature_object = bpy.data.objects.new(name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
@@ -116,8 +121,8 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
|
||||
|
||||
# MESH
|
||||
if options.should_import_mesh:
|
||||
mesh_data = bpy.data.meshes.new(options.name)
|
||||
mesh_object = bpy.data.objects.new(options.name, mesh_data)
|
||||
mesh_data = bpy.data.meshes.new(name)
|
||||
mesh_object = bpy.data.objects.new(name, mesh_data)
|
||||
|
||||
# MATERIALS
|
||||
if options.should_import_materials:
|
||||
@@ -280,4 +285,7 @@ def import_psk(psk: Psk, context, options: PskImportOptions) -> PskImportResult:
|
||||
except:
|
||||
pass
|
||||
|
||||
result.armature_object = armature_object
|
||||
result.mesh_object = mesh_object
|
||||
|
||||
return result
|
||||
|
||||
@@ -47,6 +47,13 @@ def poly_flags_to_triangle_type_and_bit_flags(poly_flags: int) -> (str, set[str]
|
||||
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:
|
||||
should_import_vertex_colors: BoolProperty(
|
||||
default=True,
|
||||
@@ -76,11 +83,20 @@ class PskImportMixin:
|
||||
options=empty_set,
|
||||
description='Import extra UV maps, if available'
|
||||
)
|
||||
should_import_mesh: BoolProperty(
|
||||
default=True,
|
||||
name='Import Mesh',
|
||||
import_components: EnumProperty(
|
||||
name='Import Components',
|
||||
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(
|
||||
default=True,
|
||||
@@ -88,10 +104,8 @@ class PskImportMixin:
|
||||
options=empty_set,
|
||||
)
|
||||
should_import_skeleton: BoolProperty(
|
||||
default=True,
|
||||
name='Import Skeleton',
|
||||
options=empty_set,
|
||||
description='Import skeleton'
|
||||
get=should_import_skleton_get,
|
||||
)
|
||||
bone_length: FloatProperty(
|
||||
default=1.0,
|
||||
|
||||
Reference in New Issue
Block a user