mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2025-02-07 22:41:26 +01:00
Initial commit for PSKX support
This commit is contained in:
parent
5a13faeb5e
commit
4099c95381
@ -58,7 +58,7 @@ def psk_export_menu_func(self, context):
|
||||
|
||||
|
||||
def psk_import_menu_func(self, context):
|
||||
self.layout.operator(psk_importer.PskImportOperator.bl_idname, text='Unreal PSK (.psk)')
|
||||
self.layout.operator(psk_importer.PskImportOperator.bl_idname, text='Unreal PSK (.psk/.pskx)')
|
||||
|
||||
|
||||
def psa_export_menu_func(self, context):
|
||||
|
@ -1,4 +1,43 @@
|
||||
from ctypes import *
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Color(Structure):
|
||||
_fields_ = [
|
||||
('r', c_ubyte),
|
||||
('g', c_ubyte),
|
||||
('b', c_ubyte),
|
||||
('a', c_ubyte),
|
||||
]
|
||||
|
||||
def __iter__(self):
|
||||
yield self.r
|
||||
yield self.g
|
||||
yield self.b
|
||||
yield self.a
|
||||
|
||||
def __eq__(self, other):
|
||||
return all(map(lambda x: x[0] == x[1], zip(self, other)))
|
||||
|
||||
def __repr__(self):
|
||||
return repr(tuple(self))
|
||||
|
||||
def normalized(self) -> Tuple:
|
||||
return tuple(map(lambda x: x / 255.0, iter(self)))
|
||||
|
||||
|
||||
class Vector2(Structure):
|
||||
_fields_ = [
|
||||
('x', c_float),
|
||||
('y', c_float),
|
||||
]
|
||||
|
||||
def __iter__(self):
|
||||
yield self.x
|
||||
yield self.y
|
||||
|
||||
def __repr__(self):
|
||||
return repr(tuple(self))
|
||||
|
||||
|
||||
class Vector3(Structure):
|
||||
|
@ -41,6 +41,15 @@ class Psk(object):
|
||||
('smoothing_groups', c_int32)
|
||||
]
|
||||
|
||||
class Face32(Structure):
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('wedge_indices', c_uint32 * 3),
|
||||
('material_index', c_uint8),
|
||||
('aux_material_index', c_uint8),
|
||||
('smoothing_groups', c_int32)
|
||||
]
|
||||
|
||||
class Material(Structure):
|
||||
_fields_ = [
|
||||
('name', c_char * 64),
|
||||
@ -71,6 +80,18 @@ class Psk(object):
|
||||
('bone_index', c_int32),
|
||||
]
|
||||
|
||||
@property
|
||||
def has_extra_uvs(self):
|
||||
return len(self.extra_uvs) > 0
|
||||
|
||||
@property
|
||||
def has_vertex_colors(self):
|
||||
return len(self.vertex_colors)
|
||||
|
||||
@property
|
||||
def has_vertex_normals(self):
|
||||
return len(self.vertex_normals)
|
||||
|
||||
def __init__(self):
|
||||
self.points: List[Vector3] = []
|
||||
self.wedges: List[Psk.Wedge] = []
|
||||
@ -78,3 +99,6 @@ class Psk(object):
|
||||
self.materials: List[Psk.Material] = []
|
||||
self.weights: List[Psk.Weight] = []
|
||||
self.bones: List[Psk.Bone] = []
|
||||
self.extra_uvs: List[Vector2] = []
|
||||
self.vertex_colors: List[Color] = []
|
||||
self.vertex_normals: List[Vector3] = []
|
||||
|
@ -120,7 +120,6 @@ class PskImporter(object):
|
||||
bm_face.material_index = face.material_index
|
||||
except ValueError:
|
||||
degenerate_face_indices.add(face_index)
|
||||
pass
|
||||
|
||||
if len(degenerate_face_indices) > 0:
|
||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||
@ -129,7 +128,7 @@ class PskImporter(object):
|
||||
|
||||
# TEXTURE COORDINATES
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new()
|
||||
uv_layer = mesh_data.uv_layers.new(name='VTXW0000')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
@ -138,11 +137,56 @@ class PskImporter(object):
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
|
||||
# EXTRA UVS
|
||||
if psk.has_extra_uvs:
|
||||
extra_uv_channel_count = int(len(psk.extra_uvs) / len(psk.wedges))
|
||||
wedge_index_offset = 0
|
||||
for extra_uv_index in range(extra_uv_channel_count):
|
||||
data_index = 0
|
||||
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
if face_index in degenerate_face_indices:
|
||||
continue
|
||||
for wedge_index in reversed(face.wedge_indices):
|
||||
u, v = psk.extra_uvs[wedge_index_offset + wedge_index]
|
||||
uv_layer.data[data_index].uv = u, 1.0 - v
|
||||
data_index += 1
|
||||
wedge_index_offset += len(psk.wedges)
|
||||
|
||||
# VERTEX COLORS
|
||||
if psk.has_vertex_colors:
|
||||
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
|
||||
vertex_colors = [None] * len(psk.points)
|
||||
ambiguous_vertex_color_point_indices = []
|
||||
for wedge_index, wedge in enumerate(psk.wedges):
|
||||
point_index = wedge.point_index
|
||||
psk_vertex_color = psk.vertex_colors[wedge_index]
|
||||
if vertex_colors[point_index] is not None and vertex_colors[point_index] != psk_vertex_color:
|
||||
ambiguous_vertex_color_point_indices.append(point_index)
|
||||
vertex_colors[point_index] = psk_vertex_color
|
||||
|
||||
for loop_index, loop in enumerate(mesh_data.loops):
|
||||
vertex_color = vertex_colors[loop.vertex_index]
|
||||
if vertex_color is not None:
|
||||
vertex_color_data.data[loop_index].color = vertex_color.normalized()
|
||||
else:
|
||||
vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0
|
||||
|
||||
if len(ambiguous_vertex_color_point_indices) > 0:
|
||||
print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.')
|
||||
|
||||
# # VERTEX NORMALS
|
||||
if psk.has_vertex_normals:
|
||||
mesh_data.polygons.foreach_set("use_smooth", [True] * len(mesh_data.polygons))
|
||||
normals = []
|
||||
for vertex_normal in psk.vertex_normals:
|
||||
normals.append(tuple(vertex_normal))
|
||||
mesh_data.normals_split_custom_set_from_vertices(normals)
|
||||
mesh_data.use_auto_smooth = True
|
||||
|
||||
bm.normal_update()
|
||||
bm.free()
|
||||
|
||||
# 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 import_bone in map(lambda x: import_bones[x], sorted(list(vertex_group_bone_indices))):
|
||||
@ -169,7 +213,7 @@ class PskImportOperator(Operator, ImportHelper):
|
||||
bl_label = 'Export'
|
||||
__doc__ = 'Load a PSK file'
|
||||
filename_ext = '.psk'
|
||||
filter_glob: StringProperty(default='*.psk', options={'HIDDEN'})
|
||||
filter_glob: StringProperty(default='*.psk;*.pskx', options={'HIDDEN'})
|
||||
filepath: StringProperty(
|
||||
name='File Path',
|
||||
description='File path used for exporting the PSK file',
|
||||
|
@ -41,6 +41,14 @@ class PskReader(object):
|
||||
PskReader.read_types(fp, Psk.Bone, section, psk.bones)
|
||||
elif section.name == b'RAWWEIGHTS':
|
||||
PskReader.read_types(fp, Psk.Weight, section, psk.weights)
|
||||
elif section.name == b'FACE3200':
|
||||
PskReader.read_types(fp, Psk.Face32, section, psk.faces)
|
||||
elif section.name == b'VERTEXCOLOR':
|
||||
PskReader.read_types(fp, Color, section, psk.vertex_colors)
|
||||
elif section.name.startswith(b'EXTRAUVS'):
|
||||
PskReader.read_types(fp, Vector2, section, psk.extra_uvs)
|
||||
elif section.name == b'VTXNORMS':
|
||||
PskReader.read_types(fp, Vector3, section, psk.vertex_normals)
|
||||
else:
|
||||
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
||||
raise RuntimeError(f'Unrecognized section "{section.name} at position {15:fp.tell()}"')
|
||||
return psk
|
||||
|
Loading…
x
Reference in New Issue
Block a user