309 lines
10 KiB
Python
309 lines
10 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
UI Utilities Module for Plugin
|
||
UI工具模块 - 提供UI相关的通用函数
|
||
"""
|
||
#========================================= IMPORT =========================================
|
||
from scripts.ui.Qt import QtWidgets, QtCore, QtGui
|
||
from scripts.ui.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
|
||
TEXT = localization.TEXT
|
||
#============================================ UI BASE ==========================================
|
||
class BaseUI(QtWidgets.QWidget):
|
||
"""
|
||
UI基类
|
||
所有UI面板的基类,提供通用的UI功能
|
||
"""
|
||
def __init__(self, parent=None):
|
||
super().__init__(parent)
|
||
# 初始化字典
|
||
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控件文本,支持多语言切换
|
||
"""
|
||
from scripts.ui import localization
|
||
# 批量刷新注册字典中的控件
|
||
for group in [self.controls, self.labels, self.buttons, getattr(self, 'inputs', {}), getattr(self, 'splitters', {})]:
|
||
for key, widget in group.items():
|
||
# QLabel/QPushButton/QCheckBox/QRadioButton等
|
||
if hasattr(widget, 'setText'):
|
||
widget.setText(TEXT(key, widget.text() if hasattr(widget, 'text') else ""))
|
||
# QLineEdit等
|
||
elif hasattr(widget, 'setPlaceholderText'):
|
||
widget.setPlaceholderText(TEXT(key, widget.placeholderText() if hasattr(widget, 'placeholderText') else ""))
|
||
# 递归刷新所有自定义子UI(即BaseUI子类)
|
||
for child in self.findChildren(QtWidgets.QWidget):
|
||
if child is not self and hasattr(child, 'update_language') and callable(child.update_language):
|
||
child.update_language()
|
||
|
||
|
||
#============================================ 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(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(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(TEXT(key, current_tip))
|
||
break
|
||
|
||
# 递归处理所有子控件
|
||
for child in widget.findChildren(QtWidgets.QWidget):
|
||
update_ui_texts(child)
|
||
|