3d_image和2d_image
根据高度图创建地形
This commit is contained in:
parent
c66b9003f5
commit
78dae899cf
File diff suppressed because one or more lines are too long
@ -1,3 +1,5 @@
|
||||
from inspect import getargs
|
||||
|
||||
from panda3d.core import (Point3, Point2, CollisionTraverser, CollisionHandlerQueue,
|
||||
CollisionNode, CollisionRay, GeomNode, LineSegs, RenderState,
|
||||
DepthTestAttrib, ColorAttrib)
|
||||
@ -119,6 +121,9 @@ class EventHandler:
|
||||
if not evt:
|
||||
print("事件为空")
|
||||
return
|
||||
if self.world.currentTool == "地形编辑":
|
||||
self._handleTerrainEdit(evt,"add")
|
||||
return
|
||||
|
||||
# 获取鼠标点击的位置
|
||||
x = evt.get('x', 0)
|
||||
@ -260,7 +265,154 @@ class EventHandler:
|
||||
print(f"清理碰撞检测节点失败: {str(e)}")
|
||||
|
||||
print("=== 鼠标左键事件处理结束 ===\n")
|
||||
|
||||
|
||||
def mousePressEventRight(self,evt):
|
||||
"""处理鼠标右键按下事件"""
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
# 检查是否是地形编辑模式
|
||||
if self.world.currentTool == "地形编辑":
|
||||
self._handleTerrainEdit(evt, "subtract") # 降低地形
|
||||
return
|
||||
|
||||
# 其他右键处理逻辑可以在这里添加
|
||||
print("鼠标右键事件处理")
|
||||
|
||||
def _handleTerrainEdit(self, evt, operation):
|
||||
"""处理地形编辑"""
|
||||
try:
|
||||
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)
|
||||
|
||||
nearPoint = Point3()
|
||||
farPoint = Point3()
|
||||
self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint)
|
||||
|
||||
worldNearPoint = self.world.render.getRelativePoint(self.world.cam, nearPoint)
|
||||
worldFarPoint = self.world.render.getRelativePoint(self.world.cam, farPoint)
|
||||
|
||||
picker = CollisionTraverser()
|
||||
queue = CollisionHandlerQueue()
|
||||
|
||||
pickerNode = CollisionNode('terrain_edit_ray')
|
||||
pickerNP = self.world.cam.attachNewNode(pickerNode)
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
# 确保我们检测所有碰撞体
|
||||
pickerNode.setFromCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 使用相机坐标系的点创建射线
|
||||
direction = farPoint - nearPoint
|
||||
direction.normalize()
|
||||
pickerNode.addSolid(CollisionRay(nearPoint, direction))
|
||||
|
||||
picker.addCollider(pickerNP, queue)
|
||||
picker.traverse(self.world.render)
|
||||
print(f"地形碰撞检测结果数量: {queue.getNumEntries()}")
|
||||
|
||||
# 添加调试信息,显示所有场景中的碰撞体
|
||||
print("场景中的碰撞体:")
|
||||
|
||||
def show_colliders(node, level=0):
|
||||
indent = " " * level
|
||||
if not node.isEmpty():
|
||||
if isinstance(node.node(), CollisionNode):
|
||||
print(f"{indent}CollisionNode: {node.getName()}")
|
||||
for child in node.getChildren():
|
||||
show_colliders(child, level + 1)
|
||||
|
||||
show_colliders(self.world.render)
|
||||
|
||||
# 射线检测结果处理
|
||||
hitPos = None
|
||||
hitNode = None
|
||||
hitTerrainInfo = None
|
||||
|
||||
if queue.getNumEntries() > 0:
|
||||
queue.sortEntries()
|
||||
# 遍历所有碰撞结果,找到地形节点
|
||||
for i in range(queue.getNumEntries()):
|
||||
entry = queue.getEntry(i)
|
||||
collided_node = entry.getIntoNodePath()
|
||||
print(f"碰撞到节点: {collided_node.getName()}")
|
||||
|
||||
# 检查是否是地形节点
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if collided_node == terrain_node or terrain_node.isAncestorOf(collided_node):
|
||||
hitPos = entry.getSurfacePoint(self.world.render)
|
||||
hitNode = collided_node
|
||||
hitTerrainInfo = terrain_info
|
||||
print(f"找到地形节点: {terrain_node.getName()}")
|
||||
break
|
||||
|
||||
if hitPos:
|
||||
break
|
||||
|
||||
if hitPos and hitTerrainInfo:
|
||||
# 修改地形高度
|
||||
x_pos, y_pos = hitPos.getX(), hitPos.getY()
|
||||
|
||||
# 使用 getattr 获取地形编辑参数
|
||||
radius = getattr(self.world, 'terrain_edit_radius', 3.0)
|
||||
strength = getattr(self.world, 'terrain_edit_strength', 0.3)
|
||||
|
||||
print(f"准备编辑地形: 位置({x_pos:.2f}, {y_pos:.2f}), 半径{radius}, 强度{strength}, 操作{operation}")
|
||||
|
||||
# 添加更多调试信息
|
||||
print(f"地形信息: {hitTerrainInfo}")
|
||||
if 'heightmap' in hitTerrainInfo:
|
||||
print(f"高度图路径: {hitTerrainInfo['heightmap']}")
|
||||
|
||||
try:
|
||||
# 检查 terrain_manager 是否有 modifyTerrainHeight 方法
|
||||
if not hasattr(self.world.terrain_manager, 'modifyTerrainHeight'):
|
||||
print("✗ 错误: terrain_manager 没有 modifyTerrainHeight 方法")
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
return
|
||||
|
||||
# 调用地形编辑方法
|
||||
success = self.world.terrain_manager.modifyTerrainHeight(
|
||||
hitTerrainInfo, x_pos, y_pos, radius=radius, strength=strength, operation=operation)
|
||||
|
||||
if success:
|
||||
print(f"✓ 地形编辑成功: {operation} at ({x_pos:.2f}, {y_pos:.2f})")
|
||||
# 显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
|
||||
# 强制刷新地形显示
|
||||
if 'terrain' in hitTerrainInfo:
|
||||
hitTerrainInfo['terrain'].generate()
|
||||
print("✓ 地形已刷新")
|
||||
else:
|
||||
print("✗ 地形编辑失败")
|
||||
# 即使编辑失败也显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
except Exception as e:
|
||||
print(f"✗ 地形编辑过程中出现异常: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# 即使编辑失败也显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
else:
|
||||
print("没有检测到地形碰撞")
|
||||
# 显示射线(无碰撞)
|
||||
self.showClickRay(worldNearPoint, worldFarPoint)
|
||||
|
||||
# 清理碰撞检测节点
|
||||
pickerNP.removeNode()
|
||||
print("地形编辑处理完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"地形编辑处理出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _handleSelectionClick(self, hitNode):
|
||||
"""处理选择工具的点击事件"""
|
||||
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")
|
||||
|
||||
@ -53,14 +53,14 @@ class SelectionSystem:
|
||||
# 高亮相关
|
||||
self.gizmoHighlightAxis = None
|
||||
self.gizmo_colors = {
|
||||
"x": (1, 0, 0, 0), # 红色
|
||||
"y": (0, 1, 0, 0), # 绿色
|
||||
"z": (0, 0, 1, 0) # 蓝色
|
||||
"x": (1, 0, 0, 1.0), # 红色
|
||||
"y": (0, 1, 0, 1.0), # 绿色
|
||||
"z": (0, 0, 1, 1.00) # 蓝色
|
||||
}
|
||||
self.gizmo_highlight_colors = {
|
||||
"x": (1, 1, 0, 0), # 黄色高亮
|
||||
"y": (1, 1, 0, 0), # 黄色高亮
|
||||
"z": (1, 1, 0, 0) # 黄色高亮
|
||||
"x": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
|
||||
"y": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
|
||||
"z": (1.0, 1.0, 0.0, 1.0) # 黄色高亮
|
||||
}
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
@ -465,63 +465,74 @@ class SelectionSystem:
|
||||
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
|
||||
axis_Rotnodes = [self.gizmoRotXAxis, self.gizmoRotYAxis, self.gizmoRotZAxis]
|
||||
|
||||
# 设置坐标轴主节点的渲染属性
|
||||
if self.gizmo:
|
||||
self.gizmo.setLightOff() # 禁用光照
|
||||
self.gizmo.setShaderOff() # 禁用着色器
|
||||
self.gizmo.setFogOff() # 禁用雾效
|
||||
self.gizmo.setBin("fixed", 40) # 设置为fixed渲染层级,数值越大越优先
|
||||
self.gizmo.setDepthWrite(False) # 禁用深度写入
|
||||
self.gizmo.setDepthTest(False) # 禁用深度测试,确保始终可见
|
||||
|
||||
# 设置各轴节点的渲染属性
|
||||
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(True)
|
||||
axis_node.setBin("fixed", 40) # 与主节点相同优先级
|
||||
axis_node.setDepthWrite(False) # 禁用深度写入
|
||||
axis_node.setDepthTest(False) # 禁用深度测试
|
||||
|
||||
# 设置旋转轴节点的渲染属性
|
||||
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)
|
||||
axis_rotnode.setBin("fixed", 40) # 与主节点相同优先级
|
||||
axis_rotnode.setDepthWrite(False) # 禁用深度写入
|
||||
axis_rotnode.setDepthTest(False) # 禁用深度测试
|
||||
|
||||
# 收集所有handle节点
|
||||
arrow_nodes = []
|
||||
if self.gizmoXAxis:
|
||||
x_handle = self.gizmoXAxis.find("x_handle")
|
||||
if x_handle:
|
||||
if x_handle and not x_handle.isEmpty():
|
||||
arrow_nodes.append(x_handle)
|
||||
if self.gizmoYAxis:
|
||||
y_handle = self.gizmoYAxis.find("y_handle")
|
||||
if y_handle:
|
||||
if y_handle and not y_handle.isEmpty():
|
||||
arrow_nodes.append(y_handle)
|
||||
if self.gizmoZAxis:
|
||||
z_handle = self.gizmoZAxis.find("z_handle")
|
||||
if z_handle:
|
||||
if z_handle and not z_handle.isEmpty():
|
||||
arrow_nodes.append(z_handle)
|
||||
|
||||
rot_nodes = []
|
||||
if self.gizmoRotXAxis:
|
||||
x_rHandle = self.gizmoRotXAxis.find("x_handle")
|
||||
if x_rHandle:
|
||||
if x_rHandle and not x_rHandle.isEmpty():
|
||||
rot_nodes.append(x_rHandle)
|
||||
if self.gizmoRotYAxis:
|
||||
y_rHandle = self.gizmoRotYAxis.find("y_handle")
|
||||
if y_rHandle:
|
||||
if y_rHandle and not y_rHandle.isEmpty():
|
||||
rot_nodes.append(y_rHandle)
|
||||
if self.gizmoRotZAxis:
|
||||
z_rHandle = self.gizmoRotZAxis.find("z_handle")
|
||||
if z_rHandle:
|
||||
if z_rHandle and not z_rHandle.isEmpty():
|
||||
rot_nodes.append(z_rHandle)
|
||||
|
||||
# 设置handle节点的渲染属性
|
||||
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.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||
arrow_node.setDepthWrite(False)
|
||||
arrow_node.setDepthTest(False)
|
||||
# 启用透明度支持
|
||||
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
for rot_node in rot_nodes:
|
||||
@ -529,20 +540,12 @@ class SelectionSystem:
|
||||
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.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||
rot_node.setDepthWrite(False)
|
||||
rot_node.setDepthTest(False)
|
||||
# 启用透明度支持
|
||||
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)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴渲染属性失败: {str(e)}")
|
||||
|
||||
@ -647,7 +650,7 @@ class SelectionSystem:
|
||||
|
||||
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())
|
||||
@ -799,12 +802,27 @@ class SelectionSystem:
|
||||
# 创建或获取材质
|
||||
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)
|
||||
# # 设置材质属性 - 使用自发光确保在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)
|
||||
|
||||
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
|
||||
adjusted_color = Vec4(
|
||||
min(color[0]*20, 1.0),
|
||||
min(color[1]*20, 1.0),
|
||||
min(color[2]*20, 1.0),
|
||||
color[3]
|
||||
)
|
||||
|
||||
mat.setBaseColor(adjusted_color)
|
||||
# mat.setDiffuse(adjusted_color * 0.8) # 稍微降低漫反射亮度
|
||||
# mat.setAmbient(adjusted_color * 0.3) # 设置环境光反射
|
||||
# mat.setSpecular(Vec4(0.3, 0.3, 0.3, 1.0)) # 适度的镜面反射
|
||||
# mat.setShininess(25.0) # 适中的高光强度
|
||||
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
|
||||
|
||||
# 应用材质
|
||||
handle_node.setMaterial(mat, 1)
|
||||
@ -820,9 +838,9 @@ class SelectionSystem:
|
||||
handle_node.setShaderOff() # 禁用着色器
|
||||
handle_node.setFogOff() # 禁用雾效果
|
||||
|
||||
handle_node.setBin("fixed",31)
|
||||
#arrow_node.setDepthWrite(False)
|
||||
#arrow_node.setDepthTest(True)
|
||||
handle_node.setBin("fixed",41)
|
||||
handle_node.setDepthWrite(False)
|
||||
handle_node.setDepthTest(False)
|
||||
|
||||
# 保存材质引用以便后续修改
|
||||
if axis == "x":
|
||||
@ -835,9 +853,9 @@ class SelectionSystem:
|
||||
axis_node.setLightOff()
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 30)
|
||||
axis_node.setBin("fixed", 40)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setDepthTest(True)
|
||||
axis_node.setDepthTest(False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
@ -889,12 +907,28 @@ class SelectionSystem:
|
||||
# 创建或获取材质
|
||||
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)
|
||||
# # 设置材质属性 - 使用自发光确保在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)
|
||||
|
||||
# 设置材质属性 - 使用更自然的颜色,避免过亮的自发光
|
||||
# 将颜色值控制在合理范围内
|
||||
adjusted_color = Vec4(
|
||||
min(color[0], 1.0),
|
||||
min(color[1], 1.0),
|
||||
min(color[2], 1.0),
|
||||
color[3]
|
||||
)
|
||||
|
||||
mat.setBaseColor(adjusted_color)
|
||||
#mat.setDiffuse(adjusted_color * 1) # 稍微降低漫反射亮度
|
||||
#mat.setAmbient(adjusted_color * 0.4) # 设置环境光反射
|
||||
# mat.setSpecular(Vec4(0.4, 0.4, 0.4, 1.0)) # 适度的镜面反射
|
||||
# mat.setShininess(1.0) # 适中的高光强度
|
||||
mat.setEmission(Vec4(1, 1, 1, 1.0)) # 自发光
|
||||
|
||||
# 应用材质
|
||||
handle_node.setMaterial(mat, 1)
|
||||
@ -910,9 +944,9 @@ class SelectionSystem:
|
||||
handle_node.setShaderOff() # 禁用着色器
|
||||
handle_node.setFogOff() # 禁用雾效果
|
||||
|
||||
handle_node.setBin("fixed",31)
|
||||
#arrow_node.setDepthWrite(False)
|
||||
#arrow_node.setDepthTest(True)
|
||||
handle_node.setBin("fixed",41)
|
||||
handle_node.setDepthWrite(False)
|
||||
handle_node.setDepthTest(False)
|
||||
|
||||
# 保存材质引用以便后续修改
|
||||
if axis == "x":
|
||||
@ -925,9 +959,9 @@ class SelectionSystem:
|
||||
axis_node.setLightOff()
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 30)
|
||||
#axis_node.setDepthWrite(False)
|
||||
#axis_node.setDepthTest(True)
|
||||
axis_node.setBin("fixed", 40)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setDepthTest(False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
@ -964,6 +998,8 @@ class SelectionSystem:
|
||||
# 稍微放大以增强视觉效果
|
||||
axis_nodes[axis].setScale(1.1)
|
||||
|
||||
self.gizmoHighlightAxis = axis
|
||||
|
||||
except Exception as e:
|
||||
print(f"鼠标进入坐标轴处理失败: {e}")
|
||||
|
||||
@ -990,6 +1026,9 @@ class SelectionSystem:
|
||||
if axis in axis_nodes and axis_nodes[axis]:
|
||||
axis_nodes[axis].setScale(1.0)
|
||||
|
||||
if self.gizmoHighlightAxis == axis:
|
||||
self.gizmoHighlightAxis = None
|
||||
|
||||
except Exception as e:
|
||||
print(f"鼠标离开坐标轴处理失败: {e}")
|
||||
|
||||
@ -1435,8 +1474,12 @@ class SelectionSystem:
|
||||
print("拖拽更新失败: 没有坐标轴起始位置")
|
||||
return
|
||||
|
||||
is_scale_tool = self.world.tool_manager.isScaleTool()
|
||||
is_rotate_tool = self.world.tool_manager.isRotateTool()
|
||||
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
|
||||
|
||||
is_gui_element = (hasattr(self.gizmoTarget,'getTag') and
|
||||
self.gizmoTarget.getTag("is_gui_element") == "1")
|
||||
|
||||
|
||||
# 计算鼠标移动距离(屏幕像素)
|
||||
mouseDeltaX = mouseX - self.dragStartMousePos[0]
|
||||
@ -1446,22 +1489,35 @@ class SelectionSystem:
|
||||
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)
|
||||
if is_gui_element:
|
||||
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":
|
||||
new_scale = Vec3(start_scale.x, start_scale.y, start_scale.z * scale_factor)
|
||||
else:
|
||||
new_scale = Vec3(start_scale.x * scale_factor,
|
||||
start_scale.y * scale_factor,
|
||||
start_scale.z * scale_factor)
|
||||
else:
|
||||
new_scale = Vec3(start_scale.x * scale_factor,
|
||||
start_scale * scale_factor,
|
||||
start_scale.z * scale_factor)
|
||||
#应用新缩放值
|
||||
# 普通3D模型的缩放处理
|
||||
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.y * scale_factor,
|
||||
start_scale.z * scale_factor)
|
||||
|
||||
# 应用新缩放值
|
||||
self.gizmoTarget.setScale(new_scale)
|
||||
#实时更新属性面板
|
||||
# 安全地更新属性面板
|
||||
#self._safeUpdatePropertyPanel()
|
||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||
return
|
||||
elif is_rotate_tool:
|
||||
@ -1481,6 +1537,7 @@ class SelectionSystem:
|
||||
start_hpr.y + rotation_amount,
|
||||
start_hpr.z + rotation_amount)
|
||||
self.gizmoTarget.setHpr(new_hpr)
|
||||
#self._safeUpdatePropertyPanel()
|
||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||
return
|
||||
|
||||
@ -1647,6 +1704,7 @@ class SelectionSystem:
|
||||
|
||||
# 实时更新属性面板
|
||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||
#self._safeUpdatePropertyPanel()
|
||||
|
||||
# 每次拖拽都输出调试信息(但限制频率)
|
||||
if not hasattr(self, '_last_drag_debug_time'):
|
||||
@ -1663,6 +1721,39 @@ class SelectionSystem:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _safeUpdatePropertyPanel(self):
|
||||
"""安全地更新属性面板"""
|
||||
try:
|
||||
# 检查属性面板是否存在且有效
|
||||
if (hasattr(self.world, 'property_panel') and
|
||||
self.world.property_panel is not None):
|
||||
|
||||
# 检查目标节点是否有效
|
||||
if (self.gizmoTarget is not None and
|
||||
not self.gizmoTarget.isEmpty()):
|
||||
|
||||
# 检查是否有refreshModelValues方法
|
||||
if hasattr(self.world.property_panel, 'refreshModelValues'):
|
||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||
# 如果是GUI元素,可能需要特殊的处理
|
||||
elif (hasattr(self.gizmoTarget, 'getTag') and
|
||||
self.gizmoTarget.getTag("is_gui_element") == "1" and
|
||||
hasattr(self.world.property_panel, 'updateGUIPropertyPanel')):
|
||||
# 对于GUI元素,可能需要重新构建整个面板
|
||||
if (hasattr(self.world, 'treeWidget') and
|
||||
self.world.treeWidget is not None):
|
||||
current_item = self.world.treeWidget.currentItem()
|
||||
if current_item is not None:
|
||||
self.world.property_panel.updatePropertyPanel(current_item)
|
||||
except RuntimeError as e:
|
||||
if "wrapped C/C++ object" in str(e):
|
||||
# 忽略控件已被删除的错误
|
||||
print("警告: 属性面板控件已被删除,跳过更新")
|
||||
else:
|
||||
print(f"更新属性面板时出错: {e}")
|
||||
except Exception as e:
|
||||
print(f"更新属性面板失败: {e}")
|
||||
|
||||
def stopGizmoDrag(self):
|
||||
"""停止坐标轴拖拽"""
|
||||
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
|
||||
@ -1690,19 +1781,34 @@ class SelectionSystem:
|
||||
def updateSelection(self, nodePath):
|
||||
"""更新选择状态"""
|
||||
print(f"\n=== 更新选择状态 ===")
|
||||
print(f"新选择的节点: {nodePath.getName() if nodePath else 'None'}")
|
||||
|
||||
# 如果正在删除节点,避免更新选择
|
||||
if hasattr(self, '_deleting_node') and self._deleting_node:
|
||||
print("正在删除节点,跳过选择更新")
|
||||
print("=== 选择状态更新完成 ===\n")
|
||||
return
|
||||
|
||||
node_name = "None"
|
||||
if nodePath and not nodePath.isEmpty():
|
||||
node_name = nodePath.getName()
|
||||
print(f"新选择的节点: {node_name}")
|
||||
|
||||
self.selectedNode = nodePath
|
||||
# 添加兼容性属性
|
||||
self.selectedObject = nodePath
|
||||
if nodePath:
|
||||
print(f"开始为节点 {nodePath.getName()} 创建选择框和坐标轴...")
|
||||
|
||||
if nodePath and not nodePath.isEmpty():
|
||||
node_name = nodePath.getName()
|
||||
print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
|
||||
|
||||
# 创建选择框
|
||||
print("创建选择框...")
|
||||
self.createSelectionBox(nodePath)
|
||||
if self.selectionBox:
|
||||
print(f"✓ 选择框创建成功: {self.selectionBox.getName()}")
|
||||
box_name = "Unknown"
|
||||
if self.selectionBox and not self.selectionBox.isEmpty():
|
||||
box_name = self.selectionBox.getName()
|
||||
print(f"✓ 选择框创建成功: {box_name}")
|
||||
else:
|
||||
print("× 选择框创建失败")
|
||||
|
||||
@ -1710,11 +1816,14 @@ class SelectionSystem:
|
||||
print("创建坐标轴...")
|
||||
self.createGizmo(nodePath)
|
||||
if self.gizmo:
|
||||
print(f"✓ 坐标轴创建成功: {self.gizmo.getName()}")
|
||||
gizmo_name = "Unknown"
|
||||
if self.gizmo and not self.gizmo.isEmpty():
|
||||
gizmo_name = self.gizmo.getName()
|
||||
print(f"✓ 坐标轴创建成功: {gizmo_name}")
|
||||
else:
|
||||
print("× 坐标轴创建失败")
|
||||
|
||||
print(f"✓ 选中了节点: {nodePath.getName()}")
|
||||
print(f"✓ 选中了节点: {node_name}")
|
||||
else:
|
||||
print("清除选择...")
|
||||
self.clearSelectionBox()
|
||||
@ -1732,10 +1841,10 @@ class SelectionSystem:
|
||||
return self.selectedNode is not None
|
||||
|
||||
def checkAndClearIfTargetDeleted(self):
|
||||
if self.gizmoTarget and self.gizmoTarget.isEmpty():
|
||||
if (self.gizmoTarget and self.gizmoTarget.isEmpty()):
|
||||
self.clearGizmo()
|
||||
|
||||
if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty():
|
||||
if (self.selectionBoxTarget and self.selectionBoxTarget.isEmpty()):
|
||||
self.clearSelectionBox()
|
||||
|
||||
def setupGizmoCollision(self):
|
||||
|
||||
472
core/terrain_manager.py
Normal file
472
core/terrain_manager.py
Normal file
@ -0,0 +1,472 @@
|
||||
# core/terrain_manager.py
|
||||
import time
|
||||
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
|
||||
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
|
||||
import os
|
||||
|
||||
|
||||
class TerrainManager:
|
||||
"""地形管理类"""
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.terrains = []
|
||||
|
||||
# core/terrain_manager.py
|
||||
|
||||
def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)):
|
||||
"""从高度图创建地形"""
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(heightmap_path):
|
||||
print(f"错误: 高度图文件 {heightmap_path} 不存在")
|
||||
return None
|
||||
|
||||
try:
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 加载高度图
|
||||
height_image = PNMImage(Filename.fromOsSpecific(heightmap_path))
|
||||
|
||||
# 检查并调整图像尺寸为2的幂次方加1
|
||||
width, height = height_image.getXSize(), height_image.getYSize()
|
||||
print(f"原始图像尺寸: {width}x{height}")
|
||||
|
||||
# 找到最接近的有效尺寸
|
||||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025, 2049]
|
||||
target_size = 129 # 默认尺寸
|
||||
|
||||
# 选择最接近的尺寸
|
||||
max_dim = max(width, height)
|
||||
for size in valid_sizes:
|
||||
if size >= max_dim:
|
||||
target_size = size
|
||||
break
|
||||
else:
|
||||
target_size = valid_sizes[-1] # 使用最大尺寸
|
||||
|
||||
# 如果需要,调整图像尺寸
|
||||
if width != target_size or height != target_size:
|
||||
print(f"调整图像尺寸从 {width}x{height} 到 {target_size}x{target_size}")
|
||||
# 使用正确的图像缩放方法
|
||||
resized_image = PNMImage(target_size, target_size)
|
||||
resized_image.quickFilterFrom(height_image)
|
||||
height_image = resized_image
|
||||
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBruteforce(True) # 使用LOD
|
||||
|
||||
terrain.setBlockSize(32)
|
||||
# terrain.setNearFarThreshold(50.0,200.0)
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (target_size - 1) / 2
|
||||
terrain_node.setPos(-center_offset * scale[0], -center_offset * scale[1], -5)
|
||||
|
||||
# 设置缩放
|
||||
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
terrain_node.setPythonTag("selectable", True)
|
||||
|
||||
# 保存地形信息(包括高度图的副本)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': heightmap_path,
|
||||
'heightfield': height_image, # 保存高度图副本
|
||||
'scale': scale,
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
|
||||
'updateSceneTree'):
|
||||
try:
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
except Exception as e:
|
||||
print(f"警告: 更新场景树时出错: {e}")
|
||||
|
||||
print(f"✓ 成功从 {heightmap_path} 创建地形")
|
||||
return terrain_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建地形时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def createFlatTerrain(self, size=(0.3, 0.3), resolution=129):
|
||||
"""创建平面地形"""
|
||||
try:
|
||||
# 确保分辨率是2的幂次方加1 (如129, 257, 513等)
|
||||
valid_resolutions = [17, 33, 65, 129, 257, 513, 1025]
|
||||
if resolution not in valid_resolutions:
|
||||
# 找到最接近的有效分辨率
|
||||
closest_res = min(valid_resolutions, key=lambda x: abs(x - resolution))
|
||||
print(f"警告: 分辨率 {resolution} 不是有效的地形分辨率,使用 {closest_res}")
|
||||
resolution = closest_res
|
||||
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"flat_terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 创建一个平面高度图,尺寸必须是2^n+1
|
||||
height_image = PNMImage(resolution, resolution)
|
||||
height_image.fill(0.5) # 设置为中等高度 (0.0-1.0范围)
|
||||
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
terrain.setBruteforce(True) # 设置LOD
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBlockSize(32) # 设置块大小
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的平面地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (resolution - 1) / 2
|
||||
terrain_node.setPos(-center_offset * size[0], -center_offset * size[1], 0)
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
|
||||
# 为地形添加碰撞体
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
# 保存地形信息(包括高度图)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': None,
|
||||
'heightfield': height_image, # 保存高度图
|
||||
'scale': (size[0], size[1], 50),
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
|
||||
'updateSceneTree'):
|
||||
try:
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
except Exception as e:
|
||||
print(f"警告: 更新场景树时出错: {e}")
|
||||
|
||||
print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}")
|
||||
return terrain_info
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建平面地形时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _applyTerrainMaterial(self, terrain_node):
|
||||
"""为地形应用材质"""
|
||||
try:
|
||||
# 创建材质
|
||||
material = Material()
|
||||
material.setAmbient((0.5, 0.5, 0.5, 1.0)) # 环境光反射
|
||||
material.setDiffuse((1, 1, 1, 1.0)) # 漫反射(绿色调)
|
||||
material.setSpecular((0.2, 0.2, 0.2, 1.0)) # 镜面反射
|
||||
material.setShininess(5.0) # 高光强度
|
||||
|
||||
# 应用材质到地形
|
||||
terrain_node.setMaterial(material)
|
||||
|
||||
# 启用材质
|
||||
terrain_node.setAttrib(ColorAttrib.makeDefault())
|
||||
|
||||
print("✓ 地形材质已应用")
|
||||
except Exception as e:
|
||||
print(f"应用地形材质时出错: {e}")
|
||||
|
||||
def updateTerrain(self):
|
||||
"""更新所有地形的LOD"""
|
||||
for terrain_info in self.terrains:
|
||||
terrain = terrain_info['terrain']
|
||||
if hasattr(terrain, 'update'):
|
||||
terrain.update()
|
||||
|
||||
def setTerrainLODThreshold(self, terrain_info, threshold):
|
||||
"""设置地形LOD阈值"""
|
||||
if terrain_info in self.terrains:
|
||||
terrain = terrain_info['terrain']
|
||||
# GeoMipTerrain可能没有setNearFarThreshold方法,使用其他方式设置LOD
|
||||
print("注意: GeoMipTerrain可能不支持直接设置LOD阈值")
|
||||
|
||||
def getTerrainHeight(self, terrain_info, x, y):
|
||||
"""获取地形上指定点的高度"""
|
||||
if terrain_info in self.terrains:
|
||||
terrain = terrain_info['terrain']
|
||||
if hasattr(terrain, 'getElevation'):
|
||||
return terrain.getElevation(x, y)
|
||||
return 0
|
||||
|
||||
# core/terrain_manager.py
|
||||
|
||||
def modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
|
||||
"""修改地形高度"""
|
||||
if terrain_info not in self.terrains:
|
||||
return False
|
||||
|
||||
try:
|
||||
terrain = terrain_info['terrain']
|
||||
terrain_node = terrain_info['node'] # 获取地形节点
|
||||
|
||||
# 直接使用我们保存的高度图数据
|
||||
if 'heightfield' not in terrain_info:
|
||||
print("地形信息中没有保存高度图数据")
|
||||
return False
|
||||
|
||||
heightmap = terrain_info['heightfield']
|
||||
if not heightmap:
|
||||
print("无法获取地形高度图数据")
|
||||
return False
|
||||
|
||||
# 获取高度图尺寸
|
||||
width = heightmap.getXSize()
|
||||
height = heightmap.getYSize()
|
||||
|
||||
# 获取地形节点信息
|
||||
terrain_pos = terrain_node.getPos()
|
||||
terrain_scale = terrain_node.getScale()
|
||||
|
||||
# 正确的坐标转换方法
|
||||
# 1. 将世界坐标转换为相对于地形中心的坐标
|
||||
relative_x = x - terrain_pos.getX()
|
||||
relative_y = y - terrain_pos.getY()
|
||||
|
||||
# 2. 考虑地形的缩放
|
||||
scaled_x = relative_x / terrain_scale.getX()
|
||||
scaled_y = relative_y / terrain_scale.getY()
|
||||
|
||||
# 3. 转换为高度图坐标
|
||||
center_offset = (width - 1) / 2
|
||||
center_x = int(scaled_x + center_offset)
|
||||
center_y = int(scaled_y + center_offset)
|
||||
|
||||
print(f"坐标转换详情:")
|
||||
print(f" 世界坐标: ({x:.2f}, {y:.2f})")
|
||||
print(f" 地形位置: ({terrain_pos.getX():.2f}, {terrain_pos.getY():.2f})")
|
||||
print(f" 地形缩放: ({terrain_scale.getX():.2f}, {terrain_scale.getY():.2f})")
|
||||
print(f" 相对坐标: ({relative_x:.2f}, {relative_y:.2f})")
|
||||
print(f" 缩放坐标: ({scaled_x:.2f}, {scaled_y:.2f})")
|
||||
print(f" 中心偏移: {center_offset}")
|
||||
print(f" 高度图坐标: ({center_x}, {center_y})")
|
||||
print(f" 高度图尺寸: {width} x {height}")
|
||||
|
||||
# 检查中心点是否在有效范围内
|
||||
if not (0 <= center_x < width and 0 <= center_y < height):
|
||||
print(f"❌ 中心点坐标超出范围: ({center_x}, {center_y})")
|
||||
print(f"❌ 有效范围: (0-{width - 1}, 0-{height - 1})")
|
||||
return False
|
||||
|
||||
# 修改半径范围内的点
|
||||
modified = False
|
||||
|
||||
# 使用正确的半径计算(考虑地形缩放)
|
||||
effective_radius = max(1, int(radius / max(terrain_scale.getX(), terrain_scale.getY())))
|
||||
|
||||
print(f"有效编辑半径: {effective_radius}")
|
||||
|
||||
# 限制编辑半径不超过地形尺寸的1/4,避免性能问题
|
||||
effective_radius = min(effective_radius, width // 4, height // 4)
|
||||
|
||||
for dx in range(-effective_radius, effective_radius + 1):
|
||||
for dy in range(-effective_radius, effective_radius + 1):
|
||||
# 检查距离
|
||||
distance = (dx * dx + dy * dy) ** 0.5
|
||||
if distance <= effective_radius:
|
||||
# 计算影响权重
|
||||
weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0
|
||||
|
||||
# 计算目标点坐标
|
||||
target_x = center_x + dx
|
||||
target_y = center_y + dy
|
||||
|
||||
# 检查边界
|
||||
if 0 <= target_x < width and 0 <= target_y < height:
|
||||
# 获取当前高度
|
||||
current_height = heightmap.getRed(target_x, target_y)
|
||||
|
||||
# 根据操作类型修改高度
|
||||
if operation == "add":
|
||||
new_height = min(1.0, current_height + strength * weight * 0.01)
|
||||
elif operation == "subtract":
|
||||
new_height = max(0.0, current_height - strength * weight * 0.01)
|
||||
elif operation == "set":
|
||||
# 平坦化操作,设置为中等高度
|
||||
new_height = 0.5
|
||||
else:
|
||||
new_height = current_height
|
||||
|
||||
# 设置新高度
|
||||
heightmap.setRed(target_x, target_y, new_height)
|
||||
heightmap.setGreen(target_x, target_y, new_height)
|
||||
heightmap.setBlue(target_x, target_y, new_height)
|
||||
modified = True
|
||||
|
||||
# 如果有修改,则重新生成地形
|
||||
if modified:
|
||||
# 重新设置高度图并生成地形
|
||||
terrain.setHeightfield(heightmap)
|
||||
terrain.generate()
|
||||
|
||||
# 关键修复:重新创建碰撞体,因为在生成地形时会丢失原有碰撞体
|
||||
from panda3d.core import BitMask32, CollisionNode, CollisionPlane, Plane, Point3
|
||||
# 移除旧的地形碰撞体(如果存在)
|
||||
for child in terrain_node.getChildren():
|
||||
if isinstance(child.node(), CollisionNode) and child.getName().startswith("terrain_collision_"):
|
||||
child.removeNode()
|
||||
|
||||
# 创建新的地形碰撞体
|
||||
# 使用更简单的方法:直接给地形节点添加碰撞体
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 确保地形节点本身具有碰撞掩码
|
||||
|
||||
# 为地形的每个子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}, 操作{operation}")
|
||||
print(f"✓ 重新设置了地形碰撞体")
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
return modified
|
||||
|
||||
except Exception as e:
|
||||
print(f"修改地形高度时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def deleteTerrain(self, terrain_info):
|
||||
"""删除地形"""
|
||||
if terrain_info in self.terrains:
|
||||
# 从场景中移除地形节点
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node and not terrain_node.isEmpty():
|
||||
terrain_node.removeNode()
|
||||
|
||||
# 从列表中移除
|
||||
self.terrains.remove(terrain_info)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}")
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def getTerrainByName(self, name):
|
||||
"""根据名称查找地形"""
|
||||
for terrain_info in self.terrains:
|
||||
if terrain_info.get('name') == name:
|
||||
return terrain_info
|
||||
return None
|
||||
|
||||
def getAllTerrainNames(self):
|
||||
"""获取所有地形名称"""
|
||||
return [terrain_info.get('name', f'Terrain_{i}') for i, terrain_info in enumerate(self.terrains)]
|
||||
|
||||
def setTerrainColor(self, terrain_info, color):
|
||||
"""设置地形颜色"""
|
||||
try:
|
||||
if terrain_info in self.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node:
|
||||
# 创建新材质
|
||||
material = Material()
|
||||
material.setAmbient((color[0] * 0.5, color[1] * 0.5, color[2] * 0.5, 1.0))
|
||||
material.setDiffuse((color[0], color[1], color[2], 1.0))
|
||||
material.setSpecular((0.2, 0.2, 0.2, 1.0))
|
||||
material.setShininess(5.0)
|
||||
|
||||
# 应用材质
|
||||
terrain_node.setMaterial(material)
|
||||
print(f"✓ 地形颜色已更新: {color}")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"设置地形颜色时出错: {e}")
|
||||
return False
|
||||
|
||||
def setTerrainTexture(self, terrain_info, texture_path):
|
||||
"""为地形设置纹理"""
|
||||
try:
|
||||
if terrain_info in self.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node and os.path.exists(texture_path):
|
||||
# 加载纹理
|
||||
texture = self.world.loader.loadTexture(texture_path)
|
||||
if texture:
|
||||
# 应用纹理
|
||||
terrain_node.setTexture(texture, 1)
|
||||
print(f"✓ 地形纹理已应用: {texture_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 无法加载纹理: {texture_path}")
|
||||
else:
|
||||
print(f"✗ 纹理文件不存在: {texture_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"应用地形纹理时出错: {e}")
|
||||
return False
|
||||
@ -10,9 +10,9 @@ GUI元素管理模块
|
||||
|
||||
from direct.gui.DirectGui import *
|
||||
from panda3d.core import *
|
||||
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||||
QDoubleSpinBox, QPushButton, QDialogButtonBox,
|
||||
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout)
|
||||
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||||
QDoubleSpinBox, QPushButton, QDialogButtonBox,
|
||||
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout, QGridLayout, QSpinBox)
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import Qt
|
||||
# 尝试导入 QtWebEngineWidgets,如果失败则设置为 None
|
||||
@ -147,6 +147,112 @@ class GUIManager:
|
||||
|
||||
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return entry
|
||||
def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
|
||||
"""创建2D GUI图片"""
|
||||
from direct.gui.DirectGui import DirectFrame
|
||||
from panda3d.core import TransparencyAttrib,Texture,CardMaker,CardMaker,Vec3
|
||||
|
||||
# 将3D坐标转换为2D屏幕坐标
|
||||
gui_pos = (pos[0] * 0.1, 0, pos[2] * 0.1)
|
||||
|
||||
#使用CardMaker创建一个更可靠的图片框架
|
||||
cm = CardMaker('gui-2d-image')
|
||||
cm.setFrame(-size,size,-size,size)
|
||||
|
||||
image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||||
image_node.setPos(gui_pos)
|
||||
image_node.setBin('fixed',0)
|
||||
image_node.setDepthWrite(False)
|
||||
image_node.setDepthTest(False)
|
||||
image_node.setColor(1,1,1,1)
|
||||
|
||||
# 设置透明度支持
|
||||
image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 如果提供了图像路径,则加载纹理
|
||||
if image_path:
|
||||
try:
|
||||
texture = self.world.loader.loadTexture(image_path)
|
||||
if texture:
|
||||
image_node.setTexture(texture,1)
|
||||
texture.setWrapU(Texture.WM_clamp)
|
||||
texture.setWrapV(Texture.WM_clamp)
|
||||
texture.setMinfilter(Texture.FT_linear)
|
||||
texture.setMagfilter(Texture.FT_linear)
|
||||
image_node.setColor(1,1,1,1)
|
||||
else:
|
||||
print(f"⚠️ 无法加载2D图片纹理: {image_path}")
|
||||
except Exception as e:
|
||||
print(f"❌ 加载2D图片纹理失败: {e}")
|
||||
|
||||
# 为GUI元素添加标识
|
||||
image_node.setTag("gui_type", "2d_image")
|
||||
image_node.setTag("gui_id", f"2d_image_{len(self.gui_elements)}")
|
||||
image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}")
|
||||
if image_path:
|
||||
image_node.setTag("image_path", image_path)
|
||||
image_node.setTag("is_gui_element", "1")
|
||||
|
||||
self.gui_elements.append(image_node)
|
||||
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 创建2D GUI图片 (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||
return image_node
|
||||
|
||||
def constrain2DPosition(self,gui_element,new_x=None,new_z=None):
|
||||
"""限制2dGUI元素位置在屏幕范围内"""
|
||||
try:
|
||||
from panda3d.core import Vec3
|
||||
|
||||
bounds = gui_element.getTightBounds()
|
||||
element_width=0
|
||||
element_height=0
|
||||
|
||||
if bounds:
|
||||
min_point,max_point = bounds
|
||||
element_width = (max_point.getX() - min_point.getX())/2
|
||||
element_height = (max_point.getZ()-min_point.getZ())/2
|
||||
|
||||
#获取当前缩放
|
||||
scale = gui_element.getScale()
|
||||
if hasattr(scale,'getX'):
|
||||
scale_x = scale.getX()
|
||||
scale_z = scale.getZ() if hasattr(scale,'getZ') else scale_x
|
||||
else:
|
||||
scale_x = scale_z = scale if isinstance(scale,(int,float)) else 1.0
|
||||
|
||||
actual_width = element_width * scale_x
|
||||
actual_height = element_height * scale_z
|
||||
|
||||
screen_width = 1.9
|
||||
screen_height = 0.9
|
||||
|
||||
min_x = -screen_width + actual_width
|
||||
max_x = screen_width - actual_width
|
||||
min_z = -screen_height + actual_height
|
||||
max_z = screen_height - actual_height
|
||||
|
||||
#获取当前位置
|
||||
current_pos = gui_element.getPos()
|
||||
x = new_x if new_x is not None else current_pos.getX()
|
||||
z = new_z if new_z is not None else current_pos.getZ()
|
||||
|
||||
#应用边界限制
|
||||
x = max(min_x,min(max_x,x))
|
||||
z = max(min_z,min(max_z,z))
|
||||
|
||||
return Vec3(x,current_pos.getY(),z)
|
||||
except Exception as e:
|
||||
print(f"约束2D位置时出错: {e}")
|
||||
# 出错时返回原始值
|
||||
current_pos = gui_element.getPos()
|
||||
x = new_x if new_x is not None else current_pos.getX()
|
||||
z = new_z if new_z is not None else current_pos.getZ()
|
||||
return Vec3(x, current_pos.getY(), z)
|
||||
|
||||
|
||||
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1):
|
||||
"""创建3D空间文本"""
|
||||
@ -436,25 +542,34 @@ class GUIManager:
|
||||
|
||||
elif property_name == "color": # 添加颜色处理
|
||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||
# 更新材质颜色
|
||||
if not gui_element.hasMaterial():
|
||||
material = Material(f"text-material-{gui_element.getName()}")
|
||||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
gui_element.setMaterial(material, 1)
|
||||
else:
|
||||
material = gui_element.getMaterial()
|
||||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
gui_element.setMaterial(material, 1)
|
||||
# 对于2D GUI元素(button和label),使用frameColor属性
|
||||
if gui_type in ["button", "label"]:
|
||||
# 设置背景颜色
|
||||
gui_element['frameColor'] = (value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0)
|
||||
print(f"成功更新2D GUI背景颜色: {gui_type} -> {value}")
|
||||
|
||||
# 对于3D元素,使用材质颜色
|
||||
elif gui_type in ["3d_text", "3d_image", "virtual_screen"]:
|
||||
# 更新材质颜色
|
||||
if not gui_element.hasMaterial():
|
||||
material = Material(f"text-material-{gui_element.getName()}")
|
||||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
gui_element.setMaterial(material, 1)
|
||||
else:
|
||||
material = gui_element.getMaterial()
|
||||
material.setBaseColor(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
material.setDiffuse(Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
gui_element.setMaterial(material, 1)
|
||||
|
||||
# 更新 TextNode 的文本颜色
|
||||
if isinstance(gui_element.node(), TextNode):
|
||||
gui_element.node().setTextColor(
|
||||
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
# if gui_type in ["3d_text", "virtual_screen"]:
|
||||
# gui_element.setColor(*value)
|
||||
# elif gui_type in ["button", "label"]:
|
||||
# gui_element['text_fg'] = value
|
||||
if isinstance(gui_element.node(), TextNode):
|
||||
gui_element.node().setTextColor(
|
||||
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||
print(f"成功更新3D GUI颜色: {gui_type} -> {value}")
|
||||
|
||||
else:
|
||||
print(f"警告: 未知的GUI类型 {gui_type},无法设置颜色")
|
||||
|
||||
elif property_name == "position":
|
||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||
@ -497,6 +612,9 @@ class GUIManager:
|
||||
elif gui_type == "3d_image":
|
||||
image_path = gui_element.getTag("image_path")
|
||||
self.createGUI3DImage(new_pos,image_path,size=(2,2))
|
||||
elif gui_type == "2d_image":
|
||||
image_path = gui_element.getTag("image_path")
|
||||
self.createGUI2DImage(new_pos, image_path, size=0.2)
|
||||
elif gui_type == "virtual_screen":
|
||||
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
||||
|
||||
@ -745,7 +863,20 @@ class GUIManager:
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
)
|
||||
y_pos -= spacing
|
||||
|
||||
|
||||
# 2D图片工具
|
||||
btn_2d_image = DirectButton(
|
||||
parent=self.guiEditPanel,
|
||||
text="2D图片",
|
||||
pos=(0, 0, y_pos),
|
||||
scale=0.04,
|
||||
command=self.setGUICreateTool,
|
||||
extraArgs=["2d_image"],
|
||||
frameColor=(0.8, 0.6, 0.2, 1),
|
||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
)
|
||||
y_pos -= spacing
|
||||
|
||||
# 虚拟屏幕工具
|
||||
btn_screen = DirectButton(
|
||||
parent=self.guiEditPanel,
|
||||
@ -944,6 +1075,8 @@ class GUIManager:
|
||||
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
||||
elif gui_type == "3d_image":
|
||||
element = self.createGUI3DImage(pos)
|
||||
elif gui_type == "2d_image":
|
||||
element = self.createGUI2DImage(pos)
|
||||
elif gui_type == "virtual_screen":
|
||||
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
||||
else:
|
||||
@ -1039,54 +1172,198 @@ class GUIManager:
|
||||
|
||||
# 位置属性
|
||||
if hasattr(gui_element, 'getPos'):
|
||||
# 根据GUI类型设置组名
|
||||
if gui_type in ["button", "label", "entry", "2d_image"]:
|
||||
transform_group = QGroupBox("变换 Rect Transform")
|
||||
else:
|
||||
transform_group = QGroupBox("变换 Transform")
|
||||
|
||||
transform_layout = QGridLayout()
|
||||
|
||||
pos = gui_element.getPos()
|
||||
|
||||
# 根据GUI类型决定位置编辑方式
|
||||
if gui_type in ["button", "label", "entry"]:
|
||||
if gui_type in ["button", "label", "entry","2d_image"]:
|
||||
# 2D GUI组件使用屏幕坐标
|
||||
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||||
logical_z = pos.getZ() / 0.1
|
||||
|
||||
|
||||
transform_layout.addWidget(QLabel("屏幕位置"), 0, 0)
|
||||
|
||||
x_label = QLabel("X")
|
||||
z_label = QLabel("z")
|
||||
x_label.setAlignment(Qt.Aligncenter)
|
||||
z_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(x_label, 0, 1)
|
||||
transform_layout.addWidget(z_label, 0, 2)
|
||||
|
||||
xPos = QDoubleSpinBox()
|
||||
xPos.setRange(-50, 50)
|
||||
xPos.setValue(logical_x)
|
||||
xPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "x", v))
|
||||
layout.addRow("屏幕位置 X:", xPos)
|
||||
|
||||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||||
transform_layout.addWidget(xPos, 1, 1)
|
||||
|
||||
zPos = QDoubleSpinBox()
|
||||
zPos.setRange(-50, 50)
|
||||
zPos.setValue(logical_z)
|
||||
zPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "z", v))
|
||||
layout.addRow("屏幕位置 Z:", zPos)
|
||||
|
||||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||||
transform_layout.addWidget(zPos, 1, 2)
|
||||
|
||||
# 显示实际屏幕坐标(只读)
|
||||
transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
|
||||
|
||||
actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||||
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
layout.addRow("实际屏幕 X:", actualXLabel)
|
||||
|
||||
actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||||
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
layout.addRow("实际屏幕 Z:", actualZLabel)
|
||||
|
||||
transform_layout.addWidget(actualXLabel, 3, 1)
|
||||
transform_layout.addWidget(actualZLabel, 3, 2)
|
||||
# 添加宽度和高度控件(对于2D图像)
|
||||
if gui_type == "2d_image":
|
||||
|
||||
# 添加排序控制组
|
||||
sort_group = QGroupBox("渲染顺序")
|
||||
sort_layout = QGridLayout()
|
||||
|
||||
# 获取当前的sort值,如果没有设置则默认为0
|
||||
current_sort = int(gui_element.getTag("sort") or "0")
|
||||
|
||||
sort_layout.addWidget(QLabel("层级:"), 0, 0)
|
||||
|
||||
sort_spin = QSpinBox()
|
||||
sort_spin.setRange(-1000, 1000) # 设置合理的范围
|
||||
sort_spin.setValue(current_sort)
|
||||
|
||||
# 创建更新排序的函数
|
||||
def updateSort(value):
|
||||
try:
|
||||
# 设置标签
|
||||
gui_element.setTag("sort", str(value))
|
||||
# 应用sort到节点 - 使用fixed bin和指定的值
|
||||
gui_element.setBin("fixed", value)
|
||||
print(f"✓ 更新2D图像渲染顺序: {value}")
|
||||
except Exception as e:
|
||||
print(f"✗ 更新2D图像渲染顺序失败: {e}")
|
||||
|
||||
sort_spin.valueChanged.connect(updateSort)
|
||||
sort_layout.addWidget(sort_spin, 0, 1)
|
||||
|
||||
# 添加说明标签
|
||||
sort_help = QLabel("(数值越大越靠前)")
|
||||
sort_help.setStyleSheet("font-size: 10px; color: gray;")
|
||||
sort_layout.addWidget(sort_help, 1, 0, 1, 2)
|
||||
|
||||
sort_group.setLayout(sort_layout)
|
||||
layout.addWidget(sort_group)
|
||||
|
||||
scale = gui_element.getScale()
|
||||
width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,
|
||||
(tuple, list)) else scale
|
||||
height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,
|
||||
(tuple, list)) and len(
|
||||
scale) > 1 else scale
|
||||
|
||||
# 宽度控件
|
||||
transform_layout.addWidget(QLabel("宽度"), 4, 0)
|
||||
widthSpinBox = QDoubleSpinBox()
|
||||
widthSpinBox.setRange(0.1, 10)
|
||||
widthSpinBox.setSingleStep(0.1)
|
||||
widthSpinBox.setValue(width)
|
||||
widthSpinBox.valueChanged.connect(
|
||||
lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
transform_layout.addWidget(widthSpinBox, 4, 1)
|
||||
|
||||
# 高度控件
|
||||
transform_layout.addWidget(QLabel("高度"), 4, 2)
|
||||
heightSpinBox = QDoubleSpinBox()
|
||||
heightSpinBox.setRange(0.1, 10)
|
||||
heightSpinBox.setSingleStep(0.1)
|
||||
heightSpinBox.setValue(height)
|
||||
heightSpinBox.valueChanged.connect(
|
||||
lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
transform_layout.addWidget(heightSpinBox, 4, 3)
|
||||
|
||||
else:
|
||||
# 3D GUI组件使用世界坐标
|
||||
transform_layout.addWidget(QLabel("位置"), 0, 0)
|
||||
|
||||
# X, Y, Z 标签居中
|
||||
x_label = QLabel("X")
|
||||
y_label = QLabel("Y")
|
||||
z_label = QLabel("Z")
|
||||
x_label.setAlignment(Qt.AlignCenter)
|
||||
y_label.setAlignment(Qt.AlignCenter)
|
||||
z_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(x_label, 0, 1)
|
||||
transform_layout.addWidget(y_label, 0, 2)
|
||||
transform_layout.addWidget(z_label, 0, 3)
|
||||
|
||||
# 位置数值输入框
|
||||
xPos = QDoubleSpinBox()
|
||||
xPos.setRange(-1000, 1000)
|
||||
xPos.setRange(-100, 100)
|
||||
xPos.setValue(pos.getX())
|
||||
xPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [v, pos.getY(), pos.getZ()]))
|
||||
layout.addRow("位置 X:", xPos)
|
||||
|
||||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
|
||||
transform_layout.addWidget(xPos, 1, 1)
|
||||
|
||||
yPos = QDoubleSpinBox()
|
||||
yPos.setRange(-1000, 1000)
|
||||
yPos.setRange(-100, 100)
|
||||
yPos.setValue(pos.getY())
|
||||
yPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), v, pos.getZ()]))
|
||||
layout.addRow("位置 Y:", yPos)
|
||||
|
||||
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
|
||||
transform_layout.addWidget(yPos, 1, 2)
|
||||
|
||||
zPos = QDoubleSpinBox()
|
||||
zPos.setRange(-1000, 1000)
|
||||
zPos.setRange(-100, 100)
|
||||
zPos.setValue(pos.getZ())
|
||||
zPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), pos.getY(), v]))
|
||||
layout.addRow("位置 Z:", zPos)
|
||||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
|
||||
transform_layout.addWidget(zPos, 1, 3)
|
||||
|
||||
# 缩放属性
|
||||
scale = gui_element.getScale()
|
||||
transform_layout.addWidget(QLabel("缩放"), 2, 0)
|
||||
|
||||
# X, Y, Z 缩放标签居中
|
||||
sx_label = QLabel("X")
|
||||
sy_label = QLabel("Y")
|
||||
sz_label = QLabel("Z")
|
||||
sx_label.setAlignment(Qt.AlignCenter)
|
||||
sy_label.setAlignment(Qt.AlignCenter)
|
||||
sz_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(sx_label, 2, 1)
|
||||
transform_layout.addWidget(sy_label, 2, 2)
|
||||
transform_layout.addWidget(sz_label, 2, 3)
|
||||
|
||||
# 缩放数值输入框
|
||||
scale_x = QDoubleSpinBox()
|
||||
scale_x.setRange(0.01, 10)
|
||||
scale_x.setSingleStep(0.1)
|
||||
scale_x.setValue(
|
||||
scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale)
|
||||
scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
transform_layout.addWidget(scale_x, 3, 1)
|
||||
|
||||
scale_y = QDoubleSpinBox()
|
||||
scale_y.setRange(0.01, 10)
|
||||
scale_y.setSingleStep(0.1)
|
||||
scale_y.setValue(
|
||||
scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len(
|
||||
scale) > 1 else scale)
|
||||
scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v))
|
||||
transform_layout.addWidget(scale_y, 3, 2)
|
||||
|
||||
scale_z = QDoubleSpinBox()
|
||||
scale_z.setRange(0.01, 10)
|
||||
scale_z.setSingleStep(0.1)
|
||||
scale_z.setValue(
|
||||
scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len(
|
||||
scale) > 2 else scale)
|
||||
scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
transform_layout.addWidget(scale_z, 3, 3)
|
||||
transform_group.setLayout(transform_layout)
|
||||
self._propertyLayout.addWidget(transform_group)
|
||||
|
||||
# 缩放属性
|
||||
if hasattr(gui_element, 'getScale'):
|
||||
@ -1136,24 +1413,24 @@ class GUIManager:
|
||||
r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0
|
||||
self.editGUIElement(gui_element, "color", [r, g, b, 1.0])
|
||||
|
||||
def editGUI2DPosition(self, gui_element, axis, value):
|
||||
"""编辑2D GUI元素位置"""
|
||||
try:
|
||||
current_pos = gui_element.getPos()
|
||||
|
||||
if axis == "x":
|
||||
# 将逻辑坐标转换为屏幕坐标
|
||||
new_screen_x = value * 0.1
|
||||
gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
||||
elif axis == "z":
|
||||
# 将逻辑坐标转换为屏幕坐标
|
||||
new_screen_z = value * 0.1
|
||||
gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
||||
|
||||
print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"编辑2D GUI位置失败: {str(e)}")
|
||||
# def editGUI2DPosition(self, gui_element, axis, value):
|
||||
# """编辑2D GUI元素位置"""
|
||||
# try:
|
||||
# current_pos = gui_element.getPos()
|
||||
#
|
||||
# if axis == "x":
|
||||
# # 将逻辑坐标转换为屏幕坐标
|
||||
# new_screen_x = value * 0.1
|
||||
# gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
||||
# elif axis == "z":
|
||||
# # 将逻辑坐标转换为屏幕坐标
|
||||
# new_screen_z = value * 0.1
|
||||
# gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
||||
#
|
||||
# print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"编辑2D GUI位置失败: {str(e)}")
|
||||
|
||||
def update3DImageTexture(self, model_nodepath, image_path):
|
||||
from panda3d.core import Texture
|
||||
@ -1190,7 +1467,164 @@ class GUIManager:
|
||||
except Exception as e:
|
||||
print(f"❌ 更新纹理时出错: {e}")
|
||||
|
||||
# 替换现有的 createCesiumView 方法
|
||||
def update2DImageTexture(self, gui_element, image_path):
|
||||
"""更新2D图片纹理"""
|
||||
try:
|
||||
# 加载新纹理
|
||||
new_texture = self.world.loader.loadTexture(image_path)
|
||||
if new_texture:
|
||||
new_texture.setMagfilter(Texture.FT_linear)
|
||||
new_texture.setMinfilter(Texture.FT_linear_mipmap_linear)
|
||||
|
||||
new_texture.setFormat(Texture.F_rgba)
|
||||
new_texture.setWrapU(Texture.WM_clamp)
|
||||
new_texture.setWrapV(Texture.WM_clamp)
|
||||
|
||||
# 应用纹理到模型
|
||||
gui_element.setTexture(new_texture, 1)
|
||||
|
||||
# 更新标签
|
||||
gui_element.setTag("texture_path", image_path)
|
||||
gui_element.setTag("image_path", image_path)
|
||||
|
||||
print(f"✅ 2D图像纹理已更新为: {image_path}")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 无法加载2D图片纹理: {image_path}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 更新2D图片纹理时出错: {e}")
|
||||
return False
|
||||
|
||||
# 在gui_manager.py或其他相关文件中添加以下方法
|
||||
|
||||
def editGUI2DPosition(self, gui_element, axis, value):
|
||||
"""编辑2D GUI元素位置并应用边界约束"""
|
||||
try:
|
||||
gui_type = gui_element.getTag("gui_type")
|
||||
|
||||
if gui_type in ["button", "label", "entry", "2d_image"]:
|
||||
# 2D元素使用屏幕坐标,需要转换
|
||||
current_pos = gui_element.getPos()
|
||||
|
||||
if axis == "x":
|
||||
# 转换逻辑坐标到屏幕坐标
|
||||
screen_x = value * 0.1
|
||||
# 应用边界约束
|
||||
constrained_pos = self.constrain2DPosition(gui_element, new_x=screen_x)
|
||||
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
|
||||
elif axis == "z":
|
||||
screen_z = value * 0.1
|
||||
# 应用边界约束
|
||||
constrained_pos = self.constrain2DPosition(gui_element, new_z=screen_z)
|
||||
new_pos = (constrained_pos.getX(), constrained_pos.getY(), constrained_pos.getZ())
|
||||
else:
|
||||
return False
|
||||
|
||||
gui_element.setPos(*new_pos)
|
||||
print(f"✓ 更新2D GUI元素位置: {axis}={value} (约束后位置: {new_pos})")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 不支持的GUI类型进行2D位置编辑: {gui_type}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ 更新2D GUI元素位置失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def editGUI3DPosition(self, gui_element, axis, value):
|
||||
"""编辑3D GUI元素位置"""
|
||||
try:
|
||||
gui_type = gui_element.getTag("gui_type")
|
||||
|
||||
if gui_type in ["3d_text", "3d_image"]:
|
||||
current_pos = gui_element.getPos()
|
||||
|
||||
if axis == "x":
|
||||
new_pos = (value, current_pos.getY(), current_pos.getZ())
|
||||
elif axis == "y":
|
||||
new_pos = (current_pos.getX(), value, current_pos.getZ())
|
||||
elif axis == "z":
|
||||
new_pos = (current_pos.getX(), current_pos.getY(), value)
|
||||
else:
|
||||
return False
|
||||
|
||||
gui_element.setPos(*new_pos)
|
||||
print(f"✓ 更新3D GUI元素位置: {axis}={value}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 不支持的GUI类型进行3D位置编辑: {gui_type}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ 更新3D GUI元素位置失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def editGUIScale(self, gui_element, axis, value):
|
||||
"""编辑GUI元素缩放"""
|
||||
try:
|
||||
gui_type = gui_element.getTag("gui_type")
|
||||
current_scale = gui_element.getScale()
|
||||
|
||||
# 确保缩放值不为0
|
||||
if value == 0:
|
||||
value = 0.01
|
||||
|
||||
if gui_type in ["3d_text", "3d_image"]:
|
||||
# 3D元素处理
|
||||
if axis == "x":
|
||||
new_scale = (value, current_scale.getY(), current_scale.getZ())
|
||||
elif axis == "y":
|
||||
new_scale = (current_scale.getX(), value, current_scale.getZ())
|
||||
elif axis == "z":
|
||||
new_scale = (current_scale.getX(), current_scale.getY(), value)
|
||||
else:
|
||||
return False
|
||||
|
||||
gui_element.setScale(*new_scale)
|
||||
elif gui_type == "2d_image":
|
||||
# 2D图像特殊处理 - 分别控制宽度和高度
|
||||
if axis == "x":
|
||||
# X轴控制宽度
|
||||
gui_element.setScale(value, current_scale.getZ() if hasattr(current_scale, 'getZ')
|
||||
else current_scale[1] if isinstance(current_scale, (list, tuple))
|
||||
else current_scale)
|
||||
elif axis == "z":
|
||||
# Z轴控制高度
|
||||
gui_element.setScale(current_scale.getX() if hasattr(current_scale, 'getX')
|
||||
else current_scale[0] if isinstance(current_scale, (list, tuple))
|
||||
else current_scale, value)
|
||||
else:
|
||||
# 其他情况使用统一缩放
|
||||
gui_element.setScale(value)
|
||||
gui_element.setTransparency(TransparencyAttrib.MAlpha)
|
||||
else:
|
||||
# 其他2D元素处理
|
||||
if axis in ["x", "z"]: # 对于2D图像,x和z分别代表宽度和高度
|
||||
# 保持原有缩放比例,仅调整指定轴
|
||||
if axis == "x":
|
||||
gui_element.setScale(value,
|
||||
current_scale.getZ() if hasattr(current_scale, 'getZ')
|
||||
else current_scale[1] if isinstance(current_scale, (list, tuple))
|
||||
else current_scale)
|
||||
elif axis == "z":
|
||||
gui_element.setScale(
|
||||
current_scale.getX() if hasattr(current_scale, 'getX')
|
||||
else current_scale[0] if isinstance(current_scale, (list, tuple))
|
||||
else current_scale, value)
|
||||
else:
|
||||
# 对于其他2D元素,使用统一缩放
|
||||
gui_element.setScale(value)
|
||||
|
||||
print(f"✓ 更新GUI元素缩放: {axis}={value}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"✗ 更新GUI元素缩放失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def createCesiumView(self, main_window=None):
|
||||
"""创建 Cesium 视图窗口(离线版本)"""
|
||||
|
||||
76
main.py
76
main.py
@ -19,6 +19,7 @@ from core.vr_manager import VRManager
|
||||
from core.vr_input_handler import VRInputHandler
|
||||
from core.alvr_streamer import ALVRStreamer
|
||||
from gui.gui_manager import GUIManager
|
||||
from core.terrain_manager import TerrainManager
|
||||
from scene.scene_manager import SceneManager
|
||||
from project.project_manager import ProjectManager
|
||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
||||
@ -84,6 +85,11 @@ class MyWorld(CoreWorld):
|
||||
self.script_manager.start_system()
|
||||
|
||||
#self.material_editor = None
|
||||
self.terrain_manager = TerrainManager(self)
|
||||
|
||||
self.terrain_edit_radius = 3.0
|
||||
self.terrain_edit_strength=0.3
|
||||
self.terrain_edit_operation = "add"
|
||||
|
||||
print("✓ MyWorld 初始化完成")
|
||||
|
||||
@ -164,6 +170,17 @@ class MyWorld(CoreWorld):
|
||||
"""树形控件的兼容性设置器"""
|
||||
self.interface_manager.treeWidget = value
|
||||
|
||||
#保留terrains属性以兼容现有代码
|
||||
@property
|
||||
def terrains(self):
|
||||
"""地形列表的兼容性属性"""
|
||||
return self.terrain_manager.terrains
|
||||
|
||||
@terrains.setter
|
||||
def terrains(self,value):
|
||||
"""地形列表的兼容性设置器"""
|
||||
self.terrain_manager.terrains = value
|
||||
|
||||
# ==================== GUI管理功能代理 ====================
|
||||
|
||||
# GUI元素创建方法 - 代理到gui_manager
|
||||
@ -187,11 +204,15 @@ class MyWorld(CoreWorld):
|
||||
"""创建3D图片"""
|
||||
return self.gui_manager.createGUI3DImage(pos,text,size)
|
||||
|
||||
def createSpotLight(self,pos=(-20,0,5)):
|
||||
def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2):
|
||||
"""创建2D GUI图片"""
|
||||
return self.gui_manager.createGUI2DImage(pos, image_path, size)
|
||||
|
||||
def createSpotLight(self,pos=(0,0,5)):
|
||||
"""创建聚光灯"""
|
||||
return self.scene_manager.createSpotLight(pos)
|
||||
|
||||
def createPointLight(self,pos=(20,0,5)):
|
||||
def createPointLight(self,pos=(0,0,5)):
|
||||
"""创建点光源"""
|
||||
return self.scene_manager.createPointLight(pos)
|
||||
|
||||
@ -686,6 +707,46 @@ class MyWorld(CoreWorld):
|
||||
else:
|
||||
return self.scene_manager.load_cesium_tileset(url,position)
|
||||
|
||||
# ==================== 地形管理功能代理 ====================
|
||||
# 地形创建方法 - 代理到terrain_manager
|
||||
def createTerrainFromHeightMap(self, heightmap_path, scale=(1, 1, 1)):
|
||||
"""从高度图创建地形"""
|
||||
return self.terrain_manager.createTerrainFromHeightMap(heightmap_path, scale)
|
||||
|
||||
def createFlatTerrain(self, size=(0.3, 0.3), resolution=256):
|
||||
"""创建平面地形"""
|
||||
return self.terrain_manager.createFlatTerrain(size, resolution)
|
||||
|
||||
def updateTerrain(self):
|
||||
"""更新所有地形的LOD"""
|
||||
return self.terrain_manager.updateTerrain()
|
||||
|
||||
def getTerrainHeight(self, terrain_info, x, y):
|
||||
"""获取地形上指定点的高度"""
|
||||
return self.terrain_manager.getTerrainHeight(terrain_info, x, y)
|
||||
|
||||
def setTerrainEditParameters(self,radius=None,strength=None,operation=None):
|
||||
if radius is not None:
|
||||
self.terrain_edit_radius = float(radius)
|
||||
if strength is not None:
|
||||
self.terrain_edit_strenght = float(strength)
|
||||
if operation is not None:
|
||||
self.terrain_edit_operation = operation
|
||||
print(f"地形编辑参数已更新: 半径={self.terrain_edit_radius}, 强度={self.terrain_edit_strength}, 操作={self.terrain_edit_operation}")
|
||||
def getTerrainEditParameters(self):
|
||||
"""获取当前地形编辑参数"""
|
||||
return {
|
||||
'radius': self.terrain_edit_radius,
|
||||
'strength': self.terrain_edit_strength,
|
||||
'operation': self.terrain_edit_operation
|
||||
}
|
||||
|
||||
def modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
|
||||
"""修改地形高度的便捷方法"""
|
||||
if hasattr(self, 'terrain_manager'):
|
||||
return self.terrain_manager.modifyTerrainHeight(terrain_info, x, y, radius, strength, operation)
|
||||
return False
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
@ -716,17 +777,6 @@ def buildPackage(appw):
|
||||
return world.project_manager.buildPackage(appw)
|
||||
|
||||
|
||||
# def open_material_editor(self):
|
||||
# """打开材质编辑器作为子窗口"""
|
||||
# if not self.material_editor:
|
||||
# self.material_editor = MaterialEditor()
|
||||
# # 设置为子窗口
|
||||
# self.material_editor.setParent(self)
|
||||
# self.material_editor.setWindowFlags(Qt.Window)
|
||||
#
|
||||
# self.material_editor.show()
|
||||
#
|
||||
|
||||
if __name__ == "__main__":
|
||||
world = MyWorld()
|
||||
|
||||
|
||||
@ -667,20 +667,20 @@ class SceneManager:
|
||||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||||
# 设置碰撞掩码
|
||||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位作为模型的碰撞掩码
|
||||
|
||||
|
||||
# 获取模型的边界
|
||||
bounds = model.getBounds()
|
||||
center = bounds.getCenter()
|
||||
radius = bounds.getRadius()
|
||||
|
||||
|
||||
# 添加碰撞球体
|
||||
cSphere = CollisionSphere(center, radius)
|
||||
cNode.addSolid(cSphere)
|
||||
|
||||
|
||||
# 将碰撞节点附加到模型上
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
#cNodePath.hide()
|
||||
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
||||
#cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
||||
|
||||
# ==================== 场景树管理 ====================
|
||||
|
||||
@ -943,34 +943,27 @@ class SceneManager:
|
||||
|
||||
render_pipeline = get_render_pipeline()
|
||||
|
||||
# 创建一个挂载节点(你控制的)
|
||||
light_np = NodePath("SpotlightAttachNode")
|
||||
# 为每个灯光创建独立的节点
|
||||
light_np = NodePath(f"Spotlight_{len(self.Spotlight)}")
|
||||
light_np.reparentTo(self.world.render)
|
||||
#light_np.setPos(*pos)
|
||||
|
||||
self.half_energy = 5000
|
||||
self.lamp_fov = 70
|
||||
self.lamp_radius = 1000
|
||||
light_np.setPos(*pos)
|
||||
|
||||
# 创建独立的灯光对象
|
||||
light = SpotLight()
|
||||
light.direction = Vec3(0, 0, -1) # 光照方向
|
||||
light.fov = self.lamp_fov # 光源角度(类似手电筒)
|
||||
light.fov = self.lamp_fov if hasattr(self, 'lamp_fov') else 70 # 光源角度
|
||||
light.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||||
light.energy = self.half_energy # 光照强度
|
||||
light.radius = self.lamp_radius # 影响范围
|
||||
light.energy = self.half_energy if hasattr(self, 'half_energy') else 5000 # 光照强度
|
||||
light.radius = self.lamp_radius if hasattr(self, 'lamp_radius') else 1000 # 影响范围
|
||||
light.casts_shadows = True # 是否投射阴影
|
||||
light.shadow_map_resolution = 256 # 阴影分辨率
|
||||
light.setPos(*pos)
|
||||
#light_np.setPos(*pos)
|
||||
|
||||
#light_np = render_pipeline.add_light(light, parent=self.world.render)
|
||||
render_pipeline.add_light(light) # 添加到渲染管线
|
||||
|
||||
light_name = f"Spotlight_{len(self.Spotlight)}"
|
||||
|
||||
light_np.setName(light_name) # 设置唯一名称
|
||||
#light_np.reparentTo(self.world.render) # 挂载到场景根节点
|
||||
#light_name = f"Spotlight_{len(self.Spotlight)}"
|
||||
|
||||
#light_np.setName(light_name) # 设置唯一名称
|
||||
light_np.setTag("light_type", "spot_light")
|
||||
light_np.setTag("is_scene_element", "1")
|
||||
light_np.setTag("light_energy", str(light.energy))
|
||||
@ -982,7 +975,7 @@ class SceneManager:
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
#print("nikan"+light_np.getHpr())
|
||||
return light,light_np
|
||||
|
||||
def createPointLight(self, pos=(0, 0, 0)):
|
||||
from RenderPipelineFile.rpcore import PointLight, RenderPipeline
|
||||
@ -990,14 +983,14 @@ class SceneManager:
|
||||
|
||||
render_pipeline = get_render_pipeline()
|
||||
|
||||
# 创建一个挂载节点(你控制的)
|
||||
light_np = NodePath("PointlightAttachNode")
|
||||
# 为每个灯光创建独立的节点
|
||||
light_np = NodePath(f"Pointlight_{len(self.Pointlight)}")
|
||||
light_np.reparentTo(self.world.render)
|
||||
light_np.setPos(*pos)
|
||||
|
||||
|
||||
# 创建独立的灯光对象
|
||||
light = PointLight()
|
||||
light.setPos(*pos)
|
||||
light_np.setPos(*pos)
|
||||
light.energy = 5000
|
||||
light.radius = 1000
|
||||
light.inner_radius = 0.4
|
||||
@ -1005,30 +998,27 @@ class SceneManager:
|
||||
light.casts_shadows = True # 是否投射阴影
|
||||
light.shadow_map_resolution = 256 # 阴影分辨率
|
||||
|
||||
render_pipeline.add_light(light) # 添加到渲染管线
|
||||
|
||||
light_name = f"Pointlight{len(self.Pointlight)}"
|
||||
|
||||
light_np.setName(light_name) # 设置唯一名称
|
||||
|
||||
#light_np = NodePath(f"PointLight_{len(self.Pointlight)}")
|
||||
#light_np.reparentTo(self.world.render)
|
||||
#light_np.setPos(*pos)
|
||||
# 添加到渲染管线
|
||||
render_pipeline.add_light(light)
|
||||
|
||||
# 设置标签和Python标签
|
||||
light_np.setTag("light_type", "point_light")
|
||||
light_np.setTag("is_scene_element", "1")
|
||||
light_np.setTag("light_energy", str(light.energy))
|
||||
|
||||
# 保存光源对象引用(重要!用于属性面板)
|
||||
# 保存光源对象引用(重要!用于属性面板和删除操作)
|
||||
light_np.setPythonTag("rp_light_object", light)
|
||||
|
||||
# 添加到灯光列表
|
||||
self.Pointlight.append(light_np)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
return light,light_np
|
||||
|
||||
print(f"创建点光源: {light_np.getName()}")
|
||||
return light, light_np
|
||||
|
||||
# ==================== GLB 转换方法 ====================
|
||||
|
||||
def _shouldConvertToGLB(self, filepath):
|
||||
@ -1486,42 +1476,66 @@ except Exception as e:
|
||||
normal = GeomVertexWriter(vdata, 'normal')
|
||||
color = GeomVertexWriter(vdata, 'color')
|
||||
|
||||
# 定义立方体顶点(稍微大一些,便于识别)
|
||||
# 定义立方体顶点
|
||||
size = 1.0
|
||||
vertices = [
|
||||
(-size, -size, -size), (size, -size, -size), (size, size, -size), (-size, size, -size),
|
||||
(-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size)
|
||||
# 前面 (Z+)
|
||||
(-size, -size, size), (size, -size, size), (size, size, size), (-size, size, size),
|
||||
# 后面 (Z-)
|
||||
(-size, -size, -size), (-size, size, -size), (size, size, -size), (size, -size, -size),
|
||||
# 左面 (X-)
|
||||
(-size, -size, -size), (-size, -size, size), (-size, size, size), (-size, size, -size),
|
||||
# 右面 (X+)
|
||||
(size, -size, -size), (size, size, -size), (size, size, size), (size, -size, size),
|
||||
# 上面 (Y+)
|
||||
(-size, size, -size), (-size, size, size), (size, size, size), (size, size, -size),
|
||||
# 下面 (Y-)
|
||||
(-size, -size, -size), (size, -size, -size), (size, -size, size), (-size, -size, size)
|
||||
]
|
||||
|
||||
# 使用更鲜明的颜色
|
||||
for vert in vertices:
|
||||
normals = [
|
||||
# 前面法线
|
||||
(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1),
|
||||
# 后面法线
|
||||
(0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1),
|
||||
# 左面法线
|
||||
(-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0),
|
||||
# 右面法线
|
||||
(1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0),
|
||||
# 上面法线
|
||||
(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0),
|
||||
# 下面法线
|
||||
(0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0)
|
||||
]
|
||||
|
||||
# 青色
|
||||
face_colors = [
|
||||
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 前面 - 青色
|
||||
(0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), (0.0, 0.8, 0.8, 1.0), # 后面 - 稍暗青色
|
||||
(0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), (0.0, 0.9, 0.9, 1.0), # 左面 - 中等青色
|
||||
(0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), (0.0, 0.7, 0.7, 1.0), # 右面 - 稍暗青色
|
||||
(0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), (0.0, 1.0, 1.0, 1.0), # 上面 - 青色
|
||||
(0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0), (0.0, 0.6, 0.6, 1.0) # 下面 - 更暗青色
|
||||
]
|
||||
|
||||
for i, vert in enumerate(vertices):
|
||||
vertex.addData3f(*vert)
|
||||
normal.addData3f(0, 0, 1)
|
||||
color.addData4f(0.0, 1.0, 1.0, 1.0) # 青色
|
||||
normal.addData3f(*normals[i])
|
||||
color.addData4f(*face_colors[i])
|
||||
|
||||
# 创建几何体
|
||||
geom = Geom(vdata)
|
||||
|
||||
# 创建面
|
||||
# 创建面(每个面两个三角形)
|
||||
prim = GeomTriangles(Geom.UHStatic)
|
||||
# 底面
|
||||
prim.addVertices(0, 1, 2)
|
||||
prim.addVertices(0, 2, 3)
|
||||
# 顶面
|
||||
prim.addVertices(4, 7, 6)
|
||||
prim.addVertices(4, 6, 5)
|
||||
# 前面
|
||||
prim.addVertices(0, 4, 5)
|
||||
prim.addVertices(0, 5, 1)
|
||||
# 后面
|
||||
prim.addVertices(2, 6, 7)
|
||||
prim.addVertices(2, 7, 3)
|
||||
# 左面
|
||||
prim.addVertices(0, 3, 7)
|
||||
prim.addVertices(0, 7, 4)
|
||||
# 右面
|
||||
prim.addVertices(1, 5, 6)
|
||||
prim.addVertices(1, 6, 2)
|
||||
|
||||
# 每个面4个顶点,生成2个三角形
|
||||
for face in range(6): # 6个面
|
||||
base_index = face * 4
|
||||
# 第一个三角形
|
||||
prim.addVertices(base_index, base_index + 1, base_index + 2)
|
||||
# 第二个三角形
|
||||
prim.addVertices(base_index, base_index + 2, base_index + 3)
|
||||
|
||||
prim.closePrimitive()
|
||||
geom.addPrimitive(prim)
|
||||
@ -1532,7 +1546,10 @@ except Exception as e:
|
||||
|
||||
# 添加到场景
|
||||
cube_node = parent_node.attachNewNode(geom_node)
|
||||
cube_node.setScale(5) # 适当大小
|
||||
cube_node.setScale(5) # 设置合适的大小
|
||||
|
||||
# 设置双面渲染
|
||||
cube_node.setTwoSided(True)
|
||||
|
||||
# 添加材质
|
||||
material = Material()
|
||||
|
||||
239
test_axis_fix.py
239
test_axis_fix.py
@ -1,239 +0,0 @@
|
||||
#!/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
|
||||
from PyQt5.QtCore import Qt
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
from QPanda3D.Panda3DWorld import Panda3DWorld
|
||||
from panda3d.core import *
|
||||
|
||||
class AxisTestWorld(Panda3DWorld):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
print("=== 坐标轴修复验证测试 ===")
|
||||
self.setBackgroundColor(0.2, 0.2, 0.3)
|
||||
|
||||
# 调整相机位置
|
||||
self.cam.setPos(8, -15, 8)
|
||||
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.currentTool = "移动"
|
||||
|
||||
# 模拟选择系统和工具管理器
|
||||
from core.selection import SelectionSystem
|
||||
self.selection = SelectionSystem(self)
|
||||
|
||||
# 简单的工具管理器模拟
|
||||
class SimpleToolManager:
|
||||
def isScaleTool(self):
|
||||
return False
|
||||
def isRotateTool(self):
|
||||
return False
|
||||
|
||||
self.tool_manager = SimpleToolManager()
|
||||
|
||||
# 模拟属性面板
|
||||
class SimplePropertyPanel:
|
||||
def refreshModelValues(self, node):
|
||||
print(f"属性更新: {node.getName()} 位置={node.getPos()}")
|
||||
|
||||
self.property_panel = SimplePropertyPanel()
|
||||
|
||||
# 自动选中立方体
|
||||
self.selectedNode = self.testCube
|
||||
self.selection.updateSelection(self.testCube)
|
||||
|
||||
print("测试说明:")
|
||||
print("1. 鼠标悬停在坐标轴上应该变为黄色高亮")
|
||||
print("2. 点击并拖拽坐标轴,立方体应该沿对应轴移动")
|
||||
print("3. 拖动行为应该与鼠标移动方向一致")
|
||||
|
||||
def setQtWidget(self, widget):
|
||||
"""设置Qt部件引用"""
|
||||
self.qtWidget = widget
|
||||
|
||||
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)
|
||||
|
||||
self.testCube = self.render.attachNewNode("testCube")
|
||||
|
||||
# 6个面,不同颜色
|
||||
faces = [
|
||||
(Point3(0, 1, 0), Vec3(0, 0, 0), (1, 0.5, 0.5, 1)), # 前面
|
||||
(Point3(0, -1, 0), Vec3(0, 0, 180), (0.5, 1, 0.5, 1)), # 后面
|
||||
(Point3(-1, 0, 0), Vec3(0, 0, 90), (0.5, 0.5, 1, 1)), # 左面
|
||||
(Point3(1, 0, 0), Vec3(0, 0, -90), (1, 1, 0.5, 1)), # 右面
|
||||
(Point3(0, 0, 1), Vec3(-90, 0, 0), (1, 0.5, 1, 1)), # 顶面
|
||||
(Point3(0, 0, -1), Vec3(90, 0, 0), (0.5, 1, 1, 1)) # 底面
|
||||
]
|
||||
|
||||
for pos, hpr, color in faces:
|
||||
face = self.testCube.attachNewNode(cm.generate())
|
||||
face.setPos(pos)
|
||||
face.setHpr(hpr)
|
||||
face.setColor(*color)
|
||||
|
||||
print(f"✓ 创建测试立方体,位置: {self.testCube.getPos()}")
|
||||
return self.testCube
|
||||
|
||||
def mousePressEventLeft(self, evt):
|
||||
"""处理鼠标左键按下事件"""
|
||||
if not evt:
|
||||
return
|
||||
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
|
||||
# 检查坐标轴点击
|
||||
if self.selection.gizmo:
|
||||
gizmoAxis = self.selection.checkGizmoClick(x, y)
|
||||
if gizmoAxis:
|
||||
print(f"✓ 坐标轴点击检测成功: {gizmoAxis}")
|
||||
self.selection.startGizmoDrag(gizmoAxis, x, y)
|
||||
return
|
||||
else:
|
||||
print("× 坐标轴点击检测失败")
|
||||
|
||||
def mouseReleaseEventLeft(self, evt):
|
||||
"""处理鼠标左键释放事件"""
|
||||
if self.selection.isDraggingGizmo:
|
||||
self.selection.stopGizmoDrag()
|
||||
|
||||
def mouseMoveEvent(self, evt):
|
||||
"""处理鼠标移动事件"""
|
||||
if not evt:
|
||||
return
|
||||
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
|
||||
# 处理坐标轴拖拽
|
||||
if self.selection.isDraggingGizmo:
|
||||
self.selection.updateGizmoDrag(x, y)
|
||||
return
|
||||
|
||||
# 更新坐标轴高亮
|
||||
if self.selection.gizmo and not self.selection.isDraggingGizmo:
|
||||
self.selection.updateGizmoHighlight(x, y)
|
||||
|
||||
|
||||
class TestWidget(QPanda3DWidget):
|
||||
"""测试部件"""
|
||||
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):
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.world.mousePressEventLeft({
|
||||
'x': event.x(),
|
||||
'y': event.y()
|
||||
})
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.world.mouseReleaseEventLeft({
|
||||
'x': event.x(),
|
||||
'y': event.y()
|
||||
})
|
||||
event.accept()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
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)
|
||||
|
||||
# 添加说明
|
||||
label = QLabel("坐标轴修复验证测试 - 测试鼠标悬停高亮和拖动一致性")
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
label.setStyleSheet("background-color: lightgreen; padding: 10px; font-size: 12px;")
|
||||
layout.addWidget(label)
|
||||
|
||||
# 创建世界和部件
|
||||
world = AxisTestWorld()
|
||||
pandaWidget = TestWidget(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())
|
||||
@ -1,4 +1,4 @@
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu, QStyle
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.sip import delete
|
||||
from panda3d.core import GeomNode, ModelRoot
|
||||
@ -137,6 +137,22 @@ class InterfaceManager:
|
||||
def deleteNode(self, nodePath, item):
|
||||
"""删除节点"""
|
||||
try:
|
||||
item_data = item.data(0,Qt.UserRole+1)
|
||||
if item_data == "terrain":
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
terrain_to_remove = None
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
if terrain_info['node'] == nodePath:
|
||||
terrain_to_remove = terrain_info
|
||||
break
|
||||
if terrain_to_remove:
|
||||
self.world.terrain_manager.deleteTerrain(terrain_to_remove)
|
||||
print(f"成功删除地形节点:{nodePath.getName()}")
|
||||
self.updateSceneTree()
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
# 从场景中移除
|
||||
self.world.property_panel.removeActorForModel(nodePath)
|
||||
|
||||
@ -199,15 +215,6 @@ class InterfaceManager:
|
||||
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
||||
print("添加相机节点")
|
||||
|
||||
# # 添加模型节点组
|
||||
# modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
|
||||
# print(f"模型列表中的模型数量: {len(self.world.models)}")
|
||||
#
|
||||
# # 添加GUI元素节点组
|
||||
# guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
|
||||
#
|
||||
# lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
|
||||
|
||||
BLACK_LIST = {'','**','temp','collision'}
|
||||
|
||||
from panda3d.core import CollisionNode
|
||||
@ -216,6 +223,9 @@ class InterfaceManager:
|
||||
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(),CollisionNode) or isinstance(node.node(),ModelRoot) or name==""
|
||||
|
||||
def addNodeToTree(node,parentItem,force = False):
|
||||
if node.isEmpty():
|
||||
return
|
||||
|
||||
if not force and should_skip(node):
|
||||
return
|
||||
nodeItem = QTreeWidgetItem(parentItem,[node.getName()])
|
||||
@ -224,52 +234,53 @@ class InterfaceManager:
|
||||
for child in node.getChildren():
|
||||
addNodeToTree(child,nodeItem,force = False)
|
||||
|
||||
# 递归添加节点及其子节点
|
||||
# def addNodeToTree(node, parentItem):
|
||||
# print(f"\n处理节点: {node.getName()}")
|
||||
# # 创建节点项
|
||||
# nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
|
||||
# nodeItem.setData(0, Qt.UserRole, node)
|
||||
# print(f"添加节点: {node.getName()}")
|
||||
#
|
||||
# # 递归处理所有子节点
|
||||
# for child in node.getChildren():
|
||||
# # 检查是否是有效的模型节点
|
||||
# if (isinstance(child.node(), GeomNode) or
|
||||
# child.hasTag("file") or
|
||||
# child.getName() == "RootNode" or
|
||||
# isinstance(child.node(), ModelRoot)):
|
||||
# print(f"处理子节点: {child.getName()}")
|
||||
# addNodeToTree(child, nodeItem)
|
||||
# else:
|
||||
# print(f"跳过节点: {child.getName()}")
|
||||
|
||||
for model in self.world.models:
|
||||
addNodeToTree(model, sceneRoot,force=True)
|
||||
if not model.isEmpty():
|
||||
addNodeToTree(model, sceneRoot,force=True)
|
||||
|
||||
# 添加所有GUI元素
|
||||
for gui in self.world.gui_elements:
|
||||
gui_type = gui.getTag("gui_type") or "unknown"
|
||||
gui_text = gui.getTag("gui_text") or "GUI元素"
|
||||
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
||||
item.setData(0, Qt.UserRole, gui)
|
||||
# 检查是否是有效的GUI节点(具有getTag方法的NodePath)
|
||||
if hasattr(gui, 'getTag') and hasattr(gui, 'getName'):
|
||||
gui_type = gui.getTag("gui_type") or "unknown"
|
||||
gui_text = gui.getTag("text") or gui.getTag("gui_text") or "GUI元素"
|
||||
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
||||
item.setData(0, Qt.UserRole, gui)
|
||||
else:
|
||||
# 跳过非GUI节点(如QDockWidget等Qt对象)
|
||||
continue
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
|
||||
#添加灯光节点
|
||||
for light in self.world.Spotlight + self.world.Pointlight:
|
||||
addNodeToTree(light, sceneRoot, force=True)
|
||||
if not light.isEmpty:
|
||||
addNodeToTree(light, sceneRoot, force=True)
|
||||
|
||||
#添加 Cesium tilesets
|
||||
if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'tilesets'):
|
||||
for i , tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
tileset_node = tileset_info['node']
|
||||
tileset_url = tileset_info['url']
|
||||
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
||||
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
||||
addNodeToTree(tileset_node,tileset_item,force=True)
|
||||
if not tileset_node.isEmpty():
|
||||
tileset_url = tileset_info['url']
|
||||
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
||||
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
||||
addNodeToTree(tileset_node,tileset_item,force=True)
|
||||
#添加地形节点
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
terrain_name = terrain_info.get('name','未知地形')
|
||||
terrain_item = QTreeWidgetItem(sceneRoot,[f"地形:{terrain_name}"])
|
||||
terrain_item.setData(0,Qt.UserRole,terrain_node)
|
||||
terrain_item.setData(0,Qt.UserRole+1,"terrain") # 标记为地形节点
|
||||
addNodeToTree(terrain_node,terrain_item,force=True)
|
||||
|
||||
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
|
||||
# 展开所有节点
|
||||
#self.treeWidget.expandAll()
|
||||
|
||||
@ -12,7 +12,8 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
|
||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||||
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
|
||||
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget,QDialog)
|
||||
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
|
||||
QSpinBox)
|
||||
from PyQt5.QtCore import Qt, QDir, QTimer
|
||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
||||
|
||||
@ -115,6 +116,13 @@ class MainWindow(QMainWindow):
|
||||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||||
self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
|
||||
|
||||
#添加地形菜单
|
||||
self.createTerrainMenu = self.createMenu.addMenu('地形')
|
||||
self.createFlatTerrainAction = self.createTerrainMenu.addAction('创建平面地形')
|
||||
self.createHeightmapTerrainAction = self.createTerrainMenu.addAction('从高度图创建地形')
|
||||
self.createTerrainMenu.addSeparator()
|
||||
self.terrainEditModeAction = self.createTerrainMenu.addAction('地形编辑模式')
|
||||
|
||||
# GUI菜单
|
||||
self.guiMenu = menubar.addMenu('GUI')
|
||||
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
|
||||
@ -275,6 +283,10 @@ class MainWindow(QMainWindow):
|
||||
self.create3DImageTool.setText("3D图片")
|
||||
self.toolbar.addWidget(self.create3DImageTool)
|
||||
|
||||
self.create2DImageTool = QToolButton()
|
||||
self.create2DImageTool.setText("2D图片")
|
||||
self.toolbar.addWidget(self.create2DImageTool)
|
||||
|
||||
self.createSpotLight = QToolButton()
|
||||
self.createSpotLight.setText("聚光灯")
|
||||
self.toolbar.addWidget(self.createSpotLight)
|
||||
@ -299,6 +311,21 @@ class MainWindow(QMainWindow):
|
||||
self.addModelTool.clicked.connect(self.onAddModelClicked)
|
||||
self.toolbar.addWidget(self.addModelTool)
|
||||
|
||||
#地形
|
||||
# 地形工具
|
||||
self.createFlatTerrainTool = QToolButton()
|
||||
self.createFlatTerrainTool.setText("平面地形")
|
||||
self.toolbar.addWidget(self.createFlatTerrainTool)
|
||||
|
||||
self.createHeightmapTerrainTool = QToolButton()
|
||||
self.createHeightmapTerrainTool.setText("高度图地形")
|
||||
self.toolbar.addWidget(self.createHeightmapTerrainTool)
|
||||
|
||||
self.terrainEditTool = QToolButton()
|
||||
self.terrainEditTool.setText("地形编辑")
|
||||
self.terrainEditTool.setCheckable(True)
|
||||
self.toolbar.addWidget(self.terrainEditTool)
|
||||
|
||||
# 默认选择"选择"工具
|
||||
self.selectTool.setChecked(True)
|
||||
self.world.setCurrentTool("选择")
|
||||
@ -458,14 +485,25 @@ class MainWindow(QMainWindow):
|
||||
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
|
||||
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
|
||||
self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView)
|
||||
|
||||
#连接地形创建事件
|
||||
self.createFlatTerrainAction.triggered.connect(self.onCreateFlatTerrain)
|
||||
self.createHeightmapTerrainAction.triggered.connect(self.onCreateHeightmapTerrain)
|
||||
self.terrainEditModeAction.triggered.connect(self.onTerrainEditMode)
|
||||
|
||||
# 连接工具栏GUI创建按钮事件
|
||||
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
|
||||
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
|
||||
self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText())
|
||||
self.create3DImageTool.clicked.connect(lambda: self.world.createGUI3DImage())
|
||||
self.create2DImageTool.clicked.connect(lambda: self.world.createGUI2DImage())
|
||||
self.createSpotLight.clicked.connect(lambda :self.world.createSpotLight())
|
||||
self.createPointLight.clicked.connect(lambda :self.world.createPointLight())
|
||||
|
||||
# 连接工具栏地形创建按钮事件
|
||||
self.createFlatTerrainTool.clicked.connect(self.onCreateFlatTerrain)
|
||||
self.createHeightmapTerrainTool.clicked.connect(self.onCreateHeightmapTerrain)
|
||||
self.terrainEditTool.clicked.connect(self.onTerrainEditMode)
|
||||
|
||||
# 连接树节点点击信号
|
||||
# self.treeWidget.itemClicked.connect(self.world.onTreeItemClicked)
|
||||
@ -955,7 +993,158 @@ class MainWindow(QMainWindow):
|
||||
print(f"⚠️ 关闭应用程序时出错: {e}")
|
||||
event.accept() # 即使出错也要关闭
|
||||
|
||||
def onCreateFlatTerrain(self):
|
||||
"""创建平面地形"""
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("创建平面地形")
|
||||
dialog.setModal(True)
|
||||
dialog.resize(300,200)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
width_layout = QHBoxLayout()
|
||||
width_layout.addWidget(QLabel("宽度:"))
|
||||
width_spin = QDoubleSpinBox()
|
||||
width_spin.setRange(0,10000)
|
||||
width_spin.setValue(0.3)
|
||||
width_layout.addWidget(width_spin)
|
||||
layout.addLayout(width_layout)
|
||||
|
||||
# 高度
|
||||
height_layout = QHBoxLayout()
|
||||
height_layout.addWidget(QLabel("高度:"))
|
||||
height_spin = QDoubleSpinBox()
|
||||
height_spin.setRange(0, 10000)
|
||||
height_spin.setValue(0.3)
|
||||
height_layout.addWidget(height_spin)
|
||||
layout.addLayout(height_layout)
|
||||
|
||||
# 分辨率
|
||||
resolution_layout = QHBoxLayout()
|
||||
resolution_layout.addWidget(QLabel("分辨率:"))
|
||||
resolution_spin = QSpinBox()
|
||||
resolution_spin.setRange(16, 2048)
|
||||
resolution_spin.setValue(256)
|
||||
resolution_spin.setSingleStep(16)
|
||||
resolution_layout.addWidget(resolution_spin)
|
||||
layout.addLayout(resolution_layout)
|
||||
|
||||
# 按钮
|
||||
button_layout = QHBoxLayout()
|
||||
ok_button = QPushButton("创建")
|
||||
cancel_button = QPushButton("取消")
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接信号
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# 显示对话框
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
width = width_spin.value()
|
||||
height = height_spin.value()
|
||||
resolution = resolution_spin.value()
|
||||
|
||||
# 调用世界对象创建地形
|
||||
terrain_info = self.world.createFlatTerrain((width, height), resolution)
|
||||
if terrain_info:
|
||||
QMessageBox.information(self, "成功", "平面地形创建成功!")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "平面地形创建失败!")
|
||||
|
||||
def onCreateHeightmapTerrain(self):
|
||||
"""从高度图创建地形"""
|
||||
file_path,_=QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"选择高度图文件",
|
||||
"",
|
||||
"图像文件 (*.png *.jpg *.jpeg *.bmp *.tga);;所有文件 (*)"
|
||||
)
|
||||
|
||||
if file_path:
|
||||
#创建对话框获取地形参数
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("设置地形参数")
|
||||
dialog.setModal(True)
|
||||
dialog.resize(300,250)
|
||||
|
||||
layout = QVBoxLayout(dialog)
|
||||
|
||||
x_scale_layout = QHBoxLayout()
|
||||
x_scale_layout.addWidget(QLabel("X缩放:"))
|
||||
x_scale_spin = QDoubleSpinBox()
|
||||
x_scale_spin.setRange(0.1,1000)
|
||||
x_scale_spin.setValue(0.3)
|
||||
x_scale_spin.setSingleStep(10)
|
||||
x_scale_layout.addWidget(x_scale_spin)
|
||||
layout.addLayout(x_scale_layout)
|
||||
|
||||
# Y缩放
|
||||
y_scale_layout = QHBoxLayout()
|
||||
y_scale_layout.addWidget(QLabel("Y缩放:"))
|
||||
y_scale_spin = QDoubleSpinBox()
|
||||
y_scale_spin.setRange(0.1, 1000)
|
||||
y_scale_spin.setValue(0.3)
|
||||
y_scale_spin.setSingleStep(10)
|
||||
y_scale_layout.addWidget(y_scale_spin)
|
||||
layout.addLayout(y_scale_layout)
|
||||
|
||||
# Z缩放
|
||||
z_scale_layout = QHBoxLayout()
|
||||
z_scale_layout.addWidget(QLabel("Z缩放:"))
|
||||
z_scale_spin = QDoubleSpinBox()
|
||||
z_scale_spin.setRange(0.1, 1000)
|
||||
z_scale_spin.setValue(50)
|
||||
z_scale_spin.setSingleStep(5)
|
||||
z_scale_layout.addWidget(z_scale_spin)
|
||||
layout.addLayout(z_scale_layout)
|
||||
|
||||
# 按钮
|
||||
button_layout = QHBoxLayout()
|
||||
ok_button = QPushButton("创建")
|
||||
cancel_button = QPushButton("取消")
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# 连接信号
|
||||
ok_button.clicked.connect(dialog.accept)
|
||||
cancel_button.clicked.connect(dialog.reject)
|
||||
|
||||
# 显示对话框
|
||||
if dialog.exec_() == QDialog.Accepted:
|
||||
x_scale = x_scale_spin.value()
|
||||
y_scale = y_scale_spin.value()
|
||||
z_scale = z_scale_spin.value()
|
||||
|
||||
# 调用世界对象创建地形
|
||||
terrain_info = self.world.createTerrainFromHeightMap(
|
||||
file_path,
|
||||
(x_scale, y_scale, z_scale)
|
||||
)
|
||||
if terrain_info:
|
||||
QMessageBox.information(self, "成功", "高度图地形创建成功!")
|
||||
else:
|
||||
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
|
||||
|
||||
def onTerrainEditMode(self):
|
||||
"""地形编辑模式"""
|
||||
# 检查当前是否已经处于地形编辑模式
|
||||
if self.world.currentTool == "地形编辑":
|
||||
# 退出地形编辑模式
|
||||
self.world.setCurrentTool(None)
|
||||
self.terrainEditTool.setChecked(False)
|
||||
self.terrainEditTool.setText("地形编辑")
|
||||
QMessageBox.information(self, "地形编辑", "已退出地形编辑模式")
|
||||
else:
|
||||
# 进入地形编辑模式
|
||||
self.world.setCurrentTool("地形编辑")
|
||||
self.terrainEditTool.setChecked(True)
|
||||
self.terrainEditTool.setText("退出地形编辑")
|
||||
QMessageBox.information(self, "地形编辑",
|
||||
"已进入地形编辑模式\n\n使用鼠标左键抬高地形\n使用鼠标右键降低地形")
|
||||
def setup_main_window(world):
|
||||
"""设置主窗口的便利函数"""
|
||||
app = QApplication.instance()
|
||||
@ -965,4 +1154,4 @@ def setup_main_window(world):
|
||||
main_window = MainWindow(world)
|
||||
main_window.show()
|
||||
|
||||
return app, main_window
|
||||
return app, main_window
|
||||
1393
ui/property_panel.py
1393
ui/property_panel.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user