mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2025-02-14 17:02:33 +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):
|
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):
|
def psa_export_menu_func(self, context):
|
||||||
|
@ -1,4 +1,43 @@
|
|||||||
from ctypes import *
|
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):
|
class Vector3(Structure):
|
||||||
|
@ -41,6 +41,15 @@ class Psk(object):
|
|||||||
('smoothing_groups', c_int32)
|
('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):
|
class Material(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
('name', c_char * 64),
|
('name', c_char * 64),
|
||||||
@ -71,6 +80,18 @@ class Psk(object):
|
|||||||
('bone_index', c_int32),
|
('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):
|
def __init__(self):
|
||||||
self.points: List[Vector3] = []
|
self.points: List[Vector3] = []
|
||||||
self.wedges: List[Psk.Wedge] = []
|
self.wedges: List[Psk.Wedge] = []
|
||||||
@ -78,3 +99,6 @@ class Psk(object):
|
|||||||
self.materials: List[Psk.Material] = []
|
self.materials: List[Psk.Material] = []
|
||||||
self.weights: List[Psk.Weight] = []
|
self.weights: List[Psk.Weight] = []
|
||||||
self.bones: List[Psk.Bone] = []
|
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
|
bm_face.material_index = face.material_index
|
||||||
except ValueError:
|
except ValueError:
|
||||||
degenerate_face_indices.add(face_index)
|
degenerate_face_indices.add(face_index)
|
||||||
pass
|
|
||||||
|
|
||||||
if len(degenerate_face_indices) > 0:
|
if len(degenerate_face_indices) > 0:
|
||||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||||
@ -129,7 +128,7 @@ class PskImporter(object):
|
|||||||
|
|
||||||
# TEXTURE COORDINATES
|
# TEXTURE COORDINATES
|
||||||
data_index = 0
|
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):
|
for face_index, face in enumerate(psk.faces):
|
||||||
if face_index in degenerate_face_indices:
|
if face_index in degenerate_face_indices:
|
||||||
continue
|
continue
|
||||||
@ -138,11 +137,56 @@ class PskImporter(object):
|
|||||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||||
data_index += 1
|
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.normal_update()
|
||||||
bm.free()
|
bm.free()
|
||||||
|
|
||||||
# VERTEX WEIGHTS
|
|
||||||
|
|
||||||
# Get a list of all bones that have weights associated with them.
|
# 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))
|
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))):
|
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'
|
bl_label = 'Export'
|
||||||
__doc__ = 'Load a PSK file'
|
__doc__ = 'Load a PSK file'
|
||||||
filename_ext = '.psk'
|
filename_ext = '.psk'
|
||||||
filter_glob: StringProperty(default='*.psk', options={'HIDDEN'})
|
filter_glob: StringProperty(default='*.psk;*.pskx', options={'HIDDEN'})
|
||||||
filepath: StringProperty(
|
filepath: StringProperty(
|
||||||
name='File Path',
|
name='File Path',
|
||||||
description='File path used for exporting the PSK file',
|
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)
|
PskReader.read_types(fp, Psk.Bone, section, psk.bones)
|
||||||
elif section.name == b'RAWWEIGHTS':
|
elif section.name == b'RAWWEIGHTS':
|
||||||
PskReader.read_types(fp, Psk.Weight, section, psk.weights)
|
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:
|
else:
|
||||||
raise RuntimeError(f'Unrecognized section "{section.name}"')
|
raise RuntimeError(f'Unrecognized section "{section.name} at position {15:fp.tell()}"')
|
||||||
return psk
|
return psk
|
||||||
|
Loading…
x
Reference in New Issue
Block a user