from panda3d.core import (Point3, Point2, CollisionTraverser, CollisionHandlerQueue, CollisionNode, CollisionRay, GeomNode) class EventHandler: """事件处理器 - 处理鼠标和键盘输入事件""" def __init__(self, world): """初始化事件处理器""" self.world = world def mousePressEventLeft(self, evt): """处理鼠标左键按下事件""" print("\n=== 开始处理鼠标左键事件 ===") print(f"当前工具: {self.world.currentTool}") if not evt: print("事件为空") return # 获取鼠标点击的位置 x = evt.get('x', 0) y = evt.get('y', 0) print(f"鼠标点击位置: ({x}, {y})") # 获取准确的窗口尺寸 winWidth, winHeight = self.world.getWindowSize() # 直接使用 x, y 创建鼠标位置 mx = 2.0 * x / float(winWidth) - 1.0 my = 1.0 - 2.0 * y / float(winHeight) print(f"转换后的坐标: ({mx}, {my})") # 创建射线 nearPoint = Point3() farPoint = Point3() self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint) print(f"射线起点: {nearPoint}") print(f"射线终点: {farPoint}") # 进行射线检测 picker = CollisionTraverser() queue = CollisionHandlerQueue() pickerNode = CollisionNode('mouseRay') pickerNP = self.world.cam.attachNewNode(pickerNode) pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask()) # 使用 nearPoint 和 farPoint 创建射线 direction = farPoint - nearPoint direction.normalize() pickerNode.addSolid(CollisionRay(nearPoint, direction)) picker.addCollider(pickerNP, queue) picker.traverse(self.world.render) print(f"碰撞检测结果数量: {queue.getNumEntries()}") if queue.getNumEntries() > 0: # 获取最近的碰撞点 entry = queue.getEntry(0) hitPos = entry.getSurfacePoint(self.world.render) hitNode = entry.getIntoNodePath() print(f"碰撞到节点: {hitNode.getName()}") # 优先检查是否点击了坐标轴 print(f"检查坐标轴点击: 坐标轴存在={bool(self.world.selection.gizmo)}") if self.world.selection.gizmo: print("准备检查坐标轴点击...") gizmoAxis = self.world.selection.checkGizmoClick(x, y) if gizmoAxis: print(f"✓ 检测到坐标轴点击: {gizmoAxis}") # 开始坐标轴拖拽 self.world.selection.startGizmoDrag(gizmoAxis, x, y) return else: print("× 没有点击到坐标轴") # 处理GUI编辑模式 if self.world.guiEditMode: # 检查是否点击了GUI元素 clickedGUI = self.world.gui_manager.findClickedGUI(hitNode) if clickedGUI: # 选中GUI元素 self.world.selection.updateSelection(clickedGUI) self.world.gui_manager.selectGUIInTree(clickedGUI) print(f"选中GUI元素: {clickedGUI.getTag('gui_text')}") elif hasattr(self.world, 'currentGUITool') and self.world.currentGUITool: # 在点击位置创建新GUI元素 self.world.gui_manager.createGUIAtPosition(hitPos, self.world.currentGUITool) return # 根据当前工具处理点击事件 if self.world.currentTool == "选择": print("使用选择工具处理点击") self._handleSelectionClick(hitNode) else: print("没有检测到碰撞") # 如果不在GUI编辑模式,清除选择 if not self.world.guiEditMode: self.world.selection.updateSelection(None) # 在GUI编辑模式下,即使没有碰撞也可以在空白区域创建GUI if (self.world.guiEditMode and hasattr(self.world, 'currentGUITool') and self.world.currentGUITool): # 使用默认的地面高度创建GUI default_height = 0.0 world_pos = Point3(mx * 10, 0, my * 10) # 简单的屏幕到世界坐标转换 world_pos.setZ(default_height) self.world.gui_manager.createGUIAtPosition(world_pos, self.world.currentGUITool) pickerNP.removeNode() print("=== 鼠标左键事件处理结束 ===\n") def _handleSelectionClick(self, hitNode): """处理选择工具的点击事件""" # 查找可选择的节点(模型或其子节点) while hitNode != self.world.render: # 检查是否是模型或模型的子节点 isModel = hitNode in self.world.models isChildOfModel = False for model in self.world.models: # 检查是否是模型的子节点 current = hitNode while current != self.world.render: if current == model: isChildOfModel = True break current = current.getParent() if isChildOfModel: break print(f"检查节点 {hitNode.getName()}: isModel={isModel}, isChildOfModel={isChildOfModel}") if isModel or isChildOfModel: print(f"选中节点: {hitNode.getName()}") # 在树形控件中查找并选中对应的项 if self.world.interface_manager.treeWidget: root = self.world.interface_manager.treeWidget.invisibleRootItem() for i in range(root.childCount()): sceneItem = root.child(i) if sceneItem.text(0) == "场景": foundItem = self.world.interface_manager.findTreeItem(hitNode, sceneItem) if foundItem: self.world.interface_manager.treeWidget.setCurrentItem(foundItem) self.world.property_panel.updatePropertyPanel(foundItem) # 更新选择状态并显示选择框 self.world.selection.updateSelection(hitNode) break break hitNode = hitNode.getParent() def mouseReleaseEventLeft(self, evt): """处理鼠标左键释放事件""" # 处理坐标轴拖拽结束 if self.world.selection.isDraggingGizmo: self.world.selection.stopGizmoDrag() return def wheelForward(self, data=None): """处理滚轮向前滚动(前进)""" # 调用CoreWorld的父类方法 super(type(self.world), self.world).wheelForward(data) # 更新属性面板 if (self.world.interface_manager.treeWidget and self.world.interface_manager.treeWidget.currentItem() and self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"): self.world.property_panel.updatePropertyPanel( self.world.interface_manager.treeWidget.currentItem()) def wheelBackward(self, data=None): """处理滚轮向后滚动(后退)""" # 调用CoreWorld的父类方法 super(type(self.world), self.world).wheelBackward(data) # 更新属性面板 if (self.world.interface_manager.treeWidget and self.world.interface_manager.treeWidget.currentItem() and self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"): self.world.property_panel.updatePropertyPanel( self.world.interface_manager.treeWidget.currentItem()) def mousePressEventMiddle(self, evt): """处理鼠标中键按下事件""" # 已移除原有的Z轴拖拽功能 pass def mouseReleaseEventMiddle(self, evt): """处理鼠标中键释放事件""" # 已移除原有的Z轴拖拽功能 pass def mouseMoveEvent(self, evt): """处理鼠标移动事件""" if not evt: return # 处理坐标轴拖拽 if self.world.selection.isDraggingGizmo: x = evt.get('x', 0) y = evt.get('y', 0) # 获取准确的窗口尺寸 winWidth, winHeight = self.world.getWindowSize() # 将屏幕坐标转换为世界坐标 mx = 2.0 * x / float(winWidth) - 1.0 my = 1.0 - 2.0 * y / float(winHeight) # 更新坐标轴拖拽 self.world.selection.updateGizmoDrag(x, y) return # 更新坐标轴高亮(鼠标悬停效果) if self.world.selection.gizmo and not self.world.selection.isDraggingGizmo: x = evt.get('x', 0) y = evt.get('y', 0) # 只在前5次调用时输出调试信息,避免刷屏 if not hasattr(self.world, '_highlight_debug_count'): self.world._highlight_debug_count = 0 if self.world._highlight_debug_count < 5: print(f"更新坐标轴高亮: 鼠标({x}, {y}), 坐标轴存在={bool(self.world.selection.gizmo)}") self.world._highlight_debug_count += 1 self.world.selection.updateGizmoHighlight(x, y) # 调用CoreWorld的父类方法处理基础的相机旋转 super(type(self.world), self.world).mouseMoveEvent(evt)