Users can now choose not to import either the mesh or the armature when importing a PSK

This commit is contained in:
Colin Basnett
2022-06-27 18:15:14 -07:00
parent 4d1cd19a61
commit 494c5b116b

View File

@@ -19,207 +19,214 @@ from ..helpers import rgb_to_srgb
class PskImportOptions(object): class PskImportOptions(object):
def __init__(self): def __init__(self):
self.name = '' self.name = ''
self.should_import_mesh = True
self.should_import_vertex_colors = True self.should_import_vertex_colors = True
self.vertex_color_space = 'sRGB' self.vertex_color_space = 'sRGB'
self.should_import_vertex_normals = True self.should_import_vertex_normals = True
self.should_import_extra_uvs = True self.should_import_extra_uvs = True
self.should_import_skeleton = True
self.bone_length = 1.0 self.bone_length = 1.0
def import_psk(psk: Psk, context, options: PskImportOptions): def import_psk(psk: Psk, context, options: PskImportOptions):
# ARMATURE armature_object = None
armature_data = bpy.data.armatures.new(options.name)
armature_object = bpy.data.objects.new(options.name, armature_data)
armature_object.show_in_front = True
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: context.scene.collection.objects.link(armature_object)
bpy.ops.object.mode_set(mode='OBJECT')
except:
pass
armature_object.select_set(state=True) try:
bpy.context.view_layer.objects.active = armature_object 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. bpy.ops.object.mode_set(mode='EDIT')
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()
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_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 bone_index, bone in enumerate(import_bones): for bone_index, psk_bone in enumerate(psk.bones):
if bone.psk_bone.parent_index == 0 and bone_index == 0: import_bone = ImportBone(bone_index, psk_bone)
continue psk_bone.parent_index = max(0, psk_bone.parent_index)
parent = import_bones[bone.psk_bone.parent_index] import_bone.local_rotation = Quaternion(tuple(psk_bone.rotation))
bone.parent = parent import_bone.local_translation = Vector(tuple(psk_bone.location))
bone.world_matrix = parent.world_rotation_matrix.to_4x4() if psk_bone.parent_index == 0 and bone_index == 0:
translation = bone.local_translation.copy() import_bone.world_rotation_matrix = import_bone.local_rotation.to_matrix()
translation.rotate(parent.world_rotation_matrix) import_bone.world_matrix = Matrix.Translation(import_bone.local_translation)
bone.world_matrix.translation = parent.world_matrix.translation + translation import_bones.append(import_bone)
bone.world_rotation_matrix = bone.local_rotation.conjugated().to_matrix()
bone.world_rotation_matrix.rotate(parent.world_rotation_matrix)
for import_bone in import_bones: for bone_index, bone in enumerate(import_bones):
bone_name = import_bone.psk_bone.name.decode('utf-8') if bone.psk_bone.parent_index == 0 and bone_index == 0:
edit_bone = armature_data.edit_bones.new(bone_name) 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: for import_bone in import_bones:
edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index] bone_name = import_bone.psk_bone.name.decode('utf-8')
else: edit_bone = armature_data.edit_bones.new(bone_name)
import_bone.local_rotation.conjugate()
edit_bone.tail = Vector((0.0, options.bone_length, 0.0)) if import_bone.parent is not None:
edit_bone_matrix = import_bone.local_rotation.conjugated() edit_bone.parent = armature_data.edit_bones[import_bone.psk_bone.parent_index]
edit_bone_matrix.rotate(import_bone.world_matrix) else:
edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4() import_bone.local_rotation.conjugate()
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. edit_bone.tail = Vector((0.0, options.bone_length, 0.0))
# This information is used when importing animations from PSA files. edit_bone_matrix = import_bone.local_rotation.conjugated()
edit_bone['orig_quat'] = import_bone.local_rotation edit_bone_matrix.rotate(import_bone.world_matrix)
edit_bone['orig_loc'] = import_bone.local_translation edit_bone_matrix = edit_bone_matrix.to_matrix().to_4x4()
edit_bone['post_quat'] = import_bone.local_rotation.conjugated() 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
mesh_data = bpy.data.meshes.new(options.name) if options.should_import_mesh:
mesh_object = bpy.data.objects.new(options.name, mesh_data) mesh_data = bpy.data.meshes.new(options.name)
mesh_object = bpy.data.objects.new(options.name, mesh_data)
# MATERIALS # MATERIALS
for material in psk.materials: for material in psk.materials:
# TODO: re-use of materials should be an option # TODO: re-use of materials should be an option
bpy_material = bpy.data.materials.new(material.name.decode('utf-8')) bpy_material = bpy.data.materials.new(material.name.decode('utf-8'))
mesh_data.materials.append(bpy_material) mesh_data.materials.append(bpy_material)
bm = bmesh.new() bm = bmesh.new()
# VERTICES # VERTICES
for point in psk.points: for point in psk.points:
bm.verts.new(tuple(point)) bm.verts.new(tuple(point))
bm.verts.ensure_lookup_table() bm.verts.ensure_lookup_table()
degenerate_face_indices = set() degenerate_face_indices = set()
for face_index, face in enumerate(psk.faces): for face_index, face in enumerate(psk.faces):
point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)] point_indices = [bm.verts[psk.wedges[i].point_index] for i in reversed(face.wedge_indices)]
try: try:
bm_face = bm.faces.new(point_indices) bm_face = bm.faces.new(point_indices)
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)
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).')
bm.to_mesh(mesh_data) bm.to_mesh(mesh_data)
# TEXTURE COORDINATES # TEXTURE COORDINATES
data_index = 0 data_index = 0
uv_layer = mesh_data.uv_layers.new(name='VTXW0000') 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
face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)] face_wedges = [psk.wedges[i] for i in reversed(face.wedge_indices)]
for wedge in face_wedges: for wedge in face_wedges:
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 # EXTRA UVS
if psk.has_extra_uvs and options.should_import_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)) extra_uv_channel_count = int(len(psk.extra_uvs) / len(psk.wedges))
wedge_index_offset = 0 wedge_index_offset = 0
for extra_uv_index in range(extra_uv_channel_count): for extra_uv_index in range(extra_uv_channel_count):
data_index = 0 data_index = 0
uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}') uv_layer = mesh_data.uv_layers.new(name=f'EXTRAUV{extra_uv_index}')
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
for wedge_index in reversed(face.wedge_indices): for wedge_index in reversed(face.wedge_indices):
u, v = psk.extra_uvs[wedge_index_offset + wedge_index] u, v = psk.extra_uvs[wedge_index_offset + wedge_index]
uv_layer.data[data_index].uv = u, 1.0 - v uv_layer.data[data_index].uv = u, 1.0 - v
data_index += 1 data_index += 1
wedge_index_offset += len(psk.wedges) wedge_index_offset += len(psk.wedges)
# VERTEX COLORS # VERTEX COLORS
if psk.has_vertex_colors and options.should_import_vertex_colors: if psk.has_vertex_colors and options.should_import_vertex_colors:
size = (len(psk.points), 4) size = (len(psk.points), 4)
vertex_colors = np.full(size, inf) vertex_colors = np.full(size, inf)
vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR') vertex_color_data = mesh_data.vertex_colors.new(name='VERTEXCOLOR')
ambiguous_vertex_color_point_indices = [] ambiguous_vertex_color_point_indices = []
for wedge_index, wedge in enumerate(psk.wedges): for wedge_index, wedge in enumerate(psk.wedges):
point_index = wedge.point_index point_index = wedge.point_index
psk_vertex_color = psk.vertex_colors[wedge_index].normalized() 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: if vertex_colors[point_index, 0] != inf and tuple(vertex_colors[point_index]) != psk_vertex_color:
ambiguous_vertex_color_point_indices.append(point_index) ambiguous_vertex_color_point_indices.append(point_index)
else: else:
vertex_colors[point_index] = psk_vertex_color vertex_colors[point_index] = psk_vertex_color
if options.vertex_color_space == 'SRGBA': if options.vertex_color_space == 'SRGBA':
for i in range(vertex_colors.shape[0]): for i in range(vertex_colors.shape[0]):
vertex_colors[i, :3] = tuple(map(lambda x: rgb_to_srgb(x), vertex_colors[i, :3])) 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): for loop_index, loop in enumerate(mesh_data.loops):
vertex_color = vertex_colors[loop.vertex_index] vertex_color = vertex_colors[loop.vertex_index]
if vertex_color is not None: if vertex_color is not None:
vertex_color_data.data[loop_index].color = vertex_color vertex_color_data.data[loop_index].color = vertex_color
else: else:
vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0 vertex_color_data.data[loop_index].color = 1.0, 1.0, 1.0, 1.0
if len(ambiguous_vertex_color_point_indices) > 0: if len(ambiguous_vertex_color_point_indices) > 0:
print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.') print(f'WARNING: {len(ambiguous_vertex_color_point_indices)} vertex(es) with ambiguous vertex colors.')
# VERTEX NORMALS # VERTEX NORMALS
if psk.has_vertex_normals and options.should_import_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)) mesh_data.polygons.foreach_set("use_smooth", [True] * len(mesh_data.polygons))
normals = [] normals = []
for vertex_normal in psk.vertex_normals: for vertex_normal in psk.vertex_normals:
normals.append(tuple(vertex_normal)) normals.append(tuple(vertex_normal))
mesh_data.normals_split_custom_set_from_vertices(normals) mesh_data.normals_split_custom_set_from_vertices(normals)
mesh_data.use_auto_smooth = True mesh_data.use_auto_smooth = True
bm.normal_update() bm.normal_update()
bm.free() bm.free()
# 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))): vertex_groups = [None] * len(psk.bones)
import_bone.vertex_group = mesh_object.vertex_groups.new( for bone_index, psk_bone in map(lambda x: (x, psk.bones[x]), vertex_group_bone_indices):
name=import_bone.psk_bone.name.decode('windows-1252')) vertex_groups[bone_index] = mesh_object.vertex_groups.new(name=psk_bone.name.decode('windows-1252'))
for weight in psk.weights: for weight in psk.weights:
import_bones[weight.bone_index].vertex_group.add((weight.point_index,), weight.weight, 'ADD') vertex_groups[weight.bone_index].add((weight.point_index,), weight.weight, 'ADD')
# Add armature modifier to our mesh object. context.scene.collection.objects.link(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) # 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: try:
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
@@ -256,6 +263,18 @@ class PskImportPropertyGroup(PropertyGroup):
options=set(), options=set(),
description='Import extra UV maps from PSKX files, if available' 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( bone_length: FloatProperty(
default=1.0, default=1.0,
min=sys.float_info.epsilon, min=sys.float_info.epsilon,
@@ -269,7 +288,7 @@ class PskImportPropertyGroup(PropertyGroup):
class PskImportOperator(Operator, ImportHelper): class PskImportOperator(Operator, ImportHelper):
bl_idname = 'import.psk' bl_idname = 'import.psk'
bl_label = 'Export' bl_label = 'Import'
bl_options = {'INTERNAL', 'UNDO'} bl_options = {'INTERNAL', 'UNDO'}
__doc__ = 'Load a PSK file' __doc__ = 'Load a PSK file'
filename_ext = '.psk' filename_ext = '.psk'
@@ -287,10 +306,12 @@ class PskImportOperator(Operator, ImportHelper):
options = PskImportOptions() options = PskImportOptions()
options.name = os.path.splitext(os.path.basename(self.filepath))[0] 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_extra_uvs = pg.should_import_extra_uvs
options.should_import_vertex_colors = pg.should_import_vertex_colors options.should_import_vertex_colors = pg.should_import_vertex_colors
options.should_import_vertex_normals = pg.should_import_vertex_normals options.should_import_vertex_normals = pg.should_import_vertex_normals
options.vertex_color_space = pg.vertex_color_space options.vertex_color_space = pg.vertex_color_space
options.should_import_skeleton = pg.should_import_skeleton
options.bone_length = pg.bone_length options.bone_length = pg.bone_length
import_psk(psk, context, options) import_psk(psk, context, options)
@@ -300,14 +321,22 @@ class PskImportOperator(Operator, ImportHelper):
def draw(self, context): def draw(self, context):
pg = context.scene.psk_import pg = context.scene.psk_import
layout = self.layout layout = self.layout
layout.use_property_split = True layout.prop(pg, 'should_import_mesh')
layout.use_property_decorate = False row = layout.column()
layout.prop(pg, 'should_import_vertex_normals') row.use_property_split = True
layout.prop(pg, 'should_import_extra_uvs') row.use_property_decorate = False
layout.prop(pg, 'should_import_vertex_colors') if pg.should_import_mesh:
if pg.should_import_vertex_colors: row.prop(pg, 'should_import_vertex_normals')
layout.prop(pg, 'vertex_color_space') row.prop(pg, 'should_import_extra_uvs')
layout.prop(pg, 'bone_length') 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 = ( classes = (