From 64df3107fe6ccfc1d2e275cf460a0b142d43c9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Fri, 26 Sep 2025 09:24:07 +0800 Subject: [PATCH] =?UTF-8?q?1.=E8=99=9A=E6=8B=9F=E7=BB=B4=E4=BF=AE=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/assembly_interaction.py | 542 +++++++++++++++-- core/maintenance_gui.py | 1023 ++++++++++++++++++++++++++++++++ ui/main_window.py | 26 +- ui/maintenance_system.py | 658 ++++++++++++++++++++ ui/simple_maintenance_login.py | 220 +++++++ 5 files changed, 2395 insertions(+), 74 deletions(-) create mode 100644 core/maintenance_gui.py create mode 100644 ui/maintenance_system.py create mode 100644 ui/simple_maintenance_login.py diff --git a/core/assembly_interaction.py b/core/assembly_interaction.py index 3d1c9417..4e6bae63 100644 --- a/core/assembly_interaction.py +++ b/core/assembly_interaction.py @@ -11,6 +11,13 @@ from PyQt5.QtWidgets import QMessageBox, QFileDialog, QDialog, QVBoxLayout, QHBo from PyQt5.QtWidgets import QLabel, QPushButton, QTextEdit, QComboBox, QGroupBox from PyQt5.QtCore import Qt +# 导入维修系统GUI +try: + from core.maintenance_gui import MaintenanceGUI +except ImportError as e: + print(f"⚠️ 导入维修GUI失败: {e}") + MaintenanceGUI = None + class AssemblyInteractionManager(DirectObject): """拆装交互管理器(优化版)""" @@ -38,6 +45,11 @@ class AssemblyInteractionManager(DirectObject): # --- UI组件 --- self.step_dialog = None + # --- 维修系统GUI --- + self.maintenance_gui = None + if MaintenanceGUI: + self.maintenance_gui = MaintenanceGUI(world) + # --- 操作权限控制 --- self.operation_enabled = True # 是否允许进行操作 @@ -345,19 +357,29 @@ class AssemblyInteractionManager(DirectObject): step_data = self.config_data['steps'][self.current_step] required_tool = step_data.get('required_tool', '无') - # 获取当前选择的工具 + # 获取当前选择的工具(优先从维修GUI获取) 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() + if self.maintenance_gui: + current_tool = self.maintenance_gui.get_current_tool() + elif hasattr(self, 'step_dialog') and self.step_dialog and hasattr(self.step_dialog, 'tool_combo'): + current_tool = self.step_dialog.tool_combo.currentText() + + print(f"🔧 工具检查: 需要'{required_tool}',当前'{current_tool}'") # 检查工具是否匹配 tool_matches = self.check_tool_match(current_tool, required_tool) if self.mode == "training": - # 训练模式:显示提示并阻止错误操作 + # 训练模式:显示GUI警告并阻止错误操作 if not tool_matches: print("⚠️ 训练模式 - 工具不匹配,无法进行操作") - if hasattr(self, 'step_dialog') and self.step_dialog: + + # 在维修GUI中显示警告 + if self.maintenance_gui: + warning_msg = f"请先选择 '{required_tool}' 工具!" + self.maintenance_gui.show_warning(warning_msg, 2.0) + # 也在Qt对话框中显示警告(仅在非维修系统模式下) + elif hasattr(self, 'step_dialog') and self.step_dialog: QMessageBox.warning(self.step_dialog, "工具不匹配", f"当前步骤需要使用 '{required_tool}' 工具,请先选择正确的工具!") return False @@ -413,8 +435,10 @@ class AssemblyInteractionManager(DirectObject): self.mode = mode print(f"🎯 启动拆装交互模式: {self.mode}") - if not self.load_configuration(): - return False + # 如果没有配置数据,尝试加载 + if not self.config_data: + if not self.load_configuration(): + return False if not self.config_data or not self.config_data.get('steps'): QMessageBox.warning(None, "警告", "没有找到有效的配置步骤!\n请先在'拆装配置'中设置步骤。") @@ -424,9 +448,21 @@ class AssemblyInteractionManager(DirectObject): self.current_step = 0 self.is_active = True + # 设置维修系统GUI + if self.maintenance_gui: + tools_list = self.config_data.get('tools', ['手']) + self.maintenance_gui.setup_gui(tools_list, mode) + self.maintenance_gui.show_gui() + print(f"✅ 维修系统GUI已启动,模式: {mode}") + # 如果是考核模式,初始化考核相关数据 if self.mode == "exam": self.init_exam_mode() + # 显示考核开始提示 + if self.maintenance_gui: + exam_start_msg = f"📝 考核模式已启动\n\n总分: {self.exam_max_score} 分\n步骤数: {self.total_steps}\n\n✅ 可以选择和切换工具\n❌ 不会显示步骤描述和语音提示\n❌ 使用错误工具会扣分" + self.maintenance_gui.update_step_info(exam_start_msg) + print("📝 考核开始提示已在GUI中显示") # 设置碰撞检测 self.setup_picking() @@ -470,8 +506,9 @@ class AssemblyInteractionManager(DirectObject): # 调试:列出场景中的所有模型 self.list_scene_models() - # 显示步骤指引界面 - self.show_step_dialog() + # 显示步骤指引界面(维修系统使用GUI界面,跳过Qt对话框) + if not self.maintenance_gui: + self.show_step_dialog() # 开始第一步 self.start_current_step() @@ -706,7 +743,26 @@ class AssemblyInteractionManager(DirectObject): # 播放步骤音频(如果有配置) self.play_step_audio(step_data) - if self.step_dialog: + # 更新维修系统GUI的步骤信息 + if self.maintenance_gui: + step_name = step_data.get('name', f'步骤 {self.current_step + 1}') + step_description = step_data.get('description', '') + + if self.mode == "exam": + # 考核模式:只显示步骤编号和名称,不显示描述 + step_info = f"考核步骤 {self.current_step + 1}/{self.total_steps}: {step_name}" + print(f"📝 考核模式步骤信息: {step_info}") + else: + # 训练模式:显示完整信息 + step_info = f"步骤 {self.current_step + 1}/{self.total_steps}: {step_name}" + if step_description: + step_info += f"\n{step_description}" + print(f"📚 训练模式步骤信息: {step_info}") + + self.maintenance_gui.update_step_info(step_info) + + # 更新Qt步骤对话框(仅在非维修系统模式下) + if self.step_dialog and not self.maintenance_gui: self.step_dialog.update_step_info(step_data, self.current_step + 1, self.total_steps) # 准备交互(主要是确保目标模型可以被拾取) @@ -872,38 +928,211 @@ class AssemblyInteractionManager(DirectObject): print("\n✅ 所有模型都处于正确状态") def qt_mouse_press_event(self, event): - """Qt鼠标按下事件处理(只拦截左键)""" + """Qt鼠标按下事件处理(选择性拦截版)""" from PyQt5.QtCore import Qt # 只拦截左键,其他按键使用原有处理方式 if event.button() == Qt.LeftButton: print('🖱️ 左键被拦截处理') print(f'🖱️ 左键点击位置: ({event.x()}, {event.y()})') - # 调用我们的处理逻辑 + + # 检查是否点击在GUI区域 + if self.maintenance_gui and self.is_click_in_gui_area(event.x(), event.y()): + print('🎯 点击在GUI区域,直接处理工具按钮点击') + # 直接处理工具按钮点击,不依赖DirectGUI事件 + button_clicked = self.handle_gui_button_click(event.x(), event.y()) + if button_clicked: + print(f'🎯 工具按钮点击已处理: {button_clicked}') + event.accept() + return + + # 如果不是按钮点击,让原有事件处理器处理 + if hasattr(self, 'original_mouse_press_event'): + self.original_mouse_press_event(event) + else: + event.ignore() + return + + # 点击在3D区域,进行拦截处理 + print('🎯 点击在3D区域,进行拦截处理') self.handle_qt_mouse_click(event.x(), event.y()) - # 接受左键事件,阻止进一步传播 event.accept() else: - # 其他按键(右键、中键等)使用原有处理方式 - # print(f'🖱️ 其他按键({event.button()})使用原有处理方式') # 静默处理 + # 其他按键使用原有处理方式 if hasattr(self, 'original_mouse_press_event'): self.original_mouse_press_event(event) else: event.ignore() + def is_click_in_gui_area(self, click_x, click_y): + """检查鼠标点击是否在GUI区域(高精度版本)""" + try: + if not self.maintenance_gui: + return False + + # 获取窗口尺寸用于坐标转换 + if hasattr(self.world, 'qtWidget') and self.world.qtWidget: + widget_width = self.world.qtWidget.width() + widget_height = self.world.qtWidget.height() + else: + # 使用默认窗口尺寸 + widget_width = 1380 + widget_height = 750 + + # 使用与maintenance_gui完全相同的坐标转换算法 + aspect_x = (click_x / widget_width) * 2.67 - 1.33 + aspect_y = 1.0 - (click_y / widget_height) * 2.0 + + # 额外的坐标验证(与Panda3D标准坐标系对比) + # Panda3D aspect2d坐标系: x范围[-1.33, 1.33], y范围[-1.0, 1.0] + normalized_x = (click_x / widget_width) * 2.0 - 1.0 # 标准化到[-1, 1] + normalized_y = 1.0 - (click_y / widget_height) * 2.0 # 标准化到[-1, 1],Y轴翻转 + aspect_ratio = widget_width / widget_height + panda_x = normalized_x * aspect_ratio # 应用宽高比 + panda_y = normalized_y + + print(f"🔍 精确GUI区域检查: Qt({click_x}, {click_y}) -> aspect2d({aspect_x:.3f}, {aspect_y:.3f})") + print(f"🔍 验证坐标转换: 标准化({normalized_x:.3f}, {normalized_y:.3f}) -> Panda3D({panda_x:.3f}, {panda_y:.3f})") + print(f"📏 窗口信息: 尺寸({widget_width}x{widget_height}), 宽高比({aspect_ratio:.3f})") + + # 快速检查工具按钮区域 + if hasattr(self.maintenance_gui, 'available_tools') and self.maintenance_gui.available_tools: + # 工具按钮区域(底部条带) + tools_area_left = -1.1 + tools_area_right = 0.5 + tools_area_bottom = -1.0 + tools_area_top = -0.5 + + if (tools_area_left <= aspect_x <= tools_area_right and + tools_area_bottom <= aspect_y <= tools_area_top): + print(f"✅ 点击在工具按钮区域内") + return True + + # 检查其他GUI区域(步骤显示、当前工具、警告等) + other_gui_areas = [] + + # 步骤显示区域(顶部) + if hasattr(self.maintenance_gui, 'step_text') and self.maintenance_gui.step_text: + other_gui_areas.append({ + 'name': '步骤显示区域', + 'left': -1.8, 'right': 1.8, + 'bottom': 0.5, 'top': 1.1 + }) + + # 当前工具显示区域(右侧) + if hasattr(self.maintenance_gui, 'current_tool_text') and self.maintenance_gui.current_tool_text: + other_gui_areas.append({ + 'name': '当前工具显示区域', + 'left': 0.2, 'right': 1.8, + 'bottom': 0.6, 'top': 1.0 + }) + + # 警告显示区域(中央) + if hasattr(self.maintenance_gui, 'warning_text') and self.maintenance_gui.warning_text: + other_gui_areas.append({ + 'name': '警告显示区域', + 'left': -1.5, 'right': 1.5, + 'bottom': 0.0, 'top': 0.6 + }) + + # 检查其他GUI区域 + for area in other_gui_areas: + if (area['left'] <= aspect_x <= area['right'] and + area['bottom'] <= aspect_y <= area['top']): + print(f"✅ 点击在GUI区域内: {area['name']}") + return True + + print("❌ 点击不在任何GUI区域内") + return False + + except Exception as e: + print(f"❌ 检查GUI区域失败: {e}") + import traceback + traceback.print_exc() + return False + + def handle_gui_button_click(self, click_x, click_y): + """直接处理GUI按钮点击""" + try: + if not self.maintenance_gui: + return None + + # 获取窗口尺寸用于坐标转换 + if hasattr(self.world, 'qtWidget') and self.world.qtWidget: + widget_width = self.world.qtWidget.width() + widget_height = self.world.qtWidget.height() + else: + widget_width = 1380 + widget_height = 750 + + # 使用与maintenance_gui完全相同的坐标转换算法 + aspect_x = (click_x / widget_width) * 2.67 - 1.33 + aspect_y = 1.0 - (click_y / widget_height) * 2.0 + + print(f"🎯 直接处理按钮点击:Qt({click_x}, {click_y}) -> aspect2d({aspect_x:.3f}, {aspect_y:.3f})") + + # 检查工具按钮点击 + if hasattr(self.maintenance_gui, 'available_tools') and self.maintenance_gui.available_tools: + button_width = 0.4 + button_height = 0.25 + button_spacing = 0.45 + start_x = -0.8 + start_y = -0.75 + click_padding = 0.05 # 点击容差 + + for i, tool_data in enumerate(self.maintenance_gui.available_tools): + # 获取工具名称 + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + # 计算按钮位置 + button_x = start_x + i * button_spacing + button_y = start_y + + # 计算按钮边界(包含容差) + left = button_x - (button_width/2 + click_padding) + right = button_x + (button_width/2 + click_padding) + bottom = button_y - (button_height/2 + click_padding) + top = button_y + (button_height/2 + click_padding) + + print(f"🔍 检查按钮{i} '{tool_name}': 范围x[{left:.3f}, {right:.3f}], y[{bottom:.3f}, {top:.3f}]") + + # 检查点击是否在按钮范围内 + if (left <= aspect_x <= right and bottom <= aspect_y <= top): + print(f"✅ 直接匹配按钮{i} '{tool_name}'!调用工具选择") + # 直接调用维修GUI的工具选择方法 + self.maintenance_gui.on_tool_selected(tool_name) + return tool_name + + print("❌ 点击不在任何按钮范围内") + return None + + except Exception as e: + print(f"❌ 直接处理按钮点击失败: {e}") + import traceback + traceback.print_exc() + return None + def qt_mouse_release_event(self, event): - """Qt鼠标释放事件处理(只拦截左键)""" + """Qt鼠标释放事件处理(维修系统优化版)""" from PyQt5.QtCore import Qt # 只拦截左键释放,其他按键使用原有处理方式 if event.button() == Qt.LeftButton: print('🖱️ 左键释放被拦截处理') + + # 维修系统模式:先让原有处理器处理 + if self.maintenance_gui: + if hasattr(self, 'original_mouse_release_event'): + self.original_mouse_release_event(event) + + # 处理3D交互的鼠标释放 self.on_mouse_up() - # 接受左键释放事件 event.accept() else: # 其他按键释放使用原有处理方式 - # print(f'🖱️ 其他按键释放({event.button()})使用原有处理方式') # 静默处理 if hasattr(self, 'original_mouse_release_event'): self.original_mouse_release_event(event) else: @@ -1314,6 +1543,11 @@ class AssemblyInteractionManager(DirectObject): def complete_current_step(self): """完成当前步骤""" + # 记录操作完成(考核模式下) + if self.mode == "exam" and self.current_step in self.step_scores: + self.step_scores[self.current_step]['operation_attempts'] += 1 + print(f"📝 考核记录:步骤 {self.current_step + 1} 完成,操作次数: {self.step_scores[self.current_step]['operation_attempts']}") + # 在进入下一步之前,取消当前模型的碰撞,避免干扰 step_data = self.config_data['steps'][self.current_step] target_model_name = step_data.get('target_model') @@ -1338,6 +1572,11 @@ class AssemblyInteractionManager(DirectObject): self.is_active = False self.ignoreAll() + # 清理维修系统GUI + if self.maintenance_gui: + self.maintenance_gui.cleanup_gui() + print("✅ 维修系统GUI已清理") + # 恢复原有的Qt鼠标事件处理 print("🔄 恢复原有的Qt鼠标事件处理") if hasattr(self, 'original_mouse_press_event') and hasattr(self.world, 'qtWidget') and self.world.qtWidget: @@ -1351,59 +1590,234 @@ class AssemblyInteractionManager(DirectObject): self.step_dialog.close() self.step_dialog = None - # 如果是考核模式,显示考核结果 + # 根据模式显示不同的完成结果 if self.mode == "exam": + # 考核模式:显示详细的考核结果 self.show_exam_results() else: - QMessageBox.information(None, "完成", "所有拆装步骤已完成!") + # 训练模式:显示训练完成提示 + print("\n🎉 训练模式完成!") + completion_msg = "🎉 训练完成!\n\n所有维修步骤已完成!\n现在可以尝试考核模式检验学习成果。" + + if self.maintenance_gui: + self.maintenance_gui.update_step_info(completion_msg) + print("📋 训练完成信息已在GUI中显示") + else: + QMessageBox.information(None, "训练完成", completion_msg) 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'] + try: + # 计算最终得分 + final_score = 0 + total_operations = 0 + total_errors = 0 - 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) + for step_record in self.step_scores.values(): + final_score += step_record['current_score'] + total_operations += step_record['operation_attempts'] + if step_record['tool_error']: + total_errors += 1 + + # 计算得分率和准确率 + score_rate = (final_score / self.exam_max_score * 100) if self.exam_max_score > 0 else 0 + accuracy_rate = ((self.total_steps - total_errors) / self.total_steps * 100) if self.total_steps > 0 else 100 + + # 生成考核报告 + print(f"\n{'='*50}") + print(f"🎓 维修技能考核结果报告") + print(f"{'='*50}") + print(f"📊 总体成绩:") + print(f" 总得分: {final_score:.0f} / {self.exam_max_score:.0f} 分") + print(f" 得分率: {score_rate:.1f}%") + print(f" 操作准确率: {accuracy_rate:.1f}%") + print(f" 总操作次数: {total_operations}") + print(f" 错误次数: {total_errors}") + + # 评级系统 + if score_rate >= 90: + grade = "优秀" + grade_icon = "🏆" + grade_desc = "表现卓越,技能熟练" + elif score_rate >= 80: + grade = "良好" + grade_icon = "🥈" + grade_desc = "表现良好,基本掌握" + elif score_rate >= 60: + grade = "及格" + grade_icon = "✅" + grade_desc = "达到基本要求" + else: + grade = "不及格" + grade_icon = "❌" + grade_desc = "需要加强练习" + + print(f"\n🏅 评级: {grade_icon} {grade}") + print(f" 评语: {grade_desc}") + + # 详细步骤分析 + print(f"\n📋 详细步骤分析:") + print(f"{'-'*40}") + + perfect_steps = 0 + for step_idx, step_record in self.step_scores.items(): + step_data = self.config_data['steps'][step_idx] + step_name = step_data.get('name', f'步骤{step_idx+1}') + max_score = step_record['max_score'] + current_score = step_record['current_score'] + tool_error = step_record['tool_error'] + attempts = step_record['operation_attempts'] + + # 判断步骤完成质量 + if current_score >= max_score: + step_status = "🟢 完美" + perfect_steps += 1 + elif current_score >= max_score * 0.8: + step_status = "🟡 良好" + elif current_score > 0: + step_status = "🟠 一般" + else: + step_status = "🔴 失败" + + print(f"{step_name}:") + print(f" 状态: {step_status}") + print(f" 得分: {current_score:.0f}/{max_score:.0f} 分") + print(f" 工具使用: {'❌ 错误' if tool_error else '✅ 正确'}") + print(f" 操作次数: {attempts}") + print() + + # 生成改进建议 + suggestions = [] + if total_errors > 0: + suggestions.append("• 注意选择正确的工具进行操作") + if total_operations > self.total_steps * 1.5: + suggestions.append("• 减少不必要的操作,提高操作效率") + if perfect_steps < self.total_steps * 0.5: + suggestions.append("• 多练习以提高操作的精确度") + if score_rate < 80: + suggestions.append("• 建议重新学习相关理论知识") + + if suggestions: + print("💡 改进建议:") + for suggestion in suggestions: + print(f" {suggestion}") + print() + + print(f"{'='*50}") + + # 为GUI显示准备简化版本的报告 + gui_report = f"🎓 考核完成!\n\n" + gui_report += f"📊 成绩总览:\n" + gui_report += f"总得分: {final_score:.0f}/{self.exam_max_score:.0f} 分 ({score_rate:.1f}%)\n" + gui_report += f"评级: {grade_icon} {grade}\n" + gui_report += f"操作准确率: {accuracy_rate:.1f}%\n\n" + gui_report += f"完美完成步骤: {perfect_steps}/{self.total_steps}\n" + gui_report += f"总操作次数: {total_operations}\n" + gui_report += f"错误次数: {total_errors}" + + # 在维修GUI中显示详细考核结果 + if self.maintenance_gui: + # 创建详细的考核结果数据 + exam_result_data = { + 'final_score': final_score, + 'max_score': self.exam_max_score, + 'score_rate': score_rate, + 'accuracy_rate': accuracy_rate, + 'grade': grade, + 'grade_icon': grade_icon, + 'grade_desc': grade_desc, + 'total_operations': total_operations, + 'total_errors': total_errors, + 'perfect_steps': perfect_steps, + 'total_steps': self.total_steps, + 'step_details': [], + 'suggestions': suggestions + } + + # 添加每步详情 + for step_idx, step_record in self.step_scores.items(): + step_data = self.config_data['steps'][step_idx] + step_name = step_data.get('name', f'步骤{step_idx+1}') + max_score = step_record['max_score'] + current_score = step_record['current_score'] + tool_error = step_record['tool_error'] + attempts = step_record['operation_attempts'] + + # 判断步骤完成质量 + if current_score >= max_score: + step_status = "🟢 完美" + elif current_score >= max_score * 0.8: + step_status = "🟡 良好" + elif current_score > 0: + step_status = "🟠 一般" + else: + step_status = "🔴 失败" + + exam_result_data['step_details'].append({ + 'name': step_name, + 'status': step_status, + 'current_score': current_score, + 'max_score': max_score, + 'tool_error': tool_error, + 'attempts': attempts + }) + + # 使用GUI显示考核结果 + self.maintenance_gui.show_exam_results(exam_result_data) + print("📋 考核结果已在GUI中显示") + else: + # 如果没有GUI,使用简单的控制台输出 + print("⚠️ 维修GUI不可用,考核结果仅在控制台显示") + + # 保存考核结果到文件(可选) + self.save_exam_results(final_score, score_rate, grade) + + except Exception as e: + print(f"❌ 显示考核结果失败: {e}") + import traceback + traceback.print_exc() + QMessageBox.critical(None, "错误", f"显示考核结果失败: {str(e)}") + + def save_exam_results(self, final_score, score_rate, grade): + """保存考核结果到文件""" + try: + import datetime + import json + + # 生成考核记录 + exam_record = { + "timestamp": datetime.datetime.now().isoformat(), + "mode": "exam", + "final_score": final_score, + "max_score": self.exam_max_score, + "score_rate": score_rate, + "grade": grade, + "total_steps": self.total_steps, + "step_details": {} + } + + # 添加每步详情 + for step_idx, step_record in self.step_scores.items(): + step_name = self.config_data['steps'][step_idx].get('name', f'步骤{step_idx+1}') + exam_record["step_details"][step_name] = { + "max_score": step_record['max_score'], + "current_score": step_record['current_score'], + "tool_error": step_record['tool_error'], + "operation_attempts": step_record['operation_attempts'] + } + + # 保存到文件 + filename = f"exam_result_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + filepath = os.path.join(".", filename) + + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(exam_record, f, ensure_ascii=False, indent=2) + + print(f"📄 考核结果已保存到: {filepath}") + + except Exception as e: + print(f"⚠️ 保存考核结果失败: {e}") + # 不影响主流程,只是记录警告 def stop_interaction_mode(self): """停止交互模式""" diff --git a/core/maintenance_gui.py b/core/maintenance_gui.py new file mode 100644 index 00000000..a5cadb99 --- /dev/null +++ b/core/maintenance_gui.py @@ -0,0 +1,1023 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +维修系统场景GUI界面 +包括步骤显示、工具选择按钮和警告提示 +""" + +from direct.gui.DirectGui import * +from direct.gui import DirectGuiGlobals as DGG +from direct.showbase.DirectObject import DirectObject +from direct.task import Task +from panda3d.core import TextNode, Vec3, Vec4 + +# 引入全局变量 +import builtins + + +class MaintenanceGUI(DirectObject): + """维修系统GUI管理器""" + + def __init__(self, world): + DirectObject.__init__(self) + self.world = world + + # 获取GUI父节点引用 + self.aspect2d = None + print(f"🔍 初始化维修GUI,world类型: {type(world).__name__}") + print(f"🔍 world是否有aspect2d: {hasattr(world, 'aspect2d')}") + + if hasattr(world, 'aspect2d'): + self.aspect2d = world.aspect2d + print("✅ 从world获取aspect2d成功") + else: + print("❌ world没有aspect2d属性") + + # GUI元素 + self.step_text = None # 步骤显示文本 + self.current_tool_text = None # 当前工具显示文本 + self.warning_text = None # 警告文本 + self.tool_buttons = [] # 工具按钮列表 + + # 状态 + self.current_tool = "手" # 当前选择的工具 + self.available_tools = [] # 可用工具列表 + self.current_step_info = "" # 当前步骤信息 + self.mode = "training" # 模式:training 或 exam + + # 警告显示任务 + self.warning_task = None + + print("✅ 维修系统GUI初始化完成") + + # 添加测试快捷键 + self.accept("t", self.test_tool_selection) # 按T键测试工具选择 + self.accept("1", lambda: self.test_specific_button(0)) # 按1键测试第一个按钮 + self.accept("2", lambda: self.test_specific_button(1)) # 按2键测试第二个按钮 + self.accept("3", lambda: self.test_specific_button(2)) # 按3键测试第三个按钮 + self.accept("p", self.print_button_positions) # 按P键打印按钮位置 + + # 添加鼠标监控任务 + self.mouse_monitor_task = None + + def setup_gui(self, tools_list, mode="training"): + """设置GUI界面""" + try: + print(f"🎨 设置维修系统GUI,工具列表: {tools_list}, 模式: {mode}") + + self.available_tools = tools_list if tools_list else ["手"] + self.mode = mode + + # 重新尝试获取aspect2d引用 + self.get_aspect2d_reference() + + # 清理现有GUI + self.cleanup_gui() + + # 创建步骤显示文本 + self.create_step_text() + + # 创建工具选择按钮(训练和考核模式都需要) + self.create_tool_buttons() + self.create_current_tool_text() + + # 创建警告文本(仅训练模式需要) + if mode == "training": + self.create_warning_text() + + print("✅ 维修系统GUI设置完成") + + # 启动鼠标监控任务(训练和考核模式都需要) + self.start_mouse_monitor() + + except Exception as e: + print(f"❌ 设置维修系统GUI失败: {e}") + import traceback + traceback.print_exc() + + def start_mouse_monitor(self): + """启动鼠标监控任务""" + try: + # 停止可能存在的旧任务 + if self.mouse_monitor_task: + taskMgr.remove(self.mouse_monitor_task) + + # 启动新的监控任务 + self.mouse_monitor_task = taskMgr.add(self.monitor_mouse_clicks, "mouse_monitor") + print("✅ 鼠标监控任务已启动") + print(f"✅ 任务管理器中的任务: {[task.getName() for task in taskMgr.getAllTasks()]}") + + # 测试一下任务是否能正常运行 + self.test_mouse_monitor() + + except Exception as e: + print(f"❌ 启动鼠标监控失败: {e}") + import traceback + traceback.print_exc() + + def test_mouse_monitor(self): + """测试鼠标监控功能""" + try: + print("🧪 测试鼠标监控功能...") + + # 尝试直接调用监控函数 + result = self.monitor_mouse_clicks(None) + print(f"🧪 监控函数调用结果: {result}") + + except Exception as e: + print(f"❌ 测试鼠标监控失败: {e}") + import traceback + traceback.print_exc() + + def monitor_mouse_clicks(self, task): + """监控鼠标点击并手动检测按钮""" + try: + # 添加计数器来减少日志输出 + if not hasattr(self, '_monitor_counter'): + self._monitor_counter = 0 + self._monitor_counter += 1 + + # 每100帧输出一次状态 + if self._monitor_counter % 100 == 1: + print(f"🔄 鼠标监控运行中... (帧 {self._monitor_counter})") + + # 检查鼠标状态 + mouse_watcher = None + if hasattr(self.world, 'mouseWatcherNode'): + mouse_watcher = self.world.mouseWatcherNode + if self._monitor_counter % 100 == 1: + print(f"✅ 从world获取mouseWatcherNode: {mouse_watcher}") + else: + # 使用全局base对象 + try: + import builtins + if hasattr(builtins, 'base') and hasattr(builtins.base, 'mouseWatcherNode'): + mouse_watcher = builtins.base.mouseWatcherNode + if self._monitor_counter % 100 == 1: + print(f"✅ 从base获取mouseWatcherNode: {mouse_watcher}") + except: + if self._monitor_counter % 100 == 1: + print("❌ 无法获取mouseWatcherNode") + + if mouse_watcher: + has_mouse = mouse_watcher.hasMouse() + if has_mouse: + mouse_pos = mouse_watcher.getMouse() + + # 检查左键状态 + mouse_x = mouse_pos.getX() + mouse_y = mouse_pos.getY() + + # 检查左键是否被按下 + is_button_down = mouse_watcher.isButtonDown('mouse1') + + # 如果没有记录当前状态,初始化 + if not hasattr(self, '_last_button_state'): + self._last_button_state = False + + # 检测按钮从未按下到按下的状态变化(按下瞬间) + if is_button_down and not self._last_button_state: + print(f"🖱️ 鼠标监控:检测到按钮按下,位置({mouse_x:.3f}, {mouse_y:.3f})") + + # 检查这次点击是否在按钮区域 + clicked_button = self.check_button_click(mouse_x, mouse_y) + if clicked_button is not None: + print(f"🎯 鼠标监控检测到按钮点击: {clicked_button}") + self.on_tool_selected(clicked_button) + + # 更新按钮状态 + self._last_button_state = is_button_down + else: + if self._monitor_counter % 100 == 1: + print("❌ mouseWatcher为空") + + return task.cont if task else None + + except Exception as e: + print(f"❌ 鼠标监控异常: {e}") + import traceback + traceback.print_exc() + return task.cont if task else None + + def check_button_click(self, mouse_x, mouse_y): + """检查鼠标点击是否在按钮区域""" + try: + button_width = 0.4 # 与create_tool_buttons保持一致 + button_height = 0.25 # 与create_tool_buttons保持一致 + button_spacing = 0.45 # 与create_tool_buttons保持一致 + start_x = -0.8 # 与create_tool_buttons保持一致 + start_y = -0.75 # 与create_tool_buttons保持一致 + + print(f"🔍 检查按钮点击:鼠标({mouse_x:.3f}, {mouse_y:.3f})") + + for i, tool_data in enumerate(self.available_tools): + # 获取工具名称 + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + # 计算按钮位置 + button_x = start_x + i * button_spacing + button_y = start_y + + # 计算按钮边界 + left = button_x - button_width/2 + right = button_x + button_width/2 + bottom = button_y - button_height/2 + top = button_y + button_height/2 + + print(f" 按钮{i} '{tool_name}': 中心({button_x:.3f}, {button_y:.3f}), 范围x[{left:.3f}, {right:.3f}], y[{bottom:.3f}, {top:.3f}]") + + # 检查鼠标是否在按钮范围内 + if (left <= mouse_x <= right and bottom <= mouse_y <= top): + print(f"✅ 点击在按钮{i} '{tool_name}' 范围内") + return tool_name + + print("❌ 点击不在任何按钮范围内") + return None + + except Exception as e: + print(f"❌ 检查按钮点击异常: {e}") + return None + + def handle_mouse_click(self, qt_x, qt_y): + """直接处理鼠标点击(从拆装交互系统调用)""" + try: + print(f"🖱️ 直接处理鼠标点击:Qt坐标({qt_x}, {qt_y})") + + # 将Qt坐标转换为aspect2d坐标 + # 获取窗口尺寸 + if hasattr(self.world, 'qtWidget') and self.world.qtWidget: + widget_width = self.world.qtWidget.width() + widget_height = self.world.qtWidget.height() + else: + widget_width = 1380 + widget_height = 750 + + # 坐标转换(与之前拆装交互系统中的算法一致) + aspect_x = (qt_x / widget_width) * 2.67 - 1.33 + aspect_y = 1.0 - (qt_y / widget_height) * 2.0 + + print(f"🔄 坐标转换:Qt({qt_x}, {qt_y}) -> aspect2d({aspect_x:.3f}, {aspect_y:.3f})") + + # 检查是否点击在按钮区域 + clicked_button = self.check_button_click(aspect_x, aspect_y) + if clicked_button: + print(f"🎯 直接处理:检测到按钮点击 '{clicked_button}'") + self.on_tool_selected(clicked_button) + return True + + return False + + except Exception as e: + print(f"❌ 直接处理鼠标点击失败: {e}") + import traceback + traceback.print_exc() + return False + + def get_aspect2d_reference(self): + """获取aspect2d引用""" + if self.aspect2d: + return # 已经有引用了 + + try: + print("🔍 重新尝试获取aspect2d引用...") + + # 方法1: 直接使用world对象(推荐) + if hasattr(self.world, 'aspect2d'): + self.aspect2d = self.world.aspect2d + print("✅ 从world获取aspect2d成功") + return + + # 方法2: 使用world对象作为父节点 + # 在main.py的Panda3DWorld中,world对象本身就是ShowBase的实例 + self.aspect2d = self.world.aspect2d if hasattr(self.world, 'aspect2d') else None + + if self.aspect2d: + print("✅ 使用world.aspect2d成功") + else: + print("❌ 无法获取aspect2d引用") + + except Exception as e: + print(f"❌ 获取aspect2d引用失败: {e}") + import traceback + traceback.print_exc() + + def create_step_text(self): + """创建步骤显示文本""" + try: + if not self.aspect2d: + print("❌ aspect2d引用不可用,无法创建步骤文本") + return + + self.step_text = DirectLabel( + text="准备开始维修训练...", + text_align=TextNode.ALeft, + text_scale=0.08, + text_fg=(1, 1, 1, 1), + text_bg=(0, 0, 0, 0.7), + frameColor=(0, 0, 0, 0.5), + frameSize=(-1.8, 1.8, -0.3, 0.3), + pos=(-1.7, 0, 0.8), + parent=self.aspect2d + ) + print("✅ 步骤显示文本创建成功") + + except Exception as e: + print(f"❌ 创建步骤文本失败: {e}") + import traceback + traceback.print_exc() + + def create_tool_buttons(self): + """创建工具选择按钮""" + try: + print(f"🔧 开始创建工具按钮...") + print(f" aspect2d引用: {self.aspect2d}") + print(f" 可用工具: {self.available_tools}") + print(f" 模式: {self.mode}") + + if not self.aspect2d: + print("❌ aspect2d引用不可用,无法创建工具按钮") + print(" 尝试重新获取aspect2d引用...") + self.get_aspect2d_reference() + if not self.aspect2d: + print("❌ 重新获取aspect2d失败") + return + + print(f"🔧 使用aspect2d创建工具按钮: {self.available_tools}") + + # 按钮布局参数(优化为更容易点击) + button_width = 0.4 # 增加宽度 + button_height = 0.25 # 增加高度,让按钮更容易点击 + button_spacing = 0.45 # 增加间距 + start_x = -0.8 # 进一步向右移动 + start_y = -0.75 # 向上移动一点 + + for i, tool in enumerate(self.available_tools): + # 处理工具数据格式 + if isinstance(tool, dict): + tool_name = tool.get('name', str(tool)) + tool_description = tool.get('description', '') + else: + tool_name = str(tool) + tool_description = '' + + print(f" 正在创建按钮 {i+1}/{len(self.available_tools)}: {tool_name}") + + # 计算按钮位置(横向排列) + pos_x = start_x + i * button_spacing + pos_y = start_y + + # 创建按钮 + button = DirectButton( + text=tool_name, + text_scale=0.06, + text_fg=(1, 1, 1, 1), + frameColor=(0.3, 0.3, 0.8, 0.8), + frameSize=(-button_width/2, button_width/2, -button_height/2, button_height/2), + pos=(pos_x, 0, pos_y), + command=self.on_tool_selected, + extraArgs=[tool_name], + parent=self.aspect2d, + # 按钮交互设置 + relief=DGG.RAISED, + borderWidth=(0.01, 0.01), + text_pos=(0, -0.02), + # 强制启用鼠标交互 + enableEdit=1, + # 音效设置 + clickSound=None, + rolloverSound=None, + # 确保按钮优先级和可见性 + sortOrder=1000, # 更高的排序值 + state=DGG.NORMAL, # 确保按钮状态正常 + # 鼠标事件设置 + suppressMouse=0 + ) + + # 手动绑定额外的事件监听 + button.bind(DGG.B1PRESS, self.button_press_handler, extraArgs=[tool_name]) + button.bind(DGG.B1RELEASE, self.button_release_handler, extraArgs=[tool_name]) + + self.tool_buttons.append(button) + print(f" ✅ 成功创建工具按钮: {tool_name} at ({pos_x}, {pos_y})") + + print(f"🎯 总共创建了 {len(self.tool_buttons)} 个工具按钮") + + # 设置默认选择的工具样式 + self.update_tool_button_styles() + + except Exception as e: + print(f"❌ 创建工具按钮失败: {e}") + import traceback + traceback.print_exc() + + def create_current_tool_text(self): + """创建当前工具显示文本""" + try: + if not self.aspect2d: + print("❌ aspect2d引用不可用,无法创建当前工具文本") + return + + self.current_tool_text = DirectLabel( + text=f"当前工具: {self.current_tool}", + text_align=TextNode.ALeft, + text_scale=0.07, + text_fg=(1, 1, 0, 1), + text_bg=(0, 0, 0, 0.7), + frameColor=(0, 0, 0, 0.5), + frameSize=(-0.8, 0.8, -0.2, 0.2), + pos=(1.0, 0, 0.8), + parent=self.aspect2d + ) + print("✅ 当前工具文本创建成功") + + except Exception as e: + print(f"❌ 创建当前工具文本失败: {e}") + import traceback + traceback.print_exc() + + def create_warning_text(self): + """创建警告文本""" + try: + if not self.aspect2d: + print("❌ aspect2d引用不可用,无法创建警告文本") + return + + self.warning_text = DirectLabel( + text="", + text_align=TextNode.ACenter, + text_scale=0.09, + text_fg=(1, 0, 0, 1), + text_bg=(1, 1, 0, 0.8), + frameColor=(1, 0, 0, 0.8), + frameSize=(-1.5, 1.5, -0.3, 0.3), + pos=(0, 0, 0.3), + parent=self.aspect2d + ) + # 默认隐藏警告文本 + self.warning_text.hide() + print("✅ 警告文本创建成功") + + except Exception as e: + print(f"❌ 创建警告文本失败: {e}") + import traceback + traceback.print_exc() + + def button_press_handler(self, tool_name, event=None): + """按钮按下事件处理""" + print(f"🖱️ 按钮按下事件:{tool_name}") + + def button_release_handler(self, tool_name, event=None): + """按钮释放事件处理""" + print(f"🖱️ 按钮释放事件:{tool_name}") + print(f"🔄 直接调用工具选择:{tool_name}") + self.on_tool_selected(tool_name) + + def on_tool_selected(self, tool): + """工具选择回调""" + try: + print(f"🎯 按钮点击事件触发!") + print(f"🔧 选择工具: {tool}") + + # 考核模式下额外提示 + if hasattr(self, 'mode') and self.mode == "exam": + print(f"📝 考核模式工具选择: {tool}") + + print(f"📍 当前工具变更: {self.current_tool} -> {tool}") + + self.current_tool = tool + + # 更新当前工具显示 + if self.current_tool_text: + self.current_tool_text['text'] = f"当前工具: {tool}" + print(f"✅ 当前工具显示已更新") + + # 更新按钮样式 + self.update_tool_button_styles() + print(f"✅ 按钮样式已更新") + + # 通知拆装交互系统工具变更 + if hasattr(self.world, 'assembly_interaction') and self.world.assembly_interaction: + if hasattr(self.world.assembly_interaction, 'step_dialog') and self.world.assembly_interaction.step_dialog: + # 更新步骤对话框中的工具选择 + step_dialog = self.world.assembly_interaction.step_dialog + if hasattr(step_dialog, 'tool_combo'): + # 同步工具选择 + tool_index = -1 + for i in range(step_dialog.tool_combo.count()): + if step_dialog.tool_combo.itemText(i) == tool: + tool_index = i + break + if tool_index >= 0: + step_dialog.tool_combo.setCurrentIndex(tool_index) + print(f"✅ Qt对话框工具选择已同步") + + print(f"✅ 工具切换完成: {tool}") + + except Exception as e: + print(f"❌ 工具选择失败: {e}") + import traceback + traceback.print_exc() + + def update_tool_button_styles(self): + """更新工具按钮样式""" + try: + for i, button in enumerate(self.tool_buttons): + # 处理工具数据格式 + tool_data = self.available_tools[i] + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + if tool_name == self.current_tool: + # 选中状态:亮蓝色 + button['frameColor'] = (0.2, 0.8, 1.0, 0.9) + button['text_fg'] = (0, 0, 0, 1) + else: + # 未选中状态:深蓝色 + button['frameColor'] = (0.3, 0.3, 0.8, 0.8) + button['text_fg'] = (1, 1, 1, 1) + + except Exception as e: + print(f"❌ 更新按钮样式失败: {e}") + import traceback + traceback.print_exc() + + def update_step_info(self, step_text): + """更新步骤信息""" + try: + print(f"📋 更新步骤信息: {step_text}") + + self.current_step_info = step_text + + if self.step_text: + self.step_text['text'] = step_text + + except Exception as e: + print(f"❌ 更新步骤信息失败: {e}") + + def show_warning(self, message, duration=2.0): + """显示警告信息(仅训练模式)""" + try: + # 考核模式下不显示警告 + if hasattr(self, 'mode') and self.mode == "exam": + print(f"📝 考核模式:隐藏警告 - {message}") + return + + print(f"⚠️ 显示警告: {message}") + + if not self.warning_text: + return + + # 取消之前的警告任务 + if self.warning_task: + taskMgr.remove(self.warning_task) + self.warning_task = None + + # 显示警告文本 + self.warning_text['text'] = message + self.warning_text.show() + + # 设置自动隐藏任务 + self.warning_task = taskMgr.doMethodLater( + duration, + self.hide_warning, + 'hide_warning_task' + ) + + except Exception as e: + print(f"❌ 显示警告失败: {e}") + + def hide_warning(self, task=None): + """隐藏警告信息""" + try: + if self.warning_text: + self.warning_text.hide() + + if self.warning_task: + self.warning_task = None + + return Task.done + + except Exception as e: + print(f"❌ 隐藏警告失败: {e}") + return Task.done + + def test_tool_selection(self): + """测试工具选择功能(按T键触发)""" + if self.available_tools: + # 循环选择工具进行测试 + current_index = 0 + for i, tool_data in enumerate(self.available_tools): + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + if tool_name == self.current_tool: + current_index = i + break + + # 选择下一个工具 + next_index = (current_index + 1) % len(self.available_tools) + next_tool_data = self.available_tools[next_index] + + if isinstance(next_tool_data, dict): + next_tool_name = next_tool_data.get('name', str(next_tool_data)) + else: + next_tool_name = str(next_tool_data) + + print(f"⌨️ 键盘测试:切换到工具 '{next_tool_name}'") + self.on_tool_selected(next_tool_name) + + def test_specific_button(self, button_index): + """测试特定按钮(通过索引)""" + if not hasattr(self, 'available_tools') or not self.available_tools: + print(f"⌨️ 按钮{button_index}: 没有可用工具") + return + + if button_index >= len(self.available_tools): + print(f"⌨️ 按钮{button_index}: 索引超出范围(最大{len(self.available_tools)-1})") + return + + # 获取工具名称 + tool_data = self.available_tools[button_index] + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + print(f"⌨️ 按钮{button_index}测试:切换到工具 '{tool_name}'") + self.on_tool_selected(tool_name) + + def print_button_positions(self): + """打印所有按钮的位置信息""" + try: + if not hasattr(self, 'available_tools') or not self.available_tools: + print("📍 没有可用工具按钮") + return + + print("📍 ===== 按钮位置信息 =====") + + button_width = 0.4 + button_height = 0.25 + button_spacing = 0.45 + start_x = -0.8 + start_y = -0.75 + + for i, tool_data in enumerate(self.available_tools): + # 获取工具名称 + if isinstance(tool_data, dict): + tool_name = tool_data.get('name', str(tool_data)) + else: + tool_name = str(tool_data) + + # 计算按钮位置 + button_x = start_x + i * button_spacing + button_y = start_y + + # 计算按钮边界 + left = button_x - button_width/2 + right = button_x + button_width/2 + bottom = button_y - button_height/2 + top = button_y + button_height/2 + + print(f"📍 按钮{i} '{tool_name}':") + print(f" 中心: ({button_x:.3f}, {button_y:.3f})") + print(f" 范围: x[{left:.3f}, {right:.3f}], y[{bottom:.3f}, {top:.3f}]") + print(f" 尺寸: {button_width} x {button_height}") + + print("📍 ========================") + + except Exception as e: + print(f"❌ 打印按钮位置失败: {e}") + import traceback + traceback.print_exc() + + def show_exam_results(self, exam_data): + """显示考核结果(GUI界面)""" + try: + print("🎓 开始显示考核结果GUI...") + + # 清理现有GUI元素 + self.cleanup_gui() + + # 创建考核结果界面 + self.create_exam_result_gui(exam_data) + + print("✅ 考核结果GUI显示完成") + + except Exception as e: + print(f"❌ 显示考核结果GUI失败: {e}") + import traceback + traceback.print_exc() + + def create_exam_result_gui(self, exam_data): + """创建考核结果GUI界面""" + try: + if not self.aspect2d: + print("❌ aspect2d引用不可用,无法创建考核结果GUI") + return + + # 主背景 - 黑色背景 + from direct.gui.DirectGui import DirectFrame + self.exam_bg = DirectFrame( + frameColor=(0, 0, 0, 0.95), # 更深的黑色背景 + frameSize=(-2.0, 2.0, -1.0, 1.0), + pos=(0, 0, 0), + parent=self.aspect2d + ) + + # 标题 + self.exam_title = DirectLabel( + text="🎓 考核结果报告", + text_align=TextNode.ACenter, + text_scale=0.12, + text_fg=(1, 1, 0, 1), # 黄色文字 + text_bg=(0, 0, 0, 0), # 透明背景 + frameColor=(0, 0, 0, 0), # 透明边框 + frameSize=(-1.5, 1.5, -0.15, 0.15), + pos=(0, 0, 0.8), + parent=self.aspect2d + ) + + # 总体成绩区域 + score_text = f"📊 总得分: {exam_data['final_score']:.0f}/{exam_data['max_score']:.0f} 分 ({exam_data['score_rate']:.1f}%)\n" + score_text += f"🏅 评级: {exam_data['grade_icon']} {exam_data['grade']} - {exam_data['grade_desc']}\n" + score_text += f"🎯 操作准确率: {exam_data['accuracy_rate']:.1f}% | 完美步骤: {exam_data['perfect_steps']}/{exam_data['total_steps']}" + + # 根据成绩选择文字颜色(在黑色背景下更突出) + if exam_data['score_rate'] >= 90: + score_fg_color = (0.2, 1.0, 0.2, 1) # 亮绿色 + elif exam_data['score_rate'] >= 80: + score_fg_color = (0.8, 1.0, 0.2, 1) # 亮黄绿色 + elif exam_data['score_rate'] >= 60: + score_fg_color = (1.0, 0.8, 0.2, 1) # 亮橙色 + else: + score_fg_color = (1.0, 0.3, 0.3, 1) # 亮红色 + + self.exam_score = DirectLabel( + text=score_text, + text_align=TextNode.ACenter, + text_scale=0.07, + text_fg=score_fg_color, # 根据成绩动态变色 + text_bg=(0, 0, 0, 0), # 透明背景 + frameColor=(0, 0, 0, 0), # 透明边框 + frameSize=(-1.9, 1.9, -0.3, 0.3), + pos=(0, 0, 0.35), + parent=self.aspect2d + ) + + # 步骤详情区域 + steps_text = "📋 步骤详情:\n" + for step in exam_data['step_details']: + steps_text += f"{step['status']} {step['name']}: {step['current_score']:.0f}/{step['max_score']:.0f}分" + if step['tool_error']: + steps_text += " ❌工具错误" + steps_text += f" ({step['attempts']}次操作)\n" + + self.exam_steps = DirectLabel( + text=steps_text, + text_align=TextNode.ALeft, + text_scale=0.06, + text_fg=(0.9, 0.9, 0.9, 1), # 浅灰色文字 + text_bg=(0, 0, 0, 0), # 透明背景 + frameColor=(0, 0, 0, 0), # 透明边框 + frameSize=(-1.8, 1.8, -0.4, 0.4), + pos=(0, 0, -0.1), + parent=self.aspect2d + ) + + # 改进建议区域(如果有建议) + if exam_data['suggestions']: + suggestions_text = "💡 改进建议:\n" + for suggestion in exam_data['suggestions']: + suggestions_text += f"{suggestion}\n" + + self.exam_suggestions = DirectLabel( + text=suggestions_text, + text_align=TextNode.ALeft, + text_scale=0.05, + text_fg=(1, 0.8, 0.2, 1), # 橙黄色文字 + text_bg=(0, 0, 0, 0), # 透明背景 + frameColor=(0, 0, 0, 0), # 透明边框 + frameSize=(-1.8, 1.8, -0.25, 0.25), + pos=(0, 0, -0.7), + parent=self.aspect2d + ) + + # 倒计时显示 + self.exam_countdown_text = DirectLabel( + text="10秒后自动关闭", + text_align=TextNode.ACenter, + text_scale=0.06, + text_fg=(0.8, 0.8, 0.8, 1), # 灰色文字 + text_bg=(0, 0, 0, 0), # 透明背景 + frameColor=(0, 0, 0, 0), # 透明边框 + frameSize=(-0.8, 0.8, -0.1, 0.1), + pos=(0, 0, -0.8), + parent=self.aspect2d + ) + + # 启动10秒倒计时任务 + self.start_countdown_timer() + + print("✅ 考核结果GUI元素创建完成") + + except Exception as e: + print(f"❌ 创建考核结果GUI失败: {e}") + import traceback + traceback.print_exc() + + def close_exam_results(self): + """关闭考核结果界面""" + try: + print("🔄 关闭考核结果界面...") + + # 清理考核结果GUI元素 + if hasattr(self, 'exam_bg') and self.exam_bg: + self.exam_bg.removeNode() + self.exam_bg = None + + if hasattr(self, 'exam_title') and self.exam_title: + self.exam_title.removeNode() + self.exam_title = None + + if hasattr(self, 'exam_score') and self.exam_score: + self.exam_score.removeNode() + self.exam_score = None + + if hasattr(self, 'exam_steps') and self.exam_steps: + self.exam_steps.removeNode() + self.exam_steps = None + + if hasattr(self, 'exam_suggestions') and self.exam_suggestions: + self.exam_suggestions.removeNode() + self.exam_suggestions = None + + if hasattr(self, 'exam_countdown_text') and self.exam_countdown_text: + self.exam_countdown_text.removeNode() + self.exam_countdown_text = None + + # 停止倒计时任务 + if hasattr(self, 'exam_countdown_task') and self.exam_countdown_task: + taskMgr.remove(self.exam_countdown_task) + self.exam_countdown_task = None + + print("✅ 考核结果界面已关闭") + + except Exception as e: + print(f"❌ 关闭考核结果界面失败: {e}") + import traceback + traceback.print_exc() + + def start_countdown_timer(self): + """启动10秒倒计时""" + try: + self.countdown_seconds = 10 + print(f"⏰ 启动考核结果倒计时:{self.countdown_seconds}秒") + + # 启动倒计时任务 + self.exam_countdown_task = taskMgr.doMethodLater( + 1.0, # 每秒执行一次 + self.update_countdown, + 'exam_countdown_task' + ) + + except Exception as e: + print(f"❌ 启动倒计时失败: {e}") + import traceback + traceback.print_exc() + + def update_countdown(self, task): + """更新倒计时显示""" + try: + self.countdown_seconds -= 1 + + if self.countdown_seconds > 0: + # 更新倒计时显示 + if hasattr(self, 'exam_countdown_text') and self.exam_countdown_text: + self.exam_countdown_text['text'] = f"{self.countdown_seconds}秒后自动关闭" + + print(f"⏰ 倒计时:{self.countdown_seconds}秒") + + # 继续倒计时 + return task.again + else: + # 倒计时结束,自动关闭 + print("⏰ 倒计时结束,自动关闭考核结果界面") + self.close_exam_results() + return task.done + + except Exception as e: + print(f"❌ 更新倒计时失败: {e}") + import traceback + traceback.print_exc() + return task.done + + + def get_current_tool(self): + """获取当前选择的工具""" + return self.current_tool + + def set_mode(self, mode): + """设置模式""" + self.mode = mode + print(f"🎯 维修GUI模式设置为: {mode}") + + def cleanup_gui(self): + """清理GUI元素""" + try: + print("🧹 清理维修系统GUI") + + # 清理步骤文本 + if self.step_text: + self.step_text.destroy() + self.step_text = None + + # 清理当前工具文本 + if self.current_tool_text: + self.current_tool_text.destroy() + self.current_tool_text = None + + # 清理警告文本 + if self.warning_text: + self.warning_text.destroy() + self.warning_text = None + + # 清理工具按钮 + for button in self.tool_buttons: + if button: + button.destroy() + self.tool_buttons.clear() + + # 取消警告任务 + if self.warning_task: + taskMgr.remove(self.warning_task) + self.warning_task = None + + # 取消鼠标监控任务 + if self.mouse_monitor_task: + taskMgr.remove(self.mouse_monitor_task) + self.mouse_monitor_task = None + + # 清理考核结果界面 + self.close_exam_results() + + print("✅ 维修系统GUI清理完成") + + except Exception as e: + print(f"❌ 清理维修系统GUI失败: {e}") + + def show_gui(self): + """显示GUI""" + try: + print(f"🎨 显示维修系统GUI...") + print(f" 模式: {self.mode}") + print(f" 步骤文本: {self.step_text is not None}") + print(f" 当前工具文本: {self.current_tool_text is not None}") + print(f" 工具按钮数量: {len(self.tool_buttons)}") + + if self.step_text: + self.step_text.show() + print(" ✅ 步骤文本已显示") + + if self.mode == "training": + if self.current_tool_text: + self.current_tool_text.show() + print(" ✅ 当前工具文本已显示") + + for i, button in enumerate(self.tool_buttons): + button.show() + print(f" ✅ 工具按钮 {i+1} 已显示") + + print("✅ 维修系统GUI显示完成") + + except Exception as e: + print(f"❌ 显示维修系统GUI失败: {e}") + import traceback + traceback.print_exc() + + def hide_gui(self): + """隐藏GUI""" + try: + if self.step_text: + self.step_text.hide() + + if self.current_tool_text: + self.current_tool_text.hide() + + if self.warning_text: + self.warning_text.hide() + + for button in self.tool_buttons: + button.hide() + + print("✅ 维修系统GUI隐藏") + + except Exception as e: + print(f"❌ 隐藏维修系统GUI失败: {e}") \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index addf2aa3..01dc5b6c 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -3516,18 +3516,24 @@ class MainWindow(QMainWindow): with open(subject_path, 'r', encoding='utf-8') as f: subject_config = json.load(f) - # 启动拆装交互系统 - if hasattr(self.world, 'assembly_interaction') and self.world.assembly_interaction: - # 设置配置 - self.world.assembly_interaction.config_data = subject_config - - # 启动交互模式 - self.world.assembly_interaction.start_interaction_mode(mode=mode) - + # 初始化拆装交互系统(如果还没有) + from core.assembly_interaction import AssemblyInteractionManager + + if not hasattr(self.world, 'assembly_interaction') or not self.world.assembly_interaction: + print("🔧 初始化拆装交互系统...") + self.world.assembly_interaction = AssemblyInteractionManager(self.world) + + # 设置配置并启动交互模式 + self.world.assembly_interaction.config_data = subject_config + + # 启动交互模式 + success = self.world.assembly_interaction.start_interaction_mode(mode=mode) + + if success: print(f"✅ 维修科目启动成功") else: - print("❌ 错误:拆装交互系统未初始化") - QMessageBox.warning(self, "错误", "拆装交互系统未初始化") + print("❌ 维修科目启动失败") + QMessageBox.warning(self, "错误", "维修科目启动失败") except Exception as e: print(f"❌ 启动维修科目失败: {e}") diff --git a/ui/maintenance_system.py b/ui/maintenance_system.py new file mode 100644 index 00000000..77d609ea --- /dev/null +++ b/ui/maintenance_system.py @@ -0,0 +1,658 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +维修系统界面模块 +包含登录界面、科目列表、模式选择等功能 +""" + +import os +import json +from PyQt5.QtWidgets import ( + QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, + QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem, + QMessageBox, QRadioButton, QTextEdit, QSplitter +) +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QFont, QPixmap + + +class MaintenanceLoginDialog(QDialog): + """维修系统登录对话框""" + + login_success = pyqtSignal() # 登录成功信号 + + def __init__(self, parent=None): + super().__init__(parent) + self.setupUI() + + def setupUI(self): + """设置用户界面""" + self.setWindowTitle("维修系统 - 登录") + self.setFixedSize(400, 300) + self.setModal(True) + + # 应用简化样式 - 确保可见性和可用性 + self.setStyleSheet(""" + QDialog { + background-color: #f5f5f5; + color: #333333; + } + QLabel { + color: #333333; + font-size: 14px; + } + QLineEdit { + background-color: white; + color: #333333; + border: 2px solid #cccccc; + border-radius: 4px; + padding: 8px; + font-size: 14px; + min-height: 20px; + } + QLineEdit:focus { + border-color: #0078d4; + } + QPushButton { + background-color: #0078d4; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + min-height: 30px; + } + QPushButton:hover { + background-color: #106ebe; + } + QPushButton:pressed { + background-color: #005a9e; + } + QGroupBox { + font-size: 16px; + font-weight: bold; + color: #0078d4; + border: 2px solid #cccccc; + border-radius: 4px; + margin-top: 10px; + padding-top: 10px; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 5px 0 5px; + } + """) + + layout = QVBoxLayout(self) + layout.setSpacing(15) + layout.setContentsMargins(20, 20, 20, 20) + + # 标题 + title_label = QLabel("🔧 维修系统") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + font-size: 20px; + font-weight: bold; + color: #0078d4; + margin: 10px 0px; + """) + layout.addWidget(title_label) + + # 登录表单 + login_group = QGroupBox("身份验证") + login_layout = QFormLayout(login_group) + login_layout.setSpacing(10) + login_layout.setContentsMargins(15, 15, 15, 15) + + # 用户名 + self.username_edit = QLineEdit() + self.username_edit.setPlaceholderText("请输入用户名") + self.username_edit.setText("") # 确保清空 + login_layout.addRow("用户名:", self.username_edit) + + # 密码 + self.password_edit = QLineEdit() + self.password_edit.setEchoMode(QLineEdit.Password) + self.password_edit.setPlaceholderText("请输入密码") + self.password_edit.setText("") # 确保清空 + login_layout.addRow("密码:", self.password_edit) + + layout.addWidget(login_group) + + # 提示信息 + hint_label = QLabel("💡 默认账号密码均为: admin") + hint_label.setAlignment(Qt.AlignCenter) + hint_label.setStyleSheet(""" + color: #666666; + font-size: 12px; + margin: 5px 0px; + """) + layout.addWidget(hint_label) + + # 按钮 + button_layout = QHBoxLayout() + button_layout.setSpacing(10) + + self.cancel_btn = QPushButton("取消") + self.login_btn = QPushButton("登录") + + button_layout.addStretch() + button_layout.addWidget(self.cancel_btn) + button_layout.addWidget(self.login_btn) + + layout.addLayout(button_layout) + + # 连接信号 + self.login_btn.clicked.connect(self.on_login) + self.cancel_btn.clicked.connect(self.reject) + self.password_edit.returnPressed.connect(self.on_login) + + # 设置默认焦点和tab顺序 + self.username_edit.setFocus() + self.setTabOrder(self.username_edit, self.password_edit) + self.setTabOrder(self.password_edit, self.login_btn) + self.setTabOrder(self.login_btn, self.cancel_btn) + + print("🔧 登录界面创建完成") + print(f"📝 用户名输入框: {self.username_edit}") + print(f"📝 密码输入框: {self.password_edit}") + print(f"📝 登录按钮: {self.login_btn}") + print(f"📝 取消按钮: {self.cancel_btn}") + + def on_login(self): + """处理登录""" + username = self.username_edit.text().strip() + password = self.password_edit.text().strip() + + # 验证账号密码 + if username == "admin" and password == "admin": + print("✅ 维修系统登录成功") + self.login_success.emit() + self.accept() + else: + QMessageBox.warning(self, "登录失败", + "用户名或密码错误!\n请使用: admin / admin") + self.password_edit.clear() + self.password_edit.setFocus() + + +class MaintenanceSubjectDialog(QDialog): + """维修系统科目选择对话框""" + + subject_selected = pyqtSignal(str, str) # 科目路径, 模式 + + def __init__(self, project_path, parent=None): + super().__init__(parent) + self.project_path = project_path + self.subjects_path = os.path.join(project_path, "Subjects") if project_path else None + self.current_subject_path = None + self.setupUI() + self.load_subjects() + + def setupUI(self): + """设置用户界面""" + self.setWindowTitle("维修系统 - 科目选择") + self.setFixedSize(800, 600) + self.setModal(True) + + # 应用样式 + self.setStyleSheet(""" + QDialog { + background-color: #1e1e2e; + color: #e0e0ff; + } + QLabel { + color: #e0e0ff; + } + QListWidget { + background-color: #2d2d44; + color: #e0e0ff; + border: 2px solid #3a3a4a; + border-radius: 8px; + padding: 5px; + font-size: 14px; + } + QListWidget::item { + padding: 8px; + border-radius: 4px; + margin: 2px; + } + QListWidget::item:hover { + background-color: #3a3a4a; + } + QListWidget::item:selected { + background-color: #8b5cf6; + color: white; + } + QTextEdit { + background-color: #2d2d44; + color: #e0e0ff; + border: 2px solid #3a3a4a; + border-radius: 8px; + padding: 8px; + font-size: 12px; + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 10px 20px; + border-radius: 8px; + font-weight: bold; + font-size: 14px; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + QGroupBox { + font-size: 14px; + font-weight: bold; + color: #8b5cf6; + border: 2px solid #3a3a4a; + border-radius: 8px; + margin: 10px 0px; + padding-top: 15px; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 5px 0 5px; + } + QRadioButton { + color: #e0e0ff; + font-size: 14px; + spacing: 8px; + } + QRadioButton::indicator { + width: 16px; + height: 16px; + } + QRadioButton::indicator:unchecked { + border: 2px solid #3a3a4a; + border-radius: 8px; + background-color: #2d2d44; + } + QRadioButton::indicator:checked { + border: 2px solid #8b5cf6; + border-radius: 8px; + background-color: #8b5cf6; + } + """) + + layout = QVBoxLayout(self) + + # 标题 + title_label = QLabel("📚 选择维修科目") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + font-size: 20px; + font-weight: bold; + color: #8b5cf6; + margin: 15px 0px; + """) + layout.addWidget(title_label) + + # 主要内容区域 + main_splitter = QSplitter(Qt.Horizontal) + + # 左侧:科目列表 + left_widget = QGroupBox("科目列表") + left_layout = QVBoxLayout(left_widget) + + self.subject_list = QListWidget() + self.subject_list.itemClicked.connect(self.on_subject_selected) + left_layout.addWidget(self.subject_list) + + main_splitter.addWidget(left_widget) + + # 右侧:科目详情和模式选择 + right_widget = QGroupBox("科目详情") + right_layout = QVBoxLayout(right_widget) + + # 科目描述 + self.subject_description = QTextEdit() + self.subject_description.setReadOnly(True) + self.subject_description.setMaximumHeight(150) + self.subject_description.setPlainText("请选择一个科目查看详情...") + right_layout.addWidget(self.subject_description) + + # 模式选择 + mode_group = QGroupBox("选择模式") + mode_layout = QVBoxLayout(mode_group) + + self.training_radio = QRadioButton("🎓 训练模式") + self.training_radio.setChecked(True) + mode_layout.addWidget(self.training_radio) + + training_desc = QLabel("• 显示详细的步骤描述和操作提示\n• 提供工具选择的正确性提示\n• 播放语音指导") + training_desc.setStyleSheet("color: #94a3b8; font-size: 12px; margin-left: 20px;") + mode_layout.addWidget(training_desc) + + self.exam_radio = QRadioButton("📋 考核模式") + mode_layout.addWidget(self.exam_radio) + + exam_desc = QLabel("• 不显示步骤描述\n• 不提供工具选择提示\n• 无语音指导,独立完成操作") + exam_desc.setStyleSheet("color: #94a3b8; font-size: 12px; margin-left: 20px;") + mode_layout.addWidget(exam_desc) + + right_layout.addWidget(mode_group) + + main_splitter.addWidget(right_widget) + + # 设置分割器比例 + main_splitter.setSizes([300, 500]) + layout.addWidget(main_splitter) + + # 底部按钮 + button_layout = QHBoxLayout() + + self.refresh_btn = QPushButton("🔄 刷新") + self.cancel_btn = QPushButton("取消") + self.start_btn = QPushButton("开始") + self.start_btn.setEnabled(False) + + button_layout.addWidget(self.refresh_btn) + button_layout.addStretch() + button_layout.addWidget(self.cancel_btn) + button_layout.addWidget(self.start_btn) + + layout.addLayout(button_layout) + + # 连接信号 + self.refresh_btn.clicked.connect(self.load_subjects) + self.cancel_btn.clicked.connect(self.reject) + self.start_btn.clicked.connect(self.on_start) + + def load_subjects(self): + """加载科目列表""" + self.subject_list.clear() + self.current_subject_path = None + self.start_btn.setEnabled(False) + self.subject_description.setPlainText("请选择一个科目查看详情...") + + if not self.subjects_path or not os.path.exists(self.subjects_path): + # 添加提示项 + item = QListWidgetItem("📁 未找到Subjects目录") + item.setData(Qt.UserRole, None) + self.subject_list.addItem(item) + + if not self.project_path: + self.subject_description.setPlainText( + "❌ 错误:没有打开的项目\n\n" + "请先通过 文件 -> 打开 菜单打开一个项目,\n" + "然后再使用维修系统功能。" + ) + else: + self.subject_description.setPlainText( + f"❌ 错误:找不到Subjects目录\n\n" + f"项目路径: {self.project_path}\n" + f"期望目录: {self.subjects_path}\n\n" + "请在项目根目录下创建Subjects文件夹,\n" + "并在其中放置科目的JSON配置文件。" + ) + return + + # 扫描JSON文件 + json_files = [] + try: + for file in os.listdir(self.subjects_path): + if file.endswith('.json'): + json_path = os.path.join(self.subjects_path, file) + json_files.append((file, json_path)) + except Exception as e: + print(f"❌ 扫描Subjects目录失败: {e}") + item = QListWidgetItem("❌ 读取目录失败") + item.setData(Qt.UserRole, None) + self.subject_list.addItem(item) + return + + if not json_files: + item = QListWidgetItem("📄 目录中没有JSON文件") + item.setData(Qt.UserRole, None) + self.subject_list.addItem(item) + self.subject_description.setPlainText( + f"ℹ️ 提示:Subjects目录为空\n\n" + f"目录路径: {self.subjects_path}\n\n" + "请在此目录中放置维修科目的JSON配置文件。" + ) + return + + # 加载科目 + loaded_count = 0 + for filename, filepath in json_files: + try: + with open(filepath, 'r', encoding='utf-8') as f: + subject_data = json.load(f) + + # 获取科目名称 + subject_name = subject_data.get('name', filename.replace('.json', '')) + + # 创建列表项 + item = QListWidgetItem(f"📋 {subject_name}") + item.setData(Qt.UserRole, { + 'path': filepath, + 'data': subject_data, + 'filename': filename + }) + self.subject_list.addItem(item) + loaded_count += 1 + + except Exception as e: + print(f"❌ 加载科目文件失败 {filename}: {e}") + item = QListWidgetItem(f"❌ {filename} (加载失败)") + item.setData(Qt.UserRole, None) + self.subject_list.addItem(item) + + print(f"✅ 成功加载 {loaded_count} 个维修科目") + + def on_subject_selected(self, item): + """处理科目选择""" + try: + print(f"📋 科目选择事件触发,item: {item}") + + if not item: + print("❌ item为空") + self.current_subject_path = None + self.start_btn.setEnabled(False) + self.subject_description.setPlainText("请选择一个有效的科目。") + return + + subject_info = item.data(Qt.UserRole) + print(f"📋 科目信息: {subject_info}") + + if not subject_info: + print("❌ 科目信息为空") + self.current_subject_path = None + self.start_btn.setEnabled(False) + self.subject_description.setPlainText("此科目无法加载,请检查文件格式。") + return + + self.current_subject_path = subject_info['path'] + self.start_btn.setEnabled(True) + + print(f"✅ 选择科目: {subject_info.get('filename', 'unknown')}") + + # 显示科目详情 + subject_data = subject_info['data'] + description = self.format_subject_description(subject_data, subject_info['filename']) + self.subject_description.setPlainText(description) + + print("✅ 科目详情显示完成") + + except Exception as e: + print(f"❌ 科目选择处理失败: {e}") + import traceback + traceback.print_exc() + + self.current_subject_path = None + self.start_btn.setEnabled(False) + self.subject_description.setPlainText(f"处理科目信息时出错:\n{str(e)}") + + # 显示错误对话框 + QMessageBox.critical(self, "错误", f"处理科目选择时出错:\n{str(e)}") + + def format_subject_description(self, subject_data, filename): + """格式化科目描述""" + try: + print(f"🔧 格式化科目描述: {filename}") + + if not subject_data: + return "❌ 科目数据为空" + + lines = [] + + # 基本信息 + name = subject_data.get('name', '未知科目') + lines.append(f"📋 科目: {name}") + lines.append(f"📁 文件: {filename}") + lines.append("") + + # 描述信息 + if 'description' in subject_data and subject_data['description']: + lines.append(f"📝 描述: {subject_data['description']}") + lines.append("") + + # 步骤信息 + steps = subject_data.get('steps', []) + if steps and isinstance(steps, list): + lines.append(f"📊 步骤数量: {len(steps)}") + + # 计算总分 + try: + total_score = 0 + for step in steps: + if isinstance(step, dict): + score = step.get('score', 0) + if isinstance(score, (int, float)): + total_score += score + + if total_score > 0: + lines.append(f"💯 总分: {total_score} 分") + except Exception as e: + print(f"⚠️ 计算总分时出错: {e}") + + lines.append("") + lines.append("📋 步骤列表:") + + for i, step in enumerate(steps, 1): + try: + if isinstance(step, dict): + step_name = step.get('name', f'步骤{i}') + step_score = step.get('score', 0) + + if isinstance(step_score, (int, float)) and step_score > 0: + lines.append(f" {i}. {step_name} ({step_score}分)") + else: + lines.append(f" {i}. {step_name}") + else: + lines.append(f" {i}. 步骤{i} (格式错误)") + except Exception as e: + print(f"⚠️ 处理步骤{i}时出错: {e}") + lines.append(f" {i}. 步骤{i} (处理错误)") + else: + lines.append("⚠️ 此科目没有配置步骤") + + # 工具信息 + tools = subject_data.get('tools', []) + if tools and isinstance(tools, list): + try: + valid_tools = [str(tool) for tool in tools if tool] + if valid_tools: + lines.append("") + lines.append(f"🔧 可用工具: {', '.join(valid_tools)}") + except Exception as e: + print(f"⚠️ 处理工具列表时出错: {e}") + lines.append("") + lines.append("🔧 可用工具: (处理错误)") + + result = '\n'.join(lines) + print(f"✅ 科目描述格式化完成,长度: {len(result)}") + return result + + except Exception as e: + print(f"❌ 格式化科目描述失败: {e}") + import traceback + traceback.print_exc() + + return f"❌ 格式化科目描述时出错:\n{str(e)}\n\n文件: {filename}" + + def on_start(self): + """开始科目""" + if not self.current_subject_path: + QMessageBox.warning(self, "错误", "请先选择一个科目!") + return + + # 获取选择的模式 + mode = "training" if self.training_radio.isChecked() else "exam" + + print(f"🚀 开始维修科目: {self.current_subject_path}, 模式: {mode}") + + # 发射信号 + self.subject_selected.emit(self.current_subject_path, mode) + self.accept() + + +class MaintenanceSystemManager: + """维修系统管理器""" + + def __init__(self, world): + self.world = world + self.login_dialog = None + self.subject_dialog = None + + def show_maintenance_system(self, parent_window): + """显示维修系统""" + # 创建登录对话框 + self.login_dialog = MaintenanceLoginDialog(parent_window) + self.login_dialog.login_success.connect( + lambda: self.show_subject_selection(parent_window) + ) + self.login_dialog.exec_() + + def show_subject_selection(self, parent_window): + """显示科目选择界面""" + # 获取当前项目路径 + project_path = None + if hasattr(self.world, 'project_manager') and self.world.project_manager: + project_path = self.world.project_manager.getCurrentProjectPath() + + # 创建科目选择对话框 + self.subject_dialog = MaintenanceSubjectDialog(project_path, parent_window) + self.subject_dialog.subject_selected.connect(self.start_maintenance_subject) + self.subject_dialog.exec_() + + def start_maintenance_subject(self, subject_path, mode): + """开始维修科目""" + try: + print(f"🎯 启动维修科目: {subject_path}") + print(f"📝 模式: {mode}") + + # 加载科目配置 + with open(subject_path, 'r', encoding='utf-8') as f: + subject_config = json.load(f) + + # 启动拆装交互系统 + if hasattr(self.world, 'assembly_interaction') and self.world.assembly_interaction: + # 设置配置 + self.world.assembly_interaction.config_data = subject_config + + # 启动交互模式 + self.world.assembly_interaction.start_interaction_mode(mode=mode) + + print(f"✅ 维修科目启动成功") + else: + print("❌ 错误:拆装交互系统未初始化") + + except Exception as e: + print(f"❌ 启动维修科目失败: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/ui/simple_maintenance_login.py b/ui/simple_maintenance_login.py new file mode 100644 index 00000000..2cf5ba45 --- /dev/null +++ b/ui/simple_maintenance_login.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +简化的维修系统登录界面 +解决显示和输入问题 +""" + +from PyQt5.QtWidgets import ( + QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, + QLineEdit, QPushButton, QLabel, QMessageBox +) +from PyQt5.QtCore import Qt, pyqtSignal + + +class SimpleMaintenanceLoginDialog(QDialog): + """简化的维修系统登录对话框""" + + login_success = pyqtSignal() # 登录成功信号 + + def __init__(self, parent=None): + super().__init__(parent) + self.setupUI() + + def setupUI(self): + """设置用户界面""" + self.setWindowTitle("维修系统 - 登录") + self.resize(400, 250) + self.setModal(True) + + # 设置简洁的样式 + self.setStyleSheet(""" + QDialog { + background-color: #ffffff; + font-family: "Microsoft YaHei", Arial, sans-serif; + } + QGroupBox { + font-size: 14px; + font-weight: bold; + color: #2c3e50; + border: 2px solid #bdc3c7; + border-radius: 5px; + margin-top: 10px; + padding-top: 10px; + } + QGroupBox::title { + subcontrol-origin: margin; + left: 10px; + padding: 0 5px; + } + QLabel { + color: #2c3e50; + font-size: 14px; + } + QLineEdit { + background-color: #ffffff; + border: 2px solid #bdc3c7; + border-radius: 3px; + padding: 6px 8px; + font-size: 14px; + color: #2c3e50; + min-height: 16px; + } + QLineEdit:focus { + border-color: #3498db; + background-color: #ffffff; + } + QPushButton { + background-color: #3498db; + color: white; + border: none; + padding: 8px 20px; + border-radius: 3px; + font-size: 14px; + font-weight: bold; + min-height: 16px; + } + QPushButton:hover { + background-color: #2980b9; + } + QPushButton:pressed { + background-color: #21618c; + } + QPushButton#cancel_btn { + background-color: #95a5a6; + } + QPushButton#cancel_btn:hover { + background-color: #7f8c8d; + } + """) + + # 主布局 + main_layout = QVBoxLayout(self) + main_layout.setSpacing(20) + main_layout.setContentsMargins(30, 30, 30, 30) + + # 标题 + title_label = QLabel("🔧 维修系统") + title_label.setAlignment(Qt.AlignCenter) + title_label.setStyleSheet(""" + font-size: 18px; + font-weight: bold; + color: #2c3e50; + margin: 10px 0px; + """) + main_layout.addWidget(title_label) + + # 登录表单组 + form_group = QGroupBox("身份验证") + form_layout = QFormLayout(form_group) + form_layout.setSpacing(15) + form_layout.setContentsMargins(20, 20, 20, 20) + + # 用户名输入框 + self.username_edit = QLineEdit() + self.username_edit.setPlaceholderText("请输入用户名") + form_layout.addRow("用户名:", self.username_edit) + + # 密码输入框 + self.password_edit = QLineEdit() + self.password_edit.setEchoMode(QLineEdit.Password) + self.password_edit.setPlaceholderText("请输入密码") + form_layout.addRow("密码:", self.password_edit) + + main_layout.addWidget(form_group) + + # 提示信息 + hint_label = QLabel("💡 默认账号密码均为: admin") + hint_label.setAlignment(Qt.AlignCenter) + hint_label.setStyleSheet(""" + color: #7f8c8d; + font-size: 12px; + margin: 5px 0px; + """) + main_layout.addWidget(hint_label) + + # 按钮布局 + button_layout = QHBoxLayout() + button_layout.setSpacing(10) + + self.cancel_btn = QPushButton("取消") + self.cancel_btn.setObjectName("cancel_btn") + + self.login_btn = QPushButton("登录") + + button_layout.addStretch() + button_layout.addWidget(self.cancel_btn) + button_layout.addWidget(self.login_btn) + + main_layout.addLayout(button_layout) + + # 连接信号 + self.login_btn.clicked.connect(self.handle_login) + self.cancel_btn.clicked.connect(self.reject) + self.password_edit.returnPressed.connect(self.handle_login) + + # 设置焦点和Tab顺序 + self.username_edit.setFocus() + self.setTabOrder(self.username_edit, self.password_edit) + self.setTabOrder(self.password_edit, self.login_btn) + self.setTabOrder(self.login_btn, self.cancel_btn) + + print("✅ 简化登录界面创建完成") + print(f"📝 窗口大小: {self.size().width()}x{self.size().height()}") + + def handle_login(self): + """处理登录""" + username = self.username_edit.text().strip() + password = self.password_edit.text().strip() + + print(f"🔍 登录尝试: 用户名='{username}', 密码='{password}'") + + # 验证账号密码 + if username == "admin" and password == "admin": + print("✅ 登录验证成功") + self.login_success.emit() + self.accept() + else: + print("❌ 登录验证失败") + QMessageBox.warning( + self, + "登录失败", + "用户名或密码错误!\n\n请使用:\n用户名: admin\n密码: admin" + ) + self.password_edit.clear() + self.password_edit.setFocus() + + +def test_simple_login(): + """测试简化登录界面""" + import sys + from PyQt5.QtWidgets import QApplication + + print("🧪 测试简化登录界面") + print("=" * 30) + + app = QApplication(sys.argv) + + dialog = SimpleMaintenanceLoginDialog() + + print("💡 请测试以下功能:") + print("1. 检查是否显示完整的登录界面") + print("2. 在用户名框输入 'admin'") + print("3. 在密码框输入 'admin'") + print("4. 点击登录按钮") + + def on_success(): + print("🎉 登录成功!") + QMessageBox.information(None, "成功", "登录测试成功!") + + dialog.login_success.connect(on_success) + + result = dialog.exec_() + print(f"📊 测试结果: {'成功' if result == QDialog.Accepted else '取消'}") + + return result == QDialog.Accepted + + +if __name__ == "__main__": + test_simple_login() \ No newline at end of file