* PSK importer now working
* Fleshing out PSA importer (not done yet but getting there)
This commit is contained in:
@@ -36,8 +36,8 @@ class Psk(object):
|
||||
class Face(Structure):
|
||||
_fields_ = [
|
||||
('wedge_indices', c_uint16 * 3),
|
||||
('material_index', c_int8),
|
||||
('aux_material_index', c_int8),
|
||||
('material_index', c_uint8),
|
||||
('aux_material_index', c_uint8),
|
||||
('smoothing_groups', c_int32)
|
||||
]
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
from typing import Type
|
||||
from .data import *
|
||||
from .builder import PskBuilder
|
||||
from typing import Type
|
||||
from bpy.types import Operator
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
from bpy.props import StringProperty
|
||||
|
||||
MAX_WEDGE_COUNT = 65536
|
||||
MAX_POINT_COUNT = 4294967296
|
||||
MAX_BONE_COUNT = 256
|
||||
MAX_MATERIAL_COUNT = 256
|
||||
|
||||
|
||||
class PskExporter(object):
|
||||
|
||||
def __init__(self, psk: Psk):
|
||||
self.psk: Psk = psk
|
||||
|
||||
@@ -19,27 +29,61 @@ class PskExporter(object):
|
||||
fp.write(datum)
|
||||
|
||||
def export(self, path: str):
|
||||
if len(self.psk.wedges) > MAX_WEDGE_COUNT:
|
||||
raise RuntimeError(f'Number of wedges ({len(self.psk.wedges)}) exceeds limit of {MAX_WEDGE_COUNT}')
|
||||
if len(self.psk.bones) > MAX_BONE_COUNT:
|
||||
raise RuntimeError(f'Number of bones ({len(self.psk.bones)}) exceeds limit of {MAX_BONE_COUNT}')
|
||||
if len(self.psk.points) > MAX_POINT_COUNT:
|
||||
raise RuntimeError(f'Numbers of vertices ({len(self.psk.points)}) exceeds limit of {MAX_POINT_COUNT}')
|
||||
if len(self.psk.materials) > MAX_MATERIAL_COUNT:
|
||||
raise RuntimeError(f'Number of materials ({len(self.psk.materials)}) exceeds limit of {MAX_MATERIAL_COUNT}')
|
||||
|
||||
with open(path, 'wb') as fp:
|
||||
self.write_section(fp, b'ACTRHEAD')
|
||||
self.write_section(fp, b'PNTS0000', Vector3, self.psk.points)
|
||||
|
||||
# WEDGES
|
||||
if len(self.psk.wedges) <= 65536:
|
||||
wedge_type = Psk.Wedge16
|
||||
else:
|
||||
wedge_type = Psk.Wedge32
|
||||
|
||||
wedges = []
|
||||
for index, w in enumerate(self.psk.wedges):
|
||||
wedge = wedge_type()
|
||||
wedge = Psk.Wedge16()
|
||||
wedge.material_index = w.material_index
|
||||
wedge.u = w.u
|
||||
wedge.v = w.v
|
||||
wedge.point_index = w.point_index
|
||||
wedges.append(wedge)
|
||||
|
||||
self.write_section(fp, b'VTXW0000', wedge_type, wedges)
|
||||
self.write_section(fp, b'VTXW0000', Psk.Wedge16, wedges)
|
||||
self.write_section(fp, b'FACE0000', Psk.Face, self.psk.faces)
|
||||
self.write_section(fp, b'MATT0000', Psk.Material, self.psk.materials)
|
||||
self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones)
|
||||
self.write_section(fp, b'RAWWEIGHTS', Psk.Weight, self.psk.weights)
|
||||
|
||||
|
||||
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 invoke(self, context, event):
|
||||
try:
|
||||
PskBuilder.get_input_objects(context)
|
||||
except RuntimeError as e:
|
||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
builder = PskBuilder()
|
||||
psk = builder.build(context)
|
||||
exporter = PskExporter(psk)
|
||||
exporter.export(self.filepath)
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import os
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
from typing import Optional
|
||||
from .data import Psk
|
||||
from mathutils import Quaternion, Vector, Matrix
|
||||
from .reader import PskReader
|
||||
from bpy.props import StringProperty
|
||||
from bpy.types import Operator
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
|
||||
class PskImporter(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def import_psk(self, psk: Psk, context):
|
||||
def import_psk(self, psk: Psk, name: str, context):
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new('armature')
|
||||
armature_object = bpy.data.objects.new('new_ao', armature_data)
|
||||
armature_data = bpy.data.armatures.new(name)
|
||||
armature_object = bpy.data.objects.new(name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
|
||||
try:
|
||||
@@ -24,19 +32,68 @@ class PskImporter(object):
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
for bone in psk.bones:
|
||||
edit_bone = armature_data.edit_bones.new(bone.name.decode('utf-8'))
|
||||
edit_bone.parent = armature_data.edit_bones[bone.parent_index]
|
||||
edit_bone.head = (bone.location.x, bone.location.y, bone.location.z)
|
||||
rotation = mathutils.Quaternion(*bone.rotation)
|
||||
edit_bone.tail = edit_bone.head + (mathutils.Vector(0, 0, 1) @ rotation)
|
||||
# Intermediate bone type for the purpose of construction.
|
||||
class ImportBone(object):
|
||||
def __init__(self, index: int, psk_bone: Psk.Bone):
|
||||
self.index: int = index
|
||||
self.psk_bone: Psk.Bone = psk_bone
|
||||
self.parent: Optional[ImportBone] = None
|
||||
self.local_rotation: Quaternion = Quaternion()
|
||||
self.local_translation: Vector = Vector()
|
||||
self.world_rotation_matrix: Matrix = Matrix()
|
||||
self.world_matrix: Matrix = Matrix()
|
||||
self.vertex_group = None
|
||||
|
||||
import_bones = []
|
||||
should_invert_root = False
|
||||
new_bone_size = 8.0
|
||||
|
||||
for bone_index, psk_bone in enumerate(psk.bones):
|
||||
import_bone = ImportBone(bone_index, psk_bone)
|
||||
psk_bone.parent_index = max(0, psk_bone.parent_index)
|
||||
import_bone.local_rotation = Quaternion(tuple(psk_bone.rotation))
|
||||
import_bone.local_translation = Vector(tuple(psk_bone.location))
|
||||
if psk_bone.parent_index == 0 and bone_index == 0:
|
||||
if should_invert_root:
|
||||
import_bone.world_rotation_matrix = import_bone.local_rotation.conjugated().to_matrix()
|
||||
else:
|
||||
import_bone.world_rotation_matrix = import_bone.local_rotation.to_matrix()
|
||||
import_bone.world_matrix = Matrix.Translation(import_bone.local_translation)
|
||||
import_bones.append(import_bone)
|
||||
|
||||
for bone_index, bone in enumerate(import_bones):
|
||||
if bone.psk_bone.parent_index == 0 and bone_index == 0:
|
||||
continue
|
||||
parent = import_bones[bone.psk_bone.parent_index]
|
||||
bone.parent = parent
|
||||
bone.world_matrix = parent.world_rotation_matrix.to_4x4()
|
||||
translation = bone.local_translation.copy()
|
||||
translation.rotate(parent.world_rotation_matrix)
|
||||
bone.world_matrix.translation = parent.world_matrix.translation + translation
|
||||
bone.world_rotation_matrix = bone.local_rotation.conjugated().to_matrix()
|
||||
bone.world_rotation_matrix.rotate(parent.world_rotation_matrix)
|
||||
|
||||
for bone in import_bones:
|
||||
edit_bone = armature_data.edit_bones.new(bone.psk_bone.name.decode('utf-8'))
|
||||
if bone.parent is not None:
|
||||
edit_bone.parent = armature_data.edit_bones[bone.psk_bone.parent_index]
|
||||
elif not should_invert_root:
|
||||
bone.local_rotation.conjugate()
|
||||
post_quat = bone.local_rotation.conjugated()
|
||||
edit_bone.tail = Vector((0.0, new_bone_size, 0.0))
|
||||
m = post_quat.copy()
|
||||
m.rotate(bone.world_matrix)
|
||||
m = m.to_matrix().to_4x4()
|
||||
m.translation = bone.world_matrix.translation
|
||||
edit_bone.matrix = m
|
||||
|
||||
# MESH
|
||||
mesh_data = bpy.data.meshes.new('mesh')
|
||||
mesh_object = bpy.data.objects.new('new_mo', mesh_data)
|
||||
mesh_data = bpy.data.meshes.new(name)
|
||||
mesh_object = bpy.data.objects.new(name, mesh_data)
|
||||
|
||||
# MATERIALS
|
||||
for material in psk.materials:
|
||||
# TODO: re-use of materials should be an option
|
||||
bpy_material = bpy.data.materials.new(material.name.decode('utf-8'))
|
||||
mesh_data.materials.append(bpy_material)
|
||||
|
||||
@@ -44,7 +101,7 @@ class PskImporter(object):
|
||||
|
||||
# VERTICES
|
||||
for point in psk.points:
|
||||
bm.verts.new((point.x, point.y, point.z))
|
||||
bm.verts.new(tuple(point))
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
@@ -64,8 +121,47 @@ class PskImporter(object):
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
|
||||
bm.normal_update()
|
||||
bm.free()
|
||||
|
||||
# TODO: weights (vertex grorups etc.)
|
||||
# VERTEX WEIGHTS
|
||||
|
||||
# Get a list of all bones that have weights associated with them.
|
||||
vertex_group_bone_indices = set(map(lambda weight: weight.bone_index, psk.weights))
|
||||
for bone_index in sorted(list(vertex_group_bone_indices)):
|
||||
import_bones[bone_index].vertex_group = mesh_object.vertex_groups.new(name=import_bones[bone_index].psk_bone.name.decode('windows-1252'))
|
||||
|
||||
for weight in psk.weights:
|
||||
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD')
|
||||
|
||||
# Add armature modifier to our mesh object.
|
||||
armature_modifier = mesh_object.modifiers.new(name='Armature', type='ARMATURE')
|
||||
armature_modifier.object = armature_object
|
||||
mesh_object.parent = armature_object
|
||||
|
||||
context.scene.collection.objects.link(mesh_object)
|
||||
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class PskImportOperator(Operator, ImportHelper):
|
||||
bl_idname = 'import.psk'
|
||||
bl_label = 'Export'
|
||||
__doc__ = 'PSK Importer (.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):
|
||||
reader = PskReader()
|
||||
psk = reader.read(self.filepath)
|
||||
name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||
PskImporter().import_psk(psk, name, context)
|
||||
return {'FINISHED'}
|
||||
@@ -1,57 +0,0 @@
|
||||
from bpy.types import Operator
|
||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||
from bpy.props import StringProperty, BoolProperty, FloatProperty
|
||||
from .builder import PskBuilder
|
||||
from .exporter import PskExporter
|
||||
from .reader import PskReader
|
||||
from .importer import PskImporter
|
||||
|
||||
|
||||
class PskImportOperator(Operator, ImportHelper):
|
||||
bl_idname = 'import.psk'
|
||||
bl_label = 'Export'
|
||||
__doc__ = 'PSK Importer (.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):
|
||||
reader = PskReader()
|
||||
psk = reader.read(self.filepath)
|
||||
PskImporter().import_psk(psk, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
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 invoke(self, context, event):
|
||||
try:
|
||||
PskBuilder.get_input_objects(context)
|
||||
except RuntimeError as e:
|
||||
self.report({'ERROR_INVALID_CONTEXT'}, str(e))
|
||||
return {'CANCELLED'}
|
||||
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
builder = PskBuilder()
|
||||
psk = builder.build(context)
|
||||
exporter = PskExporter(psk)
|
||||
exporter.export(self.filepath)
|
||||
return {'FINISHED'}
|
||||
Reference in New Issue
Block a user