forked from Rowland/EG
424 lines
16 KiB
Python
424 lines
16 KiB
Python
from PIL.ImageChops import lighter
|
||
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:
|
||
# 如果是灯光节点,直接调用灯光删除方法
|
||
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")
|
||
# 添加相机节点
|
||
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")
|
||
|
||
#添加灯光节点
|
||
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)
|