#!/usr/bin/env python # -*- coding: utf-8 -*- """ Rigging function module 绑定系统功能模块 - 提供绑定系统相关的功能函数 """ #===================================== IMPORT MODULES ===================================== import maya.cmds as cmds import pymel.core as pm import maya.mel as mel from maya import OpenMayaUI as omui from scripts.ui.Qt import QtWidgets, QtCore, QtGui from scripts.ui.Qt.QtCompat import wrapInstance import webbrowser import subprocess import importlib import traceback import sys import os # 导入配置 import config TOOL_NAME = config.TOOL_NAME TOOL_VERSION = config.TOOL_VERSION TOOL_AUTHOR = config.TOOL_AUTHOR TOOL_YEAR = config.TOOL_YEAR TOOL_MOD_FILENAME = config.TOOL_MOD_FILENAME TOOL_LANG = config.TOOL_LANG TOOL_WSCL_NAME = config.TOOL_WSCL_NAME TOOL_HELP_URL = config.TOOL_HELP_URL TOOL_PATH = config.TOOL_PATH SCRIPTS_PATH = config.SCRIPTS_PATH TOOL_MAIN_SCRIPT = config.TOOL_MAIN_SCRIPT UI_PATH = config.UI_PATH STYLE_FILE = config.STYLE_FILE ICONS_PATH = config.ICONS_PATH TOOL_ICON = config.TOOL_ICON ASSETS_PATH = config.ASSETS_PATH DNA_FILE_PATH = config.DNA_FILE_PATH DNA_IMG_PATH = config.DNA_IMG_PATH TOOL_COMMAND_ICON = config.TOOL_COMMAND_ICON TOOL_WIDTH = config.TOOL_WIDTH TOOL_HEIGHT = config.TOOL_HEIGHT # Localization from scripts.ui import localization TEXT = localization.TEXT #========================================== GLOBALS ======================================== # 存储当前选中的关节和控制器信息 selected_joint = None selected_controller = None # 存储关节和控制器的原始属性,用于撤销操作 original_joint_properties = {} original_controller_properties = {} #========================================== FUNCTIONS ======================================== #------------------------------------ UI UTILITIES ------------------------------------ def handle_ui_event(event_type, event_data): """ 处理UI事件 Args: event_type: 事件类型 event_data: 事件数据 """ if event_type == "button_click": # 处理按钮点击事件 print(f"Button clicked: {event_data}") elif event_type == "slider_change": # 处理滑条改变事件 print(f"Slider changed: {event_data}") else: print(f"Unknown event type: {event_type}") def connect_ui_signals(ui_widget, event_type, event_handler): """ 连接UI信号 Args: ui_widget: UI控件 event_type: 事件类型 event_handler: 事件处理函数 """ if event_type == "button_click": # 连接按钮点击信号 ui_widget.clicked.connect(event_handler) elif event_type == "slider_change": # 连接滑条改变信号 ui_widget.valueChanged.connect(event_handler) else: print(f"Unknown event type: {event_type}") #------------------------------------ JOINT OPERATIONS ------------------------------------ def add_joint(): """ 添加关节 在场景中创建新的关节并添加到骨骼层级结构中 """ # 在场景中创建新的关节 try: # 创建新关节 new_joint = cmds.joint(name="joint1", position=[0, 0, 0]) print(f"成功创建关节: {new_joint}") return new_joint except Exception as e: print(f"创建关节失败: {str(e)}") return None def remove_joint(): """ 移除选中的关节 从场景中删除选中的关节 """ # 获取当前选中的关节 selected = cmds.ls(selection=True, type="joint") if not selected: print("没有选中关节") return False try: # 删除选中的关节 for joint in selected: cmds.delete(joint) print(f"成功删除关节: {selected}") return True except Exception as e: print(f"删除关节失败: {str(e)}") return False def duplicate_joint(): """ 复制选中的关节 复制场景中选中的关节及其属性 """ # 获取当前选中的关节 selected = cmds.ls(selection=True, type="joint") if not selected: print("没有选中关节") return None try: # 复制选中的关节 duplicated = cmds.duplicate(selected, returnRootsOnly=True) print(f"成功复制关节: {duplicated}") return duplicated except Exception as e: print(f"复制关节失败: {str(e)}") return None def update_joint_properties(joint_name, position=None, rotation=None, scale=None): """ 更新关节属性 Args: joint_name: 关节名称 position: 位置坐标 [x, y, z] rotation: 旋转角度 [rx, ry, rz] scale: 缩放比例 [sx, sy, sz] """ if not cmds.objExists(joint_name): print(f"关节不存在: {joint_name}") return False try: # 更新位置 if position: cmds.setAttr(f"{joint_name}.translateX", position[0]) cmds.setAttr(f"{joint_name}.translateY", position[1]) cmds.setAttr(f"{joint_name}.translateZ", position[2]) # 更新旋转 if rotation: cmds.setAttr(f"{joint_name}.rotateX", rotation[0]) cmds.setAttr(f"{joint_name}.rotateY", rotation[1]) cmds.setAttr(f"{joint_name}.rotateZ", rotation[2]) # 更新缩放 if scale: cmds.setAttr(f"{joint_name}.scaleX", scale[0]) cmds.setAttr(f"{joint_name}.scaleY", scale[1]) cmds.setAttr(f"{joint_name}.scaleZ", scale[2]) print(f"成功更新关节属性: {joint_name}") return True except Exception as e: print(f"更新关节属性失败: {str(e)}") return False def reset_joint_properties(joint_name): """ 重置关节属性 Args: joint_name: 关节名称 """ if not cmds.objExists(joint_name): print(f"关节不存在: {joint_name}") return False try: # 重置位置 cmds.setAttr(f"{joint_name}.translateX", 0) cmds.setAttr(f"{joint_name}.translateY", 0) cmds.setAttr(f"{joint_name}.translateZ", 0) # 重置旋转 cmds.setAttr(f"{joint_name}.rotateX", 0) cmds.setAttr(f"{joint_name}.rotateY", 0) cmds.setAttr(f"{joint_name}.rotateZ", 0) # 重置缩放 cmds.setAttr(f"{joint_name}.scaleX", 1) cmds.setAttr(f"{joint_name}.scaleY", 1) cmds.setAttr(f"{joint_name}.scaleZ", 1) print(f"成功重置关节属性: {joint_name}") return True except Exception as e: print(f"重置关节属性失败: {str(e)}") return False #------------------------------------ CONTROLLER OPERATIONS ------------------------------------ def add_controller(): """ 添加控制器 在场景中创建新的控制器 """ try: # 创建控制器曲线 circle = cmds.circle(name="controller1", normal=[0, 1, 0], radius=1)[0] print(f"成功创建控制器: {circle}") return circle except Exception as e: print(f"创建控制器失败: {str(e)}") return None def remove_controller(): """ 移除选中的控制器 从场景中删除选中的控制器 """ # 获取当前选中的控制器 selected = cmds.ls(selection=True, type="transform") if not selected: print("没有选中控制器") return False try: # 删除选中的控制器 for ctrl in selected: cmds.delete(ctrl) print(f"成功删除控制器: {selected}") return True except Exception as e: print(f"删除控制器失败: {str(e)}") return False def duplicate_controller(): """ 复制选中的控制器 复制场景中选中的控制器及其属性 """ # 获取当前选中的控制器 selected = cmds.ls(selection=True, type="transform") if not selected: print("没有选中控制器") return None try: # 复制选中的控制器 duplicated = cmds.duplicate(selected, returnRootsOnly=True) print(f"成功复制控制器: {duplicated}") return duplicated except Exception as e: print(f"复制控制器失败: {str(e)}") return None def update_controller_properties(controller_name, position=None, rotation=None, scale=None, color=None): """ 更新控制器属性 Args: controller_name: 控制器名称 position: 位置坐标 [x, y, z] rotation: 旋转角度 [rx, ry, rz] scale: 缩放比例 [sx, sy, sz] color: 控制器颜色索引 """ if not cmds.objExists(controller_name): print(f"控制器不存在: {controller_name}") return False try: # 更新位置 if position: cmds.setAttr(f"{controller_name}.translateX", position[0]) cmds.setAttr(f"{controller_name}.translateY", position[1]) cmds.setAttr(f"{controller_name}.translateZ", position[2]) # 更新旋转 if rotation: cmds.setAttr(f"{controller_name}.rotateX", rotation[0]) cmds.setAttr(f"{controller_name}.rotateY", rotation[1]) cmds.setAttr(f"{controller_name}.rotateZ", rotation[2]) # 更新缩放 if scale: cmds.setAttr(f"{controller_name}.scaleX", scale[0]) cmds.setAttr(f"{controller_name}.scaleY", scale[1]) cmds.setAttr(f"{controller_name}.scaleZ", scale[2]) # 更新颜色 if color is not None: shapes = cmds.listRelatives(controller_name, shapes=True) if shapes: for shape in shapes: cmds.setAttr(f"{shape}.overrideEnabled", 1) cmds.setAttr(f"{shape}.overrideColor", color) print(f"成功更新控制器属性: {controller_name}") return True except Exception as e: print(f"更新控制器属性失败: {str(e)}") return False def reset_controller_properties(controller_name): """ 重置控制器属性 Args: controller_name: 控制器名称 """ if not cmds.objExists(controller_name): print(f"控制器不存在: {controller_name}") return False try: # 重置位置 cmds.setAttr(f"{controller_name}.translateX", 0) cmds.setAttr(f"{controller_name}.translateY", 0) cmds.setAttr(f"{controller_name}.translateZ", 0) # 重置旋转 cmds.setAttr(f"{controller_name}.rotateX", 0) cmds.setAttr(f"{controller_name}.rotateY", 0) cmds.setAttr(f"{controller_name}.rotateZ", 0) # 重置缩放 cmds.setAttr(f"{controller_name}.scaleX", 1) cmds.setAttr(f"{controller_name}.scaleY", 1) cmds.setAttr(f"{controller_name}.scaleZ", 1) print(f"成功重置控制器属性: {controller_name}") return True except Exception as e: print(f"重置控制器属性失败: {str(e)}") return False #------------------------------------ DNA OPERATIONS ------------------------------------ def import_dna(): """ 导入DNA文件 从文件中导入DNA数据 """ try: # 打开文件对话框选择DNA文件 file_path = cmds.fileDialog2(fileFilter="DNA Files (*.dna);;All Files (*.*)", dialogStyle=2, fileMode=1) if not file_path: return None file_path = file_path[0] # 获取选中的文件路径 # 这里应该调用DNA导入API # 暂时使用打印信息代替 print(f"导入DNA文件: {file_path}") return file_path except Exception as e: print(f"导入DNA文件失败: {str(e)}") return None def export_dna(): """ 导出DNA文件 将当前绑定数据导出为DNA文件 """ try: # 打开文件对话框选择保存路径 file_path = cmds.fileDialog2(fileFilter="DNA Files (*.dna);;All Files (*.*)", dialogStyle=2, fileMode=0) if not file_path: return None file_path = file_path[0] # 获取选中的文件路径 # 确保文件扩展名为.dna if not file_path.lower().endswith(".dna"): file_path += ".dna" # 这里应该调用DNA导出API # 暂时使用打印信息代替 print(f"导出DNA文件: {file_path}") return file_path except Exception as e: print(f"导出DNA文件失败: {str(e)}") return None def calibrate_dna(): """ 校准DNA数据 校准当前绑定数据与DNA标准 """ try: # 这里应该调用DNA校准API # 暂时使用打印信息代替 print("校准DNA数据") return True except Exception as e: print(f"校准DNA数据失败: {str(e)}") return False #------------------------------------ UTILITY FUNCTIONS ------------------------------------ def update_ui_list(list_widget, items): """ 更新UI列表控件 Args: list_widget: 列表控件 items: 要添加的项目列表 """ if not list_widget: return # 清空列表 list_widget.clear() # 添加项目 for item in items: list_widget.addItem(item) def update_group_count(list_widget, group_name): """ 更新组标题中的计数 Args: list_widget: 列表控件 group_name: 组名称 """ if not list_widget: return # 获取列表项目数量 count = list_widget.count() # 更新组标题 group_box = list_widget.parent() if isinstance(group_box, QtWidgets.QGroupBox): group_box.setTitle(f"{group_name} ({count})") #------------------------------------ UI EVENT HANDLERS ------------------------------------ def handle_joint_name_changed(ui_widget): """ 处理关节名称变化事件 Args: ui_widget: UI控件,应该是一个文本输入框 """ global selected_joint # 获取当前选中的关节和新名称 if selected_joint and ui_widget: new_name = ui_widget.text() if new_name and new_name != selected_joint: try: # 重命名关节 cmds.rename(selected_joint, new_name) selected_joint = new_name return True except Exception as e: print(f"重命名关节失败: {str(e)}") return False return False def handle_joint_position_changed(axis, value=None, ui_widgets=None): """ 处理关节位置变化事件 Args: axis: 轴向 (0=X, 1=Y, 2=Z) value: 新的位置值,如果为None则从ui_widgets中获取 ui_widgets: UI控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not selected_joint: return False # 获取当前位置 pos = [ cmds.getAttr(f"{selected_joint}.translateX"), cmds.getAttr(f"{selected_joint}.translateY"), cmds.getAttr(f"{selected_joint}.translateZ") ] # 更新指定轴向的位置 if value is not None: pos[axis] = value elif ui_widgets and len(ui_widgets) >= 3: if axis == 0 and ui_widgets[0]: pos[0] = ui_widgets[0].value() elif axis == 1 and ui_widgets[1]: pos[1] = ui_widgets[1].value() elif axis == 2 and ui_widgets[2]: pos[2] = ui_widgets[2].value() else: return False # 更新关节属性 return update_joint_properties(selected_joint, position=pos) def handle_joint_rotation_changed(axis, value=None, ui_widgets=None): """ 处理关节旋转变化事件 Args: axis: 轴向 (0=X, 1=Y, 2=Z) value: 新的旋转值,如果为None则从ui_widgets中获取 ui_widgets: UI控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not selected_joint: return False # 获取当前旋转 rot = [ cmds.getAttr(f"{selected_joint}.rotateX"), cmds.getAttr(f"{selected_joint}.rotateY"), cmds.getAttr(f"{selected_joint}.rotateZ") ] # 更新指定轴向的旋转 if value is not None: rot[axis] = value elif ui_widgets and len(ui_widgets) >= 3: if axis == 0 and ui_widgets[0]: rot[0] = ui_widgets[0].value() elif axis == 1 and ui_widgets[1]: rot[1] = ui_widgets[1].value() elif axis == 2 and ui_widgets[2]: rot[2] = ui_widgets[2].value() else: return False # 更新关节属性 return update_joint_properties(selected_joint, rotation=rot) def handle_joint_scale_changed(axis, value=None, ui_widgets=None): """ 处理关节缩放变化事件 Args: axis: 轴向 (0=X, 1=Y, 2=Z) value: 新的缩放值,如果为None则从ui_widgets中获取 ui_widgets: UI控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not selected_joint: return False # 获取当前缩放 scale = [ cmds.getAttr(f"{selected_joint}.scaleX"), cmds.getAttr(f"{selected_joint}.scaleY"), cmds.getAttr(f"{selected_joint}.scaleZ") ] # 更新指定轴向的缩放 if value is not None: scale[axis] = value elif ui_widgets and len(ui_widgets) >= 3: if axis == 0 and ui_widgets[0]: scale[0] = ui_widgets[0].value() elif axis == 1 and ui_widgets[1]: scale[1] = ui_widgets[1].value() elif axis == 2 and ui_widgets[2]: scale[2] = ui_widgets[2].value() else: return False # 更新关节属性 return update_joint_properties(selected_joint, scale=scale) def apply_joint_properties_from_ui(pos_widgets=None, rot_widgets=None, scale_widgets=None): """ 从UI控件中应用关节属性 Args: pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin] rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin] scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not selected_joint: return False # 获取UI中的属性值 pos = None rot = None scale = None if pos_widgets and len(pos_widgets) >= 3: pos = [ pos_widgets[0].value() if pos_widgets[0] else 0, pos_widgets[1].value() if pos_widgets[1] else 0, pos_widgets[2].value() if pos_widgets[2] else 0 ] if rot_widgets and len(rot_widgets) >= 3: rot = [ rot_widgets[0].value() if rot_widgets[0] else 0, rot_widgets[1].value() if rot_widgets[1] else 0, rot_widgets[2].value() if rot_widgets[2] else 0 ] if scale_widgets and len(scale_widgets) >= 3: scale = [ scale_widgets[0].value() if scale_widgets[0] else 1, scale_widgets[1].value() if scale_widgets[1] else 1, scale_widgets[2].value() if scale_widgets[2] else 1 ] # 更新关节属性 return update_joint_properties(selected_joint, position=pos, rotation=rot, scale=scale) def reset_joint_properties_ui(pos_widgets=None, rot_widgets=None, scale_widgets=None): """ 重置关节属性UI Args: pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin] rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin] scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not selected_joint: return False # 重置关节属性 reset_joint_properties(selected_joint) # 更新UI显示 if pos_widgets and len(pos_widgets) >= 3: if pos_widgets[0]: pos_widgets[0].setValue(0) if pos_widgets[1]: pos_widgets[1].setValue(0) if pos_widgets[2]: pos_widgets[2].setValue(0) if rot_widgets and len(rot_widgets) >= 3: if rot_widgets[0]: rot_widgets[0].setValue(0) if rot_widgets[1]: rot_widgets[1].setValue(0) if rot_widgets[2]: rot_widgets[2].setValue(0) if scale_widgets and len(scale_widgets) >= 3: if scale_widgets[0]: scale_widgets[0].setValue(1) if scale_widgets[1]: scale_widgets[1].setValue(1) if scale_widgets[2]: scale_widgets[2].setValue(1) return True def update_joint_ui(joint_name, name_widget=None, pos_widgets=None, rot_widgets=None, scale_widgets=None): """ 更新关节UI Args: joint_name: 关节名称 name_widget: 名称控件 pos_widgets: 位置控件列表 [x_spin, y_spin, z_spin] rot_widgets: 旋转控件列表 [x_spin, y_spin, z_spin] scale_widgets: 缩放控件列表 [x_spin, y_spin, z_spin] """ global selected_joint if not cmds.objExists(joint_name): return False # 更新选中的关节 selected_joint = joint_name # 更新名称控件 if name_widget: name_widget.setText(joint_name) # 获取关节属性 pos = [ cmds.getAttr(f"{joint_name}.translateX"), cmds.getAttr(f"{joint_name}.translateY"), cmds.getAttr(f"{joint_name}.translateZ") ] rot = [ cmds.getAttr(f"{joint_name}.rotateX"), cmds.getAttr(f"{joint_name}.rotateY"), cmds.getAttr(f"{joint_name}.rotateZ") ] scale = [ cmds.getAttr(f"{joint_name}.scaleX"), cmds.getAttr(f"{joint_name}.scaleY"), cmds.getAttr(f"{joint_name}.scaleZ") ] # 更新UI控件 if pos_widgets and len(pos_widgets) >= 3: if pos_widgets[0]: pos_widgets[0].setValue(pos[0]) if pos_widgets[1]: pos_widgets[1].setValue(pos[1]) if pos_widgets[2]: pos_widgets[2].setValue(pos[2]) if rot_widgets and len(rot_widgets) >= 3: if rot_widgets[0]: rot_widgets[0].setValue(rot[0]) if rot_widgets[1]: rot_widgets[1].setValue(rot[1]) if rot_widgets[2]: rot_widgets[2].setValue(rot[2]) if scale_widgets and len(scale_widgets) >= 3: if scale_widgets[0]: scale_widgets[0].setValue(scale[0]) if scale_widgets[1]: scale_widgets[1].setValue(scale[1]) if scale_widgets[2]: scale_widgets[2].setValue(scale[2]) return True def on_selection_changed(): """ 选择变化事件处理 当Maya中的选择变化时更新UI """ global selected_joint, selected_controller # 获取当前选中的对象 selected = cmds.ls(selection=True) if not selected: # 清除选中状态 selected_joint = None selected_controller = None return # 检查选中的对象类型 for obj in selected: # 检查是否是关节 if cmds.objectType(obj) == "joint": selected_joint = obj # 可以在这里添加代码来更新UI print(f"选中关节: {selected_joint}") break # 检查是否是控制器(通常是transform节点) elif cmds.objectType(obj) == "transform": # 这里可以添加额外的检查来确定是否是控制器 # 例如检查是否有特定的属性或命名规则 selected_controller = obj # 可以在这里添加代码来更新UI print(f"选中控制器: {selected_controller}") break def browse_file(ui_instance, title, input_widget, file_filter=None): """浏览文件或目录 Args: ui_instance: UI实例,用于获取主窗口 title (str): 对话框标题 input_widget (QLineEdit): 用于显示路径的输入框控件 file_filter (str, optional): 文件过滤器,如果为None则浏览目录,否则浏览文件 """ from Qt import QtWidgets import os current_path = input_widget.text() or os.path.expanduser("~") if file_filter: # 浏览文件 if file_filter == "dna": file_filter = "DNA文件 (*.dna);;所有文件 (*.*)" elif file_filter == "json": file_filter = "JSON文件 (*.json);;所有文件 (*.*)" else: file_filter = "所有文件 (*.*)" file_path, _ = QtWidgets.QFileDialog.getOpenFileName( ui_instance.main_widget, title, current_path, file_filter ) if file_path: input_widget.setText(file_path) else: # 浏览目录 dir_path = QtWidgets.QFileDialog.getExistingDirectory( ui_instance.main_widget, title, current_path ) if dir_path: input_widget.setText(dir_path) def on_selection_changed(ui_instance=None): """处理Maya选择变化事件 Args: ui_instance: UI实例,可选 """ import maya.cmds as cmds # 获取当前选择 selection = cmds.ls(selection=True) if not selection: return # 更新UI显示 print(f"当前选择: {selection}") # 这里可以添加更多的选择处理逻辑 # 如果提供了UI实例,可以更新UI if ui_instance: # 更新UI显示 pass def export_presets(*args): """导出预设""" print("导出预设") # 实现导出预设的功能 def import_presets(*args): """导入预设""" print("导入预设") # 实现导入预设的功能 # ======================================= 占位函数 ======================================= # 以下是未实现功能的占位函数,用于连接未创建的按钮 def add_joint(*args): """添加关节(占位函数)""" print("添加关节功能尚未实现") def remove_joint(*args): """删除关节(占位函数)""" print("删除关节功能尚未实现") def duplicate_joint(*args): """复制关节(占位函数)""" print("复制关节功能尚未实现") def add_controller(*args): """添加控制器(占位函数)""" print("添加控制器功能尚未实现") def remove_controller(*args): """删除控制器(占位函数)""" print("删除控制器功能尚未实现") def duplicate_controller(*args): """复制控制器(占位函数)""" print("复制控制器功能尚未实现") def import_dna(*args): """导入DNA(占位函数)""" print("导入DNA功能尚未实现") def export_dna(*args): """导出DNA(占位函数)""" print("导出DNA功能尚未实现") def calibrate_dna(*args): """校准DNA(占位函数)""" print("校准DNA功能尚未实现")