1
0
forked from Rowland/EG
EG/core/InfoPanelManager.py

1694 lines
66 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 修改后的 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)