forked from Rowland/EG
1055 lines
39 KiB
Python
1055 lines
39 KiB
Python
# 修改后的 InfoPanelManager.py
|
||
from xml.sax.handler import property_encoding
|
||
|
||
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"info_panel_{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=500, # 设置一个非常大的值,几乎不自动换行
|
||
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位置编辑
|
||
|
||
if not visible:
|
||
panel_node.hide()
|
||
|
||
return panel_node
|
||
|
||
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
|
||
|
||
def registerDataSource(self, panel_id, data_callback, update_interval=1.0):
|
||
"""
|
||
注册数据源,定期更新面板内容
|
||
data_callback: 返回数据的回调函数
|
||
update_interval: 更新间隔(秒)
|
||
"""
|
||
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
|
||
}
|
||
|
||
self.data_sources[panel_id] = data_source
|
||
|
||
# 启动数据更新线程
|
||
thread = threading.Thread(target=self._updateDataThread, args=(panel_id,), daemon=True)
|
||
thread.start()
|
||
|
||
return True
|
||
|
||
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:
|
||
self.updatePanelContent(panel_id, content=data)
|
||
|
||
# 等待下次更新
|
||
interval = self.data_sources[panel_id]['interval']
|
||
time.sleep(interval)
|
||
|
||
except Exception as e:
|
||
print(f"更新面板 {panel_id} 数据时出错: {e}")
|
||
time.sleep(1.0) # 出错时等待1秒再重试
|
||
|
||
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'] = 500
|
||
print(f"更新面板换行: 设置为500(几乎不换行)")
|
||
|
||
# 如果有背景图片,也需要更新其大小
|
||
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']
|
||
# 当字体大小改变时,仍然保持较大的换行值
|
||
panel_data['content_label']['text_wordwrap'] = 500
|
||
|
||
# 更新背景图片
|
||
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
|
||
|
||
# 在 add_methods_to_property_panel 函数中添加以下方法
|
||
|
||
def add_methods_to_property_panel(property_panel_instance):
|
||
# ... (原有代码保持不变)
|
||
|
||
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
|
||
|
||
# 将新方法绑定到实例
|
||
import types
|
||
# ... (原有绑定保持不变)
|
||
property_panel_instance.createHTTPInfoPanel = types.MethodType(createHTTPInfoPanel, property_panel_instance)
|
||
property_panel_instance.updateHTTPInfoPanel = types.MethodType(updateHTTPInfoPanel, property_panel_instance)
|
||
|
||
|
||
# 示例数据源函数
|
||
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 信息面板支持
|
||
"""
|
||
|
||
# 添加信息面板管理器作为类属性
|
||
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)
|