3d_image和2d_image
根据高度图创建地形
This commit is contained in:
parent
c66b9003f5
commit
afb4a4bb50
File diff suppressed because one or more lines are too long
@ -119,6 +119,9 @@ class EventHandler:
|
|||||||
if not evt:
|
if not evt:
|
||||||
print("事件为空")
|
print("事件为空")
|
||||||
return
|
return
|
||||||
|
if self.world.currentTool == "地形编辑":
|
||||||
|
self._handleTerrainEdit(evt,"add")
|
||||||
|
return
|
||||||
|
|
||||||
# 获取鼠标点击的位置
|
# 获取鼠标点击的位置
|
||||||
x = evt.get('x', 0)
|
x = evt.get('x', 0)
|
||||||
@ -260,7 +263,103 @@ class EventHandler:
|
|||||||
print(f"清理碰撞检测节点失败: {str(e)}")
|
print(f"清理碰撞检测节点失败: {str(e)}")
|
||||||
|
|
||||||
print("=== 鼠标左键事件处理结束 ===\n")
|
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.allOn()) # 检查所有碰撞
|
||||||
|
|
||||||
|
# 使用相机坐标系的点创建射线
|
||||||
|
direction = farPoint - nearPoint
|
||||||
|
direction.normalize()
|
||||||
|
pickerNode.addSolid(CollisionRay(nearPoint, direction))
|
||||||
|
|
||||||
|
picker.addCollider(pickerNP, queue)
|
||||||
|
picker.traverse(self.world.render)
|
||||||
|
print(f"地形碰撞检测结果数量: {queue.getNumEntries()}")
|
||||||
|
|
||||||
|
# 射线检测结果处理
|
||||||
|
hitPos = None
|
||||||
|
hitNode = None
|
||||||
|
|
||||||
|
if queue.getNumEntries() > 0:
|
||||||
|
# 遍历所有碰撞结果,找到地形节点
|
||||||
|
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
|
||||||
|
print(f"找到地形节点: {terrain_node.getName()}")
|
||||||
|
|
||||||
|
# 修改地形高度
|
||||||
|
x_pos, y_pos = hitPos.getX(), hitPos.getY()
|
||||||
|
success = self.world.modifyTerrainHeight(
|
||||||
|
terrain_info, x_pos, y_pos, radius=3.0, strength=0.3, operation=operation)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print(f"✓ 地形编辑成功: {operation} at ({x_pos:.2f}, {y_pos:.2f})")
|
||||||
|
# 显示射线
|
||||||
|
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||||
|
else:
|
||||||
|
print("✗ 地形编辑失败")
|
||||||
|
break
|
||||||
|
|
||||||
|
if hitPos:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not hitPos:
|
||||||
|
print("没有检测到地形碰撞")
|
||||||
|
# 显示射线(无碰撞)
|
||||||
|
self.showClickRay(worldNearPoint, worldFarPoint)
|
||||||
|
|
||||||
|
# 清理碰撞检测节点
|
||||||
|
pickerNP.removeNode()
|
||||||
|
print("地形编辑处理完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"地形编辑处理出错: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
def _handleSelectionClick(self, hitNode):
|
def _handleSelectionClick(self, hitNode):
|
||||||
"""处理选择工具的点击事件"""
|
"""处理选择工具的点击事件"""
|
||||||
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")
|
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")
|
||||||
|
|||||||
@ -53,14 +53,14 @@ class SelectionSystem:
|
|||||||
# 高亮相关
|
# 高亮相关
|
||||||
self.gizmoHighlightAxis = None
|
self.gizmoHighlightAxis = None
|
||||||
self.gizmo_colors = {
|
self.gizmo_colors = {
|
||||||
"x": (1, 0, 0, 0), # 红色
|
"x": (1, 0, 0, 1.0), # 红色
|
||||||
"y": (0, 1, 0, 0), # 绿色
|
"y": (0, 1, 0, 1.0), # 绿色
|
||||||
"z": (0, 0, 1, 0) # 蓝色
|
"z": (0, 0, 1, 1.00) # 蓝色
|
||||||
}
|
}
|
||||||
self.gizmo_highlight_colors = {
|
self.gizmo_highlight_colors = {
|
||||||
"x": (1, 1, 0, 0), # 黄色高亮
|
"x": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
|
||||||
"y": (1, 1, 0, 0), # 黄色高亮
|
"y": (1.0, 1.0, 0.0, 1.0), # 黄色高亮
|
||||||
"z": (1, 1, 0, 0) # 黄色高亮
|
"z": (1.0, 1.0, 0.0, 1.0) # 黄色高亮
|
||||||
}
|
}
|
||||||
|
|
||||||
print("✓ 选择和变换系统初始化完成")
|
print("✓ 选择和变换系统初始化完成")
|
||||||
@ -465,63 +465,74 @@ class SelectionSystem:
|
|||||||
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
|
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
|
||||||
axis_Rotnodes = [self.gizmoRotXAxis, self.gizmoRotYAxis, self.gizmoRotZAxis]
|
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:
|
for axis_node in axis_nodes:
|
||||||
if axis_node:
|
if axis_node:
|
||||||
#禁用光照和阴影
|
|
||||||
axis_node.setLightOff()
|
axis_node.setLightOff()
|
||||||
axis_node.setShaderOff()
|
axis_node.setShaderOff()
|
||||||
axis_node.setFogOff()
|
axis_node.setFogOff()
|
||||||
#设置渲染层级,确保大多数对象之前渲染
|
axis_node.setBin("fixed", 40) # 与主节点相同优先级
|
||||||
axis_node.setBin("fixed",30)
|
axis_node.setDepthWrite(False) # 禁用深度写入
|
||||||
#axis_node.setDepthWrite(False)
|
axis_node.setDepthTest(False) # 禁用深度测试
|
||||||
#axis_node.setDepthTest(True)
|
|
||||||
|
|
||||||
|
# 设置旋转轴节点的渲染属性
|
||||||
for axis_rotnode in axis_Rotnodes:
|
for axis_rotnode in axis_Rotnodes:
|
||||||
if axis_rotnode:
|
if axis_rotnode:
|
||||||
axis_rotnode.setLightOff()
|
axis_rotnode.setLightOff()
|
||||||
axis_rotnode.setShaderOff()
|
axis_rotnode.setShaderOff()
|
||||||
axis_rotnode.setFogOff()
|
axis_rotnode.setFogOff()
|
||||||
axis_rotnode.setBin("fixed",30)
|
axis_rotnode.setBin("fixed", 40) # 与主节点相同优先级
|
||||||
#axis_rotnode.setDepthWrite(False)
|
axis_rotnode.setDepthWrite(False) # 禁用深度写入
|
||||||
#axis_rotnode.setDepthTest(True)
|
axis_rotnode.setDepthTest(False) # 禁用深度测试
|
||||||
|
|
||||||
|
# 收集所有handle节点
|
||||||
arrow_nodes = []
|
arrow_nodes = []
|
||||||
if self.gizmoXAxis:
|
if self.gizmoXAxis:
|
||||||
x_handle = self.gizmoXAxis.find("x_handle")
|
x_handle = self.gizmoXAxis.find("x_handle")
|
||||||
if x_handle:
|
if x_handle and not x_handle.isEmpty():
|
||||||
arrow_nodes.append(x_handle)
|
arrow_nodes.append(x_handle)
|
||||||
if self.gizmoYAxis:
|
if self.gizmoYAxis:
|
||||||
y_handle = self.gizmoYAxis.find("y_handle")
|
y_handle = self.gizmoYAxis.find("y_handle")
|
||||||
if y_handle:
|
if y_handle and not y_handle.isEmpty():
|
||||||
arrow_nodes.append(y_handle)
|
arrow_nodes.append(y_handle)
|
||||||
if self.gizmoZAxis:
|
if self.gizmoZAxis:
|
||||||
z_handle = self.gizmoZAxis.find("z_handle")
|
z_handle = self.gizmoZAxis.find("z_handle")
|
||||||
if z_handle:
|
if z_handle and not z_handle.isEmpty():
|
||||||
arrow_nodes.append(z_handle)
|
arrow_nodes.append(z_handle)
|
||||||
|
|
||||||
rot_nodes = []
|
rot_nodes = []
|
||||||
if self.gizmoRotXAxis:
|
if self.gizmoRotXAxis:
|
||||||
x_rHandle = self.gizmoRotXAxis.find("x_handle")
|
x_rHandle = self.gizmoRotXAxis.find("x_handle")
|
||||||
if x_rHandle:
|
if x_rHandle and not x_rHandle.isEmpty():
|
||||||
rot_nodes.append(x_rHandle)
|
rot_nodes.append(x_rHandle)
|
||||||
if self.gizmoRotYAxis:
|
if self.gizmoRotYAxis:
|
||||||
y_rHandle = self.gizmoRotYAxis.find("y_handle")
|
y_rHandle = self.gizmoRotYAxis.find("y_handle")
|
||||||
if y_rHandle:
|
if y_rHandle and not y_rHandle.isEmpty():
|
||||||
rot_nodes.append(y_rHandle)
|
rot_nodes.append(y_rHandle)
|
||||||
if self.gizmoRotZAxis:
|
if self.gizmoRotZAxis:
|
||||||
z_rHandle = self.gizmoRotZAxis.find("z_handle")
|
z_rHandle = self.gizmoRotZAxis.find("z_handle")
|
||||||
if z_rHandle:
|
if z_rHandle and not z_rHandle.isEmpty():
|
||||||
rot_nodes.append(z_rHandle)
|
rot_nodes.append(z_rHandle)
|
||||||
|
|
||||||
|
# 设置handle节点的渲染属性
|
||||||
for arrow_node in arrow_nodes:
|
for arrow_node in arrow_nodes:
|
||||||
if arrow_node:
|
if arrow_node:
|
||||||
arrow_node.setLightOff()
|
arrow_node.setLightOff()
|
||||||
arrow_node.setShaderOff()
|
arrow_node.setShaderOff()
|
||||||
arrow_node.setFogOff()
|
arrow_node.setFogOff()
|
||||||
arrow_node.setBin("fixed",31)
|
arrow_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||||
#arrow_node.setDepthWrite(False)
|
arrow_node.setDepthWrite(False)
|
||||||
#arrow_node.setDepthTest(False)
|
arrow_node.setDepthTest(False)
|
||||||
#启用透明度S
|
# 启用透明度支持
|
||||||
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||||
|
|
||||||
for rot_node in rot_nodes:
|
for rot_node in rot_nodes:
|
||||||
@ -529,20 +540,12 @@ class SelectionSystem:
|
|||||||
rot_node.setLightOff()
|
rot_node.setLightOff()
|
||||||
rot_node.setShaderOff()
|
rot_node.setShaderOff()
|
||||||
rot_node.setFogOff()
|
rot_node.setFogOff()
|
||||||
rot_node.setBin("fixed",31)
|
rot_node.setBin("fixed", 41) # 略高于主节点,确保最优先显示
|
||||||
#rot_node.setDepthWrite(False)
|
rot_node.setDepthWrite(False)
|
||||||
#rot_node.setDepthTest(False)
|
rot_node.setDepthTest(False)
|
||||||
#启用透明度S
|
# 启用透明度支持
|
||||||
rot_node.setTransparency(TransparencyAttrib.MAlpha)
|
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:
|
except Exception as e:
|
||||||
print(f"设置坐标轴渲染属性失败: {str(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
|
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
||||||
|
|
||||||
#安区地更新朝向
|
# 安区地更新朝向
|
||||||
|
|
||||||
if is_scale_tool:
|
if is_scale_tool:
|
||||||
#self.gizmo.setHpr(self.gizmoTarget.getHpr())
|
#self.gizmo.setHpr(self.gizmoTarget.getHpr())
|
||||||
@ -799,12 +802,27 @@ class SelectionSystem:
|
|||||||
# 创建或获取材质
|
# 创建或获取材质
|
||||||
mat = Material()
|
mat = Material()
|
||||||
|
|
||||||
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
||||||
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
||||||
mat.setDiffuse(Vec4(0, 0, 0, 1))
|
# mat.setDiffuse(Vec4(0, 0, 0, 1))
|
||||||
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
||||||
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
||||||
mat.set_roughness(1)
|
# 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)
|
handle_node.setMaterial(mat, 1)
|
||||||
@ -820,9 +838,9 @@ class SelectionSystem:
|
|||||||
handle_node.setShaderOff() # 禁用着色器
|
handle_node.setShaderOff() # 禁用着色器
|
||||||
handle_node.setFogOff() # 禁用雾效果
|
handle_node.setFogOff() # 禁用雾效果
|
||||||
|
|
||||||
handle_node.setBin("fixed",31)
|
handle_node.setBin("fixed",41)
|
||||||
#arrow_node.setDepthWrite(False)
|
handle_node.setDepthWrite(False)
|
||||||
#arrow_node.setDepthTest(True)
|
handle_node.setDepthTest(False)
|
||||||
|
|
||||||
# 保存材质引用以便后续修改
|
# 保存材质引用以便后续修改
|
||||||
if axis == "x":
|
if axis == "x":
|
||||||
@ -835,9 +853,9 @@ class SelectionSystem:
|
|||||||
axis_node.setLightOff()
|
axis_node.setLightOff()
|
||||||
axis_node.setShaderOff()
|
axis_node.setShaderOff()
|
||||||
axis_node.setFogOff()
|
axis_node.setFogOff()
|
||||||
axis_node.setBin("fixed", 30)
|
axis_node.setBin("fixed", 40)
|
||||||
axis_node.setDepthWrite(False)
|
axis_node.setDepthWrite(False)
|
||||||
axis_node.setDepthTest(True)
|
axis_node.setDepthTest(False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||||
@ -889,12 +907,28 @@ class SelectionSystem:
|
|||||||
# 创建或获取材质
|
# 创建或获取材质
|
||||||
mat = Material()
|
mat = Material()
|
||||||
|
|
||||||
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
# # 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
||||||
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
# mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
||||||
mat.setDiffuse(Vec4(0, 0, 0, 1))
|
# mat.setDiffuse(Vec4(0, 0, 0, 1))
|
||||||
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
# #mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
||||||
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
# mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
||||||
mat.set_roughness(1)
|
# 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)
|
handle_node.setMaterial(mat, 1)
|
||||||
@ -910,9 +944,9 @@ class SelectionSystem:
|
|||||||
handle_node.setShaderOff() # 禁用着色器
|
handle_node.setShaderOff() # 禁用着色器
|
||||||
handle_node.setFogOff() # 禁用雾效果
|
handle_node.setFogOff() # 禁用雾效果
|
||||||
|
|
||||||
handle_node.setBin("fixed",31)
|
handle_node.setBin("fixed",41)
|
||||||
#arrow_node.setDepthWrite(False)
|
handle_node.setDepthWrite(False)
|
||||||
#arrow_node.setDepthTest(True)
|
handle_node.setDepthTest(False)
|
||||||
|
|
||||||
# 保存材质引用以便后续修改
|
# 保存材质引用以便后续修改
|
||||||
if axis == "x":
|
if axis == "x":
|
||||||
@ -925,9 +959,9 @@ class SelectionSystem:
|
|||||||
axis_node.setLightOff()
|
axis_node.setLightOff()
|
||||||
axis_node.setShaderOff()
|
axis_node.setShaderOff()
|
||||||
axis_node.setFogOff()
|
axis_node.setFogOff()
|
||||||
axis_node.setBin("fixed", 30)
|
axis_node.setBin("fixed", 40)
|
||||||
#axis_node.setDepthWrite(False)
|
axis_node.setDepthWrite(False)
|
||||||
#axis_node.setDepthTest(True)
|
axis_node.setDepthTest(False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||||
@ -964,6 +998,8 @@ class SelectionSystem:
|
|||||||
# 稍微放大以增强视觉效果
|
# 稍微放大以增强视觉效果
|
||||||
axis_nodes[axis].setScale(1.1)
|
axis_nodes[axis].setScale(1.1)
|
||||||
|
|
||||||
|
self.gizmoHighlightAxis = axis
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"鼠标进入坐标轴处理失败: {e}")
|
print(f"鼠标进入坐标轴处理失败: {e}")
|
||||||
|
|
||||||
@ -990,6 +1026,9 @@ class SelectionSystem:
|
|||||||
if axis in axis_nodes and axis_nodes[axis]:
|
if axis in axis_nodes and axis_nodes[axis]:
|
||||||
axis_nodes[axis].setScale(1.0)
|
axis_nodes[axis].setScale(1.0)
|
||||||
|
|
||||||
|
if self.gizmoHighlightAxis == axis:
|
||||||
|
self.gizmoHighlightAxis = None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"鼠标离开坐标轴处理失败: {e}")
|
print(f"鼠标离开坐标轴处理失败: {e}")
|
||||||
|
|
||||||
@ -1435,8 +1474,12 @@ class SelectionSystem:
|
|||||||
print("拖拽更新失败: 没有坐标轴起始位置")
|
print("拖拽更新失败: 没有坐标轴起始位置")
|
||||||
return
|
return
|
||||||
|
|
||||||
is_scale_tool = self.world.tool_manager.isScaleTool()
|
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
||||||
is_rotate_tool = self.world.tool_manager.isRotateTool()
|
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]
|
mouseDeltaX = mouseX - self.dragStartMousePos[0]
|
||||||
@ -1446,22 +1489,35 @@ class SelectionSystem:
|
|||||||
scale_factor = 1.0 + (mouseDeltaX + mouseDeltaY) * 0.01
|
scale_factor = 1.0 + (mouseDeltaX + mouseDeltaY) * 0.01
|
||||||
start_scale = getattr(self,'gizmoTargetStartScale',Vec3(1,1,1))
|
start_scale = getattr(self,'gizmoTargetStartScale',Vec3(1,1,1))
|
||||||
|
|
||||||
target_hpr = self.gizmoTarget.getHpr()
|
if is_gui_element:
|
||||||
|
if self.dragGizmoAxis == "x":
|
||||||
if self.dragGizmoAxis == "x":
|
new_scale = Vec3(start_scale.x * scale_factor,start_scale.y,start_scale.z)
|
||||||
new_scale = Vec3(start_scale.x * scale_factor,start_scale.y,start_scale.z)
|
elif self.dragGizmoAxis == "y":
|
||||||
elif self.dragGizmoAxis == "y":
|
new_scale = Vec3(start_scale.x, start_scale.y * scale_factor, start_scale.z)
|
||||||
new_scale = Vec3(start_scale.x,start_scale.y*scale_factor,start_scale.z)
|
elif self.dragGizmoAxis == "z":
|
||||||
elif self.dragGizmoAxis == "z":
|
new_scale = Vec3(start_scale.x, start_scale.y, start_scale.z * scale_factor)
|
||||||
z_scale_factor = 1.0 - (mouseDeltaX + mouseDeltaY)*0.01
|
else:
|
||||||
new_scale = Vec3(start_scale.x,start_scale.y,start_scale.z*z_scale_factor)
|
new_scale = Vec3(start_scale.x * scale_factor,
|
||||||
|
start_scale.y * scale_factor,
|
||||||
|
start_scale.z * scale_factor)
|
||||||
else:
|
else:
|
||||||
new_scale = Vec3(start_scale.x * scale_factor,
|
# 普通3D模型的缩放处理
|
||||||
start_scale * scale_factor,
|
if self.dragGizmoAxis == "x":
|
||||||
start_scale.z * scale_factor)
|
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.gizmoTarget.setScale(new_scale)
|
||||||
#实时更新属性面板
|
# 安全地更新属性面板
|
||||||
|
#self._safeUpdatePropertyPanel()
|
||||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||||
return
|
return
|
||||||
elif is_rotate_tool:
|
elif is_rotate_tool:
|
||||||
@ -1481,6 +1537,7 @@ class SelectionSystem:
|
|||||||
start_hpr.y + rotation_amount,
|
start_hpr.y + rotation_amount,
|
||||||
start_hpr.z + rotation_amount)
|
start_hpr.z + rotation_amount)
|
||||||
self.gizmoTarget.setHpr(new_hpr)
|
self.gizmoTarget.setHpr(new_hpr)
|
||||||
|
#self._safeUpdatePropertyPanel()
|
||||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1647,6 +1704,7 @@ class SelectionSystem:
|
|||||||
|
|
||||||
# 实时更新属性面板
|
# 实时更新属性面板
|
||||||
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
self.world.property_panel.refreshModelValues(self.gizmoTarget)
|
||||||
|
#self._safeUpdatePropertyPanel()
|
||||||
|
|
||||||
# 每次拖拽都输出调试信息(但限制频率)
|
# 每次拖拽都输出调试信息(但限制频率)
|
||||||
if not hasattr(self, '_last_drag_debug_time'):
|
if not hasattr(self, '_last_drag_debug_time'):
|
||||||
@ -1663,6 +1721,39 @@ class SelectionSystem:
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
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):
|
def stopGizmoDrag(self):
|
||||||
"""停止坐标轴拖拽"""
|
"""停止坐标轴拖拽"""
|
||||||
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
|
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
|
||||||
@ -1690,19 +1781,34 @@ class SelectionSystem:
|
|||||||
def updateSelection(self, nodePath):
|
def updateSelection(self, nodePath):
|
||||||
"""更新选择状态"""
|
"""更新选择状态"""
|
||||||
print(f"\n=== 更新选择状态 ===")
|
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.selectedNode = nodePath
|
||||||
# 添加兼容性属性
|
# 添加兼容性属性
|
||||||
self.selectedObject = 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("创建选择框...")
|
print("创建选择框...")
|
||||||
self.createSelectionBox(nodePath)
|
self.createSelectionBox(nodePath)
|
||||||
if self.selectionBox:
|
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:
|
else:
|
||||||
print("× 选择框创建失败")
|
print("× 选择框创建失败")
|
||||||
|
|
||||||
@ -1710,11 +1816,14 @@ class SelectionSystem:
|
|||||||
print("创建坐标轴...")
|
print("创建坐标轴...")
|
||||||
self.createGizmo(nodePath)
|
self.createGizmo(nodePath)
|
||||||
if self.gizmo:
|
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:
|
else:
|
||||||
print("× 坐标轴创建失败")
|
print("× 坐标轴创建失败")
|
||||||
|
|
||||||
print(f"✓ 选中了节点: {nodePath.getName()}")
|
print(f"✓ 选中了节点: {node_name}")
|
||||||
else:
|
else:
|
||||||
print("清除选择...")
|
print("清除选择...")
|
||||||
self.clearSelectionBox()
|
self.clearSelectionBox()
|
||||||
@ -1732,10 +1841,10 @@ class SelectionSystem:
|
|||||||
return self.selectedNode is not None
|
return self.selectedNode is not None
|
||||||
|
|
||||||
def checkAndClearIfTargetDeleted(self):
|
def checkAndClearIfTargetDeleted(self):
|
||||||
if self.gizmoTarget and self.gizmoTarget.isEmpty():
|
if (self.gizmoTarget and self.gizmoTarget.isEmpty()):
|
||||||
self.clearGizmo()
|
self.clearGizmo()
|
||||||
|
|
||||||
if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty():
|
if (self.selectionBoxTarget and self.selectionBoxTarget.isEmpty()):
|
||||||
self.clearSelectionBox()
|
self.clearSelectionBox()
|
||||||
|
|
||||||
def setupGizmoCollision(self):
|
def setupGizmoCollision(self):
|
||||||
|
|||||||
378
core/terrain_manager.py
Normal file
378
core/terrain_manager.py
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
# core/terrain_manager.py
|
||||||
|
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)}"
|
||||||
|
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]
|
||||||
|
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(False) # 使用LOD
|
||||||
|
|
||||||
|
# 生成地形
|
||||||
|
terrain.generate()
|
||||||
|
|
||||||
|
# 获取地形节点
|
||||||
|
terrain_node = terrain.getRoot()
|
||||||
|
|
||||||
|
# 设置缩放
|
||||||
|
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||||||
|
|
||||||
|
# 将地形添加到场景中
|
||||||
|
terrain_node.reparentTo(self.world.render)
|
||||||
|
|
||||||
|
# 为地形节点设置名称,便于在场景树中识别
|
||||||
|
terrain_node.setName(f"Terrain_{os.path.basename(heightmap_path)}")
|
||||||
|
|
||||||
|
from panda3d.core import BitMask32
|
||||||
|
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||||
|
|
||||||
|
# 添加材质
|
||||||
|
self._applyTerrainMaterial(terrain_node)
|
||||||
|
|
||||||
|
# 保存地形信息(包括高度图的副本)
|
||||||
|
terrain_info = {
|
||||||
|
'terrain': terrain,
|
||||||
|
'node': terrain_node,
|
||||||
|
'heightmap': heightmap_path,
|
||||||
|
'heightfield': height_image, # 保存高度图副本
|
||||||
|
'scale': scale,
|
||||||
|
'name': f"Terrain_{os.path.basename(heightmap_path)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.terrains.append(terrain_info)
|
||||||
|
|
||||||
|
# 更新场景树
|
||||||
|
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||||
|
self.world.scene_manager.updateSceneTree()
|
||||||
|
|
||||||
|
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=(100, 100), 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)}"
|
||||||
|
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(False) # 使用LOD
|
||||||
|
|
||||||
|
# 设置地形参数
|
||||||
|
terrain.setBlockSize(32) # 设置块大小
|
||||||
|
|
||||||
|
# 生成地形
|
||||||
|
terrain.generate()
|
||||||
|
|
||||||
|
# 获取地形节点
|
||||||
|
terrain_node = terrain.getRoot()
|
||||||
|
|
||||||
|
# 设置缩放以达到指定大小
|
||||||
|
terrain_node.setScale(size[0], size[1], 50) # Z轴缩放更大以显示地形起伏
|
||||||
|
|
||||||
|
# 将地形添加到场景中
|
||||||
|
terrain_node.reparentTo(self.world.render)
|
||||||
|
|
||||||
|
# 为地形节点设置名称,便于在场景树中识别
|
||||||
|
terrain_node.setName(f"FlatTerrain_{len(self.terrains)}")
|
||||||
|
# 为地形添加碰撞体
|
||||||
|
from panda3d.core import BitMask32
|
||||||
|
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||||
|
|
||||||
|
# 添加材质
|
||||||
|
self._applyTerrainMaterial(terrain_node)
|
||||||
|
|
||||||
|
# 保存地形信息(包括高度图)
|
||||||
|
terrain_info = {
|
||||||
|
'terrain': terrain,
|
||||||
|
'node': terrain_node,
|
||||||
|
'heightmap': None,
|
||||||
|
'heightfield': height_image, # 保存高度图
|
||||||
|
'scale': (size[0], size[1], 50),
|
||||||
|
'name': f"FlatTerrain_{len(self.terrains)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
self.terrains.append(terrain_info)
|
||||||
|
|
||||||
|
# 更新场景树
|
||||||
|
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||||
|
self.world.scene_manager.updateSceneTree()
|
||||||
|
|
||||||
|
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.makeDefaultColor())
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
# 直接使用我们保存的高度图数据
|
||||||
|
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()
|
||||||
|
|
||||||
|
# 转换坐标到高度图空间
|
||||||
|
center_x = int((x / terrain_info['scale'][0]) * width + width / 2)
|
||||||
|
center_y = int((y / terrain_info['scale'][1]) * height + height / 2)
|
||||||
|
|
||||||
|
# 修改半径范围内的点
|
||||||
|
modified = False
|
||||||
|
for dx in range(-int(radius), int(radius) + 1):
|
||||||
|
for dy in range(-int(radius), int(radius) + 1):
|
||||||
|
# 检查距离
|
||||||
|
distance = (dx * dx + dy * dy) ** 0.5
|
||||||
|
if distance <= radius:
|
||||||
|
# 计算影响权重
|
||||||
|
weight = 1.0 - (distance / radius)
|
||||||
|
|
||||||
|
# 计算目标点坐标
|
||||||
|
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 = current_height + strength * weight * 0.1
|
||||||
|
elif operation == "subtract":
|
||||||
|
new_height = current_height - strength * weight * 0.1
|
||||||
|
elif operation == "set":
|
||||||
|
new_height = strength
|
||||||
|
else:
|
||||||
|
new_height = current_height
|
||||||
|
|
||||||
|
# 限制高度范围
|
||||||
|
new_height = max(0.0, min(1.0, new_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()
|
||||||
|
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}")
|
||||||
|
|
||||||
|
# 更新场景树
|
||||||
|
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:
|
||||||
|
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')}")
|
||||||
|
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 direct.gui.DirectGui import *
|
||||||
from panda3d.core import *
|
from panda3d.core import *
|
||||||
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QFormLayout, QLineEdit,
|
||||||
QDoubleSpinBox, QPushButton, QDialogButtonBox,
|
QDoubleSpinBox, QPushButton, QDialogButtonBox,
|
||||||
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout)
|
QColorDialog, QLabel, QWidget, QGroupBox, QHBoxLayout, QGridLayout, QSpinBox)
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt5.QtGui import QColor
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
# 尝试导入 QtWebEngineWidgets,如果失败则设置为 None
|
# 尝试导入 QtWebEngineWidgets,如果失败则设置为 None
|
||||||
@ -147,6 +147,112 @@ class GUIManager:
|
|||||||
|
|
||||||
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
print(f"✓ 创建GUI输入框: {placeholder} (逻辑位置: {pos}, 屏幕位置: {gui_pos})")
|
||||||
return entry
|
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):
|
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1):
|
||||||
"""创建3D空间文本"""
|
"""创建3D空间文本"""
|
||||||
@ -436,25 +542,34 @@ class GUIManager:
|
|||||||
|
|
||||||
elif property_name == "color": # 添加颜色处理
|
elif property_name == "color": # 添加颜色处理
|
||||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||||
# 更新材质颜色
|
# 对于2D GUI元素(button和label),使用frameColor属性
|
||||||
if not gui_element.hasMaterial():
|
if gui_type in ["button", "label"]:
|
||||||
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))
|
gui_element['frameColor'] = (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))
|
print(f"成功更新2D GUI背景颜色: {gui_type} -> {value}")
|
||||||
gui_element.setMaterial(material, 1)
|
|
||||||
else:
|
# 对于3D元素,使用材质颜色
|
||||||
material = gui_element.getMaterial()
|
elif gui_type in ["3d_text", "3d_image", "virtual_screen"]:
|
||||||
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))
|
if not gui_element.hasMaterial():
|
||||||
gui_element.setMaterial(material, 1)
|
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 的文本颜色
|
# 更新 TextNode 的文本颜色
|
||||||
if isinstance(gui_element.node(), TextNode):
|
if isinstance(gui_element.node(), TextNode):
|
||||||
gui_element.node().setTextColor(
|
gui_element.node().setTextColor(
|
||||||
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
Vec4(value[0], value[1], value[2], value[3] if len(value) > 3 else 1.0))
|
||||||
# if gui_type in ["3d_text", "virtual_screen"]:
|
print(f"成功更新3D GUI颜色: {gui_type} -> {value}")
|
||||||
# gui_element.setColor(*value)
|
|
||||||
# elif gui_type in ["button", "label"]:
|
else:
|
||||||
# gui_element['text_fg'] = value
|
print(f"警告: 未知的GUI类型 {gui_type},无法设置颜色")
|
||||||
|
|
||||||
elif property_name == "position":
|
elif property_name == "position":
|
||||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||||
@ -497,6 +612,9 @@ class GUIManager:
|
|||||||
elif gui_type == "3d_image":
|
elif gui_type == "3d_image":
|
||||||
image_path = gui_element.getTag("image_path")
|
image_path = gui_element.getTag("image_path")
|
||||||
self.createGUI3DImage(new_pos,image_path,size=(2,2))
|
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":
|
elif gui_type == "virtual_screen":
|
||||||
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
self.createGUIVirtualScreen(new_pos, text=gui_text + "_副本")
|
||||||
|
|
||||||
@ -745,7 +863,20 @@ class GUIManager:
|
|||||||
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
text_font=self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||||
)
|
)
|
||||||
y_pos -= spacing
|
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(
|
btn_screen = DirectButton(
|
||||||
parent=self.guiEditPanel,
|
parent=self.guiEditPanel,
|
||||||
@ -944,6 +1075,8 @@ class GUIManager:
|
|||||||
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
element = self.createGUI3DText(pos, f"3D文本_{len(self.gui_elements)}")
|
||||||
elif gui_type == "3d_image":
|
elif gui_type == "3d_image":
|
||||||
element = self.createGUI3DImage(pos)
|
element = self.createGUI3DImage(pos)
|
||||||
|
elif gui_type == "2d_image":
|
||||||
|
element = self.createGUI2DImage(pos)
|
||||||
elif gui_type == "virtual_screen":
|
elif gui_type == "virtual_screen":
|
||||||
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
element = self.createGUIVirtualScreen(pos, text=f"屏幕_{len(self.gui_elements)}")
|
||||||
else:
|
else:
|
||||||
@ -1039,54 +1172,198 @@ class GUIManager:
|
|||||||
|
|
||||||
# 位置属性
|
# 位置属性
|
||||||
if hasattr(gui_element, 'getPos'):
|
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()
|
pos = gui_element.getPos()
|
||||||
|
|
||||||
# 根据GUI类型决定位置编辑方式
|
# 根据GUI类型决定位置编辑方式
|
||||||
if gui_type in ["button", "label", "entry"]:
|
if gui_type in ["button", "label", "entry","2d_image"]:
|
||||||
# 2D GUI组件使用屏幕坐标
|
# 2D GUI组件使用屏幕坐标
|
||||||
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||||||
logical_z = pos.getZ() / 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 = QDoubleSpinBox()
|
||||||
xPos.setRange(-50, 50)
|
xPos.setRange(-50, 50)
|
||||||
xPos.setValue(logical_x)
|
xPos.setValue(logical_x)
|
||||||
xPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "x", v))
|
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||||||
layout.addRow("屏幕位置 X:", xPos)
|
transform_layout.addWidget(xPos, 1, 1)
|
||||||
|
|
||||||
zPos = QDoubleSpinBox()
|
zPos = QDoubleSpinBox()
|
||||||
zPos.setRange(-50, 50)
|
zPos.setRange(-50, 50)
|
||||||
zPos.setValue(logical_z)
|
zPos.setValue(logical_z)
|
||||||
zPos.valueChanged.connect(lambda v: self.editGUI2DPosition(gui_element, "z", v))
|
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||||||
layout.addRow("屏幕位置 Z:", zPos)
|
transform_layout.addWidget(zPos, 1, 2)
|
||||||
|
|
||||||
# 显示实际屏幕坐标(只读)
|
# 显示实际屏幕坐标(只读)
|
||||||
|
transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
|
||||||
|
|
||||||
actualXLabel = QLabel(f"{pos.getX():.3f}")
|
actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||||||
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||||
layout.addRow("实际屏幕 X:", actualXLabel)
|
|
||||||
|
|
||||||
actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||||||
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
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:
|
else:
|
||||||
# 3D GUI组件使用世界坐标
|
# 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 = QDoubleSpinBox()
|
||||||
xPos.setRange(-1000, 1000)
|
xPos.setRange(-100, 100)
|
||||||
xPos.setValue(pos.getX())
|
xPos.setValue(pos.getX())
|
||||||
xPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [v, pos.getY(), pos.getZ()]))
|
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
|
||||||
layout.addRow("位置 X:", xPos)
|
transform_layout.addWidget(xPos, 1, 1)
|
||||||
|
|
||||||
yPos = QDoubleSpinBox()
|
yPos = QDoubleSpinBox()
|
||||||
yPos.setRange(-1000, 1000)
|
yPos.setRange(-100, 100)
|
||||||
yPos.setValue(pos.getY())
|
yPos.setValue(pos.getY())
|
||||||
yPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), v, pos.getZ()]))
|
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
|
||||||
layout.addRow("位置 Y:", yPos)
|
transform_layout.addWidget(yPos, 1, 2)
|
||||||
|
|
||||||
zPos = QDoubleSpinBox()
|
zPos = QDoubleSpinBox()
|
||||||
zPos.setRange(-1000, 1000)
|
zPos.setRange(-100, 100)
|
||||||
zPos.setValue(pos.getZ())
|
zPos.setValue(pos.getZ())
|
||||||
zPos.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "position", [pos.getX(), pos.getY(), v]))
|
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
|
||||||
layout.addRow("位置 Z:", zPos)
|
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'):
|
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
|
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])
|
self.editGUIElement(gui_element, "color", [r, g, b, 1.0])
|
||||||
|
|
||||||
def editGUI2DPosition(self, gui_element, axis, value):
|
# def editGUI2DPosition(self, gui_element, axis, value):
|
||||||
"""编辑2D GUI元素位置"""
|
# """编辑2D GUI元素位置"""
|
||||||
try:
|
# try:
|
||||||
current_pos = gui_element.getPos()
|
# current_pos = gui_element.getPos()
|
||||||
|
#
|
||||||
if axis == "x":
|
# if axis == "x":
|
||||||
# 将逻辑坐标转换为屏幕坐标
|
# # 将逻辑坐标转换为屏幕坐标
|
||||||
new_screen_x = value * 0.1
|
# new_screen_x = value * 0.1
|
||||||
gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
# gui_element.setPos(new_screen_x, current_pos.getY(), current_pos.getZ())
|
||||||
elif axis == "z":
|
# elif axis == "z":
|
||||||
# 将逻辑坐标转换为屏幕坐标
|
# # 将逻辑坐标转换为屏幕坐标
|
||||||
new_screen_z = value * 0.1
|
# new_screen_z = value * 0.1
|
||||||
gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
# gui_element.setPos(current_pos.getX(), current_pos.getY(), new_screen_z)
|
||||||
|
#
|
||||||
print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
# print(f"更新2D GUI位置: {axis}轴 = {value} (屏幕坐标: {gui_element.getPos()})")
|
||||||
|
#
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
print(f"编辑2D GUI位置失败: {str(e)}")
|
# print(f"编辑2D GUI位置失败: {str(e)}")
|
||||||
|
|
||||||
def update3DImageTexture(self, model_nodepath, image_path):
|
def update3DImageTexture(self, model_nodepath, image_path):
|
||||||
from panda3d.core import Texture
|
from panda3d.core import Texture
|
||||||
@ -1190,7 +1467,164 @@ class GUIManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ 更新纹理时出错: {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):
|
def createCesiumView(self, main_window=None):
|
||||||
"""创建 Cesium 视图窗口(离线版本)"""
|
"""创建 Cesium 视图窗口(离线版本)"""
|
||||||
|
|||||||
54
main.py
54
main.py
@ -19,6 +19,7 @@ from core.vr_manager import VRManager
|
|||||||
from core.vr_input_handler import VRInputHandler
|
from core.vr_input_handler import VRInputHandler
|
||||||
from core.alvr_streamer import ALVRStreamer
|
from core.alvr_streamer import ALVRStreamer
|
||||||
from gui.gui_manager import GUIManager
|
from gui.gui_manager import GUIManager
|
||||||
|
from core.terrain_manager import TerrainManager
|
||||||
from scene.scene_manager import SceneManager
|
from scene.scene_manager import SceneManager
|
||||||
from project.project_manager import ProjectManager
|
from project.project_manager import ProjectManager
|
||||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
||||||
@ -84,6 +85,7 @@ class MyWorld(CoreWorld):
|
|||||||
self.script_manager.start_system()
|
self.script_manager.start_system()
|
||||||
|
|
||||||
#self.material_editor = None
|
#self.material_editor = None
|
||||||
|
self.terrain_manager = TerrainManager(self)
|
||||||
|
|
||||||
print("✓ MyWorld 初始化完成")
|
print("✓ MyWorld 初始化完成")
|
||||||
|
|
||||||
@ -164,6 +166,17 @@ class MyWorld(CoreWorld):
|
|||||||
"""树形控件的兼容性设置器"""
|
"""树形控件的兼容性设置器"""
|
||||||
self.interface_manager.treeWidget = value
|
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元素创建方法 - 代理到gui_manager
|
# GUI元素创建方法 - 代理到gui_manager
|
||||||
@ -187,11 +200,15 @@ class MyWorld(CoreWorld):
|
|||||||
"""创建3D图片"""
|
"""创建3D图片"""
|
||||||
return self.gui_manager.createGUI3DImage(pos,text,size)
|
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)
|
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)
|
return self.scene_manager.createPointLight(pos)
|
||||||
|
|
||||||
@ -686,6 +703,28 @@ class MyWorld(CoreWorld):
|
|||||||
else:
|
else:
|
||||||
return self.scene_manager.load_cesium_tileset(url,position)
|
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 modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
|
||||||
|
"""修改地形高度"""
|
||||||
|
return self.terrain_manager.modifyTerrainHeight(terrain_info, x, y, radius, strength, operation)
|
||||||
|
|
||||||
|
def getTerrainHeight(self, terrain_info, x, y):
|
||||||
|
"""获取地形上指定点的高度"""
|
||||||
|
return self.terrain_manager.getTerrainHeight(terrain_info, x, y)
|
||||||
|
|
||||||
|
|
||||||
# ==================== 项目管理功能代理 ====================
|
# ==================== 项目管理功能代理 ====================
|
||||||
# 以下函数代理到project_manager模块的对应功能
|
# 以下函数代理到project_manager模块的对应功能
|
||||||
@ -716,17 +755,6 @@ def buildPackage(appw):
|
|||||||
return world.project_manager.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__":
|
if __name__ == "__main__":
|
||||||
world = MyWorld()
|
world = MyWorld()
|
||||||
|
|
||||||
|
|||||||
@ -667,20 +667,20 @@ class SceneManager:
|
|||||||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||||||
# 设置碰撞掩码
|
# 设置碰撞掩码
|
||||||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位作为模型的碰撞掩码
|
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位作为模型的碰撞掩码
|
||||||
|
|
||||||
# 获取模型的边界
|
# 获取模型的边界
|
||||||
bounds = model.getBounds()
|
bounds = model.getBounds()
|
||||||
center = bounds.getCenter()
|
center = bounds.getCenter()
|
||||||
radius = bounds.getRadius()
|
radius = bounds.getRadius()
|
||||||
|
|
||||||
# 添加碰撞球体
|
# 添加碰撞球体
|
||||||
cSphere = CollisionSphere(center, radius)
|
cSphere = CollisionSphere(center, radius)
|
||||||
cNode.addSolid(cSphere)
|
cNode.addSolid(cSphere)
|
||||||
|
|
||||||
# 将碰撞节点附加到模型上
|
# 将碰撞节点附加到模型上
|
||||||
cNodePath = model.attachNewNode(cNode)
|
cNodePath = model.attachNewNode(cNode)
|
||||||
#cNodePath.hide()
|
#cNodePath.hide()
|
||||||
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
#cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
||||||
|
|
||||||
# ==================== 场景树管理 ====================
|
# ==================== 场景树管理 ====================
|
||||||
|
|
||||||
@ -943,34 +943,27 @@ class SceneManager:
|
|||||||
|
|
||||||
render_pipeline = get_render_pipeline()
|
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.reparentTo(self.world.render)
|
||||||
#light_np.setPos(*pos)
|
light_np.setPos(*pos)
|
||||||
|
|
||||||
self.half_energy = 5000
|
|
||||||
self.lamp_fov = 70
|
|
||||||
self.lamp_radius = 1000
|
|
||||||
|
|
||||||
|
# 创建独立的灯光对象
|
||||||
light = SpotLight()
|
light = SpotLight()
|
||||||
light.direction = Vec3(0, 0, -1) # 光照方向
|
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.set_color_from_temperature(5 * 1000.0) # 色温(K)
|
||||||
light.energy = self.half_energy # 光照强度
|
light.energy = self.half_energy if hasattr(self, 'half_energy') else 5000 # 光照强度
|
||||||
light.radius = self.lamp_radius # 影响范围
|
light.radius = self.lamp_radius if hasattr(self, 'lamp_radius') else 1000 # 影响范围
|
||||||
light.casts_shadows = True # 是否投射阴影
|
light.casts_shadows = True # 是否投射阴影
|
||||||
light.shadow_map_resolution = 256 # 阴影分辨率
|
light.shadow_map_resolution = 256 # 阴影分辨率
|
||||||
light.setPos(*pos)
|
light.setPos(*pos)
|
||||||
#light_np.setPos(*pos)
|
|
||||||
|
|
||||||
#light_np = render_pipeline.add_light(light, parent=self.world.render)
|
|
||||||
render_pipeline.add_light(light) # 添加到渲染管线
|
render_pipeline.add_light(light) # 添加到渲染管线
|
||||||
|
|
||||||
light_name = f"Spotlight_{len(self.Spotlight)}"
|
#light_name = f"Spotlight_{len(self.Spotlight)}"
|
||||||
|
|
||||||
light_np.setName(light_name) # 设置唯一名称
|
|
||||||
#light_np.reparentTo(self.world.render) # 挂载到场景根节点
|
|
||||||
|
|
||||||
|
#light_np.setName(light_name) # 设置唯一名称
|
||||||
light_np.setTag("light_type", "spot_light")
|
light_np.setTag("light_type", "spot_light")
|
||||||
light_np.setTag("is_scene_element", "1")
|
light_np.setTag("is_scene_element", "1")
|
||||||
light_np.setTag("light_energy", str(light.energy))
|
light_np.setTag("light_energy", str(light.energy))
|
||||||
@ -982,7 +975,7 @@ class SceneManager:
|
|||||||
if hasattr(self.world, 'updateSceneTree'):
|
if hasattr(self.world, 'updateSceneTree'):
|
||||||
self.world.updateSceneTree()
|
self.world.updateSceneTree()
|
||||||
|
|
||||||
#print("nikan"+light_np.getHpr())
|
return light,light_np
|
||||||
|
|
||||||
def createPointLight(self, pos=(0, 0, 0)):
|
def createPointLight(self, pos=(0, 0, 0)):
|
||||||
from RenderPipelineFile.rpcore import PointLight, RenderPipeline
|
from RenderPipelineFile.rpcore import PointLight, RenderPipeline
|
||||||
@ -990,14 +983,14 @@ class SceneManager:
|
|||||||
|
|
||||||
render_pipeline = get_render_pipeline()
|
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.reparentTo(self.world.render)
|
||||||
|
light_np.setPos(*pos)
|
||||||
|
|
||||||
|
# 创建独立的灯光对象
|
||||||
light = PointLight()
|
light = PointLight()
|
||||||
light.setPos(*pos)
|
light.setPos(*pos)
|
||||||
light_np.setPos(*pos)
|
|
||||||
light.energy = 5000
|
light.energy = 5000
|
||||||
light.radius = 1000
|
light.radius = 1000
|
||||||
light.inner_radius = 0.4
|
light.inner_radius = 0.4
|
||||||
@ -1005,30 +998,27 @@ class SceneManager:
|
|||||||
light.casts_shadows = True # 是否投射阴影
|
light.casts_shadows = True # 是否投射阴影
|
||||||
light.shadow_map_resolution = 256 # 阴影分辨率
|
light.shadow_map_resolution = 256 # 阴影分辨率
|
||||||
|
|
||||||
render_pipeline.add_light(light) # 添加到渲染管线
|
# 添加到渲染管线
|
||||||
|
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)
|
|
||||||
|
|
||||||
|
# 设置标签和Python标签
|
||||||
light_np.setTag("light_type", "point_light")
|
light_np.setTag("light_type", "point_light")
|
||||||
light_np.setTag("is_scene_element", "1")
|
light_np.setTag("is_scene_element", "1")
|
||||||
light_np.setTag("light_energy", str(light.energy))
|
light_np.setTag("light_energy", str(light.energy))
|
||||||
|
|
||||||
# 保存光源对象引用(重要!用于属性面板)
|
# 保存光源对象引用(重要!用于属性面板和删除操作)
|
||||||
light_np.setPythonTag("rp_light_object", light)
|
light_np.setPythonTag("rp_light_object", light)
|
||||||
|
|
||||||
|
# 添加到灯光列表
|
||||||
self.Pointlight.append(light_np)
|
self.Pointlight.append(light_np)
|
||||||
|
|
||||||
|
# 更新场景树
|
||||||
if hasattr(self.world, 'updateSceneTree'):
|
if hasattr(self.world, 'updateSceneTree'):
|
||||||
self.world.updateSceneTree()
|
self.world.updateSceneTree()
|
||||||
|
|
||||||
return light,light_np
|
print(f"创建点光源: {light_np.getName()}")
|
||||||
|
return light, light_np
|
||||||
|
|
||||||
# ==================== GLB 转换方法 ====================
|
# ==================== GLB 转换方法 ====================
|
||||||
|
|
||||||
def _shouldConvertToGLB(self, filepath):
|
def _shouldConvertToGLB(self, filepath):
|
||||||
@ -1486,42 +1476,66 @@ except Exception as e:
|
|||||||
normal = GeomVertexWriter(vdata, 'normal')
|
normal = GeomVertexWriter(vdata, 'normal')
|
||||||
color = GeomVertexWriter(vdata, 'color')
|
color = GeomVertexWriter(vdata, 'color')
|
||||||
|
|
||||||
# 定义立方体顶点(稍微大一些,便于识别)
|
# 定义立方体顶点
|
||||||
size = 1.0
|
size = 1.0
|
||||||
vertices = [
|
vertices = [
|
||||||
(-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)
|
(-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)
|
||||||
]
|
]
|
||||||
|
|
||||||
# 使用更鲜明的颜色
|
normals = [
|
||||||
for vert in vertices:
|
# 前面法线
|
||||||
|
(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)
|
vertex.addData3f(*vert)
|
||||||
normal.addData3f(0, 0, 1)
|
normal.addData3f(*normals[i])
|
||||||
color.addData4f(0.0, 1.0, 1.0, 1.0) # 青色
|
color.addData4f(*face_colors[i])
|
||||||
|
|
||||||
# 创建几何体
|
# 创建几何体
|
||||||
geom = Geom(vdata)
|
geom = Geom(vdata)
|
||||||
|
|
||||||
# 创建面
|
# 创建面(每个面两个三角形)
|
||||||
prim = GeomTriangles(Geom.UHStatic)
|
prim = GeomTriangles(Geom.UHStatic)
|
||||||
# 底面
|
|
||||||
prim.addVertices(0, 1, 2)
|
# 每个面4个顶点,生成2个三角形
|
||||||
prim.addVertices(0, 2, 3)
|
for face in range(6): # 6个面
|
||||||
# 顶面
|
base_index = face * 4
|
||||||
prim.addVertices(4, 7, 6)
|
# 第一个三角形
|
||||||
prim.addVertices(4, 6, 5)
|
prim.addVertices(base_index, base_index + 1, base_index + 2)
|
||||||
# 前面
|
# 第二个三角形
|
||||||
prim.addVertices(0, 4, 5)
|
prim.addVertices(base_index, base_index + 2, base_index + 3)
|
||||||
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)
|
|
||||||
|
|
||||||
prim.closePrimitive()
|
prim.closePrimitive()
|
||||||
geom.addPrimitive(prim)
|
geom.addPrimitive(prim)
|
||||||
@ -1532,7 +1546,10 @@ except Exception as e:
|
|||||||
|
|
||||||
# 添加到场景
|
# 添加到场景
|
||||||
cube_node = parent_node.attachNewNode(geom_node)
|
cube_node = parent_node.attachNewNode(geom_node)
|
||||||
cube_node.setScale(5) # 适当大小
|
cube_node.setScale(5) # 设置合适的大小
|
||||||
|
|
||||||
|
# 设置双面渲染
|
||||||
|
cube_node.setTwoSided(True)
|
||||||
|
|
||||||
# 添加材质
|
# 添加材质
|
||||||
material = Material()
|
material = Material()
|
||||||
|
|||||||
@ -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.QtCore import Qt
|
||||||
from PyQt5.sip import delete
|
from PyQt5.sip import delete
|
||||||
from panda3d.core import GeomNode, ModelRoot
|
from panda3d.core import GeomNode, ModelRoot
|
||||||
@ -199,15 +199,6 @@ class InterfaceManager:
|
|||||||
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
||||||
print("添加相机节点")
|
print("添加相机节点")
|
||||||
|
|
||||||
# # 添加模型节点组
|
|
||||||
# modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
|
|
||||||
# print(f"模型列表中的模型数量: {len(self.world.models)}")
|
|
||||||
#
|
|
||||||
# # 添加GUI元素节点组
|
|
||||||
# guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
|
|
||||||
#
|
|
||||||
# lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
|
|
||||||
|
|
||||||
BLACK_LIST = {'','**','temp','collision'}
|
BLACK_LIST = {'','**','temp','collision'}
|
||||||
|
|
||||||
from panda3d.core import CollisionNode
|
from panda3d.core import CollisionNode
|
||||||
@ -224,35 +215,20 @@ class InterfaceManager:
|
|||||||
for child in node.getChildren():
|
for child in node.getChildren():
|
||||||
addNodeToTree(child,nodeItem,force = False)
|
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:
|
for model in self.world.models:
|
||||||
addNodeToTree(model, sceneRoot,force=True)
|
addNodeToTree(model, sceneRoot,force=True)
|
||||||
|
|
||||||
# 添加所有GUI元素
|
# 添加所有GUI元素
|
||||||
for gui in self.world.gui_elements:
|
for gui in self.world.gui_elements:
|
||||||
gui_type = gui.getTag("gui_type") or "unknown"
|
# 检查是否是有效的GUI节点(具有getTag方法的NodePath)
|
||||||
gui_text = gui.getTag("gui_text") or "GUI元素"
|
if hasattr(gui, 'getTag') and hasattr(gui, 'getName'):
|
||||||
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
gui_type = gui.getTag("gui_type") or "unknown"
|
||||||
item.setData(0, Qt.UserRole, gui)
|
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
|
||||||
#添加灯光节点
|
#添加灯光节点
|
||||||
for light in self.world.Spotlight + self.world.Pointlight:
|
for light in self.world.Spotlight + self.world.Pointlight:
|
||||||
addNodeToTree(light, sceneRoot, force=True)
|
addNodeToTree(light, sceneRoot, force=True)
|
||||||
@ -265,6 +241,16 @@ class InterfaceManager:
|
|||||||
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
||||||
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
||||||
addNodeToTree(tileset_node,tileset_item,force=True)
|
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:
|
if hasattr(self.world, 'ground') and self.world.ground:
|
||||||
|
|||||||
@ -12,7 +12,8 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
|
|||||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||||||
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
|
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 PyQt5.QtCore import Qt, QDir, QTimer
|
||||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget
|
||||||
|
|
||||||
@ -115,6 +116,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||||||
self.createPointLightAction = 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菜单
|
# GUI菜单
|
||||||
self.guiMenu = menubar.addMenu('GUI')
|
self.guiMenu = menubar.addMenu('GUI')
|
||||||
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
|
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
|
||||||
@ -275,6 +283,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.create3DImageTool.setText("3D图片")
|
self.create3DImageTool.setText("3D图片")
|
||||||
self.toolbar.addWidget(self.create3DImageTool)
|
self.toolbar.addWidget(self.create3DImageTool)
|
||||||
|
|
||||||
|
self.create2DImageTool = QToolButton()
|
||||||
|
self.create2DImageTool.setText("2D图片")
|
||||||
|
self.toolbar.addWidget(self.create2DImageTool)
|
||||||
|
|
||||||
self.createSpotLight = QToolButton()
|
self.createSpotLight = QToolButton()
|
||||||
self.createSpotLight.setText("聚光灯")
|
self.createSpotLight.setText("聚光灯")
|
||||||
self.toolbar.addWidget(self.createSpotLight)
|
self.toolbar.addWidget(self.createSpotLight)
|
||||||
@ -299,6 +311,21 @@ class MainWindow(QMainWindow):
|
|||||||
self.addModelTool.clicked.connect(self.onAddModelClicked)
|
self.addModelTool.clicked.connect(self.onAddModelClicked)
|
||||||
self.toolbar.addWidget(self.addModelTool)
|
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.selectTool.setChecked(True)
|
||||||
self.world.setCurrentTool("选择")
|
self.world.setCurrentTool("选择")
|
||||||
@ -458,14 +485,25 @@ class MainWindow(QMainWindow):
|
|||||||
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
|
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
|
||||||
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
|
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
|
||||||
self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView)
|
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创建按钮事件
|
# 连接工具栏GUI创建按钮事件
|
||||||
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
|
self.createButtonTool.clicked.connect(lambda: self.world.createGUIButton())
|
||||||
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
|
self.createLabelTool.clicked.connect(lambda: self.world.createGUILabel())
|
||||||
self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText())
|
self.create3DTextTool.clicked.connect(lambda: self.world.createGUI3DText())
|
||||||
self.create3DImageTool.clicked.connect(lambda: self.world.createGUI3DImage())
|
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.createSpotLight.clicked.connect(lambda :self.world.createSpotLight())
|
||||||
self.createPointLight.clicked.connect(lambda :self.world.createPointLight())
|
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)
|
# self.treeWidget.itemClicked.connect(self.world.onTreeItemClicked)
|
||||||
@ -955,7 +993,158 @@ class MainWindow(QMainWindow):
|
|||||||
print(f"⚠️ 关闭应用程序时出错: {e}")
|
print(f"⚠️ 关闭应用程序时出错: {e}")
|
||||||
event.accept() # 即使出错也要关闭
|
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(1,10000)
|
||||||
|
width_spin.setValue(100)
|
||||||
|
width_layout.addWidget(width_spin)
|
||||||
|
layout.addLayout(width_layout)
|
||||||
|
|
||||||
|
# 高度
|
||||||
|
height_layout = QHBoxLayout()
|
||||||
|
height_layout.addWidget(QLabel("高度:"))
|
||||||
|
height_spin = QDoubleSpinBox()
|
||||||
|
height_spin.setRange(1, 10000)
|
||||||
|
height_spin.setValue(100)
|
||||||
|
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):
|
def setup_main_window(world):
|
||||||
"""设置主窗口的便利函数"""
|
"""设置主窗口的便利函数"""
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
@ -965,4 +1154,4 @@ def setup_main_window(world):
|
|||||||
main_window = MainWindow(world)
|
main_window = MainWindow(world)
|
||||||
main_window.show()
|
main_window.show()
|
||||||
|
|
||||||
return app, main_window
|
return app, main_window
|
||||||
1021
ui/property_panel.py
1021
ui/property_panel.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user