1
0
forked from Rowland/EG
EG/ui/interface_manager.py
2025-10-20 09:30:27 +08:00

470 lines
19 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 PIL.ImageChops import lighter
from PyQt5.QtGui import QFont
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 onTreeWidgetClicked(self, index):
"""处理树形控件点击事件(包括空白区域)"""
# 检查点击的是否是空白区域
if not self.treeWidget.itemFromIndex(index): # 点击的是空白区域
self.world.selection.updateSelection(None)
self.world.property_panel.clearPropertyPanel()
print("点击树形控件空白区域,清除选中状态")
def onTreeCurrentItemChanged(self, current, previous):
"""处理树形控件当前选中项改变事件"""
# 当 current 为 None 时,表示点击了空白区域
if current is None:
self.world.selection.updateSelection(None)
print("点击空白区域,清除选中状态")
# 当 current 不为 None 时,表示选中了某个项目
else:
# 更新选择状态
nodePath = current.data(0, Qt.UserRole)
if nodePath:
self.world.selected_np = nodePath
self.world.selection.updateSelection(nodePath)
self.world.property_panel.updatePropertyPanel(current)
print(f"树形控件选中项改变: {current.text(0)}")
def onTreeItemClicked(self, item, column):
"""处理树形控件项目点击事件"""
#print(f"树形控件点击事件触发item: {item}, column: {column}")
# 检查是否点击了空白区域
# 当点击空白区域时item可能是一个空的QTreeWidgetItem对象
if not item or (item.text(0) == "" and item.data(0, Qt.UserRole) is None):
self.world.selection.updateSelection(None)
print("点击空白区域,清除选中状态")
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)
print(f"树形控件点击: {item.text(0)}")
else:
# 如果没有节点对象,清除选择
self.world.selection.updateSelection(None)
self.world.property_panel.clearPropertyPanel()
print("点击了无数据项,清除选中状态")
# 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:
# 如果是灯光节点,直接调用灯光删除方法
if self.isLightNode(nodePath):
self.deleteLightNode(nodePath, item)
return
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._recursiveRemoveLights(nodePath)
# 从场景中移除
self.world.property_panel.removeActorForModel(nodePath)
# 清除选择状态
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 not nodePath.isEmpty():
# 先递归删除所有子节点
children = list(nodePath.getChildren())
for child in children:
try:
if not child.isEmpty():
child.removeNode()
except Exception as e:
print(f"删除子节点失败: {str(e)}")
# 再删除节点本身
nodePath.removeNode()
if hasattr(self.world, 'selection'):
self.world.selection.checkAndClearIfTargetDeleted()
if hasattr(self.world, 'models') and 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)}")
import traceback
traceback.print_exc()
def _cleanupAllLightsInSubtree(self,parentNode):
try:
if parentNode.isEmpty():
return
#收集所有子节点
all_nodes = []
def collect_all_nodes(node):
if not node.isEmpty():
all_nodes.append(node)
for child in node.getChildren():
collect_all_nodes(child)
collect_all_nodes(parentNode)
ligths_processed = 0
for node in all_nodes:
if self.isLightNode(node):
if hasattr(node,'getPythonTag'):
light_object = node.getPythonTag('rp_light_object')
if light_object and hasattr(self.world,'render_pipeline'):
try:
self.world.render_pipeline.remove_light(light_object)
print(f"✓ 从渲染管线移除灯光: {node.getName()}")
except Exception as e:
print(f"⚠ 从渲染管线移除灯光失败: {str(e)}")
#从灯光列表中移除
try:
if node in self.world.Spotlight:
self.world.Spotlight.remove(node)
print(f"从Spotlight列表移除{node.getName()}")
if node in self.world.Pointlight:
self.world.Pointlight.remove(node)
print(f"从pointlight列表移除{node.getName()}")
except Exception as e:
print(f"从灯列表移除失败{str(e)}")
ligths_processed += 1
if ligths_processed>0:
print(f"清理{ligths_processed}个灯光节点")
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, ['场景'])
sceneRoot.setData(0, Qt.UserRole, self.world.render)
sceneRoot.setData(0, Qt.UserRole + 1, "SCENE_ROOT")
font = QFont("Microsoft YaHei", 10, QFont.Light) # 假设您希望字体大小为10
sceneRoot.setFont(0, font)
# 添加相机节点
cameraItem = QTreeWidgetItem(sceneRoot, ['相机'])
cameraItem.setData(0, Qt.UserRole, self.world.cam)
cameraItem.setData(0, Qt.UserRole + 1, "CAMERA_NODE")
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)
groundItem.setData(0,Qt.UserRole + 1, "SCENE_NODE")
ground_nodes = [
('ground2','地板2'),
('ground3','地板3'),
('ground4','地板4'),
('ground5','地板5'),
('ground6','地板6')
]
for attr_name,display_name in ground_nodes:
if hasattr(self.world,attr_name):
ground_node = getattr(self.world,attr_name)
if ground_node:
extraGroundItem = QTreeWidgetItem(sceneRoot,[display_name])
extraGroundItem.setData(0,Qt.UserRole,ground_node)
extraGroundItem.setData(0,Qt.UserRole+1,"SCENE_NODE")
#添加灯光节点
for light in self.world.Spotlight:
if light:
addNodeToTree(light, sceneRoot, force=True)
for light in self.world.Pointlight:
if light:
addNodeToTree(light, sceneRoot, force=True)
# for light in self.world.Spotlight + self.world.Pointlight:
# if not light.isEmpty:
# print(f"33333333333333333333333333333{light}")
# 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)