From 7f6860ab12b5d16172af8a065308cc1c9c843a87 Mon Sep 17 00:00:00 2001 From: Jeffreytsai1004 Date: Thu, 16 Jan 2025 00:28:37 +0800 Subject: [PATCH] Update --- scripts/BuildBody.py | 396 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 scripts/BuildBody.py diff --git a/scripts/BuildBody.py b/scripts/BuildBody.py new file mode 100644 index 0000000..28e466a --- /dev/null +++ b/scripts/BuildBody.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import maya.cmds as cmds +import sys + + +def body_transformation(gender_mesh): + + def add_chain_depth_attribute(joints): + for joint in joints: + if not cmds.attributeQuery('chainDepth', node=joint, exists=True): + cmds.addAttr(joint, longName='chainDepth', attributeType='long', defaultValue=0) + cmds.setAttr(joint + '.chainDepth', keyable=True) + + def set_chain_depth_value(joints, value): + for joint in joints: + cmds.setAttr(joint + '.chainDepth', value) + + # 获取场景中的所有关节 + all_joints = cmds.ls(type="joint") + + # 移除"DHIhead:spine_04"层次中的关节(避免头部) + exclude_joints = cmds.ls("DHIhead:spine_04", dag=True, type="joint") + all_joints = list(set(all_joints) - set(exclude_joints)) + add_chain_depth_attribute(all_joints) + + # 过滤末端关节(没有子关节的关节) + end_joints = [joint for joint in all_joints if not cmds.listRelatives(joint, children=True, type='joint')] + + + # 将chainDepth属性设置为0,所有末端关节 + set_chain_depth_value(all_joints, 100) + set_chain_depth_value(end_joints, 0) + + parents1 = [] + + for joint_name in end_joints: + p_joint = cmds.listRelatives(joint_name, parent=True, type="joint") + if p_joint: + children = cmds.listRelatives(p_joint, children=True, type="joint") or [] + if all(cmds.getAttr(child + ".chainDepth") == 0 for child in children): + + parents1.append(p_joint[0]) + + set_chain_depth_value(parents1, 1) + #链深度添加 Attr 循环 + chainDepth = 1 + while parents1: + chainDepth += 1 + new_parents = [] + for joint_name in parents1: + p_joint = cmds.listRelatives(joint_name, parent=True, type="joint") + if p_joint: + children = cmds.listRelatives(p_joint, children=True, type="joint") or [] + if all(cmds.getAttr(child + ".chainDepth") < chainDepth for child in children): + new_parents.append(p_joint[0]) + if new_parents: + set_chain_depth_value(new_parents, chainDepth) + parents1 = new_parents + else: + break + + j_name = [] + j_delta_x = [] + j_delta_y = [] + j_delta_z = [] + # DELTA X 计算 + for joint_name in end_joints: + mesh_name = gender_mesh + + # 1. 获取关节的世界空间位置 + joint_pos = cmds.xform(joint_name, query=True, worldSpace=True, translation=True) + + # 2. 找到最近的顶点"NVertex",位于关节位置 + closest_point_node = cmds.createNode("closestPointOnMesh") + cmds.connectAttr(mesh_name + ".worldMesh", closest_point_node + ".inMesh") + cmds.setAttr(closest_point_node + ".inPosition", joint_pos[0], joint_pos[1], joint_pos[2]) + + nearest_vertex_index = cmds.getAttr(closest_point_node + ".closestVertexIndex") + cmds.delete(closest_point_node) + vertex = mesh_name + ".vtx[{}]".format(nearest_vertex_index) + cmds.select(vertex, replace=True) + + # 增长选择 + + for i in range(1): + + # 获取当前选择顶点 + vertices = cmds.ls(selection=True, flatten=True) + + # 将顶点转换为边 + edges = [] + for vertex in vertices: + edges.extend(cmds.polyListComponentConversion(vertex, fromVertex=True, toEdge=True)) + + # 选择边 + cmds.select(edges) + + # 获取当前选择边 + edges = cmds.ls(selection=True, flatten=True) + + # 将边转换为顶点 + vertices = [] + for edge in edges: + vertices.extend(cmds.polyListComponentConversion(edge, fromEdge=True, toVertex=True)) + + # 选择顶点 + cmds.select(vertices) + + vertices = cmds.ls(selection=True, flatten=True) + # 将顶点添加到列表中 + vertex_position_before = [] + for vertex in vertices: + position = cmds.pointPosition(vertex, world=True) + vertex_position_before.append(position) + + + # 3 将目标混合形状节点设置为1 + cmds.setAttr("BodyShape.B2", 1) + + # 4. 获取NVertex的新位置并找到新位置 + vertex_position_after = [] + for vertex in vertices: + position = cmds.pointPosition(vertex, world=True) + vertex_position_after.append(position) + + delta_vertex = [] + + for i in range(len(vertex_position_after)): + sub_delta = [] + for j in range(3): + sub_delta.append(vertex_position_after[i][j] - vertex_position_before[i][j]) + delta_vertex.append(sub_delta) + + average_vertex = [sum(x) / len(x) for x in zip(*delta_vertex)] + + # 5. 将关节移动到NVertex移动的距离 + delta_x = average_vertex[0] + delta_y = average_vertex[1] + delta_z = average_vertex[2] + + if not cmds.attributeQuery("delta_x", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_x", at="double") + if not cmds.attributeQuery("delta_y", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_y", at="double") + if not cmds.attributeQuery("delta_z", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_z", at="double") + + cmds.setAttr(joint_name + ".delta_x", delta_x) + cmds.setAttr(joint_name + ".delta_y", delta_y) + cmds.setAttr(joint_name + ".delta_z", delta_z) + + # 6 重置 + cmds.setAttr("BodyShape.B2", 0) + j_name.append(joint_name) + j_delta_x.append(delta_x) + j_delta_y.append(delta_y) + j_delta_z.append(delta_z) + #cmds.move(delta_x, delta_y, delta_z, joint_name, relative=True) + for i in range(len(j_name)): + cmds.move(j_delta_x[i], j_delta_y[i], j_delta_z[i], j_name[i], relative=True) + + + + #修复身体部分 + moving_joint = [] + target_joint = [] + def fix_body(moving_joint, target_joint): + m_pos = cmds.xform(moving_joint, query=True, worldSpace=True, rotatePivot=True) + ta_pos = cmds.xform(target_joint, query=True, worldSpace=True, rotatePivot=True) + child_joints = cmds.listRelatives(moving_joint, c=True, type="joint") + child_positions = [] + if child_joints: + for child_joint in child_joints: + child_positions.append(cmds.xform(child_joint, q=True, ws=True, t=True)) + # 将"FACIAL_C_LowerLipRotation"关节的平移和旋转枢轴属性设置为与"FACIAL_C_MouthUpper"相同 + cmds.xform(moving_joint, translation=ta_pos, rotatePivot=m_pos, worldSpace=True) + if child_joints: + for i, child_joint in enumerate(child_joints): + cmds.xform(child_joint, ws=True, t=child_positions[i]) + m_pos = cmds.xform(moving_joint, query=True, worldSpace=True, rotatePivot=True) + ta_pos = cmds.xform(target_joint, query=True, worldSpace=True, rotatePivot=True) + cmds.xform(target_joint, translation=m_pos, rotatePivot=ta_pos, worldSpace=True) + + # 过滤chainDepth值为0的关节 + CD = 1 + lower_CD = 0 + maxCD = 15 + while CD <= maxCD: + joint_CD = [joint for joint in all_joints if cmds.getAttr(joint + ".chainDepth") == CD] + lower_joints = [joint for joint in all_joints if cmds.getAttr(joint + ".chainDepth") == lower_CD] + for joint_name in joint_CD: + + first_place = cmds.xform(joint_name, q=True, ws=True, translation=True) + child_joints = cmds.listRelatives(joint_name, children=True, type="joint") + average_point = [] + + if child_joints: + + target_points = [] + for child_joint in child_joints: + if child_joint in lower_joints: + delta_x = cmds.getAttr(child_joint + '.delta_x') + delta_y = cmds.getAttr(child_joint + '.delta_y') + delta_z = cmds.getAttr(child_joint + '.delta_z') + target_points.append([delta_x, delta_y, delta_z]) + if child_joint in end_joints: + delta_x = cmds.getAttr(child_joint + '.delta_x') + delta_y = cmds.getAttr(child_joint + '.delta_y') + delta_z = cmds.getAttr(child_joint + '.delta_z') + #print(cmds.getAttr(child_joint + ".chainDepth")) + target_points.append([delta_x, delta_y, delta_z]) + target_points.append([delta_x, delta_y, delta_z]) + target_points.append([delta_x, delta_y, delta_z]) + target_points.append([delta_x, delta_y, delta_z]) + target_points.append([delta_x, delta_y, delta_z]) + average_point = [sum(x) / len(x) for x in zip(*target_points)] + if not average_point: + average_point = [0, 0, 0] + avg_x = average_point[0] + avg_y = average_point[1] + avg_z = average_point[2] + cmds.move(avg_x, avg_y, avg_z, joint_name, relative=True) + for child_joint in child_joints: + cmds.move(-avg_x, -avg_y, -avg_z, child_joint, relative=True) + pureJoint = joint_name[:-2] + last_letter = joint_name[-2:] + joint_name_corrective = pureJoint+"_correctiveRoot"+last_letter + joint_name_half = pureJoint+"_half"+last_letter + if joint_name_corrective in all_joints: + fix_body(joint_name, joint_name_corrective) + #print(joint_name + " fixed to the " + joint_name_corrective) + if joint_name_half in all_joints: + fix_body(joint_name, joint_name_half) + #print(joint_name + " fixed to the " + joint_name_half) + end_place = cmds.xform(joint_name, q=True, ws=True, translation=True) + delta_x = end_place[0] - first_place[0] + delta_y = end_place[1] - first_place[1] + delta_z = end_place[2] - first_place[2] + if not cmds.attributeQuery("delta_x", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_x", at="double") + if not cmds.attributeQuery("delta_y", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_y", at="double") + if not cmds.attributeQuery("delta_z", node=joint_name, exists=True): + cmds.addAttr(joint_name, ln="delta_z", at="double") + + cmds.setAttr(joint_name + ".delta_x", delta_x) + cmds.setAttr(joint_name + ".delta_y", delta_y) + cmds.setAttr(joint_name + ".delta_z", delta_z) + + lower_CD += 1 + CD += 1 + + # + # + # 将身体原始骨骼转换为新头部骨骼 + CD = 15 + lower_CD = -1 + minCD = 0 + + # 定义关节名称和命名空间 + head_joint = "DHIhead:spine_04" + body_joint = "DHIbody:spine_04" + + # 将关节名称和命名空间分开 + head_namespace, head_joint_name = head_joint.split(":") + body_namespace, body_joint_name = body_joint.split(":") + + # 获取头部关节的子关节列表 + head_child_joints = cmds.listRelatives(head_joint, children=True, allDescendents=True,type="joint") + body_child_joints = cmds.listRelatives(body_joint, children=True, allDescendents=True, type="joint") + + while CD >= minCD: + joint_CD = [joint for joint in body_child_joints if cmds.getAttr(joint + ".chainDepth") == CD] + for joint_name in joint_CD: + body_namespace, body_joint_name = joint_name.split(":") + # 构建当前关节的关节名称 + head_current_joint = "{}:{}".format(head_namespace, body_joint_name) + body_current_joint = "{}:{}".format(body_namespace, body_joint_name) + if cmds.objExists(head_current_joint): + moving_joint = body_current_joint + target_joint = head_current_joint + def move2target_joint(): + lower_lip_rotation_pos = cmds.xform(moving_joint, query=True, worldSpace=True, rotatePivot=True) + jaw_pos = cmds.xform(target_joint, query=True, worldSpace=True, rotatePivot=True) + # 获取子关节 + child_joints = cmds.listRelatives(moving_joint, c=True, type="joint") + # 存储子关节的初始位置 + child_positions = [] + if child_joints: + for child_joint in child_joints: + child_positions.append(cmds.xform(child_joint, q=True, ws=True, t=True)) + + # 将"FACIAL_C_LowerLipRotation"关节的平移和旋转枢轴属性设置为与"FACIAL_C_MouthUpper"相同 + cmds.xform(moving_joint, translation=jaw_pos, rotatePivot=lower_lip_rotation_pos, worldSpace=True) + if child_joints: + + # 将每个子关节移动回其原始位置 + for i, child_joint in enumerate(child_joints): + cmds.xform(child_joint, ws=True, t=child_positions[i]) + + move2target_joint() + + + lower_CD -= 1 + CD -= 1 + + fix_body("DHIbody:spine_04", "DHIhead:spine_04") + +def bind_skin(gender_mesh): + + # 身体 + mesh_obj = cmds.ls(gender_mesh)[0] + + # 复制网格 + duplicated_mesh_obj = cmds.duplicate(mesh_obj)[0] + new_name = gender_mesh + "_duplicate" + cmds.rename(duplicated_mesh_obj, new_name) + bind_obj = cmds.ls(new_name)[0] + + cmds.select([bind_obj, "DHIbody:root"]) + + # 绑定皮肤到网格 + skin_cluster = cmds.skinCluster("DHIbody:root", bind_obj)[0] + + # 将皮肤方法设置为"经典线性" + cmds.setAttr(skin_cluster + ".skinningMethod", 1) + + cmds.select([mesh_obj, bind_obj]) + + cmds.copySkinWeights(noMirror=True, surfaceAssociation="closestPoint", influenceAssociation=["name", "oneToOne"]) + + cmds.delete(mesh_obj) + cmds.rename(bind_obj, gender_mesh) +ROOT_DIR = "c:/dna_calibration" +gender=0 +gender_mesh = "f_tal_nrw_body_lod0_mesh" +skeleton_file_path = f"{ROOT_DIR}/data/Fem_Body_skeleton.fbx" +def build_body(ROOT_DIR): + #切换性别 + if gender == 0: + + skeleton_file_path = f"{ROOT_DIR}/data/Fem_Body_skeleton.fbx" + gender_mesh = "f_tal_nrw_body_lod0_mesh" + elif gender == 1: + + skeleton_file_path = f"{ROOT_DIR}/data/Ma_Body_skeleton.fbx" + gender_mesh = "m_med_nrw_body_lod0_mesh" + # 将选定的对象重命名为B2 + selected_object = cmds.ls(selection=True) + cmds.rename(selected_object, "B2") + + # 导入FBX文件 + cmds.file(skeleton_file_path, i=True, type="FBX", ignoreVersion=True, ra=True, mergeNamespacesOnClash=False, options="fbx") + + cmds.select("B2", replace=True) + + # 定义混合形状节点和目标形状名称 + blendshape_node = "BodyShape" + target_shape = "|body_rig|body_grp|body_geometry_grp|body_lod0_grp|"+gender_mesh+"|"+gender_mesh+"Shape" + cmds.blendShape(gender_mesh, automatic=True) + cmds.rename("blendShape1", "BodyShape") + cmds.blendShape(blendshape_node, edit=True, target=[(target_shape, 0.0, "B2", 1.0)]) + cmds.setAttr("BodyShape.B2", 0) + #cmds.setAttr("B2" + ".visibility", 0) + cmds.delete('B2') + + body_transformation(gender_mesh) + + # 复制头部网格 + body_mesh = gender_mesh + new_mesh = cmds.duplicate(body_mesh)[0] + + # 解锁平移属性 + cmds.setAttr(new_mesh+'.translateX', lock=False) + cmds.setAttr(new_mesh+'.translateY', lock=False) + cmds.setAttr(new_mesh+'.translateZ', lock=False) + + # 将复制网格沿X轴移动75个单位 + cmds.move(75, 0, 0, new_mesh, relative=True) + + # 将复制网格添加为目标混合形状节点 + blend_shape_node = "BodyShape" + cmds.blendShape(blend_shape_node, edit=True, target=(body_mesh, 1, new_mesh, 1.0)) + + # 将复制网格的目标混合形状权重设置为-1 + cmds.setAttr("BodyShape."+new_mesh, -1) + + cmds.setAttr("BodyShape.B2", 1) + + cmds.delete(gender_mesh+"1") + + bind_skin(gender_mesh) + +#build_body("c:/dna_calibration") \ No newline at end of file