#!/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"

{LANG.get('geometry_title', '几何模型')}

" 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)