2025-02-06 04:46:41 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
#===================================== 1. Module Imports =====================================
|
|
|
|
|
import maya.cmds as cmds
|
|
|
|
|
import maya.mel as mel
|
|
|
|
|
import sys
|
2025-02-06 04:00:17 +08:00
|
|
|
|
import os
|
|
|
|
|
|
2025-02-06 04:46:41 +08:00
|
|
|
|
from scripts.ui.widgets import (BaseWidget, LODGroup, IconButton, SearchLineEdit)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
from PySide2 import QtCore, QtGui, QtWidgets
|
|
|
|
|
from shiboken2 import wrapInstance
|
|
|
|
|
print("从PySide2加载Qt和shiboken2")
|
|
|
|
|
except ImportError:
|
|
|
|
|
try:
|
|
|
|
|
from PySide6 import QtCore, QtGui, QtWidgets
|
|
|
|
|
from shiboken6 import wrapInstance
|
|
|
|
|
print("从PySide6加载Qt和shiboken6")
|
|
|
|
|
except ImportError:
|
|
|
|
|
try:
|
|
|
|
|
from PySide import QtCore, QtGui, QtWidgets
|
|
|
|
|
from shiboken import wrapInstance
|
|
|
|
|
print("从PySide加载Qt和shiboken")
|
|
|
|
|
except ImportError as e:
|
|
|
|
|
print(f"Qt加载失败: {str(e)}")
|
|
|
|
|
QtCore = QtGui = QtWidgets = None
|
|
|
|
|
wrapInstance = None
|
|
|
|
|
|
|
|
|
|
#===================================== 2. Model Tab Class =====================================
|
2025-02-06 04:00:17 +08:00
|
|
|
|
class ModelTab(BaseWidget):
|
|
|
|
|
"""模型标签页"""
|
|
|
|
|
def __init__(self, parent=None):
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 在super()之前初始化类属性
|
|
|
|
|
self.lod_tabs = None
|
|
|
|
|
self.lod_buttons = []
|
|
|
|
|
self.source_combo = None
|
|
|
|
|
self.target_combo = None
|
|
|
|
|
# 调用父类初始化
|
2025-02-06 04:00:17 +08:00
|
|
|
|
super(ModelTab, self).__init__(parent)
|
|
|
|
|
|
|
|
|
|
def setup_ui(self):
|
2025-02-06 06:29:55 +08:00
|
|
|
|
"""初始化UI"""
|
2025-02-06 04:00:17 +08:00
|
|
|
|
layout = QtWidgets.QVBoxLayout(self)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.setSpacing(10)
|
|
|
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建标签页控件
|
|
|
|
|
self.lod_tabs = QtWidgets.QTabWidget()
|
|
|
|
|
layout.addWidget(self.lod_tabs)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 每个LOD级别的模型类型定义
|
|
|
|
|
lod_models = {
|
2025-02-06 06:43:25 +08:00
|
|
|
|
0: ["*头部", "*牙齿", "*牙龈", "*左眼", "*右眼", "*虹膜", "*睫毛", "*眼睑", "*软骨", "*身体"],
|
2025-02-06 06:29:55 +08:00
|
|
|
|
1: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"],
|
|
|
|
|
2: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"],
|
|
|
|
|
3: ["头部", "牙齿", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"],
|
|
|
|
|
4: ["头部", "牙齿", "左眼", "右眼", "虹膜"],
|
|
|
|
|
5: ["头部", "牙齿", "左眼", "右眼"],
|
|
|
|
|
6: ["头部", "牙齿", "左眼", "右眼"],
|
|
|
|
|
7: ["头部", "牙齿", "左眼", "右眼"]
|
|
|
|
|
}
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建LOD0-7标签页
|
2025-02-06 04:00:17 +08:00
|
|
|
|
for i in range(8):
|
2025-02-06 06:29:55 +08:00
|
|
|
|
tab = QtWidgets.QWidget()
|
|
|
|
|
tab_layout = QtWidgets.QVBoxLayout(tab)
|
|
|
|
|
tab_layout.setSpacing(10)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建网格布局
|
|
|
|
|
grid = QtWidgets.QGridLayout()
|
|
|
|
|
grid.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
# 获取当前LOD级别的模型类型
|
|
|
|
|
models = lod_models[i]
|
|
|
|
|
|
|
|
|
|
# 为每个模型类型创建控件
|
|
|
|
|
for idx, model in enumerate(models):
|
|
|
|
|
# 添加标签
|
|
|
|
|
label_widget = QtWidgets.QLabel(f"{model}:")
|
2025-02-06 06:43:25 +08:00
|
|
|
|
label_widget.setMinimumWidth(25)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
grid.addWidget(label_widget, idx, 0)
|
|
|
|
|
|
|
|
|
|
# 添加输入框
|
|
|
|
|
line_edit = QtWidgets.QLineEdit()
|
|
|
|
|
line_edit.setReadOnly(True)
|
|
|
|
|
line_edit.setMinimumWidth(200)
|
|
|
|
|
grid.addWidget(line_edit, idx, 1)
|
|
|
|
|
|
|
|
|
|
# 添加加载按钮
|
|
|
|
|
btn = IconButton("target.png", "加载...")
|
|
|
|
|
btn.setMinimumWidth(80)
|
|
|
|
|
if i > 0: # LOD0以外的按钮默认禁用
|
|
|
|
|
btn.setEnabled(False)
|
|
|
|
|
grid.addWidget(btn, idx, 2)
|
|
|
|
|
self.lod_buttons.append(btn)
|
|
|
|
|
|
|
|
|
|
# 连接按钮信号
|
|
|
|
|
callback_name = f"load_{model.lower()}"
|
|
|
|
|
if hasattr(self, callback_name):
|
|
|
|
|
btn.clicked.connect(getattr(self, callback_name))
|
|
|
|
|
|
|
|
|
|
# 添加删除按钮
|
|
|
|
|
delete_btn = IconButton("delete.png", "")
|
|
|
|
|
delete_btn.clicked.connect(lambda x=i: self.delete_lod(x))
|
|
|
|
|
tab_layout.addWidget(delete_btn, alignment=QtCore.Qt.AlignRight)
|
|
|
|
|
|
|
|
|
|
tab_layout.addLayout(grid)
|
|
|
|
|
tab_layout.addStretch()
|
|
|
|
|
self.lod_tabs.addTab(tab, f"LOD{i}")
|
|
|
|
|
|
|
|
|
|
# LOD功能组
|
2025-02-06 04:00:17 +08:00
|
|
|
|
lod_tools = self.create_lod_tools()
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.addWidget(lod_tools)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 模型工具组
|
2025-02-06 04:00:17 +08:00
|
|
|
|
model_tools = self.create_model_tools()
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.addWidget(model_tools)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
|
|
|
|
def create_lod_tools(self):
|
|
|
|
|
"""创建LOD功能组"""
|
|
|
|
|
group = QtWidgets.QGroupBox("LOD功能")
|
|
|
|
|
layout = QtWidgets.QHBoxLayout(group)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.setSpacing(5)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建三个按钮并设置为等宽
|
|
|
|
|
buttons = [
|
|
|
|
|
("自定义加载模型", "custom_load.png", self.load_custom_models),
|
|
|
|
|
("标准化命名", "standardize.png", self.standardize_naming),
|
|
|
|
|
("自动分组", "auto_group.png", self.auto_group)
|
|
|
|
|
]
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
for text, icon, callback in buttons:
|
|
|
|
|
btn = IconButton(icon, text)
|
|
|
|
|
btn.clicked.connect(callback)
|
|
|
|
|
btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
|
|
|
|
QtWidgets.QSizePolicy.Fixed)
|
|
|
|
|
layout.addWidget(btn)
|
|
|
|
|
|
2025-02-06 04:00:17 +08:00
|
|
|
|
return group
|
|
|
|
|
|
|
|
|
|
def create_model_tools(self):
|
|
|
|
|
"""创建模型工具组"""
|
|
|
|
|
group = QtWidgets.QGroupBox("模型工具")
|
|
|
|
|
layout = QtWidgets.QVBoxLayout(group)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.setSpacing(10)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 第一行:选择栏和创建LOD按钮
|
|
|
|
|
top_layout = QtWidgets.QHBoxLayout()
|
|
|
|
|
top_layout.setSpacing(10)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 源模型选择
|
|
|
|
|
source_layout = QtWidgets.QHBoxLayout()
|
|
|
|
|
source_label = QtWidgets.QLabel("源模型:")
|
2025-02-06 06:43:25 +08:00
|
|
|
|
source_label.setMinimumWidth(30)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
self.source_combo = QtWidgets.QComboBox()
|
|
|
|
|
self.source_combo.addItems(["选择源模型..."])
|
|
|
|
|
self.source_combo.setMinimumWidth(120)
|
|
|
|
|
source_layout.addWidget(source_label)
|
|
|
|
|
source_layout.addWidget(self.source_combo)
|
|
|
|
|
top_layout.addLayout(source_layout)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 目标LOD选择
|
|
|
|
|
target_layout = QtWidgets.QHBoxLayout()
|
|
|
|
|
target_label = QtWidgets.QLabel("目标LOD:")
|
2025-02-06 06:43:25 +08:00
|
|
|
|
target_label.setMinimumWidth(30)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
self.target_combo = QtWidgets.QComboBox()
|
|
|
|
|
self.target_combo.addItems([f"LOD{i}" for i in range(8)])
|
|
|
|
|
self.target_combo.setMinimumWidth(120)
|
|
|
|
|
target_layout.addWidget(target_label)
|
|
|
|
|
target_layout.addWidget(self.target_combo)
|
|
|
|
|
top_layout.addLayout(target_layout)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建LOD按钮
|
|
|
|
|
create_lod_btn = IconButton("create_lod.png", "创建LOD")
|
|
|
|
|
create_lod_btn.clicked.connect(self.create_lod)
|
2025-02-06 06:43:25 +08:00
|
|
|
|
create_lod_btn.setMinimumWidth(50)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
top_layout.addWidget(create_lod_btn)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.addLayout(top_layout)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 其他工具按钮,每行两个
|
2025-02-06 04:00:17 +08:00
|
|
|
|
tool_buttons = [
|
|
|
|
|
("模型分离", "polySplitVertex.png", self.split_model),
|
|
|
|
|
("生成面部配件", "supplement_meshes.png", self.generate_facial_accessories),
|
|
|
|
|
("修复法线", "repair_normals.png", self.fix_normals),
|
|
|
|
|
("修复点序", "repair_vertex_order.png", self.fix_vertex_order),
|
|
|
|
|
("修复接缝", "polyChipOff.png", self.fix_seams)
|
|
|
|
|
]
|
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
# 创建网格布局
|
|
|
|
|
grid = QtWidgets.QGridLayout()
|
|
|
|
|
grid.setSpacing(10)
|
|
|
|
|
for idx, (text, icon, callback) in enumerate(tool_buttons):
|
2025-02-06 04:00:17 +08:00
|
|
|
|
btn = IconButton(icon, text)
|
|
|
|
|
btn.clicked.connect(callback)
|
2025-02-06 06:29:55 +08:00
|
|
|
|
btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
|
|
|
|
QtWidgets.QSizePolicy.Fixed)
|
|
|
|
|
grid.addWidget(btn, idx//2, idx%2)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
layout.addLayout(grid)
|
2025-02-06 04:00:17 +08:00
|
|
|
|
return group
|
|
|
|
|
|
2025-02-06 06:29:55 +08:00
|
|
|
|
def create_lod(self):
|
|
|
|
|
"""创建LOD"""
|
|
|
|
|
# 获取选中的源模型和目标LOD
|
|
|
|
|
source_model = self.source_combo.currentText()
|
|
|
|
|
target_lod = int(self.target_combo.currentText().replace("LOD", ""))
|
|
|
|
|
|
|
|
|
|
# 启用目标LOD的所有按钮
|
|
|
|
|
for btn in self.lod_buttons:
|
|
|
|
|
if btn.parent().parent() == self.lod_tabs.widget(target_lod):
|
|
|
|
|
btn.setEnabled(True)
|
|
|
|
|
|
2025-02-06 04:00:17 +08:00
|
|
|
|
# LOD功能回调
|
|
|
|
|
def load_custom_models(self):
|
|
|
|
|
"""自定加载模型"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.load_custom_models()
|
|
|
|
|
|
|
|
|
|
def standardize_naming(self):
|
|
|
|
|
"""标准化命名"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.standardize_naming()
|
|
|
|
|
|
|
|
|
|
def auto_group(self):
|
|
|
|
|
"""自动分组"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.auto_group()
|
|
|
|
|
|
|
|
|
|
# 模型工具回调
|
|
|
|
|
def split_model(self):
|
|
|
|
|
"""分离模型"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.split_model()
|
|
|
|
|
|
|
|
|
|
def generate_facial_accessories(self):
|
|
|
|
|
"""生成面部配件"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.generate_facial_accessories()
|
|
|
|
|
|
|
|
|
|
def fix_normals(self):
|
|
|
|
|
"""修复法线"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.fix_normals()
|
|
|
|
|
|
|
|
|
|
def fix_vertex_order(self):
|
|
|
|
|
"""修复点序"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
model_utils.fix_vertex_order()
|
|
|
|
|
|
|
|
|
|
def fix_seams(self):
|
|
|
|
|
"""修复接缝"""
|
|
|
|
|
from scripts.utils import model_utils
|
2025-02-06 06:29:55 +08:00
|
|
|
|
model_utils.fix_seams()
|
|
|
|
|
|
|
|
|
|
def load_head(self, lod_index):
|
|
|
|
|
"""加载头部模型"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
file_path = self._get_file_path("头部模型")
|
|
|
|
|
if file_path:
|
|
|
|
|
model_utils.load_model(lod_index, "head", file_path)
|
|
|
|
|
self._update_input_text(lod_index, "头部", file_path)
|
|
|
|
|
|
|
|
|
|
def load_teeth(self, lod_index):
|
|
|
|
|
"""加载牙齿模型"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
file_path = self._get_file_path("牙齿模型")
|
|
|
|
|
if file_path:
|
|
|
|
|
model_utils.load_model(lod_index, "teeth", file_path)
|
|
|
|
|
self._update_input_text(lod_index, "牙齿", file_path)
|
|
|
|
|
|
|
|
|
|
def load_gums(self, lod_index):
|
|
|
|
|
"""加载牙龈模型"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
file_path = self._get_file_path("牙龈模型")
|
|
|
|
|
if file_path:
|
|
|
|
|
model_utils.load_model(lod_index, "gums", file_path)
|
|
|
|
|
self._update_input_text(lod_index, "牙龈", file_path)
|
|
|
|
|
|
|
|
|
|
def delete_lod(self, lod_index):
|
|
|
|
|
"""删除指定LOD"""
|
|
|
|
|
from scripts.utils import model_utils
|
|
|
|
|
reply = QtWidgets.QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
"删除LOD",
|
|
|
|
|
f"确定要删除LOD{lod_index}吗?",
|
|
|
|
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply == QtWidgets.QMessageBox.Yes:
|
|
|
|
|
model_utils.delete_lod(lod_index)
|
|
|
|
|
# 清空输入框
|
|
|
|
|
self._clear_lod_inputs(lod_index)
|
|
|
|
|
# 禁用按钮
|
|
|
|
|
if lod_index > 0:
|
|
|
|
|
self._disable_lod_buttons(lod_index)
|
|
|
|
|
|
|
|
|
|
def _get_file_path(self, model_type):
|
|
|
|
|
"""获取文件路径"""
|
|
|
|
|
file_path, _ = QtWidgets.QFileDialog.getOpenFileName(
|
|
|
|
|
self,
|
|
|
|
|
f"选择{model_type}",
|
|
|
|
|
"",
|
|
|
|
|
"模型文件 (*.obj *.fbx *.ma *.mb);;所有文件 (*.*)"
|
|
|
|
|
)
|
|
|
|
|
return file_path
|
|
|
|
|
|
|
|
|
|
def _update_input_text(self, lod_index, model_type, file_path):
|
|
|
|
|
"""更新输入框文本"""
|
|
|
|
|
tab = self.lod_tabs.widget(lod_index)
|
|
|
|
|
if tab:
|
|
|
|
|
grid = tab.layout().itemAt(1).layout() # 获取网格布局
|
|
|
|
|
for i in range(grid.rowCount()):
|
|
|
|
|
label = grid.itemAtPosition(i, 0).widget()
|
|
|
|
|
if label.text().startswith(model_type):
|
|
|
|
|
input_field = grid.itemAtPosition(i, 1).widget()
|
|
|
|
|
input_field.setText(os.path.basename(file_path))
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
def _clear_lod_inputs(self, lod_index):
|
|
|
|
|
"""清空LOD的所有输入框"""
|
|
|
|
|
tab = self.lod_tabs.widget(lod_index)
|
|
|
|
|
if tab:
|
|
|
|
|
grid = tab.layout().itemAt(1).layout()
|
|
|
|
|
for i in range(grid.rowCount()):
|
|
|
|
|
input_field = grid.itemAtPosition(i, 1).widget()
|
|
|
|
|
if input_field:
|
|
|
|
|
input_field.clear()
|
|
|
|
|
|
|
|
|
|
def _disable_lod_buttons(self, lod_index):
|
|
|
|
|
"""禁用LOD的所有按钮"""
|
|
|
|
|
tab = self.lod_tabs.widget(lod_index)
|
|
|
|
|
if tab:
|
|
|
|
|
grid = tab.layout().itemAt(1).layout()
|
|
|
|
|
for i in range(grid.rowCount()):
|
|
|
|
|
btn = grid.itemAtPosition(i, 2).widget()
|
|
|
|
|
if btn:
|
|
|
|
|
btn.setEnabled(False)
|