1
0
mirror of https://github.com/DarklightGames/io_scene_psk_psa.git synced 2025-02-21 11:39:37 +01:00

Added initial files.

This commit is contained in:
Colin Basnett 2019-12-01 18:18:05 -08:00
parent bc47067f2f
commit 4e7536694c
4 changed files with 268 additions and 0 deletions

47
src/__init__.py Normal file
View File

@ -0,0 +1,47 @@
bl_info = {
"name": "PSK/PSA Exporter",
"author": "Colin Basnett",
"version": ( 1, 0, 0 ),
"blender": ( 2, 80, 0 ),
"location": "File > Export > PSK Export (.psk)",
"description": "PSK/PSA Export (.psk)",
"warning": "",
"wiki_url": "https://github.com/DarklightGames/io_export_psk_psa",
"tracker_url": "https://github.com/DarklightGames/io_export_psk_psa/issues",
"category": "Import-Export"
}
if 'bpy' in locals():
import importlib
importlib.reload(psk)
importlib.reload(exporter)
importlib.reload(builder)
else:
# if i remove this line, it can be enabled just fine
from . import psk
from . import exporter
from . import builder
import bpy
classes = [
exporter.PskExportOperator
]
def menu_func(self, context):
self.layout.operator(exporter.PskExportOperator.bl_idname, text = "Unreal PSK (.psk)")
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
bpy.types.TOPBAR_MT_file_export.append(menu_func)
def unregister():
bpy.types.TOPBAR_MT_file_export.remove(menu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
if __name__ == '__main__':
register()

65
src/builder.py Normal file
View File

@ -0,0 +1,65 @@
import bpy
import bmesh
from .psk import Psk, Vector3
class PskBuilder(object):
def __init__(self):
pass
def build(self, context) -> Psk:
object = context.view_layer.objects.active
if object.type != 'MESH':
raise RuntimeError('Selected object must be a Mesh')
# 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
if armature_object is None:
raise RuntimeError('the armature modifier has no linked object')
# TODO: probably requires at least one bone? not sure
mesh_data = object.data
# TODO: if there is an edge-split modifier, we need to apply it.
# TODO: duplicate all the data
mesh = bpy.data.meshes.new('export')
# copy the contents of the mesh
bm = bmesh.new()
bm.from_mesh(mesh_data)
# triangulate everything
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(mesh)
bm.free()
del bm
psk = Psk()
# vertices
for vertex in mesh.vertices:
psk.points.append(Vector3(*vertex.co))
# TODO: wedges (a "wedge" is actually a UV'd vertex, basically)
# for wedge in mesh.wedges:
# pass
# materials
for i, m in enumerate(object.data.materials):
material = Psk.Material()
material.name = m.name
material.texture_index = i
psk.materials.append(material)
# TODO: should we make the wedges/faces at the same time??
f = Psk.Face()
# f.wedge_index_1 = 0
# TODO: weights
return psk

82
src/exporter.py Normal file
View File

@ -0,0 +1,82 @@
from bpy.types import Operator
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, FloatProperty
import struct
import io
from .psk import Psk
from .builder import PskBuilder
# https://me3explorer.fandom.com/wiki/PSK_File_Format
# https://docs.unrealengine.com/udk/Two/rsrc/Two/BinaryFormatSpecifications/UnrealAnimDataStructs.h
class PskExportOperator(Operator, ExportHelper):
bl_idname = 'export.psk'
bl_label = 'Export'
__doc__ = 'PSK Exporter (.psk)'
filename_ext = '.psk'
# filter_glob : StringProperty(default='*.psk', options={'HIDDEN'})
filepath : StringProperty(
name='File Path',
description='File path used for exporting the PSK file',
maxlen=1024,
default='')
def execute(self, context):
builder = PskBuilder()
psk = builder.build(context)
exporter = PskExporter(psk)
exporter.export(self.filepath)
return {'FINISHED'}
class PskExporter(object):
def __init__(self, psk: Psk):
self.psk: Psk = psk
@staticmethod
def write_section(f, id: bytes, data_size: int, data_count: int, data: bytes):
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 export(self, path: str):
with open(path, 'wb') as fp:
PskExporter.write_section(fp, b'ACTRHEAD', 0, 0, b'')
# 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())
# WEDGES
buffer = io.BytesIO()
if len(self.psk.wedges) <= 65536:
for w in self.psk.wedges:
write(buffer, 'ssffbbs', w.point_index, 0, w.u, w.v, w.material_index, 0, 0)
else:
for w in self.psk.wedges:
write(buffer, 'iffi', w.point_index, w.u, w.v, w.material_index)
fp.write(buffer.getvalue())
# FACES
buffer = io.BytesIO()
for f in self.psk.faces:
write(buffer, 'sssbbi', f.wedge_index_1, f.wedge_index_2, f.wedge_index_3, f.material_index,
f.aux_material_index, f.smoothing_groups)
fp.write(buffer.getvalue())
# MATERIALS
buffer = io.BytesIO()
fmt = '64s6i'
for m in self.psk.materials:
write(buffer, 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), buffer.getvalue())
def write(f, fmt, *values):
f.write(struct.pack(fmt, *values))

74
src/psk.py Normal file
View File

@ -0,0 +1,74 @@
from typing import List
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 Quaternion(object):
def __init__(self):
self.x = 0.0
self.y = 0.0
self.z = 0.0
self.w = 0.0
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 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
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 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 Weight(object):
def __init__(self):
self.weight = 0.0
self.point_index = -1
self.bone_index = -1
def __init__(self):
self.points = []
self.wedges: List[Psk.Wedge] = []
self.faces: List[Psk.Face] = []
self.materials: List[Psk.Material] = []
self.weights: List[Psk.Weight] = []
self.bones: List[Psk.Bone] = []