diff --git a/core/assembly_interaction.py b/core/assembly_interaction.py index 7ff48dc7..c58caafb 100644 --- a/core/assembly_interaction.py +++ b/core/assembly_interaction.py @@ -22,6 +22,9 @@ class AssemblyInteractionManager(DirectObject): self.current_step = 0 self.total_steps = 0 self.is_active = False + + # --- 模式控制 --- + self.mode = "training" # 默认训练模式,可选 "training" 或 "exam" # --- 拖拽相关(优化) --- self.dragging_model = None @@ -37,6 +40,11 @@ class AssemblyInteractionManager(DirectObject): # --- 操作权限控制 --- self.operation_enabled = True # 是否允许进行操作 + + # --- 考核相关 --- + self.exam_score = 0 # 考核总分 + self.exam_max_score = 0 # 考核满分 + self.step_scores = {} # 每步得分记录 # --- 碰撞检测(优化) --- self.picker_traverser = CollisionTraverser('picker_traverser') @@ -44,10 +52,38 @@ class AssemblyInteractionManager(DirectObject): self.picker_ray_node = None print("拆装交互管理器初始化完成") + + def init_exam_mode(self): + """初始化考核模式""" + print("📝 初始化考核模式...") + + # 重置考核数据 + self.exam_score = 0 + self.exam_max_score = 0 + self.step_scores = {} + + # 计算总分 + steps = self.config_data.get('steps', []) + for i, step_data in enumerate(steps): + step_score = step_data.get('score', 10) # 默认每步10分 + self.exam_max_score += step_score + self.step_scores[i] = { + 'max_score': step_score, + 'current_score': step_score, # 初始满分,操作错误时扣分 + 'tool_error': False, # 是否有工具错误 + 'operation_attempts': 0 # 操作尝试次数 + } + + print(f"📝 考核模式初始化完成,总分: {self.exam_max_score} 分") def play_step_audio(self, step_data): """播放步骤音频""" try: + # 考核模式下不播放音频 + if self.mode == "exam": + print("🔇 考核模式,音频播放已禁用") + return + # 检查是否启用了自动播放音频 settings = self.config_data.get('settings', {}) auto_play_audio = settings.get('auto_play_audio', True) @@ -294,10 +330,89 @@ class AssemblyInteractionManager(DirectObject): """设置操作是否启用""" self.operation_enabled = enabled print(f"🔧 操作权限: {'启用' if enabled else '禁用'}") + + def check_operation_permission(self): + """检查操作权限""" + if not self.is_active: + print("⚠️ 交互模式未激活") + return False + + # 获取当前步骤数据 + if self.current_step >= self.total_steps: + print("⚠️ 所有步骤已完成") + return False + + step_data = self.config_data['steps'][self.current_step] + required_tool = step_data.get('required_tool', '无') + + # 获取当前选择的工具 + current_tool = "无" + if hasattr(self, 'step_dialog') and self.step_dialog and self.step_dialog.current_tool_combo: + current_tool = self.step_dialog.current_tool_combo.currentText() + + # 检查工具是否匹配 + tool_matches = self.check_tool_match(current_tool, required_tool) + + if self.mode == "training": + # 训练模式:显示提示并阻止错误操作 + if not tool_matches: + print("⚠️ 训练模式 - 工具不匹配,无法进行操作") + if hasattr(self, 'step_dialog') and self.step_dialog: + QMessageBox.warning(self.step_dialog, "工具不匹配", + f"当前步骤需要使用 '{required_tool}' 工具,请先选择正确的工具!") + return False + return True + else: + # 考核模式:不显示提示,但记录工具错误并阻止操作 + if not tool_matches: + print(f"❌ 考核模式 - 工具错误:需要'{required_tool}',实际选择'{current_tool}',操作被阻止") + + # 记录工具错误(扣分) + self.record_tool_error() + return False + return True + + def check_tool_match(self, current_tool, required_tool): + """检查工具是否匹配""" + # 如果步骤不要求特定工具,任何工具都可以 + if required_tool == "无" or required_tool == "" or not required_tool: + return True + + # 精确匹配 + if current_tool == required_tool: + return True + + return False + + def record_tool_error(self): + """记录工具错误(考核模式下扣分)""" + if self.mode != "exam": + return + + # 获取当前步骤的考核记录 + if self.current_step in self.step_scores: + step_record = self.step_scores[self.current_step] + + # 如果是第一次工具错误,扣除一定分数 + if not step_record['tool_error']: + step_record['tool_error'] = True + # 扣除该步骤50%的分数作为工具错误惩罚 + # penalty = step_record['max_score'] * 0.5 + penalty = step_record['max_score'] + step_record['current_score'] = max(0, step_record['current_score'] - penalty) + + print(f"📝 考核记录:工具错误,扣除{penalty:.0f}分,当前步骤剩余{step_record['current_score']:.0f}分") + + # 增加操作尝试次数 + step_record['operation_attempts'] += 1 - def start_interaction_mode(self): + def start_interaction_mode(self, mode="training"): """启动交互模式""" try: + # 设置模式 + self.mode = mode + print(f"🎯 启动拆装交互模式: {self.mode}") + if not self.load_configuration(): return False @@ -308,6 +423,10 @@ class AssemblyInteractionManager(DirectObject): self.total_steps = len(self.config_data['steps']) self.current_step = 0 self.is_active = True + + # 如果是考核模式,初始化考核相关数据 + if self.mode == "exam": + self.init_exam_mode() # 设置碰撞检测 self.setup_picking() @@ -793,11 +912,7 @@ class AssemblyInteractionManager(DirectObject): return # 检查操作权限 - if not self.operation_enabled: - print("⚠️ 工具不匹配,无法进行操作") - if hasattr(self, 'step_dialog') and self.step_dialog: - QMessageBox.warning(self.step_dialog, "工具不匹配", - f"当前步骤需要使用 '{self.step_dialog.current_required_tool}' 工具,请先选择正确的工具!") + if not self.check_operation_permission(): return # 获取当前步骤的目标模型 @@ -1002,8 +1117,8 @@ class AssemblyInteractionManager(DirectObject): def start_dragging(self, model_node, hit_point): """开始拖拽模型(优化版:计算偏移量)""" # 再次检查操作权限(双重保险) - if not self.operation_enabled: - print("⚠️ 工具不匹配,无法开始拖拽") + if not self.check_operation_permission(): + print("⚠️ 权限检查失败,无法开始拖拽") return self.dragging_model = model_node @@ -1231,7 +1346,59 @@ class AssemblyInteractionManager(DirectObject): self.step_dialog.close() self.step_dialog = None - QMessageBox.information(None, "完成", "所有拆装步骤已完成!") + # 如果是考核模式,显示考核结果 + if self.mode == "exam": + self.show_exam_results() + else: + QMessageBox.information(None, "完成", "所有拆装步骤已完成!") + + def show_exam_results(self): + """显示考核结果""" + # 计算最终得分 + final_score = 0 + for step_record in self.step_scores.values(): + final_score += step_record['current_score'] + + # 计算得分率 + score_rate = (final_score / self.exam_max_score * 100) if self.exam_max_score > 0 else 0 + + # 生成详细报告 + report = f"📝 拆装考核结果报告\n" + report += f"{'='*40}\n\n" + report += f"总得分: {final_score:.0f} / {self.exam_max_score:.0f} 分\n" + report += f"得分率: {score_rate:.1f}%\n\n" + + # 评级 + if score_rate >= 90: + grade = "优秀 ⭐⭐⭐" + elif score_rate >= 80: + grade = "良好 ⭐⭐" + elif score_rate >= 60: + grade = "及格 ⭐" + else: + grade = "不及格 ❌" + + report += f"评级: {grade}\n\n" + report += f"各步骤详情:\n" + report += f"{'-'*30}\n" + + # 显示每步详情 + for step_idx, step_record in self.step_scores.items(): + step_name = self.config_data['steps'][step_idx]['name'] + max_score = step_record['max_score'] + current_score = step_record['current_score'] + tool_error = "❌" if step_record['tool_error'] else "✅" + attempts = step_record['operation_attempts'] + + report += f"{step_name}:\n" + report += f" 得分: {current_score:.0f}/{max_score:.0f} 分\n" + report += f" 工具使用: {tool_error}\n" + report += f" 操作次数: {attempts}\n\n" + + print(report) + + # 显示结果对话框 + QMessageBox.information(None, "考核完成", report) def stop_interaction_mode(self): """停止交互模式""" @@ -1280,8 +1447,8 @@ class AssemblyInteractionManager(DirectObject): """触发点击位移动画""" try: # 检查操作权限(双重保险) - if not self.operation_enabled: - print("⚠️ 工具不匹配,无法进行点击触发位移") + if not self.check_operation_permission(): + print("⚠️ 权限检查失败,无法进行点击触发位移") return step_type = self.normalize_step_type(step_data) @@ -1410,10 +1577,15 @@ class StepGuideDialog(QDialog): super().__init__() self.interaction_manager = interaction_manager self.current_required_tool = "无" # 当前步骤要求的工具 + self.mode = interaction_manager.mode # 获取模式 self.setupUI() def setupUI(self): - self.setWindowTitle("拆装步骤指引") + if self.mode == "exam": + self.setWindowTitle("拆装考核") + else: + self.setWindowTitle("拆装步骤指引") + self.setFixedSize(450, 400) self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint) layout = QVBoxLayout(self) @@ -1423,19 +1595,26 @@ class StepGuideDialog(QDialog): self.step_info_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #2E86C1;") layout.addWidget(self.step_info_label) - # 步骤描述 - self.step_desc_text = QTextEdit() - self.step_desc_text.setMaximumHeight(100) - self.step_desc_text.setReadOnly(True) - layout.addWidget(self.step_desc_text) + # 步骤描述(考核模式下隐藏) + if self.mode != "exam": + self.step_desc_text = QTextEdit() + self.step_desc_text.setMaximumHeight(100) + self.step_desc_text.setReadOnly(True) + layout.addWidget(self.step_desc_text) + else: + self.step_desc_text = None # 工具选择组 tool_group = QGroupBox("工具选择") tool_layout = QVBoxLayout(tool_group) - self.required_tool_label = QLabel("当前步骤要求工具: 无") - self.required_tool_label.setStyleSheet("font-weight: bold; color: #E74C3C;") - tool_layout.addWidget(self.required_tool_label) + # 在训练模式下显示要求的工具,考核模式下不显示 + if self.mode != "exam": + self.required_tool_label = QLabel("当前步骤要求工具: 无") + self.required_tool_label.setStyleSheet("font-weight: bold; color: #E74C3C;") + tool_layout.addWidget(self.required_tool_label) + else: + self.required_tool_label = None current_tool_layout = QHBoxLayout() current_tool_layout.addWidget(QLabel("当前选择工具:")) @@ -1445,20 +1624,28 @@ class StepGuideDialog(QDialog): current_tool_layout.addWidget(self.current_tool_combo) tool_layout.addLayout(current_tool_layout) - self.tool_status_label = QLabel("✅ 工具匹配,可以进行操作") - self.tool_status_label.setStyleSheet("color: #27AE60; font-weight: bold;") - tool_layout.addWidget(self.tool_status_label) + # 在训练模式下显示工具状态,考核模式下不显示 + if self.mode != "exam": + self.tool_status_label = QLabel("✅ 工具匹配,可以进行操作") + self.tool_status_label.setStyleSheet("color: #27AE60; font-weight: bold;") + tool_layout.addWidget(self.tool_status_label) + else: + self.tool_status_label = None layout.addWidget(tool_group) - # 操作提示 - self.operation_label = QLabel("操作提示:") - self.operation_label.setStyleSheet("font-weight: bold;") - layout.addWidget(self.operation_label) - self.operation_text = QTextEdit() - self.operation_text.setMaximumHeight(80) - self.operation_text.setReadOnly(True) - layout.addWidget(self.operation_text) + # 操作提示(考核模式下隐藏) + if self.mode != "exam": + self.operation_label = QLabel("操作提示:") + self.operation_label.setStyleSheet("font-weight: bold;") + layout.addWidget(self.operation_label) + self.operation_text = QTextEdit() + self.operation_text.setMaximumHeight(80) + self.operation_text.setReadOnly(True) + layout.addWidget(self.operation_text) + else: + self.operation_label = None + self.operation_text = None # 按钮 button_layout = QHBoxLayout() @@ -1522,16 +1709,22 @@ class StepGuideDialog(QDialog): # 检查工具是否匹配 tool_matches = self.check_tool_permission(current_tool, required_tool) - if tool_matches: - self.tool_status_label.setText("✅ 工具匹配,可以进行操作") - self.tool_status_label.setStyleSheet("color: #27AE60; font-weight: bold;") - # 启用交互操作 - self.interaction_manager.set_operation_enabled(True) + if self.mode == "training": + # 训练模式:显示工具匹配状态 + if tool_matches: + self.tool_status_label.setText("✅ 工具匹配,可以进行操作") + self.tool_status_label.setStyleSheet("color: #27AE60; font-weight: bold;") + # 启用交互操作 + self.interaction_manager.set_operation_enabled(True) + else: + self.tool_status_label.setText(f"❌ 工具不匹配,需要选择 '{required_tool}' 工具") + self.tool_status_label.setStyleSheet("color: #E74C3C; font-weight: bold;") + # 禁用交互操作 + self.interaction_manager.set_operation_enabled(False) else: - self.tool_status_label.setText(f"❌ 工具不匹配,需要选择 '{required_tool}' 工具") - self.tool_status_label.setStyleSheet("color: #E74C3C; font-weight: bold;") - # 禁用交互操作 - self.interaction_manager.set_operation_enabled(False) + # 考核模式:不显示工具状态,但仍然需要正确的工具才能操作 + # 这里不再总是启用操作,而是让权限检查在实际操作时进行 + self.interaction_manager.set_operation_enabled(True) def check_tool_permission(self, current_tool, required_tool): """检查工具权限""" @@ -1547,14 +1740,24 @@ class StepGuideDialog(QDialog): def update_step_info(self, step_data, current_step, total_steps): step_name = step_data.get('name', f'步骤 {current_step}') - self.step_info_label.setText(f"第 {current_step}/{total_steps} 步: {step_name}") + + if self.mode == "exam": + # 考核模式:显示考核信息 + step_score = step_data.get('score', 10) + self.step_info_label.setText(f"第 {current_step}/{total_steps} 步: {step_name} (分值: {step_score})") + else: + # 训练模式:正常显示 + self.step_info_label.setText(f"第 {current_step}/{total_steps} 步: {step_name}") - step_desc = step_data.get('description', '无描述') - self.step_desc_text.setPlainText(step_desc) + # 步骤描述(考核模式下不显示) + if self.step_desc_text is not None: + step_desc = step_data.get('description', '无描述') + self.step_desc_text.setPlainText(step_desc) # 更新工具要求 self.current_required_tool = step_data.get('required_tool', '无') - self.required_tool_label.setText(f"当前步骤要求工具: {self.current_required_tool}") + if self.required_tool_label is not None: + self.required_tool_label.setText(f"当前步骤要求工具: {self.current_required_tool}") # 更新工具状态 self.update_tool_status() @@ -1590,7 +1793,9 @@ class StepGuideDialog(QDialog): else: operation_hint = f"🔧 拆卸操作\n请操作模型 '{target_model}' 将其拆卸。" - self.operation_text.setPlainText(operation_hint) + # 操作提示(考核模式下不显示) + if self.operation_text is not None: + self.operation_text.setPlainText(operation_hint) def skip_current_step(self): if self.interaction_manager.is_active: diff --git a/ui/assembly_disassembly_config_simple.py b/ui/assembly_disassembly_config_simple.py index cea49925..9397f650 100644 --- a/ui/assembly_disassembly_config_simple.py +++ b/ui/assembly_disassembly_config_simple.py @@ -192,6 +192,22 @@ class AssemblyDisassemblyConfigDialog(QDialog): self.step_type_combo.addItems(["拆卸", "安装"]) basic_layout.addWidget(self.step_type_combo, 2, 1) + # 步骤分值(考核模式用) + basic_layout.addWidget(QLabel("步骤分值:"), 3, 0) + self.step_score_spin = QDoubleSpinBox() + self.step_score_spin.setRange(0, 100) + self.step_score_spin.setValue(10) # 默认10分 + self.step_score_spin.setSingleStep(1) + self.step_score_spin.setDecimals(0) + self.step_score_spin.setSuffix(" 分") + basic_layout.addWidget(self.step_score_spin, 3, 1) + + # 添加分值说明 + score_note = QLabel("注:在考核模式下,此分值将用于计算该步骤的得分。") + score_note.setWordWrap(True) + score_note.setStyleSheet("color: #666; font-style: italic; font-size: 10px;") + basic_layout.addWidget(score_note, 4, 0, 1, 2) + scroll_layout.addWidget(basic_group) # 目标模型 @@ -519,12 +535,15 @@ class AssemblyDisassemblyConfigDialog(QDialog): 'target_position': [0, 0, 0], 'audio_file': '', 'hint_text': '', - 'required_tool': '无' + 'required_tool': '无', + 'score': 10 # 默认分值 } self.config_data['steps'].append(step_data) - list_item = QListWidgetItem(step_name) + # 显示步骤名称和分值 + display_text = f"{step_name} ({step_data['score']}分)" + list_item = QListWidgetItem(display_text) list_item.setData(Qt.UserRole, step_data) self.steps_list.addItem(list_item) @@ -587,7 +606,9 @@ class AssemblyDisassemblyConfigDialog(QDialog): step_data = item.data(Qt.UserRole) new_name = f"步骤 {i + 1}" step_data['name'] = new_name - item.setText(new_name) + step_score = step_data.get('score', 10) + display_text = f"{new_name} ({step_score}分)" + item.setText(display_text) def onStepSelectionChanged(self): """步骤选择改变""" @@ -657,6 +678,9 @@ class AssemblyDisassemblyConfigDialog(QDialog): self.required_tool_combo.setCurrentIndex(index) else: self.required_tool_combo.setCurrentIndex(0) + + # 加载分值配置 + self.step_score_spin.setValue(step_data.get('score', 10)) finally: # 确保信号重新连接 @@ -678,6 +702,7 @@ class AssemblyDisassemblyConfigDialog(QDialog): self.audio_edit.textChanged.disconnect(self.saveCurrentStepConfig) self.hint_text_edit.textChanged.disconnect(self.saveCurrentStepConfig) self.required_tool_combo.currentTextChanged.disconnect(self.saveCurrentStepConfig) + self.step_score_spin.valueChanged.disconnect(self.saveCurrentStepConfig) except: # 忽略断开连接时的错误 pass @@ -697,6 +722,7 @@ class AssemblyDisassemblyConfigDialog(QDialog): self.audio_edit.textChanged.connect(self.saveCurrentStepConfig) self.hint_text_edit.textChanged.connect(self.saveCurrentStepConfig) self.required_tool_combo.currentTextChanged.connect(self.saveCurrentStepConfig) + self.step_score_spin.valueChanged.connect(self.saveCurrentStepConfig) def saveCurrentStepConfig(self): """保存当前步骤的配置到list_item的UserRole数据中""" @@ -724,12 +750,16 @@ class AssemblyDisassemblyConfigDialog(QDialog): step_data['audio_file'] = self.audio_edit.text() step_data['hint_text'] = self.hint_text_edit.toPlainText() step_data['required_tool'] = self.required_tool_combo.currentText() + step_data['score'] = int(self.step_score_spin.value()) # 保存分值 # 确保数据更新到list_item的UserRole中 self._current_step_item.setData(Qt.UserRole, step_data) - # 更新列表项的显示文本 - self._current_step_item.setText(step_data['name']) + # 更新列表项的显示文本,包含分值信息 + step_name = step_data['name'] + step_score = step_data.get('score', 10) + display_text = f"{step_name} ({step_score}分)" + self._current_step_item.setText(display_text) # 同时更新config_data中的对应数据(保持数据一致性) current_row = self.steps_list.row(self._current_step_item) @@ -869,7 +899,10 @@ class AssemblyDisassemblyConfigDialog(QDialog): # 加载步骤配置 self.config_data['steps'] = data.get('steps', []) for step_data in self.config_data['steps']: - list_item = QListWidgetItem(step_data['name']) + step_name = step_data['name'] + step_score = step_data.get('score', 10) + display_text = f"{step_name} ({step_score}分)" + list_item = QListWidgetItem(display_text) list_item.setData(Qt.UserRole, step_data) self.steps_list.addItem(list_item) diff --git a/ui/main_window.py b/ui/main_window.py index f07f6569..8d7443c3 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -16,7 +16,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout, QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog, - QSpinBox, QFrame) + QSpinBox, QFrame, QRadioButton, QTextEdit) from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect from direct.showbase.ShowBaseGlobal import aspect2d @@ -2886,20 +2886,139 @@ class MainWindow(QMainWindow): def onStartAssemblyInteraction(self): """开始拆装交互""" try: + # 显示模式选择对话框 + mode_dialog = AssemblyModeSelectionDialog(self) + if mode_dialog.exec_() != QDialog.Accepted: + return + + selected_mode = mode_dialog.get_selected_mode() + print(f"🎯 用户选择的拆装模式: {selected_mode}") + from core.assembly_interaction import AssemblyInteractionManager # 检查是否已有交互管理器实例 if not hasattr(self.world, 'assembly_interaction'): self.world.assembly_interaction = AssemblyInteractionManager(self.world) - # 启动交互模式 - self.world.assembly_interaction.start_interaction_mode() + # 启动交互模式,传递模式参数 + self.world.assembly_interaction.start_interaction_mode(mode=selected_mode) except Exception as e: QMessageBox.critical(self, "错误", f"启动拆装交互失败: {str(e)}") import traceback traceback.print_exc() +class AssemblyModeSelectionDialog(QDialog): + """拆装模式选择对话框""" + + def __init__(self, parent=None): + super().__init__(parent) + self.selected_mode = "training" # 默认选择训练模式 + self.setupUI() + + def setupUI(self): + self.setWindowTitle("选择拆装模式") + self.setFixedSize(400, 300) + self.setModal(True) + + layout = QVBoxLayout(self) + + # 标题 + title_label = QLabel("请选择拆装交互模式") + title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #2E86C1; margin: 10px;") + title_label.setAlignment(Qt.AlignCenter) + layout.addWidget(title_label) + + # 模式选择组 + mode_group = QGroupBox("模式选择") + mode_layout = QVBoxLayout(mode_group) + + # 训练模式 + self.training_radio = QRadioButton("训练模式") + self.training_radio.setChecked(True) # 默认选中 + self.training_radio.setStyleSheet("font-size: 14px; margin: 5px;") + mode_layout.addWidget(self.training_radio) + + training_desc = QTextEdit() + training_desc.setMaximumHeight(60) + training_desc.setReadOnly(True) + training_desc.setPlainText("• 显示详细的步骤描述和操作提示\n• 提供工具选择的正确性提示\n• 播放语音指导") + training_desc.setStyleSheet("background-color: #f0f8ff; border: 1px solid #ccc; margin-left: 20px;") + mode_layout.addWidget(training_desc) + + # 考核模式 + self.exam_radio = QRadioButton("考核模式") + self.exam_radio.setStyleSheet("font-size: 14px; margin: 5px;") + mode_layout.addWidget(self.exam_radio) + + exam_desc = QTextEdit() + exam_desc.setMaximumHeight(60) + exam_desc.setReadOnly(True) + exam_desc.setPlainText("• 不显示步骤描述\n• 工具选择错误时不提示,直接扣分\n• 不播放语音指导") + exam_desc.setStyleSheet("background-color: #fff5f5; border: 1px solid #ccc; margin-left: 20px;") + mode_layout.addWidget(exam_desc) + + layout.addWidget(mode_group) + + # 按钮 + button_layout = QHBoxLayout() + + self.ok_button = QPushButton("开始") + self.ok_button.setStyleSheet(""" + QPushButton { + background-color: #27AE60; + color: white; + font-size: 14px; + font-weight: bold; + padding: 8px 20px; + border: none; + border-radius: 4px; + } + QPushButton:hover { + background-color: #2ECC71; + } + """) + self.ok_button.clicked.connect(self.accept) + + self.cancel_button = QPushButton("取消") + self.cancel_button.setStyleSheet(""" + QPushButton { + background-color: #95A5A6; + color: white; + font-size: 14px; + padding: 8px 20px; + border: none; + border-radius: 4px; + } + QPushButton:hover { + background-color: #BDC3C7; + } + """) + self.cancel_button.clicked.connect(self.reject) + + button_layout.addStretch() + button_layout.addWidget(self.ok_button) + button_layout.addWidget(self.cancel_button) + + layout.addLayout(button_layout) + + # 连接单选按钮信号 + self.training_radio.toggled.connect(self.on_mode_changed) + self.exam_radio.toggled.connect(self.on_mode_changed) + + def on_mode_changed(self): + """模式改变时的处理""" + if self.training_radio.isChecked(): + self.selected_mode = "training" + elif self.exam_radio.isChecked(): + self.selected_mode = "exam" + print(f"🔄 模式选择改变: {self.selected_mode}") + + def get_selected_mode(self): + """获取选中的模式""" + return self.selected_mode + + def setup_main_window(world,path = None): """设置主窗口的便利函数""" app = QApplication.instance()