1
0
forked from Rowland/EG

1.Cesium tilesets

2.3D面板
This commit is contained in:
Hector 2025-08-25 17:41:48 +08:00
parent 8e8564048e
commit cdf2cd550e
8 changed files with 1693 additions and 76 deletions

View File

@ -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的父类方法处理基础的相机旋转

View File

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

View File

@ -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)}")
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

13
main.py
View File

@ -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模块的对应功能

View File

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

View File

@ -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, ['地板'])

View File

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

View File

@ -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):
"""更新脚本属性面板"""
# 获取对象上的脚本