diff --git a/Start_Run.py b/Start_Run.py index 225cd6a8..fa515efa 100644 --- a/Start_Run.py +++ b/Start_Run.py @@ -17,7 +17,6 @@ sys.path.insert(0, render_pipeline_file_path) icons_path = os.path.join(project_root, "icons") sys.path.insert(0, icons_path) - # 现在可以导入并运行主程序 if __name__ == "__main__": args = sys.argv[1:] diff --git a/core/terrain_manager.py b/core/terrain_manager.py index 6c7f0564..edbec218 100644 --- a/core/terrain_manager.py +++ b/core/terrain_manager.py @@ -104,6 +104,9 @@ class TerrainManager: print("错误:无法生成有效的地形节点") return None + terrain_node.setTag("is_scene_element", "1") + terrain_node.setTag("tree_item_type", "TERRAIN_NODE") + node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}" terrain_node.setName(node_name) @@ -235,6 +238,9 @@ class TerrainManager: print("错误:无法生成有效的平面地形节点") return None + terrain_node.setTag("is_scene_element", "1") + terrain_node.setTag("tree_item_type", "TERRAIN_NODE") + node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}" terrain_node.setName(node_name) diff --git a/gui/gui_manager.py b/gui/gui_manager.py index dbf9e4e6..83737e6f 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -84,6 +84,8 @@ except ImportError: # 为GUI元素添加标识(效仿3D文本方法) image_node.setTag("gui_type", "3d_image") image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}") + image_node.setTag("is_scene_element", "1") + image_node.setTag("tree_item_type", "GUI_3DIMAGE") if image_path: image_node.setTag("gui_image_path", image_path) image_node.setTag("is_gui_element", "1") @@ -203,6 +205,7 @@ class GUIManager: button.setTag("gui_text", text) button.setTag("is_gui_element", "1") button.setTag("is_scene_element", "1") # 确保这个标签被设置 + button.setTag("tree_item_type", "GUI_BUTTON") button.setTag("saved_gui_type", "button") # 添加这个标签以确保兼容性 button.setTag("gui_element_type", "button") button.setTag("created_by_user", "1") @@ -319,6 +322,7 @@ class GUIManager: label.setTag("gui_id", f"label_{len(self.gui_elements)}") label.setTag("gui_text", text) label.setTag("is_gui_element", "1") + label.setTag("tree_item_type", "GUI_LABEL") label.setTag("is_scene_element", "1") label.setTag("created_by_user", "1") label.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") @@ -430,6 +434,7 @@ class GUIManager: entry.setTag("gui_id", f"entry_{len(self.gui_elements)}") entry.setTag("gui_placeholder", placeholder) entry.setTag("is_gui_element", "1") + entry.setTag("tree_item_type", "GUI_ENTRY") entry.setTag("is_scene_element", "1") entry.setTag("created_by_user", "1") entry.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") @@ -564,6 +569,7 @@ class GUIManager: image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}") image_node.setTag("is_gui_element", "1") image_node.setTag("is_scene_element", "1") + image_node.setTag("tree_item_type", "GUI_IMAGE") image_node.setTag("created_by_user", "1") image_node.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") image_node.setName(image_name) @@ -715,6 +721,7 @@ class GUIManager: textNodePath.setTag("gui_text", text) textNodePath.setTag("is_gui_element", "1") textNodePath.setTag("is_scene_element", "1") + textNodePath.setTag("tree_item_type", "GUI_3DTEXT") textNodePath.setTag("created_by_user", "1") # 添加到GUI元素列表 @@ -851,6 +858,7 @@ class GUIManager: image_node.setTag("image_path", image_path) image_node.setTag("is_gui_element", "1") image_node.setTag("is_scene_element", "1") + image_node.setTag("tree_item_type", "GUI_3DIMAGE") image_node.setTag("created_by_user", "1") # 添加到GUI元素列表 @@ -968,6 +976,7 @@ class GUIManager: video_screen.setTag("gui_text", f"视频屏幕_{len(self.gui_elements)}") video_screen.setTag("is_gui_element", "1") video_screen.setTag("is_scene_element", "1") + video_screen.setTag("tree_item_type", "GUI_VIDEO_SCREEN") video_screen.setTag("created_by_user", "1") # 设置视频路径标签 @@ -1484,6 +1493,7 @@ class GUIManager: video_screen.setTag("gui_text", f"2D视频屏幕_{len(self.gui_elements)}") video_screen.setTag("is_gui_element", "1") video_screen.setTag("is_scene_element", "1") + video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN") video_screen.setTag("created_by_user", "1") video_screen.setTag("video_path", video_path if video_path else "") @@ -1796,6 +1806,7 @@ class GUIManager: # 设置标签以便识别和管理 sphere_np.setTag("gui_type", "spherical_video") sphere_np.setTag("is_gui_element", "1") + sphere_np.setTag("tree_item_type", "GUI_SPHERICAL_VIDEO") sphere_np.setTag("video_path", video_path or "") sphere_np.setTag("original_radius", str(radius)) @@ -2008,6 +2019,7 @@ class GUIManager: virtual_screen.setTag("gui_text", text) virtual_screen.setTag("is_gui_element", "1") virtual_screen.setTag("is_scene_element", "1") + virtual_screen.setTag("tree_item_type", "GUI_VirtualScreen") virtual_screen.setTag("created_by_user", "1") # 添加到GUI元素列表 diff --git a/project/project_manager.py b/project/project_manager.py index 5ed451ce..c3370792 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -79,7 +79,7 @@ class ProjectManager: # 自动保存初始场景 scene_file = os.path.join(scenes_path, "scene.bam") - if self.world.scene_manager.saveScene(scene_file): + if self.world.scene_manager.saveScene(scene_file, project_path): # 更新配置文件中的场景路径 project_config["scene_file"] = os.path.relpath(scene_file, full_project_path) with open(config_file, "w", encoding="utf-8") as f: @@ -302,7 +302,7 @@ class ProjectManager: return False # 保存场景 - if self.world.scene_manager.saveScene(scene_file): + if self.world.scene_manager.saveScene(scene_file, project_path): # 更新项目配置文件 config_file = os.path.join(project_path, "project.json") if os.path.exists(config_file): diff --git a/scene/scene_manager.py b/scene/scene_manager.py index d52c5ba5..7fcf50d1 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -146,39 +146,18 @@ class SceneManager: model.setTag("converted_from", os.path.splitext(original_filepath)[1]) model.setTag("converted_to_glb", "true") - # file_extension = os.path.splitext(filepath)[1].lower() - # if file_extension == '.fbx': - # print("应用FBX特定缩放 (0.01)...") - # self._applyModelScale(model, 0.01) - # model.setTag("format_scale_applied", "fbx_0.01") - # current_hpr = model.getHpr() - # model.setHpr(current_hpr.getX(), 90, current_hpr.getZ()) - # elif file_extension == '.glb': - # print("应用GLB特定缩放 (100)...") - # self._applyModelScale(model, 100.0) - # model.setTag("format_scale_applied", "glb_100") - # else: - # print(f"应用默认缩放 (1.0) 到 {file_extension} 格式...") - # self._applyModelScale(model, 1.0) - # model.setTag("format_scale_applied", f"{file_extension}_1.0") - # 可选的单位转换(主要针对FBX) - # if apply_unit_conversion and filepath.lower().endswith('.fbx'): - # print("应用FBX单位转换(厘米到米)...") - # self._applyUnitConversion(model, 0.01) + if apply_unit_conversion and filepath.lower().endswith('.fbx'): + print("应用FBX单位转换(厘米到米)...") + self._applyUnitConversion(model, 0.01) - # # 可选的单位转换(主要针对FBX) - # if apply_unit_conversion and filepath.lower().endswith('.fbx'): - # print("应用FBX单位转换(厘米到米)...") - # self._applyUnitConversion(model, 0.01) - # - # # 智能缩放标准化(处理FBX子节点的大缩放值) - # if normalize_scales and filepath.lower().endswith('.fbx'): - # print("标准化FBX模型缩放层级...") - # self._normalizeModelScales(model) + # 智能缩放标准化(处理FBX子节点的大缩放值) + if normalize_scales and filepath.lower().endswith('.fbx'): + print("标准化FBX模型缩放层级...") + self._normalizeModelScales(model) # 调整模型位置到地面 - #self._adjustModelToGround(model) + self._adjustModelToGround(model) # 创建并设置基础材质 print("\n=== 开始设置材质 ===") @@ -192,6 +171,7 @@ class SceneManager: model.setTag("file", model_name) model.setTag("is_model_root", "1") model.setTag("is_scene_element", "1") + model.setTag("tree_item_type", "IMPORTED_MODEL_NODE") # 记录应用的处理选项 if apply_unit_conversion: @@ -910,7 +890,7 @@ class SceneManager: print(f"警告: 无法获取脚本 {script_name} 的文件路径") return script_file - def saveScene(self, filename): + def saveScene(self, filename,project_path): """保存场景到BAM文件 - 完整版,支持GUI元素,地形""" try: print(f"\n=== 开始保存场景到: {filename} ===") @@ -1074,6 +1054,7 @@ class SceneManager: self.world.render.ls() print("---------------------------------") + self.take_screenshot(project_path) # 保存场景 success = self.world.render.writeBamFile(Filename.fromOsSpecific(filename)) @@ -1101,6 +1082,51 @@ class SceneManager: traceback.print_exc() return False + def take_screenshot(self, projectpath): + """ + 截图并保存到指定的完整路径 + + Args: + full_path (str): 完整的文件保存路径,包括文件名和扩展名 + + Returns: + bool: 截图是否成功 + """ + try: + from panda3d.core import Filename + import os + + print(f"\n=== 截图保存: {projectpath} ===") + + # 确保目录存在 + directory = os.path.dirname(projectpath) + if directory and not os.path.exists(directory): + os.makedirs(directory) + print(f"创建目录: {directory}") + + # 规范化路径 + filename = os.path.basename(os.path.normpath(projectpath)) + filename = f'{filename}.png' + print(f'project_path: {projectpath}') + print(f'project_name: {filename}') + full_path = os.path.normpath(os.path.join(projectpath, filename)) + p3d_filename = Filename.from_os_specific(full_path) + # 使用 Panda3D 的截图功能 + success = self.world.win.saveScreenshot(p3d_filename) + + if success: + print(f"✅ 成功截图并保存到: {full_path}") + return True + else: + print(f"❌ 截图保存失败: {full_path}") + return False + + except Exception as e: + print(f"保存截图时发生错误: {str(e)}") + import traceback + traceback.print_exc() + return False + def loadScene(self, filename): """从BAM文件加载场景""" try: @@ -1887,6 +1913,7 @@ class SceneManager: # 设置节点属性和标签 light_np.setTag("light_type", "spot_light") light_np.setTag("is_scene_element", "1") + light_np.setTag("tree_item_type", "LIGHT_NODE") light_np.setTag("light_energy", str(light.energy)) light_np.setTag("created_by_user", "1") @@ -1993,6 +2020,7 @@ class SceneManager: # 设置节点属性和标签 light_np.setTag("light_type", "point_light") light_np.setTag("is_scene_element", "1") + light_np.setTag("tree_item_type", "LIGHT_NODE") light_np.setTag("light_energy", str(light.energy)) light_np.setTag("created_by_user", "1") @@ -2521,6 +2549,7 @@ except Exception as e: # 添加标签以便场景识别和保存 tileset_node.setTag("is_scene_element", "1") + tileset_node.setTag("tree_item_type", "CESIUM_TILESET_NODE") tileset_node.setTag("element_type", "cesium_tileset") tileset_node.setTag("tileset_url", tileset_url) # 使用唯一名称作为文件标识,代替索引 diff --git a/ui/main_window.py b/ui/main_window.py index a1cb283e..92af669d 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -9,7 +9,7 @@ import os import sys -from PyQt5.QtGui import QKeySequence, QIcon +from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction, QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem, QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, @@ -23,12 +23,119 @@ from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,Cus class MainWindow(QMainWindow): """主窗口类""" - + def __init__(self, world): super().__init__() self.world = world self.world.main_window = self # 关键:让world对象能访问主窗口 + self.setStyleSheet(""" + QMainWindow { + background-color: #1e1e2e; + } + QMenuBar { + background-color: #252538; + color: #e0e0ff; + border-bottom: 1px solid #3a3a4a; + } + QMenuBar::item { + background-color: transparent; + padding: 4px 8px; + } + QMenuBar::item:selected { + background-color: rgba(139, 92, 246, 100); + } + QMenuBar::item:pressed { + background-color: rgba(139, 92, 246, 150); + } + QMenu { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QMenu::item { + padding: 4px 20px; + } + QMenu::item:selected { + background-color: rgba(139, 92, 246, 100); + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + QComboBox { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px 8px; + } + QComboBox::drop-down { + border: none; + } + QComboBox QAbstractItemView { + background-color: #2d2d44; + color: #e0e0ff; + selection-background-color: rgba(139, 92, 246, 100); + } + QLineEdit { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px; + } + QSpinBox, QDoubleSpinBox { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px; + } + QScrollBar:vertical { + background-color: #252538; + width: 15px; + border: none; + } + QScrollBar::handle:vertical { + background-color: #3a3a4a; + border-radius: 4px; + min-height: 20px; + } + QScrollBar::handle:vertical:hover { + background-color: #8b5cf6; + } + QScrollBar:horizontal { + background-color: #252538; + width: 15px; + border: none; + } + QScrollBar::handle:horizontal { + background-color: #3a3a4a; + border-radius: 4px; + min-height: 20px; + } + QScrollBar::handle:horizontal:hover { + background-color: #8b5cf6; + } + """) + # 设置 QMessageBox 样式表 + self.setupMessageBoxStyles() + self.setupCenterWidget() # 创建中间部分Panda3D self.setupMenus() # 创建菜单栏 self.setupDockWindows() @@ -47,6 +154,173 @@ class MainWindow(QMainWindow): self.dragStartPos = QPoint(0, 0) self.toolbarStartPos = QPoint(0, 0) + def setupMessageBoxStyles(self): + """设置 QMessageBox 的全局样式""" + # 设置 QMessageBox 的样式表 + msg_box_style = """ + QMessageBox { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QMessageBox QLabel { + color: #e0e0ff; + } + QMessageBox QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + min-width: 80px; + } + QMessageBox QPushButton:hover { + background-color: #7c3aed; + } + QMessageBox QPushButton:pressed { + background-color: #6d28d9; + } + QMessageBox QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + """ + + # 应用全局样式 + self.setStyleSheet(self.styleSheet() + msg_box_style) + + def createStyledInputDialog(self, parent, title, label, mode=QLineEdit.Normal, text=""): + """创建带有统一主题样式的 QInputDialog""" + dialog = QInputDialog(parent) + dialog.setWindowTitle(title) + dialog.setLabelText(label) + dialog.setTextEchoMode(mode) + dialog.setTextValue(text) + + # 设置样式表 + dialog.setStyleSheet(""" + QInputDialog { + background-color: #252538; + color: #e0e0ff; + } + QLabel { + color: #e0e0ff; + font-weight: 500; + } + QLineEdit { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 6px; + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + min-width: 80px; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + """) + + return dialog + + def createStyledFileDialog(self, parent, caption, directory="", filter=""): + """创建带有统一主题样式的 QFileDialog""" + dialog = QFileDialog(parent) + dialog.setWindowTitle(caption) + + # 设置样式表 + dialog.setStyleSheet(""" + QFileDialog { + background-color: #252538; + color: #e0e0ff; + } + QLabel { + color: #e0e0ff; + } + QListView { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + alternate-background-color: #252538; + } + QListView::item:hover { + background-color: #3a3a4a; + } + QListView::item:selected { + background-color: rgba(139, 92, 246, 100); + color: white; + } + QTreeView { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + alternate-background-color: #252538; + } + QTreeView::item:hover { + background-color: #3a3a4a; + } + QTreeView::item:selected { + background-color: rgba(139, 92, 246, 100); + color: white; + } + QComboBox { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px 8px; + } + QComboBox::drop-down { + border: none; + } + QComboBox QAbstractItemView { + background-color: #2d2d44; + color: #e0e0ff; + selection-background-color: rgba(139, 92, 246, 100); + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + QLineEdit { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px; + } + """) + + return dialog def setupCenterWidget(self): """设置窗口基本属性""" @@ -88,28 +362,56 @@ class MainWindow(QMainWindow): # 创建工具栏容器 self.embeddedToolbar = QFrame(self.pandaWidget) self.embeddedToolbar.setObjectName("UnityToolbar") + # self.embeddedToolbar.setStyleSheet(""" + # QFrame#UnityToolbar { + # background-color: rgba(240, 240, 240, 180); + # border: 1px solid rgba(200, 200, 200, 200); + # border-radius: 4px; + # } + # QToolButton { + # background-color: rgba(250, 250, 250, 150); + # border: none; + # color: #333333; + # padding: 5px; + # border-radius: 3px; + # } + # QToolButton:hover { + # background-color: rgba(220, 220, 220, 200); + # } + # QToolButton:checked { + # background-color: rgba(100, 150, 220, 180); + # color: white; + # } + # QToolButton:pressed { + # background-color: rgba(80, 130, 200, 200); + # } + # """) self.embeddedToolbar.setStyleSheet(""" QFrame#UnityToolbar { - background-color: rgba(240, 240, 240, 180); - border: 1px solid rgba(200, 200, 200, 200); - border-radius: 4px; + background-color: rgba(40, 40, 60, 200); /* 深蓝灰色背景 */ + border: 1px solid rgba(80, 80, 120, 200); /* 深蓝灰边框 */ + border-radius: 6px; + padding: 4px; } QToolButton { - background-color: rgba(250, 250, 250, 150); - border: none; - color: #333333; - padding: 5px; - border-radius: 3px; + background-color: rgba(60, 60, 90, 180); /* 稍亮的深蓝灰 */ + border: 1px solid rgba(100, 100, 150, 150); + color: #e0e0ff; /* 浅蓝白色文字 */ + padding: 6px 8px; + border-radius: 4px; + font-weight: 500; } QToolButton:hover { - background-color: rgba(220, 220, 220, 200); + background-color: rgba(80, 80, 130, 200); /* 悬停时更亮 */ + border: 1px solid rgba(139, 92, 246, 150); /* 紫色边框 */ } QToolButton:checked { - background-color: rgba(100, 150, 220, 180); + background-color: rgba(139, 92, 246, 180); /* 紫色背景 */ color: white; + border: 1px solid rgba(180, 140, 255, 200); } QToolButton:pressed { - background-color: rgba(80, 130, 200, 200); + background-color: rgba(120, 75, 220, 200); /* 按下时稍暗的紫色 */ } """) @@ -530,6 +832,43 @@ class MainWindow(QMainWindow): """创建停靠窗口""" # 创建左侧停靠窗口(层级窗口) self.leftDock = QDockWidget("层级", self) + self.leftDock.setStyleSheet(""" + QDockWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QDockWidget::title { + background-color: #2d2d44; + padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ + border-bottom: 0px solid #3a3a4a; + } + QDockWidget::close-button, QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + } + QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + } + QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + } + QTreeView { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + alternate-background-color: #252538; + } + QTreeView::item:hover { + background-color: #3a3a4a; + } + QTreeView::item:selected { + background-color: rgba(139, 92, 246, 100); + color: white; + } + """) self.treeWidget = CustomTreeWidget(self.world) self.world.setTreeWidget(self.treeWidget) # 设置树形控件引用 self.leftDock.setWidget(self.treeWidget) @@ -537,6 +876,55 @@ class MainWindow(QMainWindow): # 创建右侧停靠窗口(属性窗口) self.rightDock = QDockWidget("属性", self) + self.rightDock.setStyleSheet(""" + QDockWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QDockWidget::title { + background-color: #2d2d44; + padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ + border-bottom: 0px solid #3a3a4a; + } + QDockWidget::close-button, QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + } + QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + } + QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + } + QScrollArea { + background-color: #1e1e2e; + border: none; + } + QWidget#PropertyContainer { + background-color: #1e1e2e; + } + QLabel { + color: #e0e0ff; + } + QGroupBox { + background-color: #252538; + border: 1px solid #3a3a4a; + border-radius: 6px; + margin-top: 1ex; + color: #e0e0ff; + font-weight: 500; + padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */ + } + QGroupBox::title { + subline-offset: -2px; + padding: 0 8px; + color: #c0c0e0; + font-weight: 500; + } + """) # 创建属性面板的主容器和布局 self.propertyContainer = QWidget() @@ -585,9 +973,64 @@ class MainWindow(QMainWindow): # 创建脚本管理停靠窗口 self.scriptDock = QDockWidget("脚本管理", self) + self.scriptDock.setStyleSheet(""" + QDockWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QDockWidget::title { + background-color: #2d2d44; + padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ + border-bottom: 0px solid #3a3a4a; + } + QDockWidget::close-button, QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + } + QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + } + QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + } + QScrollArea { + background-color: #1e1e2e; + border: none; + } + QWidget#PropertyContainer { + background-color: #1e1e2e; + } + QLabel { + color: #e0e0ff; + } + QGroupBox { + background-color: #252538; + border: 1px solid #3a3a4a; + border-radius: 6px; + margin-top: 1ex; + color: #e0e0ff; + font-weight: 500; + padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */ + } + QGroupBox::title { + subline-offset: -2px; + padding: 0 8px; + color: #c0c0e0; + font-weight: 500; + } + """) # 创建脚本面板的主容器和布局(与属性面板相同结构) self.scriptContainer = QWidget() + # 设置脚本容器的背景色 + self.scriptContainer.setStyleSheet(""" + QWidget#ScriptContainer { + background-color: #1e1e2e; + } + """) self.scriptContainer.setObjectName("ScriptContainer") self.scriptLayout = QVBoxLayout(self.scriptContainer) @@ -604,9 +1047,6 @@ class MainWindow(QMainWindow): self.setupScriptPanel(self.scriptLayout) self.addDockWidget(Qt.RightDockWidgetArea, self.scriptDock) - # 将右侧停靠窗口设为标签形式 - self.tabifyDockWidget(self.rightDock, self.scriptDock) - # # 创建底部停靠窗口(资源窗口) # self.bottomDock = QDockWidget("资源", self) # self.bottomDock.setAllowedAreas(Qt.BottomDockWidgetArea) @@ -635,15 +1075,158 @@ class MainWindow(QMainWindow): # 创建底部停靠窗口(资源窗口) self.bottomDock = QDockWidget("资源", self) + self.bottomDock.setStyleSheet(""" + QDockWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QDockWidget::title { + background-color: #2d2d44; + padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ + border-bottom: 0px solid #3a3a4a; + } + QDockWidget::close-button, QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + } + QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + } + QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + } + """) + self.fileView = CustomAssetsTreeWidget(self.world) + # 为资源树添加样式 + self.fileView.setStyleSheet(""" + QTreeWidget { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + alternate-background-color: #252538; + } + QTreeWidget::item:hover { + background-color: #3a3a4a; + } + QTreeWidget::item:selected { + background-color: rgba(139, 92, 246, 100); + color: white; + } + QHeaderView::section { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + padding: 4px; + } + """) self.bottomDock.setWidget(self.fileView) self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock) # 创建底部停靠控制台 - # self.consoleDock = QDockWidget("控制台", self) - # self.consoleView = CustomConsoleDockWidget(self.world) - # self.consoleDock.setWidget(self.consoleView) - # self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock) + self.consoleDock = QDockWidget("控制台", self) + self.consoleDock.setStyleSheet(""" + QDockWidget { + background-color: #252538; + color: #e0e0ff; + border: 1px solid #3a3a4a; + } + QDockWidget::title { + background-color: #2d2d44; + padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */ + border-bottom: 0px solid #3a3a4a; + } + QDockWidget::close-button, QDockWidget::float-button { + background-color: #8b5cf6; + border: none; + icon-size: 8px; /* 调整图标大小 */ + border-radius: 4px; /* 增加圆角 */ + } + QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background-color: #7c3aed; /* 悬停时显示较亮的背景 */ + } + QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + background-color: #8b5cf6; /* 按下时显示紫色高亮 */ + } + """) + self.consoleView = CustomConsoleDockWidget(self.world) + # 为控制台添加样式 + self.consoleView.setStyleSheet(""" + QTextEdit { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + font-family: 'Consolas', 'Monaco', monospace; + } + """) + self.consoleDock.setWidget(self.consoleView) + self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock) + + # 将右侧停靠窗口设为标签形式 + # self.tabifyDockWidget(self.rightDock, self.scriptDock) + # # 将底部的两个窗口也标签化 + # self.tabifyDockWidget(self.bottomDock, self.consoleDock) + # 设定默认显示的标签 + self.bottomDock.raise_() + self.rightDock.raise_() + self.scriptDock.raise_() + self.consoleDock.raise_() + self.leftDock.raise_() + # ========================================================================= + # ↓↓↓ 新增代码:为停靠窗口的标签栏(QTabBar)设置统一样式 ↓↓↓ + # ========================================================================= + # 这段样式会应用到主窗口内的所有 QTabBar,特别是停靠区域的标签栏。 + tab_bar_style = """ + /* QTabBar 的整体样式 */ + QTabBar { + qproperty-drawBase: 0; /* 移除标签栏底部的线条 */ + } + + QTabBar::tab { + background-color: #2d2d44; /* 未选中标签的背景色,与标题栏一致 */ + color: #c0c0e0; /* 未选中标签的文字颜色 */ + border: 1px solid #3a3a4a; /* 边框颜色 */ + border-bottom: none; /* 移除底部边框 */ + border-top-left-radius: 6px; + border-top-right-radius: 6px; + padding: 8px 16px; /* 内边距,让文字不拥挤 */ + font-weight: 500; + margin-right: 2px; /* 标签之间的间距 */ + } + + /* 鼠标悬停在标签上时的样式 */ + QTabBar::tab:hover { + background-color: #3a3a4a; /* 悬停时使用稍亮的背景色 */ + } + + /* 当前选中的标签页的样式 */ + QTabBar::tab:selected { + background-color: #252538; /* 选中时使用 Dock 背景色 */ + color: #ffffff; /* 选中时使用更亮的文字颜色 */ + font-weight: bold; /* 字体加粗 */ + border-color: #3a3a4a; + border-bottom: 2px solid #8b5cf6; /* 选中标签的底部高亮线 */ + } + + /* 未选中的标签 */ + QTabBar::tab:!selected { + margin-top: 2px; /* 未选中标签稍微下移,创建层次感 */ + } + + /* 标签栏底部的线条 */ + QTabBar::tab-bar { + alignment: left; + border: none; /* 移除标签栏边框 */ + } + """ + + # 获取主窗口现有的样式表,并附加我们新的样式规则 + # 这样可以避免覆盖掉其他可能存在的全局样式 + existing_style = self.styleSheet() + self.setStyleSheet(existing_style + tab_bar_style) def setupToolbar(self): """创建工具栏""" @@ -786,6 +1369,31 @@ class MainWindow(QMainWindow): # 脚本列表 self.scriptsList = QListWidget() + self.scriptsList.setStyleSheet(""" + QListWidget { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + alternate-background-color: #252538; + selection-background-color: rgba(139, 92, 246, 100); + selection-color: white; + } + QListWidget::item { + padding: 6px 8px; + border-bottom: 1px solid #2d2d44; + } + QListWidget::item:last-child { + border-bottom: none; + } + QListWidget::item:hover { + background-color: #3a3a4a; + } + QListWidget::item:selected { + background-color: rgba(139, 92, 246, 120); + color: white; + } + """) self.scriptsList.itemDoubleClicked.connect(self.onScriptDoubleClick) scriptsLayout.addWidget(self.scriptsList) @@ -829,6 +1437,32 @@ class MainWindow(QMainWindow): # 已挂载脚本列表 self.mountedScriptsList = QListWidget() + self.mountedScriptsList.setStyleSheet(""" + QListWidget { + background-color: #1e1e2e; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + alternate-background-color: #252538; + selection-background-color: rgba(139, 92, 246, 100); + selection-color: white; + max-height: 120px; + } + QListWidget::item { + padding: 4px 8px; + border-bottom: 1px solid #2d2d44; + } + QListWidget::item:last-child { + border-bottom: none; + } + QListWidget::item:hover { + background-color: #3a3a4a; + } + QListWidget::item:selected { + background-color: rgba(139, 92, 246, 120); + color: white; + } + """) self.mountedScriptsList.setMaximumHeight(100) mountLayout.addWidget(QLabel("已挂载脚本:")) mountLayout.addWidget(self.mountedScriptsList) @@ -921,13 +1555,21 @@ class MainWindow(QMainWindow): def onUpdateCesiumURL(self): """更新 Cesium URL""" - url, ok = QInputDialog.getText(self, "更新 Cesium URL", "输入新的 URL:", - QLineEdit.Normal, "http://localhost:8080/Apps/HelloWorld.html") - if ok and url: - if hasattr(self.world, 'gui_manager') and self.world.gui_manager: - self.world.gui_manager.updateCesiumURL(url) - else: - QMessageBox.warning(self, "错误", "GUI 管理器不可用") + dialog = self.createStyledInputDialog( + self, + "更新 Cesium URL", + "输入新的 URL:", + QLineEdit.Normal, + "http://localhost:8080/Apps/HelloWorld.html" + ) + if dialog.exec_() == QDialog.Accepted: + url = dialog.textValue() + if url: + if hasattr(self.world, 'gui_manager') and self.world.gui_manager: + self.world.gui_manager.updateCesiumURL(url) + else: + QMessageBox.warning(self, "错误", "GUI 管理器不可用") + def onAddModelClicked(self): """处理加入模型按钮点击事件""" @@ -961,53 +1603,56 @@ class MainWindow(QMainWindow): def showAddModelDialog(self): """显示添加模型对话框""" # 打开文件选择对话框 - file_path, _ = QFileDialog.getOpenFileName( + dialog = self.createStyledFileDialog( self, "选择 3D 模型文件", "", "3D 模型文件 (*.glb *.gltf *.obj);;所有文件 (*)" ) - if file_path: - # 获取模型位置信息 - coords, ok = self.getModelCoordinates() - if ok: - longitude, latitude, height, scale = coords + if dialog.exec_() == QDialog.Accepted: + file_path = dialog.selectedFiles()[0] + if file_path: + # 获取模型位置信息 + coords, ok = self.getModelCoordinates() + if ok: + longitude, latitude, height, scale = coords - # 生成唯一的模型 ID - import uuid - model_id = f"model_{uuid.uuid4().hex[:8]}" + # 生成唯一的模型 ID + import uuid + model_id = f"model_{uuid.uuid4().hex[:8]}" - try: - # 添加模型到 Cesium - if hasattr(self.world, 'gui_manager') and self.world.gui_manager: - success = self.world.gui_manager.addModelToCesium( - model_id, - file_path, - longitude, - latitude, - height, - scale + try: + # 添加模型到 Cesium + if hasattr(self.world, 'gui_manager') and self.world.gui_manager: + success = self.world.gui_manager.addModelToCesium( + model_id, + file_path, + longitude, + latitude, + height, + scale + ) + + if success: + QMessageBox.information( + self, + "成功", + f"模型已成功添加到地图!\n模型ID: {model_id}" + ) + else: + QMessageBox.warning( + self, + "失败", + "添加模型失败,请检查控制台输出" + ) + except Exception as e: + QMessageBox.critical( + self, + "错误", + f"添加模型时发生错误:\n{str(e)}" ) - if success: - QMessageBox.information( - self, - "成功", - f"模型已成功添加到地图!\n模型ID: {model_id}" - ) - else: - QMessageBox.warning( - self, - "失败", - "添加模型失败,请检查控制台输出" - ) - except Exception as e: - QMessageBox.critical( - self, - "错误", - f"添加模型时发生错误:\n{str(e)}" - ) def getModelCoordinates(self): """获取模型坐标信息的对话框""" @@ -1080,7 +1725,7 @@ class MainWindow(QMainWindow): return None, False def onLoadCesiumTileset(self): - url,ok = QInputDialog.getText( + dialog = self.createStyledInputDialog( self, "加载 Cesium 3D Tiles", "输入 tileset.json URL:", @@ -1088,34 +1733,35 @@ class MainWindow(QMainWindow): "https://assets.ion.cesium.com/96128/tileset.json" ) - if ok and url: - try: - # 生成唯一的 tileset 名称 - import uuid - tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" - - # 加载 tileset - if hasattr(self.world, 'addCesiumTileset'): - success = self.world.addCesiumTileset(tileset_name, url, (0, 0, 0)) - if success: - QMessageBox.information( - self, - "成功", - f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}" - ) - else: - QMessageBox.warning( - self, - "失败", - "加载 Cesium 3D Tiles 失败" - ) - except Exception as e: - QMessageBox.critical( - self, - "错误", - f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}" - ) + if dialog.exec_() == QDialog.Accepted: + url = dialog.textValue() + if url: + try: + # 生成唯一的 tileset 名称 + import uuid + tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" + # 加载 tileset + if hasattr(self.world, 'addCesiumTileset'): + success = self.world.addCesiumTileset(tileset_name, url, (0, 0, 0)) + if success: + QMessageBox.information( + self, + "成功", + f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}" + ) + else: + QMessageBox.warning( + self, + "失败", + "加载 Cesium 3D Tiles 失败" + ) + except Exception as e: + QMessageBox.critical( + self, + "错误", + f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}" + ) def onToolChanged(self, button): """工具切换事件处理""" @@ -1708,17 +2354,21 @@ class MainWindow(QMainWindow): def onCreateScriptDialog(self): """菜单创建脚本事件""" - script_name, ok = QInputDialog.getText(self, "创建脚本", "输入脚本名称:") - if ok and script_name.strip(): - try: - success = self.world.createScript(script_name.strip(), "basic") - if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") - self.refreshScriptsList() - else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") - except Exception as e: - QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") + dialog = self.createStyledInputDialog(self, "创建脚本", "输入脚本名称:") + + if dialog.exec_() == QDialog.Accepted: + script_name = dialog.textValue() + if script_name.strip(): + try: + success = self.world.createScript(script_name.strip(), "basic") + if success: + QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") + self.refreshScriptsList() + else: + QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") + except Exception as e: + QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") + def onLoadScript(self): """加载脚本按钮事件""" @@ -1739,19 +2389,26 @@ class MainWindow(QMainWindow): def onLoadScriptFile(self): """加载脚本文件菜单事件""" - file_path, _ = QFileDialog.getOpenFileName( - self, "选择脚本文件", "", "Python文件 (*.py)" + dialog = self.createStyledFileDialog( + self, + "选择脚本文件", + "", + "Python文件 (*.py)" ) - if file_path: - try: - success = self.world.loadScript(file_path) - if success: - QMessageBox.information(self, "成功", "脚本文件加载成功!") - self.refreshScriptsList() - else: - QMessageBox.warning(self, "错误", "脚本文件加载失败!") - except Exception as e: - QMessageBox.critical(self, "错误", f"加载脚本文件时出错: {str(e)}") + + if dialog.exec_() == QDialog.Accepted: + file_path = dialog.selectedFiles()[0] + if file_path: + try: + success = self.world.loadScript(file_path) + if success: + QMessageBox.information(self, "成功", "脚本文件加载成功!") + self.refreshScriptsList() + else: + QMessageBox.warning(self, "错误", "脚本文件加载失败!") + except Exception as e: + QMessageBox.critical(self, "错误", f"加载脚本文件时出错: {str(e)}") + def onReloadAllScripts(self): """重载所有脚本事件""" @@ -1870,6 +2527,43 @@ class MainWindow(QMainWindow): dialog.setWindowTitle("创建平面地形") dialog.setModal(True) dialog.resize(300,200) + # 设置对话框样式 + dialog.setStyleSheet(""" + QDialog { + background-color: #252538; + color: #e0e0ff; + } + QLabel { + color: #e0e0ff; + font-weight: 500; + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + min-width: 80px; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + QDoubleSpinBox, QSpinBox { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 4px; + } + """) layout = QVBoxLayout(dialog) @@ -1927,85 +2621,87 @@ class MainWindow(QMainWindow): def onCreateHeightmapTerrain(self): """从高度图创建地形""" - file_path,_=QFileDialog.getOpenFileName( + dialog = self.createStyledFileDialog( self, "选择高度图文件", "", "图像文件 (*.png *.jpg *.jpeg *.bmp *.tga);;所有文件 (*)" ) - if file_path: - #创建对话框获取地形参数 - dialog = QDialog(self) - dialog.setWindowTitle("设置地形参数") - dialog.setModal(True) - dialog.resize(300,250) + if dialog.exec_() == QDialog.Accepted: + file_path = dialog.selectedFiles()[0] + if file_path: + #创建对话框获取地形参数 + dialog = QDialog(self) + dialog.setWindowTitle("设置地形参数") + dialog.setModal(True) + dialog.resize(300,250) - layout = QVBoxLayout(dialog) + layout = QVBoxLayout(dialog) - x_scale_layout = QHBoxLayout() - x_scale_layout.addWidget(QLabel("X缩放:")) - x_scale_spin = QDoubleSpinBox() - x_scale_spin.setRange(0.1,1000) - x_scale_spin.setValue(0.3) - x_scale_spin.setSingleStep(10) - x_scale_layout.addWidget(x_scale_spin) - layout.addLayout(x_scale_layout) + x_scale_layout = QHBoxLayout() + x_scale_layout.addWidget(QLabel("X缩放:")) + x_scale_spin = QDoubleSpinBox() + x_scale_spin.setRange(0.1,1000) + x_scale_spin.setValue(0.3) + x_scale_spin.setSingleStep(10) + x_scale_layout.addWidget(x_scale_spin) + layout.addLayout(x_scale_layout) - # Y缩放 - y_scale_layout = QHBoxLayout() - y_scale_layout.addWidget(QLabel("Y缩放:")) - y_scale_spin = QDoubleSpinBox() - y_scale_spin.setRange(0.1, 1000) - y_scale_spin.setValue(0.3) - y_scale_spin.setSingleStep(10) - y_scale_layout.addWidget(y_scale_spin) - layout.addLayout(y_scale_layout) + # Y缩放 + y_scale_layout = QHBoxLayout() + y_scale_layout.addWidget(QLabel("Y缩放:")) + y_scale_spin = QDoubleSpinBox() + y_scale_spin.setRange(0.1, 1000) + y_scale_spin.setValue(0.3) + y_scale_spin.setSingleStep(10) + y_scale_layout.addWidget(y_scale_spin) + layout.addLayout(y_scale_layout) - # Z缩放 - z_scale_layout = QHBoxLayout() - z_scale_layout.addWidget(QLabel("Z缩放:")) - z_scale_spin = QDoubleSpinBox() - z_scale_spin.setRange(0.1, 1000) - z_scale_spin.setValue(50) - z_scale_spin.setSingleStep(5) - z_scale_layout.addWidget(z_scale_spin) - layout.addLayout(z_scale_layout) + # Z缩放 + z_scale_layout = QHBoxLayout() + z_scale_layout.addWidget(QLabel("Z缩放:")) + z_scale_spin = QDoubleSpinBox() + z_scale_spin.setRange(0.1, 1000) + z_scale_spin.setValue(50) + z_scale_spin.setSingleStep(5) + z_scale_layout.addWidget(z_scale_spin) + layout.addLayout(z_scale_layout) - # 按钮 - button_layout = QHBoxLayout() - ok_button = QPushButton("创建") - cancel_button = QPushButton("取消") - button_layout.addWidget(ok_button) - button_layout.addWidget(cancel_button) - layout.addLayout(button_layout) + # 按钮 + button_layout = QHBoxLayout() + ok_button = QPushButton("创建") + cancel_button = QPushButton("取消") + button_layout.addWidget(ok_button) + button_layout.addWidget(cancel_button) + layout.addLayout(button_layout) - # 连接信号 - ok_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) + # 连接信号 + ok_button.clicked.connect(dialog.accept) + cancel_button.clicked.connect(dialog.reject) - # 显示对话框 - if dialog.exec_() == QDialog.Accepted: - x_scale = x_scale_spin.value() - y_scale = y_scale_spin.value() - z_scale = z_scale_spin.value() + # 显示对话框 + if dialog.exec_() == QDialog.Accepted: + x_scale = x_scale_spin.value() + y_scale = y_scale_spin.value() + z_scale = z_scale_spin.value() - # 调用世界对象创建地形 - terrain_info = self.world.createTerrainFromHeightMap( - file_path, - (x_scale, y_scale, z_scale) - ) - if terrain_info: - QMessageBox.information(self, "成功", "高度图地形创建成功!") - else: - QMessageBox.warning(self, "错误", "高度图地形创建失败!") + # 调用世界对象创建地形 + terrain_info = self.world.createTerrainFromHeightMap( + file_path, + (x_scale, y_scale, z_scale) + ) + if terrain_info: + QMessageBox.information(self, "成功", "高度图地形创建成功!") + else: + QMessageBox.warning(self, "错误", "高度图地形创建失败!") def setup_main_window(world,path = None): """设置主窗口的便利函数""" app = QApplication.instance() if app is None: app = QApplication(sys.argv) - + main_window = MainWindow(world) main_window.show() from main import openProjectForPath diff --git a/ui/property_panel.py b/ui/property_panel.py index c62aa2e4..d7262ee6 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -7,7 +7,7 @@ from typing import Hashable from PyQt5.QtGui import QColor from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget, - QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog) + QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog, QMessageBox) from PyQt5.QtCore import Qt from deploy_libs.unicodedata import normalize from direct.actor.Actor import Actor @@ -235,23 +235,23 @@ class PropertyPanelManager: #材质属性 self._updateTerrainMaterialPanel(terrain_node,terrain_info) - #删除按钮 - delete_btn = QPushButton("删除地形") - delete_btn.setStyleSheet(""" - QPushButton{ - background-color:#ff4444; - color:white; - border:none; - padding:8px; - border-radius:4px; - margin-top:10px - } - QPushButton:hover{ - background-color:#ff6666; - } - """) - delete_btn.clicked.connect(lambda:self._deleteTerrain(terrain_info,item)) - self._propertyLayout.addWidget(delete_btn) + # #删除按钮 + # delete_btn = QPushButton("删除地形") + # delete_btn.setStyleSheet(""" + # QPushButton{ + # background-color:#ff4444; + # color:white; + # border:none; + # padding:8px; + # border-radius:4px; + # margin-top:10px + # } + # QPushButton:hover{ + # background-color:#ff6666; + # } + # """) + # delete_btn.clicked.connect(lambda:self._deleteTerrain(terrain_info,item)) + # self._propertyLayout.addWidget(delete_btn) except Exception as e: print(f"显示地形属性时出错: {e}") import traceback @@ -909,23 +909,23 @@ class PropertyPanelManager: scale_group.setLayout(scale_layout) self._propertyLayout.addWidget(scale_group) - # 删除按钮 - delete_btn = QPushButton("删除 Tileset") - delete_btn.setStyleSheet(""" - QPushButton { - background-color: #ff4444; - color: white; - border: none; - padding: 8px; - border-radius: 4px; - margin-top: 10px; - } - QPushButton:hover { - background-color: #ff6666; - } - """) - delete_btn.clicked.connect(lambda: self._deleteCesiumTileset(nodePath, item)) - self._propertyLayout.addWidget(delete_btn) + # # 删除按钮 + # delete_btn = QPushButton("删除 Tileset") + # delete_btn.setStyleSheet(""" + # QPushButton { + # background-color: #ff4444; + # color: white; + # border: none; + # padding: 8px; + # border-radius: 4px; + # margin-top: 10px; + # } + # QPushButton:hover { + # background-color: #ff6666; + # } + # """) + # delete_btn.clicked.connect(lambda: self._deleteCesiumTileset(nodePath, item)) + # self._propertyLayout.addWidget(delete_btn) # 添加弹性空间 self._propertyLayout.addStretch() @@ -1280,8 +1280,6 @@ class PropertyPanelManager: model.setScale(current_scale.getX(), current_scale.getY(), value) self.refreshModelValues(model) - - def updateGUIPropertyPanel(self, gui_element,item): """更新GUI元素属性面板""" self.clearPropertyPanel() diff --git a/ui/widgets.py b/ui/widgets.py index c1507f18..a2347cfb 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -18,7 +18,7 @@ from PyQt5.QtCore import Qt, QUrl, QMimeData from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush from PyQt5.sip import wrapinstance from direct.showbase.ShowBaseGlobal import aspect2d -from panda3d.core import ModelRoot, NodePath +from panda3d.core import ModelRoot, NodePath, CollisionNode from QPanda3D.QPanda3DWidget import QPanda3DWidget from scene import util @@ -30,7 +30,68 @@ class NewProjectDialog(QDialog): super().__init__(parent) self.setWindowTitle("新建项目") self.setMinimumWidth(500) - + + # 设置对话框样式与主窗口保持一致 + self.setStyleSheet(""" + QDialog { + background-color: #252538; + color: #e0e0ff; + } + QGroupBox { + background-color: #2d2d44; + border: 1px solid #3a3a4a; + border-radius: 6px; + margin-top: 1ex; /* 保持这个设置 */ + color: #e0e0ff; + font-weight: 500; + padding-top: 10px; /* 增加顶部内边距,为标题留出空间 */ + } + QGroupBox::title { + subline-offset: -2px; + padding: 0 8px; + color: #c0c0e0; + font-weight: 500; + } + QLineEdit { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + border-radius: 4px; + padding: 6px; + } + QLineEdit:disabled { + background-color: #1e1e2e; + color: #8888aa; + } + QPushButton { + background-color: #8b5cf6; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + QPushButton:disabled { + background-color: #4c4c6e; + color: #8888aa; + } + QLabel { + color: #e0e0ff; + } + QLabel:disabled { + color: #8888aa; + } + QDialogButtonBox QPushButton { + min-width: 80px; + } + """) + # 创建布局 layout = QVBoxLayout(self) @@ -1251,12 +1312,60 @@ class CustomConsoleDockWidget(QWidget): self.autoScrollBtn = QPushButton("自动滚动") self.autoScrollBtn.setCheckable(True) self.autoScrollBtn.setChecked(True) + self.autoScrollBtn.setStyleSheet(""" + QPushButton { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:checked { + background-color: #8b5cf6; + color: white; + border: 1px solid #7c3aed; + } + QPushButton:hover { + background-color: #3a3a4a; + } + QPushButton:checked:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + """) toolbar.addWidget(self.autoScrollBtn) # 时间戳开关 self.timestampBtn = QPushButton("显示时间") self.timestampBtn.setCheckable(True) self.timestampBtn.setChecked(True) + self.timestampBtn.setStyleSheet(""" + QPushButton { + background-color: #2d2d44; + color: #e0e0ff; + border: 1px solid #3a3a4a; + padding: 6px 12px; + border-radius: 4px; + font-weight: 500; + } + QPushButton:checked { + background-color: #8b5cf6; + color: white; + border: 1px solid #7c3aed; + } + QPushButton:hover { + background-color: #3a3a4a; + } + QPushButton:checked:hover { + background-color: #7c3aed; + } + QPushButton:pressed { + background-color: #6d28d9; + } + """) toolbar.addWidget(self.timestampBtn) toolbar.addStretch() @@ -1419,34 +1528,54 @@ class CustomTreeWidget(QTreeWidget): self.original_scales={} + self.setStyleSheet(""" + /* 设置折叠状态下,带子节点的箭头颜色 */ + QTreeWidget::branch:has-children:!open { + color: #8b5cf6; /* 紫色 */ + } + + /* 设置展开状态下,带子节点的箭头颜色 */ + QTreeWidget::branch:has-children:open { + color: #9ca3af; /* 灰色,提供状态变化反馈 */ + } + + /* 鼠标悬停在任意箭头上时,颜色变亮 */ + QTreeWidget::branch:hover { + color: #a78bfa; /* 亮紫色 */ + } + """) + def initData(self): """初始化变量""" # 定义2D GUI元素类型 self.gui_2d_types = { - "GUI_BUTTON", # DirectButton - "GUI_LABEL", # DirectLabel - "GUI_ENTRY", # DirectEntry - "GUI_IMAGE", + "GUI_BUTTON", # GUI 按钮 + "GUI_LABEL", # GUI 标签 + "GUI_ENTRY", # GUI 输入框 + "GUI_IMAGE", # GUI 图片 + "GUI_2D_VIDEO_SCREEN", # GUI 2D视频 + "GUI_SPHERICAL_VIDEO", # GUI 3D球形视频 "GUI_NODE" # 其他2D GUI容器 } # 定义3D GUI元素类型 self.gui_3d_types = { - "GUI_3DTEXT", # 3D TextNode - "GUI_3DIMAGE", - "GUI_VIRTUAL_SCREEN" # Virtual Screen + "GUI_3DTEXT", # 3D 文本节点 + "GUI_3DIMAGE", # 3D 图片节点 + "GUI_VIRTUAL_SCREEN", # 3D视频 + "GUI_VirtualScreen" # 3D虚拟视频 } # 定义3D场景节点类型(可以接受3D GUI元素和其他3D场景元素) self.scene_3d_types = { "SCENE_ROOT", "SCENE_NODE", - "LIGHT_NODE", + "LIGHT_NODE", # 灯节点 "CAMERA_NODE", - "IMPORTED_MODEL_NODE", + "IMPORTED_MODEL_NODE", # 导入模型节点 "MODEL_NODE", - "TERRAIN_NODE", - "CESIUM_TILESET_NODE" + "TERRAIN_NODE", # 地形节点 + "CESIUM_TILESET_NODE" # 3D Tileset } # 这是一个最佳实践,它让代码的意图变得非常清晰。 @@ -2192,53 +2321,60 @@ class CustomTreeWidget(QTreeWidget): return top_item return None - def create_model_items(self, model): + def create_model_items(self, model: NodePath): + """ + 【此函数保持不变】 + 创建模型项。 + 只寻找模型下一层带有 'is_scene_element' 标签的子节点作为分支的根, + 然后完整地展示这些分支。 + """ if not model: print("传入的参数model为空") return - # 创建根节点项 - # root_item = QTreeWidgetItem(self) - # root_item.setText(0, model.getName() or "Unnamed Node") - # root_item.setText(1, model.node().getTypeName()) - # root_item.setIcon(0, self.item_icons.get('model', self.item_icons['default'])) - # 存储NodePath引用以便后续操作 - # root_item.setData(0, Qt.UserRole, model) + # 找到场景树的根节点,我们将把模型节点添加到这里 root_item = self._findSceneRoot() + if not root_item: + print("错误:未能找到场景根节点项") + return - # 递归添加子节点 - self._add_children_recursive(root_item, model) + # 1. 在模型的第一层子节点中进行筛选 + for child_node in model.getChildren(): + if child_node.hasTag("is_scene_element"): + print(f"找到带标签的根节点:{child_node.getName()}") - return root_item + # 为这个带标签的节点创建一个树项 + child_item = QTreeWidgetItem(root_item) + child_item.setText(0, child_node.getName() or "Unnamed Tagged Node") + child_item.setData(0, Qt.UserRole, child_node) + child_item.setData(0, Qt.UserRole + 1, child_node.getTag("tree_item_type")) + # self._add_node_info(child_item, child_node) # 可选信息 - def _add_children_recursive(self, parent_item, node_path: NodePath): - """递归添加子节点到树项""" - print(f'开始递归添加子节点') - # 获取所有子节点 - children = node_path.getChildren() + # 2. 对这个节点的所有后代进行“无条件”递归添加 (但会跳过碰撞体) + self._add_all_children_unconditionally(child_item, child_node) - for i in range(children.getNumPaths()): - child_node: NodePath = children.getPath(i) + def _add_all_children_unconditionally(self, parent_item: QTreeWidgetItem, node_path: NodePath): + """ + 【此函数已更新】 + 无条件地、递归地添加一个节点下的所有子节点,但会跳过碰撞节点。 + """ + for child_node in node_path.getChildren(): - # 过滤条件 - if not child_node.hasTag("is_scene_element"): - print(f"不存在------------------------{child_node.hasTag('is_scene_element')}") - continue + # 新增:检查节点是否为碰撞节点 + if isinstance(child_node.node(), CollisionNode): + # print(f"跳过碰撞节点: {child_node.getName()}") # 用于调试 + continue # 如果是,则跳过此节点及其所有子节点 - print(f"存在------------------------{child_node.getName()}") # 创建子项 child_item = QTreeWidgetItem(parent_item) - child_item.setText(0, child_node.getName() or f"Child_{i}") - - # 存储NodePath引用 + child_item.setText(0, child_node.getName() or "Unnamed Child") child_item.setData(0, Qt.UserRole, child_node) + child_item.setData(0, Qt.UserRole + 1, child_node.getTag("tree_item_type")) + # self._add_node_info(child_item, child_node) # 可选信息 - # 添加额外信息(可选) - # self._add_node_info(child_item, child_node) - - # 递归处理子节点的子节点 - if child_node.getNumChildren() > 0: - self._add_children_recursive(child_item, child_node) + # 继续无条件地递归 + if not child_node.is_empty(): + self._add_all_children_unconditionally(child_item, child_node) # ==================== 辅助方法 ==================== def _findSceneRoot(self): @@ -2269,6 +2405,17 @@ class CustomTreeWidget(QTreeWidget): def add_node_to_tree_widget(self, node, parent_item, node_type): """将node元素添加到树形控件""" + if hasattr(node, 'getTag'): + if node.hasTag('tree_item_type'): + print(f"node0: {node.getName()},{node.getTag('tree_item_type')}") + tree_type = node.getTag('tree_item_type') + else: + node.setTag('tree_item_type', node_type) + else: + print(f"node2: {node.getName()},{node_type}") + tree_type = node_type + + # BLACK_LIST 和依赖项导入保持不变 BLACK_LIST = {'', '**', 'temp', 'collision'} from panda3d.core import CollisionNode, ModelRoot @@ -2283,7 +2430,7 @@ class CustomTreeWidget(QTreeWidget): nodeItem = QTreeWidgetItem(parentItem, [node.getName()]) nodeItem.setData(0, Qt.UserRole, node) - nodeItem.setData(0, Qt.UserRole + 1, node_type) + nodeItem.setData(0, Qt.UserRole + 1, tree_type) for child in node.getChildren(): # 递归调用,但我们只关心顶级的nodeItem @@ -2301,7 +2448,7 @@ class CustomTreeWidget(QTreeWidget): node_name = "" try: - if node_type == "IMPORTED_MODEL_NODE": + if tree_type == "IMPORTED_MODEL_NODE": # getTag('file') 可能是你自己设置的tag,这里假设它存在 node_name = node.getTag("file") if hasattr(node, 'getTag') and node.hasTag("file") else node.getName() @@ -2312,7 +2459,7 @@ class CustomTreeWidget(QTreeWidget): node_name = node.getName() if hasattr(node, 'getName') else "node" new_qt_item = QTreeWidgetItem(parent_item, [node_name]) new_qt_item.setData(0, Qt.UserRole, node) - new_qt_item.setData(0, Qt.UserRole + 1, node_type) + new_qt_item.setData(0, Qt.UserRole + 1, tree_type) # 确保 new_qt_item 成功创建后再继续操作 if new_qt_item: