Files
MetaFusion/scripts/ui/geometry.py
2025-05-07 01:31:21 +08:00

460 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Geometry UI Module for Plugin
几何模型UI模块 - 负责显示几何模型编辑界面和基础操作
基本功能:
- 模型拾取以及加载
- LOD模型分级过滤
- LOD模型创建
- 自动加载模型
- 标准化命名
- 自动分组
- 生成面部配件(睑毛,舌头,泪腺 等)
- 修复接缝(修复法线)
- 修复点序
"""
#========================================= IMPORT =========================================
from Qt import QtWidgets, QtCore, QtGui
from Qt.QtCompat import wrapInstance
from maya import OpenMayaUI as omui
import maya.cmds as cmds
import maya.mel as mel
import maya.utils as utils
import webbrowser
import subprocess
import importlib
import traceback
import locale
import sys
import os
from scripts.ui import ui_utils
from scripts.utils import utils_geometry
#========================================== CONFIG ========================================
import config
TOOL_NAME = config.TOOL_NAME
TOOL_VERSION = config.TOOL_VERSION
TOOL_AUTHOR = config.TOOL_AUTHOR
TOOL_YEAR = config.TOOL_YEAR
TOOL_MOD_FILENAME = config.TOOL_MOD_FILENAME
TOOL_LANG = config.TOOL_LANG
TOOL_WSCL_NAME = config.TOOL_WSCL_NAME
TOOL_HELP_URL = config.TOOL_HELP_URL
TOOL_PATH = config.TOOL_PATH
SCRIPTS_PATH = config.SCRIPTS_PATH
TOOL_MAIN_SCRIPT = config.TOOL_MAIN_SCRIPT
UI_PATH = config.UI_PATH
STYLE_FILE = config.STYLE_FILE
ICONS_PATH = config.ICONS_PATH
TOOL_ICON = config.TOOL_ICON
ASSETS_PATH = config.ASSETS_PATH
DNA_FILE_PATH = config.DNA_FILE_PATH
DNA_IMG_PATH = config.DNA_IMG_PATH
TOOL_COMMAND_ICON = config.TOOL_COMMAND_ICON
TOOL_WIDTH = config.TOOL_WIDTH
TOOL_HEIGHT = config.TOOL_HEIGHT
#========================================= LOCATION =======================================
from scripts.ui import localization
LANG = localization.LANG
class GeometryUI(ui_utils.BaseUI):
"""
几何模型UI类 - 负责显示几何模型编辑界面和基础操作
继承自BaseUI类实现几何模型相关的UI功能
"""
#========================================== INIT ========================================
def __init__(self):
"""
初始化几何模型UI
创建主控件和布局,并连接信号和槽
"""
super(GeometryUI, self).__init__()
# 创建主控件
self.main_widget = QtWidgets.QWidget()
self.main_widget.setObjectName("geometryMainWidget")
# 初始化UI
self.create_widgets()
self.create_layouts()
self.create_connections()
#========================================= WIDGET =======================================
def create_widgets(self):
"""
创建几何模型UI控件
包括按钮、标签、列表等
"""
# 标题标签 - 使用HTML格式化标题
title_text = f"<h4 style='margin:0;padding:5px;'>{LANG.get('geometry_title', '几何模型')}</h4>"
self.controls["title_label"] = QtWidgets.QLabel(title_text)
self.controls["title_label"].setObjectName("geometryTitleLabel")
self.controls["title_label"].setAlignment(QtCore.Qt.AlignCenter)
self.controls["title_label"].setMaximumHeight(30) # 限制标题高度
# 创建主容器
self.controls["main_container"] = QtWidgets.QWidget()
self.controls["main_container"].setObjectName("geometryMainContainer")
# 创建标签页控件
self.controls["tab_widget"] = QtWidgets.QTabWidget()
self.controls["tab_widget"].setObjectName("geometryTabWidget")
# 创建LOD标签页
self.create_lod_tabs()
# 创建底部功能按钮区域
self.create_bottom_buttons()
# 模型列表
self.controls["model_list"] = QtWidgets.QListWidget()
self.controls["model_list"].setObjectName("modelList")
# 模型操作按钮
self.buttons["add_model"] = QtWidgets.QPushButton(LANG.get("add_model", "添加模型"))
self.buttons["add_model"].setObjectName("addModelButton")
self.buttons["remove_model"] = QtWidgets.QPushButton(LANG.get("remove_model", "移除模型"))
self.buttons["remove_model"].setObjectName("removeModelButton")
self.buttons["duplicate_model"] = QtWidgets.QPushButton(LANG.get("duplicate_model", "复制模型"))
self.buttons["duplicate_model"].setObjectName("duplicateModelButton")
# 右侧面板控件 - 模型属性
self.controls["model_properties_group"] = QtWidgets.QGroupBox(LANG.get("model_properties", "模型属性"))
self.controls["model_properties_group"].setObjectName("modelPropertiesGroup")
# 模型名称标签和输入框
self.controls["model_name_label"] = QtWidgets.QLabel(LANG.get("name", "名称:"))
self.controls["model_name_label"].setObjectName("modelNameLabel")
self.controls["model_name_input"] = QtWidgets.QLineEdit()
self.controls["model_name_input"].setObjectName("modelNameInput")
self.controls["model_name_input"].setPlaceholderText(LANG.get("enter_model_name", "输入模型名称"))
# 模型类型标签和下拉框
self.controls["model_type_label"] = QtWidgets.QLabel(LANG.get("type", "类型:"))
self.controls["model_type_label"].setObjectName("modelTypeLabel")
self.controls["model_type_combo"] = QtWidgets.QComboBox()
self.controls["model_type_combo"].setObjectName("modelTypeCombo")
self.controls["model_type_combo"].addItems(["Base", "Head", "Body", "Accessory", "Other"])
# 模型可见性复选框
self.controls["model_visible_check"] = QtWidgets.QCheckBox(LANG.get("visible", "可见"))
self.controls["model_visible_check"].setObjectName("modelVisibleCheck")
self.controls["model_visible_check"].setChecked(True)
# 模型属性按钮
self.buttons["apply_properties"] = QtWidgets.QPushButton(LANG.get("apply", "应用"))
self.buttons["apply_properties"].setObjectName("applyPropertiesButton")
self.buttons["reset_properties"] = QtWidgets.QPushButton(LANG.get("reset", "重置"))
self.buttons["reset_properties"].setObjectName("resetPropertiesButton")
# 右侧面板控件 - 模型工具
self.controls["model_tools_group"] = QtWidgets.QGroupBox(LANG.get("model_tools", "模型工具"))
self.controls["model_tools_group"].setObjectName("modelToolsGroup")
# 模型工具按钮
self.buttons["standardize_names"] = QtWidgets.QPushButton(LANG.get("standardize_names", "标准化命名"))
self.buttons["standardize_names"].setObjectName("standardizeNamesButton")
self.buttons["auto_group"] = QtWidgets.QPushButton(LANG.get("auto_group", "自动分组"))
self.buttons["auto_group"].setObjectName("autoGroupButton")
self.buttons["generate_accessories"] = QtWidgets.QPushButton(LANG.get("generate_accessories", "生成配件"))
self.buttons["generate_accessories"].setObjectName("generateAccessoriesButton")
self.buttons["fix_seams"] = QtWidgets.QPushButton(LANG.get("fix_seams", "修复接缝"))
self.buttons["fix_seams"].setObjectName("fixSeamsButton")
self.buttons["fix_vertex_order"] = QtWidgets.QPushButton(LANG.get("fix_vertex_order", "修复点序"))
self.buttons["fix_vertex_order"].setObjectName("fixVertexOrderButton")
# 底部工具面板
# 导入部分
self.controls["import_group"] = QtWidgets.QGroupBox(LANG.get("import", "导入"))
self.controls["import_group"].setObjectName("importGroup")
self.buttons["import_model"] = QtWidgets.QPushButton(LANG.get("import_model", "导入模型"))
self.buttons["import_model"].setObjectName("importModelButton")
self.buttons["import_fbx"] = QtWidgets.QPushButton(LANG.get("import_fbx", "导入FBX"))
self.buttons["import_fbx"].setObjectName("importFbxButton")
self.buttons["import_obj"] = QtWidgets.QPushButton(LANG.get("import_obj", "导入OBJ"))
self.buttons["import_obj"].setObjectName("importObjButton")
# 导出部分
self.controls["export_group"] = QtWidgets.QGroupBox(LANG.get("export", "导出"))
self.controls["export_group"].setObjectName("exportGroup")
self.buttons["export_model"] = QtWidgets.QPushButton(LANG.get("export_model", "导出模型"))
self.buttons["export_model"].setObjectName("exportModelButton")
self.buttons["export_fbx"] = QtWidgets.QPushButton(LANG.get("export_fbx", "导出 FBX"))
self.buttons["export_fbx"].setObjectName("exportFbxButton")
self.buttons["export_obj"] = QtWidgets.QPushButton(LANG.get("export_obj", "导出 OBJ"))
self.buttons["export_obj"].setObjectName("exportObjButton")
# 工具部分
self.controls["tools_group"] = QtWidgets.QGroupBox(LANG.get("tools", "工具"))
self.controls["tools_group"].setObjectName("toolsGroup")
self.buttons["check_model"] = QtWidgets.QPushButton(LANG.get("check_model", "检查模型"))
self.buttons["check_model"].setObjectName("checkModelButton")
self.buttons["optimize_model"] = QtWidgets.QPushButton(LANG.get("optimize_model", "优化模型"))
self.buttons["optimize_model"].setObjectName("optimizeModelButton")
self.buttons["clean_model"] = QtWidgets.QPushButton(LANG.get("clean_model", "清理模型"))
self.buttons["clean_model"].setObjectName("cleanModelButton")
self.buttons["uv_tools"] = QtWidgets.QPushButton(LANG.get("uv_tools", "UV工具"))
self.buttons["uv_tools"].setObjectName("uvToolsButton")
def create_lod_tabs(self):
"""
创建LOD标签页
包括LOD0~LOD7的标签页
"""
# 创建8个LOD标签页
lod_names = [f"LOD{i}" for i in range(8)]
# 创建标签页和内容
for lod_name in lod_names:
# 创建标签页
tab = QtWidgets.QWidget()
tab.setObjectName(f"{lod_name}Tab")
# 创建标签页布局
tab_layout = QtWidgets.QVBoxLayout(tab)
tab_layout.setContentsMargins(5, 5, 5, 5)
tab_layout.setSpacing(5)
# 创建身体部位输入字段
body_parts = [
"头部", "牙齿", "牙龈", "左眼", "右眼", "红脸", "眉毛", "眉毛", "躯体"
]
# 根据LOD级别限制显示的身体部位
if lod_name in ["LOD0", "LOD1"]:
parts_to_show = body_parts
elif lod_name in ["LOD2", "LOD3"]:
parts_to_show = body_parts[:8]
elif lod_name in ["LOD4", "LOD5"]:
parts_to_show = body_parts[:5]
else: # LOD6, LOD7
parts_to_show = body_parts[:4]
# 为每个身体部位创建输入字段和加载按钮
for part in parts_to_show:
# 创建水平布局
part_layout = QtWidgets.QHBoxLayout()
part_layout.setSpacing(5)
# 创建标签
label = QtWidgets.QLabel(f"{part}:")
label.setMinimumWidth(40)
label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
# 创建输入字段
input_field = QtWidgets.QLineEdit()
input_field.setObjectName(f"{lod_name}_{part}_input")
input_field.setPlaceholderText(LANG.get("enter_model_name", "输入模型名称"))
# 创建加载按钮
load_button = QtWidgets.QPushButton(LANG.get(" load ", " 加 载 "))
load_button.setObjectName(f"{lod_name}_{part}_load_button")
load_button.setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "loading.png")))
load_button.setFixedSize(120, 24)
load_button.setToolTip(LANG.get("load_model", "加载模型"))
# 将控件添加到布局
part_layout.addWidget(label)
part_layout.addWidget(input_field)
part_layout.addWidget(load_button)
# 将布局添加到标签页布局
tab_layout.addLayout(part_layout)
# 保存控件引用
self.controls[f"{lod_name}_{part}_label"] = label
self.controls[f"{lod_name}_{part}_input"] = input_field
self.buttons[f"{lod_name}_{part}_load"] = load_button
# 添加弹性空间
tab_layout.addStretch(1)
# 将标签页添加到标签页控件
self.controls["tab_widget"].addTab(tab, lod_name)
# 添加垃圾桶按钮到标签页控件右侧
trash_icon = QtGui.QIcon(os.path.join(ICONS_PATH, "delete.png"))
trash_button = QtWidgets.QPushButton()
trash_button.setIcon(trash_icon)
trash_button.setIconSize(QtCore.QSize(28, 28))
trash_button.setFixedSize(28, 28)
trash_button.setToolTip(LANG.get("delete", "删除"))
trash_button.setStyleSheet("QPushButton { border: none; background-color: transparent; }")
self.controls["tab_widget"].setCornerWidget(trash_button, QtCore.Qt.TopRightCorner)
self.buttons["delete"] = trash_button
def create_bottom_buttons(self):
"""
创建底部功能按钮区域
包括模型工具组其中包含拓扑结构、选择LOD、创建LOD按钮等
"""
# 创建底部功能区域
self.controls["bottom_area"] = QtWidgets.QWidget()
self.controls["bottom_area"].setObjectName("geometryBottomArea")
self.controls["bottom_area"].setMinimumHeight(180)
self.controls["bottom_area"].setMaximumHeight(250)
# 创建底部区域布局
bottom_layout = QtWidgets.QVBoxLayout(self.controls["bottom_area"])
bottom_layout.setContentsMargins(5, 5, 5, 5)
bottom_layout.setSpacing(5)
# 创建模型工具区域
model_tools_group = QtWidgets.QGroupBox(LANG.get("model_tools", "模型工具"))
model_tools_group.setObjectName("modelToolsGroup")
# 创建模型工具布局
model_tools_layout = QtWidgets.QGridLayout(model_tools_group)
model_tools_layout.setContentsMargins(5, 5, 5, 5)
model_tools_layout.setSpacing(5)
# 第一行放置拓扑结构、选择LOD和创建LOD按钮
# 拓扑结构标签和下拉框
topology_label = QtWidgets.QLabel(LANG.get("topology_structure", "拓扑结构") + ":")
model_tools_layout.addWidget(topology_label, 0, 0)
self.controls["topology_combo"] = QtWidgets.QComboBox()
self.controls["topology_combo"].setObjectName("topologyCombo")
self.controls["topology_combo"].addItem("MetaHuman")
self.controls["topology_combo"].setMinimumWidth(100)
model_tools_layout.addWidget(self.controls["topology_combo"], 0, 1)
# 选择LOD标签和下拉框
lod_label = QtWidgets.QLabel(LANG.get("select_lod", "选择LOD") + ":")
model_tools_layout.addWidget(lod_label, 0, 2)
self.controls["lod_combo"] = QtWidgets.QComboBox()
self.controls["lod_combo"].setObjectName("lodCombo")
self.controls["lod_combo"].addItem(LANG.get("all", "全部"))
for i in range(8): # LOD0~LOD7
self.controls["lod_combo"].addItem(f"LOD{i}")
self.controls["lod_combo"].setMinimumWidth(80)
model_tools_layout.addWidget(self.controls["lod_combo"], 0, 3)
# 创建LOD按钮
self.buttons["create_lod"] = QtWidgets.QPushButton()
self.buttons["create_lod"].setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, "create_lod.png")))
self.buttons["create_lod"].setText(LANG.get("create_lod", "创建LOD"))
self.buttons["create_lod"].setObjectName("createLodButton")
self.buttons["create_lod"].setMinimumHeight(30)
model_tools_layout.addWidget(self.buttons["create_lod"], 0, 4)
# 创建模型工具按钮
tool_buttons = [
{"name": "separate_model", "text": LANG.get("separate_model", "模型分离"), "icon": "polySplitVertex.png ", "row": 1, "col": 0, "colspan": 2},
{"name": "generate_face_components", "text": LANG.get("generate_face_components", "生成面部配件"), "icon": "meshes.png", "row": 1, "col": 2, "colspan": 3},
{"name": "fix_normals", "text": LANG.get("fix_normals", "修复法线"), "icon": "fix_normals.png", "row": 2, "col": 0, "colspan": 2},
{"name": "fix_vertex_order", "text": LANG.get("fix_vertex_order", "修复点序"), "icon": "normalConstraint.png ", "row": 2, "col": 2, "colspan": 3},
{"name": "fix_seams", "text": LANG.get("fix_seams", "修复接缝"), "icon": "polyChipOff.png ", "row": 3, "col": 0, "colspan": 2},
{"name": "optimize_scene", "text": LANG.get("optimize_scene", "优化场景"), "icon": "singlePerspLayout.png ", "row": 3, "col": 2, "colspan": 3}
]
# 创建并添加按钮到布局
for btn_info in tool_buttons:
button = QtWidgets.QPushButton()
button.setText(btn_info["text"])
button.setIcon(QtGui.QIcon(os.path.join(ICONS_PATH, btn_info["icon"])))
button.setObjectName(f"{btn_info['name']}Button")
button.setMinimumHeight(30)
model_tools_layout.addWidget(button, btn_info["row"], btn_info["col"], 1, btn_info["colspan"])
self.buttons[btn_info["name"]] = button
# 添加布局到底部区域
bottom_layout.addWidget(model_tools_group)
#========================================= LAYOUT =======================================
def create_layouts(self):
"""
创建几何模型UI布局
组织控件的排列和层次结构
"""
# 主布局
self.layouts["main_layout"] = QtWidgets.QVBoxLayout(self.main_widget)
self.layouts["main_layout"].setContentsMargins(0, 0, 0, 0)
self.layouts["main_layout"].setSpacing(0)
# 添加标题标签
self.layouts["main_layout"].addWidget(self.controls["title_label"])
# 添加标签页控件到主布局
self.layouts["main_layout"].addWidget(self.controls["tab_widget"])
# 添加底部功能按钮区域到主布局
self.layouts["main_layout"].addWidget(self.controls["bottom_area"])
#======================================= CONNECTION =====================================
def create_connections(self):
"""
连接信号和槽
设置UI控件的交互行为
"""
# 导入几何工具函数
from scripts.utils import utils_geometry
# 连接LOD标签页的加载按钮
lod_names = [f"LOD{i}" for i in range(8)]
body_parts = [
"头部", "牙齿", "牙龈", "左眼", "右眼", "红脸", "眉毛", "眉毛", "躯体"
]
# 根据LOD级别限制连接的身体部位
for lod_name in lod_names:
if lod_name in ["LOD0", "LOD1"]:
parts_to_connect = body_parts
elif lod_name in ["LOD2", "LOD3"]:
parts_to_connect = body_parts[:8]
elif lod_name in ["LOD4", "LOD5"]:
parts_to_connect = body_parts[:5]
else: # LOD6, LOD7
parts_to_connect = body_parts[:4]
# 连接每个身体部位的加载按钮
for part in parts_to_connect:
# 创建一个闭包来保存当前的lod_name和part值
def create_load_callback(lod, part):
return lambda: utils_geometry.load_model_for_lod(lod, part)
# 连接加载按钮
self.buttons[f"{lod_name}_{part}_load"].clicked.connect(
create_load_callback(lod_name, part)
)
# 连接垃圾桶按钮
self.buttons["delete"].clicked.connect(utils_geometry.delete)
# 连接拓扑结构下拉框
self.controls["topology_combo"].currentIndexChanged.connect(utils_geometry.update_topology)
# 连接选择LOD下拉框
self.controls["lod_combo"].currentIndexChanged.connect(utils_geometry.select_lod)
# 连接创建LOD按钮
self.buttons["create_lod"].clicked.connect(utils_geometry.create_lod)
# 连接模型工具按钮
self.buttons["separate_model"].clicked.connect(utils_geometry.separate_model)
self.buttons["generate_face_components"].clicked.connect(utils_geometry.generate_face_components)
self.buttons["fix_normals"].clicked.connect(utils_geometry.fix_normals)
self.buttons["fix_vertex_order"].clicked.connect(utils_geometry.fix_vertex_order)
self.buttons["fix_seams"].clicked.connect(utils_geometry.fix_seams)
self.buttons["optimize_scene"].clicked.connect(utils_geometry.optimize_scene)