Merge branch 'main' into Texture-Atlasing

This commit is contained in:
Onan Chew
2024-07-22 17:12:37 -04:00
committed by GitHub
13 changed files with 201 additions and 563 deletions
+14 -10
View File
@@ -1,5 +1,3 @@
if "bpy" not in locals(): if "bpy" not in locals():
import bpy import bpy
from . import ui from . import ui
@@ -7,30 +5,36 @@ if "bpy" not in locals():
from . import functions from . import functions
from .core import register from .core import register
from .core.register import __bl_ordered_classes from .core.register import __bl_ordered_classes
from .core.properties import register_properties from .core import properties
else: else:
import importlib import importlib
importlib.reload(ui) importlib.reload(ui)
importlib.reload(core) importlib.reload(core)
importlib.reload(functions) importlib.reload(functions)
importlib.reload(properties)
def register(): def register():
print("Registering Avatar Toolkit") print("Registering Avatar Toolkit")
# Register the addon properties
properties.register()
# Order the classes before registration # Order the classes before registration
core.register.order_classes() core.register.order_classes()
register_properties() register_properties()
# Register the UI classes # Register the UI classes
# Iterate over the classes to register and register them # Iterate over the classes to register and register them
for cls in __bl_ordered_classes: core.register.register_properties()
print("registering" + str(cls)) for cls in core.register.__bl_ordered_classes:
bpy.utils.register_class(cls) print("registering " + str(cls))
bpy.utils.register_class(cls)
def unregister(): def unregister():
print("Unregistering Avatar Toolkit") print("Unregistering Avatar Toolkit")
# Unregister the UI classes # Unregister the UI classes
# Iterate over the classes to unregister in reverse order and unregister them # Iterate over the classes to unregister in reverse order and unregister them
for cls in reversed(list(__bl_ordered_classes)): for cls in reversed(core.register.__bl_ordered_classes):
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
print("unregistering" + str(cls)) print("unregistering " + str(cls))
core.register.unregister_properties()
properties.unregister()
-1
View File
@@ -1,7 +1,6 @@
# core/__init__.py # core/__init__.py
from .register import register_wrap from .register import register_wrap
from . import pmx
#to reload all things in this directory and import them properly - @989onan #to reload all things in this directory and import them properly - @989onan
if "bpy" not in locals(): if "bpy" not in locals():
+17
View File
@@ -0,0 +1,17 @@
import bpy
# Importers which don't need much code should be added here, however if a importer needs alot of code
# Like the PMX and PMD importers, they should be added to their own files.
# FBX Importer settings borrowed form Cat's Blender Plugin
def import_fbx(filepath):
try:
bpy.ops.import_scene.fbx(
filepath=filepath,
automatic_bone_orientation=False,
use_prepost_rot=False,
use_anim=False
)
except (TypeError, ValueError) as e:
print(f"Error importing FBX: {str(e)}")
-224
View File
@@ -1,224 +0,0 @@
import bpy
import struct
import mathutils
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 as e:
print(f"Error importing PMD file: {filepath}")
print(f"Error details: {str(e)}")
-307
View File
@@ -1,307 +0,0 @@
import bpy
import struct
def read_pmx_header(file):
# Read PMX header information
magic = file.read(4) # Read magic bytes (should be "PMX ")
if magic != b'PMX ':
raise ValueError("Invalid PMX file")
version = struct.unpack('<f', file.read(4))[0] # Read version number (float, 4 bytes)
# Read additional header fields
data_size = struct.unpack('<i', file.read(4))[0] # Read size of remaining header data (int, 4 bytes)
encoding = struct.unpack('<b', file.read(1))[0] # Read encoding type (byte, 1 byte)
additional_uvs = struct.unpack('<b', file.read(1))[0] # Read number of additional UV layers (byte, 1 byte)
vertex_index_size = struct.unpack('<b', file.read(1))[0] # Read vertex index size (byte, 1 byte)
texture_index_size = struct.unpack('<b', file.read(1))[0] # Read texture index size (byte, 1 byte)
material_index_size = struct.unpack('<b', file.read(1))[0] # Read material index size (byte, 1 byte)
bone_index_size = struct.unpack('<b', file.read(1))[0] # Read bone index size (byte, 1 byte)
morph_index_size = struct.unpack('<b', file.read(1))[0] # Read morph index size (byte, 1 byte)
rigid_body_index_size = struct.unpack('<b', file.read(1))[0] # Read rigid body index size (byte, 1 byte)
# Read model name and comments
model_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read model name (string, variable length)
model_english_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read model English name (string, variable length)
model_comment = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read model comment (string, variable length)
model_english_comment = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read model English comment (string, variable length)
return version, encoding, additional_uvs, vertex_index_size, texture_index_size, material_index_size, bone_index_size, morph_index_size, rigid_body_index_size, model_name, model_english_name, model_comment, model_english_comment
def read_vertex(file, vertex_index_size):
position = struct.unpack('<3f', file.read(12)) # Read vertex position (float, 3 * 4 bytes)
normal = struct.unpack('<3f', file.read(12)) # Read vertex normal (float, 3 * 4 bytes)
uv = struct.unpack('<2f', file.read(8)) # Read vertex UV coordinates (float, 2 * 4 bytes)
if vertex_index_size == 1:
bone_indices = list(struct.unpack('<4B', file.read(4))) # Read bone indices (byte, 4 * 1 byte)
elif vertex_index_size == 2:
bone_indices = list(struct.unpack('<4H', file.read(8))) # Read bone indices (short, 4 * 2 bytes)
else:
bone_indices = list(struct.unpack('<4I', file.read(16))) # Read bone indices (int, 4 * 4 bytes)
bone_weights = list(struct.unpack('<4f', file.read(16))) # Read bone weights (float, 4 * 4 bytes)
edge_scale = struct.unpack('<f', file.read(4))[0] # Read edge scale (float, 4 bytes)
return position, normal, uv, bone_indices, bone_weights, edge_scale
def read_material(file, texture_index_size):
material_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read material name (string, variable length)
material_english_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read material English name (string, variable length)
diffuse_color = struct.unpack('<4f', file.read(16)) # Read diffuse color (float, 4 * 4 bytes)
specular_color = struct.unpack('<3f', file.read(12)) # Read specular color (float, 3 * 4 bytes)
specular_strength = struct.unpack('<f', file.read(4))[0] # Read specular strength (float, 4 bytes)
ambient_color = struct.unpack('<3f', file.read(12)) # Read ambient color (float, 3 * 4 bytes)
flag = struct.unpack('<b', file.read(1))[0] # Read flag (byte, 1 byte)
edge_color = struct.unpack('<4f', file.read(16)) # Read edge color (float, 4 * 4 bytes)
edge_size = struct.unpack('<f', file.read(4))[0] # Read edge size (float, 4 bytes)
texture_index = struct.unpack(f'<{texture_index_size}B', file.read(texture_index_size))[0] # Read texture index (byte, texture_index_size bytes)
sphere_texture_index = struct.unpack(f'<{texture_index_size}B', file.read(texture_index_size))[0] # Read sphere texture index (byte, texture_index_size bytes)
sphere_mode = struct.unpack('<b', file.read(1))[0] # Read sphere mode (byte, 1 byte)
toon_sharing_flag = struct.unpack('<b', file.read(1))[0] # Read toon sharing flag (byte, 1 byte)
if toon_sharing_flag == 0:
toon_texture_index = struct.unpack(f'<{texture_index_size}B', file.read(texture_index_size))[0] # Read toon texture index (byte, texture_index_size bytes)
else:
toon_texture_index = struct.unpack('<b', file.read(1))[0] # Read shared toon texture index (byte, 1 byte)
comment = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read material comment (string, variable length)
return material_name, material_english_name, diffuse_color, specular_color, specular_strength, ambient_color, flag, edge_color, edge_size, texture_index, sphere_texture_index, sphere_mode, toon_sharing_flag, toon_texture_index, comment
def read_bone(file, bone_index_size):
bone_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read bone name (string, variable length)
bone_english_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read bone English name (string, variable length)
position = struct.unpack('<3f', file.read(12)) # Read bone position (float, 3 * 4 bytes)
parent_bone_index = struct.unpack(f'<{bone_index_size}B', file.read(bone_index_size))[0] # Read parent bone index (byte, bone_index_size bytes)
layer = struct.unpack('<i', file.read(4))[0] # Read bone layer (int, 4 bytes)
flag = struct.unpack('<H', file.read(2))[0] # Read bone flag (short, 2 bytes)
if flag & 0x0001:
tail_position = struct.unpack('<3f', file.read(12)) # Read bone tail position (float, 3 * 4 bytes)
else:
tail_index = struct.unpack(f'<{bone_index_size}B', file.read(bone_index_size))[0] # Read bone tail index (byte, bone_index_size bytes)
if flag & 0x0100 or flag & 0x0200:
inherit_bone_parent_index = struct.unpack(f'<{bone_index_size}B', file.read(bone_index_size))[0] # Read inherit bone parent index (byte, bone_index_size bytes)
inherit_bone_parent_influence = struct.unpack('<f', file.read(4))[0] # Read inherit bone parent influence (float, 4 bytes)
if flag & 0x0400:
fixed_axis = struct.unpack('<3f', file.read(12)) # Read fixed axis (float, 3 * 4 bytes)
if flag & 0x0800:
local_x_vector = struct.unpack('<3f', file.read(12)) # Read local X-axis vector (float, 3 * 4 bytes)
local_z_vector = struct.unpack('<3f', file.read(12)) # Read local Z-axis vector (float, 3 * 4 bytes)
if flag & 0x2000:
external_key = struct.unpack('<i', file.read(4))[0] # Read external key (int, 4 bytes)
if flag & 0x0020:
ik_target_bone_index = struct.unpack(f'<{bone_index_size}B', file.read(bone_index_size))[0] # Read IK target bone index (byte, bone_index_size bytes)
ik_loop_count = struct.unpack('<i', file.read(4))[0] # Read IK loop count (int, 4 bytes)
ik_limit_radian = struct.unpack('<f', file.read(4))[0] # Read IK limit angle (float, 4 bytes)
ik_link_count = struct.unpack('<i', file.read(4))[0] # Read IK link count (int, 4 bytes)
ik_links = []
for _ in range(ik_link_count):
ik_link_bone_index = struct.unpack(f'<{bone_index_size}B', file.read(bone_index_size))[0] # Read IK link bone index (byte, bone_index_size bytes)
ik_link_limit_min = struct.unpack('<3f', file.read(12)) # Read IK link limit minimum (float, 3 * 4 bytes)
ik_link_limit_max = struct.unpack('<3f', file.read(12)) # Read IK link limit maximum (float, 3 * 4 bytes)
ik_links.append((ik_link_bone_index, ik_link_limit_min, ik_link_limit_max))
return bone_name, bone_english_name, position, parent_bone_index, layer, flag, tail_position, inherit_bone_parent_index, inherit_bone_parent_influence, fixed_axis, local_x_vector, local_z_vector, external_key, ik_target_bone_index, ik_loop_count, ik_limit_radian, ik_links
def read_morph(file, morph_index_size):
morph_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read morph name (string, variable length)
morph_english_name = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read morph English name (string, variable length)
panel = struct.unpack('<b', file.read(1))[0] # Read panel type (byte, 1 byte)
morph_type = struct.unpack('<b', file.read(1))[0] # Read morph type (byte, 1 byte)
offset_size = struct.unpack('<i', file.read(4))[0] # Read offset size (int, 4 bytes)
morph_data = []
for _ in range(offset_size):
if morph_type == 0: # Group
morph_index = struct.unpack(f'<{morph_index_size}B', file.read(morph_index_size))[0] # Read morph index (byte, morph_index_size bytes)
morph_value = struct.unpack('<f', file.read(4))[0] # Read morph value (float, 4 bytes)
morph_data.append((morph_index, morph_value))
elif morph_type == 1: # Vertex
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
position_offset = struct.unpack('<3f', file.read(12)) # Read position offset (float, 3 * 4 bytes)
morph_data.append((vertex_index, position_offset))
elif morph_type == 2: # Bone
bone_index = struct.unpack(f'<{morph_index_size}B', file.read(morph_index_size))[0] # Read bone index (byte, morph_index_size bytes)
position_offset = struct.unpack('<3f', file.read(12)) # Read position offset (float, 3 * 4 bytes)
rotation_offset = struct.unpack('<4f', file.read(16)) # Read rotation offset (float, 4 * 4 bytes)
morph_data.append((bone_index, position_offset, rotation_offset))
elif morph_type == 3: # UV
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
uv_offset = struct.unpack('<4f', file.read(16)) # Read UV offset (float, 4 * 4 bytes)
morph_data.append((vertex_index, uv_offset))
elif morph_type == 4: # UV extended1
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
uv_offset = struct.unpack('<4f', file.read(16)) # Read UV offset (float, 4 * 4 bytes)
morph_data.append((vertex_index, uv_offset))
elif morph_type == 5: # UV extended2
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
uv_offset = struct.unpack('<4f', file.read(16)) # Read UV offset (float, 4 * 4 bytes)
morph_data.append((vertex_index, uv_offset))
elif morph_type == 6: # UV extended3
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
uv_offset = struct.unpack('<4f', file.read(16)) # Read UV offset (float, 4 * 4 bytes)
morph_data.append((vertex_index, uv_offset))
elif morph_type == 7: # UV extended4
vertex_index = struct.unpack('<i', file.read(4))[0] # Read vertex index (int, 4 bytes)
uv_offset = struct.unpack('<4f', file.read(16)) # Read UV offset (float, 4 * 4 bytes)
morph_data.append((vertex_index, uv_offset))
elif morph_type == 8: # Material
material_index = struct.unpack('<i', file.read(4))[0] # Read material index (int, 4 bytes)
offset_type = struct.unpack('<b', file.read(1))[0] # Read offset type (byte, 1 byte)
diffuse_offset = struct.unpack('<4f', file.read(16)) # Read diffuse color offset (float, 4 * 4 bytes)
specular_offset = struct.unpack('<3f', file.read(12)) # Read specular color offset (float, 3 * 4 bytes)
specular_factor_offset = struct.unpack('<f', file.read(4))[0] # Read specular factor offset (float, 4 bytes)
ambient_offset = struct.unpack('<3f', file.read(12)) # Read ambient color offset (float, 3 * 4 bytes)
edge_color_offset = struct.unpack('<4f', file.read(16)) # Read edge color offset (float, 4 * 4 bytes)
edge_size_offset = struct.unpack('<f', file.read(4))[0] # Read edge size offset (float, 4 bytes)
texture_factor_offset = struct.unpack('<4f', file.read(16)) # Read texture factor offset (float, 4 * 4 bytes)
sphere_texture_factor_offset = struct.unpack('<4f', file.read(16)) # Read sphere texture factor offset (float, 4 * 4 bytes)
toon_texture_factor_offset = struct.unpack('<4f', file.read(16)) # Read toon texture factor offset (float, 4 * 4 bytes)
morph_data.append((material_index, offset_type, diffuse_offset, specular_offset, specular_factor_offset, ambient_offset, edge_color_offset, edge_size_offset, texture_factor_offset, sphere_texture_factor_offset, toon_texture_factor_offset))
return morph_name, morph_english_name, panel, morph_type, morph_data
def import_pmx(filepath):
try:
with open(filepath, 'rb') as file:
version, encoding, additional_uvs, vertex_index_size, texture_index_size, material_index_size, bone_index_size, morph_index_size, rigid_body_index_size, model_name, model_english_name, model_comment, model_english_comment = read_pmx_header(file)
# Read vertices
vertex_count = struct.unpack('<i', file.read(4))[0] # Read vertex count (int, 4 bytes)
vertices = []
for _ in range(vertex_count):
position, normal, uv, bone_indices, bone_weights, edge_scale = read_vertex(file, vertex_index_size)
vertices.append((position, normal, uv, bone_indices, bone_weights, edge_scale))
# Read faces
face_count = struct.unpack('<i', file.read(4))[0] # Read face count (int, 4 bytes)
faces = []
for _ in range(face_count // 3):
face_indices = struct.unpack('<3i', file.read(12)) # Read face indices (int, 3 * 4 bytes)
faces.append(face_indices)
# Read textures
texture_count = struct.unpack('<i', file.read(4))[0] # Read texture count (int, 4 bytes)
textures = []
for _ in range(texture_count):
texture_path = str(file.read(struct.unpack('<i', file.read(4))[0]), 'utf-16-le', errors='replace') # Read texture path (string, variable length)
textures.append(texture_path)
# Read materials
material_count = struct.unpack('<i', file.read(4))[0] # Read material count (int, 4 bytes)
materials = []
for _ in range(material_count):
material_name, material_english_name, diffuse_color, specular_color, specular_strength, ambient_color, flag, edge_color, edge_size, texture_index, sphere_texture_index, sphere_mode, toon_sharing_flag, toon_texture_index, comment = read_material(file, texture_index_size)
materials.append((material_name, material_english_name, diffuse_color, specular_color, specular_strength, ambient_color, flag, edge_color, edge_size, texture_index, sphere_texture_index, sphere_mode, toon_sharing_flag, toon_texture_index, comment))
# Read bones
bone_count = struct.unpack('<i', file.read(4))[0] # Read bone count (int, 4 bytes)
bones = []
for _ in range(bone_count):
bone_name, bone_english_name, position, parent_bone_index, layer, flag, tail_position, inherit_bone_parent_index, inherit_bone_parent_influence, fixed_axis, local_x_vector, local_z_vector, external_key, ik_target_bone_index, ik_loop_count, ik_limit_radian, ik_links = read_bone(file, bone_index_size)
bones.append((bone_name, bone_english_name, position, parent_bone_index, layer, flag, tail_position, inherit_bone_parent_index, inherit_bone_parent_influence, fixed_axis, local_x_vector, local_z_vector, external_key, ik_target_bone_index, ik_loop_count, ik_limit_radian, ik_links))
# Read morphs
morph_count = struct.unpack('<i', file.read(4))[0] # Read morph count (int, 4 bytes)
morphs = []
for _ in range(morph_count):
morph_name, morph_english_name, panel, morph_type, morph_data = read_morph(file, morph_index_size)
morphs.append((morph_name, morph_english_name, panel, morph_type, morph_data))
# Create Blender objects and assign PMX 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(material_data[0])
material.diffuse_color = material_data[2]
material.specular_color = material_data[3]
material.specular_intensity = material_data[4]
material.ambient = material_data[5]
# Set other material properties based on the PMX 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[2]
bone.tail = bone_data[6]
if bone_data[3] != -1:
parent_bone = armature.edit_bones[bone_data[3]]
bone.parent = parent_bone
# Set other bone properties based on the PMX data
bpy.ops.object.mode_set(mode='OBJECT')
# Assign bone weights to the mesh
for i, vertex in enumerate(vertices):
for j in range(4):
if vertex[3][j] != -1:
bone_name = bones[vertex[3][j]][0]
weight = vertex[4][j]
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 morphs to the mesh
for morph_data in morphs:
morph_name = morph_data[0]
morph_type = morph_data[3]
if morph_type == 1: # Vertex morph
shape_key = obj.shape_key_add(name=morph_name)
for offset_data in morph_data[4]:
vertex_index = offset_data[0]
offset = offset_data[1]
shape_key.data[vertex_index].co += mathutils.Vector(offset)
# Handle other morph types based on the PMX specification
print(f"Successfully imported PMX file: {filepath}")
print(f"Model Name: {model_name}")
print(f"Model English Name: {model_english_name}")
print(f"Model Comment: {model_comment}")
print(f"Model English Comment: {model_english_comment}")
except Exception as e:
print(f"Error importing PMX file: {filepath}")
print(f"Error details: {str(e)}")
+22 -17
View File
@@ -1,10 +1,12 @@
import bpy import bpy
from ..functions.translations import t, get_languages_list, update_ui
from ..core.register import register_property
from typing import Tuple
from bpy.types import Scene, PropertyGroup, Object, Material, TextureNode, Context, SceneObjects from bpy.types import Scene, PropertyGroup, Object, Material, TextureNode, Context, SceneObjects
from bpy.props import BoolProperty, EnumProperty, FloatProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty from bpy.props import BoolProperty, EnumProperty, FloatProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty
from bpy.utils import register_class from bpy.utils import register_class
class material_list_bool: class material_list_bool:
#For the love that is holy do not ever touch these. If this was java I would make these private #For the love that is holy do not ever touch these. If this was java I would make these private
#They should only be accessed via context.scene.texture_atlas_Has_Mat_List_Shown #They should only be accessed via context.scene.texture_atlas_Has_Mat_List_Shown
@@ -47,9 +49,14 @@ class material_list_bool:
class SceneMatClass(PropertyGroup): class SceneMatClass(PropertyGroup):
mat: PointerProperty(type=Material) mat: PointerProperty(type=Material)
def register() -> None:
def register_properties(): register_property((Scene, "language", bpy.props.EnumProperty(
name=t("Settings.language.label"),
description=t("Settings.language.desc"),
items=get_languages_list,
update=update_ui
)))
register_class(SceneMatClass) register_class(SceneMatClass)
#happy with how compressed this get_texture_node_list method is - @989onan #happy with how compressed this get_texture_node_list method is - @989onan
@@ -65,23 +72,21 @@ def register_properties():
Material.texture_atlas_albedo = EnumProperty(name="Albedo", description="The texture that will be used for the albedo map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_albedo", EnumProperty(name="Albedo", description="The texture that will be used for the albedo map atlas", default=0, items=get_texture_node_list))
Material.texture_atlas_normal = EnumProperty(name="Normal", description="The texture that will be used for the normal map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_normal", EnumProperty(name="Normal", description="The texture that will be used for the normal map atlas", default=0, items=get_texture_node_list))
Material.texture_atlas_emission = EnumProperty(name="Emission", description="The texture that will be used for the emission map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_emission", EnumProperty(name="Emission", description="The texture that will be used for the emission map atlas", default=0, items=get_texture_node_list))
Material.texture_atlas_ambient_occlusion = EnumProperty(name="Ambient Occlusion", description="The texture that will be used for the ambient occlusion map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_ambient_occlusion", EnumProperty(name="Ambient Occlusion", description="The texture that will be used for the ambient occlusion map atlas", default=0, items=get_texture_node_list))
Material.texture_atlas_height = EnumProperty(name="Height", description="The texture that will be used for the height map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_height", EnumProperty(name="Height", description="The texture that will be used for the height map atlas", default=0, items=get_texture_node_list))
Material.texture_atlas_roughness = EnumProperty(name="Roughness", description="The texture that will be used for the roughness map atlas", default=0, items=get_texture_node_list) register_property(Material, "texture_atlas_roughness", EnumProperty(name="Roughness", description="The texture that will be used for the roughness map atlas", default=0, items=get_texture_node_list))
Scene.texture_atlas_material_index = IntProperty(default=-1, get=(lambda self : -1), set=(lambda self,context : None)) register_property(Scene, "texture_atlas_material_index", IntProperty(default=-1, get=(lambda self : -1), set=(lambda self,context : None)))
Scene.materials = CollectionProperty(type=SceneMatClass) register_property(Scene, "materials", CollectionProperty(type=SceneMatClass))
Scene.texture_atlas_Has_Mat_List_Shown = BoolProperty(default=False, get=material_list_bool.get_bool, set=material_list_bool.set_bool) register_property(Scene, "texture_atlas_Has_Mat_List_Shown", BoolProperty(default=False, get=material_list_bool.get_bool, set=material_list_bool.set_bool))
#Scene.texture_atlas_properties = PointerProperty(type=Texture_Atlas_PropertyGroup) def unregister() -> None:
pass
+13
View File
@@ -5,6 +5,8 @@ import typing
__bl_classes = [] __bl_classes = []
# List to store the ordered classes for registration # List to store the ordered classes for registration
__bl_ordered_classes = [] __bl_ordered_classes = []
# List to store props to register
__bl_props = []
def register_wrap(cls): def register_wrap(cls):
# Check if the class has a 'bl_rna' attribute (indicating it's a Blender class) # Check if the class has a 'bl_rna' attribute (indicating it's a Blender class)
@@ -13,6 +15,17 @@ def register_wrap(cls):
__bl_classes.append(cls) __bl_classes.append(cls)
return cls return cls
# Register all properties
def register_property(prop):
__bl_props.append(prop)
def register_properties():
for prop in __bl_props:
setattr(prop[0], prop[1], prop[2])
def unregister_properties():
for prop in reversed(__bl_props):
delattr(prop[0], prop[1])
#- @989onan had to add this from Cats. This is extremely important else you will be screamed at by register order issues! #- @989onan had to add this from Cats. This is extremely important else you will be screamed at by register order issues!
# Find order to register to solve dependencies # Find order to register to solve dependencies
+64
View File
@@ -0,0 +1,64 @@
import os
import json
import bpy
from bpy.app.translations import locale
from ..core.register import register_wrap
from typing import Dict, List, Tuple
main_dir: str = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
resources_dir: str = os.path.join(main_dir, "resources")
translations_dir: str = os.path.join(resources_dir, "translations")
dictionary: Dict[str, str] = dict()
languages: List[str] = []
verbose: bool = True
def load_translations() -> None:
global dictionary, languages
dictionary = dict()
languages = ["auto"]
language: str = bpy.context.preferences.view.language
for i in os.listdir(translations_dir):
languages.append(i.split(".")[0])
translation_file: str = os.path.join(translations_dir, language + ".json")
if os.path.exists(translation_file):
with open(translation_file, 'r', encoding='utf-8') as file:
dictionary = json.load(file)["messages"]
else:
custom_language: str = language.split("_")[0]
custom_translation_file: str = os.path.join(translations_dir, custom_language + ".json")
if os.path.exists(custom_translation_file):
with open(custom_translation_file, 'r', encoding='utf-8') as file:
dictionary = json.load(file)["messages"]
else:
print(f"Translation file not found for language: {language}")
default_file: str = os.path.join(translations_dir, "en_US.json")
if os.path.exists(default_file):
with open(default_file, 'r', encoding='utf-8') as file:
dictionary = json.load(file)["messages"]
else:
print("Default translation file 'en_US.json' not found.")
def t(phrase: str, *args, **kwargs) -> str:
output: str = dictionary.get(phrase)
if output is None:
if verbose:
print('Warning: Unknown phrase: ' + phrase)
return phrase
return output.format(*args, **kwargs)
def get_languages_list(self, context) -> List[Tuple[str, str, str]]:
choices: List[Tuple[str, str, str]] = []
for language in languages:
choices.append((language, language, language))
return choices
def update_ui(self, context) -> None:
load_translations()
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
load_translations()
+9
View File
@@ -0,0 +1,9 @@
{
"messages": {
"Settings.label": "Settings",
"Settings.language.label": "Language",
"Settings.language.desc": "Select the language for the addon's UI"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"messages": {
"Settings.label": "Settings Ja Test",
"Settings.language.label": "Language Ja Test",
"Settings.language.desc": "Select the language for the addon's UI Ja Test"
}
}
-4
View File
@@ -13,7 +13,3 @@ else:
for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]: for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]:
print("reloading " +module_name) print("reloading " +module_name)
exec("importlib.reload("+module_name+")") exec("importlib.reload("+module_name+")")
+35
View File
@@ -5,6 +5,7 @@ from bpy.types import Context
from ..core.import_pmx import import_pmx from ..core.import_pmx import import_pmx
from ..core.import_pmd import import_pmd from ..core.import_pmd import import_pmd
from ..core.importer import import_fbx
@register_wrap @register_wrap
class AvatarToolkitQuickAccessPanel(bpy.types.Panel): class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
@@ -46,12 +47,17 @@ class AVATAR_TOOLKIT_OT_import_menu(bpy.types.Operator):
layout.label(text="Select Import Method") layout.label(text="Select Import Method")
layout.operator("avatar_toolkit.import_pmx", text="Import PMX") layout.operator("avatar_toolkit.import_pmx", text="Import PMX")
layout.operator("avatar_toolkit.import_pmd", text="Import PMD") layout.operator("avatar_toolkit.import_pmd", text="Import PMD")
layout.operator("avatar_toolkit.import_fbx", text="Import FBX")
@register_wrap @register_wrap
class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator): class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator):
bl_idname = "avatar_toolkit.export_menu" bl_idname = "avatar_toolkit.export_menu"
bl_label = "Export Menu" bl_label = "Export Menu"
@classmethod
def poll(cls, context):
return any(obj.type == 'MESH' for obj in context.scene.objects)
def execute(self, context: Context): def execute(self, context: Context):
return {'FINISHED'} return {'FINISHED'}
@@ -63,6 +69,7 @@ class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator):
layout = self.layout layout = self.layout
layout.label(text="Select Export Method") layout.label(text="Select Export Method")
layout.operator("avatar_toolkit.export_resonite", text="Export Resonite") layout.operator("avatar_toolkit.export_resonite", text="Export Resonite")
layout.operator("avatar_toolkit.export_fbx", text="Export FBX")
@register_wrap @register_wrap
class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator): class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator):
@@ -93,3 +100,31 @@ class AVATAR_TOOLKIT_OT_import_pmd(bpy.types.Operator):
def invoke(self, context: Context, event): def invoke(self, context: Context, event):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@register_wrap
class AVATAR_TOOLKIT_OT_import_fbx(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_fbx"
bl_label = "Import FBX"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
import_fbx(self.filepath)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
@register_wrap
class AVATAR_TOOLKIT_OT_export_fbx(bpy.types.Operator):
bl_idname = 'avatar_toolkit.export_fbx'
bl_label = "Export FBX"
bl_description = "Export the model as FBX"
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
def execute(self, context):
bpy.ops.export_scene.fbx('INVOKE_DEFAULT')
return {'FINISHED'}
+19
View File
@@ -0,0 +1,19 @@
import bpy
from ..core.register import register_wrap
from .panel import AvatarToolkitPanel
from ..functions.translations import t
@register_wrap
class AvatarToolkitSettingsPanel(bpy.types.Panel):
bl_label = t("Settings.label")
bl_idname = "OBJECT_PT_avatar_toolkit_settings"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Avatar Toolkit"
bl_parent_id = "OBJECT_PT_avatar_toolkit"
def draw(self, context):
layout = self.layout
props = context.scene
layout.prop(props, "language")