Files
MetaFusion/scripts/ui/ui_utils.py
2025-05-08 00:39:41 +08:00

298 lines
9.4 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
import weakref
#========================================== 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
TOOL_WIDTH = config.TOOL_WIDTH
TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
#============================================ UI BASE ==========================================
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 create_widgets(self):
"""创建UI控件"""
pass
def create_layouts(self):
"""创建UI布局"""
pass
def create_connections(self):
"""连接UI信号和槽"""
pass
def update_language(self):
"""
更新所有UI文本到当前语言
"""
if self.main_widget:
update_ui_texts(self.main_widget)
#============================================ UI HELPERS ==========================================
def connect_ui_signals(ui_instance, signal_mapping):
"""连接UI信号和槽"""
# 遍历信号映射字典
for widget_type, widgets in signal_mapping.items():
# 获取控件字典
widget_dict = getattr(ui_instance, widget_type, {})
# 遍历控件字典
for widget_name, signal_info in widgets.items():
# 获取控件
widget = widget_dict.get(widget_name)
if not widget:
# 静默处理未找到的控件,不显示警告
# print(f"警告: 未找到控件 {widget_name}")
continue
# 获取信号名称和处理函数
signal_name = signal_info.get('signal')
handler = signal_info.get('handler')
if not signal_name or not handler:
print(f"警告: 信号名称或处理函数未指定 {widget_name}")
continue
# 获取信号对象
signal = getattr(widget, signal_name, None)
if not signal:
print(f"警告: 未找到信号 {signal_name} 在控件 {widget_name}")
continue
# 获取可选参数
args = signal_info.get('args', [])
# 连接信号和槽
if args:
signal.connect(lambda *_, handler=handler, args=args: handler(*args))
else:
signal.connect(handler)
def connect_maya_selection_changed(ui_instance, handler, parent_widget=None):
"""连接Maya选择变化事件"""
# 如果已经有scriptJob先删除
if hasattr(ui_instance, 'selection_job') and ui_instance.selection_job > 0:
try:
cmds.scriptJob(kill=ui_instance.selection_job, force=True)
except:
pass
# 创建新的scriptJob
parent_arg = {}
if parent_widget and parent_widget.objectName():
parent_arg = {"parent": parent_widget.objectName()}
ui_instance.selection_job = cmds.scriptJob(
event=["SelectionChanged", handler],
protected=True,
**parent_arg
)
print(f"已连接选择变化事件, scriptJob ID: {ui_instance.selection_job}")
#============================================ MAYA HELPERS ==========================================
def get_maya_main_window():
"""获取Maya主窗口"""
ptr = omui.MQtUtil.mainWindow()
if ptr is not None:
return wrapInstance(int(ptr), QtWidgets.QWidget)
def get_parent_widget(widget_name):
"""根据控件名称查找父容器控件"""
# 获取Maya主窗口
main_window = get_maya_main_window()
if not main_window:
return None
# 查找所有子控件
def find_widget(parent, name):
# 检查当前控件
if parent.objectName() == name:
return parent
# 递归查找子控件
for child in parent.children():
if isinstance(child, QtWidgets.QWidget):
# 检查子控件
if child.objectName() == name:
return child
# 递归查找
result = find_widget(child, name)
if result:
return result
return None
# 从主窗口开始查找
return find_widget(main_window, widget_name)
def load_icon(icon_name):
"""
加载图标,支持多种来源
Args:
icon_name (str): 图标名称
Returns:
QIcon: 加载的图标对象
"""
if not icon_name:
return QtGui.QIcon()
# 尝试从插件图标路径加载
if ICONS_PATH and os.path.exists(ICONS_PATH):
# 检查不同的文件扩展名
extensions = ['', '.png', '.jpg', '.svg', '.ico']
for ext in extensions:
path = os.path.join(ICONS_PATH, icon_name + ext)
if os.path.exists(path):
return QtGui.QIcon(path)
# 尝试从Maya内置图标加载
for prefix in [':', ':/']:
try:
icon = QtGui.QIcon(QtGui.QPixmap(f"{prefix}{icon_name}"))
if not icon.isNull():
return icon
except:
continue
# 如果都失败,返回一个空图标
return QtGui.QIcon()
#============================================ SPLITTER ==========================================
def set_splitter_proportions(splitter, proportions):
"""
设置分割器各部分的比例
Args:
splitter: 要设置的分割器
proportions: 比例列表,如 [0.3, 0.7] 表示左侧占30%右侧占70%
"""
if not isinstance(splitter, QtWidgets.QSplitter) or not proportions:
return
# 确保比例数量与分割器子部件数量一致
if len(proportions) != splitter.count():
return
# 确保比例总和为1
total = sum(proportions)
if total <= 0:
return
# 计算每个部件应该的大小
if splitter.orientation() == QtCore.Qt.Horizontal:
total_size = splitter.width()
else:
total_size = splitter.height()
# 计算实际大小
sizes = [int(total_size * (p / total)) for p in proportions]
# 设置大小
splitter.setSizes(sizes)
def update_ui_texts(widget):
"""
递归更新控件文本以应用当前语言
Args:
widget: 要更新的控件或控件容器
"""
from scripts.ui import localization
# 更新标签文本
if isinstance(widget, QtWidgets.QLabel):
# 尝试查找与当前文本匹配的键
current_text = widget.text()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_text:
widget.setText(localization.get_text(key, current_text))
break
# 更新按钮文本和工具提示
elif isinstance(widget, QtWidgets.QPushButton) or isinstance(widget, QtWidgets.QToolButton):
# 更新按钮文本
if widget.text():
current_text = widget.text()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_text:
widget.setText(localization.get_text(key, current_text))
break
# 更新工具提示
if widget.toolTip():
current_tip = widget.toolTip()
for lang in ["zh_CN", "en_US"]:
for key, text in localization.LANG.get(lang, {}).items():
if text == current_tip:
widget.setToolTip(localization.get_text(key, current_tip))
break
# 递归处理所有子控件
for child in widget.findChildren(QtWidgets.QWidget):
update_ui_texts(child)