1
0
forked from Rowland/EG

1.虚拟维修考核简单流程

This commit is contained in:
陈横 2025-09-25 14:22:34 +08:00
parent e9039ab832
commit a76277b2fc
3 changed files with 411 additions and 54 deletions

View File

@ -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:

View File

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

View File

@ -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()