Files
MetaFusion/scripts/ui/ui_utils.py
2025-05-06 08:33:33 +08:00

339 lines
12 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 -*-
"""
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