File size: 8,380 Bytes
5c48b81 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
import json
import bpy
import os
def import_obj(filepath):
"""导入OBJ文件"""
if not os.path.exists(filepath):
raise FileNotFoundError(f"文件不存在:{filepath}")
bpy.ops.wm.obj_import(filepath=filepath)
print(f"成功导入:{filepath}")
def create_armature_from_bone_tree(obj, bone_tree_path):
"""根据骨骼树JSON创建骨骼系统并绑定到模型"""
with open(bone_tree_path, 'r') as f:
bones_data = json.load(f)['bones'][0]
# 创建新骨骼
bpy.ops.object.armature_add(enter_editmode=True)
armature = bpy.context.object
armature.name = "ModelArmature"
edit_bones = armature.data.edit_bones
# 创建骨骼树的递归函数
# root_bone = edit_bones.new(bones_data['name'])
# print(bones_data['name'])
def recursive_create_bones(parent_bone, bone_list):
for bone_data in bone_list:
new_bone = edit_bones.new(bone_data['name'])
print(new_bone.name)
new_bone.head = bone_data['position']
new_bone.tail = [*new_bone.head[:2], new_bone.head[2]+1] # 设置轴向长度
if parent_bone:
new_bone.parent = parent_bone
if 'children' in bone_data:
recursive_create_bones(new_bone, bone_data['children'])
# recursive_create_bones(new_bone, bone_list=bone_data.get('children', []))
# 从根骨骼开始构建
# print(bones_data.get('children', []))
recursive_create_bones(None, [bones_data])
# 设置骨骼与模型绑定
obj.parent = armature
mod = obj.modifiers.new("Armature", 'ARMATURE')
mod.object = armature
mod.use_vertex_groups = True
# 创建顶点组(与骨骼同名)
# print(armature.data.edit_bones)
for bone in armature.data.edit_bones:
# print(bone.name)
if bone.name not in obj.vertex_groups:
obj.vertex_groups.new(name=bone.name)
else:
print(f"顶点组 {bone.name} 已存在")
# 返回创建的骨骼对象用于后续操作
bpy.ops.object.mode_set(mode='OBJECT')
return armature
def apply_vertex_weights(obj, weight_file):
"""根据权重JSON设置顶点权重"""
with open(weight_file, 'r') as f:
js_data = json.load(f)
try:
weights = js_data.get('vertex_weights', {})
except:
weights = js_data
# 获取所有顶点组名称
bpy.ops.object.mode_set(mode='OBJECT')
existing_vertex_groups = {vg.name: vg for vg in obj.vertex_groups}
# print(existing_vertex_groups)
# 遍历每个顶点的权重
for vert_idx, weight_dict in enumerate(weights):
if vert_idx >= len(obj.data.vertices):
print(f"顶点索引 {vert_idx} 超出范围")
continue
# 清除当前顶点的所有权重
for group in obj.vertex_groups:
if vert_idx < len(obj.data.vertices):
group.remove([vert_idx])
# 重新分配权重
bones = ['root', 'neck', 'jaw', 'leftEye', 'rightEye']
# for vert_idx, weight_dict in enumerate(weights):
# print(obj.vertex_groups)
for bone_idx, weight in enumerate(weight_dict):
bone_name = bones[bone_idx]
# print(bone_name)
if bone_name not in existing_vertex_groups:
print(f"顶点组 {bone_name} 不存在,跳过")
continue
vgroup = existing_vertex_groups[bone_name]
if vgroup and vert_idx < len(obj.data.vertices):
vgroup.add([vert_idx], weight, 'REPLACE')
else:
print(f"顶点索引 {vert_idx} 或顶点组 {bone_name} 无效")
def add_shape_keys(base_obj, bs_obj_files):
"""添加多个Shape Keys(表情文件)"""
if not base_obj.data.shape_keys:
base_obj.shape_key_add(name="Basis")
for idx, path in enumerate(bs_obj_files):
if not os.path.exists(path):
print(f"表情文件缺失:{path}")
continue
# 导入表情模型(需保持基础模型选中)
bpy.ops.wm.obj_import(filepath=path)
imported_obj = [obj for obj in bpy.data.objects if obj.select_get()][0]
# 创建新的Shape Key
new_sk_name = os.path.basename(path).split('.')[0]
new_sk = base_obj.shape_key_add(name=new_sk_name)
# 复制顶点位置到Shape Key
for v in base_obj.data.vertices:
if v.index < len(imported_obj.data.vertices):
new_sk.data[v.index].co = imported_obj.data.vertices[v.index].co
# 清理临时对象
bpy.data.objects.remove(imported_obj)
def layout_bones_pose(armature, pose_config):
"""设置骨骼的初始姿势(可选)"""
if pose_config:
with open(pose_config, 'r') as f:
pose_data = json.load(f)
for bone in armature.pose.bones:
if bone.name in pose_data:
bone.rotation_euler = tuple(pose_data[bone.name]['rotation'])
bone.location = tuple(pose_data[bone.name]['location'])
def apply_rotation(obj):
"""手动应用 90 度旋转(绕 X 轴)并将变换应用到模型"""
obj.rotation_euler = (1.5708, 0, 0) # 90 度旋转(弧度制:1.5708 = π/2)
bpy.context.view_layer.update() # 更新场景以应用旋转
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) # 应用旋转
print(f"Applied 90-degree rotation to object: {obj.name}")
def export_as_glb(obj, output_path, output_vertex_order_file):
"""导出为GLB格式,确保包含骨骼信息"""
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# 切换到对象模式
bpy.ops.object.mode_set(mode='OBJECT')
# 获取基础模型
base_objects = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH']
if len(base_objects) != 1:
raise ValueError("Scene should contain exactly one base mesh object.")
base_obj = base_objects[0]
# 获取顶点数据
vertices = [(i, v.co.z) for i, v in enumerate(base_obj.data.vertices)]
# 根据 Z 轴坐标排序
sorted_vertices = sorted(vertices, key=lambda x: x[1]) # 按 Z 坐标从小到大排序
sorted_vertex_indices = [idx for idx, z in sorted_vertices]
# 输出顶点顺序到文件
with open(output_vertex_order_file, "w") as f:
json.dump(sorted_vertex_indices, f, indent=4) # 保存为 JSON 数组
print(f"Exported vertex order to: {output_vertex_order_file}")
# 执行导出
bpy.ops.export_scene.gltf(filepath=output_path,
export_format='GLB',
export_skins=True,
export_texcoords=False, # 不导出 UV 数据
export_normals=False # 不导出法线数据
)
print(f"导出成功:{output_path}")
def main():
base_model_path = "runtime_data/nature.obj" # 基础模型路径
expression_dir = "runtime_data/bs" # 表情文件夹
bone_tree_path = "runtime_data/bone_tree.json" # 骨骼结构配置
weight_data_path = "runtime_data/lbs_weight_20k.json" # 权重数据
output_glb_path = "runtime_data/skin.glb"
output_vertex_order_file = "runtime_data/vertex_order.json" # 输出顶点顺序文件
# 清空场景
bpy.ops.wm.read_homefile(use_empty=True)
# 导入基础模型
import_obj(base_model_path)
base_obj = bpy.context.view_layer.objects.active
# 创建骨骼系统
armature = create_armature_from_bone_tree(base_obj, bone_tree_path)
# 设置骨骼姿势(如果需要)
# layout_bones_pose(armature, "pose_config.json")
# 应用顶点权重
apply_vertex_weights(base_obj, weight_data_path)
# 加载所有Shape Keys(表情)
expression_files = [
os.path.join(expression_dir, f)
for f in os.listdir(expression_dir)
if f.endswith(('.obj', '.OBJ'))
]
add_shape_keys(base_obj, expression_files)
apply_rotation(base_obj)
# 导出为GLB格式
export_as_glb(base_obj, output_glb_path, output_vertex_order_file)
if __name__ == "__main__":
main()
|