mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2025-02-17 18:19:15 +01:00
WIP import functionality (PSK working, PSA in the works)
This commit is contained in:
parent
0622cf43e5
commit
4a9a921583
@ -16,55 +16,83 @@ if 'bpy' in locals():
|
|||||||
importlib.reload(psk_data)
|
importlib.reload(psk_data)
|
||||||
importlib.reload(psk_builder)
|
importlib.reload(psk_builder)
|
||||||
importlib.reload(psk_exporter)
|
importlib.reload(psk_exporter)
|
||||||
|
importlib.reload(psk_importer)
|
||||||
|
importlib.reload(psk_reader)
|
||||||
importlib.reload(psk_operator)
|
importlib.reload(psk_operator)
|
||||||
importlib.reload(psa_data)
|
importlib.reload(psa_data)
|
||||||
importlib.reload(psa_builder)
|
importlib.reload(psa_builder)
|
||||||
importlib.reload(psa_exporter)
|
importlib.reload(psa_exporter)
|
||||||
importlib.reload(psa_operator)
|
importlib.reload(psa_operator)
|
||||||
|
importlib.reload(psa_reader)
|
||||||
|
importlib.reload(psa_importer)
|
||||||
else:
|
else:
|
||||||
# if i remove this line, it can be enabled just fine
|
# if i remove this line, it can be enabled just fine
|
||||||
from .psk import data as psk_data
|
from .psk import data as psk_data
|
||||||
from .psk import builder as psk_builder
|
from .psk import builder as psk_builder
|
||||||
from .psk import exporter as psk_exporter
|
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 .psk import operator as psk_operator
|
||||||
from .psa import data as psa_data
|
from .psa import data as psa_data
|
||||||
from .psa import builder as psa_builder
|
from .psa import builder as psa_builder
|
||||||
from .psa import exporter as psa_exporter
|
from .psa import exporter as psa_exporter
|
||||||
from .psa import operator as psa_operator
|
from .psa import operator as psa_operator
|
||||||
|
from .psa import reader as psa_reader
|
||||||
|
from .psa import importer as psa_importer
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.props import IntProperty, CollectionProperty
|
from bpy.props import IntProperty, CollectionProperty
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
psk_operator.PskExportOperator,
|
psk_operator.PskExportOperator,
|
||||||
|
psk_operator.PskImportOperator,
|
||||||
psa_operator.PsaExportOperator,
|
psa_operator.PsaExportOperator,
|
||||||
|
psa_operator.PsaImportOperator,
|
||||||
psa_operator.PSA_UL_ActionList,
|
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)')
|
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)')
|
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():
|
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_menu_func)
|
bpy.types.TOPBAR_MT_file_export.append(psk_export_menu_func)
|
||||||
bpy.types.TOPBAR_MT_file_export.append(psa_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_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_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():
|
def unregister():
|
||||||
del bpy.types.Scene.psa_action_list_index
|
del bpy.types.Scene.psa_action_list_index
|
||||||
|
del bpy.types.Scene.psa_import_action_list_index
|
||||||
del bpy.types.Scene.psa_action_list
|
del bpy.types.Scene.psa_action_list
|
||||||
bpy.types.TOPBAR_MT_file_export.remove(psa_menu_func)
|
del bpy.types.Scene.psa_import_action_list
|
||||||
bpy.types.TOPBAR_MT_file_export.remove(psk_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_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)
|
||||||
|
|
||||||
|
@ -17,6 +17,12 @@ class Quaternion(Structure):
|
|||||||
('w', c_float),
|
('w', c_float),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self.x
|
||||||
|
yield self.y
|
||||||
|
yield self.z
|
||||||
|
yield self.w
|
||||||
|
|
||||||
|
|
||||||
class Section(Structure):
|
class Section(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
|
@ -22,7 +22,7 @@ class Psa(object):
|
|||||||
('bone_count', c_int32),
|
('bone_count', c_int32),
|
||||||
('root_include', c_int32),
|
('root_include', c_int32),
|
||||||
('compression_style', c_int32),
|
('compression_style', c_int32),
|
||||||
('key_quotum', c_int32), # what the fuck is a quotum
|
('key_quotum', c_int32),
|
||||||
('key_reduction', c_float),
|
('key_reduction', c_float),
|
||||||
('track_time', c_float),
|
('track_time', c_float),
|
||||||
('fps', c_float),
|
('fps', c_float),
|
||||||
|
15
io_export_psk_psa/psa/importer.py
Normal file
15
io_export_psk_psa/psa/importer.py
Normal 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)
|
@ -1,12 +1,23 @@
|
|||||||
from bpy.types import Operator, Action, UIList, PropertyGroup
|
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 bpy.props import StringProperty, BoolProperty, CollectionProperty, PointerProperty
|
||||||
from .builder import PsaBuilder, PsaBuilderOptions
|
from .builder import PsaBuilder, PsaBuilderOptions
|
||||||
from .exporter import PsaExporter
|
from .exporter import PsaExporter
|
||||||
|
from .reader import PsaReader
|
||||||
|
from .importer import PsaImporter
|
||||||
import bpy
|
import bpy
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class ImportActionListItem(PropertyGroup):
|
||||||
|
action_name: StringProperty()
|
||||||
|
is_selected: BoolProperty(default=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.action_name
|
||||||
|
|
||||||
|
|
||||||
class ActionListItem(PropertyGroup):
|
class ActionListItem(PropertyGroup):
|
||||||
action: PointerProperty(type=Action)
|
action: PointerProperty(type=Action)
|
||||||
is_selected: BoolProperty(default=False)
|
is_selected: BoolProperty(default=False)
|
||||||
@ -16,6 +27,22 @@ class ActionListItem(PropertyGroup):
|
|||||||
return self.action.name
|
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):
|
class PSA_UL_ActionList(UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
layout.alignment = 'LEFT'
|
layout.alignment = 'LEFT'
|
||||||
@ -37,8 +64,8 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
bl_label = 'Export'
|
bl_label = 'Export'
|
||||||
__doc__ = 'PSA Exporter (.psa)'
|
__doc__ = 'PSA Exporter (.psa)'
|
||||||
filename_ext = '.psa'
|
filename_ext = '.psa'
|
||||||
filter_glob : StringProperty(default='*.psa', options={'HIDDEN'})
|
filter_glob: StringProperty(default='*.psa', options={'HIDDEN'})
|
||||||
filepath : StringProperty(
|
filepath: StringProperty(
|
||||||
name='File Path',
|
name='File Path',
|
||||||
description='File path used for exporting the PSA file',
|
description='File path used for exporting the PSA file',
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
@ -103,3 +130,47 @@ class PsaExportOperator(Operator, ExportHelper):
|
|||||||
exporter = PsaExporter(psk)
|
exporter = PsaExporter(psk)
|
||||||
exporter.export(self.filepath)
|
exporter.export(self.filepath)
|
||||||
return {'FINISHED'}
|
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'}
|
||||||
|
53
io_export_psk_psa/psa/reader.py
Normal file
53
io_export_psk_psa/psa/reader.py
Normal 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
|
@ -159,9 +159,9 @@ class PskBuilder(object):
|
|||||||
for f in object.data.loop_triangles:
|
for f in object.data.loop_triangles:
|
||||||
face = Psk.Face()
|
face = Psk.Face()
|
||||||
face.material_index = material_indices[f.material_index]
|
face.material_index = material_indices[f.material_index]
|
||||||
face.wedge_index_1 = f.loops[2] + wedge_offset
|
face.wedge_indices[0] = f.loops[2] + wedge_offset
|
||||||
face.wedge_index_2 = f.loops[1] + wedge_offset
|
face.wedge_indices[1] = f.loops[1] + wedge_offset
|
||||||
face.wedge_index_3 = f.loops[0] + wedge_offset
|
face.wedge_indices[2] = f.loops[0] + wedge_offset
|
||||||
face.smoothing_groups = poly_groups[f.polygon_index]
|
face.smoothing_groups = poly_groups[f.polygon_index]
|
||||||
psk.faces.append(face)
|
psk.faces.append(face)
|
||||||
# update the material index of the wedges
|
# update the material index of the wedges
|
||||||
|
@ -25,9 +25,7 @@ class Psk(object):
|
|||||||
|
|
||||||
class Face(Structure):
|
class Face(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
('wedge_index_1', c_int16),
|
('wedge_indices', c_int16 * 3),
|
||||||
('wedge_index_2', c_int16),
|
|
||||||
('wedge_index_3', c_int16),
|
|
||||||
('material_index', c_int8),
|
('material_index', c_int8),
|
||||||
('aux_material_index', c_int8),
|
('aux_material_index', c_int8),
|
||||||
('smoothing_groups', c_int32)
|
('smoothing_groups', c_int32)
|
||||||
|
71
io_export_psk_psa/psk/importer.py
Normal file
71
io_export_psk_psa/psk/importer.py
Normal 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)
|
@ -1,8 +1,29 @@
|
|||||||
from bpy.types import Operator
|
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 bpy.props import StringProperty, BoolProperty, FloatProperty
|
||||||
from .builder import PskBuilder
|
from .builder import PskBuilder
|
||||||
from .exporter import PskExporter
|
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):
|
class PskExportOperator(Operator, ExportHelper):
|
||||||
|
46
io_export_psk_psa/psk/reader.py
Normal file
46
io_export_psk_psa/psk/reader.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user