1.坐标轴深度待更改

2.拖动后节点可见性逻辑改进
This commit is contained in:
Hector 2025-08-21 10:44:59 +08:00
parent dc150f793b
commit e4084c5bf6
5 changed files with 217 additions and 170 deletions

File diff suppressed because one or more lines are too long

View File

@ -308,6 +308,13 @@ class SelectionSystem:
self.setGizmoAxisColor("y", self.gizmo_colors["y"])
self.setGizmoAxisColor("z", self.gizmo_colors["z"])
self._updateGizmoScreenSize()
self._setupGizmoRendering()
# 现在才显示坐标轴
self.gizmo.show()
# 只启动一次更新任务
taskMgr.add(self.updateGizmoTask, "updateGizmo")
@ -363,38 +370,59 @@ class SelectionSystem:
self.setGizmoAxisColor("y", self.gizmo_colors["y"])
self.setGizmoAxisColor("z", self.gizmo_colors["z"])
# 使用最强的渲染设置,确保坐标轴绝对不会被遮挡
# self.gizmo.setBin("default", 0) # 使用最高的GUI渲染层
# self.gizmo.setDepthTest(True) # 完全禁用深度测试
# self.gizmo.setDepthWrite(True) # 禁用深度写入
# self.gizmo.setTwoSided(True) # 双面渲染
#设置渲染属性,解决模型遮挡和阴影问题
self._setupGizmoRendering()
except Exception as e:
print(f"创建坐标轴几何体失败: {str(e)}")
def _applyAxisMaterial(self, node, color):
from panda3d.core import Material, Vec4, ColorWriteAttrib, DepthWriteAttrib
# 构造一个“自发光”的材质,让颜色不依赖灯光/法线
m = Material()
col = Vec4(*color)
m.setBaseColor(col) # 记录用
m.setDiffuse(col) # 记录用RP标准材质可能不读取
m.setEmission(Vec4(col.x, col.y, col.z, 1.0)) # 关键:用发光通道显示颜色
node.setMaterial(m, 1)
def _setupGizmoRendering(self):
try:
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
# 走能直接写到最终画面的阶段(两种方式二选一):
# 方式1完全绕过管线最稳
node.setTag("pipeline-disable", "1")
# 方式2留在管线内但走最终阶段若不想禁用管线可改用下面这一行并去掉上一行
# node.setTag("pipeline-stage", "final")
for axis_node in axis_nodes:
if axis_node:
#禁用光照和阴影
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
#设置渲染层级,确保大多数对象之前渲染
axis_node.setBin("fixed",30)
axis_node.setDepthWrite(False)
axis_node.setDepthTest(False)
arrow_nodes = []
if self.gizmoXAxis:
x_arrow = self.gizmoXAxis.find("x_arrow")
if x_arrow:
arrow_nodes.append(x_arrow)
if self.gizmoYAxis:
y_arrow = self.gizmoYAxis.find("y_arrow")
if y_arrow:
arrow_nodes.append(y_arrow)
if self.gizmoZAxis:
z_arrow = self.gizmoZAxis.find("z_arrow")
if z_arrow:
arrow_nodes.append(z_arrow)
# 可见性状态:允许颜色写入,不写深度,双面可见,禁用雾
node.setAttrib(ColorWriteAttrib.make(ColorWriteAttrib.C_all))
node.setAttrib(DepthWriteAttrib.make(False))
node.setTwoSided(True)
node.setFogOff()
for arrow_node in arrow_nodes:
if arrow_node:
arrow_node.setLightOff()
arrow_node.setShaderOff()
arrow_node.setFogOff()
arrow_node.setBin("fixed",31)
arrow_node.setDepthWrite(False)
arrow_node.setDepthTest(False)
#启用透明度S
arrow_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)
except Exception as e:
print(f"设置坐标轴渲染属性失败: {str(e)}")
def updateGizmoTask(self, task):
"""坐标轴更新任务 - 包含固定大小功能"""
@ -429,25 +457,6 @@ class SelectionSystem:
else:
# 只在必要时更新位置和朝向
self._updateGizmoPositionAndOrientation()
# # 更新坐标轴位置,始终在目标节点中心
# minPoint = Point3()
# maxPoint = Point3()
# if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
# # 计算中心点
# center = Point3((minPoint.x + maxPoint.x) * 0.5,
# (minPoint.y + maxPoint.y) * 0.5,
# (minPoint.z + maxPoint.z) * 0.5)
# self.gizmo.setPos(center)
#
# # 【关键修复】:更新坐标轴朝向以跟踪父节点的变化
# 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)
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
self._updateGizmoScreenSize()
@ -517,8 +526,8 @@ class SelectionSystem:
self.gizmo.setScale(scale_factor)
# 限制缩放范围,避免过大或过小
min_scale = 0.1
max_scale = 10.0
min_scale = 0.08
max_scale = 100.0
final_scale = max(min_scale, min(max_scale, scale_factor))
if final_scale != scale_factor:
@ -630,19 +639,13 @@ class SelectionSystem:
else:
arrow_node.setTransparency(TransparencyAttrib.MNone)
# 创建自定义渲染状态 - 确保始终在最前方
# state = RenderState.make(
# ColorWriteAttrib.make(ColorWriteAttrib.CAll), # 允许颜色写入
# DepthTestAttrib.make(DepthTestAttrib.MAlways), # 始终通过深度测试
# DepthWriteAttrib.make(DepthWriteAttrib.MOff) # 不写入深度缓冲区
# )
# # 应用渲染状态
# arrow_node.set_state(state)
arrow_node.setLightOff() # 禁用光照影响
arrow_node.setShaderOff() # 禁用着色器
arrow_node.setFogOff() # 禁用雾效果
arrow_node.setBin("fixed", 10) # 使用固定渲染顺序,确保在最前
arrow_node.setBin("fixed",31)
#arrow_node.setDepthWrite(False)
#arrow_node.setDepthTest(True)
# 保存材质引用以便后续修改
if axis == "x":
@ -655,8 +658,9 @@ class SelectionSystem:
axis_node.setLightOff()
axis_node.setShaderOff()
axis_node.setFogOff()
#axis_node.set_state(state)
axis_node.setBin("fixed", 9) # 确保轴节点在箭头模型之前渲染
axis_node.setBin("fixed", 30)
#axis_node.setDepthWrite(False)
#axis_node.setDepthTest(True)
except Exception as e:
print(f"设置坐标轴颜色失败: {str(e)}")
@ -907,38 +911,10 @@ class SelectionSystem:
# ==================== 高亮和交互 ====================
def updateGizmoHighlight(self, mouseX, mouseY):
"""更新坐标轴高亮状态"""
if not self.gizmo or self.isDraggingGizmo:
return
import time
current_time = time.time()
if not hasattr(self,'_last_highlight_time'):
self._last_highlight_time = 0
if current_time - self._last_highlight_time<0.05:
return
self._last_highlight_time = current_time
hoveredAxis = self._detectHoveredAxis(mouseX, mouseY)
if not hasattr(self,'_hover_stability_counter'):
self._hover_stability_counter = {}
self._last_detected_axis = None
if hoveredAxis !=self._last_detected_axis:
self._hover_stability_counter[hoveredAxis]=1
self._last_detected_axis = hoveredAxis
else:
self._hover_stability_counter[hoveredAxis] = self._hover_stability_counter.get(hoveredAxis,0)+1
if self._hover_stability_counter.get(hoveredAxis,0)>=2:
if hoveredAxis != self.gizmoHighlightAxis:
self._updateAxisHighlight(hoveredAxis)
# 检测鼠标悬停的轴(使用相同的检测逻辑但不输出调试信息)
hoveredAxis = None
def detectGizmoAxisAtMouse(self, mouseX, mouseY):
"""统一的坐标轴检测方法 - 同时用于高亮和点击检测"""
if not self.gizmo or not self.gizmoTarget:
return None
try:
# 获取坐标轴中心的世界坐标
@ -951,29 +927,29 @@ class SelectionSystem:
# 将3D坐标投影到屏幕坐标
def worldToScreen(worldPos):
try:
# 转换为相机坐标系
camPos = self.world.cam.getRelativePoint(self.world.render, worldPos)
try:
# 转换为相机坐标系
camPos = self.world.cam.getRelativePoint(self.world.render, worldPos)
# 检查点是否在相机前方
if camPos.getY() <= 0:
return None
# 检查点是否在相机前方
if camPos.getY() <= 0:
return None
# 使用相机lens进行投影
screenPos = Point2()
lens = self.world.cam.node().getLens()
# 使用相机lens进行投影
screenPos = Point2()
lens = self.world.cam.node().getLens()
if lens.project(camPos, screenPos):
# 获取准确的窗口尺寸
winWidth, winHeight = self.world.getWindowSize()
if lens.project(camPos, screenPos):
# 获取准确的窗口尺寸
winWidth, winHeight = self.world.getWindowSize()
# 转换为像素坐标
winX = (screenPos.x + 1) * 0.5 * winWidth
winY = (1 - screenPos.y) * 0.5 * winHeight
return (winX, winY)
return None
except:
return None
# 转换为像素坐标
winX = (screenPos.x + 1) * 0.5 * winWidth
winY = (1 - screenPos.y) * 0.5 * winHeight
return (winX, winY)
return None
except:
return None
# 获取各坐标轴的屏幕投影
gizmo_screen = worldToScreen(gizmo_world_pos)
@ -981,70 +957,90 @@ class SelectionSystem:
y_screen = worldToScreen(y_end)
z_screen = worldToScreen(z_end)
# 只要坐标轴中心在屏幕内,就进行检测
if gizmo_screen:
click_threshold = 25
# 如果坐标轴中心不在屏幕内返回None
if not gizmo_screen:
return None
def isNearLine(mousePos, start, end, threshold):
import math
A = mousePos[1] - start[1]
B = start[0] - mousePos[0]
C = (end[1] - start[1]) * mousePos[0] + (start[0] - end[0]) * mousePos[1] + end[0] * start[1] - start[0] * end[1]
# 设置检测阈值
click_threshold = 35 # 统一使用25像素的检测阈值
length = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
if length == 0:
return False
# 更准确的点到线段距离计算方法
def distanceToLineSegment(mousePos, start, end):
import math
mx, my = mousePos
x1, y1 = start
x2, y2 = end
distance = abs(C) / length
t = ((mousePos[0] - start[0]) * (end[0] - start[0]) +
(mousePos[1] - start[1]) * (end[1] - start[1])) / (length * length)
# 线段向量
dx = x2 - x1
dy = y2 - y1
return distance < threshold and 0 <= t <= 1
# 线段长度平方
length_sq = dx * dx + dy * dy
mouse_pos = (mouseX, mouseY)
if length_sq == 0:
# 线段退化为点
return math.sqrt((mx - x1) ** 2 + (my - y1) ** 2)
# 分别检测每个轴,为在屏幕外的轴端点提供替代方案
# 按优先级检测轴Z > X > Y
# 投影参数
t = max(0, min(1, ((mx - x1) * dx + (my - y1) * dy) / length_sq))
# 对于轴端点在屏幕外的情况,使用较短的轴段进行检测
def getAxisScreenPoint(axis_name, axis_screen_end):
if axis_screen_end:
return axis_screen_end
# 如果端点在屏幕外,使用轴长度的一半作为检测点
if axis_name == "x":
half_end = gizmo_world_pos + Vec3(self.axis_length * 0.5, 0, 0)
elif axis_name == "y":
half_end = gizmo_world_pos + Vec3(0, self.axis_length * 0.5, 0)
elif axis_name == "z":
half_end = gizmo_world_pos + Vec3(0, 0, self.axis_length * 0.5)
return worldToScreen(half_end)
# 投影点坐标
proj_x = x1 + t * dx
proj_y = y1 + t * dy
# 获取有效的检测点(优先使用完整轴,备用使用半轴)
z_detect_point = getAxisScreenPoint("z", z_screen)
x_detect_point = getAxisScreenPoint("x", x_screen)
y_detect_point = getAxisScreenPoint("y", y_screen)
# 返回点到投影点的距离
return math.sqrt((mx - proj_x) ** 2 + (my - proj_y) ** 2)
if z_detect_point and isNearLine(mouse_pos, gizmo_screen, z_detect_point, click_threshold):
hoveredAxis = "z"
elif x_detect_point and isNearLine(mouse_pos, gizmo_screen, x_detect_point, click_threshold):
hoveredAxis = "x"
elif y_detect_point and isNearLine(mouse_pos, gizmo_screen, y_detect_point, click_threshold):
hoveredAxis = "y"
mouse_pos = (mouseX, mouseY)
# 检测各个轴 - 按优先级检测Z > X > Y
axes_to_check = [
("z", z_screen),
("x", x_screen),
("y", y_screen)
]
for axis_name, axis_end in axes_to_check:
if axis_end:
distance = distanceToLineSegment(mouse_pos, gizmo_screen, axis_end)
if distance < click_threshold:
return axis_name
# 如果没有检测到返回None
return None
except Exception as e:
pass # 静默处理错误,避免频繁输出
# 静默处理错误,避免频繁输出
return None
# 如果高亮状态发生变化
if hoveredAxis != self.gizmoHighlightAxis:
# 恢复之前高亮的轴
if self.gizmoHighlightAxis:
self.setGizmoAxisColor(self.gizmoHighlightAxis, self.gizmo_colors[self.gizmoHighlightAxis])
def updateGizmoHighlight(self, mouseX, mouseY):
"""更新坐标轴高亮状态"""
if not self.gizmo or self.isDraggingGizmo:
return
# 高亮新的轴
if hoveredAxis:
self.setGizmoAxisColor(hoveredAxis, self.gizmo_highlight_colors[hoveredAxis])
# 使用统一的检测方法
hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
self.gizmoHighlightAxis = hoveredAxis
# 简化稳定性检测逻辑
if not hasattr(self, '_last_detected_axis'):
self._last_detected_axis = None
# 如果检测结果发生变化,立即更新高亮
if hoveredAxis != self._last_detected_axis:
# 更新轴的高亮状态
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
self._last_detected_axis = hoveredAxis
def _detectHoveredAxis(self, mouseX, mouseY):
"""检测鼠标悬停的轴 - 提取为独立方法"""
@ -1078,14 +1074,36 @@ class SelectionSystem:
return
self.isDraggingGizmo = True
self.dragGizmoAxis = axis
# 使用当前高亮的轴,如果有的话
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) # 坐标轴的世界位置
print(f"开始拖拽 {axis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
# 确保正在拖动的轴保持高亮状态
if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors:
# 先将所有轴恢复为正常颜色
for axis_name in self.gizmo_colors.keys():
if axis_name != self.dragGizmoAxis:
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
# 然后将当前拖动的轴设置为高亮颜色
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():
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis])
self.gizmoHighlightAxis = axis
print(
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
except Exception as e:
print(f"开始坐标轴拖拽失败: {str(e)}")
@ -1311,6 +1329,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
self.isDraggingGizmo = False
self.dragGizmoAxis = None

View File

@ -369,9 +369,6 @@ class MyWorld(CoreWorld):
def updatePropertyPanel(self, item):
"""更新属性面板显示 - 代理到property_panel"""
return self.property_panel.updatePropertyPanel(item)
# def addAnimationPanel(self,originmodel,filepath):
# return self.property_panel._addAnimationPanel(originmodel,filepath)
def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板 - 代理到property_panel"""
@ -380,6 +377,9 @@ class MyWorld(CoreWorld):
def removeActorForModel(self,model):
return self.property_panel.removeActorForModel( model)
def updateNodeVisibilityAfterDrag(self,item):
return self.property_panel.updateNodeVisibilityAfterDrag(item)
# ==================== 工具管理代理 ====================
def setCurrentTool(self, tool):

View File

@ -51,6 +51,16 @@ class PropertyPanelManager:
if item.widget():
item.widget().deleteLater()
def updateNodeVisibilityAfterDrag(self, item):
"""拖拽结束后更新节点的可见性状态"""
node = item.data(0, Qt.UserRole)
if not node:
return
# 当节点被拖拽后,需要根据新父节点的状态来更新可见性
self._syncEffectiveVisibility(node)
def updatePropertyPanel(self, item):
"""更新属性面板显示"""
if not self._propertyLayout or not self._propertyLayout.parent():
@ -133,7 +143,17 @@ class PropertyPanelManager:
def _syncEffectiveVisibility(self, start_node):
"""广度优先,确保父隐藏则子一定隐藏"""
q = deque([(start_node, True)]) # (node, parent_effective_visible)
# 获取起始节点的父节点
parent_node = start_node.getParent()
# 确定父节点的有效可见性
parent_effective_visible = True
if parent_node:
parent_effective_visible = parent_node.getPythonTag("effective_visible")
if parent_effective_visible is None:
parent_effective_visible = True
q = deque([(start_node, parent_effective_visible)]) # (node, parent_effective_visible)
while q:
node, parent_eff = q.popleft()
@ -143,7 +163,7 @@ class PropertyPanelManager:
eff = parent_eff and user
node.setPythonTag("effective_visible", eff)
#特殊处理:检查是否为碰撞体节点
# 特殊处理:检查是否为碰撞体节点
if node.getName().startswith("modelCollision_"):
node.hide()

View File

@ -339,6 +339,7 @@ class CustomTreeWidget(QTreeWidget):
# 获取节点引用
dragged_node = dragged_item.data(0, Qt.UserRole)
amtarget_node = target_item.data(0, Qt.UserRole)
# 如果目标是模型根节点,使用 render 作为新父节点
if target_item.text(0) == "模型":
@ -360,13 +361,17 @@ class CustomTreeWidget(QTreeWidget):
# 接受拖放事件,更新树形控件
super().dropEvent(event)
#self.world.property_panel.updateNodeVisibilityAfterDrag(dragged_item)
# 更新属性面板
self.world.updatePropertyPanel(dragged_item)
self.world.property_panel._syncEffectiveVisibility(dragged_node)
else:
event.ignore()
def isValidParentChild(self, dragged_item, target_item):
"""检查是否是有效的父子关系"""
# 不能拖放到自己上