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:
|
||||
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的父类方法处理基础的相机旋转
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
13
main.py
@ -183,6 +183,10 @@ class MyWorld(CoreWorld):
|
||||
"""创建3D空间文本"""
|
||||
return self.gui_manager.createGUI3DText(pos, text, size)
|
||||
|
||||
def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(2,2)):
|
||||
"""创建3D图片"""
|
||||
return self.gui_manager.createGUI3DImage(pos,text,size)
|
||||
|
||||
def createSpotLight(self,pos=(-20,0,5)):
|
||||
"""创建聚光灯"""
|
||||
return self.scene_manager.createSpotLight(pos)
|
||||
@ -673,6 +677,15 @@ class MyWorld(CoreWorld):
|
||||
"streaming_status": self.getALVRStreamingStatus() if self.isALVRConnected() else None
|
||||
}
|
||||
|
||||
def loadCesiumTileset(self,tileset_url,position=(0,0,0)):
|
||||
return self.scene_manager.load_cesium_tileset(tileset_url,position)
|
||||
|
||||
def addCesiumTileset(self,name,url,position=(0,0,0)):
|
||||
if hasattr(self,'gui_manager') and self.gui_manager:
|
||||
return self.gui_manager.addCesiumTilesetToScene(name,url,position)
|
||||
else:
|
||||
return self.scene_manager.load_cesium_tileset(url,position)
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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, ['地板'])
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
"""更新脚本属性面板"""
|
||||
# 获取对象上的脚本
|
||||
|
||||
Loading…
Reference in New Issue
Block a user