This commit is contained in:
Jeffreytsai1004 2025-01-11 16:53:48 +08:00
parent 2134bba30a
commit 069f7929ef
37 changed files with 1477 additions and 340 deletions

18
Install.mel Normal file
View File

@ -0,0 +1,18 @@
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();

500
Install.py Normal file
View File

@ -0,0 +1,500 @@
#!/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
#===================================== 2. Global Variables =====================================
try:
ROOT_PATH = os.path.dirname(INSTALL_PATH).replace("\\", "/")
except NameError:
ROOT_PATH = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
TOOL_NAME = "MetaFusion"
TOOL_VERSION = "Beta v1.0.0"
TOOL_AUTHOR = "Virtuos"
TOOL_LANG = 'en_US'
SCRIPTS_PATH = os.path.join(ROOT_PATH, "scripts").replace("\\", "/")
ICONS_PATH = os.path.join(ROOT_PATH, "icons").replace("\\", "/")
TOOL_ICON = os.path.join(ICONS_PATH, "logo.png").replace("\\", "/")
DEFAULT_ICON = "commandButton.png"
TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki"
TOOL_WSCL_NAME = "MetaFusionWorkSpaceControl"
MOD_FILE_NAME = f"{TOOL_NAME}.mod"
MAIN_SCRIPT_NAME = f"{TOOL_NAME}.py"
# UI Style configurations
BUTTON_STYLE = """
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;
}
"""
MESSAGE_BUTTON_STYLE = """
QPushButton {
background-color: #B0B0B0;
color: #303030;
border-radius: 10px;
padding: 5px;
font-weight: bold;
min-width: 80px;
}
QPushButton:hover {
background-color: #C0C0C0;
}
QPushButton:pressed {
background-color: #A0A0A0;
}
"""
#===================================== 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)
self.setStyleSheet(BUTTON_STYLE)
#===================================== 5. Main Window Class =====================================
class InstallDialog(QtWidgets.QDialog):
def __init__(self, parent=maya_main_window()):
super(InstallDialog, self).__init__(parent)
self.setup_ui()
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)
button_style = """
QPushButton {
background-color: #B0B0B0;
color: #303030;
border-radius: 10px;
padding: 5px;
font-weight: bold;
min-width: 80px;
}
QPushButton:hover {
background-color: #C0C0C0;
}
QPushButton:pressed {
background-color: #A0A0A0;
}
"""
for button in msg_box.buttons():
button.setStyleSheet(button_style)
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, MOD_FILE_NAME)
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, MOD_FILE_NAME)
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
main_script = os.path.join(SCRIPTS_PATH, MAIN_SCRIPT_NAME)
if not os.path.exists(main_script):
print(f"Error: Main script file not found: {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 DEFAULT_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)
for button in msg_box.buttons():
button.setStyleSheet(MESSAGE_BUTTON_STYLE)
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)
for button in msg_box.buttons():
button.setStyleSheet(MESSAGE_BUTTON_STYLE)
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()

48
MetaFusion.mod Normal file
View File

@ -0,0 +1,48 @@
+ MAYAVERSION:2018 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2018
MAYA_PLUG_IN_PATH +:= plugins/2018
+ MAYAVERSION:2019 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2019
MAYA_PLUG_IN_PATH +:= plugins/2019
+ MAYAVERSION:2020 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2020
MAYA_PLUG_IN_PATH +:= plugins/2020
+ MAYAVERSION:2021 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2021
MAYA_PLUG_IN_PATH +:= plugins/2021
+ MAYAVERSION:2022 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2022
MAYA_PLUG_IN_PATH +:= plugins/2022
+ MAYAVERSION:2023 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2023
MAYA_PLUG_IN_PATH +:= plugins/2023
+ MAYAVERSION:2024 PLATFORM:win64 MetaFusion any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= scripts
PYTHONPATH +:= plugins/2024
MAYA_PLUG_IN_PATH +:= plugins/2024

View File

@ -1 +1,15 @@
# MetaHuman Custome Tool For Maya # Metahuman Customized Tool
Metahuman Customized Toolbag for Maya
# Tool Name
MetaFusion
## VERSION
beta 1.0.0
## MAYA VERSION
2023

BIN
data/img/Preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,35 +0,0 @@
+ MAYAVERSION:2022 PLATFORM:win64 MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2022/windows
MAYA_PLUG_IN_PATH +:= lib/Maya2022/windows
+ MAYAVERSION:2022 PLATFORM:linux MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2022/linux
MAYA_PLUG_IN_PATH +:= lib/Maya2022/linux
+ MAYAVERSION:2023 PLATFORM:win64 MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2023/windows
MAYA_PLUG_IN_PATH +:= lib/Maya2023/windows
+ MAYAVERSION:2023 PLATFORM:linux MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2023/linux
MAYA_PLUG_IN_PATH +:= lib/Maya2023/linux
+ MAYAVERSION:2024 PLATFORM:win64 MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2024/windows
MAYA_PLUG_IN_PATH +:= lib/Maya2024/windows
+ MAYAVERSION:2024 PLATFORM:linux MetaHuman-DNA-Calibration any .
PYTHONPATH +:=
PYTHONPATH +:= data
PYTHONPATH +:= lib/Maya2024/linux
MAYA_PLUG_IN_PATH +:= lib/Maya2024/linux

View File

@ -1,304 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PySide2 import QtWidgets, QtCore, QtGui
from shiboken2 import wrapInstance
from maya import OpenMayaUI as omui
import maya.cmds as cmds
import maya.mel as mel
import maya.utils as utils
import importlib
import traceback
import subprocess
import webbrowser
import locale
import sys
import os
# 全局变量声明
TOOL_PATH = os.path.dirname(os.path.abspath(__file__)).replace('\\', '/')
TOOL_ICON = os.path.join(TOOL_PATH, "icons", "logo.png")
if not os.path.exists(TOOL_ICON):
print(f"Warning: Icon file not found at {TOOL_ICON}")
TOOL_ICON = "" # 设置为空字符串而不是 None
MAYA_VERSION = cmds.about(version=True)
TOOL_NAME = "MetaFusion"
TOOL_VERSION = "v1.0.0"
TOOL_AUTHOR = "CGNICO"
CURRENT_LANG = "en_US"
WKSP_CTRL = TOOL_NAME
DNA_PATH = ""
WKSP_PATH = "MetaFusion"
HelpURL = f"https://gitea.cgnico.com/CGNICO/{TOOL_NAME}/wiki"
#==================================================== GLOBAL FUNCTIONS ====================================================
class SetButton(QtWidgets.QPushButton):
"""
Custom rounded button class
Features:
- Rounded design
- Custom color and hover effect
- Bold text
"""
def __init__(self, text="", icon=None, color="#D0D0D0", hover_color="#E0E0E0", pressed_color="#C0C0C0"):
super(SetButton, self).__init__(text)
if icon:
self.setIcon(icon)
self.setIconSize(QtCore.QSize(24, 24))
self.setMinimumHeight(30) # Set minimum height
self.setStyleSheet(
f"""
QPushButton {{
background-color: {color};
color: #303030;
border-radius: 10px;
padding: 5px;
font-weight: bold;
text-align: center;
}}
QPushButton:hover {{
background-color: {hover_color};
}}
QPushButton:pressed {{
background-color: {pressed_color};
}}
"""
)
def get_system_encoding():
encoding = sys.getdefaultencoding()
if encoding.lower() == 'ascii':
encoding = locale.getpreferredencoding()
return encoding
def maya_main_window():
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
LANG = {
'en_US': {
"Group 01": "Group 01",
"Group 02": "Group 02",
"Button 01": "Button 01",
"Button 02": "Button 02",
"DOCUMENT": "DOCUMENT",
"Help": "Help",
"Switch Language": "Switch Language"
},
'zh_CN': {
"Group 01": "组 01",
"Group 02": "组 02",
"Button 01": "按钮 01",
"Button 02": "按钮 02",
"DOCUMENT": "文档",
"Help": "帮助",
"Switch Language": "切换语言"
}
}
#==================================================== MAIN WINDOW CLASS ====================================================
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle(TOOL_NAME)
self.setObjectName(TOOL_NAME)
self.setWindowFlags(QtCore.Qt.Window)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
self.setMinimumSize(300, 800)
self.create_widgets()
self.create_layouts()
self.create_connections()
if os.path.exists(TOOL_ICON):
self.setWindowIcon(QtGui.QIcon(TOOL_ICON))
else:
print(f"WARNING: Icon file not found: {TOOL_ICON}")
#==================================================== UI COMPONENTS ====================================================
def create_widgets(self):
self.group_01 = QtWidgets.QGroupBox(LANG[CURRENT_LANG]["Group 01"])
self.group_01_button = SetButton(LANG[CURRENT_LANG]["Button 01"], color="#A7C6ED", hover_color="#B2D3F0", pressed_color="#8BB8E0")
self.group_02 = QtWidgets.QGroupBox(LANG[CURRENT_LANG]["Group 02"])
self.group_02_button = SetButton(LANG[CURRENT_LANG]["Button 02"], color="#FFCCBC", hover_color="#FFAB91", pressed_color="#FF8A65")
self.help_btn = QtWidgets.QPushButton(LANG[CURRENT_LANG]["DOCUMENT"])
self.help_btn.setToolTip(LANG[CURRENT_LANG]["Help"])
self.help_btn.setFixedSize(100, 20)
self.help_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
color: gray;
font-weight: bold;
}
QPushButton:hover {
color: black;
}
""")
self.lang_btn = QtWidgets.QPushButton("EN" if CURRENT_LANG == 'zh_CN' else "ZH")
self.lang_btn.setToolTip(LANG[CURRENT_LANG]["Switch Language"])
self.lang_btn.setFixedSize(30, 20)
self.lang_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
color: gray;
font-weight: bold;
}
QPushButton:hover {
color: black;
}
""")
def create_layouts(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(2, 2, 2, 2)
main_layout.addStretch()
main_layout.addWidget(self.group_01)
main_layout.addWidget(self.group_01_button)
main_layout.addWidget(self.group_02)
main_layout.addWidget(self.group_02_button)
main_layout.addStretch()
# Bottom layout
bottom_layout = QtWidgets.QHBoxLayout()
icon_label = QtWidgets.QLabel()
if TOOL_ICON and os.path.exists(TOOL_ICON):
icon = QtGui.QPixmap(TOOL_ICON).scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
icon_label.setPixmap(icon)
bottom_layout.addWidget(icon_label)
version_label = QtWidgets.QLabel(f"{TOOL_VERSION}")
version_label.setStyleSheet("color: gray; font-size: 12px;")
bottom_layout.addWidget(version_label)
bottom_layout.addStretch()
bottom_layout.addWidget(self.help_btn)
bottom_layout.addWidget(self.lang_btn)
main_layout.addLayout(bottom_layout)
def create_connections(self):
self.group_01_button.clicked.connect(self.run_group_01)
self.group_02_button.clicked.connect(self.run_group_02)
self.help_btn.clicked.connect(self.show_help)
self.lang_btn.clicked.connect(self.switch_language)
def run_group_01(self):
print("Button 01")
def run_group_02(self):
print("Button 02")
def dock_to_maya(self):
if not WKSP_CTRL:
print("Error: WKSP_CTRL is not defined")
return
if cmds.workspaceControl(WKSP_CTRL, exists=True):
cmds.deleteUI(WKSP_CTRL)
def create_control():
try:
if not self.objectName():
self.setObjectName(TOOL_NAME)
workspace_control = cmds.workspaceControl(
WKSP_CTRL,
label=TOOL_NAME,
floating=True,
retain=False,
resizeWidth=True,
initialWidth=300,
minimumWidth=300
)
if not workspace_control:
raise RuntimeError("Failed to create workspace control")
cmds.workspaceControl(WKSP_CTRL, e=True, resizeWidth=True)
try:
cmds.control(self.objectName(), e=True, p=workspace_control)
except Exception as e:
print(f"Error parenting control: {e}")
return
except Exception as e:
print(f"Error creating workspace control: {e}")
traceback.print_exc()
cmds.evalDeferred(create_control)
def show_help(self):
# Specify the URL of the website you want to open
webbrowser.open(HelpURL)
def switch_language(self):
global CURRENT_LANG
CURRENT_LANG = 'en_US' if CURRENT_LANG == 'zh_CN' else 'zh_CN'
self.lang_btn.setText("EN" if CURRENT_LANG == 'zh_CN' else "CN")
self.retranslate_ui()
QtWidgets.QToolTip.showText(
self.lang_btn.mapToGlobal(QtCore.QPoint(0, -30)),
"Language switched" if CURRENT_LANG == 'en_US' else "语言已切换",
self.lang_btn
)
def show():
global main_window
try:
current_width = 300
if cmds.workspaceControl(WKSP_CTRL, exists=True):
try:
current_width = cmds.workspaceControl(WKSP_CTRL, q=True, width=True)
except:
pass
cmds.deleteUI(WKSP_CTRL, control=True)
if 'main_window' in globals() and main_window:
try:
main_window.close()
main_window.deleteLater()
except:
pass
except Exception as e:
print(f"Error cleaning up previous window: {e}")
def create_ui(retry_count=0, width=300):
global main_window
try:
if not WKSP_CTRL:
raise ValueError("WKSP_CTRL is not defined")
main_window = MainWindow()
if not main_window:
raise RuntimeError("Failed to create main window")
# 使用 try-except 包装每个 evalDeferred 调用
def deferred_actions():
try:
main_window.dock_to_maya()
cmds.workspaceControl(WKSP_CTRL, e=True, width=width)
cmds.workspaceControl(WKSP_CTRL, e=True, resizeWidth=True)
except Exception as e:
print(f"Error in deferred actions: {e}")
cmds.evalDeferred(deferred_actions)
except Exception as e:
if retry_count < 3:
print(f"{TOOL_NAME} creation failed, retrying... (Attempt {retry_count + 1})")
print(f"Error: {str(e)}")
utils.executeDeferred(lambda: create_ui(retry_count + 1, width))
else:
print(f"Failed to create {TOOL_NAME} after 3 attempts:")
traceback.print_exc()
utils.executeDeferred(lambda: create_ui(width=current_width))
if __name__ == "__main__":
show()

148
scripts/BatchImport.py Normal file
View File

@ -0,0 +1,148 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import maya.cmds as cmds
import sys
def onMayaDroppedPythonFile(*args):
run()
#Let's you batch import:
def run(*args):
if CheckForBonusTools:
multipleFilters = 'All native importable files (*.3ds *.abc *.ass *.at *.catpart *.dae *.fbx *.igs *.iges *.jt *.ma *.mb *.obj *.prt *.sat *.step *.stp *.wire);; Maya binary (*.mb);; Maya Ascii (*.ma);; WIRE_ATF (*.wire);; Obj (*.obj);; FBX (*.fbx);; DAE_FBX (*.dae);; Alembic Cache (*.abc);; Atom (*.atom);; Step (*.stp *.step);; IGES_ATF (*.igs *.iges);; ASS (*.ass);; 3DS Max (*.3ds);; CATIAV5_ATF (*.catpart);; JT_ATF (*.jt);; SAT_ATF (*.sat);; NX_ATF (*.prt)'
else:
multipleFilters = 'All native importable files (*.abc *.ass *.at *.catpart *.dae *.fbx *.igs *.iges *.jt *.ma *.mb *.obj *.prt *.sat *.step *.stp *.wire);; Maya binary (*.mb);; Maya Ascii (*.ma);; WIRE_ATF (*.wire);; Obj (*.obj);; FBX (*.fbx);; DAE_FBX (*.dae);; Alembic Cache (*.abc);; Atom (*.atom);; Step (*.stp *.step);; IGES_ATF (*.igs *.iges);; ASS (*.ass);; 3DS Max (*.3ds);; CATIAV5_ATF (*.catpart);; JT_ATF (*.jt);; SAT_ATF (*.sat);; NX_ATF (*.prt)'
files = cmds.fileDialog2(caption = 'Choose files to import', ds = 2, fileMode = 4, okCaption = 'Import', fileFilter = multipleFilters, hideNameEdit = False)
# Add checks to ensure files are valid
if not files or not isinstance(files, list) or len(files) == 0:
cmds.warning('No file was selected and the import operation was canceled.')
return # Return directly to avoid subsequent code execution
for x in files:
if any(y in x for y in ['.ma', '.MA']):
fileType = 'mayaAscii'
options = ''
ImportFiles(fileType, x, options)
if any(y in x for y in ['.mb', '.MB']):
fileType = 'mayaBinary'
options = ''
ImportFiles(fileType, x, options)
if any(y in x for y in ['.wire', '.WIRE']):
fileType = 'WIRE_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in ['.obj', '.OBJ']):
fileType = 'OBJ'
options = 'mo=0'
ImportFiles(fileType, x, options)
if any(y in x for y in ['.fbx', '.FBX']):
fileType = 'FBX'
options = ''
LoadPlugin('fbxmaya')
ImportFiles(fileType, x, options)
if any(y in x for y in ['.dae', '.DAE']):
fileType = 'DAE_FBX'
options = ''
ImportFiles(fileType, x, options)
if any(y in x for y in ['.abc', '.ABC']):
fileType = 'Alembic'
options = ''
LoadPlugin('AbcImport')
ImportFiles(fileType, x, options)
if any(y in x for y in ['.atom', '.ATOM', '.at', '.AT']):
fileType = 'atomImport'
options = ''
LoadPlugin('atomImportExport')
ImportFiles(fileType, x, options)
if any(y in x for y in ['.step', '.STEP', '.stp', '.STP']):
fileType = 'STEP_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in['igs.', '.IGS', '.iges', '.IGES']):
fileType = 'IGES_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in['.ass', '.ASS']):
fileType = 'ASS'
options = ''
LoadPlugin('mtoa')
ImportFiles(fileType, x, options)
if any(y in x for y in['.3ds', '.3DS']):
fileType = '3ds'
options = ''
if CheckForBonusTools:
LoadPlugin('3ds')
ImportFiles(fileType, x, options)
if any(y in x for y in['.catpart', '.CATPART']):
fileType = 'CATIAV5_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in['.jt', '.JT']):
fileType = 'JT_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in['.sat', '.SAT']):
fileType = 'SAT_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
if any(y in x for y in['.prt', '.PRT']):
fileType = 'NX_ATF'
options = ''
LoadPlugin('ATFPlugin')
ImportFiles(fileType, x, options)
def CheckForBonusTools(*args):
for x in sys.path:
if 'MayaBonusTools' in x:
return True
return False
def LoadPlugin(plugin, *args):
if not cmds.pluginInfo(plugin, query = True, loaded = True):
try:
cmds.loadPlugin(plugin)
sys.stdout.write('Plugin "' + plugin + '" loaded.\n')
except(RuntimeError):
cmds.warning('Could not find "' + plugin + '" plugin or could not load it. Open the Plugin Manager and make sure Maya recognized the plugin and try again.\n')
def ImportFiles(fileType, file, options, *args):
namespace = file.split('/')
namespace = namespace[-1].split('.')
namespace = namespace[0]
try:
cmds.file(str(file), i = True, type = fileType, ignoreVersion = True, mergeNamespacesOnClash = False, namespace = namespace, options = options)
sys.stdout.write('Imported "' + str(file) + '".\n')
except(UnicodeEncodeError):
sys.stdout.write('Either the directory path or the file name have some special characters.\n')
sys.stdout.write('The names will be changed.\n')
cmds.file(file, i = True, type = fileType, ignoreVersion = True, mergeNamespacesOnClash = False, namespace = namespace, options = options)
sys.stdout.write('Imported "' + file + '".\n')
except(ImportError):
cmds.warning('Could not import ' + file + '. Maybe you dont have the requiered permissions to the folder.\n')
if __name__ == '__main__':
if not cmds.about(batch = True):
run()

37
scripts/BodyPrep.py Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
用于准备身体文件去除了body_grp的锁定清理不需要的节点并重命名并存储为body_drv.mb文件用于后续加载
"""
import maya.cmds as cmds
def run():
cmds.upAxis(axis='y')
cmds.delete("DHIhead:spine_04", "Lights", "export_geo_GRP", "head_grp")
cmds.rename("rig", "body_rig")
cmds.rename("geometry_grp", "body_geometry_grp")
body_grp_lock = cmds.listRelatives('body_grp', allDescendents=True, type='transform')
for obj in body_grp_lock:
cmds.setAttr(obj + '.translateX', lock=False)
cmds.setAttr(obj + '.translateY', lock=False)
cmds.setAttr(obj + '.translateZ', lock=False)
cmds.setAttr(obj + '.rotateX', lock=False)
cmds.setAttr(obj + '.rotateY', lock=False)
cmds.setAttr(obj + '.rotateZ', lock=False)
cmds.setAttr(obj + '.scaleX', lock=False)
cmds.setAttr(obj + '.scaleY', lock=False)
cmds.setAttr(obj + '.scaleZ', lock=False)
correctiveCube = cmds.polyCube()[0]
cmds.parent('root_drv', correctiveCube)
cmds.rotate(-90,0,0,correctiveCube,relative=True)
cmds.parent('root_drv',world=True)
cmds.delete(correctiveCube)
save_path = cmds.fileDialog2(fileMode=0, caption="Save Maya Scene", fileFilter="Maya Binary (*.mb)")[0]
cmds.file(rename=save_path)
cmds.file(save=True, type='mayaBinary')
if __name__ == '__main__':
if not cmds.about(batch = True):
run()

237
scripts/DNA_Browser.py Normal file
View File

@ -0,0 +1,237 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PySide2 import QtWidgets, QtCore, QtGui
import maya.cmds as cmds
import os
class DNABrowserWidget(QtWidgets.QWidget):
dna_selected = QtCore.Signal(str) # 信号当DNA被选中时发出
def __init__(self, dna_path, img_path, parent=None):
super().__init__(parent)
self.dna_path = dna_path
self.img_path = img_path
self.setup_ui()
self.scan_dna_files()
self.update_grid()
def setup_ui(self):
# 创建主布局
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
# 创建流式布局容器
self.flow_widget = QtWidgets.QWidget()
self.flow_layout = FlowLayout(self.flow_widget)
self.flow_layout.setSpacing(5)
# 创建滚动区域
self.scroll_area = QtWidgets.QScrollArea()
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setWidget(self.flow_widget)
self.scroll_area.setStyleSheet("""
QScrollArea {
border: none;
background-color: transparent;
}
QScrollBar:vertical {
border: none;
background: #F0F0F0;
width: 8px;
margin: 0px;
}
QScrollBar::handle:vertical {
background: #CCCCCC;
border-radius: 4px;
min-height: 20px;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
}
""")
self.main_layout.addWidget(self.scroll_area)
def scan_dna_files(self):
"""扫描DNA文件夹并建立索引"""
self.dna_files = {}
if not os.path.exists(self.dna_path):
cmds.warning(f"DNA path not found: {self.dna_path}")
return
if not os.path.exists(self.img_path):
cmds.warning(f"Image path not found: {self.img_path}")
return
for file in os.listdir(self.dna_path):
if file.endswith('.dna'):
name = os.path.splitext(file)[0]
dna_file = os.path.join(self.dna_path, file).replace("\\", "/")
# 直接在img目录下查找图片
img_file = None
for ext in ['.jpg', '.png', '.jpeg']:
img_path = os.path.join(self.img_path, f"{name}{ext}").replace("\\", "/")
if os.path.exists(img_path):
img_file = img_path
break
self.dna_files[name] = {
'dna_path': dna_file,
'img_path': img_file
}
# 打印调试信息
print(f"DNA file: {name}")
print(f" DNA path: {dna_file}")
print(f" Image path: {img_file}")
print(f" Image exists: {bool(img_file and os.path.exists(img_file))}")
def update_grid(self):
"""更新DNA网格"""
# 清除现有按钮
for i in reversed(range(self.flow_layout.count())):
self.flow_layout.itemAt(i).widget().deleteLater()
# 计算按钮大小 - 减小到原来的1/4左右
container_width = self.flow_widget.width() or 300
button_width = (container_width - 60) // 6 # 每行6个按钮
button_height = int(button_width * 1.2) # 保持宽高比
# 创建DNA样本按钮
for name, info in self.dna_files.items():
dna_btn = self.create_dna_button(name, info, button_width, button_height)
self.flow_layout.addWidget(dna_btn)
def create_dna_button(self, name, info, width, height):
"""创建DNA按钮"""
btn = QtWidgets.QPushButton()
btn.setFixedSize(width, height)
# 创建按钮布局
layout = QtWidgets.QVBoxLayout(btn)
layout.setContentsMargins(2, 2, 2, 2)
layout.setSpacing(1)
# 创建图标标签
icon_label = QtWidgets.QLabel()
icon_label.setAlignment(QtCore.Qt.AlignCenter)
if info['img_path']:
pixmap = QtGui.QPixmap(info['img_path'])
scaled_pixmap = pixmap.scaled(
width - 4,
height - 16,
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation
)
icon_label.setPixmap(scaled_pixmap)
else:
icon_label.setText("No Image")
icon_label.setStyleSheet("color: #FFFFFF; font-size: 8px;") # 改为白色
# 创建文本标签
text_label = QtWidgets.QLabel(name)
text_label.setAlignment(QtCore.Qt.AlignCenter)
text_label.setStyleSheet("color: #FFFFFF; font-size: 8px;") # 改为白色
layout.addWidget(icon_label)
layout.addWidget(text_label)
# 设置样式 - 保持黑色背景,文字改为白色
btn.setStyleSheet("""
QPushButton {
background-color: #303030;
border: 1px solid #202020;
border-radius: 5px;
padding: 2px;
color: #FFFFFF; /* 按钮文字颜色改为白色 */
}
QPushButton:hover {
background-color: #404040;
border: 1px solid #303030;
}
QPushButton:pressed {
background-color: #202020;
}
""")
btn.setProperty('dna_path', info['dna_path'])
btn.clicked.connect(lambda: self.on_dna_selected(info['dna_path']))
return btn
def on_dna_selected(self, dna_path):
"""当DNA被选中时发出信号"""
self.dna_selected.emit(dna_path)
class FlowLayout(QtWidgets.QLayout):
def __init__(self, parent=None):
super().__init__(parent)
self.itemList = []
self.spacing_x = 5
self.spacing_y = 5
def addItem(self, item):
self.itemList.append(item)
def count(self):
return len(self.itemList)
def itemAt(self, index):
if 0 <= index < len(self.itemList):
return self.itemList[index]
return None
def takeAt(self, index):
if 0 <= index < len(self.itemList):
return self.itemList.pop(index)
return None
def expandingDirections(self):
return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0))
def hasHeightForWidth(self):
return True
def heightForWidth(self, width):
height = self.doLayout(QtCore.QRect(0, 0, width, 0), True)
return height
def setGeometry(self, rect):
super().setGeometry(rect)
self.doLayout(rect, False)
def sizeHint(self):
return self.minimumSize()
def minimumSize(self):
size = QtCore.QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
return size
def doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
lineHeight = 0
for item in self.itemList:
widget = item.widget()
spaceX = self.spacing_x
spaceY = self.spacing_y
nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x()
y = y + lineHeight + spaceY
nextX = x + item.sizeHint().width() + spaceX
lineHeight = 0
if not testOnly:
item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineHeight = max(lineHeight, item.sizeHint().height())
return y + lineHeight - rect.y()
def create_browser(dna_path, img_path, parent=None):
"""创建并返回DNA浏览器实例"""
return DNABrowserWidget(dna_path, img_path, parent)

474
scripts/MetaFusion.py Normal file
View File

@ -0,0 +1,474 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#===================================== IMPORTS =====================================
# Standard library imports
import os
import sys
import webbrowser
import locale
# Qt imports
from PySide2 import QtWidgets, QtCore, QtGui
from shiboken2 import wrapInstance
# Maya imports
from maya import OpenMayaUI as omui
import maya.cmds as cmds
import maya.mel as mel
#===================================== IMPORT MODULES =====================================
# Standard library imports
import BodyPrep
import BatchImport
import DNA_Viewer
import DNA_Browser
from DNA_Browser import FlowLayout
#===================================== CONSTANTS =====================================
# Tool info
TOOL_NAME = "MetaFusion"
TOOL_VERSION = "Beta v1.0.0"
TOOL_AUTHOR = "Virtuos"
# UI Constants
TOOL_WSCL_NAME = "MetaFusionWorkSpaceControl"
TOOL_HELP_URL = f"http://10.72.61.59:3000/ArtGroup/{TOOL_NAME}/wiki"
DEFAULT_WINDOW_SIZE = (500, 800)
# Paths
TOOL_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__))).replace("\\", "/")
SCRIPTS_PATH = os.path.join(TOOL_PATH, "scripts").replace("\\", "/")
ICONS_PATH = os.path.join(TOOL_PATH, "icons").replace("\\", "/")
TOOL_ICON = os.path.join(ICONS_PATH, "logo.png").replace("\\", "/")
# Metahuman paths
DATA_PATH = os.path.join(TOOL_PATH, "data").replace("\\", "/")
DNA_PATH = os.path.join(DATA_PATH, "dna").replace("\\", "/")
BODY_PATH = os.path.join(DATA_PATH, "body").replace("\\", "/")
IMG_PATH = os.path.join(DATA_PATH, "img").replace("\\", "/")
MAP_PATH = os.path.join(DATA_PATH, "map").replace("\\", "/")
MASKS_PATH = os.path.join(DATA_PATH, "masks").replace("\\", "/")
SHADERS_PATH = os.path.join(DATA_PATH, "shaders").replace("\\", "/")
MH4_PATH = os.path.join(DATA_PATH, "mh4").replace("\\", "/")
MH4_DNA_PATH = os.path.join(MH4_PATH, "dna").replace("\\", "/")
OUT_PATH = os.path.join(DATA_PATH, "out").replace("\\", "/")
SAVE_PATH = os.path.join(DATA_PATH, "save").replace("\\", "/")
MAYA_VERSION = cmds.about(version=True)
PLUGIN_PATH = os.path.join(TOOL_PATH, "plugins", f"{MAYA_VERSION}").replace("\\", "/")
if not os.path.exists(PLUGIN_PATH):
cmds.warning(f"Plugin path not found: {PLUGIN_PATH}")
# 打印上面的所有变量
print(f"TOOL_PATH: {TOOL_PATH}")
print(f"SCRIPTS_PATH: {SCRIPTS_PATH}")
print(f"ICONS_PATH: {ICONS_PATH}")
print(f"TOOL_ICON: {TOOL_ICON}")
print(f"DATA_PATH: {DATA_PATH}")
print(f"DNA_PATH: {DNA_PATH}")
print(f"BODY_PATH: {BODY_PATH}")
print(f"IMG_PATH: {IMG_PATH}")
print(f"MAP_PATH: {MAP_PATH}")
print(f"MASKS_PATH: {MASKS_PATH}")
print(f"SHADERS_PATH: {SHADERS_PATH}")
#===================================== LANGUAGE SETTINGS =====================================
TOOL_LANG = 'en_US'
SUPPORTED_LANGUAGES = ['en_US', 'zh_CN']
LANG = {
"en_US": {
"MetaFusion": "MetaFusion",
"Load DNA": "Load DNA",
"Prepare Body": "Prepare Body",
"Help": "Help",
"Switch Language": "Switch Language",
"EN": "EN",
"ZH": "ZH",
"English": "English",
"Chinese": "Chinese",
"Language switched": "Language switched",
"DNA Samples": "DNA Samples",
"Prepare": "Prepare",
"Import": "Import",
"Body Prepare": "Body Prepare",
"Batch Import": "Batch Import",
"DNA Edit": "DNA Edit",
"Open DNA Viewer": "Open DNA Viewer",
"Load DNA": "Load DNA",
"DNA File:": "DNA File:"
},
"zh_CN": {
"MetaFusion": "MetaFusion",
"DNA Samples": "DNA 样本",
"Load DNA": "加载 DNA",
"Prepare Body": "准备身体",
"Help": "帮助",
"Switch Language": "切换语言",
"EN": "英文",
"ZH": "中文",
"English": "英语",
"Chinese": "中文",
"Language switched": "语言已切换",
"DNA Samples": "DNA 样本",
"Prepare": "准备",
"Import": "导入",
"Body Prepare": "身体准备",
"Batch Import": "批量导入",
"DNA Edit": "DNA 编辑",
"Open DNA Viewer": "打开 DNA 查看器",
"Load DNA": "加载 DNA",
"DNA File:": "DNA 文件:"
}
}
#===================================== UTILITY FUNCTIONS =====================================
def get_system_encoding():
encoding = sys.getdefaultencoding()
if encoding.lower() == 'ascii':
encoding = locale.getpreferredencoding()
return encoding
def maya_main_window():
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)
#===================================== UI COMPONENTS =====================================
class MainButton(QtWidgets.QPushButton):
DEFAULT_COLORS = {
"normal": "#D0D0D0",
"hover": "#E0E0E0",
"pressed": "#C0C0C0"
}
def __init__(self, text="", icon=None, color=None, hover_color=None, pressed_color=None):
super().__init__(text)
if icon:
self.setIcon(icon)
self.setIconSize(QtCore.QSize(24, 24))
self.setMinimumHeight(30)
colors = {
"normal": color or self.DEFAULT_COLORS["normal"],
"hover": hover_color or self.DEFAULT_COLORS["hover"],
"pressed": pressed_color or self.DEFAULT_COLORS["pressed"]
}
self.setStyleSheet(self._generate_style_sheet(**colors))
@staticmethod
def _generate_style_sheet(normal, hover, pressed):
return f"""
QPushButton {{
background-color: {normal};
color: #303030;
border-radius: 10px;
padding: 5px;
font-weight: bold;
text-align: center;
}}
QPushButton:hover {{
background-color: {hover};
}}
QPushButton:pressed {{
background-color: {pressed};
}}
"""
class BottomButton(QtWidgets.QPushButton):
def __init__(self, text="", icon=None):
super().__init__(text)
self.setMinimumHeight(20)
self.setStyleSheet(self._generate_style_sheet())
self.setFont(QtGui.QFont("Microsoft YaHei", 10))
@staticmethod
def _generate_style_sheet():
return """
QPushButton {
background-color: transparent;
border: none;
color: gray;
font-weight: bold;
}
QPushButton:hover {
color: black;
}
"""
#===================================== MAIN WINDOW =====================================
class MainWindow(QtWidgets.QWidget):
instance = None
def __init__(self, parent=maya_main_window()):
super(MainWindow, self).__init__(parent)
self.setWindowTitle(f"{TOOL_NAME} - {TOOL_VERSION}")
self.setObjectName(TOOL_PATH)
self.setWindowFlags(QtCore.Qt.Window)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
self.setMinimumSize(300, 800)
self.create_widgets()
self.create_layouts()
self.create_connections()
if os.path.exists(TOOL_ICON):
self.setWindowIcon(QtGui.QIcon(TOOL_ICON))
else:
print(f"WARNING: Icon file not found: {TOOL_ICON}")
@classmethod
def show_window(cls):
try:
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
cmds.deleteUI(TOOL_WSCL_NAME, control=True)
if cls.instance is not None:
try:
cls.instance.close()
cls.instance.deleteLater()
except Exception:
pass
cls.instance = cls()
cls.instance.dock_to_maya()
return cls.instance
except Exception as e:
print(f"Error showing {TOOL_NAME} window: {e}")
return None
def dock_to_maya(self):
if cmds.workspaceControl(TOOL_WSCL_NAME, exists=True):
cmds.deleteUI(TOOL_WSCL_NAME)
try:
workspace_control = cmds.workspaceControl(
TOOL_WSCL_NAME,
label=TOOL_NAME,
floating=True,
retain=True,
resizeWidth=True,
initialWidth=500,
minimumWidth=500
)
cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True)
cmds.control(self.objectName(), e=True, p=workspace_control)
cmds.evalDeferred(lambda: cmds.workspaceControl(TOOL_WSCL_NAME, e=True, resizeWidth=True))
except Exception as e:
print(f"Error creating workspace control: {e}")
#===================================== UI COMPONENTS =====================================
def create_widgets(self):
# DNA Samples group
self.dna_browser = DNA_Browser.create_browser(DNA_PATH, IMG_PATH)
self.dna_browser.dna_selected.connect(self.on_dna_selected)
# DNA File input
self.dna_file_layout = QtWidgets.QHBoxLayout()
self.dna_file_label = QtWidgets.QLabel("DNA File:")
self.dna_file_input = QtWidgets.QLineEdit()
self.dna_file_input.setStyleSheet("""
QLineEdit {
background-color: #303030;
color: #CCCCCC;
border: 1px solid #202020;
border-radius: 3px;
padding: 2px 5px;
font-size: 10px;
}
QLineEdit:hover {
border: 1px solid #404040;
}
QLineEdit:focus {
border: 1px solid #505050;
background-color: #353535;
}
""")
self.dna_file_input.textChanged.connect(self.on_dna_file_changed)
self.dna_file_layout.addWidget(self.dna_file_label)
self.dna_file_layout.addWidget(self.dna_file_input)
self.load_dna_btn = MainButton(LANG[TOOL_LANG]["Load DNA"],
color="#E6B3B3", hover_color="#F2BFBF",
pressed_color="#D99E9E")
# Create function buttons
# Prepare group
self.prepare_btn = MainButton(LANG[TOOL_LANG]["Prepare"])
self.body_prepare_btn = MainButton(LANG[TOOL_LANG]["Body Prepare"], color="#FFEBA1", hover_color="#FFF5B3", pressed_color="#FFE68A")
# Import group
self.import_btn = MainButton(LANG[TOOL_LANG]["Import"])
self.batch_import_btn = MainButton(LANG[TOOL_LANG]["Batch Import"], color="#A7C6ED", hover_color="#B2D3F0", pressed_color="#8BB8E0")
# DNA Edit group
self.dna_edit_btn = MainButton(LANG[TOOL_LANG]["DNA Edit"])
self.dna_viewer_btn = MainButton(LANG[TOOL_LANG]["Open DNA Viewer"], color="#B8E6B3", hover_color="#C4F2BF", pressed_color="#A3D99E")
# Bottom buttons (existing code)
self.help_btn = BottomButton(LANG[TOOL_LANG]["Help"])
self.help_btn.setToolTip(LANG[TOOL_LANG]["Help"])
self.help_btn.setFixedSize(100, 20)
self.lang_btn = BottomButton(LANG[TOOL_LANG]["ZH" if TOOL_LANG == 'en_US' else "EN"])
self.lang_btn.setToolTip(LANG[TOOL_LANG]["Switch Language"])
self.lang_btn.setFixedSize(30, 20)
for button in [self.help_btn, self.lang_btn]:
button.setFont(QtGui.QFont("Microsoft YaHei", 10))
def create_layouts(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(2, 2, 2, 2)
# Content layout
content_layout = QtWidgets.QVBoxLayout()
content_layout.setContentsMargins(5, 5, 5, 5)
# DNA Samples group
dna_samples_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["DNA Samples"])
dna_samples_layout = QtWidgets.QVBoxLayout(dna_samples_group)
dna_samples_layout.addWidget(self.dna_browser)
dna_samples_layout.addLayout(self.dna_file_layout)
dna_samples_layout.addWidget(self.load_dna_btn)
content_layout.addWidget(dna_samples_group)
# Prepare group
prepare_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["Prepare"])
prepare_layout = QtWidgets.QVBoxLayout(prepare_group)
prepare_layout.addWidget(self.body_prepare_btn)
content_layout.addWidget(prepare_group)
# Import group
import_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["Import"])
import_layout = QtWidgets.QVBoxLayout(import_group)
import_layout.addWidget(self.batch_import_btn)
content_layout.addWidget(import_group)
# DNA Edit group
dna_edit_group = QtWidgets.QGroupBox(LANG[TOOL_LANG]["DNA Edit"])
dna_edit_layout = QtWidgets.QVBoxLayout(dna_edit_group)
dna_edit_layout.addWidget(self.dna_viewer_btn)
content_layout.addWidget(dna_edit_group)
main_layout.addLayout(content_layout)
main_layout.addStretch()
# Bottom layout (existing code)
bottom_layout = QtWidgets.QHBoxLayout()
bottom_layout.setContentsMargins(5, 0, 5, 5)
icon_label = QtWidgets.QLabel()
if os.path.exists(TOOL_ICON):
icon = QtGui.QPixmap(TOOL_ICON).scaled(24, 24, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
icon_label.setPixmap(icon)
version_label = QtWidgets.QLabel(f"{TOOL_VERSION}")
version_label.setStyleSheet("color: gray; font-size: 12px;")
bottom_layout.addWidget(icon_label)
bottom_layout.addWidget(version_label)
bottom_layout.addStretch()
bottom_layout.addWidget(self.help_btn)
bottom_layout.addWidget(self.lang_btn)
main_layout.addLayout(bottom_layout)
def create_connections(self):
# Connect function buttons
self.body_prepare_btn.clicked.connect(self.run_body_prepare)
self.batch_import_btn.clicked.connect(self.run_batch_import)
self.dna_viewer_btn.clicked.connect(self.run_dna_viewer)
# Connect DNA Samples buttons
self.load_dna_btn.clicked.connect(self.run_load_dna)
# Existing connections
self.help_btn.clicked.connect(self.help)
self.lang_btn.clicked.connect(self.switch_language)
#===================================== FUNCTIONS =====================================
#===================================== MAIN FUNCTIONS =====================================
# Prepare group
def run_body_prepare(self):
import BodyPrep
BodyPrep.run()
# Import group
def run_batch_import(self):
import BatchImport
BatchImport.run()
# DNA Edit group
def run_dna_viewer(self):
import DNA_Viewer
DNA_Viewer.show()
# DNA Samples group
def run_load_dna(self):
"""加载选中的DNA文件"""
if hasattr(self, 'dna_list') and self.dna_list.currentItem():
import DNA_Viewer
DNA_Viewer.load_dna(DNA_File)
else:
cmds.warning("Please select a DNA file first")
#===================================== BOTTOM LAYOUT =====================================
def help(self):
webbrowser.open(TOOL_HELP_URL)
def switch_language(self):
global TOOL_LANG
TOOL_LANG = 'en_US' if TOOL_LANG == 'zh_CN' else 'zh_CN'
self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN")
self.retranslate_ui()
QtWidgets.QToolTip.showText(
self.lang_btn.mapToGlobal(QtCore.QPoint(0, -30)),
"Language switched" if TOOL_LANG == 'en_US' else "语言已切换",
self.lang_btn
)
def retranslate_ui(self):
# Update function button translations
self.load_dna_btn.setText(LANG[TOOL_LANG]["Load DNA"])
self.body_prepare_btn.setText(LANG[TOOL_LANG]["Body Prepare"])
self.batch_import_btn.setText(LANG[TOOL_LANG]["Batch Import"])
self.dna_viewer_btn.setText(LANG[TOOL_LANG]["Open DNA Viewer"])
for button in [
self.body_prepare_btn,
self.batch_import_btn,
self.dna_viewer_btn
]:
button.setFont(QtGui.QFont("Microsoft YaHei", 10))
# Update bottom button translations
self.help_btn.setText(LANG[TOOL_LANG]["Help"])
self.lang_btn.setText("ZH" if TOOL_LANG == 'en_US' else "EN")
for button in [
self.help_btn,
self.lang_btn
]:
button.setFont(QtGui.QFont("Microsoft YaHei", 10))
self.dna_file_label.setText(LANG[TOOL_LANG]["DNA File:"])
def on_dna_selected(self, dna_path):
"""当DNA被选中时"""
global DNA_File
DNA_File = dna_path
self.dna_file_input.setText(DNA_File)
print(f"Selected DNA file: {DNA_File}")
def on_dna_file_changed(self):
"""当DNA文件输入框内容改变时"""
global DNA_File
DNA_File = self.dna_file_input.text()
print(f"DNA file path updated: {DNA_File}")
#===================================== LAUNCH FUNCTIONS =====================================
def show():
return MainWindow.show_window()