From a984f8a9214db3de2d8f4ae444c367210b4fd087 Mon Sep 17 00:00:00 2001 From: Jeffreytsai1004 Date: Thu, 6 Feb 2025 06:29:55 +0800 Subject: [PATCH] Update --- scripts/ui/models.py | 279 +++++++++++++++++++++++++++++++++--------- scripts/ui/widgets.py | 20 +-- 2 files changed, 234 insertions(+), 65 deletions(-) diff --git a/scripts/ui/models.py b/scripts/ui/models.py index 918e7ed..1e229b5 100644 --- a/scripts/ui/models.py +++ b/scripts/ui/models.py @@ -32,88 +32,155 @@ except ImportError: class ModelTab(BaseWidget): """模型标签页""" def __init__(self, parent=None): + # 在super()之前初始化类属性 + self.lod_tabs = None + self.lod_buttons = [] + self.source_combo = None + self.target_combo = None + # 调用父类初始化 super(ModelTab, self).__init__(parent) def setup_ui(self): + """初始化UI""" layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(10) + layout.setContentsMargins(10, 10, 10, 10) - # 创建滚动区域 - scroll = QtWidgets.QScrollArea() - scroll.setWidgetResizable(True) - scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - layout.addWidget(scroll) + # 创建标签页控件 + self.lod_tabs = QtWidgets.QTabWidget() + layout.addWidget(self.lod_tabs) - # 创建内容控件 - content = QtWidgets.QWidget() - content_layout = QtWidgets.QVBoxLayout(content) - scroll.setWidget(content) + # 每个LOD级别的模型类型定义 + lod_models = { + 0: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"], + 1: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "软骨", "身体"], + 2: ["头部", "牙齿", "牙龈", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], + 3: ["头部", "牙齿", "左眼", "右眼", "虹膜", "睫毛", "眼睑", "身体"], + 4: ["头部", "牙齿", "左眼", "右眼", "虹膜"], + 5: ["头部", "牙齿", "左眼", "右眼"], + 6: ["头部", "牙齿", "左眼", "右眼"], + 7: ["头部", "牙齿", "左眼", "右眼"] + } - # 添加LOD组 + # 创建LOD0-7标签页 for i in range(8): - lod_group = LODGroup(i) - content_layout.addWidget(lod_group) + tab = QtWidgets.QWidget() + tab_layout = QtWidgets.QVBoxLayout(tab) + tab_layout.setSpacing(10) - # 添加LOD功能组 + # 创建网格布局 + grid = QtWidgets.QGridLayout() + grid.setSpacing(10) + + # 获取当前LOD级别的模型类型 + models = lod_models[i] + + # 为每个模型类型创建控件 + for idx, model in enumerate(models): + # 添加标签 + label_widget = QtWidgets.QLabel(f"{model}:") + label_widget.setMinimumWidth(50) + 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功能组 lod_tools = self.create_lod_tools() - content_layout.addWidget(lod_tools) + layout.addWidget(lod_tools) - # 添加模型工具组 + # 模型工具组 model_tools = self.create_model_tools() - content_layout.addWidget(model_tools) - - content_layout.addStretch() + layout.addWidget(model_tools) def create_lod_tools(self): """创建LOD功能组""" group = QtWidgets.QGroupBox("LOD功能") layout = QtWidgets.QHBoxLayout(group) + layout.setSpacing(5) - # 自定加载模型 - load_btn = IconButton("load_meshes.png", "自定加载模型") - load_btn.clicked.connect(self.load_custom_models) - layout.addWidget(load_btn) + # 创建三个按钮并设置为等宽 + buttons = [ + ("自定义加载模型", "custom_load.png", self.load_custom_models), + ("标准化命名", "standardize.png", self.standardize_naming), + ("自动分组", "auto_group.png", self.auto_group) + ] - # 标准化命名 - name_btn = IconButton("standardized_naming.png", "标准化命名") - name_btn.clicked.connect(self.standardize_naming) - layout.addWidget(name_btn) - - # 自动分组 - group_btn = IconButton("automatic_grouping.png", "自动分组") - group_btn.clicked.connect(self.auto_group) - layout.addWidget(group_btn) - - layout.addStretch() + 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) + return group def create_model_tools(self): """创建模型工具组""" group = QtWidgets.QGroupBox("模型工具") layout = QtWidgets.QVBoxLayout(group) + layout.setSpacing(10) - # 拓扑和LOD选择 - options = QtWidgets.QHBoxLayout() + # 第一行:选择栏和创建LOD按钮 + top_layout = QtWidgets.QHBoxLayout() + top_layout.setSpacing(10) - # 拓扑结构选择 - options.addWidget(QtWidgets.QLabel("拓扑结构:")) - topo_combo = QtWidgets.QComboBox() - topo_combo.addItem("MetaHuman") - options.addWidget(topo_combo) + # 源模型选择 + source_layout = QtWidgets.QHBoxLayout() + source_label = QtWidgets.QLabel("源模型:") + source_label.setMinimumWidth(60) + 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) - # LOD选择 - options.addWidget(QtWidgets.QLabel("选择LOD:")) - lod_combo = QtWidgets.QComboBox() - lod_combo.addItem("全部") - for i in range(8): - lod_combo.addItem(f"LOD{i}") - options.addWidget(lod_combo) + # 目标LOD选择 + target_layout = QtWidgets.QHBoxLayout() + target_label = QtWidgets.QLabel("目标LOD:") + target_label.setMinimumWidth(60) + 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) - options.addStretch() - layout.addLayout(options) + # 创建LOD按钮 + create_lod_btn = IconButton("create_lod.png", "创建LOD") + create_lod_btn.clicked.connect(self.create_lod) + create_lod_btn.setMinimumWidth(100) + top_layout.addWidget(create_lod_btn) - # 工具按钮 - tools = QtWidgets.QHBoxLayout() + layout.addLayout(top_layout) + # 其他工具按钮,每行两个 tool_buttons = [ ("模型分离", "polySplitVertex.png", self.split_model), ("生成面部配件", "supplement_meshes.png", self.generate_facial_accessories), @@ -122,16 +189,30 @@ class ModelTab(BaseWidget): ("修复接缝", "polyChipOff.png", self.fix_seams) ] - for text, icon, callback in tool_buttons: + # 创建网格布局 + grid = QtWidgets.QGridLayout() + grid.setSpacing(10) + for idx, (text, icon, callback) in enumerate(tool_buttons): btn = IconButton(icon, text) btn.clicked.connect(callback) - tools.addWidget(btn) - - tools.addStretch() - layout.addLayout(tools) + btn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed) + grid.addWidget(btn, idx//2, idx%2) + layout.addLayout(grid) return group + 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) + # LOD功能回调 def load_custom_models(self): """自定加载模型""" @@ -172,4 +253,88 @@ class ModelTab(BaseWidget): def fix_seams(self): """修复接缝""" from scripts.utils import model_utils - model_utils.fix_seams() \ No newline at end of file + 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) \ No newline at end of file diff --git a/scripts/ui/widgets.py b/scripts/ui/widgets.py index 514dec6..e99f787 100644 --- a/scripts/ui/widgets.py +++ b/scripts/ui/widgets.py @@ -169,18 +169,22 @@ class LODGroup(QtWidgets.QGroupBox): class IconButton(QtWidgets.QPushButton): """图标按钮""" - def __init__(self, icon_name="", tooltip="", parent=None): - super(IconButton, self).__init__(parent) - self.setup_ui(icon_name, tooltip) - - def setup_ui(self, icon_name, tooltip): + def __init__(self, icon_name, text=""): + super(IconButton, self).__init__() + self.setText(text) if icon_name: icon_path = os.path.join(data.ICONS_PATH, icon_name) if os.path.exists(icon_path): self.setIcon(QtGui.QIcon(icon_path)) - if tooltip: - self.setToolTip(tooltip) - self.setFixedSize(30, 30) + self.setIconSize(QtCore.QSize(24, 24)) # 设置图标大小 + self.setMinimumHeight(32) # 设置最小高度 + # 设置文字在图标右侧 + self.setStyleSheet(""" + QPushButton { + text-align: left; + padding-left: 5px; + } + """) class SliderWithValue(QtWidgets.QWidget): """带数值显示的滑块"""