EG/ui/interface_manager.py
Hector 78dae899cf 3d_image和2d_image
根据高度图创建地形
2025-08-28 15:45:32 +08:00

360 lines
13 KiB
Python
Raw 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.

from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle
from PyQt5.QtCore import Qt
from PyQt5.sip import delete
from panda3d.core import GeomNode, ModelRoot
class InterfaceManager:
"""界面管理器 - 处理树形控件和UI交互"""
def __init__(self, world):
"""初始化界面管理器"""
self.world = world
self.treeWidget = None
self._expanded_paths = set()
def setTreeWidget(self, treeWidget):
"""设置树形控件引用并更新场景树"""
self.treeWidget = treeWidget
# 添加右键菜单
self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
self.treeWidget.customContextMenuRequested.connect(self.showTreeContextMenu)
# 更新场景树
self.world.scene_manager.updateSceneTree()
def onTreeItemClicked(self, item, column):
"""处理树形控件项目点击事件"""
if not item:
self.world.selection.updateSelection(None)
return
self.world.property_panel.updatePropertyPanel(item)
# 获取节点对象
nodePath = item.data(0, Qt.UserRole)
if nodePath:
# 更新选择状态
self.world.selected_np = nodePath
self.world.selection.updateSelection(nodePath)
# 更新属性面板
#self.world.property_panel.updatePropertyPanel(item)
print(f"树形控件点击: {item.text(0)}")
else:
# 如果没有节点对象,清除选择
self.world.selection.updateSelection(None)
#self.world.property_panel.clearPropertyPanel()
def showTreeContextMenu(self, position):
"""显示树形控件的右键菜单"""
item = self.treeWidget.itemAt(position)
if not item:
return
# 获取节点对象
nodePath = item.data(0, Qt.UserRole)
if not nodePath:
return
# 创建菜单
menu = QMenu()
# 检查是否是GUI元素
if hasattr(nodePath, 'getTag') and nodePath.getTag("gui_type"):
# GUI元素菜单
editAction = menu.addAction("编辑")
editAction.triggered.connect(lambda: self.world.gui_manager.editGUIElementDialog(nodePath))
deleteAction = menu.addAction("删除GUI元素")
deleteAction.triggered.connect(lambda: self.world.gui_manager.deleteGUIElement(nodePath))
duplicateAction = menu.addAction("复制")
duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath))
elif hasattr(nodePath,'getTag') and nodePath.getTag("element_type") == "cesium_tileset":
deleteAction = menu.addAction("删除 Cesium Tileset")
deleteAction.triggered.connect(lambda:self.deleteCesiumTileset(nodePath,item))
else:
# 为模型节点或其子节点添加删除选项
parentItem = item.parent()
if parentItem:
if self.isModelOrChild(item):
deleteAction = menu.addAction("删除")
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
else:
deleteAction = menu.addAction("删除")
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
# 显示菜单
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
def deleteCesiumTileset(self, nodePath, item):
"""删除 Cesium tileset"""
try:
# 从场景中移除
nodePath.removeNode()
# 从 tilesets 列表中移除
if hasattr(self.world, 'scene_manager'):
tilesets_to_remove = []
for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
if tileset_info['node'] == nodePath:
tilesets_to_remove.append(i)
# 从后往前删除,避免索引问题
for i in reversed(tilesets_to_remove):
del self.world.scene_manager.tilesets[i]
# 从树形控件中移除
parentItem = item.parent()
if parentItem:
parentItem.removeChild(item)
print(f"成功删除 Cesium tileset: {nodePath.getName()}")
# 清空属性面板和选择框
self.world.property_panel.clearPropertyPanel()
self.world.selection.updateSelection(None)
# 更新场景树
self.updateSceneTree()
except Exception as e:
print(f"删除 Cesium tileset 失败: {str(e)}")
def isModelOrChild(self, item):
"""检查是否是模型节点或其子节点"""
while item and item.parent():
if item.parent().text(0) == "模型":
return True
item = item.parent()
return False
def deleteNode(self, nodePath, item):
"""删除节点"""
try:
item_data = item.data(0,Qt.UserRole+1)
if item_data == "terrain":
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
terrain_to_remove = None
for terrain_info in self.world.terrain_manager.terrains:
if terrain_info['node'] == nodePath:
terrain_to_remove = terrain_info
break
if terrain_to_remove:
self.world.terrain_manager.deleteTerrain(terrain_to_remove)
print(f"成功删除地形节点:{nodePath.getName()}")
self.updateSceneTree()
self.world.property_panel.clearPropertyPanel()
self.world.selection.updateSelection(None)
return
# 从场景中移除
self.world.property_panel.removeActorForModel(nodePath)
if hasattr(nodePath,'getPythonTag'):
light_object = nodePath.getPythonTag('rp_light_object')
if light_object and hasattr(self.world,'render_pipeline'):
self.world.render_pipeline.remove_light(light_object)
if hasattr(self.world,'selection'):
if self.world.selection.selectedNode == nodePath:
self.world.selection.clearSelectionBox()
self.world.selection.clearGizmo()
self.world.selection.selectedNode = None
self.world.selection.selectedObject = None
if nodePath in self.world.Spotlight:
self.world.Spotlight.remove(nodePath)
if nodePath in self.world.Pointlight:
self.world.Pointlight.remove(nodePath)
nodePath.removeNode()
if hasattr(self.world,'selection'):
self.world.selection.checkAndClearIfTargetDeleted()
# 如果是模型根节点,从模型列表中移除
#if item.parent().text(0) == "模型":
if nodePath in self.world.models:
self.world.models.remove(nodePath)
# 从树形控件中移除
parentItem = item.parent()
if parentItem:
parentItem.removeChild(item)
print(f"成功删除节点: {nodePath.getName()}")
# 清空属性面板和选择框
self.world.property_panel.clearPropertyPanel()
self.world.selection.updateSelection(None)
except Exception as e:
print(f"删除节点失败: {str(e)}")
def updateSceneTree(self):
"""更新场景树显示 - 实际实现"""
if not self.treeWidget:
return
self._expanded_paths.clear()
self._collect_expanded()
print("\n=== 更新场景树 ===")
self.treeWidget.clear()
# 创建场景根节点
sceneRoot = QTreeWidgetItem(self.treeWidget, ['场景'])
# 添加相机节点
cameraItem = QTreeWidgetItem(sceneRoot, ['相机'])
cameraItem.setData(0, Qt.UserRole, self.world.cam)
print("添加相机节点")
BLACK_LIST = {'','**','temp','collision'}
from panda3d.core import CollisionNode
def should_skip(node):
name = node.getName()
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(),CollisionNode) or isinstance(node.node(),ModelRoot) or name==""
def addNodeToTree(node,parentItem,force = False):
if node.isEmpty():
return
if not force and should_skip(node):
return
nodeItem = QTreeWidgetItem(parentItem,[node.getName()])
nodeItem.setData(0,Qt.UserRole,node)
for child in node.getChildren():
addNodeToTree(child,nodeItem,force = False)
for model in self.world.models:
if not model.isEmpty():
addNodeToTree(model, sceneRoot,force=True)
# 添加所有GUI元素
for gui in self.world.gui_elements:
# 检查是否是有效的GUI节点具有getTag方法的NodePath
if hasattr(gui, 'getTag') and hasattr(gui, 'getName'):
gui_type = gui.getTag("gui_type") or "unknown"
gui_text = gui.getTag("text") or gui.getTag("gui_text") or "GUI元素"
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
item.setData(0, Qt.UserRole, gui)
else:
# 跳过非GUI节点如QDockWidget等Qt对象
continue
# 添加地板节点
if hasattr(self.world, 'ground') and self.world.ground:
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
groundItem.setData(0, Qt.UserRole, self.world.ground)
#添加灯光节点
for light in self.world.Spotlight + self.world.Pointlight:
if not light.isEmpty:
addNodeToTree(light, sceneRoot, force=True)
#添加 Cesium tilesets
if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'tilesets'):
for i , tileset_info in enumerate(self.world.scene_manager.tilesets):
tileset_node = tileset_info['node']
if not tileset_node.isEmpty():
tileset_url = tileset_info['url']
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
tileset_item.setData(0,Qt.UserRole,tileset_node)
addNodeToTree(tileset_node,tileset_item,force=True)
#添加地形节点
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
for terrain_info in self.world.terrain_manager.terrains:
terrain_node = terrain_info['node']
terrain_name = terrain_info.get('name','未知地形')
terrain_item = QTreeWidgetItem(sceneRoot,[f"地形:{terrain_name}"])
terrain_item.setData(0,Qt.UserRole,terrain_node)
terrain_item.setData(0,Qt.UserRole+1,"terrain") # 标记为地形节点
addNodeToTree(terrain_node,terrain_item,force=True)
# 展开所有节点
#self.treeWidget.expandAll()
self._restore_expanded()
print("=== 场景树更新完成 ===\n")
def findTreeItem(self, node, parentItem):
"""在树形控件中查找指定的节点项"""
for i in range(parentItem.childCount()):
item = parentItem.child(i)
itemNode = item.data(0, Qt.UserRole)
if itemNode == node:
return item
# 递归查找子项
if item.childCount() > 0:
found = self.findTreeItem(node, item)
if found:
return found
return None
def _collect_expanded(self,item=None,prefix=""):
if item is None:
root = self.treeWidget.invisibleRootItem()
for i in range(root.childCount()):
self._collect_expanded(root.child(i),prefix)
return
path = f"{prefix}/{item.text(0)}"
if item.isExpanded():
self._expanded_paths.add(path)
for i in range(item.childCount()):
self._collect_expanded(item.child(i),path)
def _restore_expanded(self,item=None,prefix=""):
if item is None:
root = self.treeWidget.invisibleRootItem()
for i in range(root.childCount()):
self._restore_expanded(root.child(i),prefix)
return
path = f"{prefix}/{item.text(0)}"
if path in self._expanded_paths:
item.setExpanded(True)
for i in range(item.childCount()):
self._restore_expanded(item.child(i),path)
def syncVisibilityDownward(self):
from collections import deque
if not self.world.models:
return
q = deque()
for root in self.world.models:
q.append(root)
while q:
node = q.popleft()
visible = node.getPythonTag("visible")
if visible is None:
visible = True
if not visible:
stack = [node]
while stack:
cur = stack.pop()
cur.hide()
for child in cur.getChildren():
stack.append(child)
continue
node.show()
for child in node.getChildren():
q.append(child)