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 # ensure that there is exactly one armature modifier
modifiers = [x for x in object.modifiers if x.type == 'ARMATURE'] modifiers = [x for x in object.modifiers if x.type == 'ARMATURE']
if len(modifiers) != 1: if len(modifiers) != 1:
raise RuntimeError('the mesh must have one armature modifier') raise RuntimeError('the mesh must have one armature modifier')
armature_modifier = modifiers[0] armature_modifier = modifiers[0]
armature_object = armature_modifier.object armature_object = armature_modifier.object
@@ -48,11 +50,19 @@ class PskBuilder(object):
# VERTICES # VERTICES
for vertex in object.data.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 # WEDGES
uv_layer = object.data.uv_layers.active.data 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): for loop_index, loop in enumerate(object.data.loops):
wedge = psk.wedges[loop_index] wedge = psk.wedges[loop_index]
@@ -64,11 +74,12 @@ class PskBuilder(object):
# MATERIALS # MATERIALS
for i, m in enumerate(object.data.materials): for i, m in enumerate(object.data.materials):
material = Psk.Material() material = Psk.Material()
material.name = m.name material.name = bytes(m.name, encoding='utf-8')
material.texture_index = i material.texture_index = i
psk.materials.append(material) psk.materials.append(material)
# FACES # FACES
# TODO: this is making the assumption that the mesh is triangulated
object.data.calc_loop_triangles() object.data.calc_loop_triangles()
poly_groups, groups = object.data.calc_smooth_groups(use_bitflags=True) poly_groups, groups = object.data.calc_smooth_groups(use_bitflags=True)
for f in object.data.loop_triangles: for f in object.data.loop_triangles:
@@ -87,7 +98,7 @@ class PskBuilder(object):
bone_list = list(armature_object.data.bones) bone_list = list(armature_object.data.bones)
for b in armature_object.data.bones: for b in armature_object.data.bones:
bone = psk.Bone() bone = psk.Bone()
bone.name = b.name bone.name = bytes(b.name, encoding='utf-8')
bone.children_count = len(b.children) bone.children_count = len(b.children)
bone.flags = 0 # look up what this is bone.flags = 0 # look up what this is
bone.length = 10.0 # TODO: not sure what this is bone.length = 10.0 # TODO: not sure what this is
@@ -96,15 +107,20 @@ class PskBuilder(object):
except ValueError: except ValueError:
# this should be -1? # this should be -1?
bone.parent_index = 0 bone.parent_index = 0
rotation = b.matrix.to_quaternion() bone.position.x = b.head_local.x
bone.position.x = b.head.x bone.position.y = b.head_local.y
bone.position.y = b.head.y bone.position.z = b.head_local.z
bone.position.z = b.head.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.x = rotation.x
bone.rotation.y = rotation.y bone.rotation.y = rotation.y
bone.rotation.z = rotation.z bone.rotation.z = rotation.z
bone.rotation.w = rotation.w 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.x = 1
bone.size.y = 1 bone.size.y = 1
bone.size.z = 1 bone.size.z = 1

View File

@@ -1,9 +1,11 @@
from bpy.types import Operator from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, FloatProperty from bpy.props import StringProperty, BoolProperty, FloatProperty
import ctypes
import struct import struct
import io import io
from .psk import Psk from typing import Type
from .psk import Psk, Vector3, Quaternion
from .builder import PskBuilder from .builder import PskBuilder
# https://me3explorer.fandom.com/wiki/PSK_File_Format # https://me3explorer.fandom.com/wiki/PSK_File_Format
@@ -34,72 +36,42 @@ class PskExporter(object):
self.psk: Psk = psk self.psk: Psk = psk
@staticmethod @staticmethod
def write_section(f, id: bytes, data_size: int, data_count: int, data: bytes): def write_section(fp, name: bytes, data_type: Type[ctypes.Structure] = None, data: list = None):
# TODO: we should use ctypes, would be cleaner and faster section = Psk.Section()
write(f, '20s', bytearray(id).ljust(20, b'\0')) section.name = name
write(f, 'I', 1999801) if data_type is not None and data is not None:
write(f, 'I', data_size) section.data_size = ctypes.sizeof(data_type)
write(f, 'I', data_count) section.data_count = len(data)
f.write(data) fp.write(section)
if data is not None:
for datum in data:
fp.write(datum)
def export(self, path: str): def export(self, path: str):
# TODO: add logic somewhere to assert lengths of ctype structs (pack1)
with open(path, 'wb') as fp: with open(path, 'wb') as fp:
PskExporter.write_section(fp, b'ACTRHEAD', 0, 0, b'') self.write_section(fp, b'ACTRHEAD')
# POINTS # POINTS
data = io.BytesIO() self.write_section(fp, b'PNTS0000', Vector3, self.psk.points)
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())
# WEDGES # WEDGES
data = io.BytesIO() # TODO: would be nice to have this implicit!
if len(self.psk.wedges) <= 65536: if len(self.psk.wedges) <= 65536:
fmt = 'hhffbbh' wedge_type = Psk.Wedge16
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)
else: else:
fmt = 'iffi' wedge_type = Psk.Wedge32
for w in self.psk.wedges:
write(data, fmt, w.point_index, w.u, w.v, w.material_index) self.write_section(fp, b'VTXW0000', wedge_type, self.psk.wedges)
PskExporter.write_section(fp, b'VTXW0000', struct.calcsize(fmt), len(self.psk.wedges), data.getvalue())
# FACES # FACES
data = io.BytesIO() self.write_section(fp, b'FACE0000', Psk.Face, self.psk.faces)
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())
# MATERIALS # MATERIALS
data = io.BytesIO() self.write_section(fp, b'MATT0000', Psk.Material, self.psk.materials)
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())
# BONES # BONES
data = io.BytesIO() self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones)
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())
# WEIGHTS # WEIGHTS
data = io.BytesIO() self.write_section(fp, b'RAWWEIGHTS', Psk.Weight, self.psk.weights)
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))

View File

@@ -1,73 +1,99 @@
from typing import List from typing import List
from ctypes import *
class Vector3(object): class Vector3(Structure):
def __init__(self, x = 0, y = 0, z = 0): _fields_ = [
self.x = x ('x', c_float),
self.y = y ('y', c_float),
self.z = z ('z', c_float),
]
def __iter__(self):
yield self.x
yield self.y
yield self.z
class Quaternion(object): class Quaternion(Structure):
def __init__(self): _fields_ = [
self.x = 0.0 ('x', c_float),
self.y = 0.0 ('y', c_float),
self.z = 0.0 ('z', c_float),
self.w = 0.0 ('w', c_float),
]
class Psk(object): class Psk(object):
class Wedge(object): class Section(Structure):
def __init__(self): _fields_ = [
self.point_index = -1 ('name', c_char * 20),
self.u = 0.0 ('type_flags', c_int32),
self.v = 0.0 ('data_size', c_int32),
self.material_index = -1 ('data_count', c_int32)
]
class Face(object): def __init__(self, *args, **kw):
def __init__(self): super().__init__(*args, **kw)
self.wedge_index_1 = -1 self.type_flags = 1999801
self.wedge_index_2 = -1
self.wedge_index_3 = -1
self.material_index = -1
self.aux_material_index = -1
self.smoothing_groups = -1
class Material(object): class Wedge16(Structure):
def __init__(self): _fields_ = [
self.name = '' ('point_index', c_int16),
self.texture_index = -1 ('padding1', c_int16),
self.poly_flags = 0 ('u', c_float),
self.aux_material_index = -1 ('v', c_float),
self.aux_flags = -1 ('material_index', c_int8),
self.lod_bias = 0 ('reserved', c_int8),
self.lod_style = 0 ('padding2', c_int16)
]
class Bone(object): class Wedge32(Structure):
def __init__(self): _fields_ = [
self.name = '' ('point_index', c_int32),
self.flags = 0 ('u', c_float),
self.children_count = 0 ('v', c_float),
self.parent_index = -1 ('material_index', c_int32)
self.rotation = Quaternion() ]
self.position = Vector3()
self.length = 0.0
self.size = Vector3()
class Weight(object): class Face(Structure):
def __init__(self): _fields_ = [
self.weight = 0.0 ('wedge_index_1', c_int16),
self.point_index = -1 ('wedge_index_2', c_int16),
self.bone_index = -1 ('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): def __init__(self):
self.points = [] self.points: List[Vector3] = []
self.wedges: List[Psk.Wedge] = [] self.wedges: List[Psk.Wedge16] = []
self.faces: List[Psk.Face] = [] self.faces: List[Psk.Face] = []
self.materials: List[Psk.Material] = [] self.materials: List[Psk.Material] = []
self.weights: List[Psk.Weight] = [] self.weights: List[Psk.Weight] = []