diff --git a/core/terrain_manager.py b/core/terrain_manager.py index dd940091..07ae3d7f 100644 --- a/core/terrain_manager.py +++ b/core/terrain_manager.py @@ -13,6 +13,15 @@ class TerrainManager: self.terrains = [] # core/terrain_manager.py + def _get_tree_widget(self): + """安全获取树形控件""" + try: + if (hasattr(self.world, 'interface_manager') and + hasattr(self.world.interface_manager, 'treeWidget')): + return self.world.interface_manager.treeWidget + except AttributeError: + pass + return None def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)): """从高度图创建地形""" diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 1f1c921e..95aaa827 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -272,211 +272,6 @@ class GUIManager: return None def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08): - """创建2D GUI文本输入框""" - from direct.gui.DirectGui import DirectEntry - - # 将3D坐标转换为2D屏幕坐标 - gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) - - entry = DirectEntry( - text="", - pos=gui_pos, - scale=size, - command=self.onGUIEntrySubmit, - extraArgs=[f"entry_{len(self.gui_elements)}"], - initialText=placeholder, - numLines=1, - width=12, - focus=0 - ) - - # 为GUI元素添加标识 - entry.setTag("gui_type", "entry") - entry.setTag("gui_id", f"entry_{len(self.gui_elements)}") - entry.setTag("gui_placeholder", placeholder) - entry.setTag("is_gui_element", "1") - - self.gui_elements.append(entry) - # 安全地调用updateSceneTree - if hasattr(self.world, 'updateSceneTree'): - self.world.updateSceneTree() - - print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})") - return entry - def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2): - """创建2D GUI图片""" - from direct.gui.DirectGui import DirectFrame - from panda3d.core import TransparencyAttrib,Texture,CardMaker,CardMaker,Vec3 - - # 将3D坐标转换为2D屏幕坐标 - gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) - - #使用CardMaker创建一个更可靠的图片框架 - cm = CardMaker('gui-2d-image') - cm.setFrame(-size,size,-size,size) - - image_node = self.world.aspect2d.attachNewNode(cm.generate()) - image_node.setPos(gui_pos) - image_node.setBin('fixed',0) - image_node.setDepthWrite(False) - image_node.setDepthTest(False) - image_node.setColor(1,1,1,1) - - # 设置透明度支持 - image_node.setTransparency(TransparencyAttrib.MAlpha) - - # 如果提供了图像路径,则加载纹理 - if image_path: - try: - texture = self.world.loader.loadTexture(image_path) - if texture: - image_node.setTexture(texture,1) - texture.setWrapU(Texture.WM_clamp) - texture.setWrapV(Texture.WM_clamp) - texture.setMinfilter(Texture.FT_linear) - texture.setMagfilter(Texture.FT_linear) - image_node.setColor(1,1,1,1) - else: - print(f"⚠️ 无法加载2D图片纹理: {image_path}") - except Exception as e: - print(f"❌ 加载2D图片纹理失败: {e}") - - # 为GUI元素添加标识 - image_node.setTag("gui_type", "2d_image") - image_node.setTag("gui_id", f"2d_image_{len(self.gui_elements)}") - image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}") - if image_path: - image_node.setTag("image_path", image_path) - image_node.setTag("is_gui_element", "1") - - self.gui_elements.append(image_node) - - # 安全地调用updateSceneTree - if hasattr(self.world, 'updateSceneTree'): - self.world.updateSceneTree() - - print(f"✓ 创建2D GUI图片 (逻辑位置: {pos}, 屏幕位置: {gui_pos})") - return image_node - - def constrain2DPosition(self,gui_element,new_x=None,new_z=None): - """限制2dGUI元素位置在屏幕范围内""" - try: - from panda3d.core import Vec3 - - bounds = gui_element.getTightBounds() - element_width=0 - element_height=0 - - if bounds: - min_point,max_point = bounds - element_width = (max_point.getX() - min_point.getX())/2 - element_height = (max_point.getZ()-min_point.getZ())/2 - - #获取当前缩放 - scale = gui_element.getScale() - if hasattr(scale,'getX'): - scale_x = scale.getX() - scale_z = scale.getZ() if hasattr(scale,'getZ') else scale_x - else: - scale_x = scale_z = scale if isinstance(scale,(int,float)) else 1.0 - - actual_width = element_width * scale_x - actual_height = element_height * scale_z - - screen_width = 1.9 - screen_height = 0.9 - - min_x = -screen_width + actual_width - max_x = screen_width - actual_width - min_z = -screen_height + actual_height - max_z = screen_height - actual_height - - #获取当前位置 - current_pos = gui_element.getPos() - x = new_x if new_x is not None else current_pos.getX() - z = new_z if new_z is not None else current_pos.getZ() - - #应用边界限制 - x = max(min_x,min(max_x,x)) - z = max(min_z,min(max_z,z)) - - return Vec3(x,current_pos.getY(),z) - except Exception as e: - print(f"约束2D位置时出错: {e}") - # 出错时返回原始值 - current_pos = gui_element.getPos() - x = new_x if new_x is not None else current_pos.getX() - z = new_z if new_z is not None else current_pos.getZ() - return Vec3(x, current_pos.getY(), z) - - - def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1): - """创建3D空间文本""" - from panda3d.core import TextNode,Material,Vec4,ColorAttrib,TransparencyAttrib - - textNode = TextNode(f'3d-text-{len(self.gui_elements)}') - textNode.setText(text) - textNode.setAlign(TextNode.ACenter) - if self.world.getChineseFont(): - textNode.setFont(self.world.getChineseFont()) - - textNode.setTextColor(Vec4(1,1,1,1)) - - textNodePath = self.world.render.attachNewNode(textNode) - textNodePath.setPos(*pos) - textNodePath.setScale(size,size,size) - #textNodePath.setBillboardAxis() # 让文本总是面向相机 - - # 为3D文本创建默认材质 - material = Material(f"text-material-{len(self.gui_elements)}") - material.setBaseColor(Vec4(1, 1, 1, 1)) # 白色 - material.setDiffuse(Vec4(1, 1, 1, 1)) - material.setAmbient(Vec4(0.5, 0.5, 0.5, 1)) - material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0)) - material.setShininess(10.0) - #material.setEmission(0,0,0,1) - - textNodePath.setMaterial(material, 1) - - textNodePath.setTransparency(TransparencyAttrib.MAlpha) - textNodePath.setAttrib(ColorAttrib.makeFlat(Vec4(1, 1, 1, 1))) - textNodePath.setLightOff() - - # 为GUI元素添加标识 - textNodePath.setTag("gui_type", "3d_text") - textNodePath.setTag("gui_id", f"3d_text_{len(self.gui_elements)}") - textNodePath.setTag("gui_text", text) - textNodePath.setTag("is_gui_element", "1") - - textNodePath.setDepthWrite(True) # 确保深度写入 - textNodePath.setDepthTest(True) # 启用深度测试 - textNodePath.setBin("fixed", 0) # 设置渲染层级,避免被遮挡 - - # if hasattr(self, 'render_pipeline') and self.render_pipeline: - # try: - # self.render_pipeline.set_effect( - # textNodePath, - # "effects/default.yaml", - # { - # "normal_mapping": False, - # "render_gbuffer": False, - # "alpha_testing": True, - # "parallax_mapping": False, - # "render_shadow": False, - # "render_envmap": False - # }, - # 50 - # ) - # except Exception as e: - # print(f"⚠️ PBR效果应用失败: {e}") - - self.gui_elements.append(textNodePath) - # 安全地调用updateSceneTree - if hasattr(self.world, 'updateSceneTree'): - self.world.updateSceneTree() - - print(f"✓ 创建3D文本: {text} (世界位置: {pos})") - return textNodePath """创建2D GUI文本输入框 - 支持多选创建和GUI父子关系,优化版本""" try: from direct.gui.DirectGui import DirectEntry @@ -587,6 +382,185 @@ class GUIManager: traceback.print_exc() return None + def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2): + """创建2D GUI图片""" + try: + from direct.gui.DirectGui import DirectButton + from PyQt5.QtCore import Qt + + print(f"🔘 开始创建GUI按钮,位置: {pos}, 图片路径: {image_path}, 尺寸: {size}") + + # 获取树形控件 + tree_widget = self._get_tree_widget() + if not tree_widget: + print("❌ 无法访问树形控件") + return None + + # 使用CustomTreeWidget的方法获取目标父节点列表 + target_parents = tree_widget.get_target_parents_for_gui_creation() + if not target_parents: + print("❌ 没有找到有效的父节点") + return None + + created_2dimage = [] + + # 为每个有效的父节点创建GUI按钮 + for parent_item, parent_node in target_parents: + try: + # 生成唯一名称 + image_name = f"GUIImage_{len(self.gui_elements)}" + + # 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式 + if tree_widget.is_gui_element(parent_node): + # 父节点是GUI元素 - 作为子GUI挂载 + gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node) + parent_gui_node = parent_node # 直接挂载到GUI元素 + print(f"📎 挂载到GUI父节点: {parent_node.getName()}") + else: + # 父节点是普通3D节点 - 使用屏幕坐标 + gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1) + parent_gui_node = None # 使用默认的aspect2d + print(f"📎 挂载到3D父节点: {parent_item.text(0)}") + + # 使用CardMaker创建一个更可靠的图片框架 + cm = CardMaker('gui-2d-image') + cm.setFrame(-size, size, -size, size) + + image_node = self.world.aspect2d.attachNewNode(cm.generate()) + image_node.setPos(gui_pos) + image_node.setBin('fixed', 0) + image_node.setDepthWrite(False) + image_node.setDepthTest(False) + image_node.setColor(1, 1, 1, 1) + + # 设置透明度支持 + image_node.setTransparency(TransparencyAttrib.MAlpha) + + # 如果提供了图像路径,则加载纹理 + if image_path: + try: + texture = self.world.loader.loadTexture(image_path) + if texture: + image_node.setTexture(texture, 1) + texture.setWrapU(Texture.WM_clamp) + texture.setWrapV(Texture.WM_clamp) + texture.setMinfilter(Texture.FT_linear) + texture.setMagfilter(Texture.FT_linear) + image_node.setColor(1, 1, 1, 1) + else: + print(f"⚠️ 无法加载2D图片纹理: {image_path}") + except Exception as e: + print(f"❌ 加载2D图片纹理失败: {e}") + + # 设置节点标签 + image_node.setTag("gui_type", "2d_image") + image_node.setTag("gui_id", f"2d_image_{len(self.gui_elements)}") + 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("created_by_user", "1") + image_node.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") + image_node.setName(image_name) + + # 如果有GUI父节点,建立引用关系 + if parent_gui_node: + parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else "" + image_node.setTag("gui_parent_id", parent_id) + + # 添加到GUI元素列表 + self.gui_elements.append(image_node) + + print(f"✅ 为 {parent_item.text(0)} 创建GUI按钮成功: {image_name}") + + # 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点 + qt_item = tree_widget.add_node_to_tree_widget(image_node, parent_item, "GUI_IMAGE") + if qt_item: + created_2dimage.append((image_node, qt_item)) + else: + created_2dimage.append((image_node, None)) + print("⚠️ Qt树节点添加失败,但GUI对象已创建") + + except Exception as e: + print(f"❌ 为 {parent_item.text(0)} 创建GUI按钮失败: {str(e)}") + continue + + # 处理创建结果 + if not created_2dimage: + print("❌ 没有成功创建任何GUI按钮") + return None + + # 选中最后创建的按钮并更新场景树 + if created_2dimage: + last_button, last_qt_item = created_2dimage[-1] + if last_qt_item: + tree_widget.setCurrentItem(last_qt_item) + tree_widget.update_selection_and_properties(last_button, last_qt_item) + + print(f"🎉 总共创建了 {len(created_2dimage)} 个GUI按钮") + + # 返回值处理 + if len(created_2dimage) == 1: + return created_2dimage[0][0] + else: + return [button for button, _ in created_2dimage] + + except Exception as e: + print(f"❌ 创建GUI按钮过程失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def constrain2DPosition(self,gui_element,new_x=None,new_z=None): + """限制2dGUI元素位置在屏幕范围内""" + try: + from panda3d.core import Vec3 + + bounds = gui_element.getTightBounds() + element_width=0 + element_height=0 + + if bounds: + min_point,max_point = bounds + element_width = (max_point.getX() - min_point.getX())/2 + element_height = (max_point.getZ()-min_point.getZ())/2 + + #获取当前缩放 + scale = gui_element.getScale() + if hasattr(scale,'getX'): + scale_x = scale.getX() + scale_z = scale.getZ() if hasattr(scale,'getZ') else scale_x + else: + scale_x = scale_z = scale if isinstance(scale,(int,float)) else 1.0 + + actual_width = element_width * scale_x + actual_height = element_height * scale_z + + screen_width = 1.9 + screen_height = 0.9 + + min_x = -screen_width + actual_width + max_x = screen_width - actual_width + min_z = -screen_height + actual_height + max_z = screen_height - actual_height + + #获取当前位置 + current_pos = gui_element.getPos() + x = new_x if new_x is not None else current_pos.getX() + z = new_z if new_z is not None else current_pos.getZ() + + #应用边界限制 + x = max(min_x,min(max_x,x)) + z = max(min_z,min(max_z,z)) + + return Vec3(x,current_pos.getY(),z) + except Exception as e: + print(f"约束2D位置时出错: {e}") + # 出错时返回原始值 + current_pos = gui_element.getPos() + x = new_x if new_x is not None else current_pos.getX() + z = new_z if new_z is not None else current_pos.getZ() + return Vec3(x, current_pos.getY(), z) + def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5): """创建3D空间文本 - 支持多选创建,优化版本""" try: @@ -680,6 +654,141 @@ class GUIManager: traceback.print_exc() return None + def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0): + """创建3D空间图片""" + try: + from panda3d.core import TextNode + from PyQt5.QtCore import Qt + + print(f"📄 开始创建3D文本,位置: {pos}, 3D图片位置: {image_path}, 尺寸: {size}") + + # 获取树形控件 + tree_widget = self._get_tree_widget() + if not tree_widget: + print("❌ 无法访问树形控件") + return None + + # 获取目标父节点列表 + target_parents = tree_widget.get_target_parents_for_creation() + if not target_parents: + print("❌ 没有找到有效的父节点") + return None + + created_3dimage = [] + + # 为每个有效的父节点创建3D文本 + for parent_item, parent_node in target_parents: + try: + # 生成唯一名称 + image_name = f"GUI3DImage_{len(self.gui_elements)}" + + # 参数类型检查和转换 + if isinstance(size, (list, tuple)): + if len(size) >= 2: + x_size, y_size = float(size[0]), float(size[1]) + else: + x_size = y_size = float(size[0]) if size else 1.0 + else: + x_size = y_size = float(size) + + # 创建卡片 + cm = CardMaker('gui_3d_image') + cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2) + + # 创建3D图像节点 + image_node = self.world.render.attachNewNode(cm.generate()) + image_node.setPos(*pos) + + # 为3D图像创建独立的材质 + material = Material(f"image-material-{len(self.gui_elements)}") + material.setBaseColor(LColor(1, 1, 1, 1)) + material.setDiffuse(LColor(1, 1, 1, 1)) + material.setAmbient(LColor(0.5, 0.5, 0.5, 1)) + material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0)) + material.setShininess(10.0) + material.setEmission(LColor(0, 0, 0, 1)) # 无自发光 + image_node.setMaterial(material, 1) + + image_node.setTransparency(TransparencyAttrib.MAlpha) + + # 如果提供了图像路径,则加载纹理 + if image_path: + self.update3DImageTexture(image_node, image_path) + + # 应用PBR效果(如果可用) + try: + if hasattr(self, 'render_pipeline') and self.render_pipeline: + self.render_pipeline.set_effect( + image_node, + "effects/default.yaml", + { + "normal_mapping": True, + "render_gbuffer": True, + "alpha_testing": False, + "parallax_mapping": False, + "render_shadow": False, + "render_envmap": True, + "disable_children_effects": True + }, + 50 + ) + print("✓ GUI 3D图像PBR效果已应用") + except Exception as e: + print(f"⚠️ GUI 3D图像PBR效果应用失败: {e}") + image_node.setName(image_name) + + # 设置节点标签 + image_node.setTag("gui_type", "3d_image") + image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}") + if image_path: + image_node.setTag("gui_image_path", image_path) + image_node.setTag("is_gui_element", "1") + image_node.setTag("is_scene_element", "1") + image_node.setTag("created_by_user", "1") + + # 添加到GUI元素列表 + self.gui_elements.append(image_node) + + print(f"✅ 为 {parent_item.text(0)} 创建3D文本成功: {image_name}") + + # 在Qt树形控件中添加对应节点 + qt_item = tree_widget.add_node_to_tree_widget(image_node, parent_item, "GUI_3DIMAGE") + if qt_item: + created_3dimage.append((image_node, qt_item)) + else: + created_3dimage.append((image_node, None)) + print("⚠️ Qt树节点添加失败,但GUI对象已创建") + + except Exception as e: + print(f"❌ 为 {parent_item.text(0)} 创建3D文本失败: {str(e)}") + continue + + # 处理创建结果 + if not created_3dimage: + print("❌ 没有成功创建任何3D文本") + return None + + # 选中最后创建的文本并更新场景树 + if created_3dimage: + last_image, last_qt_item = created_3dimage[-1] + if last_qt_item: + tree_widget.setCurrentItem(last_qt_item) + tree_widget.update_selection_and_properties(last_image, last_qt_item) + + print(f"🎉 总共创建了 {len(created_3dimage)} 个3D文本") + + # 返回值处理 + if len(created_3dimage) == 1: + return created_3dimage[0][0] + else: + return [text_np for text_np, _ in created_3dimage] + + except Exception as e: + print(f"❌ 创建3D文本过程失败: {str(e)}") + import traceback + traceback.print_exc() + return None + def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"): """创建3D虚拟屏幕 - 支持多选创建,优化版本""" try: diff --git a/ui/interface_manager.py b/ui/interface_manager.py index 230c23d0..d2e8e4ac 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -282,9 +282,6 @@ class InterfaceManager: terrain_item.setData(0,Qt.UserRole+1,"terrain") # 标记为地形节点 addNodeToTree(terrain_node,terrain_item,force=True) - - - # 展开所有节点 #self.treeWidget.expandAll() self._restore_expanded() diff --git a/ui/main_window.py b/ui/main_window.py index 5946ccc3..27e974e1 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -41,6 +41,11 @@ class MainWindow(QMainWindow): self.updateTimer.timeout.connect(self.updateScriptPanel) self.updateTimer.start(500) # 每500毫秒更新一次 + self.toolbarDragging = False + self.dragStartPos = QPoint(0, 0) + self.toolbarStartPos = QPoint(0, 0) + + def setupCenterWidget(self): """设置窗口基本属性""" self.setWindowTitle("引擎编辑器") @@ -178,21 +183,27 @@ class MainWindow(QMainWindow): def toolbarMouseMoveEvent(self, event): """工具栏鼠标移动事件""" - if self.toolbarDragging and event.buttons() == Qt.LeftButton: - # 计算新位置 - delta = event.globalPos() - self.dragStartPos - new_pos = self.toolbarStartPos + delta + try: + if self.toolbarDragging and event.buttons() == Qt.LeftButton: + # 计算新位置 + delta = event.globalPos() - self.dragStartPos + new_pos = self.toolbarStartPos + delta - # 边界检测 - panda_rect = self.pandaWidget.geometry() - toolbar_size = self.embeddedToolbar.size() + # 边界检测 + panda_rect = self.pandaWidget.geometry() + toolbar_size = self.embeddedToolbar.size() - # 限制在Panda3D区域内 - new_pos.setX(max(0, min(new_pos.x(), panda_rect.width() - toolbar_size.width()))) - new_pos.setY(max(0, min(new_pos.y(), panda_rect.height() - toolbar_size.height()))) + # 限制在Panda3D区域内 + new_pos.setX(max(0, min(new_pos.x(), panda_rect.width() - toolbar_size.width()))) + new_pos.setY(max(0, min(new_pos.y(), panda_rect.height() - toolbar_size.height()))) + + self.embeddedToolbar.move(new_pos) + event.accept() + except Exception as e: + print(f"工具栏鼠标移动事件出错") + import traceback + traceback.print_exc() - self.embeddedToolbar.move(new_pos) - event.accept() def toolbarMouseReleaseEvent(self, event): """工具栏鼠标释放事件""" @@ -303,26 +314,12 @@ class MainWindow(QMainWindow): self.createMenu = menubar.addMenu('创建') self.setupCreateMenuActions() # 统一创建菜单动作 - self.createGUIaddMenu = self.createMenu.addMenu('GUI') - self.createButtonAction = self.createGUIaddMenu.addAction('创建按钮') - self.createLabelAction = self.createGUIaddMenu.addAction('创建标签') - self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框') - self.createGUIaddMenu.addSeparator() - self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕') - self.createCesiumViewAction = self.createGUIaddMenu.addAction('创建Cesium地图') - self.toggleCesiumViewAction = self.createGUIaddMenu.addAction('开关地图') - self.refreshCesiumViewAction = self.createGUIaddMenu.addAction('刷新地图') - - self.createLightaddMenu = self.createMenu.addMenu('光源') - self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯') - self.createPointLightAction = self.createLightaddMenu.addAction('点光源') - #添加地形菜单 self.createTerrainMenu = self.createMenu.addMenu('地形') self.createFlatTerrainAction = self.createTerrainMenu.addAction('创建平面地形') self.createHeightmapTerrainAction = self.createTerrainMenu.addAction('从高度图创建地形') self.createTerrainMenu.addSeparator() - self.terrainEditModeAction = self.createTerrainMenu.addAction('地形编辑模式') + # self.terrainEditModeAction = self.createTerrainMenu.addAction('地形编辑模式') # GUI菜单 # GUI菜单 - 复用创建菜单的动作 @@ -383,12 +380,14 @@ class MainWindow(QMainWindow): # 3D GUI子菜单 self.create3dGUIaddMenu = self.createMenu.addMenu('3D GUI') self.create3DTextAction = self.create3dGUIaddMenu.addAction('3D文本') + self.create3DImageAction = self.create3dGUIaddMenu.addAction('3D图片') # GUI子菜单 self.createGUIaddMenu = self.createMenu.addMenu('GUI') self.createButtonAction = self.createGUIaddMenu.addAction('创建按钮') self.createLabelAction = self.createGUIaddMenu.addAction('创建标签') self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框') + self.createImageAction = self.createGUIaddMenu.addAction('创建图片') self.createGUIaddMenu.addSeparator() self.createVideoScreen = self.createGUIaddMenu.addAction('创建视频屏幕') self.createSphericalVideo = self.createGUIaddMenu.addAction('创建球形视频') @@ -408,12 +407,14 @@ class MainWindow(QMainWindow): # 连接到world对象的创建方法 # self.createEnptyaddAction.triggered.connect(self.world.createEmptyObject) self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText()) + self.create3DImageAction.triggered.connect(lambda: self.world.createGUI3DImage()) self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton()) self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel()) self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry()) + self.createImageAction.triggered.connect(lambda: self.world.createGUI2DImage()) # self.createVideoScreen.triggered.connect(self.world.createVideoScreen) # self.createSphericalVideo.triggered.connect(self.world.createSphericalVideo) - # self.createVirtualScreenAction.triggered.connect(self.world.createVirtualScreen) + self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen()) self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight()) self.createPointLightAction.triggered.connect(lambda :self.world.createPointLight()) @@ -430,9 +431,11 @@ class MainWindow(QMainWindow): return { 'createEmpty': self.createEnptyaddAction, 'create3DText': self.create3DTextAction, + 'create3DImage': self.create3DImageAction, 'createButton': self.createButtonAction, 'createLabel': self.createLabelAction, 'createEntry': self.createEntryAction, + 'createImage': self.createImageAction, 'createVideoScreen': self.createVideoScreen, 'createSphericalVideo': self.createSphericalVideo, 'createVirtualScreen': self.createVirtualScreenAction, @@ -785,13 +788,13 @@ class MainWindow(QMainWindow): self.guiEditModeAction.triggered.connect(lambda: self.world.toggleGUIEditMode()) # 连接创建事件 - 使用菜单动作而不是不存在的工具栏按钮 - self.createSpotLightAction.triggered.connect(lambda: self.world.createSpotLight()) - self.createPointLightAction.triggered.connect(lambda: self.world.createPointLight()) - self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton()) - self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel()) - self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry()) - self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText()) - self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen()) + # self.createSpotLightAction.triggered.connect(lambda: self.world.createSpotLight()) + # self.createPointLightAction.triggered.connect(lambda: self.world.createPointLight()) + # self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton()) + # self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel()) + # self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry()) + # self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText()) + # self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen()) self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView) self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView) self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView) @@ -799,7 +802,7 @@ class MainWindow(QMainWindow): # 连接地形创建事件 self.createFlatTerrainAction.triggered.connect(self.onCreateFlatTerrain) self.createHeightmapTerrainAction.triggered.connect(self.onCreateHeightmapTerrain) - self.terrainEditModeAction.triggered.connect(self.onTerrainEditMode) + # self.terrainEditModeAction.triggered.connect(self.onTerrainEditMode) # 连接树节点点击信号 self.treeWidget.itemSelectionChanged.connect( diff --git a/ui/property_panel.py b/ui/property_panel.py index b15098d6..8edc81b2 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -1553,6 +1553,7 @@ class PropertyPanelManager: ) if file_path: # 应用新纹理到 3D Image + file_path = util.normalize_model_path(file_path) success = self.world.gui_manager.update3DImageTexture(gui_element, file_path) if success: # 保存路径到 Tag @@ -1560,7 +1561,7 @@ class PropertyPanelManager: # 更新显示 texture_label.setText(file_path) # 可选:刷新场景树或其他 UI - self.world.scene_manager.updateSceneTree() + # self.world.scene_manager.updateSceneTree() select_texture_button.clicked.connect(onSelectTexture) @@ -1595,6 +1596,7 @@ class PropertyPanelManager: ) if file_path: # 应用新纹理到 2D Image + file_path = util.normalize_model_path(file_path) success = self.update2DImageTexture(gui_element, file_path) if success: # 保存路径到 Tag @@ -1603,7 +1605,7 @@ class PropertyPanelManager: # 更新显示 #texture_label.setText(file_path) # 可选:刷新场景树或其他 UI - self.world.scene_manager.updateSceneTree() + # self.world.scene_manager.updateSceneTree() select_texture_button.clicked.connect(onSelect2DTexture) diff --git a/ui/widgets.py b/ui/widgets.py index 207f26d9..f7364990 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -1302,11 +1302,40 @@ class CustomTreeWidget(QTreeWidget): parent = wrapinstance(0, QWidget) super().__init__(parent) self.world = world + self.initData() self.setupUI() # 初始化界面 self.setupContextMenu() # 初始化右键菜单 self.setupDragDrop() # 设置拖拽功能 + def initData(self): + """初始化变量""" + # 定义2D GUI元素类型 + self.gui_2d_types = { + "GUI_BUTTON", # DirectButton + "GUI_LABEL", # DirectLabel + "GUI_ENTRY", # DirectEntry + "GUI_IMAGE", + "GUI_NODE" # 其他2D GUI容器 + } + + # 定义3D GUI元素类型 + self.gui_3d_types = { + "GUI_3DTEXT", # 3D TextNode + "GUI_3DIMAGE", + "GUI_VIRTUAL_SCREEN" # Virtual Screen + } + + # 定义3D场景节点类型(可以接受3D GUI元素和其他3D场景元素) + self.scene_3d_types = { + "SCENE_ROOT", + "SCENE_NODE", + "LIGHT_NODE", + "CAMERA_NODE", + "IMPORTED_MODEL_NODE", + "MODEL_NODE" + } + def setupUI(self): """初始化UI设置""" self.setHeaderHidden(True) @@ -1356,12 +1385,14 @@ class CustomTreeWidget(QTreeWidget): # 3D GUI菜单 create3dGUIMenu = createMenu.addMenu('3D GUI') create3dGUIMenu.addAction(create_actions['create3DText']) + create3dGUIMenu.addAction(create_actions['create3DImage']) # GUI菜单 createGUIMenu = createMenu.addMenu('GUI') createGUIMenu.addAction(create_actions['createButton']) createGUIMenu.addAction(create_actions['createLabel']) createGUIMenu.addAction(create_actions['createEntry']) + createGUIMenu.addAction(create_actions['createImage']) createGUIMenu.addSeparator() createGUIMenu.addAction(create_actions['createVideoScreen']) createGUIMenu.addAction(create_actions['createSphericalVideo']) @@ -1455,7 +1486,7 @@ class CustomTreeWidget(QTreeWidget): # 检查是否是2D GUI元素 dragged_type = dragged_item.data(0, Qt.UserRole + 1) - is_2d_gui = dragged_type in {"GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_NODE"} + is_2d_gui = dragged_type in self.gui_2d_types # 重新父化到新的父节点 if new_parent_node: @@ -1595,28 +1626,13 @@ class CustomTreeWidget(QTreeWidget): target_type = target_item.data(0, Qt.UserRole + 1) # 定义2D GUI元素类型 - gui_2d_types = { - "GUI_BUTTON", # DirectButton - "GUI_LABEL", # DirectLabel - "GUI_ENTRY", # DirectEntry - "GUI_NODE" # 其他2D GUI容器 - } + gui_2d_types = self.gui_2d_types # 定义3D GUI元素类型 - gui_3d_types = { - "GUI_3DTEXT", # 3D TextNode - "GUI_VIRTUAL_SCREEN" # Virtual Screen - } + gui_3d_types = self.gui_3d_types # 定义3D场景节点类型(可以接受3D GUI元素和其他3D场景元素) - scene_3d_types = { - "SCENE_ROOT", - "SCENE_NODE", - "LIGHT_NODE", - "CAMERA_NODE", - "IMPORTED_MODEL_NODE", - "MODEL_NODE" - } + scene_3d_types = self.scene_3d_types # 检查拖拽元素的类型 is_dragged_2d_gui = dragged_type in gui_2d_types @@ -1704,24 +1720,6 @@ class CustomTreeWidget(QTreeWidget): # 出错时采用保守策略,禁止拖拽 return False - def _getNodeTypeDescription(self, node_type): - """获取节点类型的描述文本(用于错误提示)""" - type_descriptions = { - "GUI_BUTTON": "2D按钮", - "GUI_LABEL": "2D标签", - "GUI_ENTRY": "2D输入框", - "GUI_NODE": "2D GUI容器", - "GUI_3DTEXT": "3D文本", - "GUI_VIRTUAL_SCREEN": "虚拟屏幕", - "SCENE_ROOT": "场景根节点", - "SCENE_NODE": "场景节点", - "LIGHT_NODE": "灯光节点", - "CAMERA_NODE": "相机节点", - "IMPORTED_MODEL_NODE": "导入模型", - "MODEL_NODE": "模型节点" - } - return type_descriptions.get(node_type, f"未知类型({node_type})") - def _getRootNode(self, item): """获取树中节点的根节点项""" if not item: @@ -2018,10 +2016,7 @@ class CustomTreeWidget(QTreeWidget): node_type = item.data(0, Qt.UserRole + 1) # 场景根节点和普通场景节点可以作为父节点 - if node_type in ["SCENE_ROOT", - "SCENE_NODE", "LIGHT_NODE", "CAMERA_NODE", - "IMPORTED_MODEL_NODE", - "GUI_3DTEXT"]: + if node_type in self.gui_3d_types and self.scene_3d_types: return True # # 模型节点也可以作为父节点 @@ -2095,7 +2090,7 @@ class CustomTreeWidget(QTreeWidget): node_type = item.data(0, Qt.UserRole + 1) # GUI元素可以作为其他GUI元素的父节点 - if node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_NODE"]: + if node_type in self.gui_2d_types: return True # 场景根节点和普通场景节点也可以作为父节点