Merge remote-tracking branch 'refs/remotes/origin/main_ch_eg' into addRender

# Conflicts:
#	scene/scene_manager.py
#	ui/main_window.py
This commit is contained in:
Hector 2025-10-11 09:26:47 +08:00
commit 0d898b498a
10 changed files with 6524 additions and 19 deletions

File diff suppressed because one or more lines are too long

2285
core/assembly_interaction.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,10 @@ class CollisionManager:
# === 基础碰撞掩码定义 ===
# 每个掩码使用不同的位(bit)来标识,可以进行位运算组合
'TERRAIN': BitMask32.bit(0), # 地形/地面 - 通常用于地面碰撞检测
'UI_ELEMENT': BitMask32.bit(1), # UI元素 - 界面组件的碰撞检测
'CAMERA': BitMask32.bit(2), # 摄像机 - 相机的碰撞检测
'MODEL_COLLISION': BitMask32.bit(3), # 模型碰撞 - 通用模型间碰撞检测
# 'TERRAIN': BitMask32.bit(0), # 地形/地面 - 通常用于地面碰撞检测
# 'UI_ELEMENT': BitMask32.bit(1), # UI元素 - 界面组件的碰撞检测
# 'CAMERA': BitMask32.bit(2), # 摄像机 - 相机的碰撞检测
'MODEL_COLLISION': BitMask32.bit(6), # 模型碰撞 - 通用模型间碰撞检测
}
# 碰撞体形状类型

1090
core/maintenance_gui.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,944 @@
# -*- coding: utf-8 -*-
"""
拆装交互配置界面
实现模型的分步拆装交互功能配置
"""
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QTabWidget,
QWidget, QLabel, QListWidget, QListWidgetItem,
QPushButton, QComboBox, QSpinBox, QLineEdit,
QTextEdit, QGroupBox, QGridLayout, QCheckBox,
QSplitter, QTreeWidget, QTreeWidgetItem,
QMessageBox, QFileDialog, QScrollArea,
QFrame, QDoubleSpinBox, QSlider, QProgressBar)
from PyQt5.QtCore import Qt, pyqtSignal, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QFont
import os
import json
class AssemblyDisassemblyConfigDialog(QDialog):
"""拆装配置主对话框"""
def __init__(self, parent=None, world=None):
super().__init__(parent)
self.world = world
self.config_data = {
'models': [], # 参与拆装的模型列表
'steps': [], # 拆装步骤配置
'tools': [], # 使用的工具列表
'settings': {} # 全局设置
}
self.setupUI()
self.loadSceneModels()
def setupUI(self):
"""设置界面"""
self.setWindowTitle("拆装交互配置")
self.setModal(False) # 设置为非模态对话框
self.resize(1200, 800)
# 主布局
main_layout = QVBoxLayout(self)
# 创建分割器
splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(splitter)
# 左侧面板 - 模型和工具选择
left_panel = self.createLeftPanel()
splitter.addWidget(left_panel)
# 右侧面板 - 步骤配置
right_panel = self.createRightPanel()
splitter.addWidget(right_panel)
# 设置分割器比例
splitter.setSizes([400, 800])
# 底部按钮
button_layout = QHBoxLayout()
self.save_button = QPushButton("保存配置")
self.load_button = QPushButton("加载配置")
self.preview_button = QPushButton("预览效果")
self.apply_button = QPushButton("应用配置")
button_layout.addWidget(self.save_button)
button_layout.addWidget(self.load_button)
button_layout.addStretch()
button_layout.addWidget(self.preview_button)
button_layout.addWidget(self.apply_button)
main_layout.addLayout(button_layout)
# 连接信号
self.connectSignals()
def createLeftPanel(self):
"""创建左侧面板"""
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
# 场景模型选择区域
models_group = QGroupBox("场景模型")
models_layout = QVBoxLayout(models_group)
# 模型搜索框
search_layout = QHBoxLayout()
search_label = QLabel("搜索:")
self.model_search_edit = QLineEdit()
self.model_search_edit.setPlaceholderText("输入模型名称搜索...")
search_layout.addWidget(search_label)
search_layout.addWidget(self.model_search_edit)
models_layout.addLayout(search_layout)
# 场景模型列表
self.scene_models_list = QTreeWidget()
self.scene_models_list.setHeaderLabel("场景中的模型")
self.scene_models_list.setSelectionMode(QTreeWidget.ExtendedSelection)
models_layout.addWidget(self.scene_models_list)
# 选中模型操作按钮
model_buttons_layout = QHBoxLayout()
self.add_to_config_button = QPushButton("添加到配置")
self.remove_from_config_button = QPushButton("从配置移除")
model_buttons_layout.addWidget(self.add_to_config_button)
model_buttons_layout.addWidget(self.remove_from_config_button)
models_layout.addLayout(model_buttons_layout)
left_layout.addWidget(models_group)
# 已配置模型列表
config_models_group = QGroupBox("已配置模型")
config_models_layout = QVBoxLayout(config_models_group)
self.config_models_list = QListWidget()
config_models_layout.addWidget(self.config_models_list)
left_layout.addWidget(config_models_group)
# 工具配置区域
tools_group = QGroupBox("工具配置")
tools_layout = QVBoxLayout(tools_group)
# 工具添加
tool_add_layout = QHBoxLayout()
self.tool_name_edit = QLineEdit()
self.tool_name_edit.setPlaceholderText("工具名称")
self.add_tool_button = QPushButton("添加工具")
tool_add_layout.addWidget(self.tool_name_edit)
tool_add_layout.addWidget(self.add_tool_button)
tools_layout.addLayout(tool_add_layout)
# 工具列表
self.tools_list = QListWidget()
tools_layout.addWidget(self.tools_list)
# 工具操作按钮
tool_buttons_layout = QHBoxLayout()
self.edit_tool_button = QPushButton("编辑")
self.remove_tool_button = QPushButton("删除")
tool_buttons_layout.addWidget(self.edit_tool_button)
tool_buttons_layout.addWidget(self.remove_tool_button)
tools_layout.addLayout(tool_buttons_layout)
left_layout.addWidget(tools_group)
return left_widget
def createRightPanel(self):
"""创建右侧面板"""
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
# 步骤配置标签页
self.tab_widget = QTabWidget()
# 步骤列表标签页
self.steps_tab = self.createStepsTab()
self.tab_widget.addTab(self.steps_tab, "步骤配置")
# 全局设置标签页
self.settings_tab = self.createSettingsTab()
self.tab_widget.addTab(self.settings_tab, "全局设置")
right_layout.addWidget(self.tab_widget)
return right_widget
def createStepsTab(self):
"""创建步骤配置标签页"""
steps_widget = QWidget()
steps_layout = QHBoxLayout(steps_widget)
# 左侧步骤列表
steps_list_layout = QVBoxLayout()
# 步骤列表标题和操作按钮
steps_header_layout = QHBoxLayout()
steps_label = QLabel("拆装步骤")
steps_label.setFont(QFont("", 12, QFont.Bold))
self.add_step_button = QPushButton("添加步骤")
self.remove_step_button = QPushButton("删除步骤")
steps_header_layout.addWidget(steps_label)
steps_header_layout.addStretch()
steps_header_layout.addWidget(self.add_step_button)
steps_header_layout.addWidget(self.remove_step_button)
steps_list_layout.addLayout(steps_header_layout)
# 步骤列表
self.steps_list = QListWidget()
steps_list_layout.addWidget(self.steps_list)
# 步骤顺序调整按钮
step_order_layout = QHBoxLayout()
self.move_step_up_button = QPushButton("上移")
self.move_step_down_button = QPushButton("下移")
step_order_layout.addWidget(self.move_step_up_button)
step_order_layout.addWidget(self.move_step_down_button)
steps_list_layout.addLayout(step_order_layout)
# 右侧步骤详细配置
self.step_config_widget = self.createStepConfigWidget()
# 添加到布局
steps_layout.addLayout(steps_list_layout, 1)
steps_layout.addWidget(self.step_config_widget, 2)
return steps_widget
def createStepConfigWidget(self):
"""创建步骤详细配置部件"""
config_widget = QWidget()
config_layout = QVBoxLayout(config_widget)
# 滚动区域
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_content = QWidget()
scroll_layout = QVBoxLayout(scroll_content)
# 步骤基本信息
basic_info_group = QGroupBox("基本信息")
basic_info_layout = QGridLayout(basic_info_group)
# 步骤名称
basic_info_layout.addWidget(QLabel("步骤名称:"), 0, 0)
self.step_name_edit = QLineEdit()
basic_info_layout.addWidget(self.step_name_edit, 0, 1)
# 步骤描述
basic_info_layout.addWidget(QLabel("步骤描述:"), 1, 0)
self.step_description_edit = QTextEdit()
self.step_description_edit.setMaximumHeight(80)
basic_info_layout.addWidget(self.step_description_edit, 1, 1)
# 步骤类型
basic_info_layout.addWidget(QLabel("步骤类型:"), 2, 0)
self.step_type_combo = QComboBox()
self.step_type_combo.addItems(["拆卸", "安装"])
basic_info_layout.addWidget(self.step_type_combo, 2, 1)
scroll_layout.addWidget(basic_info_group)
# 目标模型配置
target_model_group = QGroupBox("目标模型")
target_model_layout = QGridLayout(target_model_group)
# 目标模型选择
target_model_layout.addWidget(QLabel("目标模型:"), 0, 0)
self.target_model_combo = QComboBox()
target_model_layout.addWidget(self.target_model_combo, 0, 1)
# 交互方式
target_model_layout.addWidget(QLabel("交互方式:"), 1, 0)
self.interaction_type_combo = QComboBox()
self.interaction_type_combo.addItems(["鼠标拖拽", "点击触发"])
target_model_layout.addWidget(self.interaction_type_combo, 1, 1)
scroll_layout.addWidget(target_model_group)
# 拖拽交互配置
self.drag_config_group = QGroupBox("拖拽交互配置")
drag_config_layout = QGridLayout(self.drag_config_group)
# 目标位置
drag_config_layout.addWidget(QLabel("目标位置 X:"), 0, 0)
self.target_pos_x_spin = QDoubleSpinBox()
self.target_pos_x_spin.setRange(-1000, 1000)
self.target_pos_x_spin.setSingleStep(0.1)
drag_config_layout.addWidget(self.target_pos_x_spin, 0, 1)
drag_config_layout.addWidget(QLabel("目标位置 Y:"), 1, 0)
self.target_pos_y_spin = QDoubleSpinBox()
self.target_pos_y_spin.setRange(-1000, 1000)
self.target_pos_y_spin.setSingleStep(0.1)
drag_config_layout.addWidget(self.target_pos_y_spin, 1, 1)
drag_config_layout.addWidget(QLabel("目标位置 Z:"), 2, 0)
self.target_pos_z_spin = QDoubleSpinBox()
self.target_pos_z_spin.setRange(-1000, 1000)
self.target_pos_z_spin.setSingleStep(0.1)
drag_config_layout.addWidget(self.target_pos_z_spin, 2, 1)
# 拖拽约束
self.constrain_x_check = QCheckBox("约束X轴")
self.constrain_y_check = QCheckBox("约束Y轴")
self.constrain_z_check = QCheckBox("约束Z轴")
constraint_layout = QHBoxLayout()
constraint_layout.addWidget(self.constrain_x_check)
constraint_layout.addWidget(self.constrain_y_check)
constraint_layout.addWidget(self.constrain_z_check)
drag_config_layout.addWidget(QLabel("拖拽约束:"), 3, 0)
drag_config_layout.addLayout(constraint_layout, 3, 1)
scroll_layout.addWidget(self.drag_config_group)
# 点击触发配置
self.click_config_group = QGroupBox("点击触发配置")
click_config_layout = QGridLayout(self.click_config_group)
# 位移距离
click_config_layout.addWidget(QLabel("位移距离 X:"), 0, 0)
self.move_distance_x_spin = QDoubleSpinBox()
self.move_distance_x_spin.setRange(-100, 100)
self.move_distance_x_spin.setSingleStep(0.1)
click_config_layout.addWidget(self.move_distance_x_spin, 0, 1)
click_config_layout.addWidget(QLabel("位移距离 Y:"), 1, 0)
self.move_distance_y_spin = QDoubleSpinBox()
self.move_distance_y_spin.setRange(-100, 100)
self.move_distance_y_spin.setSingleStep(0.1)
click_config_layout.addWidget(self.move_distance_y_spin, 1, 1)
click_config_layout.addWidget(QLabel("位移距离 Z:"), 2, 0)
self.move_distance_z_spin = QDoubleSpinBox()
self.move_distance_z_spin.setRange(-100, 100)
self.move_distance_z_spin.setSingleStep(0.1)
click_config_layout.addWidget(self.move_distance_z_spin, 2, 1)
# 位移时间
click_config_layout.addWidget(QLabel("位移时间(秒):"), 3, 0)
self.move_duration_spin = QDoubleSpinBox()
self.move_duration_spin.setRange(0.1, 10.0)
self.move_duration_spin.setSingleStep(0.1)
self.move_duration_spin.setValue(1.0)
click_config_layout.addWidget(self.move_duration_spin, 3, 1)
# 完成后隐藏
self.hide_after_move_check = QCheckBox("位移完成后隐藏模型")
click_config_layout.addWidget(self.hide_after_move_check, 4, 0, 1, 2)
scroll_layout.addWidget(self.click_config_group)
# 提示音频配置
audio_config_group = QGroupBox("步骤提示")
audio_config_layout = QGridLayout(audio_config_group)
# 提示音频文件
audio_config_layout.addWidget(QLabel("提示音频:"), 0, 0)
audio_file_layout = QHBoxLayout()
self.audio_file_edit = QLineEdit()
self.browse_audio_button = QPushButton("浏览...")
audio_file_layout.addWidget(self.audio_file_edit)
audio_file_layout.addWidget(self.browse_audio_button)
audio_config_layout.addLayout(audio_file_layout, 0, 1)
# 提示文字
audio_config_layout.addWidget(QLabel("提示文字:"), 1, 0)
self.hint_text_edit = QTextEdit()
self.hint_text_edit.setMaximumHeight(60)
audio_config_layout.addWidget(self.hint_text_edit, 1, 1)
scroll_layout.addWidget(audio_config_group)
# 使用工具配置
tool_config_group = QGroupBox("使用工具")
tool_config_layout = QGridLayout(tool_config_group)
tool_config_layout.addWidget(QLabel("所需工具:"), 0, 0)
self.required_tool_combo = QComboBox()
self.required_tool_combo.addItem("")
tool_config_layout.addWidget(self.required_tool_combo, 0, 1)
scroll_layout.addWidget(tool_config_group)
# 设置滚动区域
scroll_area.setWidget(scroll_content)
config_layout.addWidget(scroll_area)
# 默认隐藏点击配置
self.click_config_group.setVisible(False)
return config_widget
def createSettingsTab(self):
"""创建全局设置标签页"""
settings_widget = QWidget()
settings_layout = QVBoxLayout(settings_widget)
# 全局设置组
global_settings_group = QGroupBox("全局设置")
global_settings_layout = QGridLayout(global_settings_group)
# 自动播放提示音
self.auto_play_audio_check = QCheckBox("自动播放步骤提示音")
self.auto_play_audio_check.setChecked(True)
global_settings_layout.addWidget(self.auto_play_audio_check, 0, 0, 1, 2)
# 显示步骤提示文字
self.show_hint_text_check = QCheckBox("显示步骤提示文字")
self.show_hint_text_check.setChecked(True)
global_settings_layout.addWidget(self.show_hint_text_check, 1, 0, 1, 2)
# 步骤完成确认
self.require_confirmation_check = QCheckBox("步骤完成需要确认")
global_settings_layout.addWidget(self.require_confirmation_check, 2, 0, 1, 2)
# 拖拽灵敏度
global_settings_layout.addWidget(QLabel("拖拽灵敏度:"), 3, 0)
self.drag_sensitivity_slider = QSlider(Qt.Horizontal)
self.drag_sensitivity_slider.setRange(1, 10)
self.drag_sensitivity_slider.setValue(5)
global_settings_layout.addWidget(self.drag_sensitivity_slider, 3, 1)
# 动画速度
global_settings_layout.addWidget(QLabel("动画速度:"), 4, 0)
self.animation_speed_slider = QSlider(Qt.Horizontal)
self.animation_speed_slider.setRange(1, 10)
self.animation_speed_slider.setValue(5)
global_settings_layout.addWidget(self.animation_speed_slider, 4, 1)
settings_layout.addWidget(global_settings_group)
# 预设配置组
presets_group = QGroupBox("预设配置")
presets_layout = QVBoxLayout(presets_group)
# 预设列表
self.presets_list = QListWidget()
presets_layout.addWidget(self.presets_list)
# 预设操作按钮
preset_buttons_layout = QHBoxLayout()
self.save_preset_button = QPushButton("保存为预设")
self.load_preset_button = QPushButton("加载预设")
self.delete_preset_button = QPushButton("删除预设")
preset_buttons_layout.addWidget(self.save_preset_button)
preset_buttons_layout.addWidget(self.load_preset_button)
preset_buttons_layout.addWidget(self.delete_preset_button)
presets_layout.addLayout(preset_buttons_layout)
settings_layout.addWidget(presets_group)
# 添加弹性空间
settings_layout.addStretch()
return settings_widget
def connectSignals(self):
"""连接信号槽"""
# 模型搜索
self.model_search_edit.textChanged.connect(self.filterSceneModels)
# 模型操作
self.add_to_config_button.clicked.connect(self.addModelsToConfig)
self.remove_from_config_button.clicked.connect(self.removeModelsFromConfig)
# 工具操作
self.add_tool_button.clicked.connect(self.addTool)
self.edit_tool_button.clicked.connect(self.editTool)
self.remove_tool_button.clicked.connect(self.removeTool)
# 步骤操作
self.add_step_button.clicked.connect(self.addStep)
self.remove_step_button.clicked.connect(self.removeStep)
self.move_step_up_button.clicked.connect(self.moveStepUp)
self.move_step_down_button.clicked.connect(self.moveStepDown)
self.steps_list.itemSelectionChanged.connect(self.onStepSelectionChanged)
# 步骤配置变化
self.interaction_type_combo.currentTextChanged.connect(self.onInteractionTypeChanged)
self.browse_audio_button.clicked.connect(self.browseAudioFile)
# 步骤配置实时更新
self.step_name_edit.textChanged.connect(self.updateCurrentStep)
self.step_description_edit.textChanged.connect(self.updateCurrentStep)
self.step_type_combo.currentTextChanged.connect(self.updateCurrentStep)
self.target_model_combo.currentTextChanged.connect(self.updateCurrentStep)
# 按钮操作
self.save_button.clicked.connect(self.saveConfiguration)
self.load_button.clicked.connect(self.loadConfiguration)
self.preview_button.clicked.connect(self.previewConfiguration)
self.apply_button.clicked.connect(self.applyConfiguration)
def loadSceneModels(self):
"""加载场景中的模型"""
if not self.world:
return
# 清空现有列表
self.scene_models_list.clear()
# 遍历场景中的所有节点
self._addNodeToTree(self.world.render, self.scene_models_list)
# 展开根节点
self.scene_models_list.expandAll()
def _addNodeToTree(self, node, parent_item):
"""递归添加节点到树形控件"""
if node.getName() in ['render', 'camera', 'aspect2d', 'pixel2d']:
# 跳过系统节点
for child in node.getChildren():
self._addNodeToTree(child, parent_item)
return
# 创建树形项
if isinstance(parent_item, QTreeWidget):
item = QTreeWidgetItem(parent_item)
else:
item = QTreeWidgetItem(parent_item)
item.setText(0, node.getName())
item.setData(0, Qt.UserRole, node)
# 添加子节点
for child in node.getChildren():
self._addNodeToTree(child, item)
def filterSceneModels(self, text):
"""过滤场景模型列表"""
# 简单的文本过滤实现
def filterItems(item):
item_text = item.text(0).lower()
text_lower = text.lower()
# 检查当前项是否匹配
matches = text_lower in item_text
# 检查子项
child_matches = False
for i in range(item.childCount()):
child_item = item.child(i)
if filterItems(child_item):
child_matches = True
# 显示/隐藏项
item.setHidden(not (matches or child_matches))
return matches or child_matches
# 从根项开始过滤
for i in range(self.scene_models_list.topLevelItemCount()):
filterItems(self.scene_models_list.topLevelItem(i))
def addModelsToConfig(self):
"""添加选中的模型到配置"""
selected_items = self.scene_models_list.selectedItems()
for item in selected_items:
node = item.data(0, Qt.UserRole)
if node:
model_name = node.getName()
# 检查是否已经存在
existing_items = self.config_models_list.findItems(model_name, Qt.MatchExactly)
if not existing_items:
# 添加到配置模型列表
list_item = QListWidgetItem(model_name)
list_item.setData(Qt.UserRole, node)
self.config_models_list.addItem(list_item)
# 添加到配置数据
self.config_data['models'].append({
'name': model_name,
'node': node,
'original_pos': node.getPos(),
'original_hpr': node.getHpr()
})
# 更新目标模型下拉框
self.target_model_combo.addItem(model_name)
def removeModelsFromConfig(self):
"""从配置中移除选中的模型"""
selected_items = self.config_models_list.selectedItems()
for item in selected_items:
model_name = item.text()
# 从列表中移除
row = self.config_models_list.row(item)
self.config_models_list.takeItem(row)
# 从配置数据中移除
self.config_data['models'] = [
model for model in self.config_data['models']
if model['name'] != model_name
]
# 从目标模型下拉框中移除
index = self.target_model_combo.findText(model_name)
if index >= 0:
self.target_model_combo.removeItem(index)
def addTool(self):
"""添加工具"""
tool_name = self.tool_name_edit.text().strip()
if not tool_name:
QMessageBox.warning(self, "警告", "请输入工具名称")
return
# 检查是否已存在
existing_items = self.tools_list.findItems(tool_name, Qt.MatchExactly)
if existing_items:
QMessageBox.warning(self, "警告", "工具已存在")
return
# 添加到工具列表
self.tools_list.addItem(tool_name)
self.required_tool_combo.addItem(tool_name)
# 添加到配置数据
self.config_data['tools'].append({
'name': tool_name,
'description': '',
'icon': ''
})
# 清空输入框
self.tool_name_edit.clear()
def editTool(self):
"""编辑工具"""
current_item = self.tools_list.currentItem()
if not current_item:
QMessageBox.warning(self, "警告", "请选择要编辑的工具")
return
# 这里可以添加工具编辑对话框
QMessageBox.information(self, "提示", "工具编辑功能待实现")
def removeTool(self):
"""删除工具"""
current_item = self.tools_list.currentItem()
if not current_item:
QMessageBox.warning(self, "警告", "请选择要删除的工具")
return
tool_name = current_item.text()
# 从列表中移除
row = self.tools_list.row(current_item)
self.tools_list.takeItem(row)
# 从下拉框中移除
index = self.required_tool_combo.findText(tool_name)
if index >= 0:
self.required_tool_combo.removeItem(index)
# 从配置数据中移除
self.config_data['tools'] = [
tool for tool in self.config_data['tools']
if tool['name'] != tool_name
]
def addStep(self):
"""添加步骤"""
step_count = len(self.config_data['steps']) + 1
step_name = f"步骤 {step_count}"
# 创建新步骤数据
step_data = {
'name': step_name,
'description': '',
'type': '拆卸',
'target_model': '',
'interaction_type': '鼠标拖拽',
'target_position': [0, 0, 0],
'constraints': {'x': False, 'y': False, 'z': False},
'move_distance': [0, 0, 0],
'move_duration': 1.0,
'hide_after_move': False,
'audio_file': '',
'hint_text': '',
'required_tool': ''
}
# 添加到配置数据
self.config_data['steps'].append(step_data)
# 添加到步骤列表
list_item = QListWidgetItem(step_name)
list_item.setData(Qt.UserRole, step_data)
self.steps_list.addItem(list_item)
# 选中新添加的步骤
self.steps_list.setCurrentItem(list_item)
def removeStep(self):
"""删除步骤"""
current_item = self.steps_list.currentItem()
if not current_item:
QMessageBox.warning(self, "警告", "请选择要删除的步骤")
return
# 从列表中移除
row = self.steps_list.row(current_item)
self.steps_list.takeItem(row)
# 从配置数据中移除
self.config_data['steps'].pop(row)
# 重新编号步骤
self.renumberSteps()
def moveStepUp(self):
"""上移步骤"""
current_row = self.steps_list.currentRow()
if current_row <= 0:
return
# 交换数据
self.config_data['steps'][current_row], self.config_data['steps'][current_row - 1] = \
self.config_data['steps'][current_row - 1], self.config_data['steps'][current_row]
# 交换列表项
current_item = self.steps_list.takeItem(current_row)
self.steps_list.insertItem(current_row - 1, current_item)
self.steps_list.setCurrentRow(current_row - 1)
# 重新编号
self.renumberSteps()
def moveStepDown(self):
"""下移步骤"""
current_row = self.steps_list.currentRow()
if current_row >= self.steps_list.count() - 1:
return
# 交换数据
self.config_data['steps'][current_row], self.config_data['steps'][current_row + 1] = \
self.config_data['steps'][current_row + 1], self.config_data['steps'][current_row]
# 交换列表项
current_item = self.steps_list.takeItem(current_row)
self.steps_list.insertItem(current_row + 1, current_item)
self.steps_list.setCurrentRow(current_row + 1)
# 重新编号
self.renumberSteps()
def renumberSteps(self):
"""重新编号步骤"""
for i in range(self.steps_list.count()):
item = self.steps_list.item(i)
step_data = item.data(Qt.UserRole)
new_name = f"步骤 {i + 1}"
step_data['name'] = new_name
item.setText(new_name)
def onStepSelectionChanged(self):
"""步骤选择改变"""
current_item = self.steps_list.currentItem()
if not current_item:
return
step_data = current_item.data(Qt.UserRole)
if not step_data:
return
# 更新步骤配置界面
self.loadStepConfiguration(step_data)
def loadStepConfiguration(self, step_data):
"""加载步骤配置到界面"""
# 基本信息
self.step_name_edit.setText(step_data.get('name', ''))
self.step_description_edit.setPlainText(step_data.get('description', ''))
self.step_type_combo.setCurrentText(step_data.get('type', '拆卸'))
# 目标模型
target_model = step_data.get('target_model', '')
index = self.target_model_combo.findText(target_model)
if index >= 0:
self.target_model_combo.setCurrentIndex(index)
# 交互方式
interaction_type = step_data.get('interaction_type', '鼠标拖拽')
self.interaction_type_combo.setCurrentText(interaction_type)
self.onInteractionTypeChanged(interaction_type)
# 拖拽配置
target_pos = step_data.get('target_position', [0, 0, 0])
self.target_pos_x_spin.setValue(target_pos[0])
self.target_pos_y_spin.setValue(target_pos[1])
self.target_pos_z_spin.setValue(target_pos[2])
constraints = step_data.get('constraints', {'x': False, 'y': False, 'z': False})
self.constrain_x_check.setChecked(constraints.get('x', False))
self.constrain_y_check.setChecked(constraints.get('y', False))
self.constrain_z_check.setChecked(constraints.get('z', False))
# 点击触发配置
move_distance = step_data.get('move_distance', [0, 0, 0])
self.move_distance_x_spin.setValue(move_distance[0])
self.move_distance_y_spin.setValue(move_distance[1])
self.move_distance_z_spin.setValue(move_distance[2])
self.move_duration_spin.setValue(step_data.get('move_duration', 1.0))
self.hide_after_move_check.setChecked(step_data.get('hide_after_move', False))
# 提示配置
self.audio_file_edit.setText(step_data.get('audio_file', ''))
self.hint_text_edit.setPlainText(step_data.get('hint_text', ''))
# 工具配置
required_tool = step_data.get('required_tool', '')
index = self.required_tool_combo.findText(required_tool)
if index >= 0:
self.required_tool_combo.setCurrentIndex(index)
def onInteractionTypeChanged(self, interaction_type):
"""交互方式改变"""
if interaction_type == "鼠标拖拽":
self.drag_config_group.setVisible(True)
self.click_config_group.setVisible(False)
else: # 点击触发
self.drag_config_group.setVisible(False)
self.click_config_group.setVisible(True)
def updateCurrentStep(self):
"""更新当前步骤数据"""
current_item = self.steps_list.currentItem()
if not current_item:
return
step_data = current_item.data(Qt.UserRole)
if not step_data:
return
# 更新步骤数据
step_data['name'] = self.step_name_edit.text()
step_data['description'] = self.step_description_edit.toPlainText()
step_data['type'] = self.step_type_combo.currentText()
step_data['target_model'] = self.target_model_combo.currentText()
step_data['interaction_type'] = self.interaction_type_combo.currentText()
# 更新列表项显示
current_item.setText(step_data['name'])
def browseAudioFile(self):
"""浏览音频文件"""
file_path, _ = QFileDialog.getOpenFileName(
self, "选择音频文件", "",
"音频文件 (*.wav *.mp3 *.ogg *.flac);;所有文件 (*)"
)
if file_path:
self.audio_file_edit.setText(file_path)
def saveConfiguration(self):
"""保存配置"""
file_path, _ = QFileDialog.getSaveFileName(
self, "保存拆装配置", "",
"JSON文件 (*.json);;所有文件 (*)"
)
if file_path:
try:
# 准备保存的数据(去除不能序列化的对象)
save_data = {
'models': [
{
'name': model['name'],
'original_pos': list(model['original_pos']),
'original_hpr': list(model['original_hpr'])
} for model in self.config_data['models']
],
'steps': self.config_data['steps'],
'tools': self.config_data['tools'],
'settings': {
'auto_play_audio': self.auto_play_audio_check.isChecked(),
'show_hint_text': self.show_hint_text_check.isChecked(),
'require_confirmation': self.require_confirmation_check.isChecked(),
'drag_sensitivity': self.drag_sensitivity_slider.value(),
'animation_speed': self.animation_speed_slider.value()
}
}
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(save_data, f, ensure_ascii=False, indent=2)
QMessageBox.information(self, "成功", "配置保存成功")
except Exception as e:
QMessageBox.critical(self, "错误", f"保存配置失败: {str(e)}")
def loadConfiguration(self):
"""加载配置"""
file_path, _ = QFileDialog.getOpenFileName(
self, "加载拆装配置", "",
"JSON文件 (*.json);;所有文件 (*)"
)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 加载配置数据
self.loadConfigurationData(data)
QMessageBox.information(self, "成功", "配置加载成功")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载配置失败: {str(e)}")
def loadConfigurationData(self, data):
"""加载配置数据到界面"""
# 清空现有配置
self.config_models_list.clear()
self.target_model_combo.clear()
self.steps_list.clear()
# 加载模型配置
# 注意:这里需要重新匹配场景中的模型节点
# 加载步骤配置
self.config_data['steps'] = data.get('steps', [])
for step_data in self.config_data['steps']:
list_item = QListWidgetItem(step_data['name'])
list_item.setData(Qt.UserRole, step_data)
self.steps_list.addItem(list_item)
# 加载工具配置
self.tools_list.clear()
self.required_tool_combo.clear()
self.required_tool_combo.addItem("")
self.config_data['tools'] = data.get('tools', [])
for tool in self.config_data['tools']:
self.tools_list.addItem(tool['name'])
self.required_tool_combo.addItem(tool['name'])
# 加载全局设置
settings = data.get('settings', {})
self.auto_play_audio_check.setChecked(settings.get('auto_play_audio', True))
self.show_hint_text_check.setChecked(settings.get('show_hint_text', True))
self.require_confirmation_check.setChecked(settings.get('require_confirmation', False))
self.drag_sensitivity_slider.setValue(settings.get('drag_sensitivity', 5))
self.animation_speed_slider.setValue(settings.get('animation_speed', 5))
def previewConfiguration(self):
"""预览配置效果"""
QMessageBox.information(self, "提示", "预览功能待实现")
def applyConfiguration(self):
"""应用配置"""
QMessageBox.information(self, "提示", "应用配置功能待实现")

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,13 @@ import os
import sys
from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor
from PyQt5.QtWebEngineWidgets import QWebEngineView
# from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
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
from panda3d.core import OrthographicLens
@ -713,6 +713,17 @@ class MainWindow(QMainWindow):
self.scriptMenu.addSeparator()
self.openScriptsManagerAction = self.scriptMenu.addAction('脚本管理器')
# 交互菜单
self.interactionMenu = menubar.addMenu('交互')
self.assemblyDisassemblyConfigAction = self.interactionMenu.addAction('拆装配置')
self.assemblyDisassemblyConfigAction.triggered.connect(self.onOpenAssemblyDisassemblyConfig)
self.interactionMenu.addSeparator()
self.startAssemblyInteractionAction = self.interactionMenu.addAction('开始拆装交互')
self.startAssemblyInteractionAction.triggered.connect(self.onStartAssemblyInteraction)
self.interactionMenu.addSeparator()
self.maintenanceSystemAction = self.interactionMenu.addAction('🔧 维修系统')
self.maintenanceSystemAction.triggered.connect(self.onOpenMaintenanceSystem)
self.cesiumMenu = menubar.addMenu('Cesium')
self.loadCesiumTilesetAction = self.cesiumMenu.addAction('加载3Dtiles')
self.loadCesiumTilesetAction.triggered.connect(self.onLoadCesiumTileset)
@ -2540,7 +2551,7 @@ class MainWindow(QMainWindow):
if not WEB_ENGINE_AVAILABLE:
return None
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView
# from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QDockWidget
from PyQt5.QtCore import QUrl
import os
@ -3239,6 +3250,16 @@ class MainWindow(QMainWindow):
try:
print("🔄 正在关闭应用程序...")
# 关闭拆装交互相关的弹窗
if hasattr(self.world, 'assembly_interaction') and self.world.assembly_interaction:
print("🧹 关闭拆装交互弹窗...")
if hasattr(self.world.assembly_interaction, 'step_dialog') and self.world.assembly_interaction.step_dialog:
self.world.assembly_interaction.step_dialog.close()
self.world.assembly_interaction.step_dialog = None
# 停止交互模式
if self.world.assembly_interaction.is_active:
self.world.assembly_interaction.stop_interaction_mode()
# 清理工具管理器中的进程
if hasattr(self.world, 'tool_manager') and self.world.tool_manager:
print("🧹 清理工具管理器进程...")
@ -3444,6 +3465,224 @@ class MainWindow(QMainWindow):
else:
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
def onOpenAssemblyDisassemblyConfig(self):
"""打开拆装配置界面"""
try:
from ui.assembly_disassembly_config_simple import AssemblyDisassemblyConfigDialog
config_dialog = AssemblyDisassemblyConfigDialog(self, self.world)
config_dialog.show()
except Exception as e:
QMessageBox.critical(self, "错误", f"打开拆装配置界面失败: {str(e)}")
import traceback
traceback.print_exc()
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(mode=selected_mode)
except Exception as e:
QMessageBox.critical(self, "错误", f"启动拆装交互失败: {str(e)}")
import traceback
traceback.print_exc()
def onOpenMaintenanceSystem(self):
"""打开维修系统"""
try:
# 导入简化的登录界面
from ui.simple_maintenance_login import SimpleMaintenanceLoginDialog
from ui.maintenance_system import MaintenanceSubjectDialog, MaintenanceSystemManager
print("🔧 启动维修系统...")
# 显示登录界面
login_dialog = SimpleMaintenanceLoginDialog(self)
if login_dialog.exec_() == QDialog.Accepted:
print("✅ 登录成功,显示科目选择界面")
# 获取当前项目路径
project_path = None
if hasattr(self.world, 'project_manager') and self.world.project_manager:
project_path = self.world.project_manager.getCurrentProjectPath()
# 显示科目选择界面
subject_dialog = MaintenanceSubjectDialog(project_path, self)
def on_subject_selected(subject_path, mode):
"""处理科目选择"""
try:
print(f"🎯 启动维修科目: {subject_path}")
print(f"📝 模式: {mode}")
# 加载科目配置
import json
with open(subject_path, 'r', encoding='utf-8') as f:
subject_config = json.load(f)
# 初始化拆装交互系统(如果还没有)
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, "错误", "维修科目启动失败")
except Exception as e:
print(f"❌ 启动维修科目失败: {e}")
QMessageBox.critical(self, "错误", f"启动维修科目失败:\n{str(e)}")
import traceback
traceback.print_exc()
subject_dialog.subject_selected.connect(on_subject_selected)
subject_dialog.exec_()
else:
print(" 用户取消了登录")
except Exception as e:
print(f"❌ 打开维修系统失败: {e}")
QMessageBox.critical(self, "错误", f"打开维修系统失败:\n{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
# ==================== VR事件处理 ====================
def onEnterVR(self):

658
ui/maintenance_system.py Normal file
View File

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

View File

@ -4806,12 +4806,12 @@ class PropertyPanelManager:
if geom_node:
geom_node_name = geom_node.getName()
unique_name = f"{geom_node_name}({model_name})"
#print(f"材质 {i}: 使用几何节点名称 '{geom_node_name}'")
print(f"材质 {i}: 使用几何节点名称 '{geom_node_name}'")
else:
material_name = material.get_name() if hasattr(material,
'get_name') and material.get_name() else f"材质{i + 1}"
unique_name = f"{material_name}({model_name})"
#print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'")
print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'")
# 处理重复名称
if unique_name in name_counter:
@ -4850,7 +4850,7 @@ class PropertyPanelManager:
# 基础颜色编辑
base_color = self._getOrCreateMaterialBaseColor(material)
if base_color is not None:
#print(f"材质基础颜色: {base_color}")
print(f"材质基础颜色: {base_color}")
# 基础颜色标题
color_row = 2 if material_status != "标准PBR材质" else 1
@ -5236,7 +5236,7 @@ class PropertyPanelManager:
try:
# 方法1: 尝试获取base_color属性
if hasattr(material, 'base_color') and material.base_color is not None:
#print(f"✓ 找到base_color属性: {material.base_color}")
print(f"✓ 找到base_color属性: {material.base_color}")
return material.base_color
# 方法2: 尝试调用get_base_color方法
@ -5254,7 +5254,7 @@ class PropertyPanelManager:
try:
diffuse_color = material.getDiffuse()
if diffuse_color is not None:
#print(f"✓ 从diffuse颜色获取: {diffuse_color}")
print(f"✓ 从diffuse颜色获取: {diffuse_color}")
# 同时设置为base_color
if hasattr(material, 'set_base_color'):
material.set_base_color(diffuse_color)
@ -6862,7 +6862,7 @@ class PropertyPanelManager:
# print(f"找到匹配的几何节点: {geom_np.get_name()}")
return geom_np
#print("未找到匹配的几何节点")
print("未找到匹配的几何节点")
return None
def _findSpecificGeomNodeForMaterial(self, target_material):
@ -8147,7 +8147,7 @@ class PropertyPanelManager:
format_info = self._getModelFormat(origin_model)
processed = []
#print(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
print(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
for name in anim_names:
display_name = name
@ -8181,7 +8181,7 @@ class PropertyPanelManager:
display_name = name
processed.append((display_name, original_name))
#print(f"[动画分析] {original_name} → {display_name}")
print(f"[动画分析] {original_name}{display_name}")
return processed
@ -8215,7 +8215,7 @@ class PropertyPanelManager:
if frames > 1:
valid_anims += 1
total_frames += frames
#print(f"[动画分析] '{anim_name}': {frames} 帧")
print(f"[动画分析] '{anim_name}': {frames}")
else:
print(f"[动画分析] '{anim_name}': 无有效帧数 ({frames})")
except Exception as e:

View File

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