#!/usr/bin/env python # -*- coding: utf-8 -*- """ UI Utilities Module for Plugin UI工具模块 - 提供UI相关的通用函数 """ #========================================= IMPORT ========================================= from Qt import QtWidgets, QtCore, QtGui from Qt.QtCompat import wrapInstance from maya import OpenMayaUI as omui import maya.cmds as cmds import maya.mel as mel import maya.utils as utils import webbrowser import subprocess import importlib import traceback import locale import sys import os #========================================== CONFIG ======================================== 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 #========================================= LOCATION ======================================= from scripts.ui import localization LANG = localization.LANG #============================================ INIT ========================================== # 定义窗口大小变化事件处理类 # 用于监听窗口大小变化并调整分割器宽度 class SplitterResizeHandler(QtCore.QObject): def __init__(self, parent_tab, main_splitter, is_horizontal=True, panel_count=2, panel_ratio=None): super(SplitterResizeHandler, self).__init__(parent_tab) self.parent_tab = parent_tab self.main_splitter = main_splitter self.is_horizontal = is_horizontal # 是否为水平分割器 self.panel_count = panel_count # 面板数量,默认为2(左右面板) # 面板比例,默认为None,则使用默认比例 # 如果是2面板,默认比例为[1, 1](左右均等) # 如果是3面板,默认比例为[4, 1, 2](Presets面板较大,Assets面板较小,Descriptor面板中等) self.panel_ratio = panel_ratio if panel_ratio else ([1, 1] if panel_count == 2 else [4, 1, 2]) # 安装事件过滤器到父标签页 self.parent_tab.installEventFilter(self) def eventFilter(self, obj, event): # 监听大小变化事件 if obj == self.parent_tab and event.type() == QtCore.QEvent.Resize: # 当窗口大小变化时,调整分割器宽度 self.adjustSplitterSizes() return super(SplitterResizeHandler, self).eventFilter(obj, event) def adjustSplitterSizes(self): # 确保分割器存在且父标签页可见 if self.main_splitter and self.parent_tab.isVisible(): # 计算比例总和 ratio_sum = sum(self.panel_ratio) if self.is_horizontal: # 获取父标签页当前宽度 width = self.parent_tab.width() # 根据面板数量设置分割器大小 if self.panel_count == 2: # 设置分割器左右宽度按比例分配 sizes = [int(width * ratio / ratio_sum) for ratio in self.panel_ratio] self.main_splitter.setSizes(sizes) print(f"分割器 - 窗口大小变化,调整水平分割器宽度为: {sizes}") elif self.panel_count == 3: # 设置三面板分割器宽度按比例分配 sizes = [int(width * ratio / ratio_sum) for ratio in self.panel_ratio] self.main_splitter.setSizes(sizes) print(f"分割器 - 窗口大小变化,调整三面板水平分割器宽度为: {sizes}") else: # 获取父标签页当前高度 height = self.parent_tab.height() # 根据面板数量设置分割器大小 if self.panel_count == 2: # 设置分割器上下高度按比例分配 sizes = [int(height * ratio / ratio_sum) for ratio in self.panel_ratio] self.main_splitter.setSizes(sizes) print(f"分割器 - 窗口大小变化,调整垂直分割器高度为: {sizes}") elif self.panel_count == 3: # 设置三面板分割器高度按比例分配 sizes = [int(height * ratio / ratio_sum) for ratio in self.panel_ratio] self.main_splitter.setSizes(sizes) print(f"分割器 - 窗口大小变化,调整三面板垂直分割器高度为: {sizes}") # 使用类来管理UI控件,避免全局变量 class BaseUI(object): """ UI基类 所有UI面板的基类,提供通用的UI功能 """ def __init__(self): """ 初始化UI基类 """ # 初始化字典 self.controls = {} self.layouts = {} self.buttons = {} self.splitters = {} self.inputs = {} self.labels = {} # 创建主控件 self.main_widget = None def on_show_event(self, event): """ 显示事件处理 当面板显示时重置分割器大小 Args: event: 显示事件对象 """ # 重置分割器大小 self.reset_splitter_sizes() # 调用父类的showEvent方法 super(BaseUI, self).showEvent(event) def reset_splitter_sizes(self): """ 重置分割器大小,设置三个面板的初始大小 Presets面板较大,显示预设图片;Assets面板较小;Descriptor面板中等 """ if hasattr(self, 'splitters') and 'main_splitter' in self.splitters and self.splitters["main_splitter"]: # 尝试从设置中读取分割器大小 preset_size = cmds.optionVar(query="MetaFusionRiggingPresetsPanelSize") if cmds.optionVar(exists="MetaFusionRiggingPresetsPanelSize") else 400 assets_size = cmds.optionVar(query="MetaFusionRiggingAssetsPanelSize") if cmds.optionVar(exists="MetaFusionRiggingAssetsPanelSize") else 100 descriptor_size = cmds.optionVar(query="MetaFusionRiggingDescriptorPanelSize") if cmds.optionVar(exists="MetaFusionRiggingDescriptorPanelSize") else 200 # 设置分割器大小 self.splitters["main_splitter"].setSizes([preset_size, assets_size, descriptor_size]) def create_widgets(self): """ 创建UI控件 创建所有UI控件并存储到字典中 """ # 子类实现 pass def create_layouts(self): """ 创建UI布局 创建所有UI布局并存储到字典中 """ # 子类实现 pass def connect_ui_signals(self): """ 连接UI信号和槽 设置UI控件的事件处理函数 """ # 子类实现 pass def showEvent(self, event): """ 显示事件 当面板显示时调用on_show_event方法 Args: event: 显示事件对象 """ # 调用自定义的显示事件处理方法 self.on_show_event(event) # 分割器相关的工具函数 def update_splitter_position(splitter, sizes=None): """ 更新分割器位置 Args: splitter: 分割器对象 sizes: 分割器大小列表,如果为None则使用当前分割器大小 """ if not splitter: return # 记录当前分割器位置 if sizes is None: sizes = splitter.sizes() # 将分割器位置保存到设置中 # 如果是三面板分割器,则保存三个大小值 if len(sizes) == 3: # 保存三面板分割器位置(Presets、Assets、Descriptor) cmds.optionVar(intValue=["MetaFusionRiggingPresetsPanelSize", sizes[0]]) cmds.optionVar(intValue=["MetaFusionRiggingAssetsPanelSize", sizes[1]]) cmds.optionVar(intValue=["MetaFusionRiggingDescriptorPanelSize", sizes[2]]) else: # 保存左右分割器位置 cmds.optionVar(intValue=["MetaFusionRiggingLeftPanelSize", sizes[0]]) cmds.optionVar(intValue=["MetaFusionRiggingRightPanelSize", sizes[1]]) print(f"Splitter sizes updated: {sizes}") # 通用UI事件处理函数 def on_splitter_moved(ui_instance, pos, index): """ 分割器移动事件处理 记录当前分割器位置 Args: ui_instance: UI实例,包含splitters字典 pos: 分割器位置 index: 分割器索引 """ if hasattr(ui_instance, 'splitters') and 'main_splitter' in ui_instance.splitters: update_splitter_position(ui_instance.splitters["main_splitter"]) def connect_ui_signals(ui_instance, signal_mapping): """ 连接UI信号和槽 设置UI控件的事件处理函数 Args: ui_instance: UI实例,包含控件和布局 signal_mapping: 信号映射字典,格式为: { 'widget_type': { # 'buttons', 'inputs', 'splitters'等 'widget_name': { # 控件名称 'signal': 'signal_name', # 信号名称,如'clicked', 'valueChanged'等 'handler': handler_function, # 处理函数 'args': [arg1, arg2, ...] # 可选,处理函数的参数 } } } """ # 遍历信号映射字典 for widget_type, widgets in signal_mapping.items(): # 获取控件字典 widget_dict = getattr(ui_instance, widget_type, {}) # 遍历控件 for widget_name, signal_info in widgets.items(): # 检查控件是否存在 if widget_name in widget_dict: widget = widget_dict[widget_name] signal_name = signal_info.get('signal') handler = signal_info.get('handler') args = signal_info.get('args', []) # 获取信号对象 signal = getattr(widget, signal_name, None) # 连接信号和槽 if signal and handler: if args: signal.connect(lambda *extra, h=handler, a=args: h(*a)) else: signal.connect(handler) def connect_maya_selection_changed(ui_instance, handler, parent_widget=None): """ 连接Maya选择变化事件 Args: ui_instance: UI实例 handler: 选择变化事件处理函数 parent_widget: 父控件,用于设置scriptJob的parent参数 """ # 如果没有指定父控件,则使用UI实例的main_widget if parent_widget is None and hasattr(ui_instance, 'main_widget'): parent_widget = ui_instance.main_widget # 创建scriptJob if parent_widget: job_id = cmds.scriptJob(event=["SelectionChanged", handler], parent=parent_widget.objectName()) return job_id return None # 获取Maya主窗口 def get_maya_main_window(): """ 获取Maya主窗口 Returns: QWidget: Maya主窗口控件 """ main_window_ptr = omui.MQtUtil.mainWindow() return wrapInstance(int(main_window_ptr), QtWidgets.QWidget) def get_parent_widget(widget_name): """ 根据控件名称查找父容器控件 Args: widget_name (str): 控件名称 Returns: QWidget: 找到的父容器控件,如果未找到则返回None """ # 查找主窗口中的所有控件 main_window = None for widget in QtWidgets.QApplication.topLevelWidgets(): if widget.objectName() == f"{TOOL_NAME}MainWindow" or widget.objectName().endswith("MainWindow"): main_window = widget break if not main_window: print(f"无法找到主窗口,无法获取父容器: {widget_name}") return None # 在主窗口中查找指定名称的控件 found_widget = main_window.findChild(QtWidgets.QWidget, widget_name) if found_widget: return found_widget # 如果未找到精确匹配,尝试模糊匹配 for child in main_window.findChildren(QtWidgets.QWidget): if widget_name.lower() in child.objectName().lower(): return child print(f"无法找到控件: {widget_name}") return None