from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle from PyQt5.QtGui import QFont 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 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)