forked from Rowland/EG
1.Cesium tilesets
2.3D面板
This commit is contained in:
parent
8e8564048e
commit
cdf2cd550e
@ -398,12 +398,8 @@ class EventHandler:
|
|||||||
if self.world.selection.gizmo and not self.world.selection.isDraggingGizmo:
|
if self.world.selection.gizmo and not self.world.selection.isDraggingGizmo:
|
||||||
x = evt.get('x', 0)
|
x = evt.get('x', 0)
|
||||||
y = evt.get('y', 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)
|
self.world.selection.updateGizmoHighlight(x, y)
|
||||||
|
|
||||||
# 调用CoreWorld的父类方法处理基础的相机旋转
|
# 调用CoreWorld的父类方法处理基础的相机旋转
|
||||||
|
|||||||
@ -647,8 +647,11 @@ class SelectionSystem:
|
|||||||
|
|
||||||
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
||||||
|
|
||||||
|
#安区地更新朝向
|
||||||
|
|
||||||
if is_scale_tool:
|
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:
|
else:
|
||||||
parent_node = self.gizmoTarget.getParent()
|
parent_node = self.gizmoTarget.getParent()
|
||||||
if parent_node and parent_node != self.world.render:
|
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_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)
|
# x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0)
|
||||||
y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0)
|
# y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0)
|
||||||
z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length)
|
# z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length)
|
||||||
|
|
||||||
# 将3D坐标投影到屏幕坐标
|
# 将3D坐标投影到屏幕坐标
|
||||||
def worldToScreen(worldPos):
|
def worldToScreen(worldPos):
|
||||||
@ -1284,8 +1300,8 @@ class SelectionSystem:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 使用碰撞检测方法
|
# 使用碰撞检测方法
|
||||||
hoveredAxis = self.detectGizmoAxisWithCollision(mouseX, mouseY)
|
#hoveredAxis = self.detectGizmoAxisWithCollision(mouseX, mouseY)
|
||||||
#hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
|
hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
|
||||||
|
|
||||||
# 简化稳定性检测逻辑
|
# 简化稳定性检测逻辑
|
||||||
if not hasattr(self, '_last_detected_axis'):
|
if not hasattr(self, '_last_detected_axis'):
|
||||||
@ -1348,8 +1364,17 @@ class SelectionSystem:
|
|||||||
# 使用当前高亮的轴,如果有的话;否则使用传入的轴
|
# 使用当前高亮的轴,如果有的话;否则使用传入的轴
|
||||||
if self.gizmoHighlightAxis:
|
if self.gizmoHighlightAxis:
|
||||||
self.dragGizmoAxis = self.gizmoHighlightAxis
|
self.dragGizmoAxis = self.gizmoHighlightAxis
|
||||||
else:
|
elif axis and axis in self.gizmo_colors:
|
||||||
self.dragGizmoAxis = axis
|
self.dragGizmoAxis = axis
|
||||||
|
else:
|
||||||
|
# 如果没有明确指定轴,尝试通过鼠标位置检测
|
||||||
|
self.dragGizmoAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
|
||||||
|
|
||||||
|
# 如果仍然无法确定拖拽轴,则取消拖拽
|
||||||
|
if not self.dragGizmoAxis:
|
||||||
|
print("开始拖拽失败: 无法确定拖拽轴")
|
||||||
|
self.isDraggingGizmo = False
|
||||||
|
return
|
||||||
|
|
||||||
self.dragStartMousePos = (mouseX, mouseY)
|
self.dragStartMousePos = (mouseX, mouseY)
|
||||||
|
|
||||||
@ -1372,15 +1397,15 @@ class SelectionSystem:
|
|||||||
|
|
||||||
# 然后将当前拖动的轴设置为高亮颜色
|
# 然后将当前拖动的轴设置为高亮颜色
|
||||||
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis])
|
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis])
|
||||||
elif axis and axis in self.gizmo_colors:
|
# elif axis and axis in self.gizmo_colors:
|
||||||
for axis_name in self.gizmo_colors.keys():
|
# for axis_name in self.gizmo_colors.keys():
|
||||||
if axis_name != axis:
|
# if axis_name != axis:
|
||||||
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
# self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
||||||
|
#
|
||||||
self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis])
|
# self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis])
|
||||||
self.dragGizmoAxis = axis
|
# self.dragGizmoAxis = axis
|
||||||
|
#
|
||||||
self.gizmoHighlightAxis = self.dragGizmoAxis
|
# self.gizmoHighlightAxis = self.dragGizmoAxis
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
|
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
|
||||||
@ -1480,13 +1505,30 @@ class SelectionSystem:
|
|||||||
print(f"拖拽更新失败: 未知轴类型 {self.dragGizmoAxis}")
|
print(f"拖拽更新失败: 未知轴类型 {self.dragGizmoAxis}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 确定轴向量的变换上下文
|
world_axis_vector = local_axis_vector
|
||||||
|
|
||||||
if parent_node and parent_node != self.world.render:
|
if parent_node and parent_node != self.world.render:
|
||||||
transform_mat = parent_node.getMat(self.world.render)
|
try:
|
||||||
world_axis_vector = transform_mat.xformVec(local_axis_vector)
|
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:
|
else:
|
||||||
world_axis_vector = local_axis_vector
|
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
|
#axis_end = gizmo_world_pos + world_axis_vector
|
||||||
|
|
||||||
# 投影到屏幕空间
|
# 投影到屏幕空间
|
||||||
@ -1552,10 +1594,20 @@ class SelectionSystem:
|
|||||||
current_node = self.gizmoTarget.getParent()
|
current_node = self.gizmoTarget.getParent()
|
||||||
|
|
||||||
while current_node and current_node != self.world.render:
|
while current_node and current_node != self.world.render:
|
||||||
node_scale = current_node.getScale()
|
try:
|
||||||
avg_scale = (node_scale.x+node_scale.y + node_scale.z) / 3.0
|
if not current_node.isEmpty():
|
||||||
total_scale_factor *= avg_scale
|
node_scale = current_node.getScale()
|
||||||
current_node = current_node.getParent()
|
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:
|
if total_scale_factor > 0:
|
||||||
movement_distance = movement_distance / total_scale_factor
|
movement_distance = movement_distance / total_scale_factor
|
||||||
|
|||||||
@ -15,6 +15,14 @@ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
|||||||
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout)
|
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout)
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt5.QtGui import QColor
|
||||||
from PyQt5.QtCore import Qt
|
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:
|
class GUIManager:
|
||||||
@ -140,9 +148,9 @@ class GUIManager:
|
|||||||
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||||
return entry
|
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空间文本"""
|
"""创建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 = TextNode(f'3d-text-{len(self.gui_elements)}')
|
||||||
textNode.setText(text)
|
textNode.setText(text)
|
||||||
@ -150,11 +158,27 @@ class GUIManager:
|
|||||||
if self.world.getChineseFont():
|
if self.world.getChineseFont():
|
||||||
textNode.setFont(self.world.getChineseFont())
|
textNode.setFont(self.world.getChineseFont())
|
||||||
|
|
||||||
|
textNode.setTextColor(Vec4(1,1,1,1))
|
||||||
|
|
||||||
textNodePath = self.world.render.attachNewNode(textNode)
|
textNodePath = self.world.render.attachNewNode(textNode)
|
||||||
textNodePath.setPos(*pos)
|
textNodePath.setPos(*pos)
|
||||||
textNodePath.setScale(size)
|
textNodePath.setScale(size,size,size)
|
||||||
textNodePath.setColor(1, 1, 0, 1)
|
#textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||||||
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元素添加标识
|
# 为GUI元素添加标识
|
||||||
textNodePath.setTag("gui_type", "3d_text")
|
textNodePath.setTag("gui_type", "3d_text")
|
||||||
@ -162,6 +186,28 @@ class GUIManager:
|
|||||||
textNodePath.setTag("gui_text", text)
|
textNodePath.setTag("gui_text", text)
|
||||||
textNodePath.setTag("is_gui_element", "1")
|
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)
|
self.gui_elements.append(textNodePath)
|
||||||
# 安全地调用updateSceneTree
|
# 安全地调用updateSceneTree
|
||||||
if hasattr(self.world, 'updateSceneTree'):
|
if hasattr(self.world, 'updateSceneTree'):
|
||||||
@ -170,6 +216,90 @@ class GUIManager:
|
|||||||
print(f"✓ 创建3D文本: {text} (世界位置: {pos})")
|
print(f"✓ 创建3D文本: {text} (世界位置: {pos})")
|
||||||
return textNodePath
|
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="虚拟屏幕"):
|
def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
|
||||||
@ -263,6 +393,7 @@ class GUIManager:
|
|||||||
print(f"删除GUI元素失败: {str(e)}")
|
print(f"删除GUI元素失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 在 gui_manager.py 中确保 editGUIElement 方法正确处理文本颜色
|
||||||
def editGUIElement(self, gui_element, property_name, value):
|
def editGUIElement(self, gui_element, property_name, value):
|
||||||
"""编辑GUI元素属性"""
|
"""编辑GUI元素属性"""
|
||||||
try:
|
try:
|
||||||
@ -303,6 +434,28 @@ class GUIManager:
|
|||||||
|
|
||||||
gui_element.setTag("gui_text", value)
|
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":
|
elif property_name == "position":
|
||||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||||
gui_element.setPos(*value[:3])
|
gui_element.setPos(*value[:3])
|
||||||
@ -313,18 +466,13 @@ class GUIManager:
|
|||||||
elif isinstance(value, (list, tuple)) and len(value) >= 3:
|
elif isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||||
gui_element.setScale(*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}")
|
print(f"编辑GUI元素 {gui_type}: {property_name} = {value}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"编辑GUI元素失败: {str(e)}")
|
print(f"编辑GUI元素失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def duplicateGUIElement(self, gui_element):
|
def duplicateGUIElement(self, gui_element):
|
||||||
@ -346,6 +494,9 @@ class GUIManager:
|
|||||||
self.createGUIEntry(new_pos, gui_text + "_副本")
|
self.createGUIEntry(new_pos, gui_text + "_副本")
|
||||||
elif gui_type == "3d_text":
|
elif gui_type == "3d_text":
|
||||||
self.createGUI3DText(new_pos, gui_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":
|
elif gui_type == "virtual_screen":
|
||||||
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
||||||
|
|
||||||
@ -582,6 +733,19 @@ class GUIManager:
|
|||||||
)
|
)
|
||||||
y_pos -= spacing
|
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(
|
btn_screen = DirectButton(
|
||||||
parent=self.guiEditPanel,
|
parent=self.guiEditPanel,
|
||||||
@ -595,6 +759,43 @@ class GUIManager:
|
|||||||
)
|
)
|
||||||
y_pos -= spacing
|
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
|
y_pos -= 0.1
|
||||||
|
|
||||||
@ -741,6 +942,8 @@ class GUIManager:
|
|||||||
element = self.createGUIEntry(pos, f"输入框_{len(self.gui_elements)}")
|
element = self.createGUIEntry(pos, f"输入框_{len(self.gui_elements)}")
|
||||||
elif gui_type == "3d_text":
|
elif gui_type == "3d_text":
|
||||||
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
||||||
|
elif gui_type == "3d_image":
|
||||||
|
element = self.createGUI3DImage(pos)
|
||||||
elif gui_type == "virtual_screen":
|
elif gui_type == "virtual_screen":
|
||||||
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
||||||
else:
|
else:
|
||||||
@ -951,3 +1154,509 @@ class GUIManager:
|
|||||||
|
|
||||||
except Exception as e:
|
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
13
main.py
@ -183,6 +183,10 @@ class MyWorld(CoreWorld):
|
|||||||
"""创建3D空间文本"""
|
"""创建3D空间文本"""
|
||||||
return self.gui_manager.createGUI3DText(pos, text, size)
|
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)):
|
def createSpotLight(self,pos=(-20,0,5)):
|
||||||
"""创建聚光灯"""
|
"""创建聚光灯"""
|
||||||
return self.scene_manager.createSpotLight(pos)
|
return self.scene_manager.createSpotLight(pos)
|
||||||
@ -673,6 +677,15 @@ class MyWorld(CoreWorld):
|
|||||||
"streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None
|
"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模块的对应功能
|
# 以下函数代理到project_manager模块的对应功能
|
||||||
|
|||||||
@ -12,11 +12,57 @@ from panda3d.core import (
|
|||||||
MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere,
|
MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere,
|
||||||
BitMask32, TransparencyAttrib,LColor
|
BitMask32, TransparencyAttrib,LColor
|
||||||
)
|
)
|
||||||
|
import json
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
from panda3d.egg import EggData, EggVertexPool
|
from panda3d.egg import EggData, EggVertexPool
|
||||||
from direct.actor.Actor import Actor
|
from direct.actor.Actor import Actor
|
||||||
from QPanda3D.Panda3DWorld import get_render_pipeline
|
from QPanda3D.Panda3DWorld import get_render_pipeline
|
||||||
from scene import util
|
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:
|
class SceneManager:
|
||||||
"""场景管理器 - 统一管理场景中的所有元素"""
|
"""场景管理器 - 统一管理场景中的所有元素"""
|
||||||
@ -33,6 +79,8 @@ class SceneManager:
|
|||||||
self.Spotlight = []
|
self.Spotlight = []
|
||||||
self.Pointlight = []
|
self.Pointlight = []
|
||||||
|
|
||||||
|
self.tilesets = [] #来存储tilesets
|
||||||
|
self.cesium_integration = CesiumIntegration(self)
|
||||||
|
|
||||||
print("✓ 场景管理系统初始化完成")
|
print("✓ 场景管理系统初始化完成")
|
||||||
|
|
||||||
@ -1242,3 +1290,267 @@ except Exception as e:
|
|||||||
print(f"[PyAssimp转换] 转换过程出错: {e}")
|
print(f"[PyAssimp转换] 转换过程出错: {e}")
|
||||||
return False
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,10 @@ class InterfaceManager:
|
|||||||
duplicateAction = menu.addAction("复制")
|
duplicateAction = menu.addAction("复制")
|
||||||
duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath))
|
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:
|
else:
|
||||||
# 为模型节点或其子节点添加删除选项
|
# 为模型节点或其子节点添加删除选项
|
||||||
parentItem = item.parent()
|
parentItem = item.parent()
|
||||||
@ -88,6 +92,40 @@ class InterfaceManager:
|
|||||||
# 显示菜单
|
# 显示菜单
|
||||||
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
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):
|
def isModelOrChild(self, item):
|
||||||
"""检查是否是模型节点或其子节点"""
|
"""检查是否是模型节点或其子节点"""
|
||||||
while item and item.parent():
|
while item and item.parent():
|
||||||
@ -215,10 +253,19 @@ class InterfaceManager:
|
|||||||
gui_text = gui.getTag("gui_text") or "GUI元素"
|
gui_text = gui.getTag("gui_text") or "GUI元素"
|
||||||
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
||||||
item.setData(0, Qt.UserRole, gui)
|
item.setData(0, Qt.UserRole, gui)
|
||||||
|
#添加灯光节点
|
||||||
for light in self.world.Spotlight + self.world.Pointlight:
|
for light in self.world.Spotlight + self.world.Pointlight:
|
||||||
addNodeToTree(light, sceneRoot, force=True)
|
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:
|
if hasattr(self.world, 'ground') and self.world.ground:
|
||||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
|
|||||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||||||
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
|
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 PyQt5.QtCore import Qt, QDir, QTimer
|
||||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
||||||
|
|
||||||
@ -107,6 +107,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框')
|
self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框')
|
||||||
self.createGUIaddMenu.addSeparator()
|
self.createGUIaddMenu.addSeparator()
|
||||||
self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕')
|
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.createLightaddMenu = self.createMenu.addMenu('光源')
|
||||||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||||||
@ -140,6 +143,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.scriptMenu.addSeparator()
|
self.scriptMenu.addSeparator()
|
||||||
self.openScriptsManagerAction = self.scriptMenu.addAction('脚本管理器')
|
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('帮助')
|
self.helpMenu = menubar.addMenu('帮助')
|
||||||
self.aboutAction = self.helpMenu.addAction('关于')
|
self.aboutAction = self.helpMenu.addAction('关于')
|
||||||
@ -264,6 +271,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.create3DTextTool.setText("3D文本")
|
self.create3DTextTool.setText("3D文本")
|
||||||
self.toolbar.addWidget(self.create3DTextTool)
|
self.toolbar.addWidget(self.create3DTextTool)
|
||||||
|
|
||||||
|
self.create3DImageTool = QToolButton()
|
||||||
|
self.create3DImageTool.setText("3D图片")
|
||||||
|
self.toolbar.addWidget(self.create3DImageTool)
|
||||||
|
|
||||||
self.createSpotLight = QToolButton()
|
self.createSpotLight = QToolButton()
|
||||||
self.createSpotLight.setText("聚光灯")
|
self.createSpotLight.setText("聚光灯")
|
||||||
self.toolbar.addWidget(self.createSpotLight)
|
self.toolbar.addWidget(self.createSpotLight)
|
||||||
@ -272,6 +283,22 @@ class MainWindow(QMainWindow):
|
|||||||
self.createPointLight.setText("点光灯")
|
self.createPointLight.setText("点光灯")
|
||||||
self.toolbar.addWidget(self.createPointLight)
|
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.selectTool.setChecked(True)
|
||||||
self.world.setCurrentTool("选择")
|
self.world.setCurrentTool("选择")
|
||||||
@ -425,11 +452,18 @@ class MainWindow(QMainWindow):
|
|||||||
self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText())
|
self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText())
|
||||||
#self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight())
|
#self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight())
|
||||||
self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen())
|
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创建按钮事件
|
# 连接工具栏GUI创建按钮事件
|
||||||
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
|
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
|
||||||
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
|
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
|
||||||
self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText())
|
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.createSpotLight.clicked.connect(lambda :self.world.createSpotLight())
|
||||||
self.createPointLight.clicked.connect(lambda :self.world.createPointLight())
|
self.createPointLight.clicked.connect(lambda :self.world.createPointLight())
|
||||||
|
|
||||||
@ -448,6 +482,224 @@ class MainWindow(QMainWindow):
|
|||||||
self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||||||
self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
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):
|
def onToolChanged(self, button):
|
||||||
"""工具切换事件处理"""
|
"""工具切换事件处理"""
|
||||||
if button.isChecked():
|
if button.isChecked():
|
||||||
|
|||||||
@ -141,10 +141,18 @@ class PropertyPanelManager:
|
|||||||
# 获取节点对象
|
# 获取节点对象
|
||||||
model = item.data(0, Qt.UserRole)
|
model = item.data(0, Qt.UserRole)
|
||||||
|
|
||||||
|
if model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset":
|
||||||
|
self._showCesiumTilesetProperties(model,item)
|
||||||
# 检查是否是GUI元素
|
# 检查是否是GUI元素
|
||||||
if model and hasattr(model, 'getTag') and model.getTag("gui_type"):
|
elif model and hasattr(model, 'getTag') and model.getTag("gui_type"):
|
||||||
self.updateGUIPropertyPanel(model)
|
# gui_type = model.getTag("gui_type")
|
||||||
pass
|
# 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"):
|
elif model and hasattr(model, 'getTag') and model.getTag("light_type"):
|
||||||
self.updateLightPropertyPanel(model)
|
self.updateLightPropertyPanel(model)
|
||||||
pass
|
pass
|
||||||
@ -552,7 +560,6 @@ class PropertyPanelManager:
|
|||||||
spin.blockSignals(False)
|
spin.blockSignals(False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def updateGUIPropertyPanel(self, gui_element):
|
def updateGUIPropertyPanel(self, gui_element):
|
||||||
"""更新GUI元素属性面板"""
|
"""更新GUI元素属性面板"""
|
||||||
gui_type = gui_element.getTag("gui_type")
|
gui_type = gui_element.getTag("gui_type")
|
||||||
@ -568,6 +575,7 @@ class PropertyPanelManager:
|
|||||||
# typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
# typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
||||||
gui_info_layout.addWidget(typeValue, 0, 1)
|
gui_info_layout.addWidget(typeValue, 0, 1)
|
||||||
|
|
||||||
|
# 修改 updateGUIPropertyPanel 中的文本属性部分
|
||||||
# 文本属性(如果适用)
|
# 文本属性(如果适用)
|
||||||
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||||||
gui_info_layout.addWidget(QLabel("文本:"), 1, 0)
|
gui_info_layout.addWidget(QLabel("文本:"), 1, 0)
|
||||||
@ -578,7 +586,8 @@ class PropertyPanelManager:
|
|||||||
success = self.world.gui_manager.editGUIElement(gui_element, "text", text)
|
success = self.world.gui_manager.editGUIElement(gui_element, "text", text)
|
||||||
if success:
|
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)
|
textEdit.textChanged.connect(updateText)
|
||||||
gui_info_layout.addWidget(textEdit, 1, 1)
|
gui_info_layout.addWidget(textEdit, 1, 1)
|
||||||
@ -676,7 +685,6 @@ class PropertyPanelManager:
|
|||||||
[pos.getX(), pos.getY(), v]))
|
[pos.getX(), pos.getY(), v]))
|
||||||
transform_layout.addWidget(zPos, 1, 3)
|
transform_layout.addWidget(zPos, 1, 3)
|
||||||
|
|
||||||
# 缩放属性
|
|
||||||
if hasattr(gui_element, 'getScale'):
|
if hasattr(gui_element, 'getScale'):
|
||||||
scale = gui_element.getScale()
|
scale = gui_element.getScale()
|
||||||
|
|
||||||
@ -684,17 +692,51 @@ class PropertyPanelManager:
|
|||||||
|
|
||||||
transform_layout.addWidget(QLabel("缩放"), row_offset, 0)
|
transform_layout.addWidget(QLabel("缩放"), row_offset, 0)
|
||||||
|
|
||||||
scaleSpinBox = QDoubleSpinBox()
|
# X缩放
|
||||||
scaleSpinBox.setRange(0.01, 10)
|
transform_layout.addWidget(QLabel("长:"), row_offset, 1)
|
||||||
scaleSpinBox.setSingleStep(0.1)
|
scaleXSpinBox = QDoubleSpinBox()
|
||||||
scaleSpinBox.setValue(scale.getX())
|
scaleXSpinBox.setRange(0.01, 1000)
|
||||||
scaleSpinBox.valueChanged.connect(
|
scaleXSpinBox.setSingleStep(0.1)
|
||||||
lambda v: self.world.gui_manager.editGUIElement(gui_element, "scale", v))
|
scaleXSpinBox.setValue(scale.getX())
|
||||||
transform_layout.addWidget(scaleSpinBox, row_offset, 1)
|
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)
|
transform_group.setLayout(transform_layout)
|
||||||
self._propertyLayout.addWidget(transform_group)
|
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"]:
|
if gui_type in ["button", "label"]:
|
||||||
appearance_group = QGroupBox("外观属性")
|
appearance_group = QGroupBox("外观属性")
|
||||||
@ -709,6 +751,200 @@ class PropertyPanelManager:
|
|||||||
appearance_group.setLayout(appearance_layout)
|
appearance_group.setLayout(appearance_layout)
|
||||||
self._propertyLayout.addWidget(appearance_group)
|
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):
|
def _updateScriptPropertyPanel(self, game_object):
|
||||||
"""更新脚本属性面板"""
|
"""更新脚本属性面板"""
|
||||||
# 获取对象上的脚本
|
# 获取对象上的脚本
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user