1
0
mirror of https://github.com/DarklightGames/io_scene_psk_psa.git synced 2024-11-24 06:50:13 +01:00

WIP import functionality (PSK working, PSA in the works)

This commit is contained in:
Colin Basnett 2021-09-07 00:02:59 -07:00
parent 0622cf43e5
commit 4a9a921583
11 changed files with 327 additions and 18 deletions

View File

@ -16,55 +16,83 @@ if 'bpy' in locals():
importlib.reload(psk_data)
importlib.reload(psk_builder)
importlib.reload(psk_exporter)
importlib.reload(psk_importer)
importlib.reload(psk_reader)
importlib.reload(psk_operator)
importlib.reload(psa_data)
importlib.reload(psa_builder)
importlib.reload(psa_exporter)
importlib.reload(psa_operator)
importlib.reload(psa_reader)
importlib.reload(psa_importer)
else:
# if i remove this line, it can be enabled just fine
from .psk import data as psk_data
from .psk import builder as psk_builder
from .psk import exporter as psk_exporter
from .psk import reader as psk_reader
from .psk import importer as psk_importer
from .psk import operator as psk_operator
from .psa import data as psa_data
from .psa import builder as psa_builder
from .psa import exporter as psa_exporter
from .psa import operator as psa_operator
from .psa import reader as psa_reader
from .psa import importer as psa_importer
import bpy
from bpy.props import IntProperty, CollectionProperty
classes = [
psk_operator.PskExportOperator,
psk_operator.PskImportOperator,
psa_operator.PsaExportOperator,
psa_operator.PsaImportOperator,
psa_operator.PSA_UL_ActionList,
psa_operator.ActionListItem
psa_operator.PSA_UL_ImportActionList,
psa_operator.ActionListItem,
psa_operator.ImportActionListItem
]
def psk_menu_func(self, context):
def psk_export_menu_func(self, context):
self.layout.operator(psk_operator.PskExportOperator.bl_idname, text ='Unreal PSK (.psk)')
def psa_menu_func(self, context):
def psk_import_menu_func(self, context):
self.layout.operator(psk_operator.PskImportOperator.bl_idname, text ='Unreal PSK (.psk)')
def psa_export_menu_func(self, context):
self.layout.operator(psa_operator.PsaExportOperator.bl_idname, text='Unreal PSA (.psa)')
def psa_import_menu_func(self, context):
self.layout.operator(psa_operator.PsaImportOperator.bl_idname, text ='Unreal PSA (.psa)')
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_export.append(psk_menu_func)
bpy.types.TOPBAR_MT_file_export.append(psa_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_export.append(psa_export_menu_func)
bpy.types.TOPBAR_MT_file_import.append(psa_import_menu_func)
bpy.types.Scene.psa_action_list = CollectionProperty(type=psa_operator.ActionListItem)
bpy.types.Scene.psa_import_action_list = CollectionProperty(type=psa_operator.ImportActionListItem)
bpy.types.Scene.psa_action_list_index = IntProperty(name='index for list??', default=0)
bpy.types.Scene.psa_import_action_list_index = IntProperty(name='index for list??', default=0)
def unregister():
del bpy.types.Scene.psa_action_list_index
del bpy.types.Scene.psa_import_action_list_index
del bpy.types.Scene.psa_action_list
bpy.types.TOPBAR_MT_file_export.remove(psa_menu_func)
bpy.types.TOPBAR_MT_file_export.remove(psk_menu_func)
del bpy.types.Scene.psa_import_action_list
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_export.remove(psa_export_menu_func)
bpy.types.TOPBAR_MT_file_import.remove(psa_import_menu_func)
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -17,6 +17,12 @@ class Quaternion(Structure):
('w', c_float),
]
def __iter__(self):
yield self.x
yield self.y
yield self.z
yield self.w
class Section(Structure):
_fields_ = [

View File

@ -22,7 +22,7 @@ class Psa(object):
('bone_count', c_int32),
('root_include', c_int32),
('compression_style', c_int32),
('key_quotum', c_int32), # what the fuck is a quotum
('key_quotum', c_int32),
('key_reduction', c_float),
('track_time', c_float),
('fps', c_float),

View File

@ -0,0 +1,15 @@
import bpy
import bmesh
import mathutils
from .data import Psa
class PsaImporter(object):
def __init__(self):
pass
def import_psa(self, psa: Psa, context):
print('importing yay')
print(psa.sequences)
for sequence in psa.sequences:
print(sequence.name, sequence.frame_start_index, sequence.frame_count)

View File

@ -1,12 +1,23 @@
from bpy.types import Operator, Action, UIList, PropertyGroup
from bpy_extras.io_utils import ExportHelper
from bpy_extras.io_utils import ExportHelper, ImportHelper
from bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty
from .builder import PsaBuilder, PsaBuilderOptions
from .exporter import PsaExporter
from .reader import PsaReader
from .importer import PsaImporter
import bpy
import re
class ImportActionListItem(PropertyGroup):
action_name: StringProperty()
is_selected: BoolProperty(default=True)
@property
def name(self):
return self.action_name
class ActionListItem(PropertyGroup):
action: PointerProperty(type=Action)
is_selected: BoolProperty(default=False)
@ -16,6 +27,22 @@ class ActionListItem(PropertyGroup):
return self.action.name
class PSA_UL_ImportActionList(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
layout.alignment = 'LEFT'
layout.prop(item, 'is_selected', icon_only=True)
layout.label(text=item.action_name)
# def filter_items(self, context, data, property):
# # TODO: returns two lists, apparently
# actions = getattr(data, property)
# flt_flags = []
# flt_neworder = []
# if self.filter_name:
# flt_flags = bpy.types.UI_UL_list.filter_items_by_name(self.filter_name, self.bitflag_filter_item, actions, 'name', reverse=self.use_filter_invert)
# return flt_flags, flt_neworder
class PSA_UL_ActionList(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
layout.alignment = 'LEFT'
@ -37,8 +64,8 @@ class PsaExportOperator(Operator, ExportHelper):
bl_label = 'Export'
__doc__ = 'PSA Exporter (.psa)'
filename_ext = '.psa'
filter_glob : StringProperty(default='*.psa', options={'HIDDEN'})
filepath : StringProperty(
filter_glob: StringProperty(default='*.psa', options={'HIDDEN'})
filepath: StringProperty(
name='File Path',
description='File path used for exporting the PSA file',
maxlen=1024,
@ -103,3 +130,47 @@ class PsaExportOperator(Operator, ExportHelper):
exporter = PsaExporter(psk)
exporter.export(self.filepath)
return {'FINISHED'}
class PsaImportOperator(Operator, ImportHelper):
# TODO: list out the actions to be imported
bl_idname = 'import.psa'
bl_label = 'Import'
__doc__ = 'PSA Importer (.psa)'
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):
action_names = []
try:
action_names = PsaReader().scan_sequence_names(self.filepath)
except IOError:
pass
context.scene.psa_import_action_list.clear()
for action_name in action_names:
item = context.scene.psa_action_list.add()
item.action_name = action_name
item.is_selected = True
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def draw(self, context):
layout = self.layout
scene = context.scene
box = layout.box()
box.label(text='Actions', icon='ACTION')
row = box.row()
row.template_list('PSA_UL_ImportActionList', 'asd', scene, 'psa_import_action_list', scene, 'psa_import_action_list_index', rows=len(context.scene.psa_import_action_list))
def execute(self, context):
reader = PsaReader()
psa = reader.read(self.filepath)
PsaImporter().import_psa(psa, context)
return {'FINISHED'}

View File

@ -0,0 +1,53 @@
from .data import *
from typing import AnyStr
import ctypes
class PsaReader(object):
def __init__(self):
pass
@staticmethod
def read_types(fp, data_class: ctypes.Structure, section: Section, data):
buffer_length = section.data_size * section.data_count
buffer = fp.read(buffer_length)
offset = 0
for _ in range(section.data_count):
data.append(data_class.from_buffer_copy(buffer, offset))
offset += section.data_size
def scan_sequence_names(self, path) -> List[AnyStr]:
sequences = []
with open(path, 'rb') as fp:
if fp.read(8) != b'ANIMINFO':
raise IOError('Unexpected file format')
fp.seek(0, 0)
while fp.read(1):
fp.seek(-1, 1)
section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section)))
if section.name == b'ANIMINFO':
PsaReader.read_types(fp, Psa.Sequence, section, sequences)
return [sequence.name for sequence in sequences]
else:
fp.seek(section.data_size * section.data_count, 1)
return []
def read(self, path) -> Psa:
psa = Psa()
with open(path, 'rb') as fp:
while fp.read(1):
fp.seek(-1, 1)
section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section)))
if section.name == b'ANIMHEAD':
pass
elif section.name == b'BONENAMES':
PsaReader.read_types(fp, Psa.Bone, section, psa.bones)
elif section.name == b'ANIMINFO':
PsaReader.read_types(fp, Psa.Sequence, section, psa.sequences)
elif section.name == b'ANIMKEYS':
PsaReader.read_types(fp, Psa.Key, section, psa.keys)
else:
raise RuntimeError(f'Unrecognized section "{section.name}"')
return psa
1

View File

@ -159,9 +159,9 @@ class PskBuilder(object):
for f in object.data.loop_triangles:
face = Psk.Face()
face.material_index = material_indices[f.material_index]
face.wedge_index_1 = f.loops[2] + wedge_offset
face.wedge_index_2 = f.loops[1] + wedge_offset
face.wedge_index_3 = f.loops[0] + wedge_offset
face.wedge_indices[0] = f.loops[2] + wedge_offset
face.wedge_indices[1] = f.loops[1] + wedge_offset
face.wedge_indices[2] = f.loops[0] + wedge_offset
face.smoothing_groups = poly_groups[f.polygon_index]
psk.faces.append(face)
# update the material index of the wedges

View File

@ -25,9 +25,7 @@ class Psk(object):
class Face(Structure):
_fields_ = [
('wedge_index_1', c_int16),
('wedge_index_2', c_int16),
('wedge_index_3', c_int16),
('wedge_indices', c_int16 * 3),
('material_index', c_int8),
('aux_material_index', c_int8),
('smoothing_groups', c_int32)

View File

@ -0,0 +1,71 @@
import bpy
import bmesh
import mathutils
from .data import Psk
class PskImporter(object):
def __init__(self):
pass
def import_psk(self, psk: Psk, context):
# ARMATURE
armature_data = bpy.data.armatures.new('armature')
armature_object = bpy.data.objects.new('new_ao', armature_data)
context.scene.collection.objects.link(armature_object)
try:
bpy.ops.object.mode_set(mode='OBJECT')
except:
pass
armature_object.select_set(state=True)
bpy.context.view_layer.objects.active = armature_object
bpy.ops.object.mode_set(mode='EDIT')
for bone in psk.bones:
edit_bone = armature_data.edit_bones.new(bone.name.decode('utf-8'))
edit_bone.parent = armature_data.edit_bones[bone.parent_index]
edit_bone.head = (bone.location.x, bone.location.y, bone.location.z)
rotation = mathutils.Quaternion(*bone.rotation)
edit_bone.tail = edit_bone.head + (mathutils.Vector(0, 0, 1) @ rotation)
# MESH
mesh_data = bpy.data.meshes.new('mesh')
mesh_object = bpy.data.objects.new('new_mo', mesh_data)
# MATERIALS
for material in psk.materials:
bpy_material = bpy.data.materials.new(material.name.decode('utf-8'))
mesh_data.materials.append(bpy_material)
bm = bmesh.new()
# VERTICES
for point in psk.points:
bm.verts.new((point.x, point.y, point.z))
bm.verts.ensure_lookup_table()
for face in psk.faces:
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
bm_face = bm.faces.new(point_indices)
bm_face.material_index = face.material_index
bm.to_mesh(mesh_data)
# TEXTURE COORDINATES
data_index = 0
uv_layer = mesh_data.uv_layers.new()
for face_index, face in enumerate(psk.faces):
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
for wedge in face_wedges:
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
data_index += 1
bm.free()
# TODO: weights (vertex grorups etc.)
context.scene.collection.objects.link(mesh_object)

View File

@ -1,8 +1,29 @@
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from bpy_extras.io_utils import ExportHelper, ImportHelper
from bpy.props import StringProperty, BoolProperty, FloatProperty
from .builder import PskBuilder
from .exporter import PskExporter
from .reader import PskReader
from .importer import PskImporter
class PskImportOperator(Operator, ImportHelper):
bl_idname = 'import.psk'
bl_label = 'Export'
__doc__ = 'PSK Importer (.psk)'
filename_ext = '.psk'
filter_glob: StringProperty(default='*.psk', options={'HIDDEN'})
filepath: StringProperty(
name='File Path',
description='File path used for exporting the PSK file',
maxlen=1024,
default='')
def execute(self, context):
reader = PskReader()
psk = reader.read(self.filepath)
PskImporter().import_psk(psk, context)
return {'FINISHED'}
class PskExportOperator(Operator, ExportHelper):

View File

@ -0,0 +1,46 @@
from .data import *
import ctypes
class PskReader(object):
def __init__(self):
pass
@staticmethod
def read_types(fp, data_class: ctypes.Structure, section: Section, data):
buffer_length = section.data_size * section.data_count
buffer = fp.read(buffer_length)
offset = 0
for _ in range(section.data_count):
data.append(data_class.from_buffer_copy(buffer, offset))
offset += section.data_size
def read(self, path) -> Psk:
psk = Psk()
with open(path, 'rb') as fp:
while fp.read(1):
fp.seek(-1, 1)
section = Section.from_buffer_copy(fp.read(ctypes.sizeof(Section)))
if section.name == b'ACTRHEAD':
pass
elif section.name == b'PNTS0000':
PskReader.read_types(fp, Vector3, section, psk.points)
elif section.name == b'VTXW0000':
if section.data_size == ctypes.sizeof(Psk.Wedge16):
PskReader.read_types(fp, Psk.Wedge16, section, psk.wedges)
elif section.data_size == ctypes.sizeof(Psk.Wedge32):
PskReader.read_types(fp, Psk.Wedge32, section, psk.wedges)
else:
raise RuntimeError('Unrecognized wedge format')
elif section.name == b'FACE0000':
PskReader.read_types(fp, Psk.Face, section, psk.faces)
elif section.name == b'MATT0000':
PskReader.read_types(fp, Psk.Material, section, psk.materials)
elif section.name == b'REFSKELT':
PskReader.read_types(fp, Psk.Bone, section, psk.bones)
elif section.name == b'RAWWEIGHTS':
PskReader.read_types(fp, Psk.Weight, section, psk.weights)
else:
raise RuntimeError(f'Unrecognized section "{section.name}"')
return psk