Moved all the PSK data types to use ctypes (affords a major simplification of the export logic).

This commit is contained in:
Colin Basnett
2019-12-03 01:27:50 -08:00
parent e6604a41ff
commit e4a5cd74c6
3 changed files with 132 additions and 118 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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] = []