2025-02-09 21:35:41 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
2025-02-09 22:11:50 +08:00
|
|
|
|
|
|
|
|
|
#===================================== 1. Module Imports =====================================
|
|
|
|
|
import maya.OpenMayaUI as omui
|
|
|
|
|
from scripts import config
|
|
|
|
|
import maya.cmds as cmds
|
|
|
|
|
import maya.mel as mel
|
|
|
|
|
import webbrowser
|
|
|
|
|
import sys
|
|
|
|
|
import os
|
2025-02-11 00:04:32 +08:00
|
|
|
|
|
|
|
|
|
from .. import config
|
|
|
|
|
|
|
|
|
|
QtCore, QtGui, QtWidgets, wrapInstance = config.Qt()
|
|
|
|
|
|
|
|
|
|
if QtCore is None or QtGui is None or QtWidgets is None or wrapInstance is None:
|
|
|
|
|
print(f"Qt加载失败: {QtCore}, {QtGui}, {QtWidgets}, {wrapInstance}")
|
2025-02-09 23:22:48 +08:00
|
|
|
|
|
|
|
|
|
class ToolShelf(QtWidgets.QToolBar):
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super(ToolShelf, self).__init__(parent)
|
|
|
|
|
self.setIconSize(QtCore.QSize(24, 24))
|
|
|
|
|
self.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
|
|
|
|
|
|
|
|
|
|
# 添加工具按钮
|
|
|
|
|
self._add_tools()
|
|
|
|
|
|
|
|
|
|
def _add_tools(self):
|
|
|
|
|
"""添加工具按钮"""
|
|
|
|
|
tools = [
|
|
|
|
|
("保存 DNA", "save.png"),
|
|
|
|
|
("加载当前项目 DNA", "open.png"),
|
|
|
|
|
None, # 分隔符
|
|
|
|
|
("创建RL4节点", "connect.png"),
|
|
|
|
|
("删除RL4节点", "disconnect.png"),
|
|
|
|
|
None,
|
|
|
|
|
("导出蒙皮", "export_skin.png"),
|
|
|
|
|
("导入蒙皮", "import_skin.png"),
|
|
|
|
|
("复制蒙皮", "copy_skin.png"),
|
|
|
|
|
None,
|
|
|
|
|
("帮助文档", "help.png"),
|
|
|
|
|
("关于", "about.png")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for tool in tools:
|
|
|
|
|
if tool is None:
|
|
|
|
|
self.addSeparator()
|
|
|
|
|
else:
|
|
|
|
|
name, icon = tool
|
|
|
|
|
action = QtWidgets.QAction(QtGui.QIcon(f"{config.ICONS_PATH}/{icon}"), name, self)
|
|
|
|
|
self.addAction(action)
|
|
|
|
|
|
|
|
|
|
class IconButton(QtWidgets.QPushButton):
|
|
|
|
|
"""带图标的按钮"""
|
|
|
|
|
def __init__(self, icon_name, tooltip="", size=24, parent=None):
|
|
|
|
|
super(IconButton, self).__init__(parent)
|
|
|
|
|
self._setup_ui(icon_name, tooltip, size)
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self, icon_name, tooltip, size):
|
|
|
|
|
# 设置图标
|
|
|
|
|
icon = QtGui.QIcon(f"{config.ICONS_PATH}/{icon_name}")
|
|
|
|
|
self.setIcon(icon)
|
|
|
|
|
self.setIconSize(QtCore.QSize(size, size))
|
|
|
|
|
|
|
|
|
|
# 设置样式
|
|
|
|
|
self.setFixedSize(size, size)
|
|
|
|
|
self.setToolTip(tooltip)
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
QPushButton {
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
}
|
|
|
|
|
QPushButton:pressed {
|
|
|
|
|
background: rgba(255, 255, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
class SearchLineEdit(QtWidgets.QLineEdit):
|
|
|
|
|
"""带搜索图标的输入框"""
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super(SearchLineEdit, self).__init__(parent)
|
|
|
|
|
self._setup_ui()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self):
|
|
|
|
|
# 设置样式
|
|
|
|
|
self.setPlaceholderText("搜索...")
|
|
|
|
|
|
|
|
|
|
# 添加搜索图标
|
|
|
|
|
search_action = QtWidgets.QAction(self)
|
|
|
|
|
search_action.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/search.png"))
|
|
|
|
|
self.addAction(search_action, QtWidgets.QLineEdit.LeadingPosition)
|
|
|
|
|
|
|
|
|
|
# 添加清除按钮
|
|
|
|
|
self.setClearButtonEnabled(True)
|
|
|
|
|
|
|
|
|
|
class CollapsibleWidget(QtWidgets.QWidget):
|
|
|
|
|
"""可折叠的控件组"""
|
|
|
|
|
def __init__(self, title="", parent=None):
|
|
|
|
|
super(CollapsibleWidget, self).__init__(parent)
|
|
|
|
|
self._setup_ui(title)
|
|
|
|
|
self._create_connections()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self, title):
|
|
|
|
|
# === Main Layout ===
|
|
|
|
|
self.main_layout = QtWidgets.QVBoxLayout(self)
|
|
|
|
|
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
self.main_layout.setSpacing(0)
|
|
|
|
|
|
|
|
|
|
# === Header ===
|
|
|
|
|
self.header_widget = QtWidgets.QWidget()
|
|
|
|
|
header_layout = QtWidgets.QHBoxLayout(self.header_widget)
|
|
|
|
|
header_layout.setContentsMargins(5, 5, 5, 5)
|
|
|
|
|
|
|
|
|
|
# 展开/折叠按钮
|
|
|
|
|
self.toggle_btn = IconButton("arrow_down.png", size=16)
|
|
|
|
|
header_layout.addWidget(self.toggle_btn)
|
|
|
|
|
|
|
|
|
|
# 标题
|
|
|
|
|
title_label = QtWidgets.QLabel(title)
|
|
|
|
|
header_layout.addWidget(title_label)
|
|
|
|
|
header_layout.addStretch()
|
|
|
|
|
|
|
|
|
|
self.main_layout.addWidget(self.header_widget)
|
|
|
|
|
|
|
|
|
|
# === Content ===
|
|
|
|
|
self.content_widget = QtWidgets.QWidget()
|
|
|
|
|
self.content_layout = QtWidgets.QVBoxLayout(self.content_widget)
|
|
|
|
|
self.content_layout.setContentsMargins(20, 5, 5, 5)
|
|
|
|
|
|
|
|
|
|
self.main_layout.addWidget(self.content_widget)
|
|
|
|
|
|
|
|
|
|
def _create_connections(self):
|
|
|
|
|
self.toggle_btn.clicked.connect(self._toggle_content)
|
|
|
|
|
|
|
|
|
|
def _toggle_content(self):
|
|
|
|
|
"""切换内容显示/隐藏"""
|
|
|
|
|
if self.content_widget.isVisible():
|
|
|
|
|
self.content_widget.hide()
|
|
|
|
|
self.toggle_btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/arrow_right.png"))
|
|
|
|
|
else:
|
|
|
|
|
self.content_widget.show()
|
|
|
|
|
self.toggle_btn.setIcon(QtGui.QIcon(f"{config.ICONS_PATH}/arrow_down.png"))
|
|
|
|
|
|
|
|
|
|
def add_widget(self, widget):
|
|
|
|
|
"""添加控件到内容区域"""
|
|
|
|
|
self.content_layout.addWidget(widget)
|
|
|
|
|
|
|
|
|
|
class LabeledSpinBox(QtWidgets.QWidget):
|
|
|
|
|
"""带标签的数值输入框"""
|
|
|
|
|
valueChanged = QtCore.Signal(float) # 值改变信号
|
|
|
|
|
|
|
|
|
|
def __init__(self, label="", min_value=0.0, max_value=1.0, decimals=3, parent=None):
|
|
|
|
|
super(LabeledSpinBox, self).__init__(parent)
|
|
|
|
|
self._setup_ui(label, min_value, max_value, decimals)
|
|
|
|
|
self._create_connections()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self, label, min_value, max_value, decimals):
|
|
|
|
|
# === Layout ===
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(self)
|
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
|
|
# 标签
|
|
|
|
|
self.label = QtWidgets.QLabel(label)
|
|
|
|
|
layout.addWidget(self.label)
|
|
|
|
|
|
|
|
|
|
# 数值输入框
|
|
|
|
|
self.spin_box = QtWidgets.QDoubleSpinBox()
|
|
|
|
|
self.spin_box.setMinimum(min_value)
|
|
|
|
|
self.spin_box.setMaximum(max_value)
|
|
|
|
|
self.spin_box.setDecimals(decimals)
|
|
|
|
|
layout.addWidget(self.spin_box)
|
|
|
|
|
|
|
|
|
|
def _create_connections(self):
|
|
|
|
|
self.spin_box.valueChanged.connect(self.valueChanged.emit)
|
|
|
|
|
|
|
|
|
|
def value(self):
|
|
|
|
|
return self.spin_box.value()
|
|
|
|
|
|
|
|
|
|
def setValue(self, value):
|
|
|
|
|
self.spin_box.setValue(value)
|
|
|
|
|
|
|
|
|
|
class StatusWidget(QtWidgets.QWidget):
|
|
|
|
|
"""状态显示控件"""
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
super(StatusWidget, self).__init__(parent)
|
|
|
|
|
self._setup_ui()
|
|
|
|
|
|
|
|
|
|
def _setup_ui(self):
|
|
|
|
|
# === Layout ===
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(self)
|
|
|
|
|
layout.setContentsMargins(5, 2, 5, 2)
|
|
|
|
|
|
|
|
|
|
# 状态图标
|
|
|
|
|
self.icon_label = QtWidgets.QLabel()
|
|
|
|
|
self.icon_label.setFixedSize(16, 16)
|
|
|
|
|
layout.addWidget(self.icon_label)
|
|
|
|
|
|
|
|
|
|
# 状态文本
|
|
|
|
|
self.text_label = QtWidgets.QLabel()
|
|
|
|
|
layout.addWidget(self.text_label)
|
|
|
|
|
|
|
|
|
|
layout.addStretch()
|
|
|
|
|
|
|
|
|
|
def set_status(self, status, message):
|
|
|
|
|
"""设置状态
|
|
|
|
|
status: 'normal', 'warning', 'error'
|
|
|
|
|
"""
|
|
|
|
|
icon_map = {
|
|
|
|
|
'normal': 'status_normal.png',
|
|
|
|
|
'warning': 'status_warning.png',
|
|
|
|
|
'error': 'status_error.png'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if status in icon_map:
|
|
|
|
|
self.icon_label.setPixmap(
|
|
|
|
|
QtGui.QPixmap(f"{config.ICONS_PATH}/{icon_map[status]}")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.text_label.setText(message)
|