1249 lines
40 KiB
Python
1249 lines
40 KiB
Python
#!/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) |