diff --git a/src/builder.py b/src/builder.py index 1225542..4dd427b 100644 --- a/src/builder.py +++ b/src/builder.py @@ -19,8 +19,10 @@ class PskBuilder(object): # ensure that there is exactly one armature modifier modifiers = [x for x in object.modifiers if x.type == 'ARMATURE'] + if len(modifiers) != 1: raise RuntimeError('the mesh must have one armature modifier') + armature_modifier = modifiers[0] armature_object = armature_modifier.object @@ -48,11 +50,19 @@ class PskBuilder(object): # VERTICES for vertex in object.data.vertices: - psk.points.append(Vector3(*vertex.co)) + point = Vector3() + point.x = vertex.co.x + point.y = vertex.co.y + point.z = vertex.co.z + psk.points.append(point) # WEDGES uv_layer = object.data.uv_layers.active.data - psk.wedges = [psk.Wedge() for _ in range(len(object.data.loops))] + if len(object.data.loops) <= 65536: + wedge_type = Psk.Wedge16 + else: + wedge_type = Psk.Wedge32 + psk.wedges = [wedge_type() for _ in range(len(object.data.loops))] for loop_index, loop in enumerate(object.data.loops): wedge = psk.wedges[loop_index] @@ -64,11 +74,12 @@ class PskBuilder(object): # MATERIALS for i, m in enumerate(object.data.materials): material = Psk.Material() - material.name = m.name + material.name = bytes(m.name, encoding='utf-8') material.texture_index = i psk.materials.append(material) # FACES + # TODO: this is making the assumption that the mesh is triangulated object.data.calc_loop_triangles() poly_groups, groups = object.data.calc_smooth_groups(use_bitflags=True) for f in object.data.loop_triangles: @@ -87,7 +98,7 @@ class PskBuilder(object): bone_list = list(armature_object.data.bones) for b in armature_object.data.bones: bone = psk.Bone() - bone.name = b.name + bone.name = bytes(b.name, encoding='utf-8') bone.children_count = len(b.children) bone.flags = 0 # look up what this is bone.length = 10.0 # TODO: not sure what this is @@ -96,15 +107,20 @@ class PskBuilder(object): except ValueError: # this should be -1? bone.parent_index = 0 - rotation = b.matrix.to_quaternion() - bone.position.x = b.head.x - bone.position.y = b.head.y - bone.position.z = b.head.z + bone.position.x = b.head_local.x + bone.position.y = b.head_local.y + bone.position.z = b.head_local.z + print(bone.name) + print(bone.position.x) + print(bone.position.y) + print(bone.position.z) + print('----') + rotation = b.matrix_local.to_quaternion() bone.rotation.x = rotation.x bone.rotation.y = rotation.y bone.rotation.z = rotation.z bone.rotation.w = rotation.w - # TODO: not sure what "size" is supposed to be + # TODO: not sure what "size" is supposed to be exactly bone.size.x = 1 bone.size.y = 1 bone.size.z = 1 diff --git a/src/exporter.py b/src/exporter.py index 9cb000c..7fc4c74 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -1,9 +1,11 @@ from bpy.types import Operator from bpy_extras.io_utils import ExportHelper from bpy.props import StringProperty, BoolProperty, FloatProperty +import ctypes import struct import io -from .psk import Psk +from typing import Type +from .psk import Psk, Vector3, Quaternion from .builder import PskBuilder # https://me3explorer.fandom.com/wiki/PSK_File_Format @@ -34,72 +36,42 @@ class PskExporter(object): self.psk: Psk = psk @staticmethod - def write_section(f, id: bytes, data_size: int, data_count: int, data: bytes): - # TODO: we should use ctypes, would be cleaner and faster - write(f, '20s', bytearray(id).ljust(20, b'\0')) - write(f, 'I', 1999801) - write(f, 'I', data_size) - write(f, 'I', data_count) - f.write(data) - + def write_section(fp, name: bytes, data_type: Type[ctypes.Structure] = None, data: list = None): + section = Psk.Section() + section.name = name + if data_type is not None and data is not None: + section.data_size = ctypes.sizeof(data_type) + section.data_count = len(data) + fp.write(section) + if data is not None: + for datum in data: + fp.write(datum) def export(self, path: str): + # TODO: add logic somewhere to assert lengths of ctype structs (pack1) with open(path, 'wb') as fp: - PskExporter.write_section(fp, b'ACTRHEAD', 0, 0, b'') + self.write_section(fp, b'ACTRHEAD') # POINTS - data = io.BytesIO() - fmt = '3f' - for point in self.psk.points: - write(data, fmt, point.x, point.y, point.z) - PskExporter.write_section(fp, b'PNTS0000', struct.calcsize(fmt), len(self.psk.points), data.getvalue()) + self.write_section(fp, b'PNTS0000', Vector3, self.psk.points) # WEDGES - data = io.BytesIO() + # TODO: would be nice to have this implicit! if len(self.psk.wedges) <= 65536: - fmt = 'hhffbbh' - for w in self.psk.wedges: - # NOTE: there's some sort of problem here where the wedges mtl indx is wrong - # in the documentation. - write(data, fmt, w.point_index, 0, w.u, w.v, w.material_index, w.material_index, 0) + wedge_type = Psk.Wedge16 else: - fmt = 'iffi' - for w in self.psk.wedges: - write(data, fmt, w.point_index, w.u, w.v, w.material_index) - PskExporter.write_section(fp, b'VTXW0000', struct.calcsize(fmt), len(self.psk.wedges), data.getvalue()) + wedge_type = Psk.Wedge32 + + self.write_section(fp, b'VTXW0000', wedge_type, self.psk.wedges) # FACES - data = io.BytesIO() - fmt = 'HHHbbi' - for f in self.psk.faces: - write(data, fmt, f.wedge_index_1, f.wedge_index_2, f.wedge_index_3, f.material_index, - f.aux_material_index, f.smoothing_groups) - PskExporter.write_section(fp, b'FACE0000', struct.calcsize(fmt), len(self.psk.faces), data.getvalue()) + self.write_section(fp, b'FACE0000', Psk.Face, self.psk.faces) # MATERIALS - data = io.BytesIO() - fmt = '64s6i' - for m in self.psk.materials: - write(data, fmt, bytes(m.name, encoding='utf-8'), m.texture_index, m.poly_flags, m.aux_material_index, m.aux_flags, m.lod_bias, m.lod_style) - self.write_section(fp, b'MATT0000', struct.calcsize(fmt), len(self.psk.materials), data.getvalue()) + self.write_section(fp, b'MATT0000', Psk.Material, self.psk.materials) # BONES - data = io.BytesIO() - fmt = '64s3i11f' - for b in self.psk.bones: - write(data, fmt, bytes(b.name, encoding='utf-8'), - b.flags, b.children_count, b.parent_index, b.rotation.x, b.rotation.y, b.rotation.z, - b.rotation.w, b.position.x, b.position.y, b.position.z, b.length, b.size.x, b.size.y, b.size.z) - self.write_section(fp, b'REFSKELT', struct.calcsize(fmt), len(self.psk.bones), data.getvalue()) + self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones) # WEIGHTS - data = io.BytesIO() - fmt = 'f2i' - for w in self.psk.weights: - print(w.weight, w.point_index, w.bone_index) - write(data, fmt, w.weight, w.point_index, w.bone_index) - self.write_section(fp, b'RAWWEIGHTS', struct.calcsize(fmt), len(self.psk.weights), data.getvalue()) - - -def write(f, fmt, *values): - f.write(struct.pack(fmt, *values)) + self.write_section(fp, b'RAWWEIGHTS', Psk.Weight, self.psk.weights) diff --git a/src/psk.py b/src/psk.py index 6250c24..64280ea 100644 --- a/src/psk.py +++ b/src/psk.py @@ -1,73 +1,99 @@ from typing import List +from ctypes import * -class Vector3(object): - def __init__(self, x = 0, y = 0, z = 0): - self.x = x - self.y = y - self.z = z - - def __iter__(self): - yield self.x - yield self.y - yield self.z +class Vector3(Structure): + _fields_ = [ + ('x', c_float), + ('y', c_float), + ('z', c_float), + ] -class Quaternion(object): - def __init__(self): - self.x = 0.0 - self.y = 0.0 - self.z = 0.0 - self.w = 0.0 +class Quaternion(Structure): + _fields_ = [ + ('x', c_float), + ('y', c_float), + ('z', c_float), + ('w', c_float), + ] class Psk(object): - class Wedge(object): - def __init__(self): - self.point_index = -1 - self.u = 0.0 - self.v = 0.0 - self.material_index = -1 + class Section(Structure): + _fields_ = [ + ('name', c_char * 20), + ('type_flags', c_int32), + ('data_size', c_int32), + ('data_count', c_int32) + ] - class Face(object): - def __init__(self): - self.wedge_index_1 = -1 - self.wedge_index_2 = -1 - self.wedge_index_3 = -1 - self.material_index = -1 - self.aux_material_index = -1 - self.smoothing_groups = -1 + def __init__(self, *args, **kw): + super().__init__(*args, **kw) + self.type_flags = 1999801 - class Material(object): - def __init__(self): - self.name = '' - self.texture_index = -1 - self.poly_flags = 0 - self.aux_material_index = -1 - self.aux_flags = -1 - self.lod_bias = 0 - self.lod_style = 0 + class Wedge16(Structure): + _fields_ = [ + ('point_index', c_int16), + ('padding1', c_int16), + ('u', c_float), + ('v', c_float), + ('material_index', c_int8), + ('reserved', c_int8), + ('padding2', c_int16) + ] - class Bone(object): - def __init__(self): - self.name = '' - self.flags = 0 - self.children_count = 0 - self.parent_index = -1 - self.rotation = Quaternion() - self.position = Vector3() - self.length = 0.0 - self.size = Vector3() + class Wedge32(Structure): + _fields_ = [ + ('point_index', c_int32), + ('u', c_float), + ('v', c_float), + ('material_index', c_int32) + ] - class Weight(object): - def __init__(self): - self.weight = 0.0 - self.point_index = -1 - self.bone_index = -1 + class Face(Structure): + _fields_ = [ + ('wedge_index_1', c_int16), + ('wedge_index_2', c_int16), + ('wedge_index_3', c_int16), + ('material_index', c_int8), + ('aux_material_index', c_int8), + ('smoothing_groups', c_int32) + ] + + class Material(Structure): + _fields_ = [ + ('name', c_char * 64), + ('texture_index', c_int32), + ('poly_flags', c_int32), + ('aux_material', c_int32), + ('aux_flags', c_int32), + ('lod_bias', c_int32), + ('lod_style', c_int32) + ] + + class Bone(Structure): + _fields_ = [ + ('name', c_char * 64), + ('flags', c_int32), + ('children_count', c_int32), + ('parent_index', c_int32), + ('rotation', Quaternion), + ('position', Vector3), + ('length', c_float), + ('size', Vector3) + ] + + class Weight(Structure): + _fields_ = [ + ('weight', c_float), + ('point_index', c_int32), + ('bone_index', c_int32), + ] def __init__(self): - self.points = [] - self.wedges: List[Psk.Wedge] = [] + self.points: List[Vector3] = [] + self.wedges: List[Psk.Wedge16] = [] self.faces: List[Psk.Face] = [] self.materials: List[Psk.Material] = [] self.weights: List[Psk.Weight] = []