Merge pull request 'addRender' (#22) from addRender into main
Reviewed-on: Hector/EG#22
This commit is contained in:
commit
fc846e296d
@ -122,7 +122,7 @@ class EventHandler:
|
||||
if self.world.currentTool == "地形编辑":
|
||||
self._handleTerrainEdit(evt,"add")
|
||||
return
|
||||
|
||||
|
||||
# 获取鼠标点击的位置
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
@ -264,6 +264,154 @@ class EventHandler:
|
||||
|
||||
print("=== 鼠标左键事件处理结束 ===\n")
|
||||
|
||||
def mousePressEventRight(self,evt):
|
||||
"""处理鼠标右键按下事件"""
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
# 检查是否是地形编辑模式
|
||||
if self.world.currentTool == "地形编辑":
|
||||
self._handleTerrainEdit(evt, "subtract") # 降低地形
|
||||
return
|
||||
|
||||
# 其他右键处理逻辑可以在这里添加
|
||||
print("鼠标右键事件处理")
|
||||
|
||||
def _handleTerrainEdit(self, evt, operation):
|
||||
"""处理地形编辑"""
|
||||
try:
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
|
||||
winWidth, winHeight = self.world.getWindowSize()
|
||||
|
||||
mx = 2.0 * x / float(winWidth) - 1.0
|
||||
my = 1.0 - 2.0 * y / float(winHeight)
|
||||
|
||||
nearPoint = Point3()
|
||||
farPoint = Point3()
|
||||
self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint)
|
||||
|
||||
worldNearPoint = self.world.render.getRelativePoint(self.world.cam, nearPoint)
|
||||
worldFarPoint = self.world.render.getRelativePoint(self.world.cam, farPoint)
|
||||
|
||||
picker = CollisionTraverser()
|
||||
queue = CollisionHandlerQueue()
|
||||
|
||||
pickerNode = CollisionNode('terrain_edit_ray')
|
||||
pickerNP = self.world.cam.attachNewNode(pickerNode)
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
# 确保我们检测所有碰撞体
|
||||
pickerNode.setFromCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 使用相机坐标系的点创建射线
|
||||
direction = farPoint - nearPoint
|
||||
direction.normalize()
|
||||
pickerNode.addSolid(CollisionRay(nearPoint, direction))
|
||||
|
||||
picker.addCollider(pickerNP, queue)
|
||||
picker.traverse(self.world.render)
|
||||
print(f"地形碰撞检测结果数量: {queue.getNumEntries()}")
|
||||
|
||||
# 添加调试信息,显示所有场景中的碰撞体
|
||||
print("场景中的碰撞体:")
|
||||
|
||||
def show_colliders(node, level=0):
|
||||
indent = " " * level
|
||||
if not node.isEmpty():
|
||||
if isinstance(node.node(), CollisionNode):
|
||||
print(f"{indent}CollisionNode: {node.getName()}")
|
||||
for child in node.getChildren():
|
||||
show_colliders(child, level + 1)
|
||||
|
||||
show_colliders(self.world.render)
|
||||
|
||||
# 射线检测结果处理
|
||||
hitPos = None
|
||||
hitNode = None
|
||||
hitTerrainInfo = None
|
||||
|
||||
if queue.getNumEntries() > 0:
|
||||
queue.sortEntries()
|
||||
# 遍历所有碰撞结果,找到地形节点
|
||||
for i in range(queue.getNumEntries()):
|
||||
entry = queue.getEntry(i)
|
||||
collided_node = entry.getIntoNodePath()
|
||||
print(f"碰撞到节点: {collided_node.getName()}")
|
||||
|
||||
# 检查是否是地形节点
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if collided_node == terrain_node or terrain_node.isAncestorOf(collided_node):
|
||||
hitPos = entry.getSurfacePoint(self.world.render)
|
||||
hitNode = collided_node
|
||||
hitTerrainInfo = terrain_info
|
||||
print(f"找到地形节点: {terrain_node.getName()}")
|
||||
break
|
||||
|
||||
if hitPos:
|
||||
break
|
||||
|
||||
if hitPos and hitTerrainInfo:
|
||||
# 修改地形高度
|
||||
x_pos, y_pos = hitPos.getX(), hitPos.getY()
|
||||
|
||||
# 使用 getattr 获取地形编辑参数
|
||||
radius = getattr(self.world, 'terrain_edit_radius', 3.0)
|
||||
strength = getattr(self.world, 'terrain_edit_strength', 0.3)
|
||||
|
||||
print(f"准备编辑地形: 位置({x_pos:.2f}, {y_pos:.2f}), 半径{radius}, 强度{strength}, 操作{operation}")
|
||||
|
||||
# 添加更多调试信息
|
||||
print(f"地形信息: {hitTerrainInfo}")
|
||||
if 'heightmap' in hitTerrainInfo:
|
||||
print(f"高度图路径: {hitTerrainInfo['heightmap']}")
|
||||
|
||||
try:
|
||||
# 检查 terrain_manager 是否有 modifyTerrainHeight 方法
|
||||
if not hasattr(self.world.terrain_manager, 'modifyTerrainHeight'):
|
||||
print("✗ 错误: terrain_manager 没有 modifyTerrainHeight 方法")
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
return
|
||||
|
||||
# 调用地形编辑方法
|
||||
success = self.world.terrain_manager.modifyTerrainHeight(
|
||||
hitTerrainInfo, x_pos, y_pos, radius=radius, strength=strength, operation=operation)
|
||||
|
||||
if success:
|
||||
print(f"✓ 地形编辑成功: {operation} at ({x_pos:.2f}, {y_pos:.2f})")
|
||||
# 显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
|
||||
# 强制刷新地形显示
|
||||
if 'terrain' in hitTerrainInfo:
|
||||
hitTerrainInfo['terrain'].generate()
|
||||
print("✓ 地形已刷新")
|
||||
else:
|
||||
print("✗ 地形编辑失败")
|
||||
# 即使编辑失败也显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
except Exception as e:
|
||||
print(f"✗ 地形编辑过程中出现异常: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# 即使编辑失败也显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
else:
|
||||
print("没有检测到地形碰撞")
|
||||
# 显示射线(无碰撞)
|
||||
self.showClickRay(worldNearPoint, worldFarPoint)
|
||||
|
||||
# 清理碰撞检测节点
|
||||
pickerNP.removeNode()
|
||||
print("地形编辑处理完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"地形编辑处理出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def mousePressEventRight(self,evt):
|
||||
"""处理鼠标右键按下事件"""
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
# core/terrain_manager.py
|
||||
import time
|
||||
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
|
||||
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
|
||||
import os
|
||||
@ -22,7 +23,7 @@ class TerrainManager:
|
||||
|
||||
try:
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"terrain_{len(self.terrains)}"
|
||||
terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 加载高度图
|
||||
@ -33,7 +34,7 @@ class TerrainManager:
|
||||
print(f"原始图像尺寸: {width}x{height}")
|
||||
|
||||
# 找到最接近的有效尺寸
|
||||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025]
|
||||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025, 2049]
|
||||
target_size = 129 # 默认尺寸
|
||||
|
||||
# 选择最接近的尺寸
|
||||
@ -48,6 +49,7 @@ class TerrainManager:
|
||||
# 如果需要,调整图像尺寸
|
||||
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
|
||||
@ -56,7 +58,10 @@ class TerrainManager:
|
||||
terrain.setHeightfield(height_image)
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBruteforce(False) # 使用LOD
|
||||
terrain.setBruteforce(True) # 使用LOD
|
||||
|
||||
terrain.setBlockSize(32)
|
||||
# terrain.setNearFarThreshold(50.0,200.0)
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
@ -64,21 +69,34 @@ class TerrainManager:
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (target_size - 1) / 2
|
||||
terrain_node.setPos(-center_offset * scale[0], -center_offset * scale[1], -5)
|
||||
|
||||
# 设置缩放
|
||||
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
|
||||
# 为地形节点设置名称,便于在场景树中识别
|
||||
terrain_node.setName(f"Terrain_{os.path.basename(heightmap_path)}")
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
terrain_node.setPythonTag("selectable", True)
|
||||
|
||||
# 保存地形信息(包括高度图的副本)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
@ -86,14 +104,18 @@ class TerrainManager:
|
||||
'heightmap': heightmap_path,
|
||||
'heightfield': height_image, # 保存高度图副本
|
||||
'scale': scale,
|
||||
'name': f"Terrain_{os.path.basename(heightmap_path)}"
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
|
||||
'updateSceneTree'):
|
||||
try:
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
except Exception as e:
|
||||
print(f"警告: 更新场景树时出错: {e}")
|
||||
|
||||
print(f"✓ 成功从 {heightmap_path} 创建地形")
|
||||
return terrain_info
|
||||
@ -104,7 +126,7 @@ class TerrainManager:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def createFlatTerrain(self, size=(100, 100), resolution=129):
|
||||
def createFlatTerrain(self, size=(0.3, 0.3), resolution=129):
|
||||
"""创建平面地形"""
|
||||
try:
|
||||
# 确保分辨率是2的幂次方加1 (如129, 257, 513等)
|
||||
@ -116,7 +138,7 @@ class TerrainManager:
|
||||
resolution = closest_res
|
||||
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"flat_terrain_{len(self.terrains)}"
|
||||
terrain_name = f"flat_terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 创建一个平面高度图,尺寸必须是2^n+1
|
||||
@ -125,7 +147,7 @@ class TerrainManager:
|
||||
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
terrain.setBruteforce(False) # 使用LOD
|
||||
terrain.setBruteforce(True) # 设置LOD
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBlockSize(32) # 设置块大小
|
||||
@ -136,17 +158,26 @@ class TerrainManager:
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
# 设置缩放以达到指定大小
|
||||
terrain_node.setScale(size[0], size[1], 50) # Z轴缩放更大以显示地形起伏
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的平面地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (resolution - 1) / 2
|
||||
terrain_node.setPos(-center_offset * size[0], -center_offset * size[1], 0)
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
|
||||
# 为地形节点设置名称,便于在场景树中识别
|
||||
terrain_node.setName(f"FlatTerrain_{len(self.terrains)}")
|
||||
# 为地形添加碰撞体
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
@ -158,14 +189,18 @@ class TerrainManager:
|
||||
'heightmap': None,
|
||||
'heightfield': height_image, # 保存高度图
|
||||
'scale': (size[0], size[1], 50),
|
||||
'name': f"FlatTerrain_{len(self.terrains)}"
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
|
||||
'updateSceneTree'):
|
||||
try:
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
except Exception as e:
|
||||
print(f"警告: 更新场景树时出错: {e}")
|
||||
|
||||
print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}")
|
||||
return terrain_info
|
||||
@ -190,7 +225,7 @@ class TerrainManager:
|
||||
terrain_node.setMaterial(material)
|
||||
|
||||
# 启用材质
|
||||
terrain_node.setAttrib(ColorAttrib.makeDefaultColor())
|
||||
terrain_node.setAttrib(ColorAttrib.makeDefault())
|
||||
|
||||
print("✓ 地形材质已应用")
|
||||
except Exception as e:
|
||||
@ -227,6 +262,7 @@ class TerrainManager:
|
||||
|
||||
try:
|
||||
terrain = terrain_info['terrain']
|
||||
terrain_node = terrain_info['node'] # 获取地形节点
|
||||
|
||||
# 直接使用我们保存的高度图数据
|
||||
if 'heightfield' not in terrain_info:
|
||||
@ -242,19 +278,58 @@ class TerrainManager:
|
||||
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)
|
||||
# 获取地形节点信息
|
||||
terrain_pos = terrain_node.getPos()
|
||||
terrain_scale = terrain_node.getScale()
|
||||
|
||||
# 正确的坐标转换方法
|
||||
# 1. 将世界坐标转换为相对于地形中心的坐标
|
||||
relative_x = x - terrain_pos.getX()
|
||||
relative_y = y - terrain_pos.getY()
|
||||
|
||||
# 2. 考虑地形的缩放
|
||||
scaled_x = relative_x / terrain_scale.getX()
|
||||
scaled_y = relative_y / terrain_scale.getY()
|
||||
|
||||
# 3. 转换为高度图坐标
|
||||
center_offset = (width - 1) / 2
|
||||
center_x = int(scaled_x + center_offset)
|
||||
center_y = int(scaled_y + center_offset)
|
||||
|
||||
print(f"坐标转换详情:")
|
||||
print(f" 世界坐标: ({x:.2f}, {y:.2f})")
|
||||
print(f" 地形位置: ({terrain_pos.getX():.2f}, {terrain_pos.getY():.2f})")
|
||||
print(f" 地形缩放: ({terrain_scale.getX():.2f}, {terrain_scale.getY():.2f})")
|
||||
print(f" 相对坐标: ({relative_x:.2f}, {relative_y:.2f})")
|
||||
print(f" 缩放坐标: ({scaled_x:.2f}, {scaled_y:.2f})")
|
||||
print(f" 中心偏移: {center_offset}")
|
||||
print(f" 高度图坐标: ({center_x}, {center_y})")
|
||||
print(f" 高度图尺寸: {width} x {height}")
|
||||
|
||||
# 检查中心点是否在有效范围内
|
||||
if not (0 <= center_x < width and 0 <= center_y < height):
|
||||
print(f"❌ 中心点坐标超出范围: ({center_x}, {center_y})")
|
||||
print(f"❌ 有效范围: (0-{width - 1}, 0-{height - 1})")
|
||||
return False
|
||||
|
||||
# 修改半径范围内的点
|
||||
modified = False
|
||||
for dx in range(-int(radius), int(radius) + 1):
|
||||
for dy in range(-int(radius), int(radius) + 1):
|
||||
|
||||
# 使用正确的半径计算(考虑地形缩放)
|
||||
effective_radius = max(1, int(radius / max(terrain_scale.getX(), terrain_scale.getY())))
|
||||
|
||||
print(f"有效编辑半径: {effective_radius}")
|
||||
|
||||
# 限制编辑半径不超过地形尺寸的1/4,避免性能问题
|
||||
effective_radius = min(effective_radius, width // 4, height // 4)
|
||||
|
||||
for dx in range(-effective_radius, effective_radius + 1):
|
||||
for dy in range(-effective_radius, effective_radius + 1):
|
||||
# 检查距离
|
||||
distance = (dx * dx + dy * dy) ** 0.5
|
||||
if distance <= radius:
|
||||
if distance <= effective_radius:
|
||||
# 计算影响权重
|
||||
weight = 1.0 - (distance / radius)
|
||||
weight = 1.0 - (distance / effective_radius) if effective_radius > 0 else 1.0
|
||||
|
||||
# 计算目标点坐标
|
||||
target_x = center_x + dx
|
||||
@ -267,17 +342,15 @@ class TerrainManager:
|
||||
|
||||
# 根据操作类型修改高度
|
||||
if operation == "add":
|
||||
new_height = current_height + strength * weight * 0.1
|
||||
new_height = min(1.0, current_height + strength * weight * 0.01)
|
||||
elif operation == "subtract":
|
||||
new_height = current_height - strength * weight * 0.1
|
||||
new_height = max(0.0, current_height - strength * weight * 0.01)
|
||||
elif operation == "set":
|
||||
new_height = strength
|
||||
# 平坦化操作,设置为中等高度
|
||||
new_height = 0.5
|
||||
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)
|
||||
@ -289,7 +362,24 @@ class TerrainManager:
|
||||
# 重新设置高度图并生成地形
|
||||
terrain.setHeightfield(heightmap)
|
||||
terrain.generate()
|
||||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}")
|
||||
|
||||
# 关键修复:重新创建碰撞体,因为在生成地形时会丢失原有碰撞体
|
||||
from panda3d.core import BitMask32, CollisionNode, CollisionPlane, Plane, Point3
|
||||
# 移除旧的地形碰撞体(如果存在)
|
||||
for child in terrain_node.getChildren():
|
||||
if isinstance(child.node(), CollisionNode) and child.getName().startswith("terrain_collision_"):
|
||||
child.removeNode()
|
||||
|
||||
# 创建新的地形碰撞体
|
||||
# 使用更简单的方法:直接给地形节点添加碰撞体
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 确保地形节点本身具有碰撞掩码
|
||||
|
||||
# 为地形的每个子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}, 操作{operation}")
|
||||
print(f"✓ 重新设置了地形碰撞体")
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
@ -308,7 +398,7 @@ class TerrainManager:
|
||||
if terrain_info in self.terrains:
|
||||
# 从场景中移除地形节点
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node:
|
||||
if terrain_node and not terrain_node.isEmpty():
|
||||
terrain_node.removeNode()
|
||||
|
||||
# 从列表中移除
|
||||
@ -319,6 +409,10 @@ class TerrainManager:
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}")
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
30
main.py
30
main.py
@ -87,6 +87,10 @@ class MyWorld(CoreWorld):
|
||||
#self.material_editor = None
|
||||
self.terrain_manager = TerrainManager(self)
|
||||
|
||||
self.terrain_edit_radius = 3.0
|
||||
self.terrain_edit_strength=0.3
|
||||
self.terrain_edit_operation = "add"
|
||||
|
||||
print("✓ MyWorld 初始化完成")
|
||||
|
||||
# ==================== 兼容性属性 ====================
|
||||
@ -717,14 +721,32 @@ class MyWorld(CoreWorld):
|
||||
"""更新所有地形的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)
|
||||
|
||||
def setTerrainEditParameters(self,radius=None,strength=None,operation=None):
|
||||
if radius is not None:
|
||||
self.terrain_edit_radius = float(radius)
|
||||
if strength is not None:
|
||||
self.terrain_edit_strenght = float(strength)
|
||||
if operation is not None:
|
||||
self.terrain_edit_operation = operation
|
||||
print(f"地形编辑参数已更新: 半径={self.terrain_edit_radius}, 强度={self.terrain_edit_strength}, 操作={self.terrain_edit_operation}")
|
||||
def getTerrainEditParameters(self):
|
||||
"""获取当前地形编辑参数"""
|
||||
return {
|
||||
'radius': self.terrain_edit_radius,
|
||||
'strength': self.terrain_edit_strength,
|
||||
'operation': self.terrain_edit_operation
|
||||
}
|
||||
|
||||
def modifyTerrainHeight(self, terrain_info, x, y, radius, strength, operation="add"):
|
||||
"""修改地形高度的便捷方法"""
|
||||
if hasattr(self, 'terrain_manager'):
|
||||
return self.terrain_manager.modifyTerrainHeight(terrain_info, x, y, radius, strength, operation)
|
||||
return False
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
|
||||
239
test_axis_fix.py
239
test_axis_fix.py
@ -1,239 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
坐标轴修复验证脚本
|
||||
用于测试鼠标悬停高亮和拖动一致性的修复效果
|
||||
"""
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
|
||||
from PyQt5.QtCore import Qt
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
from QPanda3D.Panda3DWorld import Panda3DWorld
|
||||
from panda3d.core import *
|
||||
|
||||
class AxisTestWorld(Panda3DWorld):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
print("=== 坐标轴修复验证测试 ===")
|
||||
self.setBackgroundColor(0.2, 0.2, 0.3)
|
||||
|
||||
# 调整相机位置
|
||||
self.cam.setPos(8, -15, 8)
|
||||
self.cam.lookAt(0, 0, 0)
|
||||
|
||||
# Qt部件引用
|
||||
self.qtWidget = None
|
||||
|
||||
# 基础光照
|
||||
alight = AmbientLight('alight')
|
||||
alight.setColor((0.3, 0.3, 0.3, 1))
|
||||
alnp = self.render.attachNewNode(alight)
|
||||
self.render.setLight(alnp)
|
||||
|
||||
dlight = DirectionalLight('dlight')
|
||||
dlight.setColor((0.8, 0.8, 0.8, 1))
|
||||
dlnp = self.render.attachNewNode(dlight)
|
||||
dlnp.setHpr(45, -45, 0)
|
||||
self.render.setLight(dlnp)
|
||||
|
||||
# 创建测试立方体
|
||||
self.createTestCube()
|
||||
|
||||
# 设置当前工具
|
||||
self.currentTool = "移动"
|
||||
|
||||
# 模拟选择系统和工具管理器
|
||||
from core.selection import SelectionSystem
|
||||
self.selection = SelectionSystem(self)
|
||||
|
||||
# 简单的工具管理器模拟
|
||||
class SimpleToolManager:
|
||||
def isScaleTool(self):
|
||||
return False
|
||||
def isRotateTool(self):
|
||||
return False
|
||||
|
||||
self.tool_manager = SimpleToolManager()
|
||||
|
||||
# 模拟属性面板
|
||||
class SimplePropertyPanel:
|
||||
def refreshModelValues(self, node):
|
||||
print(f"属性更新: {node.getName()} 位置={node.getPos()}")
|
||||
|
||||
self.property_panel = SimplePropertyPanel()
|
||||
|
||||
# 自动选中立方体
|
||||
self.selectedNode = self.testCube
|
||||
self.selection.updateSelection(self.testCube)
|
||||
|
||||
print("测试说明:")
|
||||
print("1. 鼠标悬停在坐标轴上应该变为黄色高亮")
|
||||
print("2. 点击并拖拽坐标轴,立方体应该沿对应轴移动")
|
||||
print("3. 拖动行为应该与鼠标移动方向一致")
|
||||
|
||||
def setQtWidget(self, widget):
|
||||
"""设置Qt部件引用"""
|
||||
self.qtWidget = widget
|
||||
|
||||
def getWindowSize(self):
|
||||
"""获取准确的窗口尺寸"""
|
||||
if self.qtWidget:
|
||||
width, height = self.qtWidget.getActualSize()
|
||||
if width > 0 and height > 0:
|
||||
return width, height
|
||||
|
||||
if hasattr(self, 'win') and self.win:
|
||||
width = self.win.getXSize()
|
||||
height = self.win.getYSize()
|
||||
return width, height
|
||||
|
||||
return 800, 600
|
||||
|
||||
def createTestCube(self):
|
||||
"""创建测试立方体"""
|
||||
from panda3d.core import CardMaker
|
||||
|
||||
cm = CardMaker('cube')
|
||||
cm.setFrame(-1, 1, -1, 1)
|
||||
|
||||
self.testCube = self.render.attachNewNode("testCube")
|
||||
|
||||
# 6个面,不同颜色
|
||||
faces = [
|
||||
(Point3(0, 1, 0), Vec3(0, 0, 0), (1, 0.5, 0.5, 1)), # 前面
|
||||
(Point3(0, -1, 0), Vec3(0, 0, 180), (0.5, 1, 0.5, 1)), # 后面
|
||||
(Point3(-1, 0, 0), Vec3(0, 0, 90), (0.5, 0.5, 1, 1)), # 左面
|
||||
(Point3(1, 0, 0), Vec3(0, 0, -90), (1, 1, 0.5, 1)), # 右面
|
||||
(Point3(0, 0, 1), Vec3(-90, 0, 0), (1, 0.5, 1, 1)), # 顶面
|
||||
(Point3(0, 0, -1), Vec3(90, 0, 0), (0.5, 1, 1, 1)) # 底面
|
||||
]
|
||||
|
||||
for pos, hpr, color in faces:
|
||||
face = self.testCube.attachNewNode(cm.generate())
|
||||
face.setPos(pos)
|
||||
face.setHpr(hpr)
|
||||
face.setColor(*color)
|
||||
|
||||
print(f"✓ 创建测试立方体,位置: {self.testCube.getPos()}")
|
||||
return self.testCube
|
||||
|
||||
def mousePressEventLeft(self, evt):
|
||||
"""处理鼠标左键按下事件"""
|
||||
if not evt:
|
||||
return
|
||||
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
|
||||
# 检查坐标轴点击
|
||||
if self.selection.gizmo:
|
||||
gizmoAxis = self.selection.checkGizmoClick(x, y)
|
||||
if gizmoAxis:
|
||||
print(f"✓ 坐标轴点击检测成功: {gizmoAxis}")
|
||||
self.selection.startGizmoDrag(gizmoAxis, x, y)
|
||||
return
|
||||
else:
|
||||
print("× 坐标轴点击检测失败")
|
||||
|
||||
def mouseReleaseEventLeft(self, evt):
|
||||
"""处理鼠标左键释放事件"""
|
||||
if self.selection.isDraggingGizmo:
|
||||
self.selection.stopGizmoDrag()
|
||||
|
||||
def mouseMoveEvent(self, evt):
|
||||
"""处理鼠标移动事件"""
|
||||
if not evt:
|
||||
return
|
||||
|
||||
x = evt.get('x', 0)
|
||||
y = evt.get('y', 0)
|
||||
|
||||
# 处理坐标轴拖拽
|
||||
if self.selection.isDraggingGizmo:
|
||||
self.selection.updateGizmoDrag(x, y)
|
||||
return
|
||||
|
||||
# 更新坐标轴高亮
|
||||
if self.selection.gizmo and not self.selection.isDraggingGizmo:
|
||||
self.selection.updateGizmoHighlight(x, y)
|
||||
|
||||
|
||||
class TestWidget(QPanda3DWidget):
|
||||
"""测试部件"""
|
||||
def __init__(self, world, parent=None):
|
||||
super().__init__(world, parent)
|
||||
self.world = world
|
||||
|
||||
if hasattr(world, 'setQtWidget'):
|
||||
world.setQtWidget(self)
|
||||
|
||||
def getActualSize(self):
|
||||
return (self.width(), self.height())
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.world.mousePressEventLeft({
|
||||
'x': event.x(),
|
||||
'y': event.y()
|
||||
})
|
||||
event.accept()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
self.world.mouseReleaseEventLeft({
|
||||
'x': event.x(),
|
||||
'y': event.y()
|
||||
})
|
||||
event.accept()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
self.world.mouseMoveEvent({
|
||||
'x': event.x(),
|
||||
'y': event.y()
|
||||
})
|
||||
event.accept()
|
||||
|
||||
|
||||
def main():
|
||||
print("启动坐标轴修复验证测试...")
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 创建主窗口
|
||||
mainWindow = QMainWindow()
|
||||
mainWindow.setWindowTitle("坐标轴修复验证测试")
|
||||
mainWindow.setGeometry(100, 100, 900, 700)
|
||||
|
||||
# 创建布局
|
||||
centralWidget = QWidget()
|
||||
layout = QVBoxLayout(centralWidget)
|
||||
|
||||
# 添加说明
|
||||
label = QLabel("坐标轴修复验证测试 - 测试鼠标悬停高亮和拖动一致性")
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
label.setStyleSheet("background-color: lightgreen; padding: 10px; font-size: 12px;")
|
||||
layout.addWidget(label)
|
||||
|
||||
# 创建世界和部件
|
||||
world = AxisTestWorld()
|
||||
pandaWidget = TestWidget(world)
|
||||
layout.addWidget(pandaWidget)
|
||||
|
||||
mainWindow.setCentralWidget(centralWidget)
|
||||
mainWindow.show()
|
||||
|
||||
print("\n修复验证项目:")
|
||||
print("1. ✓ 鼠标悬停时坐标轴应该正确高亮为黄色")
|
||||
print("2. ✓ 点击坐标轴应该能正确开始拖拽")
|
||||
print("3. ✓ 拖拽方向应该与鼠标移动方向一致")
|
||||
print("4. ✓ 减少了控制台调试输出")
|
||||
|
||||
return app.exec_()
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -137,6 +137,22 @@ class InterfaceManager:
|
||||
def deleteNode(self, nodePath, item):
|
||||
"""删除节点"""
|
||||
try:
|
||||
item_data = item.data(0,Qt.UserRole+1)
|
||||
if item_data == "terrain":
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
terrain_to_remove = None
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
if terrain_info['node'] == nodePath:
|
||||
terrain_to_remove = terrain_info
|
||||
break
|
||||
if terrain_to_remove:
|
||||
self.world.terrain_manager.deleteTerrain(terrain_to_remove)
|
||||
print(f"成功删除地形节点:{nodePath.getName()}")
|
||||
self.updateSceneTree()
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
# 从场景中移除
|
||||
self.world.property_panel.removeActorForModel(nodePath)
|
||||
|
||||
@ -207,6 +223,9 @@ class InterfaceManager:
|
||||
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(),CollisionNode) or isinstance(node.node(),ModelRoot) or name==""
|
||||
|
||||
def addNodeToTree(node,parentItem,force = False):
|
||||
if node.isEmpty():
|
||||
return
|
||||
|
||||
if not force and should_skip(node):
|
||||
return
|
||||
nodeItem = QTreeWidgetItem(parentItem,[node.getName()])
|
||||
@ -216,7 +235,8 @@ class InterfaceManager:
|
||||
addNodeToTree(child,nodeItem,force = False)
|
||||
|
||||
for model in self.world.models:
|
||||
addNodeToTree(model, sceneRoot,force=True)
|
||||
if not model.isEmpty():
|
||||
addNodeToTree(model, sceneRoot,force=True)
|
||||
|
||||
# 添加所有GUI元素
|
||||
for gui in self.world.gui_elements:
|
||||
@ -229,18 +249,26 @@ class InterfaceManager:
|
||||
else:
|
||||
# 跳过非GUI节点(如QDockWidget等Qt对象)
|
||||
continue
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
|
||||
#添加灯光节点
|
||||
for light in self.world.Spotlight + self.world.Pointlight:
|
||||
addNodeToTree(light, sceneRoot, force=True)
|
||||
if not light.isEmpty:
|
||||
addNodeToTree(light, sceneRoot, force=True)
|
||||
|
||||
#添加 Cesium tilesets
|
||||
if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'tilesets'):
|
||||
for i , tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
tileset_node = tileset_info['node']
|
||||
tileset_url = tileset_info['url']
|
||||
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
||||
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
||||
addNodeToTree(tileset_node,tileset_item,force=True)
|
||||
if not tileset_node.isEmpty():
|
||||
tileset_url = tileset_info['url']
|
||||
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
|
||||
tileset_item.setData(0,Qt.UserRole,tileset_node)
|
||||
addNodeToTree(tileset_node,tileset_item,force=True)
|
||||
#添加地形节点
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
@ -252,10 +280,7 @@ class InterfaceManager:
|
||||
addNodeToTree(terrain_node,terrain_item,force=True)
|
||||
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
groundItem = QTreeWidgetItem(sceneRoot, ['地板'])
|
||||
groundItem.setData(0, Qt.UserRole, self.world.ground)
|
||||
|
||||
|
||||
# 展开所有节点
|
||||
#self.treeWidget.expandAll()
|
||||
|
||||
@ -1005,8 +1005,8 @@ class MainWindow(QMainWindow):
|
||||
width_layout = QHBoxLayout()
|
||||
width_layout.addWidget(QLabel("宽度:"))
|
||||
width_spin = QDoubleSpinBox()
|
||||
width_spin.setRange(1,10000)
|
||||
width_spin.setValue(100)
|
||||
width_spin.setRange(0,10000)
|
||||
width_spin.setValue(0.3)
|
||||
width_layout.addWidget(width_spin)
|
||||
layout.addLayout(width_layout)
|
||||
|
||||
@ -1014,8 +1014,8 @@ class MainWindow(QMainWindow):
|
||||
height_layout = QHBoxLayout()
|
||||
height_layout.addWidget(QLabel("高度:"))
|
||||
height_spin = QDoubleSpinBox()
|
||||
height_spin.setRange(1, 10000)
|
||||
height_spin.setValue(100)
|
||||
height_spin.setRange(0, 10000)
|
||||
height_spin.setValue(0.3)
|
||||
height_layout.addWidget(height_spin)
|
||||
layout.addLayout(height_layout)
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import os
|
||||
from collections import deque
|
||||
from traceback import print_exc
|
||||
from traceback import print_exc, print_last
|
||||
from types import new_class
|
||||
from typing import Hashable
|
||||
|
||||
@ -9,6 +10,7 @@ from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
|
||||
from PyQt5.QtCore import Qt
|
||||
from deploy_libs.unicodedata import normalize
|
||||
from direct.actor.Actor import Actor
|
||||
from idna import check_label
|
||||
from jinja2.compiler import has_safe_repr
|
||||
from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup
|
||||
from scene import util
|
||||
@ -23,6 +25,14 @@ class PropertyPanelManager:
|
||||
self._propertyLayout = None
|
||||
self._actor_cache={}
|
||||
|
||||
# 初始化地形编辑参数
|
||||
if not hasattr(self.world, 'terrain_edit_radius'):
|
||||
self.world.terrain_edit_radius = 3.0
|
||||
if not hasattr(self.world, 'terrain_edit_strength'):
|
||||
self.world.terrain_edit_strength = 0.3
|
||||
if not hasattr(self.world, 'terrain_edit_operation'): # 这里原来是 terrain_edit_opertaion
|
||||
self.world.terrain_edit_operation = "add"
|
||||
|
||||
# 定义紧凑样式
|
||||
self.compact_style = """
|
||||
QDoubleSpinBox {
|
||||
@ -143,7 +153,9 @@ class PropertyPanelManager:
|
||||
# 获取节点对象
|
||||
model = item.data(0, Qt.UserRole)
|
||||
|
||||
if model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset":
|
||||
if self._isTerrainNode(model,item):
|
||||
self._showTerrainProperties(model,item)
|
||||
elif model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset":
|
||||
self._showCesiumTilesetProperties(model,item)
|
||||
elif model and hasattr(model, 'getTag') and model.getTag("gui_type"):
|
||||
self.updateGUIPropertyPanel(model)
|
||||
@ -161,6 +173,362 @@ class PropertyPanelManager:
|
||||
if propertyWidget:
|
||||
propertyWidget.update()
|
||||
|
||||
def _isTerrainNode(self,node,item):
|
||||
"""检查是否是地形节点"""
|
||||
item_data = item.data(0,Qt.UserRole+1)
|
||||
if item_data == "terrain":
|
||||
return True
|
||||
|
||||
if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
if terrain_info['node'] == node:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _showTerrainProperties(self,terrain_node,item):
|
||||
"""显示地形属性面板"""
|
||||
try:
|
||||
terrain_info = None
|
||||
if hasattr(self.world,'terrain_manager'):
|
||||
for info in self.world.terrain_manager.terrains:
|
||||
if info['node'] == terrain_node:
|
||||
terrain_info = info
|
||||
break
|
||||
if not terrain_info:
|
||||
no_info_label = QLabel("未找到地形信息")
|
||||
no_info_label.setStyleSheet("color:red;font-weight:bold;")
|
||||
self._propertyLayout.addWidget(no_info_label)
|
||||
return
|
||||
|
||||
info_group = QGroupBox("地形信息")
|
||||
info_layout = QGridLayout()
|
||||
info_layout.addWidget(QLabel("名称:"),0,0)
|
||||
name_label = QLabel(terrain_info.get('name','未知'))
|
||||
info_layout.addWidget(name_label,0,1)
|
||||
|
||||
info_layout.addWidget(QLabel("类型:"),1,0)
|
||||
type_label = QLabel("高度图地形"if terrain_info.get('heightmap') else "平面地形")
|
||||
info_layout.addWidget(type_label,1,1)
|
||||
|
||||
if terrain_info.get('heightmap'):
|
||||
info_layout.addWidget(QLabel("高度图:"),2,0)
|
||||
heightmap_label = QLabel(os.path.basename(terrain_info['heightmap']))
|
||||
heightmap_label.setWordWrap(True)
|
||||
info_layout.addWidget(heightmap_label,2,1)
|
||||
info_group.setLayout(info_layout)
|
||||
self._propertyLayout.addWidget(info_group)
|
||||
|
||||
#变换属性
|
||||
self._updateTerrainTransformPanel(terrain_node)
|
||||
#地形编辑控制面板
|
||||
self._createTerrainEditPanel(terrain_info)
|
||||
#材质属性
|
||||
self._updateTerrainMaterialPanel(terrain_node,terrain_info)
|
||||
|
||||
#删除按钮
|
||||
delete_btn = QPushButton("删除地形")
|
||||
delete_btn.setStyleSheet("""
|
||||
QPushButton{
|
||||
background-color:#ff4444;
|
||||
color:white;
|
||||
border:none;
|
||||
padding:8px;
|
||||
border-radius:4px;
|
||||
margin-top:10px
|
||||
}
|
||||
QPushButton:hover{
|
||||
background-color:#ff6666;
|
||||
}
|
||||
""")
|
||||
delete_btn.clicked.connect(lambda:self._deleteTerrain(terrain_info,item))
|
||||
self._propertyLayout.addWidget(delete_btn)
|
||||
except Exception as e:
|
||||
print(f"显示地形属性时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _updateTerrainTransformPanel(self,terrain_node):
|
||||
"""更新地形变化属性面板"""
|
||||
try:
|
||||
transform_group = QGroupBox("变换 Transform")
|
||||
transform_layout = QGridLayout()
|
||||
|
||||
pos = terrain_node.getPos()
|
||||
scale = terrain_node.getScale()
|
||||
|
||||
transform_layout.addWidget(QLabel("位置"),0,0)
|
||||
|
||||
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)
|
||||
|
||||
self.pos_x = QDoubleSpinBox()
|
||||
self.pos_y = QDoubleSpinBox()
|
||||
self.pos_z = QDoubleSpinBox()
|
||||
|
||||
for pos_widget in [self.pos_x,self.pos_y,self.pos_z]:
|
||||
pos_widget.setRange(-1000000.0,1000000.0)
|
||||
self.pos_x.setValue(pos.getX())
|
||||
self.pos_y.setValue(pos.getY())
|
||||
self.pos_z.setValue(pos.getZ())
|
||||
|
||||
def updateXPosition(value):
|
||||
terrain_node.setX(value)
|
||||
self.refreshModelValues(terrain_node)
|
||||
self.pos_x.valueChanged.connect(updateXPosition)
|
||||
|
||||
def updateYPosition(value):
|
||||
terrain_node.setY(value)
|
||||
self.refreshModelValues(terrain_node)
|
||||
self.pos_y.valueChanged.connect(updateYPosition)
|
||||
|
||||
def updateZPosition(value):
|
||||
terrain_node.setZ(value)
|
||||
self.refreshModelValues(terrain_node)
|
||||
self.pos_z.valueChanged.connect(updateZPosition)
|
||||
|
||||
transform_layout.addWidget(self.pos_x,1,1)
|
||||
transform_layout.addWidget(self.pos_y,1,2)
|
||||
transform_layout.addWidget(self.pos_z,1,3)
|
||||
|
||||
transform_layout.addWidget(QLabel("旋转"),2,0)
|
||||
self.rot_h = QDoubleSpinBox()
|
||||
self.rot_p = QDoubleSpinBox()
|
||||
self.rot_r = QDoubleSpinBox()
|
||||
|
||||
for rot_widget in [self.rot_h,self.rot_p,self.rot_r]:
|
||||
rot_widget.setRange(-360,360)
|
||||
|
||||
self.rot_h.setValue(terrain_node.getH())
|
||||
self.rot_p.setValue(terrain_node.getP())
|
||||
self.rot_r.setValue(terrain_node.getR())
|
||||
|
||||
self.rot_h.valueChanged.connect(lambda v:terrain_node.setH(v))
|
||||
self.rot_p.valueChanged.connect(lambda v:terrain_node.setP(v))
|
||||
self.rot_r.valueChanged.connect(lambda v:terrain_node.setR(v))
|
||||
|
||||
h_label = QLabel("H")
|
||||
p_label = QLabel("P")
|
||||
r_label = QLabel("R")
|
||||
h_label.setAlignment(Qt.AlignCenter)
|
||||
p_label.setAlignment(Qt.AlignCenter)
|
||||
r_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(h_label,2,1)
|
||||
transform_layout.addWidget(p_label,2,2)
|
||||
transform_layout.addWidget(r_label,2,3)
|
||||
transform_layout.addWidget(self.rot_h,3,1)
|
||||
transform_layout.addWidget(self.rot_p,3,2)
|
||||
transform_layout.addWidget(self.rot_r,3,3)
|
||||
|
||||
transform_layout.addWidget(QLabel("缩放"),4,0)
|
||||
self.scale_x = QDoubleSpinBox()
|
||||
self.scale_y = QDoubleSpinBox()
|
||||
self.scale_z = QDoubleSpinBox()
|
||||
|
||||
for scale_widget in [self.scale_x,self.scale_y,self.scale_z]:
|
||||
scale_widget.setRange(-1000,1000)
|
||||
scale_widget.setSingleStep(0.1)
|
||||
self.scale_x.setValue(scale.getX())
|
||||
self.scale_y.setValue(scale.getY())
|
||||
self.scale_z.setValue(scale.getZ())
|
||||
|
||||
self.scale_x.valueChanged.connect(lambda v:terrain_node.setScaleX(v))
|
||||
self.scale_y.valueChanged.connect(lambda v:terrain_node.setScaleY(v))
|
||||
self.scale_z.valueChanged.connect(lambda v:terrain_node.setScaleZ(v))
|
||||
|
||||
x_label2 = QLabel("X")
|
||||
y_label2 = QLabel("Y")
|
||||
z_label2 = QLabel("Z")
|
||||
x_label2.setAlignment(Qt.AlignCenter)
|
||||
y_label2.setAlignment(Qt.AlignCenter)
|
||||
z_label2.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(x_label2,4,1)
|
||||
transform_layout.addWidget(y_label2,4,2)
|
||||
transform_layout.addWidget(z_label2,4,3)
|
||||
transform_layout.addWidget(self.scale_x,5,1)
|
||||
transform_layout.addWidget(self.scale_y,5,2)
|
||||
transform_layout.addWidget(self.scale_z,5,3)
|
||||
|
||||
transform_group.setLayout(transform_layout)
|
||||
self._propertyLayout.addWidget(transform_group)
|
||||
except Exception as e:
|
||||
print(f"更新地形变换面板时出错: {e}")
|
||||
|
||||
def _createTerrainEditPanel(self, terrain_info):
|
||||
try:
|
||||
edit_group = QGroupBox("地形编辑")
|
||||
edit_layout = QGridLayout()
|
||||
|
||||
# 编辑半径
|
||||
edit_layout.addWidget(QLabel("编辑半径:"), 0, 0)
|
||||
self.terrain_radius_spin = QDoubleSpinBox()
|
||||
self.terrain_radius_spin.setRange(0.1, 50.0)
|
||||
self.terrain_radius_spin.setSingleStep(0.5)
|
||||
self.terrain_radius_spin.setValue(getattr(self.world, 'terrain_edit_radius', 3.0))
|
||||
self.terrain_radius_spin.valueChanged.connect(self._onTerrainRadiusChanged)
|
||||
edit_layout.addWidget(self.terrain_radius_spin, 0, 1)
|
||||
|
||||
# 编辑强度
|
||||
edit_layout.addWidget(QLabel("编辑强度:"), 1, 0)
|
||||
self.terrain_strength_spin = QDoubleSpinBox()
|
||||
self.terrain_strength_spin.setRange(0.01, 50)
|
||||
self.terrain_strength_spin.setSingleStep(0.1)
|
||||
self.terrain_strength_spin.setValue(getattr(self.world, 'terrain_edit_strength', 0.3))
|
||||
self.terrain_strength_spin.valueChanged.connect(self._onTerrainStrengthChanged)
|
||||
edit_layout.addWidget(self.terrain_strength_spin, 1, 1)
|
||||
|
||||
edit_layout.addWidget(QLabel("操作类型:"), 2, 0)
|
||||
self.terrain_operation_combo = QComboBox()
|
||||
self.terrain_operation_combo.addItems(["升高地形", "降低地形", "平坦化"])
|
||||
current_op = getattr(self.world, 'terrain_edit_operation', "add")
|
||||
if current_op == "add":
|
||||
self.terrain_operation_combo.setCurrentText("升高地形")
|
||||
elif current_op == "subtract":
|
||||
self.terrain_operation_combo.setCurrentText("降低地形")
|
||||
else:
|
||||
self.terrain_operation_combo.setCurrentText("平坦化")
|
||||
self.terrain_operation_combo.currentTextChanged.connect(self._onTerrainOperationChanged)
|
||||
edit_layout.addWidget(self.terrain_operation_combo, 2, 1)
|
||||
|
||||
help_label = QLabel("在地形编辑模式下,使用鼠标左键升高地形,右键降低地形")
|
||||
help_label.setWordWrap(True)
|
||||
help_label.setStyleSheet("font-size:10px;color:gray;")
|
||||
edit_layout.addWidget(help_label, 3, 0, 1, 2)
|
||||
|
||||
edit_group.setLayout(edit_layout)
|
||||
self._propertyLayout.addWidget(edit_group)
|
||||
|
||||
# 确保初始值被正确设置到 world 对象上
|
||||
self._onTerrainRadiusChanged(self.terrain_radius_spin.value())
|
||||
self._onTerrainStrengthChanged(self.terrain_strength_spin.value())
|
||||
self._onTerrainOperationChanged(self.terrain_operation_combo.currentText())
|
||||
except Exception as e:
|
||||
print(f"创建地形编辑面板时出错: {e}")
|
||||
|
||||
def _onTerrainRadiusChanged(self,value):
|
||||
"""地形编辑半径改变"""
|
||||
if hasattr(self.world,'terrain_edit_radius'):
|
||||
self.world.terrain_edit_radius = float(value)
|
||||
|
||||
def _onTerrainStrengthChanged(self,value):
|
||||
"""地形编辑强度改变"""
|
||||
if hasattr(self.world,'terrain_edit_strength'):
|
||||
self.world.terrain_edit_strength = float(value)
|
||||
|
||||
def _onTerrainOperationChanged(self, text):
|
||||
"""地形编辑操作类型改变"""
|
||||
operation_map = {
|
||||
"升高地形": "add",
|
||||
"降低地形": "subtract",
|
||||
"平坦化": "set"
|
||||
}
|
||||
if hasattr(self.world, 'terrain_edit_operation'):
|
||||
# 正确设置地形编辑操作属性
|
||||
self.world.terrain_edit_operation = operation_map.get(text, "add")
|
||||
print(f"地形编辑操作已设置为: {self.world.terrain_edit_operation}")
|
||||
|
||||
def _updateTerrainMaterialPanel(self,terrain_node,terrain_info):
|
||||
"""更新地形材质属性面板"""
|
||||
try:
|
||||
material_group = QGroupBox("材质属性")
|
||||
material_layout = QGridLayout()
|
||||
#颜色设置
|
||||
material_layout.addWidget(QLabel("颜色:"),0,0)
|
||||
color_button = QPushButton("选择颜色")
|
||||
color_button.clicked.connect(lambda:self._selectTerrainColor(terrain_info))
|
||||
#纹理设置
|
||||
material_layout.addWidget(QLabel("纹理:"),1,0)
|
||||
texture_button = QPushButton("选择纹理")
|
||||
texture_button.clicked.connect(lambda :self._selectTerrainTexture(terrain_info))
|
||||
material_layout.addWidget(texture_button,1,1)
|
||||
|
||||
material_group.setLayout(material_layout)
|
||||
self._propertyLayout.addWidget(material_group)
|
||||
except Exception as e:
|
||||
print(f"更新材质面板时出错{e}")
|
||||
|
||||
def _selectTerrainColor(self,terrain_info):
|
||||
try:
|
||||
from PyQt5.QtWidgets import QColorDialog
|
||||
from PyQt5.QtGui import QColor
|
||||
|
||||
current_color = QColor(255,255,255)
|
||||
|
||||
color = QColorDialog.getColor(current_color,None,"选择地形颜色")
|
||||
if color.isValid():
|
||||
r,g,b = color.red()/255.0,color.green()/255.0,color.blue()/255.0
|
||||
if hasattr(self.world,'terrain_manager'):
|
||||
self.world.terrain_manager.setTerrainColor(terrain_info,(r,g,b))
|
||||
except Exception as e:
|
||||
print(f"选择地形颜色时出错: {e}")
|
||||
|
||||
def _selectTerrainTexture(self,terrain_info):
|
||||
"""选择地形纹理"""
|
||||
try:
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
file_path,_ = QFileDialog.getOpenFileName(
|
||||
None,
|
||||
"选择地形纹理",
|
||||
"",
|
||||
"图像文件(*.png *.jpg *.jpeg *.bmp *.tga *.dds)"
|
||||
)
|
||||
|
||||
if file_path and os.path.exists(file_path):
|
||||
if hasattr(self.world,'terrain_manager'):
|
||||
success = self.world.terrain_manager.setTerrainTexture(terrain_info,file_path)
|
||||
if success:
|
||||
print(f"地形纹理已应用{file_path}")
|
||||
else:
|
||||
print(f"应用地形纹理失败")
|
||||
except Exception as e:
|
||||
print(f"选择地形纹理时出错: {e}")
|
||||
|
||||
def _deleteTerrain(self, terrain_info, item):
|
||||
"""删除地形"""
|
||||
try:
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
reply = QMessageBox.question(
|
||||
None,
|
||||
'确认删除',
|
||||
f'确定要删除地形 "{terrain_info.get("name", "未知地形")}" 吗?',
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
# 调用 interface_manager 中已经实现的 deleteNode 方法
|
||||
if hasattr(self.world, 'interface_manager') and self.world.interface_manager.treeWidget:
|
||||
self.world.interface_manager.deleteNode(terrain_info['node'], item)
|
||||
else:
|
||||
# 如果 interface_manager 不可用,使用原来的删除逻辑
|
||||
if hasattr(self.world, 'terrain_manager'):
|
||||
success = self.world.terrain_manager.deleteTerrain(terrain_info)
|
||||
if success:
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
|
||||
'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
# 清空属性面板
|
||||
self.clearPropertyPanel()
|
||||
|
||||
print(f"✓ 地形已删除: {terrain_info.get('name', '未知')}")
|
||||
else:
|
||||
print("✗ 删除地形失败")
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除地形时出错: {e}")
|
||||
|
||||
def _cleanupAllReferences(self):
|
||||
"""清理所有控件引用"""
|
||||
# 清理变换控件引用
|
||||
|
||||
Loading…
Reference in New Issue
Block a user