From cdf2cd550ea5f020218aa19e49f2d8cb3c0b548d Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Mon, 25 Aug 2025 17:41:48 +0800 Subject: [PATCH] =?UTF-8?q?1.Cesium=20tilesets=202.3D=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/event_handler.py | 8 +- core/selection.py | 98 +++-- gui/gui_manager.py | 769 ++++++++++++++++++++++++++++++++++++++-- main.py | 13 + scene/scene_manager.py | 312 ++++++++++++++++ ui/interface_manager.py | 49 ++- ui/main_window.py | 258 +++++++++++++- ui/property_panel.py | 262 +++++++++++++- 8 files changed, 1693 insertions(+), 76 deletions(-) diff --git a/core/event_handler.py b/core/event_handler.py index 8308734d..ae684c7f 100644 --- a/core/event_handler.py +++ b/core/event_handler.py @@ -398,12 +398,8 @@ class EventHandler: if self.world.selection.gizmo and not self.world.selection.isDraggingGizmo: x = evt.get('x', 0) y = evt.get('y', 0) - # 只在前5次调用时输出调试信息,避免刷屏 - if not hasattr(self.world, '_highlight_debug_count'): - self.world._highlight_debug_count = 0 - if self.world._highlight_debug_count < 5: - print(f"更新坐标轴高亮: 鼠标({x}, {y}), 坐标轴存在={bool(self.world.selection.gizmo)}") - self.world._highlight_debug_count += 1 + # 减少高亮调试输出,只在需要时输出 + # 已静默处理,避免控制台刷屏 self.world.selection.updateGizmoHighlight(x, y) # 调用CoreWorld的父类方法处理基础的相机旋转 diff --git a/core/selection.py b/core/selection.py index 8cd4e826..063239a6 100644 --- a/core/selection.py +++ b/core/selection.py @@ -647,8 +647,11 @@ class SelectionSystem: is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False + #安区地更新朝向 + if is_scale_tool: - self.gizmo.setHpr(self.gizmoTarget.getHpr()) + #self.gizmo.setHpr(self.gizmoTarget.getHpr()) + self.gizmo.setQuat(self.gizmoTarget.getQuat(self.world.render)) else: parent_node = self.gizmoTarget.getParent() if parent_node and parent_node != self.world.render: @@ -1184,10 +1187,23 @@ class SelectionSystem: # 获取坐标轴中心的世界坐标 gizmo_world_pos = self.gizmo.getPos(self.world.render) + #获取坐标轴的世界朝向(考虑旋转) + gizmo_world_quat = self.gizmo.getQuat(self.world.render) + + #计算各轴在世界坐标系中的实际方向向量 + x_axis_world = gizmo_world_quat.xform(Vec3(1,0,0)) + y_axis_world = gizmo_world_quat.xform(Vec3(0,1,0)) + z_axis_world = gizmo_world_quat.xform(Vec3(0,0,1)) + + x_end = gizmo_world_pos + x_axis_world * self.axis_length + y_end = gizmo_world_pos + y_axis_world * self.axis_length + z_end = gizmo_world_pos + z_axis_world * self.axis_length + + # 计算各轴端点的世界坐标 - x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0) - y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) - z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length) + # x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0) + # y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) + # z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length) # 将3D坐标投影到屏幕坐标 def worldToScreen(worldPos): @@ -1284,8 +1300,8 @@ class SelectionSystem: return # 使用碰撞检测方法 - hoveredAxis = self.detectGizmoAxisWithCollision(mouseX, mouseY) - #hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY) + #hoveredAxis = self.detectGizmoAxisWithCollision(mouseX, mouseY) + hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY) # 简化稳定性检测逻辑 if not hasattr(self, '_last_detected_axis'): @@ -1348,8 +1364,17 @@ class SelectionSystem: # 使用当前高亮的轴,如果有的话;否则使用传入的轴 if self.gizmoHighlightAxis: self.dragGizmoAxis = self.gizmoHighlightAxis - else: + elif axis and axis in self.gizmo_colors: self.dragGizmoAxis = axis + else: + # 如果没有明确指定轴,尝试通过鼠标位置检测 + self.dragGizmoAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY) + + # 如果仍然无法确定拖拽轴,则取消拖拽 + if not self.dragGizmoAxis: + print("开始拖拽失败: 无法确定拖拽轴") + self.isDraggingGizmo = False + return self.dragStartMousePos = (mouseX, mouseY) @@ -1372,15 +1397,15 @@ class SelectionSystem: # 然后将当前拖动的轴设置为高亮颜色 self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis]) - elif axis and axis in self.gizmo_colors: - for axis_name in self.gizmo_colors.keys(): - if axis_name != axis: - self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name]) - - self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis]) - self.dragGizmoAxis = axis - - self.gizmoHighlightAxis = self.dragGizmoAxis + # elif axis and axis in self.gizmo_colors: + # for axis_name in self.gizmo_colors.keys(): + # if axis_name != axis: + # self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name]) + # + # self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis]) + # self.dragGizmoAxis = axis + # + # self.gizmoHighlightAxis = self.dragGizmoAxis print( f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})") @@ -1480,13 +1505,30 @@ class SelectionSystem: print(f"拖拽更新失败: 未知轴类型 {self.dragGizmoAxis}") return - # 确定轴向量的变换上下文 + world_axis_vector = local_axis_vector + if parent_node and parent_node != self.world.render: - transform_mat = parent_node.getMat(self.world.render) - world_axis_vector = transform_mat.xformVec(local_axis_vector) + try: + if parent_node.getTransform().hasMat(): + transform_mat = parent_node.getMat(self.world.render) + if not transform_mat.isSingular(): + world_axis_vector = transform_mat.xformVec(local_axis_vector) + else: + print("警告: 检测到奇异变换矩阵,使用默认轴向量") + else: + print("警告: 父节点没有有效的变换矩阵,使用默认轴向量") + except Exception as e: + print(f"变换计算出错: {e},使用默认轴向量") else: world_axis_vector = local_axis_vector + # 确定轴向量的变换上下文 + # if parent_node and parent_node != self.world.render: + # transform_mat = parent_node.getMat(self.world.render) + # world_axis_vector = transform_mat.xformVec(local_axis_vector) + # else: + # world_axis_vector = local_axis_vector + #axis_end = gizmo_world_pos + world_axis_vector # 投影到屏幕空间 @@ -1552,10 +1594,20 @@ class SelectionSystem: current_node = self.gizmoTarget.getParent() while current_node and current_node != self.world.render: - node_scale = current_node.getScale() - avg_scale = (node_scale.x+node_scale.y + node_scale.z) / 3.0 - total_scale_factor *= avg_scale - current_node = current_node.getParent() + try: + if not current_node.isEmpty(): + node_scale = current_node.getScale() + if node_scale.x > 0 and node_scale.y >0 and node_scale.z >0 : + avg_scale = (node_scale.x + node_scale.y + node_scale.z)/3.0 + total_scale_factor *= avg_scale + #avg_scale = (node_scale.x+node_scale.y + node_scale.z) / 3.0 + #total_scale_factor *= avg_scale + current_node = current_node.getParent() + else: + break + except: + break + if total_scale_factor > 0: movement_distance = movement_distance / total_scale_factor diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 550f0bb3..0c87a889 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -15,6 +15,14 @@ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit, QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout) from PyQt5.QtGui import QColor from PyQt5.QtCore import Qt +# 尝试导入 QtWebEngineWidgets,如果失败则设置为 None +try: + from PyQt5.QtWebEngineWidgets import QWebEngineView + WEB_ENGINE_AVAILABLE = True +except ImportError: + QWebEngineView = None + WEB_ENGINE_AVAILABLE = False + print("⚠️ QtWebEngineWidgets 不可用,Cesium 集成功能将被禁用") class GUIManager: @@ -139,37 +147,159 @@ class GUIManager: print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})") return entry - - def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5): + + def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1): """创建3D空间文本""" - from panda3d.core import TextNode - + 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) - textNodePath.setColor(1, 1, 0, 1) - textNodePath.setBillboardAxis() # 让文本总是面向相机 - + 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 + def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0): + from panda3d.core import CardMaker, Material, LColor,TransparencyAttrib + + # 参数类型检查和转换 + 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) + + # 设置面向摄像机 + #image_node.setBillboardAxis() # 让图像总是面向相机 + + # 创建支持贴图的材质 + # mat = Material() + # mat.setName("GUI3DImageMaterial") + # color = LColor(1, 1, 1, 1) + # mat.set_base_color(color) + # mat.set_roughness(0.5) + # mat.set_metallic(0.0) + # image_node.set_material(mat) + + # 为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}") + + # 为GUI元素添加标识(效仿3D文本方法) + 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") + + self.gui_elements.append(image_node) + + # 更新场景树 + if hasattr(self.world, 'updateSceneTree'): + self.world.updateSceneTree() + + print(f"✓ 3D图像创建完成: {image_path or '无纹理'} (世界位置: {pos})") + return image_node def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"): @@ -262,15 +392,16 @@ class GUIManager: except Exception as e: print(f"删除GUI元素失败: {str(e)}") return False - + + # 在 gui_manager.py 中确保 editGUIElement 方法正确处理文本颜色 def editGUIElement(self, gui_element, property_name, value): """编辑GUI元素属性""" try: from panda3d.core import TextNode - + gui_type = gui_element.getTag("gui_type") if hasattr(gui_element, 'getTag') else "unknown" print(f"开始编辑GUI元素: 类型={gui_type}, 属性={property_name}, 值={value}") - + if property_name == "text": if gui_type in ["button", "label"]: gui_element['text'] = value @@ -285,7 +416,7 @@ class GUIManager: print(f"成功更新3D文本: {value}") else: print(f"警告: {gui_type}节点类型为{type(gui_element.node())},不是TextNode类型") - + elif gui_type == "virtual_screen": # 对于虚拟屏幕,需要找到TextNode子节点 print(f"虚拟屏幕有 {gui_element.getNumChildren()} 个子节点") @@ -297,36 +428,53 @@ class GUIManager: text_found = True print(f"成功更新虚拟屏幕文本: {value}") break - + if not text_found: print(f"警告: 在{gui_type}中未找到TextNode子节点") - + gui_element.setTag("gui_text", value) - + + elif property_name == "color": # 添加颜色处理 + if isinstance(value, (list, tuple)) and len(value) >= 3: + # 更新材质颜色 + if not gui_element.hasMaterial(): + material = Material(f"text-material-{gui_element.getName()}") + material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)) + material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)) + gui_element.setMaterial(material, 1) + else: + material = gui_element.getMaterial() + material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)) + material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)) + gui_element.setMaterial(material, 1) + # 更新 TextNode 的文本颜色 + if isinstance(gui_element.node(), TextNode): + gui_element.node().setTextColor( + Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)) + # if gui_type in ["3d_text", "virtual_screen"]: + # gui_element.setColor(*value) + # elif gui_type in ["button", "label"]: + # gui_element['text_fg'] = value + elif property_name == "position": if isinstance(value, (list, tuple)) and len(value) >= 3: gui_element.setPos(*value[:3]) - + elif property_name == "scale": if isinstance(value, (int, float)): gui_element.setScale(value) elif isinstance(value, (list, tuple)) and len(value) >= 3: gui_element.setScale(*value[:3]) - - elif property_name == "color": - if isinstance(value, (list, tuple)) and len(value) >= 3: - if gui_type in ["button", "label"]: - gui_element['frameColor'] = value - else: - gui_element.setColor(*value) - + print(f"编辑GUI元素 {gui_type}: {property_name} = {value}") return True - + except Exception as e: print(f"编辑GUI元素失败: {str(e)}") + import traceback + traceback.print_exc() return False - + def duplicateGUIElement(self, gui_element): """复制GUI元素""" try: @@ -346,6 +494,9 @@ class GUIManager: self.createGUIEntry(new_pos, gui_text + "_副本") elif gui_type == "3d_text": self.createGUI3DText(new_pos, gui_text + "_副本") + elif gui_type == "3d_image": + image_path = gui_element.getTag("image_path") + self.createGUI3DImage(new_pos,image_path,size=(2,2)) elif gui_type == "virtual_screen": self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本") @@ -581,6 +732,19 @@ class GUIManager: text_font=self.world.getChineseFont() if self.world.getChineseFont() else None ) y_pos -= spacing + + #3D图片工具 + btn_image = DirectButton( + parent = self.guiEditPanel, + text="3D图片", + pos=(0,0,y_pos), + scale=0.04, + command=self.setGUICreateTool, + extraArgs=["3d_image"], + frameColor=(0.2,0.8,0.8,1), + text_font=self.world.getChineseFont() if self.world.getChineseFont() else None + ) + y_pos -= spacing # 虚拟屏幕工具 btn_screen = DirectButton( @@ -594,6 +758,43 @@ class GUIManager: text_font=self.world.getChineseFont() if self.world.getChineseFont() else None ) y_pos -= spacing + + #Cesium 集成工具 (仅在Webengine 可用时显示) + if WEB_ENGINE_AVAILABLE: + label_cesium = DirectLabel( + parent=self.guiEditPanel, + text="Cesium 集成", + pos=(0, 0, y_pos), + scale=0.04, + text_fg=(1, 1, 0, 1), + frameColor=(0, 0, 0, 0), + text_font=self.world.getChineseFont() if self.world.getChineseFont() else None + ) + y_pos -= 0.08 + + # 切换 Cesium 视图按钮 + btn_toggle_cesium = DirectButton( + parent=self.guiEditPanel, + text="切换地图视图", + pos=(0, 0, y_pos), + scale=0.04, + command=self.toggleCesiumView, + frameColor=(0.2, 0.8, 0.6, 1), + text_font=self.world.getChineseFont() if self.world.getChineseFont() else None + ) + y_pos -= spacing + + # 刷新 Cesium 视图按钮 + btn_refresh_cesium = DirectButton( + parent=self.guiEditPanel, + text="刷新地图", + pos=(0, 0, y_pos), + scale=0.04, + command=self.refreshCesiumView, + frameColor=(0.6, 0.8, 0.2, 1), + text_font=self.world.getChineseFont() if self.world.getChineseFont() else None + ) + y_pos -= spacing # 分隔线 y_pos -= 0.1 @@ -741,6 +942,8 @@ class GUIManager: element = self.createGUIEntry(pos, f"输入框_{len(self.gui_elements)}") elif gui_type == "3d_text": element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}") + elif gui_type == "3d_image": + element = self.createGUI3DImage(pos) elif gui_type == "virtual_screen": element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}") else: @@ -950,4 +1153,510 @@ class GUIManager: print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})") except Exception as e: - print(f"编辑2D GUI位置失败: {str(e)}") \ No newline at end of file + print(f"编辑2D GUI位置失败: {str(e)}") + + def update3DImageTexture(self, model_nodepath, image_path): + from panda3d.core import Texture + + try: + # 加载新纹理 + new_texture = self.world.loader.loadTexture(image_path) + if new_texture: + # 确保纹理过滤质量 + new_texture.setMagfilter(Texture.FT_linear) + new_texture.setMinfilter(Texture.FT_linear_mipmap_linear) + + # 应用纹理到模型 + model_nodepath.setTexture(new_texture, 1) + + # 更新标签 + model_nodepath.setTag("gui_image_path", image_path) + + # 确保材质设置正确 + if not model_nodepath.has_material(): + from panda3d.core import Material, LColor + mat = Material() + mat.setName(f"image-material-{id(model_nodepath)}") + mat.setBaseColor(LColor(1, 1, 1, 1)) + mat.setDiffuse(LColor(1, 1, 1, 1)) + mat.setAmbient(LColor(0.5, 0.5, 0.5, 1)) + mat.setSpecular(LColor(0.1, 0.1, 0.1, 1.0)) + mat.setShininess(10.0) + model_nodepath.setMaterial(mat, 1) + + print(f"✅ 3D图像纹理已更新为: {image_path}") + else: + print(f"❌ 无法加载纹理: {image_path}") + except Exception as e: + print(f"❌ 更新纹理时出错: {e}") + + # 替换现有的 createCesiumView 方法 + + def createCesiumView(self, main_window=None): + """创建 Cesium 视图窗口(离线版本)""" + if not WEB_ENGINE_AVAILABLE: + print("❌ 无法创建Cesium视图: Web引擎不可用") + return None + + try: + from PyQt5.QtWebEngineWidgets import QWebEngineView + from PyQt5.QtWidgets import QDockWidget + from PyQt5.QtCore import QUrl + import os + + # 尝试获取主窗口引用 + if main_window is None: + print("🔍 尝试获取主窗口引用...") + + # 检查各种可能的主窗口引用 + if hasattr(self.world, 'interface_manager'): + print(f" - interface_manager 存在: {self.world.interface_manager}") + if hasattr(self.world.interface_manager, 'main_window'): + main_window = self.world.interface_manager.main_window + print(f" - interface_manager.main_window: {main_window}") + + if main_window is None and hasattr(self.world, 'main_window'): + main_window = self.world.main_window + print(f" - world.main_window: {main_window}") + + # 如果仍然没有主窗口,尝试从树形控件获取 + if main_window is None and self.world.treeWidget: + try: + main_window = self.world.treeWidget.window() + print(f" - 从 treeWidget 获取窗口: {main_window}") + except: + pass + + if main_window is None: + print("✗ 无法获取主窗口引用") + return None + else: + print(f"✅ 使用传入的主窗口引用: {main_window}") + + # 检查主窗口是否有效 + if not hasattr(main_window, 'addDockWidget'): + print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法") + return None + + # 检查是否已经存在 Cesium 视图 + for element in self.gui_elements: + if hasattr(element, 'objectName') and element.objectName() == "CesiumView": + print("⚠ Cesium 视图已经存在") + # 将其前置显示 + element.show() + element.raise_() + return element + + # 创建停靠窗口 + print(f"🔧 创建 Cesium 停靠窗口,父窗口: {main_window}") + cesium_dock = QDockWidget("Cesium 地图视图(离线)", main_window) + cesium_dock.setObjectName("CesiumView") + + # 创建 Web 视图 + self.cesium_view = QWebEngineView() + + # 使用本地 HTML 文件(离线模式) + local_html_path = os.path.abspath("./cesium_offline.html") + if os.path.exists(local_html_path): + print(f"🌐 加载离线 Cesium: file://{local_html_path}") + self.cesium_view.load(QUrl(f"file://{local_html_path}")) + else: + print("⚠️ 离线文件不存在,使用在线版本") + self.cesium_view.load(QUrl("http://localhost:8080/Apps/HelloWorld.html")) + + # 设置内容 + cesium_dock.setWidget(self.cesium_view) + + # 添加到主窗口 + print("📍 将 Cesium 视图添加到主窗口") + main_window.addDockWidget(Qt.RightDockWidgetArea, cesium_dock) + + # 添加到GUI元素列表以便管理 + self.gui_elements.append(cesium_dock) + + print("✓ Cesium 离线视图已创建并集成到项目中") + return cesium_dock + + except Exception as e: + print(f"✗ 创建 Cesium 视图失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def toggleCesiumView(self): + """切换 Cesium 视图显示状态""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用,无法切换 Cesium 视图") + return None + + try: + # 查找现有的 Cesium 视图 + cesium_dock = None + cesium_index = -1 + for i, element in enumerate(self.gui_elements): + if hasattr(element, 'objectName') and element.objectName() == "CesiumView": + cesium_dock = element + cesium_index = i + break + + # 如果存在则移除,否则创建 + if cesium_dock: + # 获取主窗口引用以正确移除停靠窗口 + main_window = None + if (hasattr(self.world, 'interface_manager') and + hasattr(self.world.interface_manager, 'main_window') and + self.world.interface_manager.main_window): + main_window = self.world.interface_manager.main_window + elif hasattr(self.world, 'main_window') and self.world.main_window: + main_window = self.world.main_window + + if main_window and hasattr(main_window, 'removeDockWidget'): + main_window.removeDockWidget(cesium_dock) + + # 从列表中移除 + if cesium_index >= 0: + self.gui_elements.pop(cesium_index) + + print("✓ Cesium 视图已隐藏") + return None + else: + return self.createCesiumView() + + except Exception as e: + print(f"✗ 切换 Cesium 视图失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def refreshCesiumView(self): + """刷新 Cesium 视图""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用,无法刷新 Cesium 视图") + return False + + try: + for element in self.gui_elements: + if hasattr(element, 'objectName') and element.objectName() == "CesiumView": + web_view = element.widget() + if isinstance(web_view, QWebEngineView): + web_view.reload() + print("✓ Cesium 视图已刷新") + return True + print("⚠ 未找到 Cesium 视图") + return False + except Exception as e: + print(f"✗ 刷新 Cesium 视图失败: {str(e)}") + return False + + def updateCesiumURL(self, url): + """更新 Cesium 视图的 URL""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用,无法更新 Cesium URL") + return False + + try: + for element in self.gui_elements: + if hasattr(element, 'objectName') and element.objectName() == "CesiumView": + web_view = element.widget() + if isinstance(web_view, QWebEngineView): + from PyQt5.QtCore import QUrl + web_view.load(QUrl(url)) + print(f"✓ Cesium URL 已更新为: {url}") + return True + print("⚠ 未找到 Cesium 视图") + return False + except Exception as e: + print(f"✗ 更新 Cesium URL 失败: {str(e)}") + return False + + # 在 GUIManager 类中添加以下方法 + + def addModelToCesium(self, model_id, model_url, longitude, latitude, height=0, scale=1.0): + """向 Cesium 添加模型""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用,无法操作 Cesium") + return False + + try: + # 查找 Cesium 视图 + cesium_view = None + for element in self.gui_elements: + if (hasattr(element, 'objectName') and + element.objectName() == "CesiumView" and + hasattr(element, 'widget')): + cesium_view = element.widget() + break + + if not cesium_view: + print("✗ 未找到 Cesium 视图") + return False + + # 转义特殊字符以防止 JavaScript 语法错误 + escaped_model_id = str(model_id).replace("'", "\\'") + escaped_model_url = str(model_url).replace("'", "\\'").replace("\\", "/") + + # 构造 JavaScript 调用 + js_code = f""" + (function() {{ + if (window.CesiumAPI && typeof window.CesiumAPI.addModel === 'function') {{ + try {{ + var result = window.CesiumAPI.addModel( + '{escaped_model_id}', + '{escaped_model_url}', + {{ + longitude: {longitude}, + latitude: {latitude}, + height: {height} + }}, + {scale} + ); + console.log('Cesium 添加模型结果:', result); + return result || {{success: true, message: 'Model added'}}; + }} catch (error) {{ + console.error('JavaScript 错误:', error); + return {{success: false, message: 'JavaScript error: ' + error.message}}; + }} + }} else {{ + console.error('CesiumAPI.addModel 不可用'); + return {{success: false, message: 'CesiumAPI.addModel not available'}}; + }} + }})(); + """ + + # 定义回调函数处理结果 + def handle_result(result): + try: + if isinstance(result, dict): + if result.get('success', False): + print(f"✓ 成功在 Cesium 中添加模型: {model_id}") + else: + print(f"✗ 在 Cesium 中添加模型失败: {result.get('message', 'Unknown error')}") + else: + print(f"✓ 已发送添加模型请求: {model_id}") + except Exception as callback_error: + print(f"✗ 处理回调结果时出错: {callback_error}") + + # 执行 JavaScript 并获取结果 + cesium_view.page().runJavaScript(js_code, handle_result) + return True + + except Exception as e: + print(f"✗ 添加模型到 Cesium 失败: {e}") + import traceback + traceback.print_exc() + return False + + # 添加新的方法来集成 Panda3D 场景中的 Cesium Tiles + def addCesiumTilesetToScene(self, tileset_name, tileset_url, position=(0, 0, 0)): + """在 Panda3D 场景中添加 Cesium 3D Tiles""" + try: + # 使用场景管理器加载 tileset + tileset_node = self.world.scene_manager.load_cesium_tileset(tileset_url, position) + + if tileset_node: + # 添加到 GUI 元素列表以便管理 + self.gui_elements.append({ + 'type': 'cesium_tileset', + 'name': tileset_name, + 'node': tileset_node, + 'url': tileset_url + }) + + print(f"✓ 在场景中添加 Cesium tileset: {tileset_name}") + return tileset_node + else: + print(f"✗ 在场景中添加 Cesium tileset 失败: {tileset_name}") + return None + + except Exception as e: + print(f"✗ 在场景中添加 Cesium tileset 出错: {e}") + return None + + def removeModelFromCesium(self, model_id): + """从 Cesium 移除模型""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用") + return False + + try: + # 查找 Cesium 视图 + cesium_view = None + for element in self.gui_elements: + if (hasattr(element, 'objectName') and + element.objectName() == "CesiumView" and + hasattr(element, 'widget')): + cesium_view = element.widget() + break + + if not cesium_view: + print("✗ 未找到 Cesium 视图") + return False + + # 构造 JavaScript 调用 + js_code = f""" + if (window.CesiumAPI && typeof window.CesiumAPI.removeModel === 'function') {{ + var result = window.CesiumAPI.removeModel('{model_id}'); + result; + }} else {{ + {{success: false, message: 'CesiumAPI.removeModel not available'}}; + }} + """ + + # 定义回调函数处理结果 + def handle_result(result): + if result and isinstance(result, dict): + if result.get('success', False): + print(f"✓ 成功从 Cesium 中移除模型: {model_id}") + else: + print(f"✗ 从 Cesium 中移除模型失败: {result.get('message', 'Unknown error')}") + else: + print(f"✓ 已发送移除模型请求: {model_id} (无法获取详细结果)") + + # 执行 JavaScript 并获取结果 + cesium_view.page().runJavaScript(js_code, handle_result) + return True + + except Exception as e: + print(f"✗ 从 Cesium 移除模型失败: {e}") + return False + + def updateCesiumModelPosition(self, model_id, longitude, latitude, height=0): + """更新 Cesium 中模型的位置""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用") + return False + + try: + # 查找 Cesium 视图 + cesium_view = None + for element in self.gui_elements: + if (hasattr(element, 'objectName') and + element.objectName() == "CesiumView" and + hasattr(element, 'widget')): + cesium_view = element.widget() + break + + if not cesium_view: + print("✗ 未找到 Cesium 视图") + return False + + # 使用更安全的 JavaScript 字符串构造方式 + escaped_model_id = model_id.replace("'", "\\'") + + # 构造 JavaScript 调用 + js_code = f""" + (function() {{ + if (window.CesiumAPI && typeof window.CesiumAPI.updateModelPosition === 'function') {{ + try {{ + var result = window.CesiumAPI.updateModelPosition( + '{escaped_model_id}', + {{ + longitude: {longitude}, + latitude: {latitude}, + height: {height} + }} + ); + return result || {{success: true, message: 'Position updated'}}; + }} catch (error) {{ + return {{success: false, message: 'JavaScript error: ' + error.message}}; + }} + }} else {{ + return {{success: false, message: 'CesiumAPI.updateModelPosition not available'}}; + }} + }})(); + """ + + # 定义回调函数处理结果 + def handle_result(result): + try: + if isinstance(result, dict): + if result.get('success', False): + print(f"✓ 成功更新 Cesium 中模型位置: {model_id}") + else: + print(f"✗ 更新 Cesium 中模型位置失败: {result.get('message', 'Unknown error')}") + else: + print(f"✓ 已发送更新模型位置请求: {model_id}") + except Exception as callback_error: + print(f"✗ 处理回调结果时出错: {callback_error}") + + # 执行 JavaScript 并获取结果 + cesium_view.page().runJavaScript(js_code, handle_result) + return True + + except Exception as e: + print(f"✗ 更新 Cesium 中模型位置失败: {e}") + return False + + def getAllCesiumModels(self): + """获取 Cesium 中所有模型的列表""" + if not WEB_ENGINE_AVAILABLE: + print("✗ QtWebEngineWidgets 不可用") + return None + + try: + # 查找 Cesium 视图 + cesium_view = None + for element in self.gui_elements: + if (hasattr(element, 'objectName') and + element.objectName() == "CesiumView" and + hasattr(element, 'widget')): + cesium_view = element.widget() + break + + if not cesium_view: + print("✗ 未找到 Cesium 视图") + return None + + # 构造 JavaScript 调用 + js_code = """ + if (window.CesiumAPI && typeof window.CesiumAPI.getAllModels === 'function') { + var result = window.CesiumAPI.getAllModels(); + JSON.stringify(result); + } else { + JSON.stringify({success: false, message: 'CesiumAPI.getAllModels not available'}); + } + """ + + # 定义回调函数处理结果 + def handle_result(result): + try: + if isinstance(result, str): + import json + result = json.loads(result) + + if result and result.get('success', False): + models = result.get('models', []) + print(f"✓ Cesium 中的模型列表: {models}") + return models + else: + print(f"✗ 获取 Cesium 模型列表失败: {result.get('message', 'Unknown error')}") + return [] + except Exception as e: + print(f"✗ 解析 Cesium 模型列表结果失败: {e}") + return [] + + # 执行 JavaScript 并获取结果 + cesium_view.page().runJavaScript(js_code) + return None # 异步操作,实际结果通过回调处理 + + except Exception as e: + print(f"✗ 获取 Cesium 模型列表失败: {e}") + return None + + # 添加一个便捷方法来加载本地模型文件 + def addLocalModelToCesium(self, model_id, local_model_path, longitude, latitude, height=0, scale=1.0): + """向 Cesium 添加本地模型文件""" + try: + # 将本地路径转换为相对路径或 URL + import os + if os.path.exists(local_model_path): + # 如果 Cesium 服务器可以访问该路径,可以直接使用 + # 否则需要将模型文件放在 Cesium 的静态资源目录中 + model_url = local_model_path.replace('\\', '/') # 确保使用正斜杠 + return self.addModelToCesium(model_id, model_url, longitude, latitude, height, scale) + else: + print(f"✗ 模型文件不存在: {local_model_path}") + return False + except Exception as e: + print(f"✗ 添加本地模型失败: {e}") + return False diff --git a/main.py b/main.py index 065ef800..2ca74757 100644 --- a/main.py +++ b/main.py @@ -183,6 +183,10 @@ class MyWorld(CoreWorld): """创建3D空间文本""" return self.gui_manager.createGUI3DText(pos, text, size) + def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(2,2)): + """创建3D图片""" + return self.gui_manager.createGUI3DImage(pos,text,size) + def createSpotLight(self,pos=(-20,0,5)): """创建聚光灯""" return self.scene_manager.createSpotLight(pos) @@ -673,6 +677,15 @@ class MyWorld(CoreWorld): "streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None } + def loadCesiumTileset(self,tileset_url,position=(0,0,0)): + return self.scene_manager.load_cesium_tileset(tileset_url,position) + + def addCesiumTileset(self,name,url,position=(0,0,0)): + if hasattr(self,'gui_manager') and self.gui_manager: + return self.gui_manager.addCesiumTilesetToScene(name,url,position) + else: + return self.scene_manager.load_cesium_tileset(url,position) + # ==================== 项目管理功能代理 ==================== # 以下函数代理到project_manager模块的对应功能 diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 75671fe2..a3ae792a 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -12,11 +12,57 @@ from panda3d.core import ( MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere, BitMask32, TransparencyAttrib,LColor ) +import json +import aiohttp +import asyncio +from pathlib import Path from panda3d.egg import EggData, EggVertexPool from direct.actor.Actor import Actor from QPanda3D.Panda3DWorld import get_render_pipeline from scene import util +class CesiumIntegration: + def __init__(self, scene_manager): + self.scene_manager = scene_manager + self.world = scene_manager.world + self.tilesets = {} + + def add_tileset(self,name,url,position=(0,0,0)): + try: + tileset_node = self.scene_manager.load_cesium_tileset(url,position) + + if tileset_node: + self.tilesets[name] = { + 'node':tileset_node, + 'url':url, + 'position':position + } + print(f"✓ 添加 Cesium tileset: {name}") + return tileset_node + else: + print(f"✗ 添加 Cesium tileset 失败: {name}") + return None + except Exception as e: + print(f"✗ 添加 Cesium tileset 出错: {e}") + return None + + def remove_tileset(self, name): + """移除 tileset""" + if name in self.tilesets: + tileset_info = self.tilesets[name] + tileset_info['node'].removeNode() + del self.tilesets[name] + print(f"✓ 移除 Cesium tileset: {name}") + return True + return False + + def get_tileset(self, name): + """获取 tileset""" + return self.tilesets.get(name, None) + + def list_tilesets(self): + """列出所有 tilesets""" + return list(self.tilesets.keys()) class SceneManager: """场景管理器 - 统一管理场景中的所有元素""" @@ -33,6 +79,8 @@ class SceneManager: self.Spotlight = [] self.Pointlight = [] + self.tilesets = [] #来存储tilesets + self.cesium_integration = CesiumIntegration(self) print("✓ 场景管理系统初始化完成") @@ -1242,3 +1290,267 @@ except Exception as e: print(f"[PyAssimp转换] 转换过程出错: {e}") return False + def load_cesium_tileset(self, tileset_url, position=(0, 0, 0)): + try: + print(f"加载 Cesium 3D Tiles: {tileset_url}") + + # 创建一个容器节点来管理tileset + node_name = f"cesium_tileset_{len(self.tilesets)}" + tileset_node = self.world.render.attachNewNode(node_name) + tileset_node.setPos(*position) + + #添加标签以便场景树识别 + tileset_node.setTag("is_scene_element","1") + tileset_node.setTag("element_type","cesium_tileset") + tileset_node.setTag("tileset_url",tileset_url) + tileset_node.setTag("file",f"tileset_{len(self.tilesets)}") + + # 存储tileset信息 + tileset_info = { + 'url': tileset_url, + 'node': tileset_node, + 'position': position, + 'tiles': {} + } + + self.tilesets.append(tileset_info) + + # 创建一个临时的可视化占位符,让用户能看到节点已添加 + self._create_placeholder_geometry(tileset_node) + + # 异步加载tileset数据 + self._load_tileset_async(tileset_url, tileset_info) + + # 更新场景树 + self.updateSceneTree() + print(f"✓ Cesium 3D Tiles 加载请求已发送") + return tileset_node + + except Exception as e: + print(f"❌ 加载 Cesium 3D Tiles 失败: {e}") + import traceback + traceback.print_exc() + return None + + def _load_tileset_async(self, tileset_url, tileset_info): + """异步加载 tileset 数据""" + + async def load_tileset(): + try: + async with aiohttp.ClientSession() as session: + async with session.get(tileset_url) as response: + if response.status == 200: + tileset_data = await response.json() + self._parse_tileset(tileset_data, tileset_info) + print(f"✓ Tileset 数据加载完成") + else: + print(f"✗ Tileset 加载失败: {response.status}") + except Exception as e: + print(f"✗ Tileset 加载出错: {e}") + + # 在 Panda3D 的任务系统中运行异步任务 + task = asyncio.ensure_future(load_tileset()) + self._current_asyncio_task = task # 保存任务引用 + self.world.taskMgr.add(self._check_async_task, "check_tileset_load", appendTask=True) + + def _check_async_task(self, panda3d_task): + # 检查 asyncio 任务是否完成 + if hasattr(self, '_current_asyncio_task'): + if self._current_asyncio_task.done(): + try: + self._current_asyncio_task.result() + except Exception as e: + print(f"异步任务出错:{e}") + # 返回 Panda3D 任务管理器的完成状态 + return panda3d_task.done # 注意是 done 而不是 DONE + # 返回 Panda3D 任务管理器的继续状态 + return panda3d_task.cont # 注意是 cont 而不是 CONTINUE + + def _parse_tileset(self,tileset_data,tileset_info): + try: + root = tileset_data.get('root',{}) + self._parse_tile(root,tileset_info['node'],tileset_info) + print("✓ Tileset 解析完成") + except Exception as e: + print(f"✗ Tileset 解析出错: {e}") + + def _parse_tile(self, tile_data, parent_node, tileset_info): + try: + # 获取tileID + tile_id = f"tile_{len(tileset_info['tiles'])}" + print(f"创建tile节点: {tile_id}") + # 创建tile节点 + tile_node = parent_node.attachNewNode(tile_id) + + tileset_info['tiles'][tile_id] = { + 'node': tile_node, + 'data': tile_data, + 'loaded': False + } + + # 如果有内容,创建占位几何体 + if 'content' in tile_data: + print(f"为tile {tile_id} 创建几何体") + self._create_tile_geometry(tile_node) + # 递归解析子tiles + children = tile_data.get('children', []) + print(f"Tile {tile_id} 有 {len(children)} 个子节点") + for child_data in children: + self._parse_tile(child_data, tile_node, tileset_info) + except Exception as e: + print(f"✗ Tile 解析出错: {e}") + import traceback + traceback.print_exc() + + def _create_tile_geometry(self,parent_node): + """为 tile 创建占位几何体""" + try: + # 创建一个简单的立方体作为占位符 + from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter + from panda3d.core import Geom, GeomTriangles, GeomNode + + format = GeomVertexFormat.getV3n3c4() + vdata = GeomVertexData('tile_cube', format, Geom.UHStatic) + + vertex = GeomVertexWriter(vdata, 'vertex') + normal = GeomVertexWriter(vdata, 'normal') + color = GeomVertexWriter(vdata, 'color') + + # 定义立方体顶点 + vertices = [ + (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, 0.5, -0.5), + (-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, 0.5) + ] + + for vert in vertices: + vertex.addData3f(*vert) + normal.addData3f(0, 0, 1) + color.addData4f(0.2, 0.6, 0.8, 1.0) + + # 创建几何体 + geom = Geom(vdata) + + # 创建面 + prim = GeomTriangles(Geom.UHStatic) + # 底面 + prim.addVertices(0, 1, 2) + prim.addVertices(0, 2, 3) + # 顶面 + prim.addVertices(4, 7, 6) + prim.addVertices(4, 6, 5) + # 前面 + prim.addVertices(0, 4, 5) + prim.addVertices(0, 5, 1) + # 后面 + prim.addVertices(2, 6, 7) + prim.addVertices(2, 7, 3) + # 左面 + prim.addVertices(0, 3, 7) + prim.addVertices(0, 7, 4) + # 右面 + prim.addVertices(1, 5, 6) + prim.addVertices(1, 6, 2) + + prim.closePrimitive() + geom.addPrimitive(prim) + + # 创建几何节点 + geom_node = GeomNode('tile_geometry') + geom_node.addGeom(geom) + + # 添加到场景 + cube_node = parent_node.attachNewNode(geom_node) + cube_node.setScale(1000) # 放大以便观察 + + # 添加材质 + material = Material() + material.setBaseColor((0.2, 0.6, 0.8, 1.0)) + material.setSpecular((0.1, 0.1, 0.1, 1.0)) + material.setShininess(10.0) + cube_node.setMaterial(material) + + except Exception as e: + print(f"✗ 创建 tile 几何体出错: {e}") + + def _create_placeholder_geometry(self, parent_node): + """创建一个简单的占位符几何体,让用户能看到节点""" + try: + from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter + from panda3d.core import Geom, GeomTriangles, GeomNode + + # 创建简单的立方体作为占位符 + format = GeomVertexFormat.getV3n3c4() + vdata = GeomVertexData('placeholder_cube', format, Geom.UHStatic) + + vertex = GeomVertexWriter(vdata, 'vertex') + normal = GeomVertexWriter(vdata, 'normal') + color = GeomVertexWriter(vdata, 'color') + + # 定义立方体顶点(稍微大一些,便于识别) + size = 1.0 + vertices = [ + (-size, -size, -size), (size, -size, -size), (size, size, -size), (-size, size, -size), + (-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size) + ] + + # 使用更鲜明的颜色 + for vert in vertices: + vertex.addData3f(*vert) + normal.addData3f(0, 0, 1) + color.addData4f(0.0, 1.0, 1.0, 1.0) # 青色 + + # 创建几何体 + geom = Geom(vdata) + + # 创建面 + prim = GeomTriangles(Geom.UHStatic) + # 底面 + prim.addVertices(0, 1, 2) + prim.addVertices(0, 2, 3) + # 顶面 + prim.addVertices(4, 7, 6) + prim.addVertices(4, 6, 5) + # 前面 + prim.addVertices(0, 4, 5) + prim.addVertices(0, 5, 1) + # 后面 + prim.addVertices(2, 6, 7) + prim.addVertices(2, 7, 3) + # 左面 + prim.addVertices(0, 3, 7) + prim.addVertices(0, 7, 4) + # 右面 + prim.addVertices(1, 5, 6) + prim.addVertices(1, 6, 2) + + prim.closePrimitive() + geom.addPrimitive(prim) + + # 创建几何节点 + geom_node = GeomNode('tileset_placeholder') + geom_node.addGeom(geom) + + # 添加到场景 + cube_node = parent_node.attachNewNode(geom_node) + cube_node.setScale(5) # 适当大小 + + # 添加材质 + material = Material() + material.setBaseColor((0.0, 1.0, 1.0, 1.0)) # 青色 + material.setSpecular((0.5, 0.5, 0.5, 1.0)) + material.setShininess(32.0) + cube_node.setMaterial(material) + + # 添加标识标签 + cube_node.setTag("element_type", "cesium_placeholder") + + print("✓ 占位符几何体创建完成") + return cube_node + except Exception as e: + print(f"✗ 创建占位符几何体出错: {e}") + import traceback + traceback.print_exc() + return None + + + diff --git a/ui/interface_manager.py b/ui/interface_manager.py index b3fbd8f3..9797f3c2 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -74,6 +74,10 @@ class InterfaceManager: duplicateAction = menu.addAction("复制") duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath)) + elif hasattr(nodePath,'getTag') and nodePath.getTag("element_type") == "cesium_tileset": + deleteAction = menu.addAction("删除 Cesium Tileset") + deleteAction.triggered.connect(lambda:self.deleteCesiumTileset(nodePath,item)) + else: # 为模型节点或其子节点添加删除选项 parentItem = item.parent() @@ -88,6 +92,40 @@ class InterfaceManager: # 显示菜单 menu.exec_(self.treeWidget.viewport().mapToGlobal(position)) + def deleteCesiumTileset(self, nodePath, item): + """删除 Cesium tileset""" + try: + # 从场景中移除 + nodePath.removeNode() + + # 从 tilesets 列表中移除 + if hasattr(self.world, 'scene_manager'): + tilesets_to_remove = [] + for i, tileset_info in enumerate(self.world.scene_manager.tilesets): + if tileset_info['node'] == nodePath: + tilesets_to_remove.append(i) + + # 从后往前删除,避免索引问题 + for i in reversed(tilesets_to_remove): + del self.world.scene_manager.tilesets[i] + + # 从树形控件中移除 + parentItem = item.parent() + if parentItem: + parentItem.removeChild(item) + + print(f"成功删除 Cesium tileset: {nodePath.getName()}") + + # 清空属性面板和选择框 + self.world.property_panel.clearPropertyPanel() + self.world.selection.updateSelection(None) + + # 更新场景树 + self.updateSceneTree() + + except Exception as e: + print(f"删除 Cesium tileset 失败: {str(e)}") + def isModelOrChild(self, item): """检查是否是模型节点或其子节点""" while item and item.parent(): @@ -215,10 +253,19 @@ class InterfaceManager: gui_text = gui.getTag("gui_text") or "GUI元素" item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"]) item.setData(0, Qt.UserRole, gui) - + #添加灯光节点 for light in self.world.Spotlight + self.world.Pointlight: addNodeToTree(light, sceneRoot, force=True) + #添加 Cesium tilesets + if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'tilesets'): + for i , tileset_info in enumerate(self.world.scene_manager.tilesets): + tileset_node = tileset_info['node'] + tileset_url = tileset_info['url'] + tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"]) + tileset_item.setData(0,Qt.UserRole,tileset_node) + addNodeToTree(tileset_node,tileset_item,force=True) + # 添加地板节点 if hasattr(self.world, 'ground') and self.world.ground: groundItem = QTreeWidgetItem(sceneRoot, ['地板']) diff --git a/ui/main_window.py b/ui/main_window.py index bd90fd3b..cf4e5f4d 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -12,7 +12,7 @@ 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) + QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget,QDialog) from PyQt5.QtCore import Qt, QDir, QTimer from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget @@ -107,6 +107,9 @@ class MainWindow(QMainWindow): 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('聚光灯') @@ -139,6 +142,10 @@ class MainWindow(QMainWindow): self.toggleHotReloadAction.setChecked(True) # 默认启用 self.scriptMenu.addSeparator() self.openScriptsManagerAction = self.scriptMenu.addAction('脚本管理器') + + self.cesiumMenu = menubar.addMenu('Cesium') + self.loadCesiumTilesetAction = self.cesiumMenu.addAction('加载3Dtiles') + self.loadCesiumTilesetAction.triggered.connect(self.onLoadCesiumTileset) # 帮助菜单 self.helpMenu = menubar.addMenu('帮助') @@ -264,6 +271,10 @@ class MainWindow(QMainWindow): self.create3DTextTool.setText("3D文本") self.toolbar.addWidget(self.create3DTextTool) + self.create3DImageTool = QToolButton() + self.create3DImageTool.setText("3D图片") + self.toolbar.addWidget(self.create3DImageTool) + self.createSpotLight = QToolButton() self.createSpotLight.setText("聚光灯") self.toolbar.addWidget(self.createSpotLight) @@ -272,6 +283,22 @@ class MainWindow(QMainWindow): self.createPointLight.setText("点光灯") self.toolbar.addWidget(self.createPointLight) + # Cesium 工具按钮 + self.cesiumViewTool = QToolButton() + self.cesiumViewTool.setText("地图视图") + self.cesiumViewTool.clicked.connect(self.onCreateCesiumView) + self.toolbar.addWidget(self.cesiumViewTool) + + self.refreshCesiumTool = QToolButton() + self.refreshCesiumTool.setText("刷新地图") + self.refreshCesiumTool.clicked.connect(self.onRefreshCesiumView) + self.toolbar.addWidget(self.refreshCesiumTool) + + self.addModelTool = QToolButton() + self.addModelTool.setText("添加模型") + self.addModelTool.clicked.connect(self.onAddModelClicked) + self.toolbar.addWidget(self.addModelTool) + # 默认选择"选择"工具 self.selectTool.setChecked(True) self.world.setCurrentTool("选择") @@ -425,11 +452,18 @@ class MainWindow(QMainWindow): self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText()) #self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight()) 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) + self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView) + self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView) + self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView) # 连接工具栏GUI创建按钮事件 self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton()) self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel()) self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText()) + self.create3DImageTool.clicked.connect(lambda: self.world.createGUI3DImage()) self.createSpotLight.clicked.connect(lambda :self.world.createSpotLight()) self.createPointLight.clicked.connect(lambda :self.world.createPointLight()) @@ -447,7 +481,225 @@ class MainWindow(QMainWindow): self.loadAllScriptsAction.triggered.connect(self.onReloadAllScripts) self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload) self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager) - + + def onCreateCesiumView(self): + if hasattr(self.world,'gui_manager') and self.world.gui_manager: + self.world.gui_manager.createCesiumView() + else: + QMessageBox.warning(self,"错误","GUI管理其不可用") + + def onToggleCesiumView(self): + """切换 Cesium 视图显示状态""" + if hasattr(self.world, 'gui_manager') and self.world.gui_manager: + self.world.gui_manager.toggleCesiumView() + else: + QMessageBox.warning(self, "错误", "GUI 管理器不可用") + + def onRefreshCesiumView(self): + """刷新 Cesium 视图""" + if hasattr(self.world, 'gui_manager') and self.world.gui_manager: + self.world.gui_manager.refreshCesiumView() + else: + QMessageBox.warning(self, "错误", "GUI 管理器不可用") + + 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 管理器不可用") + + def onAddModelClicked(self): + """处理加入模型按钮点击事件""" + # 检查 Cesium 视图是否存在 + cesium_view_exists = False + if hasattr(self.world, 'gui_manager') and self.world.gui_manager: + for element in self.world.gui_manager.gui_elements: + if hasattr(element, 'objectName') and element.objectName() == "CesiumView": + cesium_view_exists = True + break + + if not cesium_view_exists: + reply = QMessageBox.question( + self, + '提示', + 'Cesium 地图视图尚未打开,是否先打开地图视图?', + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes + ) + + if reply == QMessageBox.Yes: + self.onCreateCesiumView() + # 给一点时间让 Cesium 视图加载 + QTimer.singleShot(1000, self.showAddModelDialog) + return + else: + return + + self.showAddModelDialog() + + def showAddModelDialog(self): + """显示添加模型对话框""" + # 打开文件选择对话框 + file_path, _ = QFileDialog.getOpenFileName( + self, + "选择 3D 模型文件", + "", + "3D 模型文件 (*.glb *.gltf *.obj);;所有文件 (*)" + ) + + 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]}" + + 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)}" + ) + + def getModelCoordinates(self): + """获取模型坐标信息的对话框""" + # 创建对话框 + dialog = QDialog(self) + dialog.setWindowTitle("设置模型位置") + dialog.setModal(True) + dialog.resize(300, 200) + + layout = QVBoxLayout(dialog) + + # 经度 + lon_layout = QHBoxLayout() + lon_layout.addWidget(QLabel("经度:")) + lon_spin = QDoubleSpinBox() + lon_spin.setRange(-180, 180) + lon_spin.setValue(116.3975) # 默认北京位置 + lon_layout.addWidget(lon_spin) + layout.addLayout(lon_layout) + + # 纬度 + lat_layout = QHBoxLayout() + lat_layout.addWidget(QLabel("纬度:")) + lat_spin = QDoubleSpinBox() + lat_spin.setRange(-90, 90) + lat_spin.setValue(39.9085) # 默认北京位置 + lat_layout.addWidget(lat_spin) + layout.addLayout(lat_layout) + + # 高度 + height_layout = QHBoxLayout() + height_layout.addWidget(QLabel("高度(米):")) + height_spin = QDoubleSpinBox() + height_spin.setRange(-10000, 100000) + height_spin.setValue(0) + height_layout.addWidget(height_spin) + layout.addLayout(height_layout) + + # 缩放 + scale_layout = QHBoxLayout() + scale_layout.addWidget(QLabel("缩放:")) + scale_spin = QDoubleSpinBox() + scale_spin.setRange(0.001, 100000) + scale_spin.setValue(1.0) + scale_spin.setSingleStep(0.1) + scale_layout.addWidget(scale_spin) + layout.addLayout(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) + + # 连接信号 + ok_button.clicked.connect(dialog.accept) + cancel_button.clicked.connect(dialog.reject) + + # 显示对话框 + if dialog.exec_() == QDialog.Accepted: + return ( + lon_spin.value(), + lat_spin.value(), + height_spin.value(), + scale_spin.value() + ), True + else: + return None, False + + def onLoadCesiumTileset(self): + url,ok = QInputDialog.getText( + self, + "加载 Cesium 3D Tiles", + "输入 tileset.json URL:", + QLineEdit.Normal, + "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)}" + ) + + def onToolChanged(self, button): """工具切换事件处理""" if button.isChecked(): @@ -457,7 +709,7 @@ class MainWindow(QMainWindow): else: self.world.setCurrentTool(None) print("工具栏: 取消选择工具") - + # ==================== 脚本管理事件处理 ==================== def refreshScriptsList(self): diff --git a/ui/property_panel.py b/ui/property_panel.py index 0421e5b3..f195c5ae 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -141,10 +141,18 @@ class PropertyPanelManager: # 获取节点对象 model = item.data(0, Qt.UserRole) + if model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset": + self._showCesiumTilesetProperties(model,item) # 检查是否是GUI元素 - if model and hasattr(model, 'getTag') and model.getTag("gui_type"): - self.updateGUIPropertyPanel(model) - pass + elif model and hasattr(model, 'getTag') and model.getTag("gui_type"): + # gui_type = model.getTag("gui_type") + # if gui_type == "3d_image": + # self._updateGUIImagePropertyPanel(model) + # else: + # self.updateGUIPropertyPanel(model) + # pass + self.updateGUIPropertyPanel(model) + pass elif model and hasattr(model, 'getTag') and model.getTag("light_type"): self.updateLightPropertyPanel(model) pass @@ -552,7 +560,6 @@ class PropertyPanelManager: spin.blockSignals(False) - def updateGUIPropertyPanel(self, gui_element): """更新GUI元素属性面板""" gui_type = gui_element.getTag("gui_type") @@ -568,6 +575,7 @@ class PropertyPanelManager: # typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;") gui_info_layout.addWidget(typeValue, 0, 1) + # 修改 updateGUIPropertyPanel 中的文本属性部分 # 文本属性(如果适用) if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]: gui_info_layout.addWidget(QLabel("文本:"), 1, 0) @@ -578,7 +586,8 @@ class PropertyPanelManager: success = self.world.gui_manager.editGUIElement(gui_element, "text", text) if success: # 更新场景树显示的名称 - self.world.scene_manager.updateSceneTree() + if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'): + self.world.scene_manager.updateSceneTree() textEdit.textChanged.connect(updateText) gui_info_layout.addWidget(textEdit, 1, 1) @@ -676,7 +685,6 @@ class PropertyPanelManager: [pos.getX(), pos.getY(), v])) transform_layout.addWidget(zPos, 1, 3) - # 缩放属性 if hasattr(gui_element, 'getScale'): scale = gui_element.getScale() @@ -684,17 +692,51 @@ class PropertyPanelManager: transform_layout.addWidget(QLabel("缩放"), row_offset, 0) - scaleSpinBox = QDoubleSpinBox() - scaleSpinBox.setRange(0.01, 10) - scaleSpinBox.setSingleStep(0.1) - scaleSpinBox.setValue(scale.getX()) - scaleSpinBox.valueChanged.connect( - lambda v: self.world.gui_manager.editGUIElement(gui_element, "scale", v)) - transform_layout.addWidget(scaleSpinBox, row_offset, 1) + # X缩放 + transform_layout.addWidget(QLabel("长:"), row_offset, 1) + scaleXSpinBox = QDoubleSpinBox() + scaleXSpinBox.setRange(0.01, 1000) + scaleXSpinBox.setSingleStep(0.1) + scaleXSpinBox.setValue(scale.getX()) + scaleXSpinBox.valueChanged.connect(lambda v: self._onScaleValueChanged(scaleXSpinBox, v)) + scaleXSpinBox.valueChanged.connect(lambda v: self._updateGUIScaleX(gui_element, v)) + transform_layout.addWidget(scaleXSpinBox, row_offset, 2) + + row_offset += 1 + transform_layout.addWidget(QLabel("宽:"), row_offset, 1) + scaleYSpinBox = QDoubleSpinBox() + scaleYSpinBox.setRange(0.01, 1000) + scaleYSpinBox.setSingleStep(0.1) + scaleYSpinBox.setValue(scale.getY()) + scaleYSpinBox.valueChanged.connect(lambda v: self._onScaleValueChanged(scaleYSpinBox, v)) + scaleYSpinBox.valueChanged.connect(lambda v: self._updateGUIScaleZ(gui_element, v)) + transform_layout.addWidget(scaleYSpinBox, row_offset, 2) + + # scaleSpinBox = QDoubleSpinBox() + # scaleSpinBox.setRange(0.01, 10) + # scaleSpinBox.setSingleStep(0.1) + # scaleSpinBox.setValue(scale.getX()) + # scaleSpinBox.valueChanged.connect( + # lambda v: self.world.gui_manager.editGUIElement(gui_element, "scale", v)) + # transform_layout.addWidget(scaleSpinBox, row_offset, 1) transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) + # 外观属性组 - 添加字体颜色选择 + if gui_type in ["button", "label", "3d_text"]: + appearance_group = QGroupBox("外观属性") + appearance_layout = QGridLayout() + + # 字体颜色选择 + appearance_layout.addWidget(QLabel("字体颜色:"), 0, 0) + colorButton = QPushButton("选择颜色") + colorButton.clicked.connect(lambda checked, elem=gui_element: self._selectGUIColor(elem)) + appearance_layout.addWidget(colorButton, 0, 1) + + appearance_group.setLayout(appearance_layout) + self._propertyLayout.addWidget(appearance_group) + # 外观属性组 if gui_type in ["button", "label"]: appearance_group = QGroupBox("外观属性") @@ -709,6 +751,200 @@ class PropertyPanelManager: appearance_group.setLayout(appearance_layout) self._propertyLayout.addWidget(appearance_group) + if gui_type == "3d_image": + image_group = QGroupBox("图片设置") + image_layout = QGridLayout() + + # 当前图片路径标签 + current_image_label = QLabel("当前图片:") + image_layout.addWidget(current_image_label, 0, 0) + + # 显示当前贴图路径(简化显示) + current_texture_path = gui_element.getTag("texture_path") or "未设置" + texture_label = QLabel(current_texture_path) + texture_label.setWordWrap(True) + image_layout.addWidget(texture_label, 0, 1) + + # 选择图片按钮 + select_texture_button = QPushButton("选择图片...") + image_layout.addWidget(select_texture_button, 1, 0, 1, 2) + + def onSelectTexture(): + from PyQt5.QtWidgets import QFileDialog + file_path, _ = QFileDialog.getOpenFileName( + None, + "选择图片", + "", + "图像文件 (*.png *.jpg *.jpeg *.bmp *.tga *.dds)" + ) + if file_path: + # 应用新纹理到 3D Image + success = self.world.gui_manager.update3DImageTexture(gui_element, file_path) + if success: + # 保存路径到 Tag + gui_element.setTag("texture_path", file_path) + # 更新显示 + texture_label.setText(file_path) + # 可选:刷新场景树或其他 UI + self.world.scene_manager.updateSceneTree() + + select_texture_button.clicked.connect(onSelectTexture) + + image_group.setLayout(image_layout) + self._propertyLayout.addWidget(image_group) + + # 添加弹性空间 + self._propertyLayout.addStretch() + + # 强制更新布局 + if self._propertyLayout: + self._propertyLayout.update() + propertyWidget = self._propertyLayout.parentWidget() + if propertyWidget: + propertyWidget.update() + + def _selectGUIColor(self, gui_element): + """选择GUI元素的字体颜色""" + from PyQt5.QtWidgets import QColorDialog + from PyQt5.QtGui import QColor + from panda3d.core import Vec4 + + # 获取当前颜色(如果已设置) + current_color = QColor(255, 255, 255) # 默认白色 + + # 尝试获取当前设置的颜色 + gui_type = gui_element.getTag("gui_type") + try: + if gui_type == "3d_text": + if gui_element.hasMaterial(): + material = gui_element.getMaterial() + base_color = material.getBaseColor() + current_color = QColor( + int(base_color.getX() * 255), + int(base_color.getY() * 255), + int(base_color.getZ() * 255), + int(base_color.getW() * 255) + ) + else: + # 从节点颜色获取 + node_color = gui_element.getColor() + current_color = QColor( + int(node_color.getX() * 255), + int(node_color.getY() * 255), + int(node_color.getZ() * 255), + int(node_color.getW() * 255) + ) + # current_color_obj = gui_element.getColor() + # current_color = QColor( + # int(current_color_obj[0] * 255), + # int(current_color_obj[1] * 255), + # int(current_color_obj[2] * 255) + # ) + # 对于其他类型的元素,可以添加类似的获取当前颜色的逻辑 + except: + pass # 使用默认颜色 + + color = QColorDialog.getColor(current_color, None, "选择字体颜色") + if color.isValid(): + r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0 + self._updateGUITextColor(gui_element, (r, g, b, 1.0)) + + def _updateGUITextColor(self, gui_element, color): + """更新GUI元素的字体颜色""" + try: + gui_type = gui_element.getTag("gui_type") + + if gui_type in ["button", "label", "entry"]: + # 对于DirectGUI元素,使用text_fg属性 + gui_element['text_fg'] = color + print(f"✓ 更新DirectGUI元素字体颜色: {gui_type}") + + elif gui_type == "3d_text": + # # 对于3D文本元素,直接设置颜色 + # gui_element.setColor(*color) + # print(f"✓ 更新3D文本字体颜色: {gui_type}") + + from panda3d.core import Material,Vec4 + if not gui_element.hasMaterial(): + material = Material(f"text-material-{gui_element.getName()}") + material.setBaseColor(Vec4(color[0], color[1], color[2], color[3])) + material.setDiffuse(Vec4(color[0], color[1], color[2], color[3])) + material.setAmbient(Vec4(color[0] * 0.5, color[1] * 0.5, color[2] * 0.5, color[3])) + material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0)) + material.setShininess(10.0) + gui_element.setMaterial(material, 1) + else: + # 更新现有材质 + material = gui_element.getMaterial() + material.setBaseColor(Vec4(color[0], color[1], color[2], color[3])) + material.setDiffuse(Vec4(color[0], color[1], color[2], color[3])) + gui_element.setMaterial(material, 1) + print(f"✓ 更新3D文本材质颜色: {color}") + + gui_element.setColor(*color) + + elif gui_type == "3d_image": + # 对于3D图片,如果有文本标签的话 + # 这里可以根据需要添加特定处理 + pass + + print(f"✓ 更新GUI元素字体颜色: {gui_type}, 颜色: {color}") + except Exception as e: + print(f"✗ 更新GUI元素字体颜色失败: {e}") + import traceback + traceback.print_exc() + + def _updateGUIScaleX(self, gui_element, scale_x): + """更新GUI元素X轴缩放""" + try: + gui_type = gui_element.getTag("gui_type") + current_scale = gui_element.getScale() + + # 对于不同的GUI类型使用不同的缩放方法 + if gui_type in ["3d_text", "3d_image"]: + # 对于3D元素,直接设置缩放 + new_scale = (scale_x, current_scale.getY(), current_scale.getZ()) + gui_element.setScale(*new_scale) + else: + # 对于2D元素,保持原有的缩放方法 + gui_element.setScale(scale_x) + + print(f"✓ 更新GUI元素X轴缩放: {scale_x}") + except Exception as e: + print(f"✗ 更新GUI元素X轴缩放失败: {e}") + + def _updateGUIScaleZ(self, gui_element, scale_z): + """更新GUI元素Y轴缩放""" + try: + gui_type = gui_element.getTag("gui_type") + current_scale = gui_element.getScale() + + # 对于不同的GUI类型使用不同的缩放方法 + if gui_type in ["3d_text", "3d_image"]: + # 对于3D元素,直接设置缩放 + new_scale = (current_scale.getX(), current_scale.getZ(), scale_z) + gui_element.setScale(*new_scale) + else: + # 对于2D元素,保持原有的缩放方法 + gui_element.setScale(scale_z) + + print(f"✓ 更新GUI元素Y轴缩放: {scale_z}") + except Exception as e: + print(f"✗ 更新GUI元素Y轴缩放失败: {e}") + + def update3DImageTexture(self,nodepath,texture_path): + try: + tex = self.world.loader.loadTexture(texture_path) + if tex: + nodepath.setTexture(tex,1) + return True + else: + print(f"[警告] 无法加载贴图: {texture_path}") + return False + except Exception as e: + print(f"[错误] 更新 3D 图片纹理失败: {e}") + return False + def _updateScriptPropertyPanel(self, game_object): """更新脚本属性面板""" # 获取对象上的脚本