EG/demo/test_gizmo_drag.py
2025-12-12 16:16:15 +08:00

622 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())