forked from Rowland/EG
1694 lines
66 KiB
Python
1694 lines
66 KiB
Python
# 修改后的 InfoPanelManager.py
|
||
from xml.sax.handler import property_encoding
|
||
|
||
from PyQt5.QtCore import Qt
|
||
from PyQt5.QtWidgets import QMessageBox
|
||
from direct.gui.DirectGui import DirectFrame, DirectLabel
|
||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||
from panda3d.core import TextNode, Vec4, NodePath
|
||
from direct.showbase.DirectObject import DirectObject
|
||
import threading
|
||
import json
|
||
import time
|
||
from urllib.parse import urlparse
|
||
import requests
|
||
|
||
|
||
class InfoPanelManager(DirectObject):
|
||
"""信息面板管理器,用于创建和管理2D信息面板"""
|
||
|
||
def __init__(self, world):
|
||
"""
|
||
初始化信息面板管理器
|
||
parent: 父节点,默认为aspect2d
|
||
"""
|
||
DirectObject.__init__(self)
|
||
self.world= world
|
||
self.parent = aspect2d
|
||
self.panels = {} # 存储所有创建的面板
|
||
self.data_sources = {} # 存储数据源
|
||
self.data_callbacks = {} # 存储数据更新回调
|
||
|
||
def setParent(self, parent):
|
||
"""设置父节点"""
|
||
self.parent = parent if parent else aspect2d
|
||
|
||
def createInfoPanel(self, panel_id, position=(0, 0), size=(1.0, 0.6),
|
||
bg_color=(0.15, 0.15, 0.15, 0.9), border_color=(0.3, 0.3, 0.3, 1.0),
|
||
title_color=(1.0, 1.0, 1.0, 1.0), content_color=(0.9, 0.9, 0.9, 1.0),
|
||
visible=True, font=None, bg_image=None):
|
||
"""
|
||
创建信息面板 - 返回 NodePath 实例
|
||
"""
|
||
# 如果面板已存在,先移除它
|
||
if panel_id in self.panels:
|
||
self.removePanel(panel_id)
|
||
|
||
# 确保父节点存在
|
||
parent_node = self.parent if self.parent else aspect2d
|
||
|
||
# 根据面板ID确定标题和内容
|
||
title, content = self._getPanelContent(panel_id)
|
||
|
||
# 创建主节点,便于统一管理
|
||
panel_node = parent_node.attachNewNode(f"{panel_id}")
|
||
panel_node.setPos(position[0], 0, position[1])
|
||
|
||
# 创建主面板框架(带边框效果)
|
||
panel = DirectFrame(
|
||
frameSize=(-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2),
|
||
frameColor=bg_color,
|
||
parent=panel_node
|
||
)
|
||
|
||
# 添加背景图片(如果提供)
|
||
bg_image_node = None
|
||
if bg_image and hasattr(self.world, 'loader'):
|
||
try:
|
||
from panda3d.core import Texture, TransparencyAttrib
|
||
# 使用 world.loader 加载纹理
|
||
texture = self.world.loader.loadTexture(bg_image)
|
||
if texture:
|
||
# 创建一个覆盖整个面板的图片
|
||
bg_image_node = DirectFrame(
|
||
frameSize=(-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2),
|
||
frameTexture=texture,
|
||
parent=panel_node,
|
||
sortOrder=-10 # 确保在最底层
|
||
)
|
||
bg_image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||
except Exception as e:
|
||
print(f"加载背景图片失败: {e}")
|
||
|
||
# 添加边框
|
||
border_thickness = 0.005
|
||
# 上边框
|
||
top_border = DirectFrame(
|
||
frameSize=(-size[0] / 2 - border_thickness, size[0] / 2 + border_thickness,
|
||
size[1] / 2, size[1] / 2 + border_thickness),
|
||
frameColor=border_color,
|
||
parent=panel_node
|
||
)
|
||
# 下边框
|
||
bottom_border = DirectFrame(
|
||
frameSize=(-size[0] / 2 - border_thickness, size[0] / 2 + border_thickness,
|
||
-size[1] / 2 - border_thickness, -size[1] / 2),
|
||
frameColor=border_color,
|
||
parent=panel_node
|
||
)
|
||
# 左边框
|
||
left_border = DirectFrame(
|
||
frameSize=(-size[0] / 2 - border_thickness, -size[0] / 2,
|
||
-size[1] / 2, size[1] / 2),
|
||
frameColor=border_color,
|
||
parent=panel_node
|
||
)
|
||
# 右边框
|
||
right_border = DirectFrame(
|
||
frameSize=(size[0] / 2, size[0] / 2 + border_thickness,
|
||
-size[1] / 2, size[1] / 2),
|
||
frameColor=border_color,
|
||
parent=panel_node
|
||
)
|
||
|
||
# 创建标题栏背景
|
||
title_bar_height = 0.08
|
||
title_bar = DirectFrame(
|
||
frameSize=(-size[0] / 2 + border_thickness, size[0] / 2 - border_thickness,
|
||
size[1] / 2 - title_bar_height, size[1] / 2 - border_thickness),
|
||
frameColor=(0, 0, 0, 0),
|
||
parent=panel_node
|
||
)
|
||
|
||
# 创建标题
|
||
title_label = DirectLabel(
|
||
text=title,
|
||
text_scale=0.06,
|
||
text_fg=title_color,
|
||
text_align=TextNode.ACenter,
|
||
pos=(0, 0, size[1] / 2 - title_bar_height / 2 - border_thickness),
|
||
parent=panel_node,
|
||
relief=None,
|
||
text_font=font or None
|
||
)
|
||
|
||
# 创建内容区域背景(稍微深一点)
|
||
content_bg = DirectFrame(
|
||
frameSize=(-size[0] / 2 + border_thickness * 2, size[0] / 2 - border_thickness * 2,
|
||
-size[1] / 2 + border_thickness * 2, size[1] / 2 - title_bar_height - border_thickness * 2),
|
||
frameColor=(0, 0, 0, 0),
|
||
parent=panel_node
|
||
)
|
||
|
||
# 创建内容 - 设置一个非常大的换行值,几乎不换行
|
||
content_label = DirectLabel(
|
||
text=content,
|
||
text_scale=0.045,
|
||
text_fg=content_color,
|
||
text_align=TextNode.ALeft,
|
||
text_wordwrap=0, # 设置一个非常大的值,几乎不自动换行
|
||
pos=(-size[0] / 2 + 0.03, 0, size[1] / 2 - title_bar_height - 0.05),
|
||
parent=panel_node,
|
||
relief=None,
|
||
text_font=font or None
|
||
)
|
||
|
||
# 保存引用
|
||
self.panels[panel_id] = {
|
||
'node': panel_node,
|
||
'panel': panel,
|
||
'title_label': title_label,
|
||
'content_label': content_label,
|
||
'title_bar': title_bar,
|
||
'content_bg': content_bg,
|
||
'borders': {
|
||
'top': top_border,
|
||
'bottom': bottom_border,
|
||
'left': left_border,
|
||
'right': right_border
|
||
},
|
||
'bg_image': bg_image_node, # 保存背景图片节点引用
|
||
'properties': {
|
||
'size': size,
|
||
'position': position,
|
||
'bg_color': bg_color,
|
||
'border_color': border_color,
|
||
'title_color': title_color,
|
||
'content_color': content_color,
|
||
'font': font,
|
||
'bg_image': bg_image # 保存背景图片路径
|
||
}
|
||
}
|
||
|
||
# 设置GUI类型标记和支持3D编辑的标记
|
||
panel_node.setTag("gui_type", "info_panel")
|
||
panel_node.setTag("panel_id", panel_id)
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
panel_node.setTag("is_gui_element",'1')
|
||
panel_node.setTag("tree_item_type","INFO_PANEL")
|
||
panel_node.setTag("supports_3d_position_editing","1")
|
||
# 如果有背景图片,保存背景图片路径
|
||
if bg_image:
|
||
panel_node.setTag("image_path", bg_image)
|
||
|
||
|
||
if not visible:
|
||
panel_node.hide()
|
||
|
||
# 将面板添加到场景树
|
||
#self._addPanelToSceneTree(panel_node, panel_id)
|
||
if hasattr(self.world, 'gui_elements'):
|
||
self.world.gui_elements.append(panel_node)
|
||
|
||
return panel_node
|
||
|
||
def _addPanelToSceneTree(self, panel_node, panel_id):
|
||
"""
|
||
将信息面板添加到场景树中
|
||
"""
|
||
try:
|
||
# 获取树形控件
|
||
if hasattr(self.world, 'interface_manager') and hasattr(self.world.interface_manager, 'treeWidget'):
|
||
tree_widget = self.world.interface_manager.treeWidget
|
||
if tree_widget:
|
||
# 查找根节点项
|
||
root_item = None
|
||
for i in range(tree_widget.topLevelItemCount()):
|
||
item = tree_widget.topLevelItem(i)
|
||
if item.text(0) == "render" or item.data(0, Qt.UserRole) == self.world.render:
|
||
root_item = item
|
||
break
|
||
|
||
if root_item:
|
||
# 使用现有的 add_node_to_tree_widget 方法添加节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(panel_node, root_item, "INFO_PANEL")
|
||
if qt_item:
|
||
print(f"✅ 信息面板 {panel_id} 已添加到场景树")
|
||
# 选中创建的节点
|
||
tree_widget.setCurrentItem(qt_item)
|
||
# 更新选择和属性面板
|
||
tree_widget.update_selection_and_properties(panel_node, qt_item)
|
||
else:
|
||
print(f"⚠️ 信息面板 {panel_id} 添加到场景树失败")
|
||
else:
|
||
print("⚠️ 未找到场景树根节点,无法添加信息面板")
|
||
else:
|
||
print("⚠️ 无法访问场景树控件,信息面板未添加到场景树")
|
||
except Exception as e:
|
||
print(f"❌ 添加信息面板到场景树时出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def setPanelBackgroundImage(self, panel_id, image_path):
|
||
"""
|
||
为指定面板设置背景图片
|
||
panel_id: 面板ID
|
||
image_path: 图片路径
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
try:
|
||
panel_data = self.panels[panel_id]
|
||
|
||
# 如果image_path为None或空,清除现有的背景图片
|
||
if not image_path:
|
||
if 'bg_image' in panel_data and panel_data['bg_image']:
|
||
panel_data['bg_image'].destroy()
|
||
panel_data['bg_image'] = None
|
||
panel_data['properties']['bg_image'] = None
|
||
# 清除节点标签
|
||
if panel_data['node'].hasTag("bg_image_path"):
|
||
panel_data['node'].clearTag("bg_image_path")
|
||
return True
|
||
|
||
# 使用 world.loader 加载新图片
|
||
texture = self.world.loader.loadTexture(image_path)
|
||
if not texture:
|
||
print(f"无法加载图片: {image_path}")
|
||
return False
|
||
|
||
# 获取面板大小
|
||
size = panel_data['properties']['size']
|
||
|
||
# 如果已存在背景图片,先销毁它
|
||
if 'bg_image' in panel_data and panel_data['bg_image']:
|
||
panel_data['bg_image'].destroy()
|
||
|
||
from direct.gui.DirectGui import DirectFrame
|
||
from panda3d.core import TransparencyAttrib
|
||
|
||
bg_image_node = DirectFrame(
|
||
frameSize=(-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2),
|
||
frameTexture=texture,
|
||
parent=panel_data['node'],
|
||
sortOrder=-10 # 确保在最底层
|
||
)
|
||
|
||
bg_image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 保存引用
|
||
panel_data['bg_image'] = bg_image_node
|
||
panel_data['properties']['bg_image'] = image_path
|
||
|
||
# 更新节点标签
|
||
panel_data['node'].setTag("bg_image_path", image_path)
|
||
|
||
print(f"成功设置信息面板背景图片: {image_path}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"设置背景图片失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _getPanelContent(self, panel_id):
|
||
"""
|
||
根据面板ID获取固定的标题和内容
|
||
"""
|
||
panel_contents = {
|
||
"scene_info": {
|
||
"title": "场景信息",
|
||
"content": "项目名称: 我的3D项目\n场景节点数: 15\n总材质数: 8\n光源数量: 3"
|
||
},
|
||
"controls": {
|
||
"title": "控制说明",
|
||
"content": "WASD: 移动\n鼠标: 视角\n空格: 跳跃\nESC: 退出"
|
||
},
|
||
"model_info": {
|
||
"title": "模型信息",
|
||
"content": "模型名称: Unknown\n子节点数: 0\n类型: Unknown"
|
||
},
|
||
"terrain_info": {
|
||
"title": "地形信息",
|
||
"content": "地形名称: Unknown\n大小: 0 x 0\n分辨率: 0"
|
||
},
|
||
"light_info": {
|
||
"title": "光源信息",
|
||
"content": "光源类型: Unknown\n投射阴影: Unknown\n阴影分辨率: Unknown"
|
||
},
|
||
"sensor_data": {
|
||
"title": "传感器数据",
|
||
"content": "温度: --°C\n湿度: --%\n压力: -- hPa\n更新时间: --"
|
||
},
|
||
"system_status": {
|
||
"title": "系统状态",
|
||
"content": "CPU使用率: --%\n内存使用: --MB\n网络状态: 断开\n运行时间: --"
|
||
},
|
||
"realtime_data": {
|
||
"title": "实时数据",
|
||
"content": "数据加载中..."
|
||
},
|
||
"default": {
|
||
"title": "信息面板",
|
||
"content": "这是一个信息面板"
|
||
}
|
||
}
|
||
|
||
# 返回对应面板的内容,如果没有则返回默认内容
|
||
panel_data = panel_contents.get(panel_id, panel_contents["default"])
|
||
return panel_data["title"], panel_data["content"]
|
||
|
||
def updatePanelContent(self, panel_id, title=None, content=None):
|
||
"""
|
||
更新面板内容
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
panel_data = self.panels[panel_id]
|
||
|
||
if title is not None and panel_data['title_label']:
|
||
panel_data['title_label'].setText(title)
|
||
|
||
if content is not None and panel_data['content_label']:
|
||
panel_data['content_label'].setText(content)
|
||
|
||
return True
|
||
|
||
# 更新 registerDataSource 方法以更好地处理不同类型面板
|
||
def registerDataSource(self, panel_id, data_callback, update_interval=1.0):
|
||
"""
|
||
注册数据源,定期更新面板内容 - 改进版
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
# 停止现有的数据源(如果存在)
|
||
if panel_id in self.data_sources:
|
||
self.data_sources[panel_id]['stop'] = True
|
||
|
||
# 创建数据源
|
||
data_source = {
|
||
'callback': data_callback,
|
||
'interval': update_interval,
|
||
'stop': False,
|
||
'panel_type': '3d' if self._is3DPanel(panel_id) else '2d' if self._is2DPanel(panel_id) else 'unknown'
|
||
}
|
||
|
||
self.data_sources[panel_id] = data_source
|
||
|
||
# 启动数据更新线程
|
||
thread = threading.Thread(target=self._updateDataThread, args=(panel_id,), daemon=True)
|
||
thread.start()
|
||
|
||
return True
|
||
|
||
# 在 InfoPanelManager 类中修复 _updateDataThread 方法
|
||
def _updateDataThread(self, panel_id):
|
||
"""
|
||
数据更新线程 - 最终修复版
|
||
"""
|
||
while panel_id in self.data_sources and not self.data_sources[panel_id]['stop']:
|
||
try:
|
||
# 获取数据
|
||
data = self.data_sources[panel_id]['callback']()
|
||
|
||
# 更新面板内容
|
||
if data and panel_id in self.panels:
|
||
panel_type = self.data_sources[panel_id].get('panel_type', 'unknown')
|
||
|
||
if panel_type == '2d':
|
||
self.updatePanelContent(panel_id, content=data)
|
||
elif panel_type == '3d':
|
||
self.update3DPanelContent(panel_id, content=data)
|
||
else:
|
||
# 尝试自动检测
|
||
if self._is2DPanel(panel_id):
|
||
self.updatePanelContent(panel_id, content=data)
|
||
elif self._is3DPanel(panel_id):
|
||
self.update3DPanelContent(panel_id, content=data)
|
||
|
||
# 等待下次更新
|
||
interval = self.data_sources[panel_id]['interval']
|
||
time.sleep(interval)
|
||
|
||
except Exception as e:
|
||
print(f"更新面板 {panel_id} 数据时出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
time.sleep(1.0) # 出错时等待1秒再重试
|
||
|
||
# 在 InfoPanelManager 类中添加以下方法
|
||
def _is3DPanel(self, panel_id):
|
||
"""
|
||
判断面板是否为3D面板
|
||
"""
|
||
if panel_id not in self.panels:
|
||
return False
|
||
panel_data = self.panels[panel_id]
|
||
return 'content_node' in panel_data and 'content_label' not in panel_data
|
||
|
||
def _is2DPanel(self, panel_id):
|
||
"""
|
||
判断面板是否为2D面板
|
||
"""
|
||
if panel_id not in self.panels:
|
||
return False
|
||
panel_data = self.panels[panel_id]
|
||
return 'content_label' in panel_data and 'content_node' not in panel_data
|
||
|
||
def unregisterDataSource(self, panel_id):
|
||
"""
|
||
注销数据源
|
||
"""
|
||
if panel_id in self.data_sources:
|
||
self.data_sources[panel_id]['stop'] = True
|
||
del self.data_sources[panel_id]
|
||
return True
|
||
return False
|
||
|
||
def updatePanelProperties(self, panel_id, **properties):
|
||
"""
|
||
更新面板属性
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
panel_data = self.panels[panel_id]
|
||
props = panel_data['properties']
|
||
|
||
# 更新位置
|
||
if 'position' in properties:
|
||
pos = properties['position']
|
||
panel_data['node'].setPos(pos[0], 0, pos[1])
|
||
props['position'] = pos
|
||
|
||
# 更新大小
|
||
if 'size' in properties:
|
||
size = properties['size']
|
||
panel_data['panel']['frameSize'] = (-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2)
|
||
props['size'] = size
|
||
|
||
# 更新边框
|
||
border_thickness = 0.005
|
||
title_bar_height = 0.08
|
||
|
||
# 更新边框位置和大小
|
||
panel_data['borders']['top']['frameSize'] = (
|
||
-size[0] / 2 - border_thickness, size[0] / 2 + border_thickness,
|
||
size[1] / 2, size[1] / 2 + border_thickness
|
||
)
|
||
|
||
panel_data['borders']['bottom']['frameSize'] = (
|
||
-size[0] / 2 - border_thickness, size[0] / 2 + border_thickness,
|
||
-size[1] / 2 - border_thickness, -size[1] / 2
|
||
)
|
||
|
||
panel_data['borders']['left']['frameSize'] = (
|
||
-size[0] / 2 - border_thickness, -size[0] / 2,
|
||
-size[1] / 2, size[1] / 2
|
||
)
|
||
|
||
panel_data['borders']['right']['frameSize'] = (
|
||
size[0] / 2, size[0] / 2 + border_thickness,
|
||
-size[1] / 2, size[1] / 2
|
||
)
|
||
|
||
# 更新标题栏
|
||
panel_data['title_bar']['frameSize'] = (
|
||
-size[0] / 2 + border_thickness, size[0] / 2 - border_thickness,
|
||
size[1] / 2 - title_bar_height, size[1] / 2 - border_thickness
|
||
)
|
||
|
||
# 更新标题位置
|
||
panel_data['title_label'].setPos(
|
||
0, 0, size[1] / 2 - title_bar_height / 2 - border_thickness
|
||
)
|
||
|
||
# 更新内容背景
|
||
panel_data['content_bg']['frameSize'] = (
|
||
-size[0] / 2 + border_thickness * 2, size[0] / 2 - border_thickness * 2,
|
||
-size[1] / 2 + border_thickness * 2, size[1] / 2 - title_bar_height - border_thickness * 2
|
||
)
|
||
|
||
# 更新内容位置
|
||
panel_data['content_label'].setPos(
|
||
-size[0] / 2 + 0.03, 0, size[1] / 2 - title_bar_height - 0.05
|
||
)
|
||
|
||
panel_data['content_label']['text_wordwrap'] = 0
|
||
|
||
# 如果有背景图片,也需要更新其大小
|
||
if 'bg_image' in panel_data and panel_data['bg_image']:
|
||
panel_data['bg_image']['frameSize'] = (-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2)
|
||
|
||
# 更新背景颜色
|
||
if 'bg_color' in properties:
|
||
panel_data['panel']['frameColor'] = properties['bg_color']
|
||
props['bg_color'] = properties['bg_color']
|
||
|
||
# 更新边框颜色
|
||
if 'border_color' in properties:
|
||
color = properties['border_color']
|
||
for border in panel_data['borders'].values():
|
||
border['frameColor'] = color
|
||
props['border_color'] = color
|
||
|
||
# 更新标题颜色
|
||
if 'title_color' in properties:
|
||
panel_data['title_label']['text_fg'] = properties['title_color']
|
||
props['title_color'] = properties['title_color']
|
||
|
||
# 更新内容颜色
|
||
if 'content_color' in properties:
|
||
panel_data['content_label']['text_fg'] = properties['content_color']
|
||
props['content_color'] = properties['content_color']
|
||
|
||
# 更新标题
|
||
if 'title' in properties:
|
||
panel_data['title_label'].setText(properties['title'])
|
||
|
||
# 更新内容
|
||
if 'content' in properties:
|
||
panel_data['content_label'].setText(properties['content'])
|
||
|
||
# 更新字体大小
|
||
if 'title_size' in properties:
|
||
panel_data['title_label']['text_scale'] = properties['title_size']
|
||
props['title_size'] = properties['title_size']
|
||
|
||
if 'content_size' in properties:
|
||
panel_data['content_label']['text_scale'] = properties['content_size']
|
||
props['content_size'] = properties['content_size']
|
||
current_size = props.get('size',(1.0,0.6))
|
||
# 当字体大小改变时,仍然保持较大的换行值
|
||
panel_data['content_label']['text_wordwrap'] = 0
|
||
|
||
# 更新背景图片
|
||
if 'bg_image' in properties:
|
||
image_path = properties['bg_image']
|
||
# 无论image_path是None、空字符串还是有效路径,都调用setPanelBackgroundImage处理
|
||
self.setPanelBackgroundImage(panel_id, image_path)
|
||
# 同步更新properties中的bg_image值
|
||
props['bg_image'] = image_path
|
||
|
||
return True
|
||
|
||
def showPanel(self, panel_id):
|
||
"""显示指定面板"""
|
||
if panel_id in self.panels:
|
||
self.panels[panel_id]['node'].show()
|
||
return True
|
||
return False
|
||
|
||
def hidePanel(self, panel_id):
|
||
"""隐藏指定面板"""
|
||
if panel_id in self.panels:
|
||
self.panels[panel_id]['node'].hide()
|
||
return True
|
||
return False
|
||
|
||
def togglePanel(self, panel_id):
|
||
"""切换面板显示/隐藏状态"""
|
||
if panel_id in self.panels:
|
||
node = self.panels[panel_id]['node']
|
||
if node.isHidden():
|
||
node.show()
|
||
else:
|
||
node.hide()
|
||
return True
|
||
return False
|
||
|
||
def getPanelNode(self, panel_id):
|
||
"""获取面板的节点"""
|
||
if panel_id in self.panels:
|
||
return self.panels[panel_id]['node']
|
||
return None
|
||
|
||
def removePanel(self, panel_id):
|
||
"""移除指定面板"""
|
||
if panel_id in self.panels:
|
||
# 停止数据源更新
|
||
self.unregisterDataSource(panel_id)
|
||
|
||
panel_data = self.panels[panel_id]
|
||
# 清理所有子元素
|
||
panel_data['node'].removeNode()
|
||
# 从字典中移除
|
||
del self.panels[panel_id]
|
||
return True
|
||
return False
|
||
|
||
def removeAllPanels(self):
|
||
"""移除所有面板"""
|
||
panel_ids = list(self.panels.keys())
|
||
for panel_id in panel_ids:
|
||
self.removePanel(panel_id)
|
||
|
||
def createHTTPInfoPanel(self, panel_id, url, method="GET", headers=None, data=None,
|
||
position=(0, 0), size=(1.0, 0.6),
|
||
bg_color=(0.15, 0.15, 0.15, 0.9),
|
||
border_color=(0.3, 0.3, 0.3, 1.0),
|
||
title_color=(1.0, 1.0, 1.0, 1.0),
|
||
content_color=(0.9, 0.9, 0.9, 1.0),
|
||
update_interval=30.0, font=None):
|
||
"""
|
||
创建HTTP信息面板
|
||
panel_id: 面板ID
|
||
url: 请求的URL
|
||
method: HTTP方法
|
||
headers: 请求头
|
||
data: POST数据
|
||
position: 位置
|
||
size: 大小
|
||
bg_color: 背景颜色
|
||
border_color: 边框颜色
|
||
title_color: 标题颜色
|
||
content_color: 内容颜色
|
||
update_interval: 更新间隔(秒)
|
||
font: 字体
|
||
"""
|
||
# 创建面板
|
||
domain = urlparse(url).netloc or url[:30]
|
||
title = f"HTTP数据: {domain}"
|
||
|
||
panel_node = self.createInfoPanel(
|
||
panel_id=panel_id,
|
||
position=position,
|
||
size=size,
|
||
bg_color=bg_color,
|
||
border_color=border_color,
|
||
title_color=title_color,
|
||
content_color=content_color,
|
||
font=font
|
||
)
|
||
|
||
# 更新标题
|
||
self.updatePanelContent(panel_id, title=title)
|
||
|
||
# 立即获取并显示数据
|
||
content = fetchHTTPData(url, method, headers, data)
|
||
self.updatePanelContent(panel_id, content=content)
|
||
|
||
# 注册数据源,定期更新
|
||
def http_data_callback():
|
||
return fetchHTTPData(url, method, headers, data)
|
||
|
||
self.registerDataSource(panel_id, http_data_callback, update_interval)
|
||
|
||
# 保存HTTP请求信息,便于后续更新
|
||
if panel_id not in self.data_sources:
|
||
self.data_sources[panel_id] = {}
|
||
self.data_sources[panel_id]['http_info'] = {
|
||
'url': url,
|
||
'method': method,
|
||
'headers': headers,
|
||
'data': data
|
||
}
|
||
|
||
return panel_node
|
||
|
||
def updateHTTPInfoPanel(self, panel_id, url=None, method=None, headers=None, data=None):
|
||
"""
|
||
更新HTTP信息面板的请求参数
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
# 更新HTTP信息
|
||
if panel_id in self.data_sources and 'http_info' in self.data_sources[panel_id]:
|
||
http_info = self.data_sources[panel_id]['http_info']
|
||
if url is not None:
|
||
http_info['url'] = url
|
||
if method is not None:
|
||
http_info['method'] = method
|
||
if headers is not None:
|
||
http_info['headers'] = headers
|
||
if data is not None:
|
||
http_info['data'] = data
|
||
|
||
# 更新面板标题
|
||
if url is not None:
|
||
domain = urlparse(url).netloc or url[:30]
|
||
title = f"HTTP数据: {domain}"
|
||
self.updatePanelContent(panel_id, title=title)
|
||
|
||
# 立即获取并显示新数据
|
||
content = fetchHTTPData(
|
||
http_info['url'],
|
||
http_info['method'],
|
||
http_info['headers'],
|
||
http_info['data']
|
||
)
|
||
self.updatePanelContent(panel_id, content=content)
|
||
|
||
return True
|
||
|
||
def create3DInfoPanel(self, panel_id, position=(0, 0, 0), size=(1.0, 0.6),
|
||
bg_color=(0.15, 0.15, 0.15, 0.9), border_color=(0.3, 0.3, 0.3, 1.0),
|
||
title_color=(1.0, 1.0, 1.0, 1.0), content_color=(0.9, 0.9, 0.9, 1.0),
|
||
visible=True, font=None, bg_image=None):
|
||
"""
|
||
创建简化版3D信息面板 - 只显示文字,无面板背景,避免闪烁
|
||
"""
|
||
# 如果面板已存在,先移除它
|
||
if panel_id in self.panels:
|
||
self.removePanel(panel_id)
|
||
|
||
# 确保父节点存在
|
||
parent_node = self.parent if self.parent else self.world.render
|
||
|
||
# 根据面板ID确定标题和内容
|
||
title, content = self._getPanelContent(panel_id)
|
||
|
||
# 创建主节点,便于统一管理
|
||
panel_node = parent_node.attachNewNode(f"{panel_id}")
|
||
panel_node.setPos(position[0], position[1], position[2])
|
||
|
||
# 直接创建文字节点,不创建面板背景和边框
|
||
from panda3d.core import TextNode
|
||
|
||
# 创建标题文本
|
||
title_text_node = TextNode(f'title_{panel_id}')
|
||
title_text_node.setText(title)
|
||
title_text_node.setTextColor(*title_color)
|
||
title_text_node.setAlign(TextNode.ACenter)
|
||
if font:
|
||
title_text_node.setFont(font)
|
||
|
||
title_text = panel_node.attachNewNode(title_text_node)
|
||
title_text.setScale(0.06)
|
||
title_text.setPos(0, 0, size[1] / 4) # 将标题放在上方
|
||
|
||
# 创建内容文本
|
||
content_text_node = TextNode(f'content_{panel_id}')
|
||
content_text_node.setText(content)
|
||
content_text_node.setTextColor(*content_color)
|
||
content_text_node.setAlign(TextNode.ALeft)
|
||
content_text_node.setWordwrap(size[0] * 2) # 根据面板宽度设置换行
|
||
if font:
|
||
content_text_node.setFont(font)
|
||
|
||
content_text = panel_node.attachNewNode(content_text_node)
|
||
content_text.setScale(0.045)
|
||
content_text.setPos(-size[0] / 2, 0, size[1] / 4 - 0.1) # 将内容放在标题下方
|
||
|
||
# 保存引用
|
||
self.panels[panel_id] = {
|
||
'node': panel_node,
|
||
'title_text': title_text,
|
||
'content_text': content_text,
|
||
'title_node': title_text_node,
|
||
'content_node': content_text_node,
|
||
'properties': {
|
||
'size': size,
|
||
'position': position,
|
||
'title_color': title_color,
|
||
'content_color': content_color,
|
||
'font': font
|
||
}
|
||
}
|
||
|
||
# 设置GUI类型标记和支持3D编辑的标记
|
||
panel_node.setTag("gui_type", "info_panel_3d")
|
||
panel_node.setTag("panel_id", panel_id)
|
||
panel_node.setTag("is_gui_element", "1") # 添加此标记确保节点被识别为GUI元素
|
||
panel_node.setTag("is_scene_element", "1") # 添加此标记确保节点被识别为场景元素
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
panel_node.setTag("tree_item_type", "INFO_PANEL_3D") # 添加树节点类型标记
|
||
|
||
# 如果有背景图片,保存背景图片路径
|
||
if bg_image:
|
||
panel_node.setTag("bg_image_path", bg_image)
|
||
|
||
if not visible:
|
||
panel_node.hide()
|
||
|
||
# 将面板添加到场景树
|
||
#self._addPanelToSceneTree(panel_node, panel_id)
|
||
if hasattr(self.world, 'gui_elements'):
|
||
self.world.gui_elements.append(panel_node)
|
||
|
||
return panel_node
|
||
|
||
def update3DPanelContent(self, panel_id, title=None, content=None):
|
||
"""
|
||
更新3D面板内容
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
panel_data = self.panels[panel_id]
|
||
|
||
if title is not None and 'title_node' in panel_data:
|
||
panel_data['title_node'].setText(title)
|
||
|
||
if content is not None and 'content_node' in panel_data:
|
||
panel_data['content_node'].setText(content)
|
||
|
||
return True
|
||
|
||
def update3DPanelProperties(self, panel_id, **properties):
|
||
"""
|
||
更新3D面板属性
|
||
"""
|
||
if panel_id not in self.panels:
|
||
print(f"面板 {panel_id} 不存在")
|
||
return False
|
||
|
||
panel_data = self.panels[panel_id]
|
||
props = panel_data['properties']
|
||
|
||
# 更新位置
|
||
if 'position' in properties:
|
||
pos = properties['position']
|
||
panel_data['node'].setPos(pos[0], pos[1], pos[2])
|
||
props['position'] = pos
|
||
|
||
# 更新大小
|
||
if 'size' in properties:
|
||
size = properties['size']
|
||
props['size'] = size
|
||
|
||
# 由于3D面板使用CardMaker创建,需要重新创建几何体
|
||
print("注意:3D面板大小调整需要重新创建面板几何体")
|
||
|
||
# 更新背景颜色
|
||
if 'bg_color' in properties:
|
||
bg_color = properties['bg_color']
|
||
if 'panel_bg' in panel_data:
|
||
from panda3d.core import Material
|
||
material = Material()
|
||
material.setDiffuse(Vec4(*bg_color))
|
||
material.setAmbient(Vec4(*bg_color[:3], 1.0))
|
||
panel_data['panel_bg'].setMaterial(material, 1)
|
||
props['bg_color'] = bg_color
|
||
|
||
# 更新边框颜色
|
||
if 'border_color' in properties:
|
||
border_color = properties['border_color']
|
||
from panda3d.core import Material
|
||
border_mat = Material()
|
||
border_mat.setDiffuse(Vec4(*border_color))
|
||
for border in panel_data['borders'].values():
|
||
border.setMaterial(border_mat, 1)
|
||
props['border_color'] = border_color
|
||
|
||
# 更新标题颜色
|
||
if 'title_color' in properties:
|
||
title_color = properties['title_color']
|
||
if 'title_node' in panel_data:
|
||
panel_data['title_node'].setTextColor(*title_color)
|
||
props['title_color'] = title_color
|
||
|
||
# 更新内容颜色
|
||
if 'content_color' in properties:
|
||
content_color = properties['content_color']
|
||
if 'content_node' in panel_data:
|
||
panel_data['content_node'].setTextColor(*content_color)
|
||
props['content_color'] = content_color
|
||
|
||
# 更新标题
|
||
if 'title' in properties:
|
||
if 'title_node' in panel_data:
|
||
panel_data['title_node'].setText(properties['title'])
|
||
|
||
# 更新内容
|
||
if 'content' in properties:
|
||
if 'content_node' in panel_data:
|
||
panel_data['content_node'].setText(properties['content'])
|
||
|
||
# 更新字体大小
|
||
if 'title_size' in properties:
|
||
if 'title_text' in panel_data:
|
||
panel_data['title_text'].setScale(properties['title_size'])
|
||
props['title_size'] = properties['title_size']
|
||
|
||
if 'content_size' in properties:
|
||
if 'content_text' in panel_data:
|
||
panel_data['content_text'].setScale(properties['content_size'])
|
||
props['content_size'] = properties['content_size']
|
||
|
||
return True
|
||
|
||
def create3DHTTPInfoPanel(self, panel_id, url, method="GET", headers=None, data=None,
|
||
position=(0, 0, 0), size=(1.0, 0.6),
|
||
bg_color=(0.15, 0.15, 0.15, 0.9),
|
||
border_color=(0.3, 0.3, 0.3, 1.0),
|
||
title_color=(1.0, 1.0, 1.0, 1.0),
|
||
content_color=(0.9, 0.9, 0.9, 1.0),
|
||
update_interval=30.0, font=None):
|
||
"""
|
||
创建3D HTTP信息面板
|
||
"""
|
||
# 创建面板
|
||
domain = urlparse(url).netloc or url[:30]
|
||
title = f"HTTP数据: {domain}"
|
||
|
||
panel_node = self.create3DInfoPanel(
|
||
panel_id=panel_id,
|
||
position=position,
|
||
size=size,
|
||
bg_color=bg_color,
|
||
border_color=border_color,
|
||
title_color=title_color,
|
||
content_color=content_color,
|
||
font=font
|
||
)
|
||
|
||
# 更新标题
|
||
self.update3DPanelContent(panel_id, title=title)
|
||
|
||
# 立即获取并显示数据
|
||
content = fetchHTTPData(url, method, headers, data)
|
||
self.update3DPanelContent(panel_id, content=content)
|
||
|
||
# 注册数据源,定期更新
|
||
def http_data_callback():
|
||
return fetchHTTPData(url, method, headers, data)
|
||
|
||
self.registerDataSource(panel_id, http_data_callback, update_interval)
|
||
|
||
# 保存HTTP请求信息,便于后续更新
|
||
if panel_id not in self.data_sources:
|
||
self.data_sources[panel_id] = {}
|
||
self.data_sources[panel_id]['http_info'] = {
|
||
'url': url,
|
||
'method': method,
|
||
'headers': headers,
|
||
'data': data
|
||
}
|
||
|
||
return panel_node
|
||
|
||
# 在 add_methods_to_property_panel 函数中添加以下方法
|
||
|
||
def add_methods_to_property_panel(property_panel_instance):
|
||
# ... (原有代码保持不变)
|
||
import types
|
||
|
||
def createHTTPInfoPanel(self, url, panel_id="http_info", method="GET", headers=None, data=None,
|
||
position=(0.8, 0.0), size=(0.4, 0.3), update_interval=30.0):
|
||
"""
|
||
为属性面板创建HTTP信息面板
|
||
url: 请求的URL
|
||
panel_id: 面板ID
|
||
method: HTTP方法
|
||
headers: 请求头
|
||
data: POST数据
|
||
position: 位置
|
||
size: 大小
|
||
update_interval: 更新间隔(秒)
|
||
"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 创建HTTP信息面板
|
||
panel_node = self.info_panel_manager.createHTTPInfoPanel(
|
||
panel_id=panel_id,
|
||
url=url,
|
||
method=method,
|
||
headers=headers,
|
||
data=data,
|
||
position=position,
|
||
size=size,
|
||
update_interval=update_interval,
|
||
bg_color=(0.15, 0.25, 0.35, 0.95), # 蓝色背景
|
||
border_color=(0.3, 0.5, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 设置标签
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
print(f"✓ 已创建HTTP信息面板: {url}")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建HTTP信息面板失败: {e}")
|
||
return None
|
||
|
||
def updateHTTPInfoPanel(self, panel_id, url=None, method=None, headers=None, data=None):
|
||
"""
|
||
更新HTTP信息面板
|
||
"""
|
||
try:
|
||
if hasattr(self, 'info_panel_manager'):
|
||
return self.info_panel_manager.updateHTTPInfoPanel(panel_id, url, method, headers, data)
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 更新HTTP信息面板失败: {e}")
|
||
return False
|
||
|
||
def create3DRealtimeDataPanel(self, data_callback=None, update_interval=1.0):
|
||
"""创建3D实时数据面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 创建3D实时数据面板
|
||
panel_node = self.info_panel_manager.create3DInfoPanel(
|
||
panel_id="realtime_data_3d",
|
||
position=(0, 0, 0),
|
||
size=(0.35, 0.3),
|
||
bg_color=(0.15, 0.25, 0.35, 0.95), # 蓝色背景
|
||
border_color=(0.3, 0.5, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 设置标签
|
||
panel_node.setTag("element_type", "info_panel_3d")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
# 如果提供了数据回调函数,则注册数据源
|
||
if data_callback:
|
||
# 立即显示初始数据
|
||
initial_data = data_callback()
|
||
self.info_panel_manager.update3DPanelContent("realtime_data_3d", content=initial_data)
|
||
|
||
# 注册数据源
|
||
self.info_panel_manager.registerDataSource("realtime_data_3d", data_callback, update_interval)
|
||
else:
|
||
# 使用默认数据
|
||
default_data = "等待数据..."
|
||
self.info_panel_manager.update3DPanelContent("realtime_data_3d", content=default_data)
|
||
|
||
print("✓ 已创建3D实时数据面板")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建3D实时数据面板失败: {e}")
|
||
return None
|
||
|
||
def create3DModelInfoPanel(self, model):
|
||
"""为模型创建3D信息面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 获取模型信息
|
||
model_name = model.getName() if hasattr(model, 'getName') else 'Unknown'
|
||
num_children = model.getNumChildren() if hasattr(model, 'getNumChildren') else 0
|
||
|
||
# 创建面板内容
|
||
content = f"模型名称: {model_name}\n子节点数: {num_children}\n类型: {type(model).__name__}"
|
||
|
||
# 创建或更新面板
|
||
panel_node = self.info_panel_manager.create3DInfoPanel(
|
||
panel_id="model_info_3d",
|
||
position=(2, 0, 0), # 默认放在模型旁边
|
||
size=(0.35, 0.25),
|
||
bg_color=(0.15, 0.15, 0.25, 0.95), # 蓝紫色背景
|
||
border_color=(0.3, 0.3, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.5, 0.8, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 更新面板内容为模型特定信息
|
||
self.info_panel_manager.update3DPanelContent("model_info_3d",
|
||
title="模型信息",
|
||
content=content)
|
||
|
||
# 设置标签
|
||
panel_node.setTag("element_type", "info_panel_3d")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
print(f"✓ 已创建3D模型信息面板: {model_name}")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建3D模型信息面板失败: {e}")
|
||
return None
|
||
|
||
# 在绑定方法的部分添加3D面板方法
|
||
property_panel_instance.create3DRealtimeDataPanel = types.MethodType(create3DRealtimeDataPanel,
|
||
property_panel_instance)
|
||
property_panel_instance.create3DModelInfoPanel = types.MethodType(create3DModelInfoPanel,
|
||
property_panel_instance)
|
||
|
||
# 将新方法绑定到实例
|
||
# ... (原有绑定保持不变)
|
||
property_panel_instance.createHTTPInfoPanel = types.MethodType(createHTTPInfoPanel, property_panel_instance)
|
||
property_panel_instance.updateHTTPInfoPanel = types.MethodType(updateHTTPInfoPanel, property_panel_instance)
|
||
|
||
def serializePanelData(self, panel_id):
|
||
"""序列化面板数据用于保存"""
|
||
if panel_id not in self.panels:
|
||
return None
|
||
|
||
panel_data = self.panels[panel_id]
|
||
props = panel_data['properties']
|
||
|
||
# 获取面板类型
|
||
panel_type = "2d"
|
||
if panel_data['node'].hasTag("gui_type"):
|
||
gui_type = panel_data['node'].getTag("gui_type")
|
||
if "3d" in gui_type.lower():
|
||
panel_type = "3d"
|
||
|
||
# 构建序列化数据
|
||
serialized_data = {
|
||
'panel_id': panel_id,
|
||
'panel_type': panel_type,
|
||
'position': props.get('position', (0, 0) if panel_type == "2d" else (0, 0, 0)),
|
||
'size': props.get('size', (1.0, 0.6)),
|
||
'bg_color': props.get('bg_color', (0.15, 0.15, 0.15, 0.9)),
|
||
'border_color': props.get('border_color', (0.3, 0.3, 0.3, 1.0)),
|
||
'title_color': props.get('title_color', (1.0, 1.0, 1.0, 1.0)),
|
||
'content_color': props.get('content_color', (0.9, 0.9, 0.9, 1.0)),
|
||
'title': panel_data['title_label'].getText() if 'title_label' in panel_data else "信息面板",
|
||
'content': panel_data[
|
||
'content_label'].getText() if 'content_label' in panel_data else "" if 'content_node' not in panel_data else
|
||
panel_data['content_node'].getText(),
|
||
'font_path': props.get('font', None),
|
||
'bg_image': props.get('bg_image', None),
|
||
'visible': not panel_data['node'].isHidden()
|
||
}
|
||
|
||
# 添加HTTP面板特有数据
|
||
if panel_id in self.data_sources and 'http_info' in self.data_sources[panel_id]:
|
||
serialized_data['http_info'] = self.data_sources[panel_id]['http_info']
|
||
|
||
return serialized_data
|
||
|
||
def getAllPanelData(self):
|
||
"""获取所有面板的序列化数据"""
|
||
panel_data_list = []
|
||
for panel_id in self.panels:
|
||
data = self.serializePanelData(panel_id)
|
||
if data:
|
||
panel_data_list.append(data)
|
||
return panel_data_list
|
||
|
||
def recreatePanelFromData(self, panel_data):
|
||
"""从序列化数据重新创建面板"""
|
||
try:
|
||
panel_id = panel_data['panel_id']
|
||
panel_type = panel_data['panel_type']
|
||
position = panel_data['position']
|
||
size = panel_data['size']
|
||
|
||
# 重建面板
|
||
if panel_type == "3d":
|
||
panel_node = self.create3DInfoPanel(
|
||
panel_id=panel_id,
|
||
position=position,
|
||
size=size,
|
||
bg_color=panel_data.get('bg_color', (0.15, 0.15, 0.15, 0.9)),
|
||
border_color=panel_data.get('border_color', (0.3, 0.3, 0.3, 1.0)),
|
||
title_color=panel_data.get('title_color', (1.0, 1.0, 1.0, 1.0)),
|
||
content_color=panel_data.get('content_color', (0.9, 0.9, 0.9, 1.0)),
|
||
visible=panel_data.get('visible', True),
|
||
font=panel_data.get('font_path', None),
|
||
bg_image=panel_data.get('bg_image', None)
|
||
)
|
||
|
||
# 更新内容
|
||
self.update3DPanelContent(
|
||
panel_id,
|
||
title=panel_data.get('title', '信息面板'),
|
||
content=panel_data.get('content', '')
|
||
)
|
||
else:
|
||
panel_node = self.createInfoPanel(
|
||
panel_id=panel_id,
|
||
position=(position[0], position[1]) if len(position) >= 2 else (0, 0),
|
||
size=size,
|
||
bg_color=panel_data.get('bg_color', (0.15, 0.15, 0.15, 0.9)),
|
||
border_color=panel_data.get('border_color', (0.3, 0.3, 0.3, 1.0)),
|
||
title_color=panel_data.get('title_color', (1.0, 1.0, 1.0, 1.0)),
|
||
content_color=panel_data.get('content_color', (0.9, 0.9, 0.9, 1.0)),
|
||
visible=panel_data.get('visible', True),
|
||
font=panel_data.get('font_path', None),
|
||
bg_image=panel_data.get('bg_image', None)
|
||
)
|
||
|
||
# 更新内容
|
||
self.updatePanelContent(
|
||
panel_id,
|
||
title=panel_data.get('title', '信息面板'),
|
||
content=panel_data.get('content', '')
|
||
)
|
||
|
||
# 设置标签
|
||
if panel_node:
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1")
|
||
|
||
# 如果是HTTP面板,重新注册数据源
|
||
if 'http_info' in panel_data:
|
||
http_info = panel_data['http_info']
|
||
self.createHTTPInfoPanel(
|
||
panel_id=panel_id,
|
||
url=http_info['url'],
|
||
method=http_info.get('method', 'GET'),
|
||
headers=http_info.get('headers', None),
|
||
data=http_info.get('data', None),
|
||
position=position,
|
||
size=size,
|
||
update_interval=self.data_sources.get(panel_id, {}).get('interval',
|
||
30.0) if panel_id in self.data_sources else 30.0
|
||
)
|
||
|
||
print(f"✓ 信息面板 {panel_id} 已重建")
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 重建信息面板 {panel_data.get('panel_id', 'unknown')} 失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def onCreateSampleInfoPanel(self):
|
||
"""创建示例天气信息面板(模拟数据)"""
|
||
try:
|
||
# 获取中文字体
|
||
from panda3d.core import TextNode
|
||
font = self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
|
||
# 使用唯一的面板ID
|
||
import time
|
||
unique_id = f"weather_info_{int(time.time())}"
|
||
|
||
# 创建示例面板
|
||
weather_panel = self.createInfoPanel(
|
||
panel_id=unique_id, # 使用唯一ID
|
||
position=(1.32, 0.68),
|
||
size=(1, 0.6),
|
||
bg_color=(0.15, 0.25, 0.35, 0), # 蓝色背景
|
||
border_color=(0.3, 0.5, 0.7, 0), # 蓝色边框
|
||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0),
|
||
font=font,
|
||
bg_image="/home/tiger/图片/内部信息框2@2x.png"
|
||
)
|
||
weather_panel.setTag("name",unique_id)
|
||
|
||
# 更新面板标题
|
||
self.updatePanelContent(unique_id, title="北京天气")
|
||
|
||
self._addPanelToSceneTree(weather_panel, unique_id)
|
||
# 立即显示加载中信息
|
||
self.updatePanelContent(unique_id, content="正在获取天气数据...")
|
||
|
||
self.registerDataSource(unique_id, self.getRealWeatherData, update_interval=5.0)
|
||
|
||
print("✓ 示例天气信息面板已创建")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建示例天气信息面板失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
QMessageBox.critical(self, "错误", f"创建示例天气信息面板时出错: {str(e)}")
|
||
|
||
def getRealWeatherData(self):
|
||
"""获取真实天气数据"""
|
||
try:
|
||
import requests
|
||
import json
|
||
from datetime import datetime
|
||
|
||
# 请求天气数据
|
||
url = "https://wttr.in/Beijing?format=j1"
|
||
response = requests.get(url, timeout=10)
|
||
response.raise_for_status()
|
||
|
||
# 解析JSON数据
|
||
weather_data = response.json()
|
||
|
||
# 提取当前天气信息
|
||
current_condition = weather_data['current_condition'][0]
|
||
weather_desc = current_condition['weatherDesc'][0]['value']
|
||
temp_c = current_condition['temp_C']
|
||
feels_like = current_condition['FeelsLikeC']
|
||
humidity = current_condition['humidity']
|
||
pressure = current_condition['pressure']
|
||
visibility = current_condition['visibility']
|
||
wind_speed = current_condition['windspeedKmph']
|
||
wind_dir = current_condition['winddir16Point']
|
||
|
||
# 提取空气质量(如果可用)
|
||
air_quality = "N/A"
|
||
if 'air_quality' in weather_data and weather_data['air_quality']:
|
||
if 'us-epa-index' in current_condition:
|
||
air_quality_index = current_condition['air_quality_index']
|
||
air_quality = f"指数: {air_quality_index}"
|
||
|
||
# 获取更新时间
|
||
update_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||
|
||
# 格式化显示内容
|
||
content = f"天气状况: {weather_desc}\n温度: {temp_c}°C (体感 {feels_like}°C)\n湿度: {humidity}%\n气压: {pressure} hPa\n能见度: {visibility} km\n风速: {wind_speed} km/h ({wind_dir})\n空气质量: {air_quality}\n更新时间: {update_time}"
|
||
|
||
return content
|
||
|
||
except requests.exceptions.Timeout:
|
||
return "错误: 获取天气数据超时"
|
||
except requests.exceptions.ConnectionError:
|
||
return "错误: 网络连接失败"
|
||
except requests.exceptions.HTTPError as e:
|
||
return f"HTTP错误: {e}"
|
||
except json.JSONDecodeError:
|
||
return "错误: 无法解析天气数据"
|
||
except KeyError as e:
|
||
return f"错误: 天气数据格式不正确 (缺少字段: {e})"
|
||
except Exception as e:
|
||
return f"获取天气数据失败: {str(e)}"
|
||
|
||
|
||
# 示例数据源函数
|
||
def getRealtimeData():
|
||
"""
|
||
获取实时数据的示例函数
|
||
"""
|
||
import random
|
||
from datetime import datetime
|
||
|
||
# 模拟实时数据
|
||
value1 = round(random.uniform(10, 100), 2)
|
||
value2 = round(random.uniform(0, 50), 2)
|
||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||
|
||
return f"实时值1: {value1}\n实时值2: {value2}\n更新时间: {timestamp}"
|
||
|
||
|
||
# 使用示例函数
|
||
def createSampleInfoPanel(parent_node):
|
||
"""创建示例信息面板"""
|
||
|
||
# 创建信息面板管理器
|
||
info_manager = InfoPanelManager(parent_node)
|
||
|
||
# 创建一个实时数据面板
|
||
info_manager.createInfoPanel(
|
||
panel_id="realtime_data",
|
||
position=(0.8, 0.0),
|
||
size=(0.35, 0.3),
|
||
bg_color=(0.15, 0.25, 0.35, 0.95), # 蓝色背景
|
||
border_color=(0.3, 0.5, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 立即显示初始数据
|
||
initial_data = getRealtimeData()
|
||
info_manager.updatePanelContent("realtime_data", content=initial_data)
|
||
|
||
# 注册数据源,每秒更新一次
|
||
info_manager.registerDataSource("realtime_data", getRealtimeData, update_interval=1.0)
|
||
|
||
return info_manager
|
||
|
||
|
||
# 集成到您的 property_panel.py 中的方法
|
||
def add_methods_to_property_panel(property_panel_instance):
|
||
"""
|
||
为 property_panel 实例添加 DirectGUI 信息面板支持
|
||
"""
|
||
import types
|
||
|
||
# 添加信息面板管理器作为类属性
|
||
if not hasattr(property_panel_instance, 'info_panel_manager'):
|
||
if hasattr(property_panel_instance, 'world') and hasattr(property_panel_instance.world, 'render'):
|
||
property_panel_instance.info_panel_manager = InfoPanelManager(property_panel_instance.world.render)
|
||
else:
|
||
property_panel_instance.info_panel_manager = InfoPanelManager()
|
||
|
||
def createRealtimeDataPanel(self, data_callback=None, update_interval=1.0):
|
||
"""创建实时数据面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 创建实时数据面板
|
||
panel_node = self.info_panel_manager.createInfoPanel(
|
||
panel_id="realtime_data",
|
||
position=(0.8, 0.0),
|
||
size=(0.35, 0.3),
|
||
bg_color=(0.15, 0.25, 0.35, 0.95), # 蓝色背景
|
||
border_color=(0.3, 0.5, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 设置标签
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
# 如果提供了数据回调函数,则注册数据源
|
||
if data_callback:
|
||
# 立即显示初始数据
|
||
initial_data = data_callback()
|
||
self.info_panel_manager.updatePanelContent("realtime_data", content=initial_data)
|
||
|
||
# 注册数据源
|
||
self.info_panel_manager.registerDataSource("realtime_data", data_callback, update_interval)
|
||
else:
|
||
# 使用默认数据
|
||
default_data = "等待数据..."
|
||
self.info_panel_manager.updatePanelContent("realtime_data", content=default_data)
|
||
|
||
print("✓ 已创建实时数据面板")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建实时数据面板失败: {e}")
|
||
return None
|
||
|
||
|
||
def createModelInfoPanel(self, model):
|
||
"""为模型创建信息面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 获取模型信息
|
||
model_name = model.getName() if hasattr(model, 'getName') else 'Unknown'
|
||
num_children = model.getNumChildren() if hasattr(model, 'getNumChildren') else 0
|
||
|
||
# 创建面板内容
|
||
content = f"模型名称: {model_name}\n子节点数: {num_children}\n类型: {type(model).__name__}"
|
||
|
||
# 创建或更新面板
|
||
panel_node = self.info_panel_manager.createInfoPanel(
|
||
panel_id="model_info",
|
||
position=(0.8, 0.7),
|
||
size=(0.35, 0.25),
|
||
bg_color=(0.15, 0.15, 0.25, 0.95), # 蓝紫色背景
|
||
border_color=(0.3, 0.3, 0.7, 1.0), # 蓝色边框
|
||
title_color=(0.5, 0.8, 1.0, 1.0), # 浅蓝色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 更新面板内容为模型特定信息
|
||
self.info_panel_manager.updatePanelContent("model_info",
|
||
title="模型信息",
|
||
content=content)
|
||
|
||
# 设置标签
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
print(f"✓ 已创建模型信息面板: {model_name}")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建模型信息面板失败: {e}")
|
||
return None
|
||
|
||
def createTerrainInfoPanel(self, terrain_info):
|
||
"""为地形创建信息面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 获取地形信息
|
||
terrain_name = terrain_info.get('name', 'Unknown')
|
||
width = terrain_info.get('width', 0)
|
||
height = terrain_info.get('height', 0)
|
||
resolution = terrain_info.get('resolution', 0)
|
||
|
||
# 创建面板内容
|
||
content = f"地形名称: {terrain_name}\n大小: {width} x {height}\n分辨率: {resolution}"
|
||
|
||
# 创建或更新面板
|
||
panel_node = self.info_panel_manager.createInfoPanel(
|
||
panel_id="terrain_info",
|
||
position=(0.8, 0.7),
|
||
size=(0.35, 0.25),
|
||
bg_color=(0.15, 0.25, 0.15, 0.95), # 绿色背景
|
||
border_color=(0.3, 0.7, 0.3, 1.0), # 绿色边框
|
||
title_color=(0.5, 1.0, 0.5, 1.0), # 浅绿色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 更新面板内容为地形特定信息
|
||
self.info_panel_manager.updatePanelContent("terrain_info",
|
||
title="地形信息",
|
||
content=content)
|
||
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
print(f"✓ 已创建地形信息面板: {terrain_name}")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建地形信息面板失败: {e}")
|
||
return None
|
||
|
||
def createLightInfoPanel(self, light_object):
|
||
"""为光源创建信息面板"""
|
||
try:
|
||
# 确保父节点已设置
|
||
if self.info_panel_manager.parent is None and hasattr(self, 'world'):
|
||
self.info_panel_manager.setParent(self.world.render)
|
||
|
||
# 获取光源信息
|
||
light_type = type(light_object).__name__
|
||
casts_shadows = getattr(light_object, 'casts_shadows', 'Unknown')
|
||
shadow_res = getattr(light_object, 'shadow_map_resolution', 'Unknown')
|
||
|
||
# 创建面板内容
|
||
content = f"光源类型: {light_type}\n投射阴影: {casts_shadows}\n阴影分辨率: {shadow_res}"
|
||
|
||
# 创建或更新面板
|
||
panel_node = self.info_panel_manager.createInfoPanel(
|
||
panel_id="light_info",
|
||
position=(0.8, 0.7),
|
||
size=(0.35, 0.25),
|
||
bg_color=(0.25, 0.25, 0.15, 0.95), # 黄色背景
|
||
border_color=(0.7, 0.7, 0.3, 1.0), # 黄色边框
|
||
title_color=(1.0, 1.0, 0.5, 1.0), # 浅黄色标题
|
||
content_color=(0.95, 0.95, 0.95, 1.0)
|
||
)
|
||
|
||
# 更新面板内容为光源特定信息
|
||
self.info_panel_manager.updatePanelContent("light_info",
|
||
title="光源信息",
|
||
content=content)
|
||
|
||
panel_node.setTag("element_type", "info_panel")
|
||
panel_node.setTag("is_scene_element", "1")
|
||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||
|
||
print(f"✓ 已创建光源信息面板")
|
||
|
||
return panel_node
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建光源信息面板失败: {e}")
|
||
return None
|
||
|
||
def removeInfoPanel(self, panel_id="model_info"):
|
||
"""移除信息面板"""
|
||
if hasattr(self, 'info_panel_manager'):
|
||
self.info_panel_manager.removePanel(panel_id)
|
||
|
||
def setupInfoPanelManager(self):
|
||
"""初始化信息面板管理器"""
|
||
if not hasattr(self, 'info_panel_manager'):
|
||
if hasattr(self, 'world') and hasattr(self.world, 'render'):
|
||
self.info_panel_manager = InfoPanelManager(self.world.render)
|
||
else:
|
||
self.info_panel_manager = InfoPanelManager()
|
||
|
||
# 将方法绑定到实例
|
||
import types
|
||
property_panel_instance.createRealtimeDataPanel = types.MethodType(createRealtimeDataPanel, property_panel_instance)
|
||
property_panel_instance.createModelInfoPanel = types.MethodType(createModelInfoPanel, property_panel_instance)
|
||
property_panel_instance.createTerrainInfoPanel = types.MethodType(createTerrainInfoPanel, property_panel_instance)
|
||
property_panel_instance.createLightInfoPanel = types.MethodType(createLightInfoPanel, property_panel_instance)
|
||
property_panel_instance.removeInfoPanel = types.MethodType(removeInfoPanel, property_panel_instance)
|
||
property_panel_instance.setupInfoPanelManager = types.MethodType(setupInfoPanelManager, property_panel_instance)
|
||
|
||
def fetchHTTPData(url, method="GET", headers=None, data=None, timeout=5):
|
||
"""
|
||
获取HTTP数据的通用函数
|
||
url: 请求的URL
|
||
method: HTTP方法 (GET, POST, etc.)
|
||
headers: 请求头
|
||
data: POST数据
|
||
timeout: 超时时间(秒)
|
||
"""
|
||
try:
|
||
if headers is None:
|
||
headers = {
|
||
'User-Agent': 'InfoPanelManager/1.0',
|
||
'Accept': 'application/json,text/plain,*/*'
|
||
}
|
||
|
||
# 根据方法发送请求
|
||
if method.upper() == "GET":
|
||
response = requests.get(url, headers=headers, timeout=timeout)
|
||
elif method.upper() == "POST":
|
||
response = requests.post(url, headers=headers, data=data, timeout=timeout)
|
||
else:
|
||
response = requests.request(method, url, headers=headers, data=data, timeout=timeout)
|
||
|
||
# 检查响应状态
|
||
response.raise_for_status()
|
||
|
||
# 尝试解析JSON
|
||
try:
|
||
json_data = response.json()
|
||
return formatJSONData(json_data)
|
||
except:
|
||
# 如果不是JSON,返回文本内容
|
||
return response.text[:500] # 限制长度
|
||
|
||
except requests.exceptions.Timeout:
|
||
return "错误: 请求超时"
|
||
except requests.exceptions.ConnectionError:
|
||
return "错误: 连接失败"
|
||
except requests.exceptions.HTTPError as e:
|
||
return f"HTTP错误: {e}"
|
||
except Exception as e:
|
||
return f"获取数据失败: {str(e)}"
|
||
|
||
|
||
def formatJSONData(data, indent=0):
|
||
"""
|
||
格式化JSON数据为易读的文本
|
||
"""
|
||
if isinstance(data, dict):
|
||
lines = []
|
||
for key, value in data.items():
|
||
if isinstance(value, (dict, list)):
|
||
lines.append(f"{' ' * indent}{key}:")
|
||
lines.append(formatJSONData(value, indent + 1))
|
||
else:
|
||
lines.append(f"{' ' * indent}{key}: {value}")
|
||
return "\n".join(lines)
|
||
elif isinstance(data, list):
|
||
lines = []
|
||
for i, item in enumerate(data):
|
||
if isinstance(item, (dict, list)):
|
||
lines.append(f"{' ' * indent}[{i}]:")
|
||
lines.append(formatJSONData(item, indent + 1))
|
||
else:
|
||
lines.append(f"{' ' * indent}[{i}] {item}")
|
||
return "\n".join(lines)
|
||
else:
|
||
return str(data)
|