1430 lines
39 KiB
Markdown
1430 lines
39 KiB
Markdown
# 项目目标
|
||
|
||
我想做一个Maya的Metahuman自定义的插件,
|
||
|
||
语言:基于Python
|
||
|
||
Maya版本:2022, 2023, 2024, 2025
|
||
|
||
## 项目描述
|
||
|
||
本项目是一个Maya插件,主要功能是提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。
|
||
|
||
## 这个插件主要功能:
|
||
|
||
提供与MetaHuman相同拓扑的模型或者自定义的3d模型以来完成自定义绑定,编辑DNA,校准骨骼位置,保存DNA,载入DNA,导出fbx,保存DNA文件, 编辑BlendShape,等功能。
|
||
|
||
## 注意Reference路径不参与参见功能实现,只作为参考。Reference只作为参考,可以从中拷贝必要的文件到当前项目中
|
||
|
||
## 代码实现:
|
||
|
||
根据Maya和Python版本来获取plugin的路径,并尽可能使用PySide编写UI,要保证PySide的通用性,使用单独的ccs文件来定义定义样式。
|
||
|
||
根据Maya不同的版本来定义PySide的UI定义,版本通用性参考MSLiveLink。
|
||
|
||
## 参考代码:
|
||
|
||
DNA\_Calibration中主要参考DNA编辑等功能更,SuperRigigng主要参考UI样式并获取对应的功能的实现逻辑,MSLiveLink主要参开DNA编辑和文件处理方式。
|
||
|
||
## 产品功能对标:
|
||
|
||
DNA Calibration Document : https://epicgames.github.io/MetaHuman-DNA-Calibration/index.html
|
||
|
||
MetaHuman-DNA-Calibration 代码:https://github.com/EpicGames/MetaHuman-DNA-Calibration
|
||
|
||
SuperRigging:https://docs.pointart.net/
|
||
|
||
AnimCraft:https://geekdaxue.co/read/animcraft@cn/
|
||
|
||
## 代码初始结构:
|
||
|
||
.
|
||
|
||
├── config
|
||
|
||
│ └── \_\_init\_\_.py
|
||
|
||
│ └── data.py
|
||
|
||
├── plugins
|
||
|
||
│ └── Linux
|
||
|
||
│ │ ├── 2022
|
||
|
||
│ │ │ ├── \_py3dnacalib.so
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── libdnacalib.so.6
|
||
|
||
│ │ │ ├── libembeddedRL4.so
|
||
|
||
│ │ │ ├── libembeddedRL4.so.8
|
||
|
||
│ │ │ ├── libembeddedRL4.so.8.0.8
|
||
|
||
│ │ │ ├── MayaUE4RBFPlugin2022.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2023
|
||
|
||
│ │ │ ├── \_py3dnacalib.so
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── libdnacalib.so.6
|
||
|
||
│ │ │ ├── libembeddedRL4.so
|
||
|
||
│ │ │ ├── libembeddedRL4.so.8
|
||
|
||
│ │ │ ├── libembeddedRL4.so.8.0.8
|
||
|
||
│ │ │ ├── MayaUE4RBFPlugin2023.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2024
|
||
|
||
│ │ │ ├── \_py3dnacalib.so
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── libdnacalib.so.6
|
||
|
||
│ │ │ ├── libembeddedRL4.so
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2025
|
||
|
||
│ │ │ ├── \_py3dnacalib.so
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── embeddedRL4.so
|
||
|
||
│ │ │ ├── libdnacalib.so.6
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── pydna
|
||
|
||
│ │ │ ├── python3
|
||
|
||
│ │ │ │ ├── \_py3dna.so
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ │ ├── libdna.so.7.1.0
|
||
|
||
│ │ │ ├── python311
|
||
|
||
│ │ │ │ ├── \_py3dna.so
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ │ ├── libdna.so.7
|
||
|
||
│ │ │ ├── python397
|
||
|
||
│ │ │ │ ├── \_py3dna.so
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ │ ├── libdna.so.7.1.0
|
||
|
||
│ │ │ ├── python3108
|
||
|
||
│ │ │ │ ├── \_py3dna.so
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ │ ├── libdna.so.7.1.0
|
||
|
||
│ └── Windows
|
||
|
||
│ │ ├── 2022
|
||
|
||
│ │ │ ├── \_py3dnacalib.pyd
|
||
|
||
│ │ │ ├── dnacalib.dll
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── embeddedRL4.mll
|
||
|
||
│ │ │ ├── MayaUE4RBFPlugin2022.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2023
|
||
|
||
│ │ │ ├── \_py3dnacalib.pyd
|
||
|
||
│ │ │ ├── dnacalib.dll
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── embeddedRL4.mll
|
||
|
||
│ │ │ ├── MayaUE4RBFPlugin2023.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2024
|
||
|
||
│ │ │ ├── \_py3dnacalib.pyd
|
||
|
||
│ │ │ ├── dnacalib.dll
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── embeddedRL4.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── 2025
|
||
|
||
│ │ │ ├── \_py3dnacalib.pyd
|
||
|
||
│ │ │ ├── dnacalib.dll
|
||
|
||
│ │ │ ├── dnacalib.py
|
||
|
||
│ │ │ ├── embeddedRL4.mll
|
||
|
||
│ │ │ ├── MayaUERBFPlugin.mll
|
||
|
||
│ │ ├── pydna
|
||
|
||
│ │ │ ├── python3
|
||
|
||
│ │ │ │ ├── \_py3dna.pyd
|
||
|
||
│ │ │ │ ├── dna.dll
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ ├── python311
|
||
|
||
│ │ │ │ ├── \_py3dna.pyd
|
||
|
||
│ │ │ │ ├── \_py3dna9\_4\_3.pyd
|
||
|
||
│ │ │ │ ├── dna.dll
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ │ ├── dna9\_4\_3.dll
|
||
|
||
│ │ │ │ ├── polyalloc1\_3\_12.dll
|
||
|
||
│ │ │ │ ├── statuscode1\_2\_6.dll
|
||
|
||
│ │ │ │ ├── trio4\_0\_16.dll
|
||
|
||
│ │ │ ├── python397
|
||
|
||
│ │ │ │ ├── \_py3dna.pyd
|
||
|
||
│ │ │ │ ├── dna.dll
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ ├── python3108
|
||
|
||
│ │ │ │ ├── \_py3dna.pyd
|
||
|
||
│ │ │ │ ├── dna.dll
|
||
|
||
│ │ │ │ ├── dna.py
|
||
|
||
│ │ │ ├── \_\_init\_\_.py
|
||
|
||
├── resources
|
||
|
||
│ ├── icons
|
||
|
||
│ │ ├── ARKit52.png
|
||
|
||
│ │ ├── automatic\_grouping.png
|
||
|
||
│ │ ├── backward.png
|
||
|
||
│ │ ├── behaviour.png
|
||
|
||
│ │ ├── blendRaw.png
|
||
|
||
│ │ ├── blendShape\_current.png
|
||
|
||
│ │ ├── blendShape.png
|
||
|
||
│ │ ├── change\_password.png
|
||
|
||
│ │ ├── chinese.png
|
||
|
||
│ │ ├── clone\_blendShape.png
|
||
|
||
│ │ ├── clothing\_weight.png
|
||
|
||
│ │ ├── color.png
|
||
|
||
│ │ ├── CommandButton.png
|
||
|
||
│ │ ├── configuration.png
|
||
|
||
│ │ ├── connect.png
|
||
|
||
│ │ ├── controller.png
|
||
|
||
│ │ ├── copy\_skin.png
|
||
|
||
│ │ ├── copy.png
|
||
|
||
│ │ ├── create\_body\_ctrl.png
|
||
|
||
│ │ ├── create\_lod.png
|
||
|
||
│ │ ├── ctrl\_hide.png
|
||
|
||
│ │ ├── definition.png
|
||
|
||
│ │ ├── delete.png
|
||
|
||
│ │ ├── detector.png
|
||
|
||
│ │ ├── disconnect.png
|
||
|
||
│ │ ├── english.png
|
||
|
||
│ │ ├── exit.png
|
||
|
||
│ │ ├── export\_skin.png
|
||
|
||
│ │ ├── export.png
|
||
|
||
│ │ ├── expression.png
|
||
|
||
│ │ ├── expressions\_blend.png
|
||
|
||
│ │ ├── expressions\_current.png
|
||
|
||
│ │ ├── expressions.png
|
||
|
||
│ │ ├── forward.png
|
||
|
||
│ │ ├── help.png
|
||
|
||
│ │ ├── import\_body\_anim.png
|
||
|
||
│ │ ├── import\_face\_anim.png
|
||
|
||
│ │ ├── import\_skin.png
|
||
|
||
│ │ ├── import.png
|
||
|
||
│ │ ├── joint.png
|
||
|
||
│ │ ├── load\_meshes.png
|
||
|
||
│ │ ├── loading.png
|
||
|
||
│ │ ├── locator.png
|
||
|
||
│ │ ├── lock.png
|
||
|
||
│ │ ├── mark.png
|
||
|
||
│ │ ├── meshes.png
|
||
|
||
│ │ ├── message.png
|
||
|
||
│ │ ├── MetaFusionLogo.png
|
||
|
||
│ │ ├── mirror.png
|
||
|
||
│ │ ├── mirrorL.png
|
||
|
||
│ │ ├── mirrorR.png
|
||
|
||
│ │ ├── motion\_apply.png
|
||
|
||
│ │ ├── open\_camera.png
|
||
|
||
│ │ ├── open.png
|
||
|
||
│ │ ├── pause.png
|
||
|
||
│ │ ├── play.png
|
||
|
||
│ │ ├── plus.png
|
||
|
||
│ │ ├── pose\_A\_To\_T.png
|
||
|
||
│ │ ├── pose\_T\_To\_A.png
|
||
|
||
│ │ ├── presets.png
|
||
|
||
│ │ ├── psd.png
|
||
|
||
│ │ ├── rebuildTargets.png
|
||
|
||
│ │ ├── reduce.png
|
||
|
||
│ │ ├── rename.png
|
||
|
||
│ │ ├── repair\_normals.png
|
||
|
||
│ │ ├── repair\_vertex\_order.png
|
||
|
||
│ │ ├── reset.png
|
||
|
||
│ │ ├── resetname.png
|
||
|
||
│ │ ├── return.png
|
||
|
||
│ │ ├── save\_new.png
|
||
|
||
│ │ ├── save.png
|
||
|
||
│ │ ├── search.png
|
||
|
||
│ │ ├── set\_current.png
|
||
|
||
│ │ ├── set\_no.png
|
||
|
||
│ │ ├── set\_ok.png
|
||
|
||
│ │ ├── set\_range.png
|
||
|
||
│ │ ├── settings.png
|
||
|
||
│ │ ├── standardized\_naming.png
|
||
|
||
│ │ ├── stop.png
|
||
|
||
│ │ ├── supplement\_meshes.png
|
||
|
||
│ │ ├── symmetry.png
|
||
|
||
│ │ ├── target.png
|
||
|
||
│ │ ├── transfer\_maps.png
|
||
|
||
│ │ ├── unmark\_all.png
|
||
|
||
│ │ ├── unreal.png
|
||
|
||
│ │ ├── update.png
|
||
|
||
│ │ ├── user\_login.png
|
||
|
||
│ │ ├── visible.png
|
||
|
||
│ │ ├── warning.png
|
||
|
||
│ ├── styles
|
||
|
||
│ │ ├── style.qss
|
||
|
||
├── scripts
|
||
|
||
│ ├── ui
|
||
|
||
│ ├── MetaFusion.py
|
||
|
||
├── CleanPycache.bat
|
||
|
||
├── Install.mel
|
||
|
||
├── Install.py
|
||
|
||
├── README.md
|
||
|
||
## 重要代码内容:
|
||
|
||
├── Install.mel
|
||
|
||
```Python
|
||
global proc install()
|
||
{
|
||
string $scriptPath = `whatIs install`;
|
||
string $dirPath = `substring $scriptPath 25 (size($scriptPath))`;
|
||
$dirPath = `dirname $dirPath`;
|
||
string $pythonPath = $dirPath + "/Install.py";
|
||
$pythonPath = substituteAllString($pythonPath, "\\", "/");
|
||
|
||
string $pythonCmd = "import os, sys\n";
|
||
$pythonCmd += "INSTALL_PATH = r'" + $pythonPath + "'\n";
|
||
$pythonCmd += "sys.path.append(os.path.dirname(INSTALL_PATH))\n";
|
||
$pythonCmd += "import Install\n";
|
||
$pythonCmd += "Install.main()\n";
|
||
|
||
python($pythonCmd);
|
||
}
|
||
|
||
install();
|
||
```
|
||
|
||
├── Install.py
|
||
|
||
```Python
|
||
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
#===================================== 1. Module Imports =====================================
|
||
# Standard library imports
|
||
import os
|
||
import sys
|
||
import webbrowser
|
||
|
||
# Maya imports
|
||
import maya.mel as mel
|
||
import maya.cmds as cmds
|
||
import maya.OpenMayaUI as omui
|
||
|
||
# Qt imports
|
||
from PySide2 import QtWidgets, QtGui, QtCore
|
||
from shiboken2 import wrapInstance
|
||
|
||
# Custom imports
|
||
from config import data
|
||
QtCore, QtGui, QtWidgets = data.Qt()
|
||
|
||
#===================================== 2. Global Variables =====================================
|
||
ROOT_PATH = data.ROOT_PATH
|
||
TOOL_NAME = data.TOOL_NAME
|
||
TOOL_VERSION = data.TOOL_VERSION
|
||
TOOL_AUTHOR = data.TOOL_AUTHOR
|
||
TOOL_LANG = data.TOOL_LANG
|
||
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
|
||
TOOL_HELP_URL = data.TOOL_HELP_URL
|
||
SCRIPTS_PATH = data.SCRIPTS_PATH
|
||
ICONS_PATH = data.ICONS_PATH
|
||
|
||
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
|
||
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
|
||
TOOL_ICON = data.TOOL_ICON
|
||
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
|
||
|
||
#===================================== 3. Utility Functions =====================================
|
||
def maya_main_window():
|
||
"""Get Maya main window as QWidget"""
|
||
main_window_ptr = omui.MQtUtil.mainWindow()
|
||
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
|
||
|
||
def ensure_directory(directory_path):
|
||
"""Ensure directory exists, create if not"""
|
||
if not os.path.exists(directory_path):
|
||
os.makedirs(directory_path)
|
||
print(f"Created directory: {directory_path}")
|
||
return directory_path
|
||
|
||
def get_maya_modules_dir():
|
||
"""Get Maya modules directory path"""
|
||
maya_app_dir = cmds.internalVar(userAppDir=True)
|
||
return ensure_directory(os.path.join(maya_app_dir, "modules"))
|
||
|
||
#===================================== 4. UI Component Classes =====================================
|
||
class SetButton(QtWidgets.QPushButton):
|
||
"""Custom styled button for installation interface"""
|
||
def __init__(self, text):
|
||
super(SetButton, self).__init__(text)
|
||
|
||
#===================================== 5. Main Window Class =====================================
|
||
class InstallDialog(QtWidgets.QDialog):
|
||
def __init__(self, parent=maya_main_window()):
|
||
super(InstallDialog, self).__init__(parent)
|
||
self.load_stylesheet()
|
||
self.setup_ui()
|
||
|
||
def load_stylesheet(self):
|
||
"""加载 QSS 样式文件"""
|
||
try:
|
||
style_file = data.TOOL_STYLE_FILE
|
||
if os.path.exists(style_file):
|
||
with open(style_file, 'r') as f:
|
||
self.setStyleSheet(f.read())
|
||
else:
|
||
print(f"Warning: Style file not found: {style_file}")
|
||
except Exception as e:
|
||
print(f"Error loading stylesheet: {e}")
|
||
|
||
def setup_ui(self):
|
||
"""Initialize and setup UI components"""
|
||
self.setWindowTitle(f"{TOOL_NAME} Installation")
|
||
self.setFixedSize(220, 120)
|
||
self.setup_window_icon()
|
||
self.create_widgets()
|
||
self.create_layouts()
|
||
self.create_connections()
|
||
|
||
def setup_window_icon(self):
|
||
"""Setup window icon if available"""
|
||
if os.path.exists(TOOL_ICON):
|
||
self.setWindowIcon(QtGui.QIcon(TOOL_ICON))
|
||
else:
|
||
print(f"Warning: Icon file not found: {TOOL_ICON}")
|
||
|
||
#----------------- 5.1 UI Methods -----------------
|
||
def create_widgets(self):
|
||
self.new_shelf_toggle = QtWidgets.QCheckBox(f"{TOOL_NAME} Installation")
|
||
self.install_button = SetButton("Install " + TOOL_NAME)
|
||
self.uninstall_button = SetButton("Uninstall " + TOOL_NAME)
|
||
|
||
def create_layouts(self):
|
||
main_layout = QtWidgets.QVBoxLayout(self)
|
||
main_layout.setContentsMargins(10, 2, 10, 5)
|
||
main_layout.setSpacing(5)
|
||
|
||
header_layout = QtWidgets.QHBoxLayout()
|
||
header_layout.setSpacing(5)
|
||
|
||
welcome_label = QtWidgets.QLabel("Welcome to " + TOOL_NAME + "!")
|
||
welcome_label.setStyleSheet("font-size: 11px; padding: 0px; margin: 0px;")
|
||
header_layout.addWidget(welcome_label)
|
||
header_layout.addStretch()
|
||
|
||
main_layout.addLayout(header_layout)
|
||
main_layout.addWidget(self.install_button)
|
||
main_layout.addWidget(self.uninstall_button)
|
||
|
||
self.install_button.setFixedHeight(30)
|
||
self.uninstall_button.setFixedHeight(30)
|
||
|
||
def create_connections(self):
|
||
self.install_button.clicked.connect(self.install)
|
||
self.uninstall_button.clicked.connect(self.uninstall)
|
||
|
||
def create_styled_message_box(self, title, text):
|
||
msg_box = QtWidgets.QMessageBox(self)
|
||
msg_box.setWindowTitle(title)
|
||
msg_box.setText(text)
|
||
msg_box.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
|
||
msg_box.setStyleSheet(self.styleSheet())
|
||
return msg_box
|
||
|
||
#----------------- 5.2 Event Handler Methods -----------------
|
||
def event(self, event):
|
||
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
|
||
QtWidgets.QWhatsThis.leaveWhatsThisMode()
|
||
self.open_help_url()
|
||
return True
|
||
return QtWidgets.QDialog.event(self, event)
|
||
|
||
def closeEvent(self, event):
|
||
"""Handle window close event"""
|
||
try:
|
||
super(InstallDialog, self).closeEvent(event)
|
||
except Exception as e:
|
||
print(f"Error closing window: {e}")
|
||
event.accept()
|
||
|
||
def helpEvent(self, event):
|
||
self.open_help_url()
|
||
event.accept()
|
||
|
||
#----------------- 5.3 Utility Methods -----------------
|
||
def open_help_url(self):
|
||
webbrowser.open(TOOL_HELP_URL)
|
||
QtWidgets.QApplication.restoreOverrideCursor()
|
||
|
||
def get_script_path(self):
|
||
maya_script = mel.eval('getenv("MAYA_SCRIPT_NAME")')
|
||
if maya_script and os.path.exists(maya_script):
|
||
return os.path.dirname(maya_script)
|
||
|
||
for sys_path in sys.path:
|
||
install_path = os.path.join(sys_path, "install.py")
|
||
if os.path.exists(install_path):
|
||
return os.path.dirname(install_path)
|
||
|
||
return os.getcwd()
|
||
|
||
#----------------- 5.4 Installation Methods -----------------
|
||
def install(self):
|
||
"""Handle install request with error handling"""
|
||
if not self._validate_paths():
|
||
return
|
||
|
||
msg_box = self.create_styled_message_box(
|
||
"Confirm Installation",
|
||
f"Are you sure you want to install {TOOL_NAME}?"
|
||
)
|
||
if msg_box.exec_() == QtWidgets.QMessageBox.Yes:
|
||
try:
|
||
self.install_tool()
|
||
self.close()
|
||
except Exception as e:
|
||
error_msg = f"Error during installation: {e}"
|
||
print(error_msg)
|
||
QtWidgets.QMessageBox.critical(self, "Error", error_msg)
|
||
|
||
def uninstall(self, *args):
|
||
"""Handle uninstall request"""
|
||
msg_box = self.create_styled_message_box(
|
||
"Confirm Uninstallation",
|
||
f"Are you sure you want to uninstall {TOOL_NAME}?"
|
||
)
|
||
|
||
if msg_box.exec_() == QtWidgets.QMessageBox.Yes:
|
||
try:
|
||
self.uninstall_tool()
|
||
self.close()
|
||
except Exception as e:
|
||
error_msg = f"Error during uninstallation: {e}"
|
||
print(error_msg)
|
||
QtWidgets.QMessageBox.critical(self, "Error", error_msg)
|
||
else:
|
||
print("Uninstallation cancelled")
|
||
|
||
def create_mod_file(self):
|
||
"""Create or update the .mod file for Maya"""
|
||
modules_dir = get_maya_modules_dir()
|
||
mod_content = f"""+ {TOOL_NAME} {TOOL_VERSION} {ROOT_PATH}
|
||
scripts: {SCRIPTS_PATH}
|
||
"""
|
||
mod_file_path = os.path.join(modules_dir, TOOL_MOD_FILENAME)
|
||
self._write_mod_file(mod_file_path, mod_content)
|
||
|
||
def _write_mod_file(self, file_path, content):
|
||
"""Helper method to write .mod file"""
|
||
try:
|
||
with open(file_path, "w") as f:
|
||
f.write(content)
|
||
print(f"Successfully created/updated: {file_path}")
|
||
except Exception as e:
|
||
error_msg = f"Error writing .mod file: {e}"
|
||
print(error_msg)
|
||
QtWidgets.QMessageBox.critical(self, "Error", error_msg)
|
||
|
||
def uninstall_mod_file(self):
|
||
modules_dir = get_maya_modules_dir()
|
||
mod_file_path = os.path.join(modules_dir, TOOL_MOD_FILENAME)
|
||
if os.path.exists(mod_file_path):
|
||
try:
|
||
os.remove(mod_file_path)
|
||
print(f"{TOOL_NAME}.mod file deleted")
|
||
except Exception as e:
|
||
print(f"Error deleting {TOOL_NAME}.mod file: {e}")
|
||
|
||
def clean_existing_buttons(self):
|
||
if cmds.shelfLayout(TOOL_NAME, exists=True):
|
||
buttons = cmds.shelfLayout(TOOL_NAME, query=True, childArray=True) or []
|
||
for btn in buttons:
|
||
if cmds.shelfButton(btn, query=True, exists=True):
|
||
label = cmds.shelfButton(btn, query=True, label=True)
|
||
if label == TOOL_NAME:
|
||
cmds.deleteUI(btn)
|
||
print(f"Deleted existing {TOOL_NAME} button: {btn}")
|
||
|
||
def install_tool(self):
|
||
"""Install the tool to Maya"""
|
||
if not os.path.exists(SCRIPTS_PATH):
|
||
print(f"Error: Scripts path does not exist: {SCRIPTS_PATH}")
|
||
return
|
||
|
||
if not os.path.exists(TOOL_MAIN_SCRIPT):
|
||
print(f"Error: Main script file not found: {TOOL_MAIN_SCRIPT}")
|
||
return
|
||
|
||
# Add scripts path to Python path
|
||
if SCRIPTS_PATH not in sys.path:
|
||
sys.path.insert(0, SCRIPTS_PATH)
|
||
|
||
# Create shelf and button
|
||
self._create_shelf_button()
|
||
self.create_mod_file()
|
||
|
||
# Switch to the newly created shelf
|
||
try:
|
||
cmds.shelfTabLayout("ShelfLayout", edit=True, selectTab=TOOL_NAME)
|
||
print(f"Switched to {TOOL_NAME} shelf")
|
||
except Exception as e:
|
||
print(f"Error switching to {TOOL_NAME} shelf: {e}")
|
||
|
||
self._show_install_success_message()
|
||
|
||
def _create_shelf_button(self):
|
||
"""Create shelf button for the tool"""
|
||
shelf_layout = mel.eval('$tmpVar=$gShelfTopLevel')
|
||
|
||
# Create shelf if not exists
|
||
if not cmds.shelfLayout(TOOL_NAME, exists=True):
|
||
cmds.shelfLayout(TOOL_NAME, parent=shelf_layout)
|
||
|
||
# Clean existing buttons
|
||
self.clean_existing_buttons()
|
||
|
||
# Create new button
|
||
icon_path = TOOL_ICON if os.path.exists(TOOL_ICON) else TOOL_COMMAND_ICON
|
||
|
||
command = self._get_shelf_button_command()
|
||
|
||
cmds.shelfButton(
|
||
parent=TOOL_NAME,
|
||
image1=icon_path,
|
||
label=TOOL_NAME,
|
||
command=command,
|
||
sourceType="python",
|
||
annotation=f"{TOOL_NAME} {TOOL_VERSION}",
|
||
noDefaultPopup=True,
|
||
style="iconOnly"
|
||
)
|
||
|
||
def _get_shelf_button_command(self):
|
||
"""Get the command string for shelf button"""
|
||
return f"""
|
||
import sys
|
||
import os
|
||
SCRIPTS_PATH = r'{SCRIPTS_PATH}'
|
||
if SCRIPTS_PATH not in sys.path:
|
||
sys.path.insert(0, SCRIPTS_PATH)
|
||
os.chdir(SCRIPTS_PATH)
|
||
try:
|
||
import {TOOL_NAME}
|
||
{TOOL_NAME}.show()
|
||
except ImportError as e:
|
||
print("Error importing {TOOL_NAME}:", str(e))
|
||
print("Scripts path:", SCRIPTS_PATH)
|
||
print("sys.path:", sys.path)
|
||
print("Contents of Scripts folder:", os.listdir(SCRIPTS_PATH))
|
||
"""
|
||
|
||
def uninstall_tool(self):
|
||
"""Uninstall the tool from Maya"""
|
||
window_name = f"{TOOL_NAME}Window"
|
||
dock_name = f"{TOOL_NAME}WindowDock"
|
||
shelf_file = f"shelf_{TOOL_NAME}.mel"
|
||
|
||
if cmds.window(window_name, exists=True):
|
||
try:
|
||
cmds.deleteUI(window_name)
|
||
except Exception as e:
|
||
print(f"Error closing {TOOL_NAME} window: {e}")
|
||
|
||
if cmds.dockControl(dock_name, exists=True):
|
||
try:
|
||
cmds.deleteUI(dock_name)
|
||
except Exception as e:
|
||
print(f"Error closing docked {TOOL_NAME} window: {e}")
|
||
|
||
self.uninstall_mod_file()
|
||
|
||
# Get the current shelf before removing it
|
||
current_shelf = cmds.shelfTabLayout("ShelfLayout", query=True, selectTab=True)
|
||
|
||
# Delete Shelves and Buttons
|
||
if cmds.shelfLayout(TOOL_NAME, exists=True):
|
||
try:
|
||
cmds.deleteUI(TOOL_NAME, layout=True)
|
||
except Exception as e:
|
||
print(f"Error deleting {TOOL_NAME} shelf: {e}")
|
||
|
||
self._clean_all_shelf_buttons()
|
||
|
||
# Remove from Python path
|
||
if SCRIPTS_PATH in sys.path:
|
||
sys.path.remove(SCRIPTS_PATH)
|
||
|
||
# Deleting Shelf Files
|
||
shelf_path = os.path.join(
|
||
cmds.internalVar(userAppDir=True),
|
||
cmds.about(version=True),
|
||
"prefs",
|
||
"shelves",
|
||
f"shelf_{TOOL_NAME}.mel"
|
||
)
|
||
|
||
if os.path.exists(shelf_path):
|
||
try:
|
||
os.remove(shelf_path)
|
||
except Exception as e:
|
||
print(f"Error deleting shelf file: {e}")
|
||
|
||
# If the current tool shelf is a deleted tool shelf, switch to another tool shelf
|
||
if current_shelf == TOOL_NAME:
|
||
shelves = cmds.shelfTabLayout("ShelfLayout", query=True, childArray=True)
|
||
if shelves and len(shelves) > 0:
|
||
cmds.shelfTabLayout("ShelfLayout", edit=True, selectTab=shelves[0])
|
||
|
||
self._show_uninstall_success_message()
|
||
|
||
def _clean_all_shelf_buttons(self):
|
||
"""Clean up all shelf buttons related to the tool"""
|
||
all_shelves = cmds.shelfTabLayout("ShelfLayout", query=True, childArray=True) or []
|
||
for shelf in all_shelves:
|
||
shelf_buttons = cmds.shelfLayout(shelf, query=True, childArray=True) or []
|
||
for btn in shelf_buttons:
|
||
if cmds.shelfButton(btn, query=True, exists=True):
|
||
if cmds.shelfButton(btn, query=True, label=True) == TOOL_NAME:
|
||
cmds.deleteUI(btn)
|
||
|
||
def _show_uninstall_success_message(self):
|
||
"""Show uninstallation success message"""
|
||
msg_box = QtWidgets.QMessageBox()
|
||
msg_box.setWindowTitle("Uninstallation Successful")
|
||
msg_box.setText(f"{TOOL_NAME} has been successfully uninstalled!")
|
||
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||
msg_box.setWindowIcon(QtGui.QIcon(TOOL_ICON))
|
||
msg_box.setStyleSheet(self.styleSheet())
|
||
msg_box.exec_()
|
||
|
||
def _show_install_success_message(self):
|
||
msg_box = QtWidgets.QMessageBox()
|
||
msg_box.setWindowTitle("Installation Successful")
|
||
msg_box.setText(f"{TOOL_NAME} has been successfully installed!")
|
||
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||
msg_box.setWindowIcon(QtGui.QIcon(TOOL_ICON))
|
||
msg_box.setStyleSheet(self.styleSheet())
|
||
msg_box.exec_()
|
||
|
||
def _validate_paths(self):
|
||
"""Validate all required paths exist"""
|
||
paths = {
|
||
"Root": ROOT_PATH,
|
||
"Scripts": SCRIPTS_PATH,
|
||
"Icons": ICONS_PATH
|
||
}
|
||
|
||
for name, path in paths.items():
|
||
if not os.path.exists(path):
|
||
error_msg = f"Error: {name} path does not exist: {path}"
|
||
print(error_msg)
|
||
QtWidgets.QMessageBox.critical(self, "Error", error_msg)
|
||
return False
|
||
return True
|
||
|
||
def _log(self, message, error=False):
|
||
"""Log messages with timestamp"""
|
||
from datetime import datetime
|
||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
log_message = f"[{timestamp}] {message}"
|
||
print(log_message)
|
||
if error:
|
||
QtWidgets.QMessageBox.critical(self, "Error", message)
|
||
|
||
def _load_mel_shelf(self):
|
||
"""Load mel shelf file with error handling"""
|
||
try:
|
||
mel.eval(f'loadNewShelf "shelf_{TOOL_NAME}.mel"')
|
||
except Exception as e:
|
||
self._log(f"Error loading shelf file: {e}", error=True)
|
||
|
||
#===================================== 6. Main Function =====================================
|
||
def main():
|
||
"""Main entry point for the installer"""
|
||
try:
|
||
dialog = InstallDialog()
|
||
dialog.show()
|
||
except Exception as e:
|
||
print(f"Error launching installer: {e}")
|
||
return -1
|
||
return dialog
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
│ ├── styles
|
||
|
||
│ │ ├── style.qss
|
||
|
||
```Python
|
||
/* 全局 QPushButton 样式 */
|
||
QPushButton {
|
||
background-color: #D0D0D0;
|
||
color: #303030;
|
||
border-radius: 10px;
|
||
padding: 5px;
|
||
font-weight: bold;
|
||
min-width: 80px;
|
||
}
|
||
|
||
QPushButton:hover {
|
||
background-color: #E0E0E0;
|
||
}
|
||
|
||
QPushButton:pressed {
|
||
background-color: #C0C0C0;
|
||
}
|
||
|
||
/* 单独的消息按钮样式(可选) */
|
||
.messageButton {
|
||
background-color: #B0B0B0;
|
||
color: #303030;
|
||
border-radius: 10px;
|
||
padding: 5px;
|
||
font-weight: bold;
|
||
min-width: 80px;
|
||
}
|
||
|
||
.messageButton:hover {
|
||
background-color: #C0C0C0;
|
||
}
|
||
|
||
.messageButton:pressed {
|
||
background-color: #A0A0A0;
|
||
}
|
||
|
||
/* MetaFusion 深色主题样式 */
|
||
|
||
/* 主窗口样式 */
|
||
QMainWindow {
|
||
background-color: #333333;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
/* 菜单栏样式 */
|
||
QMenuBar {
|
||
background-color: #333333;
|
||
color: #CCCCCC;
|
||
border-bottom: 1px solid #222222;
|
||
}
|
||
|
||
QMenuBar::item {
|
||
background-color: transparent;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
QMenuBar::item:selected {
|
||
background-color: #444444;
|
||
}
|
||
|
||
/* 工具栏样式 */
|
||
QToolBar {
|
||
background-color: #333333;
|
||
border: none;
|
||
padding: 3px;
|
||
}
|
||
|
||
QToolButton {
|
||
background-color: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: 2px;
|
||
padding: 4px;
|
||
margin: 1px;
|
||
}
|
||
|
||
QToolButton:hover {
|
||
background-color: #444444;
|
||
border: 1px solid #555555;
|
||
}
|
||
|
||
/* 标签页样式 */
|
||
QTabWidget::pane {
|
||
border: 1px solid #222222;
|
||
background-color: #333333;
|
||
}
|
||
|
||
QTabBar::tab {
|
||
background-color: #2A2A2A;
|
||
color: #CCCCCC;
|
||
padding: 5px 10px;
|
||
border: 1px solid #222222;
|
||
min-width: 80px;
|
||
}
|
||
|
||
QTabBar::tab:selected {
|
||
background-color: #333333;
|
||
border-bottom: none;
|
||
}
|
||
|
||
QTabBar::tab:hover:not(:selected) {
|
||
background-color: #3A3A3A;
|
||
}
|
||
|
||
/* 列表和树形控件样式 */
|
||
QTreeView, QListView {
|
||
background-color: #2A2A2A;
|
||
border: 1px solid #222222;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
QTreeView::item:hover, QListView::item:hover {
|
||
background-color: #3A3A3A;
|
||
}
|
||
|
||
QTreeView::item:selected, QListView::item:selected {
|
||
background-color: #444444;
|
||
}
|
||
|
||
/* 输入框样式 */
|
||
QLineEdit {
|
||
background-color: #2A2A2A;
|
||
border: 1px solid #222222;
|
||
border-radius: 2px;
|
||
color: #CCCCCC;
|
||
padding: 3px;
|
||
}
|
||
|
||
/* 下拉框样式 */
|
||
QComboBox {
|
||
background-color: #2A2A2A;
|
||
border: 1px solid #222222;
|
||
border-radius: 2px;
|
||
color: #CCCCCC;
|
||
padding: 3px;
|
||
min-width: 100px;
|
||
}
|
||
|
||
QComboBox::drop-down {
|
||
border: none;
|
||
width: 20px;
|
||
}
|
||
|
||
QComboBox::down-arrow {
|
||
border-image: url(:/resources/icons/down_arrow.png);
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
/* 按钮样式 */
|
||
QPushButton {
|
||
background-color: #2A2A2A;
|
||
border: 1px solid #222222;
|
||
border-radius: 2px;
|
||
color: #CCCCCC;
|
||
padding: 5px 15px;
|
||
min-width: 80px;
|
||
}
|
||
|
||
QPushButton:hover {
|
||
background-color: #3A3A3A;
|
||
border: 1px solid #444444;
|
||
}
|
||
|
||
QPushButton:pressed {
|
||
background-color: #222222;
|
||
}
|
||
|
||
/* 滚动条样式 */
|
||
QScrollBar:vertical {
|
||
background: #2A2A2A;
|
||
width: 10px;
|
||
margin: 0;
|
||
}
|
||
|
||
QScrollBar::handle:vertical {
|
||
background: #444444;
|
||
min-height: 20px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||
height: 0px;
|
||
}
|
||
|
||
QScrollBar:horizontal {
|
||
background: #2A2A2A;
|
||
height: 10px;
|
||
margin: 0;
|
||
}
|
||
|
||
QScrollBar::handle:horizontal {
|
||
background: #444444;
|
||
min-width: 20px;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
|
||
width: 0px;
|
||
}
|
||
|
||
/* 分组框样式 */
|
||
QGroupBox {
|
||
border: 1px solid #222222;
|
||
border-radius: 3px;
|
||
margin-top: 6px;
|
||
padding-top: 6px;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
QGroupBox::title {
|
||
left: 7px;
|
||
padding: 0px 3px;
|
||
}
|
||
|
||
/* 状态栏样式 */
|
||
QStatusBar {
|
||
background-color: #333333;
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
/* 工具提示样式 */
|
||
QToolTip {
|
||
background-color: #2A2A2A;
|
||
border: 1px solid #222222;
|
||
color: #CCCCCC;
|
||
padding: 3px;
|
||
}
|
||
|
||
```
|
||
|
||
├── config
|
||
|
||
│ └── \_\_init\_\_.py
|
||
|
||
```Python
|
||
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
from . import *
|
||
```
|
||
|
||
│ └── data.py
|
||
|
||
```Python
|
||
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import os
|
||
import sys
|
||
import maya.cmds as cmds
|
||
|
||
#===================================== 2. Global Variables =====================================
|
||
try:
|
||
ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/")
|
||
except NameError:
|
||
# __file__ 在 config 中,所以返回上一级目录即项目根目录
|
||
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
|
||
TOOL_NAME = "MetaFusion"
|
||
TOOL_VERSION = "Beta v1.0.0"
|
||
TOOL_AUTHOR = "Virtuos"
|
||
TOOL_LANG = 'en_US'
|
||
TOOL_WSCL_NAME = f"{TOOL_NAME}WorkSpaceControl"
|
||
TOOL_HELP_URL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki"
|
||
|
||
#===================================== 3. Paths =====================================
|
||
# PATHS
|
||
SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/")
|
||
ICONS_PATH = os.path.join(ROOT_PATH, "resources", "icons").replace("\\", "/")
|
||
STYLES_PATH = os.path.join(ROOT_PATH, "resources", "styles").replace("\\", "/")
|
||
|
||
MAYA_VERSION = cmds.about(version=True)
|
||
SYSTEM_OS = cmds.about(os=True)
|
||
if MAYA_VERSION in ["2022", "2023", "2024", "2025"]:
|
||
PLUGIN_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, MAYA_VERSION).replace("\\", "/")
|
||
else:
|
||
print(f"MetaFusion is not supported on Maya {MAYA_VERSION}")
|
||
|
||
PYTHON_VERSION = sys.version_info.major
|
||
if PYTHON_VERSION == 3:
|
||
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python3").replace("\\", "/")
|
||
elif PYTHON_VERSION == 3.11:
|
||
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python311").replace("\\", "/")
|
||
elif PYTHON_VERSION == 3.9:
|
||
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python397").replace("\\", "/")
|
||
elif PYTHON_VERSION == 3.10:
|
||
PYDNA_PATH = os.path.join(ROOT_PATH, "plugins", SYSTEM_OS, "python3108").replace("\\", "/")
|
||
else:
|
||
print(f"MetaFusion is not supported on Python {PYTHON_VERSION}")
|
||
|
||
|
||
#===================================== 3. Files =====================================
|
||
# FILES
|
||
TOOL_MAIN_SCRIPT = os.path.join(SCRIPTS_PATH, f"{TOOL_NAME}.py").replace("\\", "/")
|
||
TOOL_STYLE_FILE = os.path.join(STYLES_PATH, "style.qss").replace("\\", "/")
|
||
TOOL_ICON = os.path.join(ICONS_PATH, f"{TOOL_NAME}Logo.png").replace("\\", "/")
|
||
TOOL_COMMAND_ICON = os.path.join(ICONS_PATH, "CommandButton.png").replace("\\", "/")
|
||
TOOL_MOD_FILENAME = f"{TOOL_NAME}.mod"
|
||
|
||
#===================================== 4. Qt =====================================
|
||
# Qt
|
||
def Qt():
|
||
try:
|
||
from PySide import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
try:
|
||
from PySide2 import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
try:
|
||
from PySide3 import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
try:
|
||
from PySide4 import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
try:
|
||
from PySide5 import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
try:
|
||
from PySide6 import QtCore, QtGui, QtWidgets
|
||
return QtCore, QtGui, QtWidgets
|
||
except ImportError:
|
||
print("未找到 Qt 模块")
|
||
return None, None, None
|
||
```
|
||
|
||
├── scripts
|
||
|
||
│ ├── MetaFusion.py
|
||
|
||
```Python
|
||
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import os
|
||
import sys
|
||
from PySide2 import QtCore, QtGui, QtWidgets
|
||
|
||
# 添加项目根目录到 Python 路径
|
||
ROOT_DIR = os.path.dirname(os.path.dirname(__file__))
|
||
if ROOT_DIR not in sys.path:
|
||
sys.path.insert(0, ROOT_DIR)
|
||
from config import data
|
||
QtCore, QtGui, QtWidgets = data.Qt()
|
||
|
||
#===================================== 2. Global Variables =====================================
|
||
TOOL_NAME = data.TOOL_NAME
|
||
TOOL_VERSION = data.TOOL_VERSION
|
||
TOOL_AUTHOR = data.TOOL_AUTHOR
|
||
TOOL_LANG = data.TOOL_LANG
|
||
TOOL_WSCL_NAME = data.TOOL_WSCL_NAME
|
||
TOOL_HELP_URL = data.TOOL_HELP_URL
|
||
SCRIPTS_PATH = data.SCRIPTS_PATH
|
||
ICONS_PATH = data.ICONS_PATH
|
||
|
||
TOOL_MAIN_SCRIPT = data.TOOL_MAIN_SCRIPT
|
||
TOOL_MOD_FILENAME = data.TOOL_MOD_FILENAME
|
||
TOOL_ICON = data.TOOL_ICON
|
||
TOOL_COMMAND_ICON = data.TOOL_COMMAND_ICON
|
||
|
||
main_window = None
|
||
|
||
class MetaFusionWindow(QtWidgets.QMainWindow):
|
||
def __init__(self, parent=None):
|
||
super(MetaFusionWindow, self).__init__(parent)
|
||
self.setWindowTitle("MetaFusion")
|
||
self.resize(800, 600)
|
||
|
||
# 加载样式表
|
||
self.load_stylesheet()
|
||
|
||
# 创建UI
|
||
self.setup_ui()
|
||
|
||
def load_stylesheet(self):
|
||
"""加载QSS样式表"""
|
||
style_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "styles", "style.qss")
|
||
if os.path.exists(style_file):
|
||
with open(style_file, 'r', encoding='utf-8') as f:
|
||
self.setStyleSheet(f.read())
|
||
|
||
def setup_ui(self):
|
||
"""设置UI结构"""
|
||
# 创建中心部件
|
||
central_widget = QtWidgets.QWidget()
|
||
self.setCentralWidget(central_widget)
|
||
|
||
# 创建主布局
|
||
main_layout = QtWidgets.QVBoxLayout(central_widget)
|
||
|
||
# 创建菜单栏
|
||
self.create_menu_bar()
|
||
|
||
# 创建工具栏
|
||
self.create_tool_bar()
|
||
|
||
# 创建标签页
|
||
self.create_tabs()
|
||
|
||
def create_menu_bar(self):
|
||
"""创建菜单栏"""
|
||
menubar = self.menuBar()
|
||
|
||
# 文件菜单
|
||
file_menu = menubar.addMenu("文件")
|
||
file_menu.addAction("新建")
|
||
file_menu.addAction("打开")
|
||
file_menu.addAction("保存")
|
||
file_menu.addSeparator()
|
||
file_menu.addAction("退出")
|
||
|
||
# 编辑菜单
|
||
edit_menu = menubar.addMenu("编辑")
|
||
edit_menu.addAction("撤销")
|
||
edit_menu.addAction("重做")
|
||
|
||
# 帮助菜单
|
||
help_menu = menubar.addMenu("帮助")
|
||
help_menu.addAction("关于")
|
||
|
||
def create_tool_bar(self):
|
||
"""创建工具栏"""
|
||
toolbar = self.addToolBar("主工具栏")
|
||
toolbar.setMovable(False)
|
||
|
||
# 添加工具栏按钮
|
||
toolbar.addAction(QtGui.QIcon(":/icons/new.png"), "新建")
|
||
toolbar.addAction(QtGui.QIcon(":/icons/open.png"), "打开")
|
||
toolbar.addAction(QtGui.QIcon(":/icons/save.png"), "保存")
|
||
|
||
def create_tabs(self):
|
||
"""创建标签页"""
|
||
self.tab_widget = QtWidgets.QTabWidget()
|
||
self.centralWidget().layout().addWidget(self.tab_widget)
|
||
|
||
# 创建四个主要标签页
|
||
self.model_tab = QtWidgets.QWidget()
|
||
self.rig_tab = QtWidgets.QWidget()
|
||
self.adjust_tab = QtWidgets.QWidget()
|
||
self.define_tab = QtWidgets.QWidget()
|
||
|
||
# 添加标签页到标签页控件
|
||
self.tab_widget.addTab(self.model_tab, "模型")
|
||
self.tab_widget.addTab(self.rig_tab, "绑定")
|
||
self.tab_widget.addTab(self.adjust_tab, "调整")
|
||
self.tab_widget.addTab(self.define_tab, "定义")
|
||
|
||
# 设置各个标签页的内容
|
||
self.setup_model_tab()
|
||
self.setup_rig_tab()
|
||
self.setup_adjust_tab()
|
||
self.setup_define_tab()
|
||
|
||
def setup_model_tab(self):
|
||
"""设置模型标签页内容"""
|
||
layout = QtWidgets.QVBoxLayout(self.model_tab)
|
||
# 在这里添加模型标签页的具体控件
|
||
|
||
def setup_rig_tab(self):
|
||
"""设置绑定标签页内容"""
|
||
layout = QtWidgets.QVBoxLayout(self.rig_tab)
|
||
# 在这里添加绑定标签页的具体控件
|
||
|
||
def setup_adjust_tab(self):
|
||
"""设置调整标签页内容"""
|
||
layout = QtWidgets.QVBoxLayout(self.adjust_tab)
|
||
# 在这里添加调整标签页的具体控件
|
||
|
||
def setup_define_tab(self):
|
||
"""设置定义标签页内容"""
|
||
layout = QtWidgets.QVBoxLayout(self.define_tab)
|
||
# 在这里添加定义标签页的具体控件
|
||
|
||
def show():
|
||
"""显示主窗口"""
|
||
global main_window
|
||
|
||
try:
|
||
main_window.close()
|
||
except:
|
||
pass
|
||
|
||
main_window = MetaFusionWindow()
|
||
main_window.show()
|
||
return main_window
|
||
|
||
if __name__ == "__main__":
|
||
app = QtWidgets.QApplication([])
|
||
window = show()
|
||
app.exec_()
|
||
``` |