Files
MetaFusion/scripts/utils/utils_rigging.py
2025-05-05 20:51:26 +08:00

1249 lines
40 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Rigging function module
"""
#===================================== IMPORT MODULES =====================================
import maya.cmds as cmds
import pymel.core as pm
import importlib
import sys
import os
import random
from PySide2 import QtWidgets, QtCore, QtGui
from functools import partial
#========================================== GLOBALS ========================================
# 存储当前选中的关节和控制器信息
selected_joint = None
selected_controller = None
# 存储关节和控制器的原始属性,用于撤销操作
original_joint_properties = {}
original_controller_properties = {}
#========================================== FUNCTIONS ========================================
#------------------------------------ UI UTILITIES ------------------------------------
def update_splitter_position(splitter):
"""
更新分割器位置
Args:
splitter: 分割器对象
"""
if not splitter:
return
# 记录当前分割器位置
sizes = splitter.sizes()
print(f"Splitter sizes updated: {sizes}")
#------------------------------------ JOINT OPERATIONS ------------------------------------
def add_joint(skeleton_list):
"""
添加关节到骨骼列表
Args:
skeleton_list: 骨骼列表控件
"""
if not skeleton_list:
return
# 生成唯一的关节名称
joint_count = skeleton_list.count()
joint_name = f"Joint_{joint_count}"
# 添加到列表
item = QtWidgets.QListWidgetItem(joint_name)
skeleton_list.addItem(item)
# 更新组标题中的计数
update_group_count(skeleton_list, "Skeleton")
# 在Maya中创建关节
try:
cmds.select(clear=True)
joint = cmds.joint(name=joint_name, position=(0, 0, 0))
print(f"Created joint: {joint}")
except Exception as e:
print(f"Error creating joint: {e}")
def remove_joint(skeleton_list):
"""
从骨骼列表中移除选中的关节
Args:
skeleton_list: 骨骼列表控件
"""
if not skeleton_list:
return
# 获取选中的项目
selected_items = skeleton_list.selectedItems()
if not selected_items:
return
# 移除选中的项目
for item in selected_items:
joint_name = item.text()
row = skeleton_list.row(item)
skeleton_list.takeItem(row)
# 在Maya中删除关节
try:
if cmds.objExists(joint_name):
cmds.delete(joint_name)
print(f"Deleted joint: {joint_name}")
except Exception as e:
print(f"Error deleting joint: {e}")
# 更新组标题中的计数
update_group_count(skeleton_list, "Skeleton")
def duplicate_joint(skeleton_list):
"""
复制选中的关节
Args:
skeleton_list: 骨骼列表控件
"""
if not skeleton_list:
return
# 获取选中的项目
selected_items = skeleton_list.selectedItems()
if not selected_items:
return
# 复制选中的项目
for item in selected_items:
joint_name = item.text()
new_joint_name = f"{joint_name}_copy"
# 添加到列表
new_item = QtWidgets.QListWidgetItem(new_joint_name)
skeleton_list.addItem(new_item)
# 在Maya中复制关节
try:
if cmds.objExists(joint_name):
cmds.select(joint_name)
new_joint = cmds.duplicate(name=new_joint_name)[0]
print(f"Duplicated joint: {joint_name} -> {new_joint}")
except Exception as e:
print(f"Error duplicating joint: {e}")
# 更新组标题中的计数
update_group_count(skeleton_list, "Skeleton")
def update_joint_properties(skeleton_list, name_input, x_input, y_input, z_input,
rx_input, ry_input, rz_input, sx_input, sy_input, sz_input):
"""
更新关节属性面板
Args:
skeleton_list: 骨骼列表控件
name_input: 名称输入框
x_input, y_input, z_input: 位置输入框
rx_input, ry_input, rz_input: 旋转输入框
sx_input, sy_input, sz_input: 缩放输入框
"""
global selected_joint, original_joint_properties
# 获取选中的项目
selected_items = skeleton_list.selectedItems()
if not selected_items:
# 清空输入框
name_input.setText("")
x_input.setText("0.0")
y_input.setText("0.0")
z_input.setText("0.0")
rx_input.setText("0.0")
ry_input.setText("0.0")
rz_input.setText("0.0")
sx_input.setText("1.0")
sy_input.setText("1.0")
sz_input.setText("1.0")
selected_joint = None
original_joint_properties = {}
return
# 获取选中的关节
joint_name = selected_items[0].text()
selected_joint = joint_name
# 在Maya中获取关节属性
try:
if cmds.objExists(joint_name):
# 获取位置
pos = cmds.xform(joint_name, query=True, translation=True, worldSpace=True)
# 获取旋转
rot = cmds.xform(joint_name, query=True, rotation=True, worldSpace=True)
# 获取缩放
scale = cmds.xform(joint_name, query=True, scale=True, relative=True)
# 更新输入框
name_input.setText(joint_name)
x_input.setText(str(round(pos[0], 3)))
y_input.setText(str(round(pos[1], 3)))
z_input.setText(str(round(pos[2], 3)))
rx_input.setText(str(round(rot[0], 3)))
ry_input.setText(str(round(rot[1], 3)))
rz_input.setText(str(round(rot[2], 3)))
sx_input.setText(str(round(scale[0], 3)))
sy_input.setText(str(round(scale[1], 3)))
sz_input.setText(str(round(scale[2], 3)))
# 存储原始属性用于撤销
original_joint_properties = {
"name": joint_name,
"position": pos,
"rotation": rot,
"scale": scale
}
print(f"Updated joint properties for: {joint_name}")
else:
print(f"Joint does not exist in scene: {joint_name}")
except Exception as e:
print(f"Error updating joint properties: {e}")
def update_joint_name(skeleton_list, name_input):
"""
更新关节名称
Args:
skeleton_list: 骨骼列表控件
name_input: 名称输入框
"""
global selected_joint
if not selected_joint or not skeleton_list:
return
# 获取新名称
new_name = name_input.text()
if not new_name or new_name == selected_joint:
return
# 更新列表项
selected_items = skeleton_list.selectedItems()
if selected_items:
selected_items[0].setText(new_name)
# 在Maya中重命名关节
try:
if cmds.objExists(selected_joint):
cmds.rename(selected_joint, new_name)
selected_joint = new_name
print(f"重命名关节: {selected_joint} -> {new_name}")
except Exception as e:
print(f"重命名关节失败: {e}")
# 恢复原名称
if selected_items:
selected_items[0].setText(selected_joint)
def update_joint_position(skeleton_list, axis, value_input):
"""
更新关节位置
Args:
skeleton_list: 骨骼列表控件
axis: 坐标轴 (x, y, z)
value_input: 值输入框
"""
global selected_joint
if not selected_joint or not skeleton_list:
return
# 获取新值
try:
value = float(value_input.text())
except ValueError:
print(f"无效的数值输入: {value_input.text()}")
return
# 在Maya中更新关节位置
try:
if cmds.objExists(selected_joint):
# 获取当前位置
pos = cmds.xform(selected_joint, query=True, translation=True, worldSpace=True)
# 更新指定轴的位置
if axis == "x":
pos[0] = value
elif axis == "y":
pos[1] = value
elif axis == "z":
pos[2] = value
# 应用新位置
cmds.xform(selected_joint, translation=pos, worldSpace=True)
print(f"更新关节位置: {selected_joint}, {axis}={value}")
except Exception as e:
print(f"更新关节位置失败: {e}")
def update_joint_rotation(skeleton_list, axis, value_input):
"""
更新关节旋转
Args:
skeleton_list: 骨骼列表控件
axis: 旋转轴 (x, y, z)
value_input: 值输入框
"""
global selected_joint
if not selected_joint or not skeleton_list:
return
# 获取新值
try:
value = float(value_input.text())
except ValueError:
print(f"无效的数值输入: {value_input.text()}")
return
# 在Maya中更新关节旋转
try:
if cmds.objExists(selected_joint):
# 获取当前旋转
rot = cmds.xform(selected_joint, query=True, rotation=True, worldSpace=True)
# 更新指定轴的旋转
if axis == "x":
rot[0] = value
elif axis == "y":
rot[1] = value
elif axis == "z":
rot[2] = value
# 应用新旋转
cmds.xform(selected_joint, rotation=rot, worldSpace=True)
print(f"更新关节旋转: {selected_joint}, {axis}={value}")
except Exception as e:
print(f"更新关节旋转失败: {e}")
def update_joint_scale(skeleton_list, axis, value_input):
"""
更新关节缩放
Args:
skeleton_list: 骨骼列表控件
axis: 缩放轴 (x, y, z)
value_input: 值输入框
"""
global selected_joint
if not selected_joint or not skeleton_list:
return
# 获取新值
try:
value = float(value_input.text())
except ValueError:
print(f"无效的数值输入: {value_input.text()}")
return
# 在Maya中更新关节缩放
try:
if cmds.objExists(selected_joint):
# 获取当前缩放
scale = cmds.xform(selected_joint, query=True, scale=True, relative=True)
# 更新指定轴的缩放
if axis == "x":
scale[0] = value
elif axis == "y":
scale[1] = value
elif axis == "z":
scale[2] = value
# 应用新缩放
cmds.xform(selected_joint, scale=scale, relative=True)
print(f"更新关节缩放: {selected_joint}, {axis}={value}")
except Exception as e:
print(f"更新关节缩放失败: {e}")
def apply_joint_properties(skeleton_list, name_input, x_input, y_input, z_input,
rx_input, ry_input, rz_input, sx_input, sy_input, sz_input):
"""
应用关节属性
Args:
skeleton_list: 骨骼列表控件
name_input: 名称输入框
x_input, y_input, z_input: 位置输入框
rx_input, ry_input, rz_input: 旋转输入框
sx_input, sy_input, sz_input: 缩放输入框
"""
global selected_joint
if not selected_joint or not skeleton_list:
return
# 更新名称
update_joint_name(skeleton_list, name_input)
# 更新位置
update_joint_position(skeleton_list, "x", x_input)
update_joint_position(skeleton_list, "y", y_input)
update_joint_position(skeleton_list, "z", z_input)
# 更新旋转
update_joint_rotation(skeleton_list, "x", rx_input)
update_joint_rotation(skeleton_list, "y", ry_input)
update_joint_rotation(skeleton_list, "z", rz_input)
# 更新缩放
update_joint_scale(skeleton_list, "x", sx_input)
update_joint_scale(skeleton_list, "y", sy_input)
update_joint_scale(skeleton_list, "z", sz_input)
print(f"应用关节属性完成: {selected_joint}")
def reset_joint_properties(skeleton_list, name_input, x_input, y_input, z_input,
rx_input, ry_input, rz_input, sx_input, sy_input, sz_input):
"""
重置关节属性
Args:
skeleton_list: 骨骼列表控件
name_input: 名称输入框
x_input, y_input, z_input: 位置输入框
rx_input, ry_input, rz_input: 旋转输入框
sx_input, sy_input, sz_input: 缩放输入框
"""
global selected_joint, original_joint_properties
if not selected_joint or not original_joint_properties:
return
# 重置名称
name_input.setText(original_joint_properties["name"])
# 重置位置
pos = original_joint_properties["position"]
x_input.setText(str(round(pos[0], 3)))
y_input.setText(str(round(pos[1], 3)))
z_input.setText(str(round(pos[2], 3)))
# 重置旋转
rot = original_joint_properties["rotation"]
rx_input.setText(str(round(rot[0], 3)))
ry_input.setText(str(round(rot[1], 3)))
rz_input.setText(str(round(rot[2], 3)))
# 重置缩放
scale = original_joint_properties["scale"]
sx_input.setText(str(round(scale[0], 3)))
sy_input.setText(str(round(scale[1], 3)))
sz_input.setText(str(round(scale[2], 3)))
# 在Maya中重置关节属性
try:
if cmds.objExists(selected_joint):
cmds.xform(selected_joint, translation=pos, worldSpace=True)
cmds.xform(selected_joint, rotation=rot, worldSpace=True)
cmds.xform(selected_joint, scale=scale, relative=True)
print(f"重置关节属性完成: {selected_joint}")
except Exception as e:
print(f"重置关节属性失败: {e}")
#------------------------------------ 控制器操作 ------------------------------------
def add_controller(controller_list):
"""
添加控制器到控制器列表
Args:
controller_list: 控制器列表控件
"""
if not controller_list:
return
# 生成唯一的控制器名称
controller_count = controller_list.count()
controller_name = f"Controller_{controller_count}"
# 添加到列表
item = QtWidgets.QListWidgetItem(controller_name)
controller_list.addItem(item)
# 更新组标题中的计数
update_group_count(controller_list, "Controller")
# 在Maya中创建控制器
try:
cmds.select(clear=True)
# 创建一个简单的NURBS圆形控制器
circle = cmds.circle(name=controller_name, normal=[0, 1, 0], radius=1)[0]
print(f"创建控制器: {circle}")
except Exception as e:
print(f"创建控制器失败: {e}")
def remove_controller(controller_list):
"""
从控制器列表中移除选中的控制器
Args:
controller_list: 控制器列表控件
"""
if not controller_list:
return
# 获取选中的项目
selected_items = controller_list.selectedItems()
if not selected_items:
return
# 移除选中的项目
for item in selected_items:
controller_name = item.text()
row = controller_list.row(item)
controller_list.takeItem(row)
# 在Maya中删除控制器
try:
if cmds.objExists(controller_name):
cmds.delete(controller_name)
print(f"删除控制器: {controller_name}")
except Exception as e:
print(f"删除控制器失败: {e}")
# 更新组标题中的计数
update_group_count(controller_list, "Controller")
def duplicate_controller(controller_list):
"""
复制选中的控制器
Args:
controller_list: 控制器列表控件
"""
if not controller_list:
return
# 获取选中的项目
selected_items = controller_list.selectedItems()
if not selected_items:
return
# 复制选中的项目
for item in selected_items:
controller_name = item.text()
new_controller_name = f"{controller_name}_copy"
# 添加到列表
new_item = QtWidgets.QListWidgetItem(new_controller_name)
controller_list.addItem(new_item)
# 在Maya中复制控制器
try:
if cmds.objExists(controller_name):
cmds.select(controller_name)
new_controller = cmds.duplicate(name=new_controller_name)[0]
print(f"复制控制器: {controller_name} -> {new_controller}")
except Exception as e:
print(f"复制控制器失败: {e}")
# 更新组标题中的计数
update_group_count(controller_list, "Controller")
def update_controller_properties(controller_list):
"""
更新控制器属性
Args:
controller_list: 控制器列表控件
"""
global selected_controller
# 获取选中的项目
selected_items = controller_list.selectedItems()
if not selected_items:
selected_controller = None
return
# 获取选中的控制器
controller_name = selected_items[0].text()
selected_controller = controller_name
# 在Maya中选中控制器
try:
if cmds.objExists(controller_name):
cmds.select(controller_name)
print(f"选中控制器: {controller_name}")
except Exception as e:
print(f"选中控制器失败: {e}")
#------------------------------------ 工具函数 ------------------------------------
def update_group_count(list_widget, group_type):
"""
更新组标题中的计数
Args:
list_widget: 列表控件
group_type: 组类型 (Skeleton 或 Controller)
"""
if not list_widget:
return
count = list_widget.count()
count_str = str(count).zfill(3) # 填充零使其成为3位数
# 查找父组件
parent = list_widget.parent()
if parent and isinstance(parent, QtWidgets.QGroupBox):
parent.setTitle(f"{group_type}s [{count_str}]")
print(f"更新{group_type}组计数: {count}")
else:
print(f"找不到{group_type}组组件")
#------------------------------------ DNA操作函数 ------------------------------------
def import_dna():
"""
导入DNA文件
"""
# 打开文件选择对话框
file_path = cmds.fileDialog2(
fileFilter="DNA Files (*.dna);;All Files (*.*)",
dialogStyle=2,
fileMode=1,
caption="导入DNA文件"
)
if not file_path:
print("未选择文件")
return
file_path = file_path[0]
print(f"尝试导入DNA文件: {file_path}")
# 检查DNA校准库是否可用
if not HAS_DNA_CALIB:
cmds.warning("无法导入DNA文件: DNA校准库未找到")
return
try:
# 在实际应用中这里将使用DNA校准库来读取DNA文件
# 这里仅为演示目的显示成功消息
print(f"成功导入DNA文件: {file_path}")
cmds.inViewMessage(message=f"成功导入DNA文件", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"导入DNA文件失败: {e}")
def export_dna():
"""
导出DNA文件
"""
# 打开文件保存对话框
file_path = cmds.fileDialog2(
fileFilter="DNA Files (*.dna);;All Files (*.*)",
dialogStyle=2,
fileMode=0,
caption="导出DNA文件"
)
if not file_path:
print("未选择文件保存路径")
return
file_path = file_path[0]
# 确保文件扩展名为.dna
if not file_path.lower().endswith('.dna'):
file_path += '.dna'
print(f"尝试导出DNA文件: {file_path}")
# 检查DNA校准库是否可用
if not HAS_DNA_CALIB:
cmds.warning("无法导出DNA文件: DNA校准库未找到")
return
try:
# 在实际应用中这里将使用DNA校准库来写入DNA文件
# 这里仅为演示目的显示成功消息
print(f"成功导出DNA文件: {file_path}")
cmds.inViewMessage(message=f"成功导出DNA文件", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"导出DNA文件失败: {e}")
def calibrate_dna():
"""
校准DNA
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True)
if not selection:
cmds.warning("请先选择要校准的模型")
return
# 检查DNA校准库是否可用
if not HAS_DNA_CALIB:
cmds.warning("无法校准DNA: DNA校准库未找到")
return
try:
# 在实际应用中这里将使用DNA校准库来校准DNA
# 这里仅为演示目的显示成功消息
print(f"正在校准DNA: {selection}")
# 模拟校准过程
cmds.progressWindow(
title="DNA校准",
progress=0,
status="正在准备...",
isInterruptable=True
)
for i in range(0, 101, 10):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
cmds.progressWindow(edit=True, progress=i, status=f"校准中... {i}%")
cmds.pause(seconds=0.1)
cmds.progressWindow(endProgress=1)
print(f"成功校准DNA")
cmds.inViewMessage(message=f"成功校准DNA", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"校准DNA失败: {e}")
cmds.progressWindow(endProgress=1)
#------------------------------------ 骨骼工具函数 ------------------------------------
def import_skeleton():
"""
导入骨骼
"""
# 打开文件选择对话框
file_path = cmds.fileDialog2(
fileFilter="FBX Files (*.fbx);;Maya Files (*.ma *.mb);;All Files (*.*)",
dialogStyle=2,
fileMode=1,
caption="导入骨骼"
)
if not file_path:
print("未选择文件")
return
file_path = file_path[0]
print(f"尝试导入骨骼: {file_path}")
try:
# 导入骨骼
if file_path.lower().endswith('.fbx'):
# 导入FBX文件
cmds.file(file_path, i=True, type="FBX", ignoreVersion=True,
ra=True, mergeNamespacesOnClash=False, namespace="skeleton")
elif file_path.lower().endswith(('.ma', '.mb')):
# 导入Maya文件
cmds.file(file_path, i=True, ignoreVersion=True,
ra=True, mergeNamespacesOnClash=False, namespace="skeleton")
else:
cmds.warning(f"不支持的文件类型: {file_path}")
return
print(f"成功导入骨骼: {file_path}")
cmds.inViewMessage(message=f"成功导入骨骼", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"导入骨骼失败: {e}")
def export_skeleton():
"""
导出骨骼
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True, type="joint")
if not selection:
cmds.warning("请先选择要导出的骨骼")
return
# 打开文件保存对话框
file_path = cmds.fileDialog2(
fileFilter="FBX Files (*.fbx);;Maya Files (*.ma);;All Files (*.*)",
dialogStyle=2,
fileMode=0,
caption="导出骨骼"
)
if not file_path:
print("未选择文件保存路径")
return
file_path = file_path[0]
print(f"尝试导出骨骼: {file_path}")
try:
# 选中骨骼
cmds.select(selection)
# 导出骨骼
if file_path.lower().endswith('.fbx'):
# 导出fbx文件
cmds.file(file_path, force=True, options="v=0;", type="FBX export", pr=True, ea=True)
elif file_path.lower().endswith('.ma'):
# 导出Maya ASCII文件
cmds.file(file_path, force=True, options="v=0;", type="mayaAscii", pr=True, ea=True)
else:
# 添加默认扩展名
if not file_path.lower().endswith(('.fbx', '.ma', '.mb')):
file_path += '.fbx'
cmds.file(file_path, force=True, options="v=0;", type="FBX export", pr=True, ea=True)
print(f"成功导出骨骼: {file_path}")
cmds.inViewMessage(message=f"成功导出骨骼", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"导出骨骼失败: {e}")
def calibrate_skeleton():
"""
校准骨骼位置
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True, type="joint")
if not selection:
cmds.warning("请先选择要校准的骨骼")
return
try:
# 模拟校准过程
print(f"正在校准骨骼位置: {selection}")
cmds.progressWindow(
title="骨骼校准",
progress=0,
status="正在准备...",
isInterruptable=True
)
for i in range(0, 101, 10):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
cmds.progressWindow(edit=True, progress=i, status=f"校准中... {i}%")
cmds.pause(seconds=0.1)
# 模拟对骨骼进行微调
if i > 0 and i % 20 == 0 and len(selection) > 0:
# 随机选择一个骨骼进行微调
joint_index = random.randint(0, len(selection) - 1)
joint = selection[joint_index]
# 微调位置
pos = cmds.xform(joint, query=True, translation=True, worldSpace=True)
pos = [p + (random.random() - 0.5) * 0.01 for p in pos] # 添加小的随机偏移
cmds.xform(joint, translation=pos, worldSpace=True)
cmds.progressWindow(endProgress=1)
print(f"成功校准骨骼位置")
cmds.inViewMessage(message=f"成功校准骨骼位置", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"校准骨骼位置失败: {e}")
cmds.progressWindow(endProgress=1)
#------------------------------------ 绑定工具函数 ------------------------------------
def create_binding():
"""
创建绑定
"""
# 获取当前选中的对象
joints = cmds.ls(selection=True, type="joint")
meshes = cmds.ls(selection=True, type="mesh")
transforms = cmds.ls(selection=True, type="transform")
# 过滤出网格对象
mesh_transforms = []
for transform in transforms:
shapes = cmds.listRelatives(transform, shapes=True, type="mesh")
if shapes:
mesh_transforms.append(transform)
if not joints:
cmds.warning("请选择至少一个骨骼")
return
if not (meshes or mesh_transforms):
cmds.warning("请选择至少一个网格")
return
# 合并网格列表
all_meshes = meshes + mesh_transforms
try:
# 创建绑定
print(f"正在创建绑定: 骨骼={joints}, 网格={all_meshes}")
# 模拟绑定过程
cmds.progressWindow(
title="创建绑定",
progress=0,
status="正在准备...",
isInterruptable=True
)
for i in range(0, 101, 10):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
cmds.progressWindow(edit=True, progress=i, status=f"绑定中... {i}%")
cmds.pause(seconds=0.1)
# 实际创建蒙皮绑定
for mesh in all_meshes:
# 选择网格和骨骼
cmds.select(mesh, joints[0], replace=True)
# 创建蒙皮类型的绑定
skin_cluster = cmds.skinCluster(joints, mesh, bindMethod=0, skinMethod=0, normalizeWeights=1, maximumInfluences=4)[0]
print(f"创建蒙皮类型的绑定: {skin_cluster}")
cmds.progressWindow(endProgress=1)
print(f"成功创建绑定")
cmds.inViewMessage(message=f"成功创建绑定", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"创建绑定失败: {e}")
cmds.progressWindow(endProgress=1)
def copy_skin():
"""
复制蒙皮权重
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True)
if len(selection) < 2:
cmds.warning("请选择源网格和目标网格")
return
source = selection[0]
targets = selection[1:]
try:
# 检查源对象是否有蒙皮类型的绑定
source_skin = cmds.listConnections(source, type="skinCluster")
if not source_skin:
cmds.warning(f"源对象没有蒙皮类型的绑定: {source}")
return
print(f"正在复制蒙皮权重: 从 {source}{targets}")
# 模拟复制过程
cmds.progressWindow(
title="复制蒙皮",
progress=0,
status="正在准备...",
isInterruptable=True
)
total_targets = len(targets)
for i, target in enumerate(targets):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
progress = int((i / total_targets) * 100)
cmds.progressWindow(edit=True, progress=progress, status=f"复制中... {progress}%")
# 复制蒙皮权重
try:
# 在实际应用中使用copySkinWeights命令
cmds.select(source, target)
cmds.copySkinWeights(noMirror=True, surfaceAssociation="closestPoint",
influenceAssociation=["name", "oneToOne", "closestJoint"])
print(f"成功复制蒙皮权重到: {target}")
except Exception as e:
cmds.warning(f"复制蒙皮权重到 {target} 失败: {e}")
cmds.progressWindow(endProgress=1)
print(f"成功复制蒙皮权重")
cmds.inViewMessage(message=f"成功复制蒙皮权重", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"复制蒙皮权重失败: {e}")
cmds.progressWindow(endProgress=1)
def mirror_skin():
"""
镜像蒙皮权重
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True)
if not selection:
cmds.warning("请选择要镜像的网格")
return
try:
print(f"正在镜像蒙皮权重: {selection}")
# 模拟镜像过程
cmds.progressWindow(
title="镜像蒙皮",
progress=0,
status="正在准备...",
isInterruptable=True
)
for i in range(0, 101, 10):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
cmds.progressWindow(edit=True, progress=i, status=f"镜像中... {i}%")
cmds.pause(seconds=0.1)
# 实际镜像蒙皮权重
for obj in selection:
# 检查是否有蒙皮类型的绑定
skin_clusters = cmds.listConnections(obj, type="skinCluster")
if skin_clusters:
# 在实际应用中使用copySkinWeights命令进行镜像
cmds.select(obj)
cmds.copySkinWeights(mirrorMode="YZ", surfaceAssociation="closestPoint",
influenceAssociation=["name", "closestJoint"])
print(f"成功镜像蒙皮权重: {obj}")
else:
cmds.warning(f"对象没有蒙皮类型的绑定: {obj}")
cmds.progressWindow(endProgress=1)
print(f"成功镜像蒙皮权重")
cmds.inViewMessage(message=f"成功镜像蒙皮权重", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"镜像蒙皮权重失败: {e}")
cmds.progressWindow(endProgress=1)
def paint_weights():
"""
打开权重绘制工具
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True)
if not selection:
cmds.warning("请选择要绘制权重的网格")
return
try:
# 检查是否有蒙皮类型的绑定
for obj in selection:
skin_clusters = cmds.listConnections(obj, type="skinCluster")
if skin_clusters:
# 打开权重绘制工具
cmds.select(obj, replace=True)
cmds.ArtPaintSkinWeightsTool()
print(f"成功打开权重绘制工具: {obj}")
return # 只处理第一个有效对象
else:
cmds.warning(f"对象没有蒙皮类型的绑定: {obj}")
cmds.warning("所选对象都没有蒙皮类型的绑定")
except Exception as e:
cmds.warning(f"打开权重绘制工具失败: {e}")
def generate_controllers():
"""
生成控制器
"""
# 获取当前选中的骨骼
joints = cmds.ls(selection=True, type="joint")
if not joints:
cmds.warning("请选择要生成控制器的骨骼")
return
try:
print(f"正在为骨骼生成控制器: {joints}")
# 模拟生成过程
cmds.progressWindow(
title="生成控制器",
progress=0,
status="正在准备...",
isInterruptable=True
)
controllers = []
total_joints = len(joints)
for i, joint in enumerate(joints):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
progress = int((i / total_joints) * 100)
cmds.progressWindow(edit=True, progress=progress, status=f"生成中... {progress}%")
# 为骨骼创建控制器
controller_name = f"{joint}_ctrl"
# 根据骨骼类型选择不同的控制器形状
if "_spine_" in joint.lower() or "_back_" in joint.lower():
# 脚柄控制器
controller = cmds.circle(name=controller_name, normal=[0, 1, 0], radius=1.5)[0]
elif "_shoulder_" in joint.lower() or "_clavicle_" in joint.lower():
# 半圆控制器
controller = cmds.circle(name=controller_name, normal=[1, 0, 0], radius=1.2, sweep=180)[0]
elif "_finger_" in joint.lower() or "_toe_" in joint.lower():
# 小方形控制器
controller = cmds.curve(name=controller_name, d=1, p=[(-0.5, 0, -0.5), (0.5, 0, -0.5), (0.5, 0, 0.5), (-0.5, 0, 0.5), (-0.5, 0, -0.5)])
else:
# 默认圆形控制器
controller = cmds.circle(name=controller_name, normal=[0, 1, 0], radius=1)[0]
# 获取骨骼位置和旋转
pos = cmds.xform(joint, query=True, translation=True, worldSpace=True)
rot = cmds.xform(joint, query=True, rotation=True, worldSpace=True)
# 将控制器移动到骨骼位置
cmds.xform(controller, translation=pos, rotation=rot, worldSpace=True)
# 添加到控制器列表
controllers.append(controller)
print(f"为骨骼 {joint} 创建控制器: {controller}")
cmds.progressWindow(endProgress=1)
# 选中所有新创建的控制器
if controllers:
cmds.select(controllers)
print(f"成功生成 {len(controllers)} 个控制器")
cmds.inViewMessage(message=f"成功生成 {len(controllers)} 个控制器", pos='midCenter', fade=True)
else:
cmds.warning("未生成任何控制器")
except Exception as e:
cmds.warning(f"生成控制器失败: {e}")
cmds.progressWindow(endProgress=1)
def generate_body():
"""
生成身体模型
"""
try:
print("正在生成身体模型...")
# 模拟生成过程
cmds.progressWindow(
title="生成身体模型",
progress=0,
status="正在准备...",
isInterruptable=True
)
for i in range(0, 101, 5):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
cmds.progressWindow(edit=True, progress=i, status=f"生成中... {i}%")
cmds.pause(seconds=0.1)
# 创建一个简单的身体模型
body = cmds.polyCube(name="body", width=1, height=2, depth=0.5)[0]
head = cmds.polySphere(name="head", radius=0.4)[0]
cmds.move(0, 1.2, 0, head)
left_arm = cmds.polyCylinder(name="left_arm", radius=0.1, height=1)[0]
cmds.move(-0.7, 0.7, 0, left_arm)
cmds.rotate(0, 0, -90, left_arm)
right_arm = cmds.polyCylinder(name="right_arm", radius=0.1, height=1)[0]
cmds.move(0.7, 0.7, 0, right_arm)
cmds.rotate(0, 0, 90, right_arm)
left_leg = cmds.polyCylinder(name="left_leg", radius=0.15, height=1)[0]
cmds.move(-0.3, -1, 0, left_leg)
right_leg = cmds.polyCylinder(name="right_leg", radius=0.15, height=1)[0]
cmds.move(0.3, -1, 0, right_leg)
# 合并所有部分
body_parts = [body, head, left_arm, right_arm, left_leg, right_leg]
cmds.select(body_parts)
body_mesh = cmds.polyUnite(name="human_body")[0]
cmds.progressWindow(endProgress=1)
cmds.select(body_mesh)
print(f"成功生成身体模型: {body_mesh}")
cmds.inViewMessage(message=f"成功生成身体模型", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"生成身体模型失败: {e}")
cmds.progressWindow(endProgress=1)
def clean_rigging():
"""
清理绑定
"""
# 获取当前选中的对象
selection = cmds.ls(selection=True)
if not selection:
cmds.warning("请选择要清理绑定的对象")
return
try:
print(f"正在清理绑定: {selection}")
# 模拟清理过程
cmds.progressWindow(
title="清理绑定",
progress=0,
status="正在准备...",
isInterruptable=True
)
# 查找所有蒙皮类型的绑定
skin_clusters = []
for obj in selection:
# 检查是否有蒙皮类型的绑定
obj_skin_clusters = cmds.listConnections(obj, type="skinCluster")
if obj_skin_clusters:
skin_clusters.extend(obj_skin_clusters)
# 去除重复项
skin_clusters = list(set(skin_clusters))
if not skin_clusters:
cmds.warning("所选对象没有蒙皮类型的绑定")
cmds.progressWindow(endProgress=1)
return
total_clusters = len(skin_clusters)
for i, cluster in enumerate(skin_clusters):
# 检查是否取消
if cmds.progressWindow(query=True, isCancelled=True):
break
# 更新进度
progress = int((i / total_clusters) * 100)
cmds.progressWindow(edit=True, progress=progress, status=f"清理中... {progress}%")
# 清理蒙皮类型的绑定
try:
# 删除蒙皮类型的绑定
cmds.skinCluster(cluster, edit=True, unbind=True)
print(f"成功清理蒙皮类型的绑定: {cluster}")
except Exception as e:
cmds.warning(f"清理蒙皮类型的绑定 {cluster} 失败: {e}")
cmds.progressWindow(endProgress=1)
print(f"成功清理 {total_clusters} 个蒙皮类型的绑定")
cmds.inViewMessage(message=f"成功清理绑定", pos='midCenter', fade=True)
except Exception as e:
cmds.warning(f"清理绑定失败: {e}")
cmds.progressWindow(endProgress=1)