#!/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")