1
0
forked from Rowland/EG

addRender #22

Merged
Hector merged 2 commits from addRender into main 2025-08-28 07:52:52 +00:00
7 changed files with 715 additions and 297 deletions

View File

@ -264,6 +264,154 @@ class EventHandler:
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.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): def mousePressEventRight(self,evt):
"""处理鼠标右键按下事件""" """处理鼠标右键按下事件"""
print(f"当前工具: {self.world.currentTool}") print(f"当前工具: {self.world.currentTool}")

View File

@ -1,4 +1,5 @@
# core/terrain_manager.py # core/terrain_manager.py
import time
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
import os import os
@ -22,7 +23,7 @@ class TerrainManager:
try: try:
# 创建GeoMipTerrain对象 # 创建GeoMipTerrain对象
terrain_name = f"terrain_{len(self.terrains)}" terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
terrain = GeoMipTerrain(terrain_name) terrain = GeoMipTerrain(terrain_name)
# 加载高度图 # 加载高度图
@ -33,7 +34,7 @@ class TerrainManager:
print(f"原始图像尺寸: {width}x{height}") 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 # 默认尺寸 target_size = 129 # 默认尺寸
# 选择最接近的尺寸 # 选择最接近的尺寸
@ -48,6 +49,7 @@ class TerrainManager:
# 如果需要,调整图像尺寸 # 如果需要,调整图像尺寸
if width != target_size or height != target_size: if width != target_size or height != target_size:
print(f"调整图像尺寸从 {width}x{height}{target_size}x{target_size}") print(f"调整图像尺寸从 {width}x{height}{target_size}x{target_size}")
# 使用正确的图像缩放方法
resized_image = PNMImage(target_size, target_size) resized_image = PNMImage(target_size, target_size)
resized_image.quickFilterFrom(height_image) resized_image.quickFilterFrom(height_image)
height_image = resized_image height_image = resized_image
@ -56,7 +58,10 @@ class TerrainManager:
terrain.setHeightfield(height_image) terrain.setHeightfield(height_image)
# 设置地形参数 # 设置地形参数
terrain.setBruteforce(False) # 使用LOD terrain.setBruteforce(True) # 使用LOD
terrain.setBlockSize(32)
# terrain.setNearFarThreshold(50.0,200.0)
# 生成地形 # 生成地形
terrain.generate() terrain.generate()
@ -64,21 +69,34 @@ class TerrainManager:
# 获取地形节点 # 获取地形节点
terrain_node = terrain.getRoot() 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.setScale(scale[0], scale[1], scale[2])
# 将地形添加到场景中 # 将地形添加到场景中
terrain_node.reparentTo(self.world.render) terrain_node.reparentTo(self.world.render)
# 为地形节点设置名称,便于在场景树中识别
terrain_node.setName(f"Terrain_{os.path.basename(heightmap_path)}")
from panda3d.core import BitMask32 from panda3d.core import BitMask32
# 设置地形节点的碰撞掩码
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码 terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
# 为地形的所有子节点也设置碰撞掩码
for child in terrain_node.getChildren():
child.setCollideMask(BitMask32.bit(2))
# 添加材质 # 添加材质
self._applyTerrainMaterial(terrain_node) self._applyTerrainMaterial(terrain_node)
terrain_node.setPythonTag("selectable", True)
# 保存地形信息(包括高度图的副本) # 保存地形信息(包括高度图的副本)
terrain_info = { terrain_info = {
'terrain': terrain, 'terrain': terrain,
@ -86,14 +104,18 @@ class TerrainManager:
'heightmap': heightmap_path, 'heightmap': heightmap_path,
'heightfield': height_image, # 保存高度图副本 'heightfield': height_image, # 保存高度图副本
'scale': scale, 'scale': scale,
'name': f"Terrain_{os.path.basename(heightmap_path)}" 'name': node_name
} }
self.terrains.append(terrain_info) self.terrains.append(terrain_info)
# 更新场景树 # 更新场景树(再次检查节点是否有效)
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'): if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
self.world.scene_manager.updateSceneTree() 'updateSceneTree'):
try:
self.world.scene_manager.updateSceneTree()
except Exception as e:
print(f"警告: 更新场景树时出错: {e}")
print(f"✓ 成功从 {heightmap_path} 创建地形") print(f"✓ 成功从 {heightmap_path} 创建地形")
return terrain_info return terrain_info
@ -104,7 +126,7 @@ class TerrainManager:
traceback.print_exc() traceback.print_exc()
return None return None
def createFlatTerrain(self, size=(100, 100), resolution=129): def createFlatTerrain(self, size=(0.3, 0.3), resolution=129):
"""创建平面地形""" """创建平面地形"""
try: try:
# 确保分辨率是2的幂次方加1 (如129, 257, 513等) # 确保分辨率是2的幂次方加1 (如129, 257, 513等)
@ -116,7 +138,7 @@ class TerrainManager:
resolution = closest_res resolution = closest_res
# 创建GeoMipTerrain对象 # 创建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) terrain = GeoMipTerrain(terrain_name)
# 创建一个平面高度图尺寸必须是2^n+1 # 创建一个平面高度图尺寸必须是2^n+1
@ -125,7 +147,7 @@ class TerrainManager:
# 使用正确的方法设置高度图 # 使用正确的方法设置高度图
terrain.setHeightfield(height_image) terrain.setHeightfield(height_image)
terrain.setBruteforce(False) # 使用LOD terrain.setBruteforce(True) # 设置LOD
# 设置地形参数 # 设置地形参数
terrain.setBlockSize(32) # 设置块大小 terrain.setBlockSize(32) # 设置块大小
@ -136,17 +158,26 @@ class TerrainManager:
# 获取地形节点 # 获取地形节点
terrain_node = terrain.getRoot() terrain_node = terrain.getRoot()
# 设置缩放以达到指定大小 if terrain_node.isEmpty():
terrain_node.setScale(size[0], size[1], 50) # Z轴缩放更大以显示地形起伏 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.reparentTo(self.world.render)
# 为地形节点设置名称,便于在场景树中识别
terrain_node.setName(f"FlatTerrain_{len(self.terrains)}")
# 为地形添加碰撞体 # 为地形添加碰撞体
from panda3d.core import BitMask32 from panda3d.core import BitMask32
# 设置地形节点的碰撞掩码
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码 terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
# 为地形的所有子节点也设置碰撞掩码
for child in terrain_node.getChildren():
child.setCollideMask(BitMask32.bit(2))
# 添加材质 # 添加材质
self._applyTerrainMaterial(terrain_node) self._applyTerrainMaterial(terrain_node)
@ -158,14 +189,18 @@ class TerrainManager:
'heightmap': None, 'heightmap': None,
'heightfield': height_image, # 保存高度图 'heightfield': height_image, # 保存高度图
'scale': (size[0], size[1], 50), 'scale': (size[0], size[1], 50),
'name': f"FlatTerrain_{len(self.terrains)}" 'name': node_name
} }
self.terrains.append(terrain_info) self.terrains.append(terrain_info)
# 更新场景树 # 更新场景树(再次检查节点是否有效)
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'): if not terrain_node.isEmpty() and hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager,
self.world.scene_manager.updateSceneTree() 'updateSceneTree'):
try:
self.world.scene_manager.updateSceneTree()
except Exception as e:
print(f"警告: 更新场景树时出错: {e}")
print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}") print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}")
return terrain_info return terrain_info
@ -190,7 +225,7 @@ class TerrainManager:
terrain_node.setMaterial(material) terrain_node.setMaterial(material)
# 启用材质 # 启用材质
terrain_node.setAttrib(ColorAttrib.makeDefaultColor()) terrain_node.setAttrib(ColorAttrib.makeDefault())
print("✓ 地形材质已应用") print("✓ 地形材质已应用")
except Exception as e: except Exception as e:
@ -227,6 +262,7 @@ class TerrainManager:
try: try:
terrain = terrain_info['terrain'] terrain = terrain_info['terrain']
terrain_node = terrain_info['node'] # 获取地形节点
# 直接使用我们保存的高度图数据 # 直接使用我们保存的高度图数据
if 'heightfield' not in terrain_info: if 'heightfield' not in terrain_info:
@ -242,19 +278,58 @@ class TerrainManager:
width = heightmap.getXSize() width = heightmap.getXSize()
height = heightmap.getYSize() height = heightmap.getYSize()
# 转换坐标到高度图空间 # 获取地形节点信息
center_x = int((x / terrain_info['scale'][0]) * width + width / 2) terrain_pos = terrain_node.getPos()
center_y = int((y / terrain_info['scale'][1]) * height + height / 2) 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 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 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 target_x = center_x + dx
@ -267,17 +342,15 @@ class TerrainManager:
# 根据操作类型修改高度 # 根据操作类型修改高度
if operation == "add": 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": 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": elif operation == "set":
new_height = strength # 平坦化操作,设置为中等高度
new_height = 0.5
else: else:
new_height = current_height new_height = current_height
# 限制高度范围
new_height = max(0.0, min(1.0, new_height))
# 设置新高度 # 设置新高度
heightmap.setRed(target_x, target_y, new_height) heightmap.setRed(target_x, target_y, new_height)
heightmap.setGreen(target_x, target_y, new_height) heightmap.setGreen(target_x, target_y, new_height)
@ -289,7 +362,24 @@ class TerrainManager:
# 重新设置高度图并生成地形 # 重新设置高度图并生成地形
terrain.setHeightfield(heightmap) terrain.setHeightfield(heightmap)
terrain.generate() 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'): 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: if terrain_info in self.terrains:
# 从场景中移除地形节点 # 从场景中移除地形节点
terrain_node = terrain_info['node'] terrain_node = terrain_info['node']
if terrain_node: if terrain_node and not terrain_node.isEmpty():
terrain_node.removeNode() terrain_node.removeNode()
# 从列表中移除 # 从列表中移除
@ -319,6 +409,10 @@ class TerrainManager:
self.world.scene_manager.updateSceneTree() self.world.scene_manager.updateSceneTree()
print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}") print(f"✓ 地形已删除: {terrain_info.get('name', 'Unknown')}")
import gc
gc.collect()
return True return True
return False return False

30
main.py
View File

@ -87,6 +87,10 @@ class MyWorld(CoreWorld):
#self.material_editor = None #self.material_editor = None
self.terrain_manager = TerrainManager(self) self.terrain_manager = TerrainManager(self)
self.terrain_edit_radius = 3.0
self.terrain_edit_strength=0.3
self.terrain_edit_operation = "add"
print("✓ MyWorld 初始化完成") print("✓ MyWorld 初始化完成")
# ==================== 兼容性属性 ==================== # ==================== 兼容性属性 ====================
@ -717,14 +721,32 @@ class MyWorld(CoreWorld):
"""更新所有地形的LOD""" """更新所有地形的LOD"""
return self.terrain_manager.updateTerrain() 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): def getTerrainHeight(self, terrain_info, x, y):
"""获取地形上指定点的高度""" """获取地形上指定点的高度"""
return self.terrain_manager.getTerrainHeight(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模块的对应功能 # 以下函数代理到project_manager模块的对应功能

View File

@ -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())

View File

@ -137,6 +137,22 @@ class InterfaceManager:
def deleteNode(self, nodePath, item): def deleteNode(self, nodePath, item):
"""删除节点""" """删除节点"""
try: 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) 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=="" 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): def addNodeToTree(node,parentItem,force = False):
if node.isEmpty():
return
if not force and should_skip(node): if not force and should_skip(node):
return return
nodeItem = QTreeWidgetItem(parentItem,[node.getName()]) nodeItem = QTreeWidgetItem(parentItem,[node.getName()])
@ -216,7 +235,8 @@ class InterfaceManager:
addNodeToTree(child,nodeItem,force = False) addNodeToTree(child,nodeItem,force = False)
for model in self.world.models: for model in self.world.models:
addNodeToTree(model, sceneRoot,force=True) if not model.isEmpty():
addNodeToTree(model, sceneRoot,force=True)
# 添加所有GUI元素 # 添加所有GUI元素
for gui in self.world.gui_elements: for gui in self.world.gui_elements:
@ -229,18 +249,26 @@ class InterfaceManager:
else: else:
# 跳过非GUI节点如QDockWidget等Qt对象 # 跳过非GUI节点如QDockWidget等Qt对象
continue 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: 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 #添加 Cesium tilesets
if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'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): for i , tileset_info in enumerate(self.world.scene_manager.tilesets):
tileset_node = tileset_info['node'] tileset_node = tileset_info['node']
tileset_url = tileset_info['url'] if not tileset_node.isEmpty():
tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"]) tileset_url = tileset_info['url']
tileset_item.setData(0,Qt.UserRole,tileset_node) tileset_item = QTreeWidgetItem(sceneRoot,[f"Cesium Tileset {i}"])
addNodeToTree(tileset_node,tileset_item,force=True) 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: if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains:
for terrain_info in 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) 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() #self.treeWidget.expandAll()

View File

@ -1005,8 +1005,8 @@ class MainWindow(QMainWindow):
width_layout = QHBoxLayout() width_layout = QHBoxLayout()
width_layout.addWidget(QLabel("宽度:")) width_layout.addWidget(QLabel("宽度:"))
width_spin = QDoubleSpinBox() width_spin = QDoubleSpinBox()
width_spin.setRange(1,10000) width_spin.setRange(0,10000)
width_spin.setValue(100) width_spin.setValue(0.3)
width_layout.addWidget(width_spin) width_layout.addWidget(width_spin)
layout.addLayout(width_layout) layout.addLayout(width_layout)
@ -1014,8 +1014,8 @@ class MainWindow(QMainWindow):
height_layout = QHBoxLayout() height_layout = QHBoxLayout()
height_layout.addWidget(QLabel("高度:")) height_layout.addWidget(QLabel("高度:"))
height_spin = QDoubleSpinBox() height_spin = QDoubleSpinBox()
height_spin.setRange(1, 10000) height_spin.setRange(0, 10000)
height_spin.setValue(100) height_spin.setValue(0.3)
height_layout.addWidget(height_spin) height_layout.addWidget(height_spin)
layout.addLayout(height_layout) layout.addLayout(height_layout)

View File

@ -1,5 +1,6 @@
import os
from collections import deque from collections import deque
from traceback import print_exc from traceback import print_exc, print_last
from types import new_class from types import new_class
from typing import Hashable from typing import Hashable
@ -9,6 +10,7 @@ from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from deploy_libs.unicodedata import normalize from deploy_libs.unicodedata import normalize
from direct.actor.Actor import Actor from direct.actor.Actor import Actor
from idna import check_label
from jinja2.compiler import has_safe_repr from jinja2.compiler import has_safe_repr
from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib, PartGroup
from scene import util from scene import util
@ -23,6 +25,14 @@ class PropertyPanelManager:
self._propertyLayout = None self._propertyLayout = None
self._actor_cache={} 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 = """ self.compact_style = """
QDoubleSpinBox { QDoubleSpinBox {
@ -143,7 +153,9 @@ class PropertyPanelManager:
# 获取节点对象 # 获取节点对象
model = item.data(0, Qt.UserRole) 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) self._showCesiumTilesetProperties(model,item)
elif model and hasattr(model, 'getTag') and model.getTag("gui_type"): elif model and hasattr(model, 'getTag') and model.getTag("gui_type"):
self.updateGUIPropertyPanel(model) self.updateGUIPropertyPanel(model)
@ -161,6 +173,362 @@ class PropertyPanelManager:
if propertyWidget: if propertyWidget:
propertyWidget.update() 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): def _cleanupAllReferences(self):
"""清理所有控件引用""" """清理所有控件引用"""
# 清理变换控件引用 # 清理变换控件引用