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:
parent
bc47067f2f
commit
4e7536694c
47
src/__init__.py
Normal file
47
src/__init__.py
Normal 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
65
src/builder.py
Normal 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
82
src/exporter.py
Normal 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
74
src/psk.py
Normal 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] = []
|
Loading…
x
Reference in New Issue
Block a user