坐标轴控制旋转,缩放

This commit is contained in:
Hector 2025-08-22 10:09:33 +08:00
parent 48c90034df
commit 8e8564048e
7 changed files with 657 additions and 176 deletions

File diff suppressed because one or more lines are too long

BIN
core/RotationHandleFull.fbx Executable file

Binary file not shown.

BIN
core/RotationHandleQuarter.fbx Executable file

Binary file not shown.

BIN
core/UniformScaleHandle.fbx Executable file

Binary file not shown.

View File

@ -11,7 +11,7 @@ from PIL.ImageChops import lighter
from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderState,
DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue,
CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib,
TransparencyAttrib, Vec4)
TransparencyAttrib, Vec4, CollisionCapsule)
from direct.task.TaskManagerGlobal import taskMgr
import math
@ -38,6 +38,9 @@ class SelectionSystem:
self.gizmoXAxis = None # X轴
self.gizmoYAxis = None # Y轴
self.gizmoZAxis = None # Z轴
self.gizmoRotXAxis = None
self.gizmoRotYAxis = None
self.gizmoRotZAxis = None
self.axis_length = 5.0 # 坐标轴长度增加到5.0
# 拖拽相关状态
@ -312,6 +315,8 @@ class SelectionSystem:
self._setupGizmoRendering()
self.setupGizmoCollision()
# 现在才显示坐标轴
self.gizmo.show()
@ -329,41 +334,120 @@ class SelectionSystem:
if not self.gizmo:
return
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False
if is_scale_tool:
model_paths = [
"core/UniformScaleHandle.fbx",
]
elif is_rotate_tool:
model_paths = [
"core/RotationHandleQuarter.fbx",
]
else:
model_paths = [
"core/TranslateArrowHandle.fbx",
"EG/core/TranslateArrowHandle.fbx",
]
arrow_model = None
# model_paths = [
# "core/TranslateArrowHandle.fbx",
# "EG/core/TranslateArrowHandle.fbx",
# ]
gizmo_model = None
gizmoRot_model = None
for path in model_paths:
try:
arrow_model = self.world.loader.loadModel(path)
if arrow_model:
if is_rotate_tool:
gizmo_model = self.world.loader.loadModel("core/TranslateArrowHandle.fbx")
gizmoRot_model = self.world.loader.loadModel(path)
else:
gizmo_model = self.world.loader.loadModel(path)
if gizmo_model:
print(f"成功加载模型: {path}")
break
except:
continue
x_rHandle = None
y_rHandle = None
z_rHandle = None
if is_rotate_tool:
self.gizmoRotXAxis = self.gizmo.attachNewNode("gizmo_rot_x_axis")
x_rHandle = gizmoRot_model.copyTo(self.gizmoRotXAxis)
x_rHandle.setName("x_handle")
self.gizmoRotYAxis = self.gizmo.attachNewNode("gizmo_rot_y_axis")
y_rHandle = gizmoRot_model.copyTo(self.gizmoRotYAxis)
y_rHandle.setName("y_handle")
self.gizmoRotZAxis = self.gizmo.attachNewNode("gizmo_rot_z_axis")
z_rHandle = gizmoRot_model.copyTo(self.gizmoRotZAxis)
z_rHandle.setName("z_handle")
self.gizmoXAxis = self.gizmo.attachNewNode("gizmo_x_axis")
x_arrow = arrow_model.copyTo(self.gizmoXAxis)
x_arrow.setName("x_arrow")
x_arrow.setHpr(0,-90,0)
x_arrow.setScale(0.1,0.05,0.05)
x_arrow.setPos(0,0,0)
x_handle = gizmo_model.copyTo(self.gizmoXAxis)
x_handle.setName("x_handle")
self.gizmoYAxis = self.gizmo.attachNewNode("gizmo_y_axis")
y_arrow = arrow_model.copyTo(self.gizmoYAxis)
y_arrow.setName("y_arrow")
y_arrow.setHpr(90,0,0)
y_arrow.setScale(0.1,0.05,0.05)
y_arrow.setPos(0,0,0)
y_handle = gizmo_model.copyTo(self.gizmoYAxis)
y_handle.setName("y_handle")
# 创建Z轴蓝色
self.gizmoZAxis = self.gizmo.attachNewNode("gizmo_z_axis")
z_arrow = arrow_model.copyTo(self.gizmoZAxis)
z_arrow.setName("z_arrow")
# 旋转箭头使其指向Z轴正方向
z_arrow.setHpr(0, 0, -90) # 根据需要调整旋转
z_arrow.setScale(0.1,0.05,0.05)
z_arrow.setPos(0, 0, 0)
z_handle = gizmo_model.copyTo(self.gizmoZAxis)
z_handle.setName("z_handle")
if is_scale_tool:
x_handle.setHpr(0,-90,0)
x_handle.setScale(0.6,0.03,0.03)
x_handle.setPos(2.2,0,0)
y_handle.setHpr(90,0,0)
y_handle.setScale(0.6,0.03,0.03)
y_handle.setPos(0,2.2,0)
z_handle.setHpr(0,0,-90)
z_handle.setScale(0.6,0.03,0.03)
z_handle.setPos(0,0,2.2)
elif is_rotate_tool:
x_rHandle.setHpr(0,0,90)
x_rHandle.setScale(0.025,0.0125,0.0125)
x_rHandle.setPos(0,0,0)
y_rHandle.setHpr(0,0,0)
y_rHandle.setScale(0.025,0.0125,0.0125)
y_rHandle.setPos(0,0,0)
z_rHandle.setHpr(-90,0,0)
z_rHandle.setScale(0.025,0.0125,0.0125)
z_rHandle.setPos(0,0,0)
x_handle.setHpr(0, -90, 0)
x_handle.setScale(0.1, 0.05, 0.05)
x_handle.setPos(0, 0, 0)
y_handle.setHpr(90, 0, 0)
y_handle.setScale(0.1, 0.05, 0.05)
y_handle.setPos(0, 0, 0)
z_handle.setHpr(0, 0, -90)
z_handle.setScale(0.1, 0.05, 0.05)
z_handle.setPos(0, 0, 0)
self.setGizmoRotAxisColor("x", self.gizmo_colors["x"])
self.setGizmoRotAxisColor("y", self.gizmo_colors["y"])
self.setGizmoRotAxisColor("z", self.gizmo_colors["z"])
else:
x_handle.setHpr(0,-90,0)
x_handle.setScale(0.1,0.05,0.05)
x_handle.setPos(0,0,0)
y_handle.setHpr(90,0,0)
y_handle.setScale(0.1,0.05,0.05)
y_handle.setPos(0,0,0)
z_handle.setHpr(0,0,-90)
z_handle.setScale(0.1,0.05,0.05)
z_handle.setPos(0,0,0)
# 设置初始颜色
self.setGizmoAxisColor("x", self.gizmo_colors["x"])
@ -379,6 +463,7 @@ class SelectionSystem:
def _setupGizmoRendering(self):
try:
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
axis_Rotnodes = [self.gizmoRotXAxis, self.gizmoRotYAxis, self.gizmoRotZAxis]
for axis_node in axis_nodes:
if axis_node:
@ -388,21 +473,45 @@ class SelectionSystem:
axis_node.setFogOff()
#设置渲染层级,确保大多数对象之前渲染
axis_node.setBin("fixed",30)
axis_node.setDepthWrite(False)
axis_node.setDepthTest(False)
#axis_node.setDepthWrite(False)
#axis_node.setDepthTest(True)
for axis_rotnode in axis_Rotnodes:
if axis_rotnode:
axis_rotnode.setLightOff()
axis_rotnode.setShaderOff()
axis_rotnode.setFogOff()
axis_rotnode.setBin("fixed",30)
#axis_rotnode.setDepthWrite(False)
#axis_rotnode.setDepthTest(True)
arrow_nodes = []
if self.gizmoXAxis:
x_arrow = self.gizmoXAxis.find("x_arrow")
if x_arrow:
arrow_nodes.append(x_arrow)
x_handle = self.gizmoXAxis.find("x_handle")
if x_handle:
arrow_nodes.append(x_handle)
if self.gizmoYAxis:
y_arrow = self.gizmoYAxis.find("y_arrow")
if y_arrow:
arrow_nodes.append(y_arrow)
y_handle = self.gizmoYAxis.find("y_handle")
if y_handle:
arrow_nodes.append(y_handle)
if self.gizmoZAxis:
z_arrow = self.gizmoZAxis.find("z_arrow")
if z_arrow:
arrow_nodes.append(z_arrow)
z_handle = self.gizmoZAxis.find("z_handle")
if z_handle:
arrow_nodes.append(z_handle)
rot_nodes = []
if self.gizmoRotXAxis:
x_rHandle = self.gizmoRotXAxis.find("x_handle")
if x_rHandle:
rot_nodes.append(x_rHandle)
if self.gizmoRotYAxis:
y_rHandle = self.gizmoRotYAxis.find("y_handle")
if y_rHandle:
rot_nodes.append(y_rHandle)
if self.gizmoRotZAxis:
z_rHandle = self.gizmoRotZAxis.find("z_handle")
if z_rHandle:
rot_nodes.append(z_rHandle)
for arrow_node in arrow_nodes:
if arrow_node:
@ -410,17 +519,30 @@ class SelectionSystem:
arrow_node.setShaderOff()
arrow_node.setFogOff()
arrow_node.setBin("fixed",31)
arrow_node.setDepthWrite(False)
arrow_node.setDepthTest(False)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(False)
#启用透明度S
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
for rot_node in rot_nodes:
if rot_node:
rot_node.setLightOff()
rot_node.setShaderOff()
rot_node.setFogOff()
rot_node.setBin("fixed",31)
#rot_node.setDepthWrite(False)
#rot_node.setDepthTest(False)
#启用透明度S
rot_node.setTransparency(TransparencyAttrib.MAlpha)
if self.gizmo:
self.gizmo.setLightOff()
self.gizmo.setShaderOff()
self.gizmo.setFogOff()
self.gizmo.setBin("fixed",29)
self.gizmo.setDepthWrite(False)
self.gizmo.setDepthTest(False)
# self.gizmo.setDepthWrite(False)
#self.gizmo.setDepthTest(False)
except Exception as e:
print(f"设置坐标轴渲染属性失败: {str(e)}")
@ -448,12 +570,49 @@ class SelectionSystem:
self.clearGizmo()
return task.done
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False
was_scale_tool = getattr(self,'_last_tool_scale_state',False)
was_rotate_tool =getattr(self,'_last_tool_rotate_state',False)
tool_changed = (is_scale_tool!=was_scale_tool) or (is_rotate_tool != was_rotate_tool)
if tool_changed:
self._last_tool_scale_state = is_scale_tool
self._last_tool_rotate_state = is_rotate_tool
if self.gizmoXAxis:
self.gizmoXAxis.removeNode()
self.gizmoXAxis = None
if self.gizmoYAxis:
self.gizmoYAxis.removeNode()
self.gizmoYAxis = None
if self.gizmoZAxis:
self.gizmoZAxis.removeNode()
self.gizmoZAxis = None
if self.gizmoRotXAxis:
self.gizmoRotXAxis.removeNode()
self.gizmoRotXAxis = None
if self.gizmoRotYAxis:
self.gizmoRotYAxis.removeNode()
self.gizmoRotYAxis = None
if self.gizmoRotZAxis:
self.gizmoRotZAxis.removeNode()
self.gizmoRotZAxis = None
self.createGizmoGeometry()
self.setGizmoAxisColor("x",self.gizmo_colors["x"])
self.setGizmoAxisColor("y",self.gizmo_colors["y"])
self.setGizmoAxisColor("z",self.gizmo_colors["z"])
self.setupGizmoCollision()
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
if light_object:
light_pos = light_object.pos
self.gizmo.setPos(light_object.pos)
self.gizmoTarget.setPos(light_pos)
else:
# 只在必要时更新位置和朝向
self._updateGizmoPositionAndOrientation()
@ -486,7 +645,11 @@ class SelectionSystem:
self.gizmo.setPos(center)
self._last_gizmo_bounds_update = current_time
# 更新朝向
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
if is_scale_tool:
self.gizmo.setHpr(self.gizmoTarget.getHpr())
else:
parent_node = self.gizmoTarget.getParent()
if parent_node and parent_node != self.world.render:
parent_hpr = parent_node.getHpr()
@ -494,6 +657,14 @@ class SelectionSystem:
else:
self.gizmo.setHpr(0,0,0)
# 更新朝向
# parent_node = self.gizmoTarget.getParent()
# if parent_node and parent_node != self.world.render:
# parent_hpr = parent_node.getHpr()
# self.gizmo.setHpr(parent_hpr)
# else:
# self.gizmo.setHpr(0, 0, 0)
def _updateGizmoScreenSize(self):
"""动态调整坐标轴大小,保持固定的屏幕大小"""
try:
@ -589,6 +760,97 @@ class SelectionSystem:
# except:
# pass
def setGizmoRotAxisColor(self, axis, color):
"""使用材质设置坐标轴颜色 - RenderPipeline兼容版本"""
try:
from panda3d.core import Material, Vec4,ColorWriteAttrib,DepthWriteAttrib,DepthTestAttrib,TransparencyAttrib
# 获取对应的轴节点
axis_nodes = {
"x": self.gizmoRotXAxis,
"y": self.gizmoRotYAxis,
"z": self.gizmoRotZAxis
}
if axis not in axis_nodes or not axis_nodes[axis]:
return
axis_node = axis_nodes[axis]
handle_node = None
handle_node = axis_node.find("x_handle") if axis == "x" else handle_node
handle_node = axis_node.find("y_handle") if axis == "y" else handle_node
handle_node = axis_node.find("z_handle") if axis == "z" else handle_node
#如果找不到特定名称的节点,尝试查找任何子节点
if not handle_node:
children = axis_node.getChildren()
if children.getNumPath()>0:
handle_node = children[0]
if not handle_node:
print(f"未找到{axis}轴的处理模型")
return
# 创建或获取材质
mat = Material()
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
mat.setDiffuse(Vec4(0, 0, 0, 1))
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
mat.set_roughness(1)
# 应用材质
handle_node.setMaterial(mat, 1)
# 设置透明度
if color[3] < 1.0:
handle_node.setTransparency(TransparencyAttrib.MAlpha)
else:
handle_node.setTransparency(TransparencyAttrib.MNone)
handle_node.setLightOff() # 禁用光照影响
handle_node.setShaderOff() # 禁用着色器
handle_node.setFogOff() # 禁用雾效果
handle_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(True)
# 保存材质引用以便后续修改
if axis == "x":
self.xMat = mat
elif axis == "y":
self.yMat = mat
elif axis == "z":
self.zMat = mat
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
axis_node.setBin("fixed", 30)
axis_node.setDepthWrite(False)
axis_node.setDepthTest(True)
except Exception as e:
print(f"设置坐标轴颜色失败: {str(e)}")
# 回退到简单颜色设置
try:
axis_nodes = {
"x": self.gizmoXAxis,
"y": self.gizmoYAxis,
"z": self.gizmoZAxis
}
if axis in axis_nodes and axis_nodes[axis]:
axis_nodes[axis].setColor(color[0], color[1], color[2], color[3])
except:
pass
def setGizmoAxisColor(self, axis, color):
"""使用材质设置坐标轴颜色 - RenderPipeline兼容版本"""
try:
@ -606,17 +868,19 @@ class SelectionSystem:
axis_node = axis_nodes[axis]
# 查找箭头模型节点
arrow_node = None
if axis == "x":
arrow_node = axis_node.find("x_arrow")
elif axis == "y":
arrow_node = axis_node.find("y_arrow")
elif axis == "z":
arrow_node = axis_node.find("z_arrow")
handle_node = None
handle_node = axis_node.find("x_handle") if axis == "x" else handle_node
handle_node = axis_node.find("y_handle") if axis == "y" else handle_node
handle_node = axis_node.find("z_handle") if axis == "z" else handle_node
if not arrow_node:
print(f"未找到{axis}轴的箭头模型")
#如果找不到特定名称的节点,尝试查找任何子节点
if not handle_node:
children = axis_node.getChildren()
if children.getNumPath()>0:
handle_node = children[0]
if not handle_node:
print(f"未找到{axis}轴的处理模型")
return
# 创建或获取材质
@ -630,20 +894,20 @@ class SelectionSystem:
mat.set_roughness(1)
# 应用材质
arrow_node.setMaterial(mat, 1)
handle_node.setMaterial(mat, 1)
# 设置透明度
if color[3] < 1.0:
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
handle_node.setTransparency(TransparencyAttrib.MAlpha)
else:
arrow_node.setTransparency(TransparencyAttrib.MNone)
handle_node.setTransparency(TransparencyAttrib.MNone)
arrow_node.setLightOff() # 禁用光照影响
arrow_node.setShaderOff() # 禁用着色器
arrow_node.setFogOff() # 禁用雾效果
handle_node.setLightOff() # 禁用光照影响
handle_node.setShaderOff() # 禁用着色器
handle_node.setFogOff() # 禁用雾效果
arrow_node.setBin("fixed",31)
handle_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(True)
@ -1019,8 +1283,9 @@ class SelectionSystem:
if not self.gizmo or self.isDraggingGizmo:
return
# 使用统一的检测方法
hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
# 使用碰撞检测方法
hoveredAxis = self.detectGizmoAxisWithCollision(mouseX, mouseY)
#hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
# 简化稳定性检测逻辑
if not hasattr(self, '_last_detected_axis'):
@ -1037,6 +1302,11 @@ class SelectionSystem:
# 高亮新的轴
if hoveredAxis:
self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis])
else:
# 如果没有悬停在任何轴上,确保所有轴都恢复原始颜色
for axis_name in ["x", "y", "z"]:
if axis_name != self.dragGizmoAxis: # 不要改变正在拖拽的轴的颜色
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
self.gizmoHighlightAxis = hoveredAxis
@ -1074,17 +1344,25 @@ class SelectionSystem:
return
self.isDraggingGizmo = True
# 使用当前高亮的轴,如果有的话
# 使用当前高亮的轴,如果有的话;否则使用传入的轴
if self.gizmoHighlightAxis:
self.dragGizmoAxis = self.gizmoHighlightAxis
else:
self.dragGizmoAxis = axis
self.dragStartMousePos = (mouseX, mouseY)
# 保存开始拖拽时目标节点的位置和坐标轴的位置
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
self.gizmoStartPos = self.gizmo.getPos(self.world.render) # 坐标轴的世界位置
# 添加对缩放的支持:保存初始缩放值
if self.world.tool_manager.isScaleTool():
self.gizmoTargetStartScale = self.gizmoTarget.getScale()
elif self.world.tool_manager.isRotateTool():
self.gizmoTargetStartHpr = self.gizmoTarget.getHpr()
# 确保正在拖动的轴保持高亮状态
if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors:
# 先将所有轴恢复为正常颜色
@ -1094,13 +1372,15 @@ class SelectionSystem:
# 然后将当前拖动的轴设置为高亮颜色
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis])
self.gizmoHighlightAxis = self.dragGizmoAxis
elif axis and axis in self.gizmo_colors:
for axis_name in self.gizmo_colors.keys():
if axis_name != axis:
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis])
self.gizmoHighlightAxis = axis
self.dragGizmoAxis = axis
self.gizmoHighlightAxis = self.dragGizmoAxis
print(
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
@ -1130,10 +1410,55 @@ class SelectionSystem:
print("拖拽更新失败: 没有坐标轴起始位置")
return
is_scale_tool = self.world.tool_manager.isScaleTool()
is_rotate_tool = self.world.tool_manager.isRotateTool()
# 计算鼠标移动距离(屏幕像素)
mouseDeltaX = mouseX - self.dragStartMousePos[0]
mouseDeltaY = mouseY - self.dragStartMousePos[1]
if is_scale_tool:
scale_factor = 1.0 + (mouseDeltaX + mouseDeltaY) * 0.01
start_scale = getattr(self,'gizmoTargetStartScale',Vec3(1,1,1))
target_hpr = self.gizmoTarget.getHpr()
if self.dragGizmoAxis == "x":
new_scale = Vec3(start_scale.x * scale_factor,start_scale.y,start_scale.z)
elif self.dragGizmoAxis == "y":
new_scale = Vec3(start_scale.x,start_scale.y*scale_factor,start_scale.z)
elif self.dragGizmoAxis == "z":
z_scale_factor = 1.0 - (mouseDeltaX + mouseDeltaY)*0.01
new_scale = Vec3(start_scale.x,start_scale.y,start_scale.z*z_scale_factor)
else:
new_scale = Vec3(start_scale.x * scale_factor,
start_scale * scale_factor,
start_scale.z * scale_factor)
#应用新缩放值
self.gizmoTarget.setScale(new_scale)
#实时更新属性面板
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
elif is_rotate_tool:
rotation_speed = 0.5
rotation_amount = (mouseDeltaX + mouseDeltaY) * rotation_speed
start_hpr = getattr(self,'gizmoTargetStartHpr',self.gizmoTarget.getHpr())
if self.dragGizmoAxis == "x":
new_hpr = Vec3(start_hpr.x+rotation_amount,start_hpr.y,start_hpr.z)
elif self.dragGizmoAxis == "y":
new_hpr = Vec3(start_hpr.x,start_hpr.y+rotation_amount,start_hpr.z)
elif self.dragGizmoAxis == "z":
new_hpr = Vec3(start_hpr.x,start_hpr.y,start_hpr.z+rotation_amount)
else:
# 默认绕所有轴旋转
new_hpr = Vec3(start_hpr.x + rotation_amount,
start_hpr.y + rotation_amount,
start_hpr.z + rotation_amount)
self.gizmoTarget.setHpr(new_hpr)
self.world.property_panel.refreshModelValues(self.gizmoTarget)
return
# 使用坐标轴的实际位置而不是目标节点位置来计算屏幕投影
gizmo_world_pos = self.gizmoStartPos
@ -1184,21 +1509,6 @@ class SelectionSystem:
axis_start_screen = worldToScreen(gizmo_world_pos)
axis_end_world = gizmo_world_pos + world_axis_vector
axis_end_screen = worldToScreen(axis_end_world)
#gizmo_screen = worldToScreen(gizmo_world_pos)
#axis_screen = worldToScreen(axis_end)
# if not gizmo_screen:
# print("拖拽更新失败: 坐标轴中心不在屏幕内")
# return
# if not axis_screen:
# print("拖拽更新失败: 坐标轴端点不在屏幕内")
# return
#
# # 计算轴在屏幕空间的方向向量
# screen_axis_dir = (
# axis_screen[0] - gizmo_screen[0],
# axis_screen[1] - gizmo_screen[1]
# )
if not axis_start_screen or not axis_end_screen:
print("拖拽更新失败: 无法获取轴线屏幕坐标")
@ -1209,7 +1519,6 @@ class SelectionSystem:
axis_end_screen[1] - axis_start_screen[1]
)
# 归一化屏幕轴方向
import math
length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2)
@ -1253,40 +1562,16 @@ class SelectionSystem:
currentPos = self.gizmoTargetStartPos
# scale_adjustment = 1.0
# if parent_node and parent_node!= self.world.render:
# current_node = parent_node
# total_scale = 1.0
# while current_node and current_node != self.world.render:
# node_scale = current_node.getScale()
# avg_scale = (node_scale.x+node_scale.y + node_scale.z)/3.0
# total_scale *= avg_scale
# current_node = current_node.getParent()
# if total_scale>0:
# scale_adjustment = 1.0 / total_scale
# # parent_scale = parent_node.getScale()
# # avg_scale = (parent_scale.x+parent_scale.y+parent_scale.z)/3.0
# # if avg_scale>0:
# # scale_adjustment = 1.0 / avg_scale
#
#
# fixed_pixel_to_world_ratio = 0.01 # 1像素 = 0.01世界单位
# scale_factor = fixed_pixel_to_world_ratio * scale_adjustment
#
# movement_distance = projected_distance * scale_factor
# # 获取当前位置并只修改选中轴的坐标
# currentPos = self.gizmoTargetStartPos
# 根据拖拽的轴,只修改对应的坐标分量
if self.dragGizmoAxis == "x":
newPos = Vec3(currentPos.x + movement_distance, currentPos.y, currentPos.z)
print(f"X轴移动{currentPos.x} -> {newPos.x}")
#print(f"X轴移动{currentPos.x} -> {newPos.x}")
elif self.dragGizmoAxis == "y":
newPos = Vec3(currentPos.x, currentPos.y + movement_distance, currentPos.z)
print(f"Y轴移动{currentPos.y} -> {newPos.y}")
#print(f"Y轴移动{currentPos.y} -> {newPos.y}")
elif self.dragGizmoAxis == "z":
newPos = Vec3(currentPos.x, currentPos.y, currentPos.z + movement_distance)
print(f"Z轴移动{currentPos.z} -> {newPos.z}")
#print(f"Z轴移动{currentPos.z} -> {newPos.z}")
else:
print(f"未知轴: {self.dragGizmoAxis}")
return
@ -1329,10 +1614,10 @@ class SelectionSystem:
def stopGizmoDrag(self):
"""停止坐标轴拖拽"""
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors:
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_colors[self.dragGizmoAxis])
# 不要将 gizmoHighlightAxis 设置为 None保持当前高亮轴的状态
# self.gizmoHighlightAxis = None
# 恢复所有轴的颜色
for axis_name in ["x", "y", "z"]:
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
self.isDraggingGizmo = False
self.dragGizmoAxis = None
@ -1341,6 +1626,13 @@ class SelectionSystem:
self.gizmoTargetStartPos = None
self.gizmoStartPos = None
if hasattr(self, 'gizmoTargetStartScale'):
delattr(self, 'gizmoTargetStartScale')
if hasattr(self, 'gizmoTargetStartHpr'):
delattr(self, 'gizmoTargetStartHpr')
# 重置高亮轴
self.gizmoHighlightAxis = None
# ==================== 选择管理 ====================
def updateSelection(self, nodePath):
@ -1393,3 +1685,164 @@ class SelectionSystem:
if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty():
self.clearSelectionBox()
def setupGizmoCollision(self):
if not self.gizmo or not self.gizmoXAxis or not self.gizmoYAxis or not self.gizmoZAxis:
return
# 清除现有的碰撞节点
for axis_name in ["x", "y", "z"]:
axis_node = getattr(self, f"gizmo{axis_name.upper()}Axis")
if axis_node:
# 查找并移除所有现有的碰撞节点
collision_nodes = axis_node.findAllMatches("**/gizmo_collision_*")
for collision_node in collision_nodes:
collision_node.removeNode()
# 为每个轴创建碰撞体
self.createAxisCollision("x", self.gizmoXAxis)
self.createAxisCollision("y", self.gizmoYAxis)
self.createAxisCollision("z", self.gizmoZAxis)
def createAxisCollision(self, axis_name, axis_node):
# 为单个轴创建碰撞体
try:
handle_node = axis_node.find(f"{axis_name}_handle")
if not handle_node or handle_node.isEmpty():
children = axis_node.getChildren()
if children.getNumPaths() > 0:
handle_node = children[0]
else:
print(f"警告: 未找到 {axis_name} 轴的 handle 节点")
return
collision_node = CollisionNode(f"gizmo_collision_{axis_name}")
collision_node.setIntoCollideMask(BitMask32.bit(1)) # 设置为into对象
collision_node.setFromCollideMask(BitMask32.allOff()) # 不作为from对象
# 调整碰撞尺寸以匹配实际的轴长度和坐标轴缩放
scale_factor = self.gizmo.getScale().x if self.gizmo else 1.0
axis_length = 2.0 * scale_factor
radius = 0.3 * scale_factor
# 根据轴的类型创建合适的碰撞体
if axis_name == "x":
capsule = CollisionCapsule(
Point3(0, 0, 0),
Point3(axis_length, 0, 0),
radius
)
collision_node.addSolid(capsule)
elif axis_name == "y":
capsule = CollisionCapsule(
Point3(0, 0, 0),
Point3(0, axis_length, 0),
radius
)
collision_node.addSolid(capsule)
elif axis_name == "z":
capsule = CollisionCapsule(
Point3(0, 0, 0),
Point3(0, 0, axis_length),
radius
)
collision_node.addSolid(capsule)
# 将碰撞节点附加到handle节点使其与可视化几何体保持一致
collision_np = handle_node.attachNewNode(collision_node)
# 设置标签以便识别
collision_np.setTag("gizmo_axis", axis_name)
collision_np.setTag("pickable", "1")
collision_np.hide() # 隐藏碰撞体,只用于检测
print(f"✓ 成功创建 {axis_name} 轴碰撞体")
except Exception as e:
print(f"创建{axis_name}轴碰撞体失败: {e}")
import traceback
traceback.print_exc()
def detectGizmoAxisWithCollision(self, mouseX, mouseY):
# 使用碰撞体检测鼠标是否悬停在坐标轴上
if not self.gizmo:
return None
try:
ray = CollisionRay()
win_width, win_height = self.world.getWindowSize()
mouse_x_ndc = (mouseX / win_width) * 2.0 - 1.0
mouse_y_ndc = 1.0 - (mouseY / win_height) * 2.0
ray.setFromLens(self.world.cam.node(), mouse_x_ndc, mouse_y_ndc)
traverser = CollisionTraverser("gizmo_traverser")
handler = CollisionHandlerQueue()
# 创建射线节点
ray_node = CollisionNode('mouseRay')
ray_node.addSolid(ray)
ray_node.setFromCollideMask(BitMask32.bit(1)) # 射线作为from对象
ray_node.setIntoCollideMask(BitMask32.allOff()) # 射线不作为into对象
ray_np = self.world.render.attachNewNode(ray_node)
# 为所有轴的碰撞体设置正确的掩码并添加到遍历器
collision_found = False
for axis_name in ["x", "y", "z"]:
axis_node = getattr(self, f"gizmo{axis_name.upper()}Axis")
if axis_node:
collision_node_path = axis_node.find("**/gizmo_collision_*")
if not collision_node_path.isEmpty():
collision_node = collision_node_path.node()
collision_node.setFromCollideMask(BitMask32.allOff()) # 碰撞体不作为from对象
collision_node.setIntoCollideMask(BitMask32.bit(1)) # 碰撞体作为into对象
collision_found = True
if not collision_found:
ray_np.removeNode()
return None
# 执行碰撞检测 - 这里是关键修复点
traverser.addCollider(ray_np, handler)
traverser.traverse(self.world.render)
ray_np.removeNode()
# 检查是否有碰撞
if handler.getNumEntries() > 0:
handler.sortEntries()
closest_entry = handler.getEntry(0)
# 获取碰撞的对象
collided_object = closest_entry.getIntoNodePath()
axis_tag = collided_object.getTag("gizmo_axis")
if axis_tag in ["x", "y", "z"]:
return axis_tag
return None
except Exception as e:
print(f"使用碰撞体检测坐标轴失败: {e}")
import traceback
traceback.print_exc()
return None
def debugGizmoCollision(self):
print("===碰撞体调试信息===")
for axis_name in ["x","y","z"]:
axis_node = getattr(self,f"gizmo{axis_name.upper()}Axis")
if axis_node:
handle_node = axis_node.find(f"{axis_name}_handle")
collision_node = axis_node.find("**/gizmo_collision_*")
print(f"{axis_name.upper()}轴:")
print(f" - 轴节点: {axis_node}")
print(f" - Handle节点: {handle_node}")
print(f" - 碰撞节点: {collision_node}")
if not collision_node.isEmpty():
print(f" - 碰撞体标签: {collision_node.getTag('gizmo_axis')}")
else:
print(f"{axis_name.upper()}轴节点不存在")

View File

@ -150,49 +150,3 @@ class ToolManager:
except Exception as e:
print(f"❌ 启动插件配置器失败: {e}")
return False
def cleanup_processes(self):
"""清理所有启动的进程"""
try:
# 清理插件配置器进程
if hasattr(self, '_plugin_configurator_process') and self._plugin_configurator_process:
if self._plugin_configurator_process.poll() is None:
print("🔄 正在关闭插件配置器...")
self._plugin_configurator_process.terminate()
try:
# 等待进程结束最多等待5秒
self._plugin_configurator_process.wait(timeout=5)
print("✅ 插件配置器已正常关闭")
except subprocess.TimeoutExpired:
print("⚠️ 插件配置器未响应,强制关闭...")
self._plugin_configurator_process.kill()
self._plugin_configurator_process.wait()
print("✅ 插件配置器已强制关闭")
self._plugin_configurator_process = None
# 清理材质编辑器进程(如果存在)
if hasattr(self, '_material_editor_process') and self._material_editor_process:
if self._material_editor_process.poll() is None:
print("🔄 正在关闭材质编辑器...")
self._material_editor_process.terminate()
try:
self._material_editor_process.wait(timeout=5)
print("✅ 材质编辑器已正常关闭")
except subprocess.TimeoutExpired:
print("⚠️ 材质编辑器未响应,强制关闭...")
self._material_editor_process.kill()
self._material_editor_process.wait()
print("✅ 材质编辑器已强制关闭")
self._material_editor_process = None
except Exception as e:
print(f"⚠️ 清理进程时出错: {e}")
def get_plugin_configurator_status(self):
"""获取插件配置器的运行状态"""
if hasattr(self, '_plugin_configurator_process') and self._plugin_configurator_process:
if self._plugin_configurator_process.poll() is None:
return "运行中"
else:
return "已停止"
return "未启动"

View File

@ -395,19 +395,27 @@ class PropertyPanelManager:
self.scale_y = QDoubleSpinBox()
self.scale_z = QDoubleSpinBox()
# 设置缩放控件属性
for scale_widget in [self.scale_x, self.scale_y, self.scale_z]:
scale_widget.setRange(0.01, 100)
scale_widget.setSingleStep(0.1)
current_scale = model.getScale()
self.scale_x.setValue(model.getScale().getX())
self.scale_y.setValue(model.getScale().getY())
self.scale_z.setValue(model.getScale().getZ())
# 设置缩放控件属性
for i, (scale_widget, scale_value) in enumerate(zip([self.scale_x, self.scale_y, self.scale_z],
[current_scale.getX(), current_scale.getY(),
current_scale.getZ()])):
scale_widget.setRange(-1000, 1000)
scale_widget.setSingleStep(0.1)
# 如果缩放值为0设置为一个很小的非零值
if scale_value == 0:
scale_value = 0.01 if scale_value >= 0 else -0.01
scale_widget.setValue(scale_value)
self.scale_x.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_x, value))
self.scale_y.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_y, value))
self.scale_z.valueChanged.connect(lambda value: self._onScaleValueChanged(self.scale_z, value))
# 连接缩放变化事件
self.scale_x.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
self.scale_y.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
self.scale_z.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
self.scale_x.valueChanged.connect(lambda value: self._updateXScale(model, value))
self.scale_y.valueChanged.connect(lambda value: self._updateYScale(model, value))
self.scale_z.valueChanged.connect(lambda value: self._updateZScale(model, value))
# 创建并设置 X, Y, Z 标签居中
x_label3 = QLabel("X")
@ -434,6 +442,72 @@ class PropertyPanelManager:
# 材质属性组
self._updateModelMaterialPanel(model)
def _onScaleValueChanged(self, scale_widget, value):
"""确保缩放值不为0"""
if value == 0:
# 设置为一个很小的非零值,保持原有符号
if hasattr(scale_widget, 'value') and scale_widget.value() > 0:
scale_widget.setValue(0.01)
else:
scale_widget.setValue(-0.01)
def _updateXScale(self, model, value):
"""更新X轴缩放值"""
# 确保值不为0
if value == 0:
sender = None
# 通过遍历找到发出信号的控件
for widget in [self.scale_x, self.scale_y, self.scale_z]:
if widget.value() == value:
sender = widget
break
if sender:
self._onScaleValueChanged(sender, value)
return
# 更新模型的X轴缩放
current_scale = model.getScale()
model.setScale(value, current_scale.getY(), current_scale.getZ())
self.refreshModelValues(model)
def _updateYScale(self, model, value):
"""更新Y轴缩放值"""
# 确保值不为0
if value == 0:
sender = None
# 通过遍历找到发出信号的控件
for widget in [self.scale_x, self.scale_y, self.scale_z]:
if widget.value() == value:
sender = widget
break
if sender:
self._onScaleValueChanged(sender, value)
return
# 更新模型的Y轴缩放
current_scale = model.getScale()
model.setScale(current_scale.getX(), value, current_scale.getZ())
self.refreshModelValues(model)
def _updateZScale(self, model, value):
"""更新Z轴缩放值"""
# 确保值不为0
if value == 0:
sender = None
# 通过遍历找到发出信号的控件
for widget in [self.scale_x, self.scale_y, self.scale_z]:
if widget.value() == value:
sender = widget
break
if sender:
self._onScaleValueChanged(sender, value)
return
# 更新模型的Z轴缩放
current_scale = model.getScale()
model.setScale(current_scale.getX(), current_scale.getY(), value)
self.refreshModelValues(model)
def refreshModelValues(self,nodePath):
if not nodePath or self._propertyLayout is None:
return