#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 测试坐标轴拖拽功能 验证物体移动是否正常工作 """ import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QPushButton, QHBoxLayout from PyQt5.QtCore import Qt from QMeta3D.QMeta3DWidget import QMeta3DWidget from QMeta3D.Meta3DWorld import Meta3DWorld from panda3d.core import * from direct.task import Task class GizmoDragTestWorld(Meta3DWorld): def __init__(self): super().__init__() print("=== 坐标轴拖拽测试 ===") self.setBackgroundColor(0.2, 0.2, 0.3) # 调整相机位置 self.cam.setPos(10, -20, 10) self.cam.lookAt(0, 0, 0) # Qt部件引用 self.qtWidget = None # 添加基础光照 alight = AmbientLight('alight') alight.setColor((0.3, 0.3, 0.3, 1)) alnp = self.render.attachNewNode(alight) self.render.setLight(alnp) dlight = DirectionalLight('dlight') dlight.setColor((0.8, 0.8, 0.8, 1)) dlnp = self.render.attachNewNode(dlight) dlnp.setHpr(45, -45, 0) self.render.setLight(dlnp) # 创建测试立方体 self.createTestCube() # 坐标轴相关变量(复制自主程序) self.gizmo = None self.gizmoTarget = None self.gizmoXAxis = None self.gizmoYAxis = None self.gizmoZAxis = None self.axis_length = 3.0 self.isDraggingGizmo = False self.dragGizmoAxis = None self.dragStartMousePos = None self.gizmoTargetStartPos = None self.gizmoHighlightAxis = None self.gizmo_colors = { "x": (1, 0, 0, 1), # 红色 "y": (0, 1, 0, 1), # 绿色 "z": (0, 0, 1, 1) # 蓝色 } self.gizmo_highlight_colors = { "x": (1, 1, 0, 1), # 黄色高亮 "y": (1, 1, 0, 1), "z": (1, 1, 0, 1) } # 当前工具 self.currentTool = "移动" # 自动选中立方体并创建坐标轴 self.selectedNode = self.testCube self.createGizmo(self.testCube) print("测试说明:") print("1. 立方体已选中,坐标轴已显示") print("2. 鼠标悬停在坐标轴上应该变为黄色") print("3. 点击并拖拽坐标轴应该移动立方体") print("4. 观看控制台输出以了解拖拽过程") def setQtWidget(self, widget): """设置Qt部件引用""" self.qtWidget = widget print(f"✓ 设置Qt部件引用") def getWindowSize(self): """获取准确的窗口尺寸""" if self.qtWidget: width, height = self.qtWidget.getActualSize() if width > 0 and height > 0: return width, height if hasattr(self, 'win') and self.win: width = self.win.getXSize() height = self.win.getYSize() return width, height return 800, 600 def createTestCube(self): """创建测试立方体""" from panda3d.core import CardMaker # 创建一个简单的立方体 cm = CardMaker('cube') cm.setFrame(-1, 1, -1, 1) # 创建6个面 self.testCube = self.render.attachNewNode("testCube") # 前面(红色) front = self.testCube.attachNewNode(cm.generate()) front.setPos(0, 1, 0) front.setColor(1, 0.5, 0.5, 1) # 后面(绿色) back = self.testCube.attachNewNode(cm.generate()) back.setPos(0, -1, 0) back.setHpr(0, 0, 180) back.setColor(0.5, 1, 0.5, 1) # 左面(蓝色) left = self.testCube.attachNewNode(cm.generate()) left.setPos(-1, 0, 0) left.setHpr(0, 0, 90) left.setColor(0.5, 0.5, 1, 1) # 右面(黄色) right = self.testCube.attachNewNode(cm.generate()) right.setPos(1, 0, 0) right.setHpr(0, 0, -90) right.setColor(1, 1, 0.5, 1) # 顶面(紫色) top = self.testCube.attachNewNode(cm.generate()) top.setPos(0, 0, 1) top.setHpr(-90, 0, 0) top.setColor(1, 0.5, 1, 1) # 底面(青色) bottom = self.testCube.attachNewNode(cm.generate()) bottom.setPos(0, 0, -1) bottom.setHpr(90, 0, 0) bottom.setColor(0.5, 1, 1, 1) print(f"✓ 创建测试立方体,位置: {self.testCube.getPos()}") return self.testCube # 从主程序复制的坐标轴相关方法 def createGizmo(self, nodePath): """为选中的节点创建坐标轴工具""" try: if self.gizmo: self.gizmo.removeNode() self.gizmo = None if not nodePath: return self.gizmo = self.render.attachNewNode("gizmo") self.gizmoTarget = nodePath # 将坐标轴放在实体上方 bounds = nodePath.getBounds() if bounds and not bounds.isEmpty(): center = bounds.getCenter() maxPoint = bounds.getMax() gizmo_pos = Point3(center.x, center.y, maxPoint.z + 2.0) self.gizmo.setPos(gizmo_pos) self.createGizmoGeometry() print(f"✓ 为节点 {nodePath.getName()} 创建了坐标轴") except Exception as e: print(f"创建坐标轴失败: {str(e)}") def createGizmoGeometry(self): """创建坐标轴的几何体""" try: if not self.gizmo: return from panda3d.core import LineSegs # 创建X轴(红色) x_lines = LineSegs() x_lines.setThickness(4.0) x_lines.setColor(1, 0, 0, 1) x_lines.moveTo(0, 0, 0) x_lines.drawTo(self.axis_length, 0, 0) # 箭头 x_lines.moveTo(self.axis_length - 0.5, -0.2, 0) x_lines.drawTo(self.axis_length, 0, 0) x_lines.drawTo(self.axis_length - 0.5, 0.2, 0) self.gizmoXAxis = self.gizmo.attachNewNode(x_lines.create()) self.gizmoXAxis.setName("gizmo_x_axis") # 创建Y轴(绿色) y_lines = LineSegs() y_lines.setThickness(4.0) y_lines.setColor(0, 1, 0, 1) y_lines.moveTo(0, 0, 0) y_lines.drawTo(0, self.axis_length, 0) # 箭头 y_lines.moveTo(-0.2, self.axis_length - 0.5, 0) y_lines.drawTo(0, self.axis_length, 0) y_lines.drawTo(0.2, self.axis_length - 0.5, 0) self.gizmoYAxis = self.gizmo.attachNewNode(y_lines.create()) self.gizmoYAxis.setName("gizmo_y_axis") # 创建Z轴(蓝色) z_lines = LineSegs() z_lines.setThickness(4.0) z_lines.setColor(0, 0, 1, 1) z_lines.moveTo(0, 0, 0) z_lines.drawTo(0, 0, self.axis_length) # 箭头 z_lines.moveTo(-0.2, 0, self.axis_length - 0.5) z_lines.drawTo(0, 0, self.axis_length) z_lines.drawTo(0.2, 0, self.axis_length - 0.5) self.gizmoZAxis = self.gizmo.attachNewNode(z_lines.create()) self.gizmoZAxis.setName("gizmo_z_axis") # 确保坐标轴不被光照影响 self.gizmoXAxis.setLightOff() self.gizmoYAxis.setLightOff() self.gizmoZAxis.setLightOff() except Exception as e: print(f"创建坐标轴几何体失败: {str(e)}") # 主程序的鼠标事件处理方法(简化版) def mousePressEventLeft(self, evt): """处理鼠标左键按下事件""" print(f"\n=== 鼠标左键点击 ===") if not evt: return x = evt.get('x', 0) y = evt.get('y', 0) print(f"鼠标位置: ({x}, {y})") # 检查是否点击了坐标轴 if self.currentTool == "移动" and self.gizmo: gizmoAxis = self.checkGizmoClick(x, y) if gizmoAxis: print(f"✓ 检测到坐标轴点击: {gizmoAxis}") self.startGizmoDrag(gizmoAxis, x, y) return else: print("× 没有点击到坐标轴") def mouseReleaseEventLeft(self, evt): """处理鼠标左键释放事件""" if self.isDraggingGizmo: self.stopGizmoDrag() def mouseMoveEvent(self, evt): """处理鼠标移动事件""" if not evt: return # 处理坐标轴拖拽 if self.isDraggingGizmo: x = evt.get('x', 0) y = evt.get('y', 0) self.updateGizmoDrag(x, y) return # 更新坐标轴高亮 if self.currentTool == "移动" and self.gizmo and not self.isDraggingGizmo: x = evt.get('x', 0) y = evt.get('y', 0) self.updateGizmoHighlight(x, y) # 从主程序复制的方法(这里需要包含所有坐标轴相关的方法) # [为了简洁,这里只展示核心方法,实际应该包含所有方法] def checkGizmoClick(self, mouseX, mouseY): """简化的坐标轴点击检测""" if not self.gizmo or not self.gizmoTarget: return None try: print(f"检查坐标轴点击: 鼠标({mouseX}, {mouseY})") # 获取坐标轴中心的世界坐标 gizmo_world_pos = self.gizmo.getPos(self.render) print(f"坐标轴世界位置: {gizmo_world_pos}") # 计算各轴端点的世界坐标 x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0) y_end = gizmo_world_pos + Vec3(0, self.axis_length, 0) z_end = gizmo_world_pos + Vec3(0, 0, self.axis_length) def worldToScreen(world_pos): cam_pos = self.cam.getRelativePoint(self.render, world_pos) if cam_pos.getY() <= 0: return None screen_pos = Point2() if self.cam.node().getLens().project(cam_pos, screen_pos): win_width, win_height = self.getWindowSize() win_x = (screen_pos.getX() + 1.0) * 0.5 * win_width win_y = (1.0 - screen_pos.getY()) * 0.5 * win_height return (win_x, win_y) return None center_screen = worldToScreen(gizmo_world_pos) x_screen = worldToScreen(x_end) y_screen = worldToScreen(y_end) z_screen = worldToScreen(z_end) if not center_screen: return None print(f"中心屏幕坐标: {center_screen}") print(f"X轴端点屏幕坐标: {x_screen}") click_threshold = 30 # 简化的距离检测 def distance_to_line(point, line_start, line_end): import math px, py = point x1, y1 = line_start x2, y2 = line_end line_length_sq = (x2 - x1) ** 2 + (y2 - y1) ** 2 if line_length_sq < 0.001: return math.sqrt((px - x1) ** 2 + (py - y1) ** 2) t = max(0, min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / line_length_sq)) projection_x = x1 + t * (x2 - x1) projection_y = y1 + t * (y2 - y1) distance = math.sqrt((px - projection_x) ** 2 + (py - projection_y) ** 2) return distance # 检测各个轴 axes_data = [ ("x", x_screen, "X轴"), ("y", y_screen, "Y轴"), ("z", z_screen, "Z轴") ] for axis_name, axis_screen, axis_label in axes_data: if axis_screen: distance = distance_to_line( (mouseX, mouseY), center_screen, axis_screen ) #print(f"{axis_label}距离: {distance:.2f}") if distance < click_threshold: print(f"✓ 点击了{axis_label}") return axis_name print("× 没有点击任何轴") return None except Exception as e: print(f"坐标轴点击检测失败: {str(e)}") return None def startGizmoDrag(self, axis, mouseX, mouseY): """开始坐标轴拖拽""" try: self.isDraggingGizmo = True self.dragGizmoAxis = axis self.dragStartMousePos = (mouseX, mouseY) # 保存开始拖拽时目标节点的位置 if self.gizmoTarget: self.gizmoTargetStartPos = self.gizmoTarget.getPos() print(f"✓ 开始拖拽 {axis} 轴,开始位置: {self.gizmoTargetStartPos}") except Exception as e: print(f"开始坐标轴拖拽失败: {str(e)}") def updateGizmoDrag(self, mouseX, mouseY): """更新坐标轴拖拽""" try: if not self.isDraggingGizmo or not self.gizmoTarget or not hasattr(self, 'dragStartMousePos'): return # 计算鼠标移动距离(屏幕像素) mouseDeltaX = mouseX - self.dragStartMousePos[0] mouseDeltaY = mouseY - self.dragStartMousePos[1] # 获取坐标轴在屏幕空间的方向向量 gizmo_world_pos = self.gizmoTargetStartPos if self.dragGizmoAxis == "x": axis_end = gizmo_world_pos + Vec3(1, 0, 0) elif self.dragGizmoAxis == "y": axis_end = gizmo_world_pos + Vec3(0, 1, 0) elif self.dragGizmoAxis == "z": axis_end = gizmo_world_pos + Vec3(0, 0, 1) else: return # 投影到屏幕空间 def worldToScreen(worldPos): # 先转换为相机坐标系 camPos = self.cam.getRelativePoint(self.render, worldPos) # 检查是否在相机前方 if camPos.getY() <= 0: return None screenPos = Point2() if self.cam.node().getLens().project(camPos, screenPos): # 获取准确的窗口尺寸 winWidth, winHeight = self.getWindowSize() winX = (screenPos.x + 1) * 0.5 * winWidth winY = (1 - screenPos.y) * 0.5 * winHeight return (winX, winY) return None gizmo_screen = worldToScreen(gizmo_world_pos) axis_screen = worldToScreen(axis_end) if not gizmo_screen or not axis_screen: return # 计算轴在屏幕空间的方向向量 screen_axis_dir = ( axis_screen[0] - gizmo_screen[0], axis_screen[1] - gizmo_screen[1] ) # 归一化屏幕轴方向 import math length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2) if length > 0: screen_axis_dir = (screen_axis_dir[0] / length, screen_axis_dir[1] / length) else: return # 将鼠标移动投影到轴方向上 projected_distance = (mouseDeltaX * screen_axis_dir[0] + mouseDeltaY * screen_axis_dir[1]) # 转换投影距离为世界坐标移动距离 scale_factor = 0.01 # 可以调整这个值来改变拖拽灵敏度 if self.dragGizmoAxis == "x": movement = Vec3(projected_distance * scale_factor, 0, 0) elif self.dragGizmoAxis == "y": movement = Vec3(0, projected_distance * scale_factor, 0) elif self.dragGizmoAxis == "z": movement = Vec3(0, 0, projected_distance * scale_factor) # 应用移动到目标节点 newPos = self.gizmoTargetStartPos + movement self.gizmoTarget.setPos(newPos) # 添加调试输出(只在前几次输出,避免刷屏) if not hasattr(self, '_drag_debug_count'): self._drag_debug_count = 0 if self._drag_debug_count < 3: print(f"拖拽调试 - 轴: {self.dragGizmoAxis}") print(f" 鼠标移动: ({mouseDeltaX:.1f}, {mouseDeltaY:.1f})") print(f" 投影距离: {projected_distance:.3f}") print(f" 世界移动: {movement}") print(f" 开始位置: {self.gizmoTargetStartPos}") print(f" 新位置: {newPos}") print(f" 实际设置位置: {self.gizmoTarget.getPos()}") self._drag_debug_count += 1 except Exception as e: print(f"更新坐标轴拖拽失败: {str(e)}") def stopGizmoDrag(self): """停止坐标轴拖拽""" self.isDraggingGizmo = False self.dragGizmoAxis = None self.dragStartMousePos = None self.gizmoTargetStartPos = None # 重置调试计数器 if hasattr(self, '_drag_debug_count'): self._drag_debug_count = 0 print("✓ 停止坐标轴拖拽") def updateGizmoHighlight(self, mouseX, mouseY): """更新坐标轴高亮状态(简化版)""" if not self.gizmo or self.isDraggingGizmo: return # 检测鼠标悬停的轴 hoveredAxis = self.checkGizmoClick(mouseX, mouseY) # 如果高亮状态发生变化 if hoveredAxis != self.gizmoHighlightAxis: # 恢复之前高亮的轴 if self.gizmoHighlightAxis: self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis]) # 高亮新的轴 if hoveredAxis: self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis]) self.gizmoHighlightAxis = hoveredAxis def setGizmoAxisColor(self, axis, color): """设置坐标轴颜色""" try: if axis == "x" and self.gizmoXAxis: self.gizmoXAxis.setColor(*color) elif axis == "y" and self.gizmoYAxis: self.gizmoYAxis.setColor(*color) elif axis == "z" and self.gizmoZAxis: self.gizmoZAxis.setColor(*color) except Exception as e: print(f"设置坐标轴颜色失败: {str(e)}") class CustomGizmoDragTestWidget(QMeta3DWidget): """支持坐标轴拖拽测试的部件""" def __init__(self, world, parent=None): super().__init__(world, parent) self.world = world if hasattr(world, 'setQtWidget'): world.setQtWidget(self) def getActualSize(self): return (self.width(), self.height()) def mousePressEvent(self, event): """处理Qt鼠标按下事件""" if event.button() == Qt.LeftButton: self.world.mousePressEventLeft({ 'x': event.x(), 'y': event.y() }) event.accept() def mouseReleaseEvent(self, event): """处理Qt鼠标释放事件""" if event.button() == Qt.LeftButton: self.world.mouseReleaseEventLeft({ 'x': event.x(), 'y': event.y() }) event.accept() def mouseMoveEvent(self, event): """处理Qt鼠标移动事件""" self.world.mouseMoveEvent({ 'x': event.x(), 'y': event.y() }) event.accept() def main(): print("启动坐标轴拖拽测试...") app = QApplication(sys.argv) # 创建主窗口 mainWindow = QMainWindow() mainWindow.setWindowTitle("坐标轴拖拽测试") mainWindow.setGeometry(100, 100, 900, 700) # 创建布局 centralWidget = QWidget() layout = QVBoxLayout(centralWidget) # 添加说明 infoLayout = QHBoxLayout() label = QLabel("坐标轴拖拽测试 - 立方体已选中,尝试拖拽红色/绿色/蓝色坐标轴") label.setAlignment(Qt.AlignCenter) label.setStyleSheet("background-color: lightcyan; padding: 10px; font-size: 12px;") infoLayout.addWidget(label) resetBtn = QPushButton("重置立方体位置") infoLayout.addWidget(resetBtn) layout.addLayout(infoLayout) # 创建世界 world = GizmoDragTestWorld() # 连接重置按钮 def resetCube(): world.testCube.setPos(0, 0, 0) print("立方体位置已重置") resetBtn.clicked.connect(resetCube) # 创建测试部件 pandaWidget = CustomGizmoDragTestWidget(world) layout.addWidget(pandaWidget) mainWindow.setCentralWidget(centralWidget) mainWindow.show() print("\n使用说明:") print("1. 立方体已选中,红/绿/蓝坐标轴应该可见") print("2. 鼠标悬停在坐标轴上应该变为黄色") print("3. 点击并拖拽坐标轴,立方体应该沿对应轴移动") print("4. 观察控制台输出了解拖拽过程") return app.exec_() if __name__ == "__main__": sys.exit(main())