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)