mirror of
https://github.com/DarklightGames/io_scene_psk_psa.git
synced 2024-11-15 02:37:39 +01:00
Users can now choose not to import either the mesh or the armature when importing a PSK
This commit is contained in:
parent
4d1cd19a61
commit
494c5b116b
@ -19,207 +19,214 @@ from ..helpers import rgb_to_srgb
|
||||
class PskImportOptions(object):
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.should_import_mesh = True
|
||||
self.should_import_vertex_colors = True
|
||||
self.vertex_color_space = 'sRGB'
|
||||
self.should_import_vertex_normals = True
|
||||
self.should_import_extra_uvs = True
|
||||
self.should_import_skeleton = True
|
||||
self.bone_length = 1.0
|
||||
|
||||
|
||||
def import_psk(psk: Psk, context, options: PskImportOptions):
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new(options.name)
|
||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
armature_object = None
|
||||
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
if options.should_import_skeleton:
|
||||
# ARMATURE
|
||||
armature_data = bpy.data.armatures.new(options.name)
|
||||
armature_object = bpy.data.objects.new(options.name, armature_data)
|
||||
armature_object.show_in_front = True
|
||||
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
context.scene.collection.objects.link(armature_object)
|
||||
|
||||
armature_object.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = armature_object
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
except:
|
||||
pass
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
armature_object.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = armature_object
|
||||
|
||||
# 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
|
||||
self.orig_quat: Quaternion = Quaternion()
|
||||
self.orig_loc: Vector = Vector()
|
||||
self.post_quat: Quaternion = Quaternion()
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
import_bones = []
|
||||
# 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
|
||||
self.orig_quat: Quaternion = Quaternion()
|
||||
self.orig_loc: Vector = Vector()
|
||||
self.post_quat: Quaternion = Quaternion()
|
||||
|
||||
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:
|
||||
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)
|
||||
import_bones = []
|
||||
|
||||
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_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:
|
||||
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 import_bone in import_bones:
|
||||
bone_name = import_bone.psk_bone.name.decode('utf-8')
|
||||
edit_bone = armature_data.edit_bones.new(bone_name)
|
||||
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)
|
||||
|
||||
if import_bone.parent is not None:
|
||||
edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index]
|
||||
else:
|
||||
import_bone.local_rotation.conjugate()
|
||||
for import_bone in import_bones:
|
||||
bone_name = import_bone.psk_bone.name.decode('utf-8')
|
||||
edit_bone = armature_data.edit_bones.new(bone_name)
|
||||
|
||||
edit_bone.tail = Vector((0.0, options.bone_length, 0.0))
|
||||
edit_bone_matrix = import_bone.local_rotation.conjugated()
|
||||
edit_bone_matrix.rotate(import_bone.world_matrix)
|
||||
edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4()
|
||||
edit_bone_matrix.translation = import_bone.world_matrix.translation
|
||||
edit_bone.matrix = edit_bone_matrix
|
||||
if import_bone.parent is not None:
|
||||
edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index]
|
||||
else:
|
||||
import_bone.local_rotation.conjugate()
|
||||
|
||||
# Store bind pose information in the bone's custom properties.
|
||||
# This information is used when importing animations from PSA files.
|
||||
edit_bone['orig_quat'] = import_bone.local_rotation
|
||||
edit_bone['orig_loc'] = import_bone.local_translation
|
||||
edit_bone['post_quat'] = import_bone.local_rotation.conjugated()
|
||||
edit_bone.tail = Vector((0.0, options.bone_length, 0.0))
|
||||
edit_bone_matrix = import_bone.local_rotation.conjugated()
|
||||
edit_bone_matrix.rotate(import_bone.world_matrix)
|
||||
edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4()
|
||||
edit_bone_matrix.translation = import_bone.world_matrix.translation
|
||||
edit_bone.matrix = edit_bone_matrix
|
||||
|
||||
# Store bind pose information in the bone's custom properties.
|
||||
# This information is used when importing animations from PSA files.
|
||||
edit_bone['orig_quat'] = import_bone.local_rotation
|
||||
edit_bone['orig_loc'] = import_bone.local_translation
|
||||
edit_bone['post_quat'] = import_bone.local_rotation.conjugated()
|
||||
|
||||
# MESH
|
||||
mesh_data = bpy.data.meshes.new(options.name)
|
||||
mesh_object = bpy.data.objects.new(options.name, mesh_data)
|
||||
if options.should_import_mesh:
|
||||
mesh_data = bpy.data.meshes.new(options.name)
|
||||
mesh_object = bpy.data.objects.new(options.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)
|
||||
# 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)
|
||||
|
||||
bm = bmesh.new()
|
||||
bm = bmesh.new()
|
||||
|
||||
# VERTICES
|
||||
for point in psk.points:
|
||||
bm.verts.new(tuple(point))
|
||||
# VERTICES
|
||||
for point in psk.points:
|
||||
bm.verts.new(tuple(point))
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
degenerate_face_indices = set()
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
|
||||
try:
|
||||
bm_face = bm.faces.new(point_indices)
|
||||
bm_face.material_index = face.material_index
|
||||
except ValueError:
|
||||
degenerate_face_indices.add(face_index)
|
||||
degenerate_face_indices = set()
|
||||
for face_index, face in enumerate(psk.faces):
|
||||
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
|
||||
try:
|
||||
bm_face = bm.faces.new(point_indices)
|
||||
bm_face.material_index = face.material_index
|
||||
except ValueError:
|
||||
degenerate_face_indices.add(face_index)
|
||||
|
||||
if len(degenerate_face_indices) > 0:
|
||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||
if len(degenerate_face_indices) > 0:
|
||||
print(f'WARNING: Discarded {len(degenerate_face_indices)} degenerate face(s).')
|
||||
|
||||
bm.to_mesh(mesh_data)
|
||||
bm.to_mesh(mesh_data)
|
||||
|
||||
# TEXTURE COORDINATES
|
||||
data_index = 0
|
||||
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
|
||||
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
|
||||
for wedge in face_wedges:
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
# TEXTURE COORDINATES
|
||||
data_index = 0
|
||||
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
|
||||
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
|
||||
for wedge in face_wedges:
|
||||
uv_layer.data[data_index].uv = wedge.u, 1.0 - wedge.v
|
||||
data_index += 1
|
||||
|
||||
# EXTRA UVS
|
||||
if psk.has_extra_uvs and options.should_import_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)
|
||||
# EXTRA UVS
|
||||
if psk.has_extra_uvs and options.should_import_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 and options.should_import_vertex_colors:
|
||||
size = (len(psk.points), 4)
|
||||
vertex_colors = np.full(size, inf)
|
||||
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
|
||||
ambiguous_vertex_color_point_indices = []
|
||||
# VERTEX COLORS
|
||||
if psk.has_vertex_colors and options.should_import_vertex_colors:
|
||||
size = (len(psk.points), 4)
|
||||
vertex_colors = np.full(size, inf)
|
||||
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
|
||||
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].normalized()
|
||||
if vertex_colors[point_index, 0] != inf and tuple(vertex_colors[point_index]) != psk_vertex_color:
|
||||
ambiguous_vertex_color_point_indices.append(point_index)
|
||||
else:
|
||||
vertex_colors[point_index] = psk_vertex_color
|
||||
for wedge_index, wedge in enumerate(psk.wedges):
|
||||
point_index = wedge.point_index
|
||||
psk_vertex_color = psk.vertex_colors[wedge_index].normalized()
|
||||
if vertex_colors[point_index, 0] != inf and tuple(vertex_colors[point_index]) != psk_vertex_color:
|
||||
ambiguous_vertex_color_point_indices.append(point_index)
|
||||
else:
|
||||
vertex_colors[point_index] = psk_vertex_color
|
||||
|
||||
if options.vertex_color_space == 'SRGBA':
|
||||
for i in range(vertex_colors.shape[0]):
|
||||
vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), vertex_colors[i, :3]))
|
||||
if options.vertex_color_space == 'SRGBA':
|
||||
for i in range(vertex_colors.shape[0]):
|
||||
vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), vertex_colors[i, :3]))
|
||||
|
||||
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
|
||||
else:
|
||||
vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0
|
||||
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
|
||||
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.')
|
||||
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 and options.should_import_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
|
||||
# VERTEX NORMALS
|
||||
if psk.has_vertex_normals and options.should_import_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()
|
||||
bm.normal_update()
|
||||
bm.free()
|
||||
|
||||
# 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))):
|
||||
import_bone.vertex_group = mesh_object.vertex_groups.new(
|
||||
name=import_bone.psk_bone.name.decode('windows-1252'))
|
||||
# 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_groups = [None] * len(psk.bones)
|
||||
for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices):
|
||||
vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=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')
|
||||
for weight in psk.weights:
|
||||
vertex_groups[weight.bone_index].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)
|
||||
|
||||
context.scene.collection.objects.link(mesh_object)
|
||||
# Add armature modifier to our mesh object.
|
||||
if options.should_import_skeleton:
|
||||
armature_modifier = mesh_object.modifiers.new(name='Armature', type='ARMATURE')
|
||||
armature_modifier.object = armature_object
|
||||
mesh_object.parent = armature_object
|
||||
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
@ -256,6 +263,18 @@ class PskImportPropertyGroup(PropertyGroup):
|
||||
options=set(),
|
||||
description='Import extra UV maps from PSKX files, if available'
|
||||
)
|
||||
should_import_mesh: BoolProperty(
|
||||
default=True,
|
||||
name='Import Mesh',
|
||||
options=set(),
|
||||
description='Import mesh'
|
||||
)
|
||||
should_import_skeleton: BoolProperty(
|
||||
default=True,
|
||||
name='Import Skeleton',
|
||||
options=set(),
|
||||
description='Import skeleton'
|
||||
)
|
||||
bone_length: FloatProperty(
|
||||
default=1.0,
|
||||
min=sys.float_info.epsilon,
|
||||
@ -269,7 +288,7 @@ class PskImportPropertyGroup(PropertyGroup):
|
||||
|
||||
class PskImportOperator(Operator, ImportHelper):
|
||||
bl_idname = 'import.psk'
|
||||
bl_label = 'Export'
|
||||
bl_label = 'Import'
|
||||
bl_options = {'INTERNAL', 'UNDO'}
|
||||
__doc__ = 'Load a PSK file'
|
||||
filename_ext = '.psk'
|
||||
@ -287,10 +306,12 @@ class PskImportOperator(Operator, ImportHelper):
|
||||
|
||||
options = PskImportOptions()
|
||||
options.name = os.path.splitext(os.path.basename(self.filepath))[0]
|
||||
options.should_import_mesh = pg.should_import_mesh
|
||||
options.should_import_extra_uvs = pg.should_import_extra_uvs
|
||||
options.should_import_vertex_colors = pg.should_import_vertex_colors
|
||||
options.should_import_vertex_normals = pg.should_import_vertex_normals
|
||||
options.vertex_color_space = pg.vertex_color_space
|
||||
options.should_import_skeleton = pg.should_import_skeleton
|
||||
options.bone_length = pg.bone_length
|
||||
|
||||
import_psk(psk, context, options)
|
||||
@ -300,14 +321,22 @@ class PskImportOperator(Operator, ImportHelper):
|
||||
def draw(self, context):
|
||||
pg = context.scene.psk_import
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
layout.prop(pg, 'should_import_vertex_normals')
|
||||
layout.prop(pg, 'should_import_extra_uvs')
|
||||
layout.prop(pg, 'should_import_vertex_colors')
|
||||
if pg.should_import_vertex_colors:
|
||||
layout.prop(pg, 'vertex_color_space')
|
||||
layout.prop(pg, 'bone_length')
|
||||
layout.prop(pg, 'should_import_mesh')
|
||||
row = layout.column()
|
||||
row.use_property_split = True
|
||||
row.use_property_decorate = False
|
||||
if pg.should_import_mesh:
|
||||
row.prop(pg, 'should_import_vertex_normals')
|
||||
row.prop(pg, 'should_import_extra_uvs')
|
||||
row.prop(pg, 'should_import_vertex_colors')
|
||||
if pg.should_import_vertex_colors:
|
||||
row.prop(pg, 'vertex_color_space')
|
||||
layout.prop(pg, 'should_import_skeleton')
|
||||
row = layout.column()
|
||||
row.use_property_split = True
|
||||
row.use_property_decorate = False
|
||||
if pg.should_import_skeleton:
|
||||
row.prop(pg, 'bone_length')
|
||||
|
||||
|
||||
classes = (
|
||||
|
Loading…
Reference in New Issue
Block a user