Files
Avatar-Toolkit/core/import_pmd.py
T
989onan c0509bbaf4 fix stuff again
- got rid of upstream stuff generated by github, which made it blow up...
- got closer on the pmx importer working
2024-06-14 21:29:30 -04:00

226 lines
10 KiB
Python

import bpy
import struct
import mathutils
import traceback
def read_pmd_header(file):
# Read PMD header information
magic = file.read(3)
if magic != b'Pmd':
raise ValueError("Invalid PMD file")
version = struct.unpack('<f', file.read(4))[0]
# Read additional header fields
model_name = file.read(20).decode('shift-jis').rstrip('\0')
comment = file.read(256).decode('shift-jis').rstrip('\0')
return version, model_name, comment
def read_pmd_vertex(file):
# Read PMD vertex information
position = struct.unpack('<3f', file.read(12))
normal = struct.unpack('<3f', file.read(12))
uv = struct.unpack('<2f', file.read(8))
bone_indices = list(struct.unpack('<2H', file.read(4)))
bone_weights = struct.unpack('<b', file.read(1))[0] / 100
edge_flag = struct.unpack('<b', file.read(1))[0]
return position, normal, uv, bone_indices, bone_weights, edge_flag
def read_pmd_material(file):
# Read PMD material information
diffuse_color = struct.unpack('<4f', file.read(16))
specular_color = struct.unpack('<3f', file.read(12))
specular_intensity = struct.unpack('<f', file.read(4))[0]
ambient_color = struct.unpack('<3f', file.read(12))
toon_index = struct.unpack('<b', file.read(1))[0]
edge_flag = struct.unpack('<b', file.read(1))[0]
vertex_count = struct.unpack('<i', file.read(4))[0]
texture_file_name = file.read(20).decode('shift-jis').rstrip('\0')
return diffuse_color, specular_color, specular_intensity, ambient_color, toon_index, edge_flag, vertex_count, texture_file_name
def read_pmd_bone(file):
# Read PMD bone information
bone_name = file.read(20).decode('shift-jis').rstrip('\0')
parent_bone_index = struct.unpack('<h', file.read(2))[0]
tail_pos_bone_index = struct.unpack('<h', file.read(2))[0]
bone_type = struct.unpack('<b', file.read(1))[0]
ik_parent_bone_index = struct.unpack('<h', file.read(2))[0]
bone_head_pos = struct.unpack('<3f', file.read(12))
return bone_name, parent_bone_index, tail_pos_bone_index, bone_type, ik_parent_bone_index, bone_head_pos
def read_pmd_ik(file):
# Read PMD IK information
ik_bone_index = struct.unpack('<h', file.read(2))[0]
ik_target_bone_index = struct.unpack('<h', file.read(2))[0]
ik_chain_length = struct.unpack('<b', file.read(1))[0]
iterations = struct.unpack('<h', file.read(2))[0]
limit_angle = struct.unpack('<f', file.read(4))[0]
ik_child_bone_indices = []
for _ in range(ik_chain_length):
ik_child_bone_index = struct.unpack('<h', file.read(2))[0]
ik_child_bone_indices.append(ik_child_bone_index)
return ik_bone_index, ik_target_bone_index, ik_chain_length, iterations, limit_angle, ik_child_bone_indices
def read_pmd_morph(file):
# Read PMD morph information
morph_name = file.read(20).decode('shift-jis').rstrip('\0')
morph_vertex_count = struct.unpack('<i', file.read(4))[0]
morph_type = struct.unpack('<b', file.read(1))[0]
morph_vertices = []
for _ in range(morph_vertex_count):
morph_vertex_index = struct.unpack('<i', file.read(4))[0]
morph_vertex_pos = struct.unpack('<3f', file.read(12))
morph_vertices.append((morph_vertex_index, morph_vertex_pos))
return morph_name, morph_vertex_count, morph_type, morph_vertices
def import_pmd(filepath):
try:
with open(filepath, 'rb') as file:
version, model_name, comment = read_pmd_header(file)
# Read vertices
vertex_count = struct.unpack('<i', file.read(4))[0]
vertices = []
for _ in range(vertex_count):
position, normal, uv, bone_indices, bone_weights, edge_flag = read_pmd_vertex(file)
vertices.append((position, normal, uv, bone_indices, bone_weights, edge_flag))
# Read faces
face_count = struct.unpack('<i', file.read(4))[0]
faces = []
for _ in range(face_count // 3):
face_indices = struct.unpack('<3i', file.read(12))
faces.append(face_indices)
# Read materials
material_count = struct.unpack('<i', file.read(4))[0]
materials = []
for _ in range(material_count):
diffuse_color, specular_color, specular_intensity, ambient_color, toon_index, edge_flag, vertex_count, texture_file_name = read_pmd_material(file)
materials.append((diffuse_color, specular_color, specular_intensity, ambient_color, toon_index, edge_flag, vertex_count, texture_file_name))
# Read bones
bone_count = struct.unpack('<h', file.read(2))[0]
bones = []
for _ in range(bone_count):
bone_name, parent_bone_index, tail_pos_bone_index, bone_type, ik_parent_bone_index, bone_head_pos = read_pmd_bone(file)
bones.append((bone_name, parent_bone_index, tail_pos_bone_index, bone_type, ik_parent_bone_index, bone_head_pos))
# Read IKs
ik_count = struct.unpack('<h', file.read(2))[0]
iks = []
for _ in range(ik_count):
ik_bone_index, ik_target_bone_index, ik_chain_length, iterations, limit_angle, ik_child_bone_indices = read_pmd_ik(file)
iks.append((ik_bone_index, ik_target_bone_index, ik_chain_length, iterations, limit_angle, ik_child_bone_indices))
# Read morphs
morph_count = struct.unpack('<h', file.read(2))[0]
morphs = []
for _ in range(morph_count):
morph_name, morph_vertex_count, morph_type, morph_vertices = read_pmd_morph(file)
morphs.append((morph_name, morph_vertex_count, morph_type, morph_vertices))
# Create Blender objects and assign PMD data
mesh = bpy.data.meshes.new(model_name)
mesh.from_pydata([v[0] for v in vertices], [], faces)
mesh.update()
obj = bpy.data.objects.new(model_name, mesh)
bpy.context.collection.objects.link(obj)
# Assign vertex normals
for i, vertex in enumerate(vertices):
mesh.vertices[i].normal = vertex[1]
# Assign UV coordinates
uv_layer = mesh.uv_layers.new()
for i, vertex in enumerate(vertices):
uv_layer.data[i].uv = vertex[2]
# Assign materials
for material_data in materials:
material = bpy.data.materials.new(f"Material_{len(mesh.materials)}")
material.diffuse_color = material_data[0]
material.specular_color = material_data[1]
material.specular_intensity = material_data[2]
material.ambient = material_data[3]
# Set other material properties based on the PMD data
mesh.materials.append(material)
# Create armature and assign bones
armature = bpy.data.armatures.new(model_name + "_Armature")
armature_obj = bpy.data.objects.new(model_name + "_Armature", armature)
bpy.context.collection.objects.link(armature_obj)
bpy.context.view_layer.objects.active = armature_obj
bpy.ops.object.mode_set(mode='EDIT')
for bone_data in bones:
bone = armature.edit_bones.new(bone_data[0])
bone.head = bone_data[5]
if bone_data[1] != -1:
parent_bone = armature.edit_bones[bone_data[1]]
bone.parent = parent_bone
bone.tail = parent_bone.head
else:
bone.tail = bone.head + mathutils.Vector((0, 0.1, 0))
# Set other bone properties based on the PMD data
bpy.ops.object.mode_set(mode='OBJECT')
# Assign bone weights to the mesh
for i, vertex in enumerate(vertices):
for j in range(2):
if vertex[3][j] != 65535:
bone_name = bones[vertex[3][j]][0]
weight = vertex[4] if j == 0 else 1 - vertex[4]
vertex_group = obj.vertex_groups.get(bone_name)
if not vertex_group:
vertex_group = obj.vertex_groups.new(name=bone_name)
vertex_group.add([i], weight, 'REPLACE')
# Assign IK constraints to bones
for ik_data in iks:
ik_bone = armature.bones[bones[ik_data[0]][0]]
ik_target_bone = armature.bones[bones[ik_data[1]][0]]
ik_constraint = ik_bone.constraints.new('IK')
ik_constraint.target = armature_obj
ik_constraint.subtarget = ik_target_bone.name
ik_constraint.chain_count = ik_data[2]
ik_constraint.iterations = ik_data[3]
ik_constraint.limit_mode = 'LIMITDIST_INSIDE'
ik_constraint.limit_mode_max_x = ik_data[4]
# Assign morphs to the mesh
for morph_data in morphs:
morph_name = morph_data[0]
morph_type = morph_data[2]
if morph_type == 0: # Vertex morph
shape_key = obj.shape_key_add(name=morph_name)
for vertex_data in morph_data[3]:
vertex_index = vertex_data[0]
vertex_offset = vertex_data[1]
shape_key.data[vertex_index].co += mathutils.Vector(vertex_offset)
print(f"Successfully imported PMD file: {filepath}")
print(f"Model Name: {model_name}")
print(f"Comment: {comment}")
except Exception:
print(f"Error importing PMD file: {filepath}")
print(f"Error details: {traceback.format_exc()}")