Compare commits

..

8 Commits
1.1.0 ... 1.1.1

6 changed files with 70 additions and 44 deletions

View File

@@ -22,4 +22,8 @@ This Blender add-on allows you to export meshes and animations to the [PSK and P
# FAQ # FAQ
## Can I use this addon to import PSK and PSA files? ## Can I use this addon to import PSK and PSA files?
Currently, no. If you are looking for a PSK/PSA importer, use [this one](https://github.com/Befzz/blender3d_import_psk_psa)! Currently, no.
Presumably you are using this in concert with the [UE Viewer](https://www.gildor.org/en/projects/umodel) program to import extracted meshes. It is *not recommended* to export PSK/PSA from UE Viewer since it [does not preserve smoothing groups](https://github.com/gildor2/UEViewer/issues/235). As a result, the shading of imported models will be incorrect and will need to be manually fixed. Instead, it is recommended to export meshes to the glTF format for import into Blender since it preserves the correct mesh shading.
Regardless, if you are dead set on using a PSK/PSA importer, use [this one](https://github.com/Befzz/blender3d_import_psk_psa).

View File

@@ -1,8 +1,8 @@
bl_info = { bl_info = {
"name": "PSK/PSA Exporter", "name": "PSK/PSA Exporter",
"author": "Colin Basnett", "author": "Colin Basnett",
"version": ( 1, 1, 0 ), "version": (1, 1, 1),
"blender": ( 2, 80, 0 ), "blender": (2, 80, 0),
"location": "File > Export > PSK Export (.psk)", "location": "File > Export > PSK Export (.psk)",
"description": "PSK/PSA Export (.psk)", "description": "PSK/PSA Export (.psk)",
"warning": "", "warning": "",

View File

@@ -85,15 +85,14 @@ class PsaBuilder(object):
sequence.name = bytes(action.name, encoding='utf-8') sequence.name = bytes(action.name, encoding='utf-8')
sequence.frame_count = frame_max - frame_min + 1 sequence.frame_count = frame_max - frame_min + 1
sequence.frame_start_index = frame_start_index sequence.frame_start_index = frame_start_index
sequence.fps = 30 # TODO: fill in later with r sequence.fps = context.scene.render.fps
frame_count = frame_max - frame_min + 1 frame_count = frame_max - frame_min + 1
for frame in range(frame_count): for frame in range(frame_count):
context.scene.frame_set(frame) context.scene.frame_set(frame_min + frame)
for bone in pose_bones: for bone in pose_bones:
# TODO: is the cast-to-matrix necessary? (guessing no)
key = Psa.Key() key = Psa.Key()
pose_bone_matrix = bone.matrix pose_bone_matrix = bone.matrix

View File

@@ -10,6 +10,7 @@ class PskInputObjects(object):
self.mesh_objects = [] self.mesh_objects = []
self.armature_object = None self.armature_object = None
class PskBuilder(object): class PskBuilder(object):
def __init__(self): def __init__(self):
pass pass
@@ -52,14 +53,8 @@ class PskBuilder(object):
def build(self, context) -> Psk: def build(self, context) -> Psk:
input_objects = PskBuilder.get_input_objects(context) input_objects = PskBuilder.get_input_objects(context)
wedge_count = sum([len(m.data.loops) for m in input_objects.mesh_objects])
if wedge_count <= 65536:
wedge_type = Psk.Wedge16
else:
wedge_type = Psk.Wedge32
psk = Psk() psk = Psk()
materials = OrderedDict() materials = OrderedDict()
if input_objects.armature_object is None: if input_objects.armature_object is None:
@@ -111,10 +106,7 @@ class PskBuilder(object):
psk.bones.append(psk_bone) psk.bones.append(psk_bone)
vertex_offset = 0 vertex_offset = 0
wedge_offset = 0
weight_offset = 0
# TODO: if there is an edge-split modifier, we need to apply it (maybe?)
for object in input_objects.mesh_objects: for object in input_objects.mesh_objects:
# VERTICES # VERTICES
for vertex in object.data.vertices: for vertex in object.data.vertices:
@@ -125,17 +117,7 @@ class PskBuilder(object):
point.z = v.z point.z = v.z
psk.points.append(point) psk.points.append(point)
# WEDGES
uv_layer = object.data.uv_layers.active.data uv_layer = object.data.uv_layers.active.data
psk.wedges.extend([wedge_type() for _ in range(len(object.data.loops))])
for loop_index, loop in enumerate(object.data.loops):
wedge = psk.wedges[wedge_offset + loop_index]
wedge.material_index = 0 # NOTE: this material index is set properly while building the faces
wedge.point_index = loop.vertex_index + vertex_offset
wedge.u, wedge.v = uv_layer[loop_index].uv
wedge.v = 1.0 - wedge.v
psk.wedges.append(wedge)
# MATERIALS # MATERIALS
material_indices = [] material_indices = []
@@ -153,20 +135,46 @@ class PskBuilder(object):
material_index = material.texture_index material_index = material.texture_index
material_indices.append(material_index) material_indices.append(material_index)
# FACES # WEDGES
object.data.calc_loop_triangles() object.data.calc_loop_triangles()
# Build a list of non-unique wedges.
wedges = []
for loop_index, loop in enumerate(object.data.loops):
wedge = Psk.Wedge()
wedge.point_index = loop.vertex_index + vertex_offset
wedge.u, wedge.v = uv_layer[loop_index].uv
wedge.v = 1.0 - wedge.v
wedges.append(wedge)
# Assign material indices to the wedges.
for triangle in object.data.loop_triangles:
for loop_index in triangle.loops:
wedges[loop_index].material_index = material_indices[triangle.material_index]
# Populate the list of wedges with unique wedges & build a look-up table of loop indices to wedge indices
wedge_indices = {}
loop_wedge_indices = [-1] * len(object.data.loops)
for loop_index, wedge in enumerate(wedges):
wedge_hash = hash(wedge)
if wedge_hash in wedge_indices:
loop_wedge_indices[loop_index] = wedge_indices[wedge_hash]
else:
wedge_index = len(psk.wedges)
wedge_indices[wedge_hash] = wedge_index
psk.wedges.append(wedge)
loop_wedge_indices[loop_index] = wedge_index
# FACES
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:
face = Psk.Face() face = Psk.Face()
face.material_index = material_indices[f.material_index] face.material_index = material_indices[f.material_index]
face.wedge_index_1 = f.loops[2] + wedge_offset face.wedge_indices[0] = loop_wedge_indices[f.loops[2]]
face.wedge_index_2 = f.loops[1] + wedge_offset face.wedge_indices[1] = loop_wedge_indices[f.loops[1]]
face.wedge_index_3 = f.loops[0] + wedge_offset face.wedge_indices[2] = loop_wedge_indices[f.loops[0]]
face.smoothing_groups = poly_groups[f.polygon_index] face.smoothing_groups = poly_groups[f.polygon_index]
psk.faces.append(face) psk.faces.append(face)
# update the material index of the wedges
for i in range(3):
psk.wedges[wedge_offset + f.loops[i]].material_index = face.material_index
# WEIGHTS # WEIGHTS
# TODO: bone ~> vg might not be 1:1, provide a nice error message if this is the case # TODO: bone ~> vg might not be 1:1, provide a nice error message if this is the case
@@ -190,8 +198,6 @@ class PskBuilder(object):
w.weight = weight w.weight = weight
psk.weights.append(w) psk.weights.append(w)
vertex_offset += len(psk.points) vertex_offset = len(psk.points)
wedge_offset += len(psk.wedges)
weight_offset += len(psk.weights)
return psk return psk

View File

@@ -4,30 +4,38 @@ from ..data import *
class Psk(object): class Psk(object):
class Wedge(object):
def __init__(self):
self.point_index: int = 0
self.u: float = 0.0
self.v: float = 0.0
self.material_index: int = 0
def __hash__(self):
return hash(f'{self.point_index}-{self.u}-{self.v}-{self.material_index}')
class Wedge16(Structure): class Wedge16(Structure):
_fields_ = [ _fields_ = [
('point_index', c_int16), ('point_index', c_uint16),
('padding1', c_int16), ('padding1', c_int16),
('u', c_float), ('u', c_float),
('v', c_float), ('v', c_float),
('material_index', c_int8), ('material_index', c_uint8),
('reserved', c_int8), ('reserved', c_int8),
('padding2', c_int16) ('padding2', c_int16)
] ]
class Wedge32(Structure): class Wedge32(Structure):
_fields_ = [ _fields_ = [
('point_index', c_int32), ('point_index', c_uint32),
('u', c_float), ('u', c_float),
('v', c_float), ('v', c_float),
('material_index', c_int32) ('material_index', c_uint32)
] ]
class Face(Structure): class Face(Structure):
_fields_ = [ _fields_ = [
('wedge_index_1', c_int16), ('wedge_indices', c_uint16 * 3),
('wedge_index_2', c_int16),
('wedge_index_3', c_int16),
('material_index', c_int8), ('material_index', c_int8),
('aux_material_index', c_int8), ('aux_material_index', c_int8),
('smoothing_groups', c_int32) ('smoothing_groups', c_int32)
@@ -65,7 +73,7 @@ class Psk(object):
def __init__(self): def __init__(self):
self.points: List[Vector3] = [] self.points: List[Vector3] = []
self.wedges: List[Psk.Wedge16] = [] self.wedges: List[Psk.Wedge] = []
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] = []

View File

@@ -29,7 +29,16 @@ class PskExporter(object):
else: else:
wedge_type = Psk.Wedge32 wedge_type = Psk.Wedge32
self.write_section(fp, b'VTXW0000', wedge_type, self.psk.wedges) wedges = []
for index, w in enumerate(self.psk.wedges):
wedge = wedge_type()
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'FACE0000', Psk.Face, self.psk.faces) 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'MATT0000', Psk.Material, self.psk.materials)
self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones) self.write_section(fp, b'REFSKELT', Psk.Bone, self.psk.bones)