339 lines
12 KiB
Python
339 lines
12 KiB
Python
#!/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 |