Moved all the PSK data types to use ctypes (affords a major simplification of the export logic).
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
138
src/psk.py
138
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] = []
|
||||
|
||||
Reference in New Issue
Block a user