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
|
# 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
|
||||||
|
|||||||
@@ -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))
|
|
||||||
|
|||||||
138
src/psk.py
138
src/psk.py
@@ -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] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user