forked from Rowland/EG
2582 lines
108 KiB
Python
2582 lines
108 KiB
Python
"""
|
||
GUI元素管理模块
|
||
|
||
负责2D/3D GUI元素的管理:
|
||
- GUI元素的创建(按钮、标签、输入框等)
|
||
- GUI编辑模式
|
||
- GUI属性编辑
|
||
- GUI元素的复制和删除
|
||
"""
|
||
|
||
from direct.gui.DirectGui import *
|
||
from panda3d.core import *
|
||
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||
QDoubleSpinBox, QPushButton, QDialogButtonBox,
|
||
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout, QGridLayout, QSpinBox)
|
||
from PyQt5.QtGui import QColor
|
||
from PyQt5.QtCore import Qt
|
||
# 尝试导入 QtWebEngineWidgets,如果失败则设置为 None
|
||
try:
|
||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||
WEB_ENGINE_AVAILABLE = True
|
||
except ImportError:
|
||
QWebEngineView = None
|
||
WEB_ENGINE_AVAILABLE = False
|
||
print("⚠️ QtWebEngineWidgets 不可用,Cesium 集成功能将被禁用")
|
||
|
||
|
||
class GUIManager:
|
||
"""GUI元素管理系统类"""
|
||
|
||
def __init__(self, world):
|
||
"""初始化GUI管理系统
|
||
|
||
Args:
|
||
world: 核心世界对象引用
|
||
"""
|
||
self.world = world
|
||
|
||
# GUI元素列表
|
||
self.gui_elements = []
|
||
|
||
#光源列表
|
||
self.light_elements = []
|
||
|
||
# GUI编辑模式状态
|
||
self.guiEditMode = False
|
||
self.guiEditPanel = None
|
||
self.guiPreviewWindow = None
|
||
self.currentGUITool = None
|
||
|
||
print("✓ GUI管理系统初始化完成")
|
||
|
||
# ==================== GUI元素创建方法 ====================
|
||
|
||
def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1):
|
||
"""创建2D GUI按钮 - 支持多选创建和GUI父子关系,优化版本"""
|
||
try:
|
||
from direct.gui.DirectGui import DirectButton
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"🔘 开始创建GUI按钮,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_buttons = []
|
||
|
||
# 为每个有效的父节点创建GUI按钮
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
button_name = f"GUIButton_{len(self.gui_elements)}"
|
||
|
||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||
if tree_widget.is_gui_element(parent_node):
|
||
# 父节点是GUI元素 - 作为子GUI挂载
|
||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||
parent_gui_node = parent_node # 直接挂载到GUI元素
|
||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||
else:
|
||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||
parent_gui_node = None # 使用默认的aspect2d
|
||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||
|
||
button = DirectButton(
|
||
text=text,
|
||
pos=gui_pos,
|
||
scale=size,
|
||
command=self.onGUIButtonClick,
|
||
extraArgs=[f"button_{len(self.gui_elements)}"],
|
||
frameColor=(0.2, 0.6, 0.8, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None,
|
||
rolloverSound=None,
|
||
clickSound=None,
|
||
parent=parent_gui_node # 设置GUI父节点
|
||
)
|
||
|
||
# 设置节点标签
|
||
button.setTag("gui_type", "button")
|
||
button.setTag("gui_id", f"button_{len(self.gui_elements)}")
|
||
button.setTag("gui_text", text)
|
||
button.setTag("is_gui_element", "1")
|
||
button.setTag("is_scene_element", "1")
|
||
button.setTag("created_by_user", "1")
|
||
button.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||
button.setName(button_name)
|
||
|
||
# 如果有GUI父节点,建立引用关系
|
||
if parent_gui_node:
|
||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||
button.setTag("gui_parent_id", parent_id)
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(button)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建GUI按钮成功: {button_name}")
|
||
|
||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(button, parent_item, "GUI_BUTTON")
|
||
if qt_item:
|
||
created_buttons.append((button, qt_item))
|
||
else:
|
||
created_buttons.append((button, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建GUI按钮失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_buttons:
|
||
print("❌ 没有成功创建任何GUI按钮")
|
||
return None
|
||
|
||
# 选中最后创建的按钮并更新场景树
|
||
if created_buttons:
|
||
last_button, last_qt_item = created_buttons[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_button, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_buttons)} 个GUI按钮")
|
||
|
||
# 返回值处理
|
||
if len(created_buttons) == 1:
|
||
return created_buttons[0][0]
|
||
else:
|
||
return [button for button, _ in created_buttons]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建GUI按钮过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
|
||
"""创建2D GUI标签 - 支持多选创建和GUI父子关系,优化版本"""
|
||
try:
|
||
from direct.gui.DirectGui import DirectLabel
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"🏷️ 开始创建GUI标签,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_labels = []
|
||
|
||
# 为每个有效的父节点创建GUI标签
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
label_name = f"GUILabel_{len(self.gui_elements)}"
|
||
|
||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||
if tree_widget.is_gui_element(parent_node):
|
||
# 父节点是GUI元素 - 作为子GUI挂载
|
||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||
parent_gui_node = parent_node
|
||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||
else:
|
||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||
parent_gui_node = None
|
||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||
|
||
label = DirectLabel(
|
||
text=text,
|
||
pos=gui_pos,
|
||
scale=size,
|
||
frameColor=(0, 0, 0, 0), # 透明背景
|
||
text_fg=(1, 1, 1, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None,
|
||
parent=parent_gui_node # 设置GUI父节点
|
||
)
|
||
|
||
# 设置节点标签
|
||
label.setTag("gui_type", "label")
|
||
label.setTag("gui_id", f"label_{len(self.gui_elements)}")
|
||
label.setTag("gui_text", text)
|
||
label.setTag("is_gui_element", "1")
|
||
label.setTag("is_scene_element", "1")
|
||
label.setTag("created_by_user", "1")
|
||
label.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||
label.setName(label_name)
|
||
|
||
# 如果有GUI父节点,建立引用关系
|
||
if parent_gui_node:
|
||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||
label.setTag("gui_parent_id", parent_id)
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(label)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建GUI标签成功: {label_name}")
|
||
|
||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(label, parent_item, "GUI_LABEL")
|
||
if qt_item:
|
||
created_labels.append((label, qt_item))
|
||
else:
|
||
created_labels.append((label, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建GUI标签失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_labels:
|
||
print("❌ 没有成功创建任何GUI标签")
|
||
return None
|
||
|
||
# 选中最后创建的标签并更新场景树
|
||
if created_labels:
|
||
last_label, last_qt_item = created_labels[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_label, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_labels)} 个GUI标签")
|
||
|
||
# 返回值处理
|
||
if len(created_labels) == 1:
|
||
return created_labels[0][0]
|
||
else:
|
||
return [label for label, _ in created_labels]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建GUI标签过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
|
||
"""创建2D GUI文本输入框 - 支持多选创建和GUI父子关系,优化版本"""
|
||
try:
|
||
from direct.gui.DirectGui import DirectEntry
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"📝 开始创建GUI输入框,位置: {pos}, 占位符: {placeholder}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_entries = []
|
||
|
||
# 为每个有效的父节点创建GUI输入框
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
entry_name = f"GUIEntry_{len(self.gui_elements)}"
|
||
|
||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||
if tree_widget.is_gui_element(parent_node):
|
||
# 父节点是GUI元素 - 作为子GUI挂载
|
||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||
parent_gui_node = parent_node
|
||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||
else:
|
||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||
parent_gui_node = None
|
||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||
|
||
entry = DirectEntry(
|
||
text="",
|
||
pos=gui_pos,
|
||
scale=size,
|
||
command=self.onGUIEntrySubmit,
|
||
extraArgs=[f"entry_{len(self.gui_elements)}"],
|
||
initialText=placeholder,
|
||
numLines=1,
|
||
width=12,
|
||
focus=0,
|
||
parent=parent_gui_node # 设置GUI父节点
|
||
)
|
||
|
||
# 设置节点标签
|
||
entry.setTag("gui_type", "entry")
|
||
entry.setTag("gui_id", f"entry_{len(self.gui_elements)}")
|
||
entry.setTag("gui_placeholder", placeholder)
|
||
entry.setTag("is_gui_element", "1")
|
||
entry.setTag("is_scene_element", "1")
|
||
entry.setTag("created_by_user", "1")
|
||
entry.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||
entry.setName(entry_name)
|
||
|
||
# 如果有GUI父节点,建立引用关系
|
||
if parent_gui_node:
|
||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||
entry.setTag("gui_parent_id", parent_id)
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(entry)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建GUI输入框成功: {entry_name}")
|
||
|
||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(entry, parent_item, "GUI_ENTRY")
|
||
if qt_item:
|
||
created_entries.append((entry, qt_item))
|
||
else:
|
||
created_entries.append((entry, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建GUI输入框失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_entries:
|
||
print("❌ 没有成功创建任何GUI输入框")
|
||
return None
|
||
|
||
# 选中最后创建的输入框并更新场景树
|
||
if created_entries:
|
||
last_entry, last_qt_item = created_entries[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_entry, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_entries)} 个GUI输入框")
|
||
|
||
# 返回值处理
|
||
if len(created_entries) == 1:
|
||
return created_entries[0][0]
|
||
else:
|
||
return [entry for entry, _ in created_entries]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建GUI输入框过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
|
||
"""创建2D GUI图片"""
|
||
try:
|
||
from direct.gui.DirectGui import DirectButton
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"🔘 开始创建GUI按钮,位置: {pos}, 图片路径: {image_path}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 使用CustomTreeWidget的方法获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_gui_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_2dimage = []
|
||
|
||
# 为每个有效的父节点创建GUI按钮
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
image_name = f"GUIImage_{len(self.gui_elements)}"
|
||
|
||
# 使用CustomTreeWidget的方法判断父节点类型并设置相应的挂载方式
|
||
if tree_widget.is_gui_element(parent_node):
|
||
# 父节点是GUI元素 - 作为子GUI挂载
|
||
gui_pos = tree_widget.calculate_relative_gui_position(pos, parent_node)
|
||
parent_gui_node = parent_node # 直接挂载到GUI元素
|
||
print(f"📎 挂载到GUI父节点: {parent_node.getName()}")
|
||
else:
|
||
# 父节点是普通3D节点 - 使用屏幕坐标
|
||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||
parent_gui_node = None # 使用默认的aspect2d
|
||
print(f"📎 挂载到3D父节点: {parent_item.text(0)}")
|
||
|
||
# 使用CardMaker创建一个更可靠的图片框架
|
||
cm = CardMaker('gui-2d-image')
|
||
cm.setFrame(-size, size, -size, size)
|
||
|
||
# image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||
if parent_gui_node:
|
||
image_node = parent_gui_node.attachNewNode(cm.generate())
|
||
else:
|
||
image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||
image_node.setPos(gui_pos)
|
||
image_node.setBin('fixed', 0)
|
||
image_node.setDepthWrite(False)
|
||
image_node.setDepthTest(False)
|
||
image_node.setColor(1, 1, 1, 1)
|
||
|
||
# 设置透明度支持
|
||
image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 如果提供了图像路径,则加载纹理
|
||
if image_path:
|
||
try:
|
||
texture = self.world.loader.loadTexture(image_path)
|
||
if texture:
|
||
image_node.setTexture(texture, 1)
|
||
texture.setWrapU(Texture.WM_clamp)
|
||
texture.setWrapV(Texture.WM_clamp)
|
||
texture.setMinfilter(Texture.FT_linear)
|
||
texture.setMagfilter(Texture.FT_linear)
|
||
image_node.setColor(1, 1, 1, 1)
|
||
else:
|
||
print(f"⚠️ 无法加载2D图片纹理: {image_path}")
|
||
except Exception as e:
|
||
print(f"❌ 加载2D图片纹理失败: {e}")
|
||
|
||
# 设置节点标签
|
||
image_node.setTag("gui_type", "2d_image")
|
||
image_node.setTag("gui_id", f"2d_image_{len(self.gui_elements)}")
|
||
image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}")
|
||
image_node.setTag("is_gui_element", "1")
|
||
image_node.setTag("is_scene_element", "1")
|
||
image_node.setTag("created_by_user", "1")
|
||
image_node.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
|
||
image_node.setName(image_name)
|
||
|
||
# 如果有GUI父节点,建立引用关系
|
||
if parent_gui_node:
|
||
parent_id = parent_gui_node.getTag("gui_id") if hasattr(parent_gui_node, 'getTag') else ""
|
||
image_node.setTag("gui_parent_id", parent_id)
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(image_node)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建GUI按钮成功: {image_name}")
|
||
|
||
# 使用CustomTreeWidget的方法在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(image_node, parent_item, "GUI_IMAGE")
|
||
if qt_item:
|
||
created_2dimage.append((image_node, qt_item))
|
||
else:
|
||
created_2dimage.append((image_node, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建GUI按钮失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_2dimage:
|
||
print("❌ 没有成功创建任何GUI按钮")
|
||
return None
|
||
|
||
# 选中最后创建的按钮并更新场景树
|
||
if created_2dimage:
|
||
last_button, last_qt_item = created_2dimage[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_button, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_2dimage)} 个GUI按钮")
|
||
|
||
# 返回值处理
|
||
if len(created_2dimage) == 1:
|
||
return created_2dimage[0][0]
|
||
else:
|
||
return [button for button, _ in created_2dimage]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建GUI按钮过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def constrain2DPosition(self,gui_element,new_x=None,new_z=None):
|
||
"""限制2dGUI元素位置在屏幕范围内"""
|
||
try:
|
||
from panda3d.core import Vec3
|
||
|
||
bounds = gui_element.getTightBounds()
|
||
element_width=0
|
||
element_height=0
|
||
|
||
if bounds:
|
||
min_point,max_point = bounds
|
||
element_width = (max_point.getX() - min_point.getX())/2
|
||
element_height = (max_point.getZ()-min_point.getZ())/2
|
||
|
||
#获取当前缩放
|
||
scale = gui_element.getScale()
|
||
if hasattr(scale,'getX'):
|
||
scale_x = scale.getX()
|
||
scale_z = scale.getZ() if hasattr(scale,'getZ') else scale_x
|
||
else:
|
||
scale_x = scale_z = scale if isinstance(scale,(int,float)) else 1.0
|
||
|
||
actual_width = element_width * scale_x
|
||
actual_height = element_height * scale_z
|
||
|
||
screen_width = 1.9
|
||
screen_height = 0.9
|
||
|
||
min_x = -screen_width + actual_width
|
||
max_x = screen_width - actual_width
|
||
min_z = -screen_height + actual_height
|
||
max_z = screen_height - actual_height
|
||
|
||
#获取当前位置
|
||
current_pos = gui_element.getPos()
|
||
x = new_x if new_x is not None else current_pos.getX()
|
||
z = new_z if new_z is not None else current_pos.getZ()
|
||
|
||
#应用边界限制
|
||
x = max(min_x,min(max_x,x))
|
||
z = max(min_z,min(max_z,z))
|
||
|
||
return Vec3(x,current_pos.getY(),z)
|
||
except Exception as e:
|
||
print(f"约束2D位置时出错: {e}")
|
||
# 出错时返回原始值
|
||
current_pos = gui_element.getPos()
|
||
x = new_x if new_x is not None else current_pos.getX()
|
||
z = new_z if new_z is not None else current_pos.getZ()
|
||
return Vec3(x, current_pos.getY(), z)
|
||
|
||
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
|
||
"""创建3D空间文本 - 支持多选创建,优化版本"""
|
||
try:
|
||
from panda3d.core import TextNode
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"📄 开始创建3D文本,位置: {pos}, 文本: {text}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_texts = []
|
||
|
||
# 为每个有效的父节点创建3D文本
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
text_name = f"GUI3DText_{len(self.gui_elements)}"
|
||
|
||
textNode = TextNode(f'3d-text-{len(self.gui_elements)}')
|
||
textNode.setText(text)
|
||
textNode.setAlign(TextNode.ACenter)
|
||
if self.world.getChineseFont():
|
||
textNode.setFont(self.world.getChineseFont())
|
||
|
||
# 挂载到选中的父节点
|
||
textNodePath = parent_node.attachNewNode(textNode)
|
||
textNodePath.setPos(*pos)
|
||
textNodePath.setScale(size)
|
||
textNodePath.setColor(1, 1, 0, 1)
|
||
textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||
textNodePath.setName(text_name)
|
||
|
||
# 设置节点标签
|
||
textNodePath.setTag("gui_type", "3d_text")
|
||
textNodePath.setTag("gui_id", f"3d_text_{len(self.gui_elements)}")
|
||
textNodePath.setTag("gui_text", text)
|
||
textNodePath.setTag("is_gui_element", "1")
|
||
textNodePath.setTag("is_scene_element", "1")
|
||
textNodePath.setTag("created_by_user", "1")
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(textNodePath)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建3D文本成功: {text_name}")
|
||
|
||
# 在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(textNodePath, parent_item, "GUI_3DTEXT")
|
||
if qt_item:
|
||
created_texts.append((textNodePath, qt_item))
|
||
else:
|
||
created_texts.append((textNodePath, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建3D文本失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_texts:
|
||
print("❌ 没有成功创建任何3D文本")
|
||
return None
|
||
|
||
# 选中最后创建的文本并更新场景树
|
||
if created_texts:
|
||
last_text, last_qt_item = created_texts[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_text, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_texts)} 个3D文本")
|
||
|
||
# 返回值处理
|
||
if len(created_texts) == 1:
|
||
return created_texts[0][0]
|
||
else:
|
||
return [text_np for text_np, _ in created_texts]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建3D文本过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
|
||
"""创建3D空间图片"""
|
||
try:
|
||
from panda3d.core import TextNode
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"📄 开始创建3D文本,位置: {pos}, 3D图片位置: {image_path}, 尺寸: {size}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_3dimage = []
|
||
|
||
# 为每个有效的父节点创建3D文本
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
image_name = f"GUI3DImage_{len(self.gui_elements)}"
|
||
|
||
# 参数类型检查和转换
|
||
if isinstance(size, (list, tuple)):
|
||
if len(size) >= 2:
|
||
x_size, y_size = float(size[0]), float(size[1])
|
||
else:
|
||
x_size = y_size = float(size[0]) if size else 1.0
|
||
else:
|
||
x_size = y_size = float(size)
|
||
|
||
# 创建卡片
|
||
cm = CardMaker('gui_3d_image')
|
||
cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2)
|
||
|
||
# 创建3D图像节点
|
||
# image_node = self.world.render.attachNewNode(cm.generate())
|
||
image_node = parent_node.attachNewNode(cm.generate())
|
||
image_node.setPos(*pos)
|
||
|
||
# 为3D图像创建独立的材质
|
||
material = Material(f"image-material-{len(self.gui_elements)}")
|
||
material.setBaseColor(LColor(1, 1, 1, 1))
|
||
material.setDiffuse(LColor(1, 1, 1, 1))
|
||
material.setAmbient(LColor(0.5, 0.5, 0.5, 1))
|
||
material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
|
||
material.setShininess(10.0)
|
||
material.setEmission(LColor(0, 0, 0, 1)) # 无自发光
|
||
image_node.setMaterial(material, 1)
|
||
|
||
image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 如果提供了图像路径,则加载纹理
|
||
if image_path:
|
||
self.update3DImageTexture(image_node, image_path)
|
||
|
||
# 应用PBR效果(如果可用)
|
||
try:
|
||
if hasattr(self, 'render_pipeline') and self.render_pipeline:
|
||
self.render_pipeline.set_effect(
|
||
image_node,
|
||
"effects/default.yaml",
|
||
{
|
||
"normal_mapping": True,
|
||
"render_gbuffer": True,
|
||
"alpha_testing": False,
|
||
"parallax_mapping": False,
|
||
"render_shadow": False,
|
||
"render_envmap": True,
|
||
"disable_children_effects": True
|
||
},
|
||
50
|
||
)
|
||
print("✓ GUI 3D图像PBR效果已应用")
|
||
except Exception as e:
|
||
print(f"⚠️ GUI 3D图像PBR效果应用失败: {e}")
|
||
image_node.setName(image_name)
|
||
|
||
# 设置节点标签
|
||
image_node.setTag("gui_type", "3d_image")
|
||
image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}")
|
||
if image_path:
|
||
image_node.setTag("gui_image_path", image_path)
|
||
image_node.setTag("is_gui_element", "1")
|
||
image_node.setTag("is_scene_element", "1")
|
||
image_node.setTag("created_by_user", "1")
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(image_node)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建3D文本成功: {image_name}")
|
||
|
||
# 在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(image_node, parent_item, "GUI_3DIMAGE")
|
||
if qt_item:
|
||
created_3dimage.append((image_node, qt_item))
|
||
else:
|
||
created_3dimage.append((image_node, None))
|
||
print("⚠️ Qt树节点添加失败,但GUI对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建3D文本失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_3dimage:
|
||
print("❌ 没有成功创建任何3D文本")
|
||
return None
|
||
|
||
# 选中最后创建的文本并更新场景树
|
||
if created_3dimage:
|
||
last_image, last_qt_item = created_3dimage[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
tree_widget.update_selection_and_properties(last_image, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_3dimage)} 个3D文本")
|
||
|
||
# 返回值处理
|
||
if len(created_3dimage) == 1:
|
||
return created_3dimage[0][0]
|
||
else:
|
||
return [text_np for text_np, _ in created_3dimage]
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建3D文本过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
|
||
"""创建3D虚拟屏幕 - 支持多选创建,优化版本"""
|
||
try:
|
||
from panda3d.core import CardMaker, TransparencyAttrib, TextNode
|
||
from PyQt5.QtCore import Qt
|
||
|
||
print(f"🖥️ 开始创建虚拟屏幕,位置: {pos}, 尺寸: {size}, 文本: {text}")
|
||
|
||
# 获取树形控件
|
||
tree_widget = self._get_tree_widget()
|
||
if not tree_widget:
|
||
print("❌ 无法访问树形控件")
|
||
return None
|
||
|
||
# 获取目标父节点列表
|
||
target_parents = tree_widget.get_target_parents_for_creation()
|
||
if not target_parents:
|
||
print("❌ 没有找到有效的父节点")
|
||
return None
|
||
|
||
created_screens = []
|
||
|
||
# 为每个有效的父节点创建虚拟屏幕
|
||
for parent_item, parent_node in target_parents:
|
||
try:
|
||
# 生成唯一名称
|
||
screen_name = f"VirtualScreen_{len(self.gui_elements)}"
|
||
|
||
# 创建虚拟屏幕几何体
|
||
cm = CardMaker(f"virtual-screen-{len(self.gui_elements)}")
|
||
cm.setFrame(-size[0] / 2, size[0] / 2, -size[1] / 2, size[1] / 2)
|
||
|
||
# 创建挂载节点 - 挂载到选中的父节点
|
||
virtual_screen = parent_node.attachNewNode(cm.generate())
|
||
virtual_screen.setPos(*pos)
|
||
virtual_screen.setName(screen_name)
|
||
virtual_screen.setColor(0.2, 0.2, 0.2, 0.8)
|
||
virtual_screen.setTransparency(TransparencyAttrib.MAlpha)
|
||
|
||
# 在虚拟屏幕上添加文本
|
||
screen_text_node = self._create_screen_text(virtual_screen, text, len(self.gui_elements))
|
||
|
||
# 设置节点标签
|
||
virtual_screen.setTag("gui_type", "virtual_screen")
|
||
virtual_screen.setTag("gui_id", f"virtual_screen_{len(self.gui_elements)}")
|
||
virtual_screen.setTag("gui_text", text)
|
||
virtual_screen.setTag("is_gui_element", "1")
|
||
virtual_screen.setTag("is_scene_element", "1")
|
||
virtual_screen.setTag("created_by_user", "1")
|
||
|
||
# 添加到GUI元素列表
|
||
self.gui_elements.append(virtual_screen)
|
||
|
||
print(f"✅ 为 {parent_item.text(0)} 创建虚拟屏幕成功: {screen_name}")
|
||
|
||
# 在Qt树形控件中添加对应节点
|
||
qt_item = tree_widget.add_node_to_tree_widget(virtual_screen, parent_item, "GUI_VirtualScreen")
|
||
if qt_item:
|
||
created_screens.append((virtual_screen, qt_item))
|
||
else:
|
||
created_screens.append((virtual_screen, None))
|
||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||
|
||
except Exception as e:
|
||
print(f"❌ 为 {parent_item.text(0)} 创建虚拟屏幕失败: {str(e)}")
|
||
continue
|
||
|
||
# 处理创建结果
|
||
if not created_screens:
|
||
print("❌ 没有成功创建任何虚拟屏幕")
|
||
return None
|
||
|
||
# 选中最后创建的虚拟屏幕
|
||
if created_screens:
|
||
last_screen_np, last_qt_item = created_screens[-1]
|
||
if last_qt_item:
|
||
tree_widget.setCurrentItem(last_qt_item)
|
||
# 更新选择和属性面板
|
||
tree_widget.update_selection_and_properties(last_screen_np, last_qt_item)
|
||
|
||
print(f"🎉 总共创建了 {len(created_screens)} 个虚拟屏幕")
|
||
|
||
# 返回值处理
|
||
if len(created_screens) == 1:
|
||
return created_screens[0][0] # 单个屏幕返回NodePath
|
||
else:
|
||
return [screen_np for screen_np, _ in created_screens] # 多个屏幕返回列表
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建虚拟屏幕过程失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def _create_screen_text(self, virtual_screen, text, screen_index):
|
||
"""为虚拟屏幕创建文本节点"""
|
||
try:
|
||
from panda3d.core import TextNode
|
||
|
||
screen_text = TextNode(f'screen-text-{screen_index}')
|
||
screen_text.setText(text)
|
||
screen_text.setAlign(TextNode.ACenter)
|
||
|
||
# 设置中文字体
|
||
if hasattr(self.world, 'getChineseFont') and self.world.getChineseFont():
|
||
screen_text.setFont(self.world.getChineseFont())
|
||
|
||
# 创建文本节点路径并设置属性
|
||
screen_text_np = virtual_screen.attachNewNode(screen_text)
|
||
screen_text_np.setPos(0, 0.01, 0) # 稍微向前偏移避免Z-fighting
|
||
screen_text_np.setScale(0.3)
|
||
screen_text_np.setColor(0, 1, 0, 1) # 绿色文本
|
||
|
||
print(f"✅ 虚拟屏幕文本创建成功: {text}")
|
||
return screen_text_np
|
||
|
||
except Exception as e:
|
||
print(f"❌ 创建虚拟屏幕文本失败: {str(e)}")
|
||
return None
|
||
|
||
def _get_tree_widget(self):
|
||
"""安全获取树形控件"""
|
||
try:
|
||
if (hasattr(self.world, 'interface_manager') and
|
||
hasattr(self.world.interface_manager, 'treeWidget')):
|
||
return self.world.interface_manager.treeWidget
|
||
except AttributeError:
|
||
pass
|
||
return None
|
||
|
||
# 暂无滑块功能
|
||
def createGUISlider(self, pos=(0, 0, 0), text="滑块", scale=0.3):
|
||
pass
|
||
"""创建2D GUI滑块"""
|
||
from direct.gui.DirectGui import DirectSlider
|
||
|
||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||
|
||
slider = DirectSlider(
|
||
pos=gui_pos,
|
||
scale=scale,
|
||
range=(0, 100),
|
||
value=50,
|
||
frameColor=(0.6, 0.6, 0.6, 1),
|
||
thumbColor=(0.2, 0.8, 0.2, 1)
|
||
)
|
||
|
||
slider.setTag("gui_type", "slider")
|
||
slider.setTag("gui_id", f"slider_{len(self.gui_elements)}")
|
||
slider.setTag("gui_text", text)
|
||
slider.setTag("is_gui_element", "1")
|
||
|
||
self.gui_elements.append(slider)
|
||
# 安全地调用updateSceneTree
|
||
if hasattr(self.world, 'updateSceneTree'):
|
||
pass # CH
|
||
self.world.updateSceneTree()
|
||
|
||
print(f"✓ 创建GUI滑块: {text} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||
return slider
|
||
|
||
# ==================== GUI元素管理方法 ====================
|
||
|
||
def deleteGUIElement(self, gui_element):
|
||
"""删除GUI元素"""
|
||
try:
|
||
if gui_element in self.gui_elements:
|
||
# # 移除GUI元素
|
||
# if hasattr(gui_element, 'removeNode'):
|
||
# gui_element.removeNode()
|
||
# elif hasattr(gui_element, 'destroy'):
|
||
# gui_element.destroy()
|
||
#
|
||
# # 从列表中移除
|
||
# self.gui_elements.remove(gui_element)
|
||
|
||
# 更新场景树
|
||
# 安全地调用updateSceneTree
|
||
tree_widget = self._get_tree_widget()
|
||
if tree_widget:
|
||
tree_widget.delete_items(tree_widget.selectedItems())
|
||
# if hasattr(self.world, 'updateSceneTree'):
|
||
# self.world.updateSceneTree()
|
||
|
||
print(f"删除GUI元素: {gui_element}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"删除GUI元素失败: {str(e)}")
|
||
return False
|
||
|
||
# 在 gui_manager.py 中确保 editGUIElement 方法正确处理文本颜色
|
||
def editGUIElement(self, gui_element, property_name, value):
|
||
"""编辑GUI元素属性"""
|
||
try:
|
||
from panda3d.core import TextNode
|
||
|
||
gui_type = gui_element.getTag("gui_type") if hasattr(gui_element, 'getTag') else "unknown"
|
||
print(f"开始编辑GUI元素: 类型={gui_type}, 属性={property_name}, 值={value}")
|
||
|
||
if property_name == "text":
|
||
if gui_type in ["button", "label"]:
|
||
gui_element['text'] = value
|
||
print(f"成功更新2D GUI文本: {value}")
|
||
elif gui_type == "entry":
|
||
gui_element.set(value)
|
||
print(f"成功更新输入框文本: {value}")
|
||
elif gui_type == "3d_text":
|
||
# 对于3D文本,直接修改自身的TextNode
|
||
if isinstance(gui_element.node(), TextNode):
|
||
gui_element.node().setText(value)
|
||
print(f"成功更新3D文本: {value}")
|
||
else:
|
||
print(f"警告: {gui_type}节点类型为{type(gui_element.node())},不是TextNode类型")
|
||
|
||
elif gui_type == "virtual_screen":
|
||
# 对于虚拟屏幕,需要找到TextNode子节点
|
||
print(f"虚拟屏幕有 {gui_element.getNumChildren()} 个子节点")
|
||
text_found = False
|
||
for i, child in enumerate(gui_element.getChildren()):
|
||
print(f"子节点 {i}: {child.getName()}, 类型: {type(child.node())}")
|
||
if isinstance(child.node(), TextNode):
|
||
child.node().setText(value)
|
||
text_found = True
|
||
print(f"成功更新虚拟屏幕文本: {value}")
|
||
break
|
||
|
||
if not text_found:
|
||
print(f"警告: 在{gui_type}中未找到TextNode子节点")
|
||
|
||
gui_element.setTag("gui_text", value)
|
||
|
||
elif property_name == "color": # 添加颜色处理
|
||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||
# 对于2D GUI元素(button和label),使用frameColor属性
|
||
if gui_type in ["button", "label"]:
|
||
# 设置背景颜色
|
||
gui_element['frameColor'] = (value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)
|
||
print(f"成功更新2D GUI背景颜色: {gui_type} -> {value}")
|
||
|
||
# 对于3D元素,使用材质颜色
|
||
elif gui_type in ["3d_text", "3d_image", "virtual_screen"]:
|
||
# 更新材质颜色
|
||
if not gui_element.hasMaterial():
|
||
material = Material(f"text-material-{gui_element.getName()}")
|
||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||
gui_element.setMaterial(material, 1)
|
||
else:
|
||
material = gui_element.getMaterial()
|
||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||
gui_element.setMaterial(material, 1)
|
||
|
||
# 更新 TextNode 的文本颜色
|
||
if isinstance(gui_element.node(), TextNode):
|
||
gui_element.node().setTextColor(
|
||
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||
print(f"成功更新3D GUI颜色: {gui_type} -> {value}")
|
||
|
||
else:
|
||
print(f"警告: 未知的GUI类型 {gui_type},无法设置颜色")
|
||
|
||
elif property_name == "position":
|
||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||
gui_element.setPos(*value[:3])
|
||
|
||
elif property_name == "scale":
|
||
if isinstance(value, (int, float)):
|
||
gui_element.setScale(value)
|
||
elif isinstance(value, (list, tuple)) and len(value) >= 3:
|
||
gui_element.setScale(*value[:3])
|
||
|
||
print(f"编辑GUI元素 {gui_type}: {property_name} = {value}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"编辑GUI元素失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def duplicateGUIElement(self, gui_element):
|
||
"""复制GUI元素"""
|
||
try:
|
||
gui_type = gui_element.getTag("gui_type")
|
||
gui_text = gui_element.getTag("gui_text")
|
||
|
||
# 获取当前位置并偏移
|
||
pos = gui_element.getPos()
|
||
new_pos = (pos.getX() + 0.2, pos.getY(), pos.getZ() + 0.2)
|
||
|
||
# 根据类型创建新的GUI元素
|
||
if gui_type == "button":
|
||
self.createGUIButton(new_pos, gui_text + "_副本")
|
||
elif gui_type == "label":
|
||
self.createGUILabel(new_pos, gui_text + "_副本")
|
||
elif gui_type == "entry":
|
||
self.createGUIEntry(new_pos, gui_text + "_副本")
|
||
elif gui_type == "3d_text":
|
||
self.createGUI3DText(new_pos, gui_text + "_副本")
|
||
elif gui_type == "3d_image":
|
||
image_path = gui_element.getTag("image_path")
|
||
self.createGUI3DImage(new_pos,image_path,size=(2,2))
|
||
elif gui_type == "2d_image":
|
||
image_path = gui_element.getTag("image_path")
|
||
self.createGUI2DImage(new_pos, image_path, size=0.2)
|
||
elif gui_type == "virtual_screen":
|
||
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
||
|
||
print(f"复制GUI元素: {gui_type} - {gui_text}")
|
||
|
||
except Exception as e:
|
||
print(f"复制GUI元素失败: {str(e)}")
|
||
|
||
def editGUIElementDialog(self, gui_element):
|
||
"""显示GUI元素编辑对话框"""
|
||
dialog = QDialog()
|
||
dialog.setWindowTitle("编辑GUI元素")
|
||
dialog.setMinimumWidth(300)
|
||
|
||
layout = QVBoxLayout(dialog)
|
||
form = QFormLayout()
|
||
|
||
gui_type = gui_element.getTag("gui_type")
|
||
gui_text = gui_element.getTag("gui_text")
|
||
|
||
# 文本编辑
|
||
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||
textEdit = QLineEdit(gui_text or "")
|
||
form.addRow("文本:", textEdit)
|
||
|
||
# 位置编辑
|
||
if hasattr(gui_element, 'getPos'):
|
||
pos = gui_element.getPos()
|
||
xEdit = QDoubleSpinBox()
|
||
xEdit.setRange(-1000, 1000)
|
||
xEdit.setValue(pos.getX())
|
||
form.addRow("位置 X:", xEdit)
|
||
|
||
yEdit = QDoubleSpinBox()
|
||
yEdit.setRange(-1000, 1000)
|
||
yEdit.setValue(pos.getY())
|
||
form.addRow("位置 Y:", yEdit)
|
||
|
||
zEdit = QDoubleSpinBox()
|
||
zEdit.setRange(-1000, 1000)
|
||
zEdit.setValue(pos.getZ())
|
||
form.addRow("位置 Z:", zEdit)
|
||
|
||
# 缩放编辑
|
||
if hasattr(gui_element, 'getScale'):
|
||
scale = gui_element.getScale()
|
||
scaleEdit = QDoubleSpinBox()
|
||
scaleEdit.setRange(0.01, 10)
|
||
scaleEdit.setSingleStep(0.1)
|
||
scaleEdit.setValue(scale.getX())
|
||
form.addRow("缩放:", scaleEdit)
|
||
|
||
layout.addLayout(form)
|
||
|
||
# 按钮
|
||
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||
buttonBox.accepted.connect(dialog.accept)
|
||
buttonBox.rejected.connect(dialog.reject)
|
||
layout.addWidget(buttonBox)
|
||
|
||
# 执行对话框
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
try:
|
||
# 应用更改
|
||
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||
self.editGUIElement(gui_element, "text", textEdit.text())
|
||
|
||
if hasattr(gui_element, 'getPos'):
|
||
self.editGUIElement(gui_element, "position", [xEdit.value(), yEdit.value(), zEdit.value()])
|
||
|
||
if hasattr(gui_element, 'getScale'):
|
||
self.editGUIElement(gui_element, "scale", scaleEdit.value())
|
||
|
||
# 更新属性面板
|
||
if self.world.treeWidget:
|
||
currentItem = self.world.treeWidget.currentItem()
|
||
if currentItem:
|
||
self.world.updatePropertyPanel(currentItem)
|
||
|
||
print("GUI元素编辑完成")
|
||
|
||
except Exception as e:
|
||
print(f"应用GUI编辑失败: {str(e)}")
|
||
|
||
# ==================== GUI事件处理方法 ====================
|
||
|
||
def onGUIButtonClick(self, button_id):
|
||
"""GUI按钮点击事件处理"""
|
||
print(f"GUI按钮被点击: {button_id}")
|
||
|
||
def onGUIEntrySubmit(self, text, entry_id):
|
||
"""GUI输入框提交事件处理"""
|
||
print(f"GUI输入框提交: {entry_id} = {text}")
|
||
|
||
# ==================== GUI编辑模式方法 ====================
|
||
|
||
def toggleGUIEditMode(self):
|
||
"""切换GUI编辑模式"""
|
||
self.guiEditMode = not self.guiEditMode
|
||
|
||
if self.guiEditMode:
|
||
self.enterGUIEditMode()
|
||
else:
|
||
self.exitGUIEditMode()
|
||
|
||
def enterGUIEditMode(self):
|
||
"""进入GUI编辑模式"""
|
||
print("\n=== 进入GUI编辑模式 ===")
|
||
|
||
# 打开GUI预览窗口
|
||
self.openGUIPreviewWindow()
|
||
|
||
# 创建GUI编辑面板
|
||
self.createGUIEditPanel()
|
||
|
||
# 改变当前工具为GUI选择工具
|
||
self.world.currentTool = "GUI编辑"
|
||
|
||
print("GUI编辑模式已激活")
|
||
print("- 使用右侧工具栏创建GUI元素")
|
||
print("- 在独立预览窗口中查看效果")
|
||
print("- 左键点击现有GUI元素选择和编辑")
|
||
|
||
def exitGUIEditMode(self):
|
||
"""退出GUI编辑模式"""
|
||
print("\n=== 退出GUI编辑模式 ===")
|
||
|
||
# 关闭GUI预览窗口
|
||
self.closeGUIPreviewWindow()
|
||
|
||
# 移除GUI编辑面板
|
||
if self.guiEditPanel:
|
||
self.guiEditPanel.destroy()
|
||
self.guiEditPanel = None
|
||
|
||
# 恢复普通工具
|
||
self.world.currentTool = "选择"
|
||
|
||
print("GUI编辑模式已关闭")
|
||
|
||
def createGUIEditPanel(self):
|
||
"""创建GUI编辑面板"""
|
||
from direct.gui.DirectGui import DirectFrame, DirectButton, DirectLabel
|
||
|
||
# 创建主面板
|
||
self.guiEditPanel = DirectFrame(
|
||
pos=(0.85, 0, 0),
|
||
frameSize=(-0.15, 0.15, -0.9, 0.9),
|
||
frameColor=(0.1, 0.1, 0.1, 0.8),
|
||
text="GUI编辑器",
|
||
text_pos=(0, 0.85),
|
||
text_scale=0.05,
|
||
text_fg=(1, 1, 1, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
|
||
# 创建工具按钮
|
||
y_pos = 0.7
|
||
spacing = 0.12
|
||
|
||
# 2D GUI工具
|
||
label_2d = DirectLabel(
|
||
parent=self.guiEditPanel,
|
||
text="2D GUI",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
text_fg=(1, 1, 0, 1),
|
||
frameColor=(0, 0, 0, 0),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= 0.08
|
||
|
||
# 按钮工具
|
||
btn_button = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="按钮",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["button"],
|
||
frameColor=(0.2, 0.6, 0.8, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 标签工具
|
||
btn_label = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="标签",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["label"],
|
||
frameColor=(0.6, 0.8, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 输入框工具
|
||
btn_entry = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="输入框",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["entry"],
|
||
frameColor=(0.8, 0.6, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 3D GUI工具
|
||
label_3d = DirectLabel(
|
||
parent=self.guiEditPanel,
|
||
text="3D GUI",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
text_fg=(1, 1, 0, 1),
|
||
frameColor=(0, 0, 0, 0),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= 0.08
|
||
|
||
# 3D文本工具
|
||
btn_3dtext = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="3D文本",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["3d_text"],
|
||
frameColor=(0.8, 0.2, 0.6, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
#3D图片工具
|
||
btn_image = DirectButton(
|
||
parent = self.guiEditPanel,
|
||
text="3D图片",
|
||
pos=(0,0,y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["3d_image"],
|
||
frameColor=(0.2,0.8,0.8,1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 2D图片工具
|
||
btn_2d_image = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="2D图片",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["2d_image"],
|
||
frameColor=(0.8, 0.6, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 虚拟屏幕工具
|
||
btn_screen = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="虚拟屏幕",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.setGUICreateTool,
|
||
extraArgs=["virtual_screen"],
|
||
frameColor=(0.6, 0.2, 0.8, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
#Cesium 集成工具 (仅在Webengine 可用时显示)
|
||
if WEB_ENGINE_AVAILABLE:
|
||
label_cesium = DirectLabel(
|
||
parent=self.guiEditPanel,
|
||
text="Cesium 集成",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
text_fg=(1, 1, 0, 1),
|
||
frameColor=(0, 0, 0, 0),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= 0.08
|
||
|
||
# 切换 Cesium 视图按钮
|
||
btn_toggle_cesium = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="切换地图视图",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.toggleCesiumView,
|
||
frameColor=(0.2, 0.8, 0.6, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 刷新 Cesium 视图按钮
|
||
btn_refresh_cesium = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="刷新地图",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.refreshCesiumView,
|
||
frameColor=(0.6, 0.8, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 分隔线
|
||
y_pos -= 0.1
|
||
|
||
# 操作按钮
|
||
label_ops = DirectLabel(
|
||
parent=self.guiEditPanel,
|
||
text="操作",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
text_fg=(1, 1, 0, 1),
|
||
frameColor=(0, 0, 0, 0),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= 0.08
|
||
|
||
# 删除工具
|
||
btn_delete = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="删除",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.deleteSelectedGUI,
|
||
frameColor=(0.8, 0.2, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 复制工具
|
||
btn_copy = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="复制",
|
||
pos=(0, 0, y_pos),
|
||
scale=0.04,
|
||
command=self.copySelectedGUI,
|
||
frameColor=(0.2, 0.8, 0.2, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
y_pos -= spacing
|
||
|
||
# 退出GUI编辑模式
|
||
btn_exit = DirectButton(
|
||
parent=self.guiEditPanel,
|
||
text="退出",
|
||
pos=(0, 0, -0.8),
|
||
scale=0.04,
|
||
command=self.toggleGUIEditMode,
|
||
frameColor=(0.5, 0.5, 0.5, 1),
|
||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||
)
|
||
|
||
# 存储当前的GUI创建工具
|
||
self.currentGUITool = None
|
||
|
||
def openGUIPreviewWindow(self):
|
||
"""打开独立的GUI预览窗口"""
|
||
try:
|
||
from gui_preview_window import GUIPreviewWindow
|
||
|
||
self.guiPreviewWindow = GUIPreviewWindow()
|
||
self.guiPreviewWindow.set_main_world(self.world)
|
||
|
||
print("✓ GUI预览窗口已打开")
|
||
print("这个独立窗口会实时显示您创建的GUI元素")
|
||
|
||
except ImportError:
|
||
print("错误: 无法导入GUI预览窗口模块")
|
||
except Exception as e:
|
||
print(f"打开GUI预览窗口失败: {str(e)}")
|
||
|
||
def closeGUIPreviewWindow(self):
|
||
"""关闭GUI预览窗口"""
|
||
if self.guiPreviewWindow:
|
||
self.guiPreviewWindow.destroy()
|
||
self.guiPreviewWindow = None
|
||
print("GUI预览窗口已关闭")
|
||
|
||
# ==================== GUI工具和选择方法 ====================
|
||
|
||
def setGUICreateTool(self, tool_type):
|
||
"""设置GUI创建工具"""
|
||
self.currentGUITool = tool_type
|
||
print(f"选择GUI创建工具: {tool_type}")
|
||
|
||
def deleteSelectedGUI(self):
|
||
"""删除选中的GUI元素"""
|
||
if self.world.selection.selectedNode and hasattr(self.world.selection.selectedNode, 'getTag'):
|
||
gui_type = self.world.selection.selectedNode.getTag("gui_type")
|
||
if gui_type:
|
||
success = self.deleteGUIElement(self.world.selection.selectedNode)
|
||
if success:
|
||
self.world.selection.updateSelection(None)
|
||
print("GUI元素已删除")
|
||
else:
|
||
print("删除GUI元素失败")
|
||
else:
|
||
print("选中的不是GUI元素")
|
||
else:
|
||
print("没有选中的GUI元素")
|
||
|
||
def copySelectedGUI(self):
|
||
"""复制选中的GUI元素"""
|
||
if self.world.selection.selectedNode and hasattr(self.world.selection.selectedNode, 'getTag'):
|
||
gui_type = self.world.selection.selectedNode.getTag("gui_type")
|
||
if gui_type:
|
||
self.duplicateGUIElement(self.world.selection.selectedNode)
|
||
print("GUI元素已复制")
|
||
else:
|
||
print("选中的不是GUI元素")
|
||
else:
|
||
print("没有选中的GUI元素")
|
||
|
||
def handleGUIEditClick(self, hitPos):
|
||
"""处理GUI编辑模式下的点击"""
|
||
if not self.guiEditMode:
|
||
return False
|
||
|
||
if self.currentGUITool:
|
||
# 创建新的GUI元素
|
||
self.createGUIAtPosition(hitPos, self.currentGUITool)
|
||
return True
|
||
|
||
return False
|
||
|
||
def createGUIAtPosition(self, world_pos, gui_type):
|
||
"""在指定位置创建GUI元素"""
|
||
print(f"在位置 {world_pos} 创建 {gui_type}")
|
||
|
||
# 根据GUI类型选择合适的坐标转换
|
||
if gui_type in ["button", "label", "entry"]:
|
||
# 2D GUI - 将世界坐标转换为屏幕逻辑坐标
|
||
screen_x = world_pos.getX() * 2 # 缩放因子
|
||
screen_z = world_pos.getZ() * 2
|
||
pos = (screen_x, 0, screen_z)
|
||
else:
|
||
# 3D GUI - 直接使用世界坐标
|
||
pos = (world_pos.getX(), world_pos.getY(), world_pos.getZ())
|
||
|
||
# 创建不同类型的GUI元素
|
||
if gui_type == "button":
|
||
element = self.createGUIButton(pos, f"按钮_{len(self.gui_elements)}")
|
||
elif gui_type == "label":
|
||
element = self.createGUILabel(pos, f"标签_{len(self.gui_elements)}")
|
||
elif gui_type == "entry":
|
||
element = self.createGUIEntry(pos, f"输入框_{len(self.gui_elements)}")
|
||
elif gui_type == "3d_text":
|
||
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
||
elif gui_type == "3d_image":
|
||
element = self.createGUI3DImage(pos)
|
||
elif gui_type == "2d_image":
|
||
element = self.createGUI2DImage(pos)
|
||
elif gui_type == "virtual_screen":
|
||
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
||
else:
|
||
print(f"未知的GUI类型: {gui_type}")
|
||
return
|
||
|
||
# 自动选中新创建的元素
|
||
self.world.selection.updateSelection(element)
|
||
self.selectGUIInTree(element)
|
||
print(f"创建并选中了新的{gui_type}元素")
|
||
|
||
def findClickedGUI(self, hitNode):
|
||
"""查找被点击的GUI元素"""
|
||
# 检查点击的节点是否是GUI元素
|
||
current = hitNode
|
||
while current != self.world.render:
|
||
if hasattr(current, 'getTag') and current.getTag("gui_type"):
|
||
return current
|
||
current = current.getParent()
|
||
return None
|
||
|
||
def selectGUIInTree(self, gui_element):
|
||
"""在树形控件中选中GUI元素"""
|
||
if not self.world.treeWidget or not gui_element:
|
||
return
|
||
|
||
def findGUIItem(item):
|
||
"""递归查找GUI元素对应的树形项"""
|
||
if item.data(0, Qt.UserRole) == gui_element:
|
||
return item
|
||
|
||
for i in range(item.childCount()):
|
||
child = item.child(i)
|
||
result = findGUIItem(child)
|
||
if result:
|
||
return result
|
||
return None
|
||
|
||
# 从根开始查找
|
||
root = self.world.treeWidget.invisibleRootItem()
|
||
for i in range(root.childCount()):
|
||
sceneItem = root.child(i)
|
||
if sceneItem.text(0) == "场景":
|
||
for j in range(sceneItem.childCount()):
|
||
childItem = sceneItem.child(j)
|
||
if childItem.text(0) == "GUI元素":
|
||
foundItem = findGUIItem(childItem)
|
||
if foundItem:
|
||
self.world.treeWidget.setCurrentItem(foundItem)
|
||
self.world.updatePropertyPanel(foundItem)
|
||
return
|
||
|
||
def updateGUISelection(self, gui_element):
|
||
"""更新GUI元素选择状态"""
|
||
self.world.selection.updateSelection(gui_element)
|
||
if gui_element and hasattr(gui_element, 'getTag'):
|
||
gui_type = gui_element.getTag("gui_type")
|
||
gui_text = gui_element.getTag("gui_text")
|
||
print(f"选中GUI元素: {gui_type} - {gui_text}")
|
||
|
||
# 在树形控件中选中
|
||
self.selectGUIInTree(gui_element)
|
||
|
||
# ==================== GUI属性面板方法 ====================
|
||
|
||
# def updateGUIPropertyPanel(self, gui_element, layout):
|
||
# """更新GUI元素属性面板"""
|
||
# gui_type = gui_element.getTag("gui_type")
|
||
# gui_text = gui_element.getTag("gui_text")
|
||
#
|
||
# # GUI类型显示
|
||
# typeLabel = QLabel("GUI类型:")
|
||
# typeValue = QLabel(gui_type)
|
||
# typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
||
# layout.addRow(typeLabel, typeValue)
|
||
#
|
||
# # 文本属性(如果适用)
|
||
# if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||
# textLabel = QLabel("文本:")
|
||
# textEdit = QLineEdit(gui_text or "")
|
||
#
|
||
# # 创建一个更新函数来处理文本变化
|
||
# def updateText(text):
|
||
# success = self.editGUIElement(gui_element, "text", text)
|
||
# if success:
|
||
# # 更新场景树显示的名称
|
||
# # 安全地调用updateSceneTree
|
||
# if hasattr(self.world, 'updateSceneTree'):
|
||
# self.world.updateSceneTree()
|
||
#
|
||
# textEdit.textChanged.connect(updateText)
|
||
# layout.addRow(textLabel, textEdit)
|
||
#
|
||
# # 位置属性
|
||
# if hasattr(gui_element, 'getPos'):
|
||
# # 根据GUI类型设置组名
|
||
# if gui_type in ["button", "label", "entry", "2d_image"]:
|
||
# transform_group = QGroupBox("变换 Rect Transform")
|
||
# else:
|
||
# transform_group = QGroupBox("变换 Transform")
|
||
#
|
||
# transform_layout = QGridLayout()
|
||
#
|
||
# pos = gui_element.getPos()
|
||
#
|
||
# # 根据GUI类型决定位置编辑方式
|
||
# if gui_type in ["button", "label", "entry","2d_image"]:
|
||
# # 2D GUI组件使用屏幕坐标
|
||
# logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||
# logical_z = pos.getZ() / 0.1
|
||
#
|
||
# transform_layout.addWidget(QLabel("屏幕位置"), 0, 0)
|
||
#
|
||
# x_label = QLabel("X")
|
||
# z_label = QLabel("z")
|
||
# x_label.setAlignment(Qt.Aligncenter)
|
||
# z_label.setAlignment(Qt.AlignCenter)
|
||
#
|
||
# transform_layout.addWidget(x_label, 0, 1)
|
||
# transform_layout.addWidget(z_label, 0, 2)
|
||
#
|
||
# xPos = QDoubleSpinBox()
|
||
# xPos.setRange(-50, 50)
|
||
# xPos.setValue(logical_x)
|
||
# xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||
# transform_layout.addWidget(xPos, 1, 1)
|
||
#
|
||
# zPos = QDoubleSpinBox()
|
||
# zPos.setRange(-50, 50)
|
||
# zPos.setValue(logical_z)
|
||
# zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||
# transform_layout.addWidget(zPos, 1, 2)
|
||
#
|
||
# # 显示实际屏幕坐标(只读)
|
||
# transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
|
||
#
|
||
# actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||
# actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||
# actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||
# actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||
#
|
||
# transform_layout.addWidget(actualXLabel, 3, 1)
|
||
# transform_layout.addWidget(actualZLabel, 3, 2)
|
||
# # 添加宽度和高度控件(对于2D图像)
|
||
# if gui_type == "2d_image":
|
||
#
|
||
# # 添加排序控制组
|
||
# sort_group = QGroupBox("渲染顺序")
|
||
# sort_layout = QGridLayout()
|
||
#
|
||
# # 获取当前的sort值,如果没有设置则默认为0
|
||
# current_sort = int(gui_element.getTag("sort") or "0")
|
||
#
|
||
# sort_layout.addWidget(QLabel("层级:"), 0, 0)
|
||
#
|
||
# sort_spin = QSpinBox()
|
||
# sort_spin.setRange(-1000, 1000) # 设置合理的范围
|
||
# sort_spin.setValue(current_sort)
|
||
#
|
||
# # 创建更新排序的函数
|
||
# def updateSort(value):
|
||
# try:
|
||
# # 设置标签
|
||
# gui_element.setTag("sort", str(value))
|
||
# # 应用sort到节点 - 使用fixed bin和指定的值
|
||
# gui_element.setBin("fixed", value)
|
||
# print(f"✓ 更新2D图像渲染顺序: {value}")
|
||
# except Exception as e:
|
||
# print(f"✗ 更新2D图像渲染顺序失败: {e}")
|
||
#
|
||
# sort_spin.valueChanged.connect(updateSort)
|
||
# sort_layout.addWidget(sort_spin, 0, 1)
|
||
#
|
||
# # 添加说明标签
|
||
# sort_help = QLabel("(数值越大越靠前)")
|
||
# sort_help.setStyleSheet("font-size: 10px; color: gray;")
|
||
# sort_layout.addWidget(sort_help, 1, 0, 1, 2)
|
||
#
|
||
# sort_group.setLayout(sort_layout)
|
||
# layout.addWidget(sort_group)
|
||
#
|
||
# scale = gui_element.getScale()
|
||
# width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,
|
||
# (tuple, list)) else scale
|
||
# height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,
|
||
# (tuple, list)) and len(
|
||
# scale) > 1 else scale
|
||
#
|
||
# # 宽度控件
|
||
# transform_layout.addWidget(QLabel("宽度"), 4, 0)
|
||
# widthSpinBox = QDoubleSpinBox()
|
||
# widthSpinBox.setRange(0.1, 10)
|
||
# widthSpinBox.setSingleStep(0.1)
|
||
# widthSpinBox.setValue(width)
|
||
# widthSpinBox.valueChanged.connect(
|
||
# lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||
# transform_layout.addWidget(widthSpinBox, 4, 1)
|
||
#
|
||
# # 高度控件
|
||
# transform_layout.addWidget(QLabel("高度"), 4, 2)
|
||
# heightSpinBox = QDoubleSpinBox()
|
||
# heightSpinBox.setRange(0.1, 10)
|
||
# heightSpinBox.setSingleStep(0.1)
|
||
# heightSpinBox.setValue(height)
|
||
# heightSpinBox.valueChanged.connect(
|
||
# lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||
# transform_layout.addWidget(heightSpinBox, 4, 3)
|
||
#
|
||
# else:
|
||
# # 3D GUI组件使用世界坐标
|
||
# transform_layout.addWidget(QLabel("位置"), 0, 0)
|
||
#
|
||
# # X, Y, Z 标签居中
|
||
# x_label = QLabel("X")
|
||
# y_label = QLabel("Y")
|
||
# z_label = QLabel("Z")
|
||
# x_label.setAlignment(Qt.AlignCenter)
|
||
# y_label.setAlignment(Qt.AlignCenter)
|
||
# z_label.setAlignment(Qt.AlignCenter)
|
||
#
|
||
# transform_layout.addWidget(x_label, 0, 1)
|
||
# transform_layout.addWidget(y_label, 0, 2)
|
||
# transform_layout.addWidget(z_label, 0, 3)
|
||
#
|
||
# # 位置数值输入框
|
||
# xPos = QDoubleSpinBox()
|
||
# xPos.setRange(-100, 100)
|
||
# xPos.setValue(pos.getX())
|
||
# xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
|
||
# transform_layout.addWidget(xPos, 1, 1)
|
||
#
|
||
# yPos = QDoubleSpinBox()
|
||
# yPos.setRange(-100, 100)
|
||
# yPos.setValue(pos.getY())
|
||
# yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
|
||
# transform_layout.addWidget(yPos, 1, 2)
|
||
#
|
||
# zPos = QDoubleSpinBox()
|
||
# zPos.setRange(-100, 100)
|
||
# zPos.setValue(pos.getZ())
|
||
# zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
|
||
# transform_layout.addWidget(zPos, 1, 3)
|
||
#
|
||
# # 缩放属性
|
||
# scale = gui_element.getScale()
|
||
# transform_layout.addWidget(QLabel("缩放"), 2, 0)
|
||
#
|
||
# # X, Y, Z 缩放标签居中
|
||
# sx_label = QLabel("X")
|
||
# sy_label = QLabel("Y")
|
||
# sz_label = QLabel("Z")
|
||
# sx_label.setAlignment(Qt.AlignCenter)
|
||
# sy_label.setAlignment(Qt.AlignCenter)
|
||
# sz_label.setAlignment(Qt.AlignCenter)
|
||
#
|
||
# transform_layout.addWidget(sx_label, 2, 1)
|
||
# transform_layout.addWidget(sy_label, 2, 2)
|
||
# transform_layout.addWidget(sz_label, 2, 3)
|
||
#
|
||
# # 缩放数值输入框
|
||
# scale_x = QDoubleSpinBox()
|
||
# scale_x.setRange(0.01, 10)
|
||
# scale_x.setSingleStep(0.1)
|
||
# scale_x.setValue(
|
||
# scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale)
|
||
# scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||
# transform_layout.addWidget(scale_x, 3, 1)
|
||
#
|
||
# scale_y = QDoubleSpinBox()
|
||
# scale_y.setRange(0.01, 10)
|
||
# scale_y.setSingleStep(0.1)
|
||
# scale_y.setValue(
|
||
# scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len(
|
||
# scale) > 1 else scale)
|
||
# scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v))
|
||
# transform_layout.addWidget(scale_y, 3, 2)
|
||
#
|
||
# scale_z = QDoubleSpinBox()
|
||
# scale_z.setRange(0.01, 10)
|
||
# scale_z.setSingleStep(0.1)
|
||
# scale_z.setValue(
|
||
# scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len(
|
||
# scale) > 2 else scale)
|
||
# scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||
# transform_layout.addWidget(scale_z, 3, 3)
|
||
# transform_group.setLayout(transform_layout)
|
||
# self._propertyLayout.addWidget(transform_group)
|
||
#
|
||
# # 缩放属性
|
||
# if hasattr(gui_element, 'getScale'):
|
||
# scale = gui_element.getScale()
|
||
#
|
||
# scaleSpinBox = QDoubleSpinBox()
|
||
# scaleSpinBox.setRange(0.01, 10)
|
||
# scaleSpinBox.setSingleStep(0.1)
|
||
# scaleSpinBox.setValue(scale.getX())
|
||
# scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v))
|
||
# layout.addRow("缩放:", scaleSpinBox)
|
||
#
|
||
# # 颜色属性(针对2D GUI)
|
||
# if gui_type in ["button", "label"]:
|
||
# colorButton = QPushButton("选择颜色")
|
||
# colorButton.clicked.connect(lambda: self.selectGUIColor(gui_element))
|
||
# layout.addRow("背景颜色:", colorButton)
|
||
#
|
||
# # 3D特有属性
|
||
# if gui_type in ["3d_text", "virtual_screen"]:
|
||
# # 旋转属性
|
||
# if hasattr(gui_element, 'getHpr'):
|
||
# hpr = gui_element.getHpr()
|
||
#
|
||
# hRot = QDoubleSpinBox()
|
||
# hRot.setRange(-180, 180)
|
||
# hRot.setValue(hpr.getX())
|
||
# hRot.valueChanged.connect(lambda v: gui_element.setH(v))
|
||
# layout.addRow("旋转 H:", hRot)
|
||
#
|
||
# pRot = QDoubleSpinBox()
|
||
# pRot.setRange(-180, 180)
|
||
# pRot.setValue(hpr.getY())
|
||
# pRot.valueChanged.connect(lambda v: gui_element.setP(v))
|
||
# layout.addRow("旋转 P:", pRot)
|
||
#
|
||
# rRot = QDoubleSpinBox()
|
||
# rRot.setRange(-180, 180)
|
||
# rRot.setValue(hpr.getZ())
|
||
# rRot.valueChanged.connect(lambda v: gui_element.setR(v))
|
||
# layout.addRow("旋转 R:", rRot)
|
||
|
||
def selectGUIColor(self, gui_element):
|
||
"""选择GUI元素颜色"""
|
||
color = QColorDialog.getColor(QColor(128, 128, 128), None, "选择颜色")
|
||
if color.isValid():
|
||
r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0
|
||
self.editGUIElement(gui_element, "color", [r, g, b, 1.0])
|
||
|
||
# def editGUI2DPosition(self, gui_element, axis, value):
|
||
# """编辑2D GUI元素位置"""
|
||
# try:
|
||
# current_pos = gui_element.getPos()
|
||
#
|
||
# if axis == "x":
|
||
# # 将逻辑坐标转换为屏幕坐标
|
||
# new_screen_x = value * 0.1
|
||
# gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
||
# elif axis == "z":
|
||
# # 将逻辑坐标转换为屏幕坐标
|
||
# new_screen_z = value * 0.1
|
||
# gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
||
#
|
||
# print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
||
#
|
||
# except Exception as e:
|
||
# print(f"编辑2D GUI位置失败: {str(e)}")
|
||
|
||
def update3DImageTexture(self, model_nodepath, image_path):
|
||
from panda3d.core import Texture
|
||
|
||
try:
|
||
# 加载新纹理
|
||
new_texture = self.world.loader.loadTexture(image_path)
|
||
if new_texture:
|
||
# 确保纹理过滤质量
|
||
new_texture.setMagfilter(Texture.FT_linear)
|
||
new_texture.setMinfilter(Texture.FT_linear_mipmap_linear)
|
||
|
||
# 应用纹理到模型
|
||
model_nodepath.setTexture(new_texture, 1)
|
||
|
||
# 更新标签
|
||
model_nodepath.setTag("gui_image_path", image_path)
|
||
|
||
# 确保材质设置正确
|
||
if not model_nodepath.has_material():
|
||
from panda3d.core import Material, LColor
|
||
mat = Material()
|
||
mat.setName(f"image-material-{id(model_nodepath)}")
|
||
mat.setBaseColor(LColor(1, 1, 1, 1))
|
||
mat.setDiffuse(LColor(1, 1, 1, 1))
|
||
mat.setAmbient(LColor(0.5, 0.5, 0.5, 1))
|
||
mat.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
|
||
mat.setShininess(10.0)
|
||
model_nodepath.setMaterial(mat, 1)
|
||
|
||
print(f"✅ 3D图像纹理已更新为: {image_path}")
|
||
else:
|
||
print(f"❌ 无法加载纹理: {image_path}")
|
||
except Exception as e:
|
||
print(f"❌ 更新纹理时出错: {e}")
|
||
|
||
def update2DImageTexture(self, gui_element, image_path):
|
||
"""更新2D图片纹理"""
|
||
try:
|
||
# 加载新纹理
|
||
new_texture = self.world.loader.loadTexture(image_path)
|
||
if new_texture:
|
||
new_texture.setMagfilter(Texture.FT_linear)
|
||
new_texture.setMinfilter(Texture.FT_linear_mipmap_linear)
|
||
|
||
new_texture.setFormat(Texture.F_rgba)
|
||
new_texture.setWrapU(Texture.WM_clamp)
|
||
new_texture.setWrapV(Texture.WM_clamp)
|
||
|
||
# 应用纹理到模型
|
||
gui_element.setTexture(new_texture, 1)
|
||
|
||
# 更新标签
|
||
gui_element.setTag("texture_path", image_path)
|
||
gui_element.setTag("image_path", image_path)
|
||
|
||
print(f"✅ 2D图像纹理已更新为: {image_path}")
|
||
return True
|
||
else:
|
||
print(f"❌ 无法加载2D图片纹理: {image_path}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"❌ 更新2D图片纹理时出错: {e}")
|
||
return False
|
||
|
||
# 在gui_manager.py或其他相关文件中添加以下方法
|
||
|
||
def editGUI2DPosition(self, gui_element, axis, value):
|
||
"""编辑2D GUI元素位置并应用边界约束"""
|
||
try:
|
||
gui_type = gui_element.getTag("gui_type")
|
||
|
||
if gui_type in ["button", "label", "entry", "2d_image"]:
|
||
# 2D元素使用屏幕坐标,需要转换
|
||
current_pos = gui_element.getPos()
|
||
|
||
if axis == "x":
|
||
# 转换逻辑坐标到屏幕坐标
|
||
screen_x = value * 0.1
|
||
# 应用边界约束
|
||
constrained_pos = self.constrain2DPosition(gui_element, new_x=screen_x)
|
||
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
|
||
elif axis == "z":
|
||
screen_z = value * 0.1
|
||
# 应用边界约束
|
||
constrained_pos = self.constrain2DPosition(gui_element, new_z=screen_z)
|
||
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
|
||
else:
|
||
return False
|
||
|
||
gui_element.setPos(*new_pos)
|
||
print(f"✓ 更新2D GUI元素位置: {axis}={value} (约束后位置: {new_pos})")
|
||
return True
|
||
else:
|
||
print(f"✗ 不支持的GUI类型进行2D位置编辑: {gui_type}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 更新2D GUI元素位置失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def editGUI3DPosition(self, gui_element, axis, value):
|
||
"""编辑3D GUI元素位置"""
|
||
try:
|
||
gui_type = gui_element.getTag("gui_type")
|
||
|
||
if gui_type in ["3d_text", "3d_image"]:
|
||
current_pos = gui_element.getPos()
|
||
|
||
if axis == "x":
|
||
new_pos = (value, current_pos.getY(), current_pos.getZ())
|
||
elif axis == "y":
|
||
new_pos = (current_pos.getX(), value, current_pos.getZ())
|
||
elif axis == "z":
|
||
new_pos = (current_pos.getX(), current_pos.getY(), value)
|
||
else:
|
||
return False
|
||
|
||
gui_element.setPos(*new_pos)
|
||
print(f"✓ 更新3D GUI元素位置: {axis}={value}")
|
||
return True
|
||
else:
|
||
print(f"✗ 不支持的GUI类型进行3D位置编辑: {gui_type}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 更新3D GUI元素位置失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def editGUIScale(self, gui_element, axis, value):
|
||
"""编辑GUI元素缩放"""
|
||
try:
|
||
gui_type = gui_element.getTag("gui_type")
|
||
current_scale = gui_element.getScale()
|
||
|
||
# 确保缩放值不为0
|
||
if value == 0:
|
||
value = 0.01
|
||
|
||
if gui_type in ["3d_text", "3d_image"]:
|
||
# 3D元素处理
|
||
if axis == "x":
|
||
new_scale = (value, current_scale.getY(), current_scale.getZ())
|
||
elif axis == "y":
|
||
new_scale = (current_scale.getX(), value, current_scale.getZ())
|
||
elif axis == "z":
|
||
new_scale = (current_scale.getX(), current_scale.getY(), value)
|
||
else:
|
||
return False
|
||
|
||
gui_element.setScale(*new_scale)
|
||
elif gui_type == "2d_image":
|
||
# 2D图像特殊处理 - 分别控制宽度和高度
|
||
if axis == "x":
|
||
# X轴控制宽度
|
||
gui_element.setScale(value, current_scale.getZ() if hasattr(current_scale, 'getZ')
|
||
else current_scale[1] if isinstance(current_scale, (list, tuple))
|
||
else current_scale)
|
||
elif axis == "z":
|
||
# Z轴控制高度
|
||
gui_element.setScale(current_scale.getX() if hasattr(current_scale, 'getX')
|
||
else current_scale[0] if isinstance(current_scale, (list, tuple))
|
||
else current_scale, value)
|
||
else:
|
||
# 其他情况使用统一缩放
|
||
gui_element.setScale(value)
|
||
gui_element.setTransparency(TransparencyAttrib.MAlpha)
|
||
else:
|
||
# 其他2D元素处理
|
||
if axis in ["x", "z"]: # 对于2D图像,x和z分别代表宽度和高度
|
||
# 保持原有缩放比例,仅调整指定轴
|
||
if axis == "x":
|
||
gui_element.setScale(value,
|
||
current_scale.getZ() if hasattr(current_scale, 'getZ')
|
||
else current_scale[1] if isinstance(current_scale, (list, tuple))
|
||
else current_scale)
|
||
elif axis == "z":
|
||
gui_element.setScale(
|
||
current_scale.getX() if hasattr(current_scale, 'getX')
|
||
else current_scale[0] if isinstance(current_scale, (list, tuple))
|
||
else current_scale, value)
|
||
else:
|
||
# 对于其他2D元素,使用统一缩放
|
||
gui_element.setScale(value)
|
||
|
||
print(f"✓ 更新GUI元素缩放: {axis}={value}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"✗ 更新GUI元素缩放失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def createCesiumView(self, main_window=None):
|
||
"""创建 Cesium 视图窗口(离线版本)"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("❌ 无法创建Cesium视图: Web引擎不可用")
|
||
return None
|
||
|
||
try:
|
||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||
from PyQt5.QtWidgets import QDockWidget
|
||
from PyQt5.QtCore import QUrl
|
||
import os
|
||
|
||
# 尝试获取主窗口引用
|
||
if main_window is None:
|
||
print("🔍 尝试获取主窗口引用...")
|
||
|
||
# 检查各种可能的主窗口引用
|
||
if hasattr(self.world, 'interface_manager'):
|
||
print(f" - interface_manager 存在: {self.world.interface_manager}")
|
||
if hasattr(self.world.interface_manager, 'main_window'):
|
||
main_window = self.world.interface_manager.main_window
|
||
print(f" - interface_manager.main_window: {main_window}")
|
||
|
||
if main_window is None and hasattr(self.world, 'main_window'):
|
||
main_window = self.world.main_window
|
||
print(f" - world.main_window: {main_window}")
|
||
|
||
# 如果仍然没有主窗口,尝试从树形控件获取
|
||
if main_window is None and self.world.treeWidget:
|
||
try:
|
||
main_window = self.world.treeWidget.window()
|
||
print(f" - 从 treeWidget 获取窗口: {main_window}")
|
||
except:
|
||
pass
|
||
|
||
if main_window is None:
|
||
print("✗ 无法获取主窗口引用")
|
||
return None
|
||
else:
|
||
print(f"✅ 使用传入的主窗口引用: {main_window}")
|
||
|
||
# 检查主窗口是否有效
|
||
if not hasattr(main_window, 'addDockWidget'):
|
||
print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法")
|
||
return None
|
||
|
||
# 检查是否已经存在 Cesium 视图
|
||
for element in self.gui_elements:
|
||
if hasattr(element, 'objectName') and element.objectName() == "CesiumView":
|
||
print("⚠ Cesium 视图已经存在")
|
||
# 将其前置显示
|
||
element.show()
|
||
element.raise_()
|
||
return element
|
||
|
||
# 创建停靠窗口
|
||
print(f"🔧 创建 Cesium 停靠窗口,父窗口: {main_window}")
|
||
cesium_dock = QDockWidget("Cesium 地图视图(离线)", main_window)
|
||
cesium_dock.setObjectName("CesiumView")
|
||
|
||
# 创建 Web 视图
|
||
self.cesium_view = QWebEngineView()
|
||
|
||
# 使用本地 HTML 文件(离线模式)
|
||
local_html_path = os.path.abspath("./cesium_offline.html")
|
||
if os.path.exists(local_html_path):
|
||
print(f"🌐 加载离线 Cesium: file://{local_html_path}")
|
||
self.cesium_view.load(QUrl(f"file://{local_html_path}"))
|
||
else:
|
||
print("⚠️ 离线文件不存在,使用在线版本")
|
||
self.cesium_view.load(QUrl("http://localhost:8080/Apps/HelloWorld.html"))
|
||
|
||
# 设置内容
|
||
cesium_dock.setWidget(self.cesium_view)
|
||
|
||
# 添加到主窗口
|
||
print("📍 将 Cesium 视图添加到主窗口")
|
||
main_window.addDockWidget(Qt.RightDockWidgetArea, cesium_dock)
|
||
|
||
# 添加到GUI元素列表以便管理
|
||
self.gui_elements.append(cesium_dock)
|
||
|
||
print("✓ Cesium 离线视图已创建并集成到项目中")
|
||
return cesium_dock
|
||
|
||
except Exception as e:
|
||
print(f"✗ 创建 Cesium 视图失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def toggleCesiumView(self):
|
||
"""切换 Cesium 视图显示状态"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用,无法切换 Cesium 视图")
|
||
return None
|
||
|
||
try:
|
||
# 查找现有的 Cesium 视图
|
||
cesium_dock = None
|
||
cesium_index = -1
|
||
for i, element in enumerate(self.gui_elements):
|
||
if hasattr(element, 'objectName') and element.objectName() == "CesiumView":
|
||
cesium_dock = element
|
||
cesium_index = i
|
||
break
|
||
|
||
# 如果存在则移除,否则创建
|
||
if cesium_dock:
|
||
# 获取主窗口引用以正确移除停靠窗口
|
||
main_window = None
|
||
if (hasattr(self.world, 'interface_manager') and
|
||
hasattr(self.world.interface_manager, 'main_window') and
|
||
self.world.interface_manager.main_window):
|
||
main_window = self.world.interface_manager.main_window
|
||
elif hasattr(self.world, 'main_window') and self.world.main_window:
|
||
main_window = self.world.main_window
|
||
|
||
if main_window and hasattr(main_window, 'removeDockWidget'):
|
||
main_window.removeDockWidget(cesium_dock)
|
||
|
||
# 从列表中移除
|
||
if cesium_index >= 0:
|
||
self.gui_elements.pop(cesium_index)
|
||
|
||
print("✓ Cesium 视图已隐藏")
|
||
return None
|
||
else:
|
||
return self.createCesiumView()
|
||
|
||
except Exception as e:
|
||
print(f"✗ 切换 Cesium 视图失败: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
def refreshCesiumView(self):
|
||
"""刷新 Cesium 视图"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用,无法刷新 Cesium 视图")
|
||
return False
|
||
|
||
try:
|
||
for element in self.gui_elements:
|
||
if hasattr(element, 'objectName') and element.objectName() == "CesiumView":
|
||
web_view = element.widget()
|
||
if isinstance(web_view, QWebEngineView):
|
||
web_view.reload()
|
||
print("✓ Cesium 视图已刷新")
|
||
return True
|
||
print("⚠ 未找到 Cesium 视图")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 刷新 Cesium 视图失败: {str(e)}")
|
||
return False
|
||
|
||
def updateCesiumURL(self, url):
|
||
"""更新 Cesium 视图的 URL"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用,无法更新 Cesium URL")
|
||
return False
|
||
|
||
try:
|
||
for element in self.gui_elements:
|
||
if hasattr(element, 'objectName') and element.objectName() == "CesiumView":
|
||
web_view = element.widget()
|
||
if isinstance(web_view, QWebEngineView):
|
||
from PyQt5.QtCore import QUrl
|
||
web_view.load(QUrl(url))
|
||
print(f"✓ Cesium URL 已更新为: {url}")
|
||
return True
|
||
print("⚠ 未找到 Cesium 视图")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 更新 Cesium URL 失败: {str(e)}")
|
||
return False
|
||
|
||
# 在 GUIManager 类中添加以下方法
|
||
|
||
def addModelToCesium(self, model_id, model_url, longitude, latitude, height=0, scale=1.0):
|
||
"""向 Cesium 添加模型"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用,无法操作 Cesium")
|
||
return False
|
||
|
||
try:
|
||
# 查找 Cesium 视图
|
||
cesium_view = None
|
||
for element in self.gui_elements:
|
||
if (hasattr(element, 'objectName') and
|
||
element.objectName() == "CesiumView" and
|
||
hasattr(element, 'widget')):
|
||
cesium_view = element.widget()
|
||
break
|
||
|
||
if not cesium_view:
|
||
print("✗ 未找到 Cesium 视图")
|
||
return False
|
||
|
||
# 转义特殊字符以防止 JavaScript 语法错误
|
||
escaped_model_id = str(model_id).replace("'", "\\'")
|
||
escaped_model_url = str(model_url).replace("'", "\\'").replace("\\", "/")
|
||
|
||
# 构造 JavaScript 调用
|
||
js_code = f"""
|
||
(function() {{
|
||
if (window.CesiumAPI && typeof window.CesiumAPI.addModel === 'function') {{
|
||
try {{
|
||
var result = window.CesiumAPI.addModel(
|
||
'{escaped_model_id}',
|
||
'{escaped_model_url}',
|
||
{{
|
||
longitude: {longitude},
|
||
latitude: {latitude},
|
||
height: {height}
|
||
}},
|
||
{scale}
|
||
);
|
||
console.log('Cesium 添加模型结果:', result);
|
||
return result || {{success: true, message: 'Model added'}};
|
||
}} catch (error) {{
|
||
console.error('JavaScript 错误:', error);
|
||
return {{success: false, message: 'JavaScript error: ' + error.message}};
|
||
}}
|
||
}} else {{
|
||
console.error('CesiumAPI.addModel 不可用');
|
||
return {{success: false, message: 'CesiumAPI.addModel not available'}};
|
||
}}
|
||
}})();
|
||
"""
|
||
|
||
# 定义回调函数处理结果
|
||
def handle_result(result):
|
||
try:
|
||
if isinstance(result, dict):
|
||
if result.get('success', False):
|
||
print(f"✓ 成功在 Cesium 中添加模型: {model_id}")
|
||
else:
|
||
print(f"✗ 在 Cesium 中添加模型失败: {result.get('message', 'Unknown error')}")
|
||
else:
|
||
print(f"✓ 已发送添加模型请求: {model_id}")
|
||
except Exception as callback_error:
|
||
print(f"✗ 处理回调结果时出错: {callback_error}")
|
||
|
||
# 执行 JavaScript 并获取结果
|
||
cesium_view.page().runJavaScript(js_code, handle_result)
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 添加模型到 Cesium 失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
# 添加新的方法来集成 Panda3D 场景中的 Cesium Tiles
|
||
def addCesiumTilesetToScene(self, tileset_name, tileset_url, position=(0, 0, 0)):
|
||
"""在 Panda3D 场景中添加 Cesium 3D Tiles"""
|
||
try:
|
||
# 使用场景管理器加载 tileset
|
||
tileset_node = self.world.scene_manager.load_cesium_tileset(tileset_url, position)
|
||
|
||
if tileset_node:
|
||
# 添加到 GUI 元素列表以便管理
|
||
self.gui_elements.append({
|
||
'type': 'cesium_tileset',
|
||
'name': tileset_name,
|
||
'node': tileset_node,
|
||
'url': tileset_url
|
||
})
|
||
|
||
print(f"✓ 在场景中添加 Cesium tileset: {tileset_name}")
|
||
return tileset_node
|
||
else:
|
||
print(f"✗ 在场景中添加 Cesium tileset 失败: {tileset_name}")
|
||
return None
|
||
|
||
except Exception as e:
|
||
print(f"✗ 在场景中添加 Cesium tileset 出错: {e}")
|
||
return None
|
||
|
||
def removeModelFromCesium(self, model_id):
|
||
"""从 Cesium 移除模型"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用")
|
||
return False
|
||
|
||
try:
|
||
# 查找 Cesium 视图
|
||
cesium_view = None
|
||
for element in self.gui_elements:
|
||
if (hasattr(element, 'objectName') and
|
||
element.objectName() == "CesiumView" and
|
||
hasattr(element, 'widget')):
|
||
cesium_view = element.widget()
|
||
break
|
||
|
||
if not cesium_view:
|
||
print("✗ 未找到 Cesium 视图")
|
||
return False
|
||
|
||
# 构造 JavaScript 调用
|
||
js_code = f"""
|
||
if (window.CesiumAPI && typeof window.CesiumAPI.removeModel === 'function') {{
|
||
var result = window.CesiumAPI.removeModel('{model_id}');
|
||
result;
|
||
}} else {{
|
||
{{success: false, message: 'CesiumAPI.removeModel not available'}};
|
||
}}
|
||
"""
|
||
|
||
# 定义回调函数处理结果
|
||
def handle_result(result):
|
||
if result and isinstance(result, dict):
|
||
if result.get('success', False):
|
||
print(f"✓ 成功从 Cesium 中移除模型: {model_id}")
|
||
else:
|
||
print(f"✗ 从 Cesium 中移除模型失败: {result.get('message', 'Unknown error')}")
|
||
else:
|
||
print(f"✓ 已发送移除模型请求: {model_id} (无法获取详细结果)")
|
||
|
||
# 执行 JavaScript 并获取结果
|
||
cesium_view.page().runJavaScript(js_code, handle_result)
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 从 Cesium 移除模型失败: {e}")
|
||
return False
|
||
|
||
def updateCesiumModelPosition(self, model_id, longitude, latitude, height=0):
|
||
"""更新 Cesium 中模型的位置"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用")
|
||
return False
|
||
|
||
try:
|
||
# 查找 Cesium 视图
|
||
cesium_view = None
|
||
for element in self.gui_elements:
|
||
if (hasattr(element, 'objectName') and
|
||
element.objectName() == "CesiumView" and
|
||
hasattr(element, 'widget')):
|
||
cesium_view = element.widget()
|
||
break
|
||
|
||
if not cesium_view:
|
||
print("✗ 未找到 Cesium 视图")
|
||
return False
|
||
|
||
# 使用更安全的 JavaScript 字符串构造方式
|
||
escaped_model_id = model_id.replace("'", "\\'")
|
||
|
||
# 构造 JavaScript 调用
|
||
js_code = f"""
|
||
(function() {{
|
||
if (window.CesiumAPI && typeof window.CesiumAPI.updateModelPosition === 'function') {{
|
||
try {{
|
||
var result = window.CesiumAPI.updateModelPosition(
|
||
'{escaped_model_id}',
|
||
{{
|
||
longitude: {longitude},
|
||
latitude: {latitude},
|
||
height: {height}
|
||
}}
|
||
);
|
||
return result || {{success: true, message: 'Position updated'}};
|
||
}} catch (error) {{
|
||
return {{success: false, message: 'JavaScript error: ' + error.message}};
|
||
}}
|
||
}} else {{
|
||
return {{success: false, message: 'CesiumAPI.updateModelPosition not available'}};
|
||
}}
|
||
}})();
|
||
"""
|
||
|
||
# 定义回调函数处理结果
|
||
def handle_result(result):
|
||
try:
|
||
if isinstance(result, dict):
|
||
if result.get('success', False):
|
||
print(f"✓ 成功更新 Cesium 中模型位置: {model_id}")
|
||
else:
|
||
print(f"✗ 更新 Cesium 中模型位置失败: {result.get('message', 'Unknown error')}")
|
||
else:
|
||
print(f"✓ 已发送更新模型位置请求: {model_id}")
|
||
except Exception as callback_error:
|
||
print(f"✗ 处理回调结果时出错: {callback_error}")
|
||
|
||
# 执行 JavaScript 并获取结果
|
||
cesium_view.page().runJavaScript(js_code, handle_result)
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"✗ 更新 Cesium 中模型位置失败: {e}")
|
||
return False
|
||
|
||
def getAllCesiumModels(self):
|
||
"""获取 Cesium 中所有模型的列表"""
|
||
if not WEB_ENGINE_AVAILABLE:
|
||
print("✗ QtWebEngineWidgets 不可用")
|
||
return None
|
||
|
||
try:
|
||
# 查找 Cesium 视图
|
||
cesium_view = None
|
||
for element in self.gui_elements:
|
||
if (hasattr(element, 'objectName') and
|
||
element.objectName() == "CesiumView" and
|
||
hasattr(element, 'widget')):
|
||
cesium_view = element.widget()
|
||
break
|
||
|
||
if not cesium_view:
|
||
print("✗ 未找到 Cesium 视图")
|
||
return None
|
||
|
||
# 构造 JavaScript 调用
|
||
js_code = """
|
||
if (window.CesiumAPI && typeof window.CesiumAPI.getAllModels === 'function') {
|
||
var result = window.CesiumAPI.getAllModels();
|
||
JSON.stringify(result);
|
||
} else {
|
||
JSON.stringify({success: false, message: 'CesiumAPI.getAllModels not available'});
|
||
}
|
||
"""
|
||
|
||
# 定义回调函数处理结果
|
||
def handle_result(result):
|
||
try:
|
||
if isinstance(result, str):
|
||
import json
|
||
result = json.loads(result)
|
||
|
||
if result and result.get('success', False):
|
||
models = result.get('models', [])
|
||
print(f"✓ Cesium 中的模型列表: {models}")
|
||
return models
|
||
else:
|
||
print(f"✗ 获取 Cesium 模型列表失败: {result.get('message', 'Unknown error')}")
|
||
return []
|
||
except Exception as e:
|
||
print(f"✗ 解析 Cesium 模型列表结果失败: {e}")
|
||
return []
|
||
|
||
# 执行 JavaScript 并获取结果
|
||
cesium_view.page().runJavaScript(js_code)
|
||
return None # 异步操作,实际结果通过回调处理
|
||
|
||
except Exception as e:
|
||
print(f"✗ 获取 Cesium 模型列表失败: {e}")
|
||
return None
|
||
|
||
# 添加一个便捷方法来加载本地模型文件
|
||
def addLocalModelToCesium(self, model_id, local_model_path, longitude, latitude, height=0, scale=1.0):
|
||
"""向 Cesium 添加本地模型文件"""
|
||
try:
|
||
# 将本地路径转换为相对路径或 URL
|
||
import os
|
||
if os.path.exists(local_model_path):
|
||
# 如果 Cesium 服务器可以访问该路径,可以直接使用
|
||
# 否则需要将模型文件放在 Cesium 的静态资源目录中
|
||
model_url = local_model_path.replace('\\', '/') # 确保使用正斜杠
|
||
return self.addModelToCesium(model_id, model_url, longitude, latitude, height, scale)
|
||
else:
|
||
print(f"✗ 模型文件不存在: {local_model_path}")
|
||
return False
|
||
except Exception as e:
|
||
print(f"✗ 添加本地模型失败: {e}")
|
||
return False
|