1.修改Bug(20250910)
This commit is contained in:
parent
95395bdb05
commit
26d10f4117
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -3,5 +3,5 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (PythonProject)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (EG)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
@ -90,6 +90,10 @@ class LightManager(RPObject):
|
||||
|
||||
def remove_light(self, light):
|
||||
""" Removes a light """
|
||||
print(f'333333333333333333333333333333,{light.casts_shadows}')
|
||||
# from RenderPipelineFile.rpcore.pynative.internal_light_manager import InternalLightManager
|
||||
# inter = InternalLightManager()
|
||||
# inter.remove_light(light)
|
||||
self.internal_mgr.remove_light(light)
|
||||
self.pta_max_light_index[0] = self.internal_mgr.max_light_index
|
||||
|
||||
|
||||
@ -73,6 +73,7 @@ native_module = None
|
||||
|
||||
# If the module was built, use it, otherwise use the python wrappers
|
||||
if NATIVE_CXX_LOADED:
|
||||
print(f'12121212121212121212121212')
|
||||
try:
|
||||
from panda3d import _rplight as _native_module # pylint: disable=wrong-import-position
|
||||
RPObject.global_debug("CORE", "Using panda3d-supplied core module")
|
||||
@ -80,6 +81,7 @@ if NATIVE_CXX_LOADED:
|
||||
RPObject.global_debug("CORE", "Using native core module")
|
||||
from rpcore.native import native_ as _native_module # pylint: disable=wrong-import-position
|
||||
else:
|
||||
print(f'343434343434343434343434343')
|
||||
from rpcore import pynative as _native_module # pylint: disable=wrong-import-position
|
||||
RPObject.global_debug("CORE", "Using simulated python-wrapper module")
|
||||
|
||||
|
||||
@ -161,7 +161,12 @@ public:
|
||||
|
||||
// Update maximum index
|
||||
if (slot == _max_index) {
|
||||
while (_max_index >= 0 && !_data[_max_index--]);
|
||||
while (_max_index >= 0 && !_data[_max_index--]);
|
||||
// 正确的修复代码
|
||||
// while (_max_index >= 0 && _data[_max_index] == NULL) {
|
||||
// _max_index--;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -116,6 +116,25 @@ class InternalLightManager(object):
|
||||
source.set_slot(slot)
|
||||
|
||||
def remove_light(self, light):
|
||||
print(f'44444444444444444444444444')
|
||||
print("\n" + "=" * 50)
|
||||
print(f"DEBUG: Entering remove_light for light object: {light.casts_shadows}")
|
||||
if light:
|
||||
print(f" - Light's Slot: {light.get_slot() if light.has_slot() else 'No Slot'}")
|
||||
print(f" - Does it cast shadows? light.get_casts_shadows() -> {light.get_casts_shadows()}")
|
||||
else:
|
||||
print(" - Light object is None!")
|
||||
|
||||
print("\n --- State of Light System BEFORE removal ---")
|
||||
if hasattr(self, '_lights') and hasattr(self._lights, '_data'):
|
||||
# 打印出当前所有灯光对象,看看有没有异常
|
||||
print(f" - Light Data Array (_data):")
|
||||
for i, l_obj in enumerate(self._lights._data):
|
||||
print(f" [{i}]: {l_obj}")
|
||||
|
||||
print(f"\n - Max Index (_max_index): {self._lights._max_index}")
|
||||
print(f" - Num Entries (_num_entries): {self._lights._num_entries}")
|
||||
print("=" * 50 + "\n")
|
||||
assert light is not None
|
||||
if not light.has_slot():
|
||||
print("ERROR: Could not detach light, light was not attached!")
|
||||
@ -132,6 +151,9 @@ class InternalLightManager(object):
|
||||
# 关键修复:先保存第一个source的slot,再清理
|
||||
first_source = light.get_shadow_source(0)
|
||||
first_source_slot = first_source.get_slot() # 保存slot值
|
||||
|
||||
# --- 在这里加上打印语句 ---
|
||||
print(f"DEBUG: Removing shadow sources. Start Slot: {first_source_slot}, Count: {num_sources}")
|
||||
|
||||
# 先发送GPU移除命令(在清理之前)
|
||||
cmd_remove = GPUCommand(GPUCommand.CMD_remove_sources)
|
||||
|
||||
@ -208,6 +208,7 @@ class RenderPipeline(RPObject):
|
||||
def remove_light(self, light):
|
||||
""" Removes a previously attached light, check out the LightManager
|
||||
remove_light documentation for further information. """
|
||||
print(f'222222222222222222222222222,{light.casts_shadows}')
|
||||
self.light_mgr.remove_light(light)
|
||||
|
||||
def load_ies_profile(self, filename):
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
# core/terrain_manager.py
|
||||
import time
|
||||
import urllib
|
||||
|
||||
from panda3d.core import GeoMipTerrain, PNMImage, Texture, Vec3, NodePath
|
||||
from panda3d.core import Filename, Material, ColorAttrib, AmbientLight, DirectionalLight
|
||||
import os
|
||||
|
||||
from scene import util
|
||||
|
||||
|
||||
class TerrainManager:
|
||||
"""地形管理类"""
|
||||
@ -31,106 +35,148 @@ class TerrainManager:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
print(f"🔆 开始创建高度图地形")
|
||||
|
||||
# 加载高度图
|
||||
height_image = PNMImage(Filename.fromOsSpecific(heightmap_path))
|
||||
|
||||
# 检查并调整图像尺寸为2的幂次方加1
|
||||
width, height = height_image.getXSize(), height_image.getYSize()
|
||||
print(f"原始图像尺寸: {width}x{height}")
|
||||
|
||||
# 找到最接近的有效尺寸
|
||||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025, 2049]
|
||||
target_size = 129 # 默认尺寸
|
||||
|
||||
# 选择最接近的尺寸
|
||||
max_dim = max(width, height)
|
||||
for size in valid_sizes:
|
||||
if size >= max_dim:
|
||||
target_size = size
|
||||
break
|
||||
else:
|
||||
target_size = valid_sizes[-1] # 使用最大尺寸
|
||||
|
||||
# 如果需要,调整图像尺寸
|
||||
if width != target_size or height != target_size:
|
||||
print(f"调整图像尺寸从 {width}x{height} 到 {target_size}x{target_size}")
|
||||
# 使用正确的图像缩放方法
|
||||
resized_image = PNMImage(target_size, target_size)
|
||||
resized_image.quickFilterFrom(height_image)
|
||||
height_image = resized_image
|
||||
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBruteforce(True) # 使用LOD
|
||||
|
||||
terrain.setBlockSize(32)
|
||||
# terrain.setNearFarThreshold(50.0,200.0)
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的地形节点")
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
|
||||
terrain_node.setName(node_name)
|
||||
# 获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
center_offset = (target_size - 1) / 2
|
||||
terrain_node.setPos(-center_offset * scale[0], -center_offset * scale[1], -5)
|
||||
created_terrains = []
|
||||
try:
|
||||
parent_item, parent_node = target_parents[0]
|
||||
|
||||
# 设置缩放
|
||||
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
# 加载高度图
|
||||
height_image = PNMImage(Filename.fromOsSpecific(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))
|
||||
# 检查并调整图像尺寸为2的幂次方加1
|
||||
width, height = height_image.getXSize(), height_image.getYSize()
|
||||
print(f"原始图像尺寸: {width}x{height}")
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
# 找到最接近的有效尺寸
|
||||
valid_sizes = [17, 33, 65, 129, 257, 513, 1025, 2049]
|
||||
target_size = 129 # 默认尺寸
|
||||
|
||||
terrain_node.setPythonTag("selectable", True)
|
||||
# 选择最接近的尺寸
|
||||
max_dim = max(width, height)
|
||||
for size in valid_sizes:
|
||||
if size >= max_dim:
|
||||
target_size = size
|
||||
break
|
||||
else:
|
||||
target_size = valid_sizes[-1] # 使用最大尺寸
|
||||
|
||||
# 保存地形信息(包括高度图的副本)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': heightmap_path,
|
||||
'heightfield': height_image, # 保存高度图副本
|
||||
'scale': scale,
|
||||
'name': node_name
|
||||
}
|
||||
# 如果需要,调整图像尺寸
|
||||
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
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
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}")
|
||||
# 设置地形参数
|
||||
terrain.setBruteforce(True) # 使用LOD
|
||||
|
||||
print(f"✓ 成功从 {heightmap_path} 创建地形")
|
||||
return terrain_info
|
||||
terrain.setBlockSize(32)
|
||||
# terrain.setNearFarThreshold(50.0,200.0)
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (target_size - 1) / 2
|
||||
terrain_node.setPos(-center_offset * scale[0], -center_offset * scale[1], -5)
|
||||
|
||||
# 设置缩放
|
||||
terrain_node.setScale(scale[0], scale[1], scale[2])
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(parent_node)
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
terrain_node.setPythonTag("selectable", True)
|
||||
|
||||
# 保存地形信息(包括高度图的副本)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': heightmap_path,
|
||||
'heightfield': height_image, # 保存高度图副本
|
||||
'scale': scale,
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建高度图地形: {terrain_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||||
if qt_item:
|
||||
created_terrains.append((terrain_node, qt_item))
|
||||
else:
|
||||
created_terrains.append((terrain_node, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建高度图地形: {str(e)}")
|
||||
return None
|
||||
|
||||
# 处理创建结果
|
||||
if not created_terrains:
|
||||
print("❌ 没有成功创建任何高度图地形")
|
||||
return None
|
||||
|
||||
# 选中最后创建的光源
|
||||
if created_terrains:
|
||||
last_light_np, last_qt_item = created_terrains[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
# 更新选择和属性面板
|
||||
tree_widget.update_selection_and_properties(last_light_np, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_terrains)} 个高度图地形")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_terrains) == 1:
|
||||
return created_terrains[0][0] # 单个光源返回NodePath
|
||||
else:
|
||||
return [light_np for light_np, _ in created_terrains] # 多个光源返回列表
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建地形时出错: {e}")
|
||||
print(f"❌ 创建高度图地形过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
@ -138,84 +184,126 @@ class TerrainManager:
|
||||
def createFlatTerrain(self, size=(0.3, 0.3), resolution=129):
|
||||
"""创建平面地形"""
|
||||
try:
|
||||
# 确保分辨率是2的幂次方加1 (如129, 257, 513等)
|
||||
valid_resolutions = [17, 33, 65, 129, 257, 513, 1025]
|
||||
if resolution not in valid_resolutions:
|
||||
# 找到最接近的有效分辨率
|
||||
closest_res = min(valid_resolutions, key=lambda x: abs(x - resolution))
|
||||
print(f"警告: 分辨率 {resolution} 不是有效的地形分辨率,使用 {closest_res}")
|
||||
resolution = closest_res
|
||||
print(f"🔆 开始创建平面地形")
|
||||
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"flat_terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 创建一个平面高度图,尺寸必须是2^n+1
|
||||
height_image = PNMImage(resolution, resolution)
|
||||
height_image.fill(0.5) # 设置为中等高度 (0.0-1.0范围)
|
||||
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
terrain.setBruteforce(True) # 设置LOD
|
||||
|
||||
# 设置地形参数
|
||||
terrain.setBlockSize(32) # 设置块大小
|
||||
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的平面地形节点")
|
||||
# 获取树形控件
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain_node.setName(node_name)
|
||||
# 获取目标父节点列表
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点")
|
||||
return None
|
||||
|
||||
center_offset = (resolution - 1) / 2
|
||||
terrain_node.setPos(-center_offset * size[0], -center_offset * size[1], 0)
|
||||
created_terrains = []
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(self.world.render)
|
||||
try:
|
||||
parent_item, parent_node = target_parents[0]
|
||||
# 确保分辨率是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
|
||||
|
||||
# 为地形添加碰撞体
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
# 创建GeoMipTerrain对象
|
||||
terrain_name = f"flat_terrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain = GeoMipTerrain(terrain_name)
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
# 创建一个平面高度图,尺寸必须是2^n+1
|
||||
height_image = PNMImage(resolution, resolution)
|
||||
height_image.fill(0.5) # 设置为中等高度 (0.0-1.0范围)
|
||||
|
||||
# 保存地形信息(包括高度图)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': None,
|
||||
'heightfield': height_image, # 保存高度图
|
||||
'scale': (size[0], size[1], 50),
|
||||
'name': node_name
|
||||
}
|
||||
# 使用正确的方法设置高度图
|
||||
terrain.setHeightfield(height_image)
|
||||
terrain.setBruteforce(True) # 设置LOD
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
# 设置地形参数
|
||||
terrain.setBlockSize(32) # 设置块大小
|
||||
|
||||
# 更新场景树(再次检查节点是否有效)
|
||||
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}")
|
||||
# 生成地形
|
||||
terrain.generate()
|
||||
|
||||
print(f"✓ 成功创建平面地形,大小: {size}, 分辨率: {resolution}")
|
||||
return terrain_info
|
||||
# 获取地形节点
|
||||
terrain_node = terrain.getRoot()
|
||||
|
||||
if terrain_node.isEmpty():
|
||||
print("错误:无法生成有效的平面地形节点")
|
||||
return None
|
||||
|
||||
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
|
||||
terrain_node.setName(node_name)
|
||||
|
||||
center_offset = (resolution - 1) / 2
|
||||
terrain_node.setPos(-center_offset * size[0], -center_offset * size[1], 0)
|
||||
|
||||
# 将地形添加到场景中
|
||||
terrain_node.reparentTo(parent_node)
|
||||
|
||||
# 为地形添加碰撞体
|
||||
from panda3d.core import BitMask32
|
||||
# 设置地形节点的碰撞掩码
|
||||
terrain_node.setCollideMask(BitMask32.bit(2)) # 使用第2位作为地形碰撞掩码
|
||||
# 为地形的所有子节点也设置碰撞掩码
|
||||
for child in terrain_node.getChildren():
|
||||
child.setCollideMask(BitMask32.bit(2))
|
||||
|
||||
# 添加材质
|
||||
self._applyTerrainMaterial(terrain_node)
|
||||
|
||||
# 保存地形信息(包括高度图)
|
||||
terrain_info = {
|
||||
'terrain': terrain,
|
||||
'node': terrain_node,
|
||||
'heightmap': None,
|
||||
'heightfield': height_image, # 保存高度图
|
||||
'scale': (size[0], size[1], 50),
|
||||
'name': node_name
|
||||
}
|
||||
|
||||
self.terrains.append(terrain_info)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 创建平面地形: {terrain_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(terrain_node, parent_item, "TERRAIN_NODE")
|
||||
if qt_item:
|
||||
created_terrains.append((terrain_node, qt_item))
|
||||
else:
|
||||
created_terrains.append((terrain_node, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 创建平面地形: {str(e)}")
|
||||
return None
|
||||
|
||||
# 处理创建结果
|
||||
if not created_terrains:
|
||||
print("❌ 没有成功创建任何平面地形")
|
||||
return None
|
||||
|
||||
# 选中最后创建的光源
|
||||
if created_terrains:
|
||||
last_light_np, last_qt_item = created_terrains[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
# 更新选择和属性面板
|
||||
tree_widget.update_selection_and_properties(last_light_np, last_qt_item)
|
||||
|
||||
print(f"🎉 总共创建了 {len(created_terrains)} 个平面地形")
|
||||
|
||||
# 返回值处理
|
||||
if len(created_terrains) == 1:
|
||||
return created_terrains[0][0] # 单个光源返回NodePath
|
||||
else:
|
||||
return [light_np for light_np, _ in created_terrains] # 多个光源返回列表
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建平面地形时出错: {e}")
|
||||
print(f"❌ 创建平面地形过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
@ -390,10 +478,6 @@ class TerrainManager:
|
||||
print(f"✓ 地形高度已修改: 位置({x}, {y}), 半径{radius}, 操作{operation}")
|
||||
print(f"✓ 重新设置了地形碰撞体")
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
return modified
|
||||
|
||||
except Exception as e:
|
||||
@ -408,14 +492,17 @@ class TerrainManager:
|
||||
# 从场景中移除地形节点
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node and not terrain_node.isEmpty():
|
||||
terrain_node.removeNode()
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
tree_widget.delete_items(tree_widget.selectedItems())
|
||||
# terrain_node.removeNode()
|
||||
#
|
||||
# # 从列表中移除
|
||||
# self.terrains.remove(terrain_info)
|
||||
|
||||
# 从列表中移除
|
||||
self.terrains.remove(terrain_info)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'):
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
# # 更新场景树
|
||||
# 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')}")
|
||||
|
||||
@ -464,6 +551,7 @@ class TerrainManager:
|
||||
if terrain_info in self.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if terrain_node and os.path.exists(texture_path):
|
||||
texture_path = util.normalize_model_path(texture_path)
|
||||
# 加载纹理
|
||||
texture = self.world.loader.loadTexture(texture_path)
|
||||
if texture:
|
||||
|
||||
@ -426,7 +426,11 @@ class GUIManager:
|
||||
cm = CardMaker('gui-2d-image')
|
||||
cm.setFrame(-size, size, -size, size)
|
||||
|
||||
image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||||
# image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||||
if parent_gui_node:
|
||||
image_node = parent_gui_node.attachNewNode(cm.generate())
|
||||
else:
|
||||
image_node = self.world.aspect2d.attachNewNode(cm.generate())
|
||||
image_node.setPos(gui_pos)
|
||||
image_node.setBin('fixed', 0)
|
||||
image_node.setDepthWrite(False)
|
||||
@ -696,7 +700,8 @@ class GUIManager:
|
||||
cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2)
|
||||
|
||||
# 创建3D图像节点
|
||||
image_node = self.world.render.attachNewNode(cm.generate())
|
||||
# image_node = self.world.render.attachNewNode(cm.generate())
|
||||
image_node = parent_node.attachNewNode(cm.generate())
|
||||
image_node.setPos(*pos)
|
||||
|
||||
# 为3D图像创建独立的材质
|
||||
@ -919,7 +924,9 @@ class GUIManager:
|
||||
pass
|
||||
return None
|
||||
|
||||
# 暂无滑块功能
|
||||
def createGUISlider(self, pos=(0, 0, 0), text="滑块", scale=0.3):
|
||||
pass
|
||||
"""创建2D GUI滑块"""
|
||||
from direct.gui.DirectGui import DirectSlider
|
||||
|
||||
@ -954,19 +961,22 @@ class GUIManager:
|
||||
"""删除GUI元素"""
|
||||
try:
|
||||
if gui_element in self.gui_elements:
|
||||
# 移除GUI元素
|
||||
if hasattr(gui_element, 'removeNode'):
|
||||
gui_element.removeNode()
|
||||
elif hasattr(gui_element, 'destroy'):
|
||||
gui_element.destroy()
|
||||
|
||||
# 从列表中移除
|
||||
self.gui_elements.remove(gui_element)
|
||||
# # 移除GUI元素
|
||||
# if hasattr(gui_element, 'removeNode'):
|
||||
# gui_element.removeNode()
|
||||
# elif hasattr(gui_element, 'destroy'):
|
||||
# gui_element.destroy()
|
||||
#
|
||||
# # 从列表中移除
|
||||
# self.gui_elements.remove(gui_element)
|
||||
|
||||
# 更新场景树
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
tree_widget.delete_items(tree_widget.selectedItems())
|
||||
# if hasattr(self.world, 'updateSceneTree'):
|
||||
# self.world.updateSceneTree()
|
||||
|
||||
print(f"删除GUI元素: {gui_element}")
|
||||
return True
|
||||
@ -1617,269 +1627,269 @@ class GUIManager:
|
||||
|
||||
# ==================== GUI属性面板方法 ====================
|
||||
|
||||
def updateGUIPropertyPanel(self, gui_element, layout):
|
||||
"""更新GUI元素属性面板"""
|
||||
gui_type = gui_element.getTag("gui_type")
|
||||
gui_text = gui_element.getTag("gui_text")
|
||||
|
||||
# GUI类型显示
|
||||
typeLabel = QLabel("GUI类型:")
|
||||
typeValue = QLabel(gui_type)
|
||||
typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
||||
layout.addRow(typeLabel, typeValue)
|
||||
|
||||
# 文本属性(如果适用)
|
||||
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||||
textLabel = QLabel("文本:")
|
||||
textEdit = QLineEdit(gui_text or "")
|
||||
|
||||
# 创建一个更新函数来处理文本变化
|
||||
def updateText(text):
|
||||
success = self.editGUIElement(gui_element, "text", text)
|
||||
if success:
|
||||
# 更新场景树显示的名称
|
||||
# 安全地调用updateSceneTree
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
textEdit.textChanged.connect(updateText)
|
||||
layout.addRow(textLabel, textEdit)
|
||||
|
||||
# 位置属性
|
||||
if hasattr(gui_element, 'getPos'):
|
||||
# 根据GUI类型设置组名
|
||||
if gui_type in ["button", "label", "entry", "2d_image"]:
|
||||
transform_group = QGroupBox("变换 Rect Transform")
|
||||
else:
|
||||
transform_group = QGroupBox("变换 Transform")
|
||||
|
||||
transform_layout = QGridLayout()
|
||||
|
||||
pos = gui_element.getPos()
|
||||
|
||||
# 根据GUI类型决定位置编辑方式
|
||||
if gui_type in ["button", "label", "entry","2d_image"]:
|
||||
# 2D GUI组件使用屏幕坐标
|
||||
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||||
logical_z = pos.getZ() / 0.1
|
||||
|
||||
transform_layout.addWidget(QLabel("屏幕位置"), 0, 0)
|
||||
|
||||
x_label = QLabel("X")
|
||||
z_label = QLabel("z")
|
||||
x_label.setAlignment(Qt.Aligncenter)
|
||||
z_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(x_label, 0, 1)
|
||||
transform_layout.addWidget(z_label, 0, 2)
|
||||
|
||||
xPos = QDoubleSpinBox()
|
||||
xPos.setRange(-50, 50)
|
||||
xPos.setValue(logical_x)
|
||||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||||
transform_layout.addWidget(xPos, 1, 1)
|
||||
|
||||
zPos = QDoubleSpinBox()
|
||||
zPos.setRange(-50, 50)
|
||||
zPos.setValue(logical_z)
|
||||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||||
transform_layout.addWidget(zPos, 1, 2)
|
||||
|
||||
# 显示实际屏幕坐标(只读)
|
||||
transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
|
||||
|
||||
actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||||
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||||
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
|
||||
transform_layout.addWidget(actualXLabel, 3, 1)
|
||||
transform_layout.addWidget(actualZLabel, 3, 2)
|
||||
# 添加宽度和高度控件(对于2D图像)
|
||||
if gui_type == "2d_image":
|
||||
|
||||
# 添加排序控制组
|
||||
sort_group = QGroupBox("渲染顺序")
|
||||
sort_layout = QGridLayout()
|
||||
|
||||
# 获取当前的sort值,如果没有设置则默认为0
|
||||
current_sort = int(gui_element.getTag("sort") or "0")
|
||||
|
||||
sort_layout.addWidget(QLabel("层级:"), 0, 0)
|
||||
|
||||
sort_spin = QSpinBox()
|
||||
sort_spin.setRange(-1000, 1000) # 设置合理的范围
|
||||
sort_spin.setValue(current_sort)
|
||||
|
||||
# 创建更新排序的函数
|
||||
def updateSort(value):
|
||||
try:
|
||||
# 设置标签
|
||||
gui_element.setTag("sort", str(value))
|
||||
# 应用sort到节点 - 使用fixed bin和指定的值
|
||||
gui_element.setBin("fixed", value)
|
||||
print(f"✓ 更新2D图像渲染顺序: {value}")
|
||||
except Exception as e:
|
||||
print(f"✗ 更新2D图像渲染顺序失败: {e}")
|
||||
|
||||
sort_spin.valueChanged.connect(updateSort)
|
||||
sort_layout.addWidget(sort_spin, 0, 1)
|
||||
|
||||
# 添加说明标签
|
||||
sort_help = QLabel("(数值越大越靠前)")
|
||||
sort_help.setStyleSheet("font-size: 10px; color: gray;")
|
||||
sort_layout.addWidget(sort_help, 1, 0, 1, 2)
|
||||
|
||||
sort_group.setLayout(sort_layout)
|
||||
layout.addWidget(sort_group)
|
||||
|
||||
scale = gui_element.getScale()
|
||||
width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,
|
||||
(tuple, list)) else scale
|
||||
height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,
|
||||
(tuple, list)) and len(
|
||||
scale) > 1 else scale
|
||||
|
||||
# 宽度控件
|
||||
transform_layout.addWidget(QLabel("宽度"), 4, 0)
|
||||
widthSpinBox = QDoubleSpinBox()
|
||||
widthSpinBox.setRange(0.1, 10)
|
||||
widthSpinBox.setSingleStep(0.1)
|
||||
widthSpinBox.setValue(width)
|
||||
widthSpinBox.valueChanged.connect(
|
||||
lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
transform_layout.addWidget(widthSpinBox, 4, 1)
|
||||
|
||||
# 高度控件
|
||||
transform_layout.addWidget(QLabel("高度"), 4, 2)
|
||||
heightSpinBox = QDoubleSpinBox()
|
||||
heightSpinBox.setRange(0.1, 10)
|
||||
heightSpinBox.setSingleStep(0.1)
|
||||
heightSpinBox.setValue(height)
|
||||
heightSpinBox.valueChanged.connect(
|
||||
lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
transform_layout.addWidget(heightSpinBox, 4, 3)
|
||||
|
||||
else:
|
||||
# 3D GUI组件使用世界坐标
|
||||
transform_layout.addWidget(QLabel("位置"), 0, 0)
|
||||
|
||||
# X, Y, Z 标签居中
|
||||
x_label = QLabel("X")
|
||||
y_label = QLabel("Y")
|
||||
z_label = QLabel("Z")
|
||||
x_label.setAlignment(Qt.AlignCenter)
|
||||
y_label.setAlignment(Qt.AlignCenter)
|
||||
z_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(x_label, 0, 1)
|
||||
transform_layout.addWidget(y_label, 0, 2)
|
||||
transform_layout.addWidget(z_label, 0, 3)
|
||||
|
||||
# 位置数值输入框
|
||||
xPos = QDoubleSpinBox()
|
||||
xPos.setRange(-100, 100)
|
||||
xPos.setValue(pos.getX())
|
||||
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
|
||||
transform_layout.addWidget(xPos, 1, 1)
|
||||
|
||||
yPos = QDoubleSpinBox()
|
||||
yPos.setRange(-100, 100)
|
||||
yPos.setValue(pos.getY())
|
||||
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
|
||||
transform_layout.addWidget(yPos, 1, 2)
|
||||
|
||||
zPos = QDoubleSpinBox()
|
||||
zPos.setRange(-100, 100)
|
||||
zPos.setValue(pos.getZ())
|
||||
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
|
||||
transform_layout.addWidget(zPos, 1, 3)
|
||||
|
||||
# 缩放属性
|
||||
scale = gui_element.getScale()
|
||||
transform_layout.addWidget(QLabel("缩放"), 2, 0)
|
||||
|
||||
# X, Y, Z 缩放标签居中
|
||||
sx_label = QLabel("X")
|
||||
sy_label = QLabel("Y")
|
||||
sz_label = QLabel("Z")
|
||||
sx_label.setAlignment(Qt.AlignCenter)
|
||||
sy_label.setAlignment(Qt.AlignCenter)
|
||||
sz_label.setAlignment(Qt.AlignCenter)
|
||||
|
||||
transform_layout.addWidget(sx_label, 2, 1)
|
||||
transform_layout.addWidget(sy_label, 2, 2)
|
||||
transform_layout.addWidget(sz_label, 2, 3)
|
||||
|
||||
# 缩放数值输入框
|
||||
scale_x = QDoubleSpinBox()
|
||||
scale_x.setRange(0.01, 10)
|
||||
scale_x.setSingleStep(0.1)
|
||||
scale_x.setValue(
|
||||
scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale)
|
||||
scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
transform_layout.addWidget(scale_x, 3, 1)
|
||||
|
||||
scale_y = QDoubleSpinBox()
|
||||
scale_y.setRange(0.01, 10)
|
||||
scale_y.setSingleStep(0.1)
|
||||
scale_y.setValue(
|
||||
scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len(
|
||||
scale) > 1 else scale)
|
||||
scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v))
|
||||
transform_layout.addWidget(scale_y, 3, 2)
|
||||
|
||||
scale_z = QDoubleSpinBox()
|
||||
scale_z.setRange(0.01, 10)
|
||||
scale_z.setSingleStep(0.1)
|
||||
scale_z.setValue(
|
||||
scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len(
|
||||
scale) > 2 else scale)
|
||||
scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
transform_layout.addWidget(scale_z, 3, 3)
|
||||
transform_group.setLayout(transform_layout)
|
||||
self._propertyLayout.addWidget(transform_group)
|
||||
|
||||
# 缩放属性
|
||||
if hasattr(gui_element, 'getScale'):
|
||||
scale = gui_element.getScale()
|
||||
|
||||
scaleSpinBox = QDoubleSpinBox()
|
||||
scaleSpinBox.setRange(0.01, 10)
|
||||
scaleSpinBox.setSingleStep(0.1)
|
||||
scaleSpinBox.setValue(scale.getX())
|
||||
scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v))
|
||||
layout.addRow("缩放:", scaleSpinBox)
|
||||
|
||||
# 颜色属性(针对2D GUI)
|
||||
if gui_type in ["button", "label"]:
|
||||
colorButton = QPushButton("选择颜色")
|
||||
colorButton.clicked.connect(lambda: self.selectGUIColor(gui_element))
|
||||
layout.addRow("背景颜色:", colorButton)
|
||||
|
||||
# 3D特有属性
|
||||
if gui_type in ["3d_text", "virtual_screen"]:
|
||||
# 旋转属性
|
||||
if hasattr(gui_element, 'getHpr'):
|
||||
hpr = gui_element.getHpr()
|
||||
|
||||
hRot = QDoubleSpinBox()
|
||||
hRot.setRange(-180, 180)
|
||||
hRot.setValue(hpr.getX())
|
||||
hRot.valueChanged.connect(lambda v: gui_element.setH(v))
|
||||
layout.addRow("旋转 H:", hRot)
|
||||
|
||||
pRot = QDoubleSpinBox()
|
||||
pRot.setRange(-180, 180)
|
||||
pRot.setValue(hpr.getY())
|
||||
pRot.valueChanged.connect(lambda v: gui_element.setP(v))
|
||||
layout.addRow("旋转 P:", pRot)
|
||||
|
||||
rRot = QDoubleSpinBox()
|
||||
rRot.setRange(-180, 180)
|
||||
rRot.setValue(hpr.getZ())
|
||||
rRot.valueChanged.connect(lambda v: gui_element.setR(v))
|
||||
layout.addRow("旋转 R:", rRot)
|
||||
# def updateGUIPropertyPanel(self, gui_element, layout):
|
||||
# """更新GUI元素属性面板"""
|
||||
# gui_type = gui_element.getTag("gui_type")
|
||||
# gui_text = gui_element.getTag("gui_text")
|
||||
#
|
||||
# # GUI类型显示
|
||||
# typeLabel = QLabel("GUI类型:")
|
||||
# typeValue = QLabel(gui_type)
|
||||
# typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
|
||||
# layout.addRow(typeLabel, typeValue)
|
||||
#
|
||||
# # 文本属性(如果适用)
|
||||
# if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
|
||||
# textLabel = QLabel("文本:")
|
||||
# textEdit = QLineEdit(gui_text or "")
|
||||
#
|
||||
# # 创建一个更新函数来处理文本变化
|
||||
# def updateText(text):
|
||||
# success = self.editGUIElement(gui_element, "text", text)
|
||||
# if success:
|
||||
# # 更新场景树显示的名称
|
||||
# # 安全地调用updateSceneTree
|
||||
# if hasattr(self.world, 'updateSceneTree'):
|
||||
# self.world.updateSceneTree()
|
||||
#
|
||||
# textEdit.textChanged.connect(updateText)
|
||||
# layout.addRow(textLabel, textEdit)
|
||||
#
|
||||
# # 位置属性
|
||||
# if hasattr(gui_element, 'getPos'):
|
||||
# # 根据GUI类型设置组名
|
||||
# if gui_type in ["button", "label", "entry", "2d_image"]:
|
||||
# transform_group = QGroupBox("变换 Rect Transform")
|
||||
# else:
|
||||
# transform_group = QGroupBox("变换 Transform")
|
||||
#
|
||||
# transform_layout = QGridLayout()
|
||||
#
|
||||
# pos = gui_element.getPos()
|
||||
#
|
||||
# # 根据GUI类型决定位置编辑方式
|
||||
# if gui_type in ["button", "label", "entry","2d_image"]:
|
||||
# # 2D GUI组件使用屏幕坐标
|
||||
# logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
|
||||
# logical_z = pos.getZ() / 0.1
|
||||
#
|
||||
# transform_layout.addWidget(QLabel("屏幕位置"), 0, 0)
|
||||
#
|
||||
# x_label = QLabel("X")
|
||||
# z_label = QLabel("z")
|
||||
# x_label.setAlignment(Qt.Aligncenter)
|
||||
# z_label.setAlignment(Qt.AlignCenter)
|
||||
#
|
||||
# transform_layout.addWidget(x_label, 0, 1)
|
||||
# transform_layout.addWidget(z_label, 0, 2)
|
||||
#
|
||||
# xPos = QDoubleSpinBox()
|
||||
# xPos.setRange(-50, 50)
|
||||
# xPos.setValue(logical_x)
|
||||
# xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
|
||||
# transform_layout.addWidget(xPos, 1, 1)
|
||||
#
|
||||
# zPos = QDoubleSpinBox()
|
||||
# zPos.setRange(-50, 50)
|
||||
# zPos.setValue(logical_z)
|
||||
# zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
|
||||
# transform_layout.addWidget(zPos, 1, 2)
|
||||
#
|
||||
# # 显示实际屏幕坐标(只读)
|
||||
# transform_layout.addWidget(QLabel("实际坐标"), 2, 0)
|
||||
#
|
||||
# actualXLabel = QLabel(f"{pos.getX():.3f}")
|
||||
# actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
# actualZLabel = QLabel(f"{pos.getZ():.3f}")
|
||||
# actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
|
||||
#
|
||||
# transform_layout.addWidget(actualXLabel, 3, 1)
|
||||
# transform_layout.addWidget(actualZLabel, 3, 2)
|
||||
# # 添加宽度和高度控件(对于2D图像)
|
||||
# if gui_type == "2d_image":
|
||||
#
|
||||
# # 添加排序控制组
|
||||
# sort_group = QGroupBox("渲染顺序")
|
||||
# sort_layout = QGridLayout()
|
||||
#
|
||||
# # 获取当前的sort值,如果没有设置则默认为0
|
||||
# current_sort = int(gui_element.getTag("sort") or "0")
|
||||
#
|
||||
# sort_layout.addWidget(QLabel("层级:"), 0, 0)
|
||||
#
|
||||
# sort_spin = QSpinBox()
|
||||
# sort_spin.setRange(-1000, 1000) # 设置合理的范围
|
||||
# sort_spin.setValue(current_sort)
|
||||
#
|
||||
# # 创建更新排序的函数
|
||||
# def updateSort(value):
|
||||
# try:
|
||||
# # 设置标签
|
||||
# gui_element.setTag("sort", str(value))
|
||||
# # 应用sort到节点 - 使用fixed bin和指定的值
|
||||
# gui_element.setBin("fixed", value)
|
||||
# print(f"✓ 更新2D图像渲染顺序: {value}")
|
||||
# except Exception as e:
|
||||
# print(f"✗ 更新2D图像渲染顺序失败: {e}")
|
||||
#
|
||||
# sort_spin.valueChanged.connect(updateSort)
|
||||
# sort_layout.addWidget(sort_spin, 0, 1)
|
||||
#
|
||||
# # 添加说明标签
|
||||
# sort_help = QLabel("(数值越大越靠前)")
|
||||
# sort_help.setStyleSheet("font-size: 10px; color: gray;")
|
||||
# sort_layout.addWidget(sort_help, 1, 0, 1, 2)
|
||||
#
|
||||
# sort_group.setLayout(sort_layout)
|
||||
# layout.addWidget(sort_group)
|
||||
#
|
||||
# scale = gui_element.getScale()
|
||||
# width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,
|
||||
# (tuple, list)) else scale
|
||||
# height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,
|
||||
# (tuple, list)) and len(
|
||||
# scale) > 1 else scale
|
||||
#
|
||||
# # 宽度控件
|
||||
# transform_layout.addWidget(QLabel("宽度"), 4, 0)
|
||||
# widthSpinBox = QDoubleSpinBox()
|
||||
# widthSpinBox.setRange(0.1, 10)
|
||||
# widthSpinBox.setSingleStep(0.1)
|
||||
# widthSpinBox.setValue(width)
|
||||
# widthSpinBox.valueChanged.connect(
|
||||
# lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
# transform_layout.addWidget(widthSpinBox, 4, 1)
|
||||
#
|
||||
# # 高度控件
|
||||
# transform_layout.addWidget(QLabel("高度"), 4, 2)
|
||||
# heightSpinBox = QDoubleSpinBox()
|
||||
# heightSpinBox.setRange(0.1, 10)
|
||||
# heightSpinBox.setSingleStep(0.1)
|
||||
# heightSpinBox.setValue(height)
|
||||
# heightSpinBox.valueChanged.connect(
|
||||
# lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
# transform_layout.addWidget(heightSpinBox, 4, 3)
|
||||
#
|
||||
# else:
|
||||
# # 3D GUI组件使用世界坐标
|
||||
# transform_layout.addWidget(QLabel("位置"), 0, 0)
|
||||
#
|
||||
# # X, Y, Z 标签居中
|
||||
# x_label = QLabel("X")
|
||||
# y_label = QLabel("Y")
|
||||
# z_label = QLabel("Z")
|
||||
# x_label.setAlignment(Qt.AlignCenter)
|
||||
# y_label.setAlignment(Qt.AlignCenter)
|
||||
# z_label.setAlignment(Qt.AlignCenter)
|
||||
#
|
||||
# transform_layout.addWidget(x_label, 0, 1)
|
||||
# transform_layout.addWidget(y_label, 0, 2)
|
||||
# transform_layout.addWidget(z_label, 0, 3)
|
||||
#
|
||||
# # 位置数值输入框
|
||||
# xPos = QDoubleSpinBox()
|
||||
# xPos.setRange(-100, 100)
|
||||
# xPos.setValue(pos.getX())
|
||||
# xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "x", v))
|
||||
# transform_layout.addWidget(xPos, 1, 1)
|
||||
#
|
||||
# yPos = QDoubleSpinBox()
|
||||
# yPos.setRange(-100, 100)
|
||||
# yPos.setValue(pos.getY())
|
||||
# yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "y", v))
|
||||
# transform_layout.addWidget(yPos, 1, 2)
|
||||
#
|
||||
# zPos = QDoubleSpinBox()
|
||||
# zPos.setRange(-100, 100)
|
||||
# zPos.setValue(pos.getZ())
|
||||
# zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI3DPosition(gui_element, "z", v))
|
||||
# transform_layout.addWidget(zPos, 1, 3)
|
||||
#
|
||||
# # 缩放属性
|
||||
# scale = gui_element.getScale()
|
||||
# transform_layout.addWidget(QLabel("缩放"), 2, 0)
|
||||
#
|
||||
# # X, Y, Z 缩放标签居中
|
||||
# sx_label = QLabel("X")
|
||||
# sy_label = QLabel("Y")
|
||||
# sz_label = QLabel("Z")
|
||||
# sx_label.setAlignment(Qt.AlignCenter)
|
||||
# sy_label.setAlignment(Qt.AlignCenter)
|
||||
# sz_label.setAlignment(Qt.AlignCenter)
|
||||
#
|
||||
# transform_layout.addWidget(sx_label, 2, 1)
|
||||
# transform_layout.addWidget(sy_label, 2, 2)
|
||||
# transform_layout.addWidget(sz_label, 2, 3)
|
||||
#
|
||||
# # 缩放数值输入框
|
||||
# scale_x = QDoubleSpinBox()
|
||||
# scale_x.setRange(0.01, 10)
|
||||
# scale_x.setSingleStep(0.1)
|
||||
# scale_x.setValue(
|
||||
# scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale)
|
||||
# scale_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "x", v))
|
||||
# transform_layout.addWidget(scale_x, 3, 1)
|
||||
#
|
||||
# scale_y = QDoubleSpinBox()
|
||||
# scale_y.setRange(0.01, 10)
|
||||
# scale_y.setSingleStep(0.1)
|
||||
# scale_y.setValue(
|
||||
# scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len(
|
||||
# scale) > 1 else scale)
|
||||
# scale_y.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "y", v))
|
||||
# transform_layout.addWidget(scale_y, 3, 2)
|
||||
#
|
||||
# scale_z = QDoubleSpinBox()
|
||||
# scale_z.setRange(0.01, 10)
|
||||
# scale_z.setSingleStep(0.1)
|
||||
# scale_z.setValue(
|
||||
# scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len(
|
||||
# scale) > 2 else scale)
|
||||
# scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v))
|
||||
# transform_layout.addWidget(scale_z, 3, 3)
|
||||
# transform_group.setLayout(transform_layout)
|
||||
# self._propertyLayout.addWidget(transform_group)
|
||||
#
|
||||
# # 缩放属性
|
||||
# if hasattr(gui_element, 'getScale'):
|
||||
# scale = gui_element.getScale()
|
||||
#
|
||||
# scaleSpinBox = QDoubleSpinBox()
|
||||
# scaleSpinBox.setRange(0.01, 10)
|
||||
# scaleSpinBox.setSingleStep(0.1)
|
||||
# scaleSpinBox.setValue(scale.getX())
|
||||
# scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v))
|
||||
# layout.addRow("缩放:", scaleSpinBox)
|
||||
#
|
||||
# # 颜色属性(针对2D GUI)
|
||||
# if gui_type in ["button", "label"]:
|
||||
# colorButton = QPushButton("选择颜色")
|
||||
# colorButton.clicked.connect(lambda: self.selectGUIColor(gui_element))
|
||||
# layout.addRow("背景颜色:", colorButton)
|
||||
#
|
||||
# # 3D特有属性
|
||||
# if gui_type in ["3d_text", "virtual_screen"]:
|
||||
# # 旋转属性
|
||||
# if hasattr(gui_element, 'getHpr'):
|
||||
# hpr = gui_element.getHpr()
|
||||
#
|
||||
# hRot = QDoubleSpinBox()
|
||||
# hRot.setRange(-180, 180)
|
||||
# hRot.setValue(hpr.getX())
|
||||
# hRot.valueChanged.connect(lambda v: gui_element.setH(v))
|
||||
# layout.addRow("旋转 H:", hRot)
|
||||
#
|
||||
# pRot = QDoubleSpinBox()
|
||||
# pRot.setRange(-180, 180)
|
||||
# pRot.setValue(hpr.getY())
|
||||
# pRot.valueChanged.connect(lambda v: gui_element.setP(v))
|
||||
# layout.addRow("旋转 P:", pRot)
|
||||
#
|
||||
# rRot = QDoubleSpinBox()
|
||||
# rRot.setRange(-180, 180)
|
||||
# rRot.setValue(hpr.getZ())
|
||||
# rRot.valueChanged.connect(lambda v: gui_element.setR(v))
|
||||
# layout.addRow("旋转 R:", rRot)
|
||||
|
||||
def selectGUIColor(self, gui_element):
|
||||
"""选择GUI元素颜色"""
|
||||
|
||||
2
main.py
2
main.py
@ -102,7 +102,7 @@ class MyWorld(CoreWorld):
|
||||
self.collision_manager = CollisionManager(self)
|
||||
|
||||
# 调试选项
|
||||
self.debug_collision = True # 是否显示碰撞体
|
||||
self.debug_collision = False # 是否显示碰撞体
|
||||
|
||||
# 默认启用模型间碰撞检测(可选)
|
||||
self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5)
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import datetime
|
||||
import subprocess
|
||||
import shutil
|
||||
@ -47,7 +46,9 @@ class ProjectManager:
|
||||
|
||||
project_path = dialog.projectPath
|
||||
project_name = dialog.projectName
|
||||
full_project_path = os.path.join(project_path, project_name)
|
||||
# full_project_path = os.path.join(project_path, project_name)
|
||||
full_project_path = os.path.normpath(os.path.join(project_path, project_name))
|
||||
print(f"full_project_path: {full_project_path}")
|
||||
|
||||
try:
|
||||
# 创建项目文件夹结构
|
||||
@ -72,10 +73,10 @@ class ProjectManager:
|
||||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||||
|
||||
print(f"项目配置文件已创建: {config_file}")
|
||||
|
||||
|
||||
# 清空当前场景
|
||||
self._clearCurrentScene()
|
||||
|
||||
|
||||
# 自动保存初始场景
|
||||
scene_file = os.path.join(scenes_path, "scene.bam")
|
||||
if self.world.scene_manager.saveScene(scene_file):
|
||||
|
||||
@ -804,6 +804,7 @@ class SceneManager:
|
||||
|
||||
# 保存场景
|
||||
success = self.world.render.writeBamFile(filename)
|
||||
print(f"success: {success}")
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
@ -948,9 +949,14 @@ class SceneManager:
|
||||
"""删除模型"""
|
||||
try:
|
||||
if model in self.models:
|
||||
model.removeNode()
|
||||
self.models.remove(model)
|
||||
self.updateSceneTree()
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
return False
|
||||
|
||||
tree_widget.delete_items(tree_widget.selectedItems())
|
||||
# model.removeNode()
|
||||
# self.models.remove(model)
|
||||
# self.updateSceneTree()
|
||||
print(f"删除模型: {model.getName()}")
|
||||
return True
|
||||
except Exception as e:
|
||||
@ -959,14 +965,15 @@ class SceneManager:
|
||||
|
||||
def clearAllModels(self):
|
||||
"""清除所有模型"""
|
||||
try:
|
||||
for model in self.models:
|
||||
model.removeNode()
|
||||
self.models.clear()
|
||||
self.updateSceneTree()
|
||||
print("清除所有模型完成")
|
||||
except Exception as e:
|
||||
print(f"清除所有模型失败: {str(e)}")
|
||||
pass
|
||||
# try:
|
||||
# for model in self.models:
|
||||
# model.removeNode()
|
||||
# self.models.clear()
|
||||
# self.updateSceneTree()
|
||||
# print("清除所有模型完成")
|
||||
# except Exception as e:
|
||||
# print(f"清除所有模型失败: {str(e)}")
|
||||
|
||||
def getModels(self):
|
||||
"""获取模型列表"""
|
||||
@ -1751,43 +1758,95 @@ except Exception as e:
|
||||
return False
|
||||
|
||||
def load_cesium_tileset(self, tileset_url, position=(0, 0, 0)):
|
||||
"""
|
||||
加载 Cesium 3D Tileset - 采用新的创建逻辑,支持多选和更完善的UI交互。
|
||||
"""
|
||||
try:
|
||||
print(f"加载 Cesium 3D Tiles: {tileset_url}")
|
||||
from panda3d.core import NodePath
|
||||
print(f"🗺️ 开始加载 Cesium 3D Tiles: {tileset_url}")
|
||||
|
||||
# 创建一个容器节点来管理tileset
|
||||
node_name = f"cesium_tileset_{len(self.tilesets)}"
|
||||
tileset_node = self.world.render.attachNewNode(node_name)
|
||||
tileset_node.setPos(*position)
|
||||
# 1. 获取UI控件和目标父节点
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
print("❌ 无法访问树形控件")
|
||||
return None
|
||||
|
||||
#添加标签以便场景树识别
|
||||
tileset_node.setTag("is_scene_element","1")
|
||||
tileset_node.setTag("element_type","cesium_tileset")
|
||||
tileset_node.setTag("tileset_url",tileset_url)
|
||||
tileset_node.setTag("file",f"tileset_{len(self.tilesets)}")
|
||||
target_parents = tree_widget.get_target_parents_for_creation()
|
||||
if not target_parents:
|
||||
print("❌ 没有找到有效的父节点来附加Tileset")
|
||||
return None
|
||||
|
||||
# 存储tileset信息
|
||||
tileset_info = {
|
||||
'url': tileset_url,
|
||||
'node': tileset_node,
|
||||
'position': position,
|
||||
'tiles': {}
|
||||
}
|
||||
created_tilesets = []
|
||||
|
||||
self.tilesets.append(tileset_info)
|
||||
# 2. 遍历所有选中的父节点,并为其创建Tileset
|
||||
for parent_item, parent_node in target_parents:
|
||||
try:
|
||||
# 生成唯一名称
|
||||
node_name = f"cesium_tileset_{len(self.tilesets)}"
|
||||
|
||||
# 创建一个临时的可视化占位符,让用户能看到节点已添加
|
||||
self._create_placeholder_geometry(tileset_node)
|
||||
# 创建一个容器节点来管理tileset,并挂载到父节点
|
||||
tileset_node = parent_node.attachNewNode(node_name)
|
||||
tileset_node.setPos(*position)
|
||||
|
||||
# 异步加载tileset数据
|
||||
self._load_tileset_async(tileset_url, tileset_info)
|
||||
# 添加标签以便场景识别和保存
|
||||
tileset_node.setTag("is_scene_element", "1")
|
||||
tileset_node.setTag("element_type", "cesium_tileset")
|
||||
tileset_node.setTag("tileset_url", tileset_url)
|
||||
# 使用唯一名称作为文件标识,代替索引
|
||||
tileset_node.setTag("file", node_name)
|
||||
|
||||
# 更新场景树
|
||||
self.updateSceneTree()
|
||||
print(f"✓ Cesium 3D Tiles 加载请求已发送")
|
||||
return tileset_node
|
||||
# 存储tileset核心信息
|
||||
tileset_info = {
|
||||
'url': tileset_url,
|
||||
'node': tileset_node,
|
||||
'position': position,
|
||||
'tiles': {} # 用于后续管理瓦片
|
||||
}
|
||||
self.tilesets.append(tileset_info)
|
||||
|
||||
# 创建一个临时的可视化占位符,让用户能看到节点已添加
|
||||
self._create_placeholder_geometry(tileset_node)
|
||||
|
||||
# 异步加载tileset的实际数据
|
||||
self._load_tileset_async(tileset_url, tileset_info)
|
||||
|
||||
print(f"✅ 为 {parent_item.text(0)} 加载 Tileset 成功: {node_name}")
|
||||
|
||||
# 在Qt树形控件中添加对应节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(tileset_node, parent_item, "CESIUM_TILESET_NODE")
|
||||
if qt_item:
|
||||
created_tilesets.append((tileset_node, qt_item))
|
||||
else:
|
||||
created_tilesets.append((tileset_node, None))
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为 {parent_item.text(0)} 加载 Tileset 失败: {str(e)}")
|
||||
continue # 继续尝试为下一个父节点创建
|
||||
|
||||
# 3. 处理创建结果
|
||||
if not created_tilesets:
|
||||
print("❌ 没有成功加载任何 Tileset")
|
||||
return None
|
||||
|
||||
# 选中最后创建的Tileset并更新UI
|
||||
if created_tilesets:
|
||||
last_tileset_node, last_qt_item = created_tilesets[-1]
|
||||
if last_qt_item:
|
||||
tree_widget.setCurrentItem(last_qt_item)
|
||||
# 更新选择状态和属性面板
|
||||
tree_widget.update_selection_and_properties(last_tileset_node, last_qt_item)
|
||||
|
||||
print(f"🎉 总共加载了 {len(created_tilesets)} 个 Cesium Tileset 实例")
|
||||
|
||||
# 4. 返回值处理
|
||||
if len(created_tilesets) == 1:
|
||||
return created_tilesets[0][0] # 单个实例返回NodePath
|
||||
else:
|
||||
return [node for node, _ in created_tilesets] # 多个实例返回NodePath列表
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 加载 Cesium 3D Tiles 失败: {e}")
|
||||
print(f"❌ 加载 Cesium 3D Tiles 过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
@ -48,83 +48,83 @@ class InterfaceManager:
|
||||
self.world.selection.updateSelection(None)
|
||||
#self.world.property_panel.clearPropertyPanel()
|
||||
|
||||
def showTreeContextMenu(self, position):
|
||||
"""显示树形控件的右键菜单"""
|
||||
item = self.treeWidget.itemAt(position)
|
||||
if not item:
|
||||
return
|
||||
|
||||
# 获取节点对象
|
||||
nodePath = item.data(0, Qt.UserRole)
|
||||
if not nodePath:
|
||||
return
|
||||
|
||||
# 创建菜单
|
||||
menu = QMenu()
|
||||
|
||||
# 检查是否是GUI元素
|
||||
if hasattr(nodePath, 'getTag') and nodePath.getTag("gui_type"):
|
||||
# GUI元素菜单
|
||||
editAction = menu.addAction("编辑")
|
||||
editAction.triggered.connect(lambda: self.world.gui_manager.editGUIElementDialog(nodePath))
|
||||
|
||||
deleteAction = menu.addAction("删除GUI元素")
|
||||
deleteAction.triggered.connect(lambda: self.world.gui_manager.deleteGUIElement(nodePath))
|
||||
|
||||
duplicateAction = menu.addAction("复制")
|
||||
duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath))
|
||||
|
||||
elif hasattr(nodePath,'getTag') and nodePath.getTag("element_type") == "cesium_tileset":
|
||||
deleteAction = menu.addAction("删除 Cesium Tileset")
|
||||
deleteAction.triggered.connect(lambda:self.deleteCesiumTileset(nodePath,item))
|
||||
|
||||
else:
|
||||
# 为模型节点或其子节点添加删除选项
|
||||
parentItem = item.parent()
|
||||
if parentItem:
|
||||
if self.isModelOrChild(item):
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
else:
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
|
||||
# 显示菜单
|
||||
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
||||
|
||||
def deleteCesiumTileset(self, nodePath, item):
|
||||
"""删除 Cesium tileset"""
|
||||
try:
|
||||
# 从场景中移除
|
||||
nodePath.removeNode()
|
||||
|
||||
# 从 tilesets 列表中移除
|
||||
if hasattr(self.world, 'scene_manager'):
|
||||
tilesets_to_remove = []
|
||||
for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
if tileset_info['node'] == nodePath:
|
||||
tilesets_to_remove.append(i)
|
||||
|
||||
# 从后往前删除,避免索引问题
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del self.world.scene_manager.tilesets[i]
|
||||
|
||||
# 从树形控件中移除
|
||||
parentItem = item.parent()
|
||||
if parentItem:
|
||||
parentItem.removeChild(item)
|
||||
|
||||
print(f"成功删除 Cesium tileset: {nodePath.getName()}")
|
||||
|
||||
# 清空属性面板和选择框
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
self.world.selection.updateSelection(None)
|
||||
|
||||
# 更新场景树
|
||||
self.updateSceneTree()
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除 Cesium tileset 失败: {str(e)}")
|
||||
# def showTreeContextMenu(self, position):
|
||||
# """显示树形控件的右键菜单"""
|
||||
# item = self.treeWidget.itemAt(position)
|
||||
# if not item:
|
||||
# return
|
||||
#
|
||||
# # 获取节点对象
|
||||
# nodePath = item.data(0, Qt.UserRole)
|
||||
# if not nodePath:
|
||||
# return
|
||||
#
|
||||
# # 创建菜单
|
||||
# menu = QMenu()
|
||||
#
|
||||
# # 检查是否是GUI元素
|
||||
# if hasattr(nodePath, 'getTag') and nodePath.getTag("gui_type"):
|
||||
# # GUI元素菜单
|
||||
# editAction = menu.addAction("编辑")
|
||||
# editAction.triggered.connect(lambda: self.world.gui_manager.editGUIElementDialog(nodePath))
|
||||
#
|
||||
# deleteAction = menu.addAction("删除GUI元素")
|
||||
# deleteAction.triggered.connect(lambda: self.world.gui_manager.deleteGUIElement(nodePath))
|
||||
#
|
||||
# duplicateAction = menu.addAction("复制")
|
||||
# duplicateAction.triggered.connect(lambda: self.world.gui_manager.duplicateGUIElement(nodePath))
|
||||
#
|
||||
# elif hasattr(nodePath,'getTag') and nodePath.getTag("element_type") == "cesium_tileset":
|
||||
# deleteAction = menu.addAction("删除 Cesium Tileset")
|
||||
# deleteAction.triggered.connect(lambda:self.deleteCesiumTileset(nodePath,item))
|
||||
#
|
||||
# else:
|
||||
# # 为模型节点或其子节点添加删除选项
|
||||
# parentItem = item.parent()
|
||||
# if parentItem:
|
||||
# if self.isModelOrChild(item):
|
||||
# deleteAction = menu.addAction("删除")
|
||||
# deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
# else:
|
||||
# deleteAction = menu.addAction("删除")
|
||||
# deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
#
|
||||
# # 显示菜单
|
||||
# menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
||||
#
|
||||
# def deleteCesiumTileset(self, nodePath, item):
|
||||
# """删除 Cesium tileset"""
|
||||
# try:
|
||||
# # 从场景中移除
|
||||
# nodePath.removeNode()
|
||||
#
|
||||
# # 从 tilesets 列表中移除
|
||||
# if hasattr(self.world, 'scene_manager'):
|
||||
# tilesets_to_remove = []
|
||||
# for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
# if tileset_info['node'] == nodePath:
|
||||
# tilesets_to_remove.append(i)
|
||||
#
|
||||
# # 从后往前删除,避免索引问题
|
||||
# for i in reversed(tilesets_to_remove):
|
||||
# del self.world.scene_manager.tilesets[i]
|
||||
#
|
||||
# # 从树形控件中移除
|
||||
# parentItem = item.parent()
|
||||
# if parentItem:
|
||||
# parentItem.removeChild(item)
|
||||
#
|
||||
# print(f"成功删除 Cesium tileset: {nodePath.getName()}")
|
||||
#
|
||||
# # 清空属性面板和选择框
|
||||
# self.world.property_panel.clearPropertyPanel()
|
||||
# self.world.selection.updateSelection(None)
|
||||
#
|
||||
# # 更新场景树
|
||||
# self.updateSceneTree()
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"删除 Cesium tileset 失败: {str(e)}")
|
||||
|
||||
def isModelOrChild(self, item):
|
||||
"""检查是否是模型节点或其子节点"""
|
||||
@ -148,9 +148,9 @@ class InterfaceManager:
|
||||
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)
|
||||
# self.updateSceneTree()
|
||||
# self.world.property_panel.clearPropertyPanel()
|
||||
# self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
# 从场景中移除
|
||||
|
||||
@ -813,11 +813,11 @@ class MainWindow(QMainWindow):
|
||||
self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||||
|
||||
# 连接脚本菜单事件
|
||||
self.createScriptAction.triggered.connect(self.onCreateScriptDialog)
|
||||
self.loadScriptAction.triggered.connect(self.onLoadScriptFile)
|
||||
self.loadAllScriptsAction.triggered.connect(self.onReloadAllScripts)
|
||||
self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||||
self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
||||
# self.createScriptAction.triggered.connect(self.onCreateScriptDialog)
|
||||
# self.loadScriptAction.triggered.connect(self.onLoadScriptFile)
|
||||
# self.loadAllScriptsAction.triggered.connect(self.onReloadAllScripts)
|
||||
# self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||||
# self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
||||
|
||||
|
||||
def onCreateCesiumView(self):
|
||||
|
||||
@ -133,6 +133,11 @@ class PropertyPanelManager:
|
||||
# 根据模型的实际可见性状态设置复选框
|
||||
self.active_check.setChecked(user_visible)
|
||||
self.name_input = QLineEdit(itemText)
|
||||
|
||||
self.name_input.returnPressed.connect(
|
||||
lambda: self.world.treeWidget.update_item_name(self.name_input.text(), item)
|
||||
)
|
||||
|
||||
name_layout.addWidget(self.active_check)
|
||||
name_layout.addWidget(self.name_input)
|
||||
self.name_group.setLayout(name_layout)
|
||||
@ -515,12 +520,12 @@ class PropertyPanelManager:
|
||||
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()
|
||||
# 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:
|
||||
@ -994,27 +999,29 @@ class PropertyPanelManager:
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
# 从场景中移除
|
||||
nodePath.removeNode()
|
||||
tree_widget = self.world.treeWidget
|
||||
tree_widget.delete_item(nodePath)
|
||||
# # 从场景中移除
|
||||
# nodePath.removeNode()
|
||||
#
|
||||
# # 从 tilesets 列表中移除
|
||||
# if hasattr(self.world, 'scene_manager'):
|
||||
# tilesets_to_remove = []
|
||||
# for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
# if tileset_info['node'] == nodePath:
|
||||
# tilesets_to_remove.append(i)
|
||||
#
|
||||
# # 从后往前删除,避免索引问题
|
||||
# for i in reversed(tilesets_to_remove):
|
||||
# del self.world.scene_manager.tilesets[i]
|
||||
#
|
||||
# # 更新场景树
|
||||
# self.world.scene_manager.updateSceneTree()
|
||||
#
|
||||
# # 清空属性面板
|
||||
# self.clearPropertyPanel()
|
||||
|
||||
# 从 tilesets 列表中移除
|
||||
if hasattr(self.world, 'scene_manager'):
|
||||
tilesets_to_remove = []
|
||||
for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
if tileset_info['node'] == nodePath:
|
||||
tilesets_to_remove.append(i)
|
||||
|
||||
# 从后往前删除,避免索引问题
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del self.world.scene_manager.tilesets[i]
|
||||
|
||||
# 更新场景树
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
# 清空属性面板
|
||||
self.clearPropertyPanel()
|
||||
|
||||
print(f"成功删除 Cesium tileset: {nodePath.getName()}")
|
||||
# print(f"成功删除 Cesium tileset: {nodePath.getName()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除 Cesium tileset 失败: {str(e)}")
|
||||
|
||||
416
ui/widgets.py
416
ui/widgets.py
@ -17,6 +17,7 @@ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QGroupBox, QHBoxLayout,
|
||||
from PyQt5.QtCore import Qt, QUrl, QMimeData
|
||||
from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush
|
||||
from PyQt5.sip import wrapinstance
|
||||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||||
from panda3d.core import ModelRoot
|
||||
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
@ -1302,6 +1303,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
parent = wrapinstance(0, QWidget)
|
||||
super().__init__(parent)
|
||||
self.world = world
|
||||
# self.selectedItems = None
|
||||
self.initData()
|
||||
self.setupUI() # 初始化界面
|
||||
self.setupContextMenu() # 初始化右键菜单
|
||||
@ -1333,9 +1335,14 @@ class CustomTreeWidget(QTreeWidget):
|
||||
"LIGHT_NODE",
|
||||
"CAMERA_NODE",
|
||||
"IMPORTED_MODEL_NODE",
|
||||
"MODEL_NODE"
|
||||
"MODEL_NODE",
|
||||
"TERRAIN_NODE",
|
||||
"CESIUM_TILESET_NODE"
|
||||
}
|
||||
|
||||
# 这是一个最佳实践,它让代码的意图变得非常清晰。
|
||||
self.valid_3d_parent_types = self.scene_3d_types.union(self.gui_3d_types)
|
||||
|
||||
def setupUI(self):
|
||||
"""初始化UI设置"""
|
||||
self.setHeaderHidden(True)
|
||||
@ -1427,105 +1434,91 @@ class CustomTreeWidget(QTreeWidget):
|
||||
print("用户取消了菜单选择")
|
||||
|
||||
def dropEvent(self, event):
|
||||
dragged_item = self.currentItem()
|
||||
target_item = self.itemAt(event.pos())
|
||||
if not dragged_item or not target_item:
|
||||
# 1. 获取所有被拖拽的项
|
||||
dragged_items = self.selectedItems()
|
||||
if not dragged_items:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if not self.isValidParentChild(dragged_item, target_item):
|
||||
event.ignore()
|
||||
return
|
||||
# 2. 在执行Qt的默认拖拽前,记录所有拖拽项的原始状态
|
||||
drag_info = []
|
||||
for item in dragged_items:
|
||||
panda_node = item.data(0, Qt.UserRole)
|
||||
if not panda_node or panda_node.is_empty():
|
||||
continue # 跳过无效节点
|
||||
|
||||
dragged_node = dragged_item.data(0, Qt.UserRole)
|
||||
target_node = target_item.data(0, Qt.UserRole)
|
||||
drag_info.append({
|
||||
"item": item,
|
||||
"node": panda_node,
|
||||
"old_parent_node": item.parent().data(0, Qt.UserRole) if item.parent() else None
|
||||
})
|
||||
|
||||
if not dragged_node or not target_node:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
# # 检查是否是有效的父子关系
|
||||
# if self.isValidParentChild(dragged_item, target_item):
|
||||
# # 保存当前的世界坐标
|
||||
# world_pos = dragged_node.getPos(self.world.render)
|
||||
#
|
||||
# # 更新场景图中的父子关系
|
||||
# dragged_node.wrtReparentTo(target_node)
|
||||
#
|
||||
# # 接受拖放事件,更新树形控件
|
||||
# super().dropEvent(event)
|
||||
#
|
||||
# #self.world.property_panel.updateNodeVisibilityAfterDrag(dragged_item)
|
||||
# # 更新属性面板
|
||||
# self.world.updatePropertyPanel(dragged_item)
|
||||
# self.world.property_panel._syncEffectiveVisibility(dragged_node)
|
||||
|
||||
print(f"dragged_node: {dragged_node}, target_node: {target_node}")
|
||||
|
||||
# 记录拖拽前的父节点
|
||||
old_parent_item = dragged_item.parent()
|
||||
old_parent_node = old_parent_item.data(0, Qt.UserRole) if old_parent_item else None
|
||||
|
||||
# 执行Qt默认拖拽
|
||||
# 3. 执行Qt的默认拖拽,让UI树先行更新
|
||||
# 这一步会自动处理移动或复制,并将项目从旧父节点移除,添加到新父节点
|
||||
super().dropEvent(event)
|
||||
|
||||
# 检查拖拽后的父节点
|
||||
new_parent_item = dragged_item.parent()
|
||||
new_parent_node = new_parent_item.data(0, Qt.UserRole) if new_parent_item else None
|
||||
|
||||
# 同步Panda3D场景图的父子关系
|
||||
# 4. 遍历记录下的信息,同步每一个Panda3D节点的状态
|
||||
try:
|
||||
# 检查是否是跨层级拖拽(父节点发生变化)
|
||||
if old_parent_node != new_parent_node:
|
||||
print(f"跨层级拖拽:从 {old_parent_node} 移动到 {new_parent_node}")
|
||||
for info in drag_info:
|
||||
dragged_item = info["item"]
|
||||
dragged_node = info["node"]
|
||||
old_parent_node = info["old_parent_node"]
|
||||
|
||||
# 保存世界坐标位置
|
||||
world_pos = dragged_node.getPos(self.world.render)
|
||||
world_hpr = dragged_node.getHpr(self.world.render)
|
||||
world_scale = dragged_node.getScale(self.world.render)
|
||||
# 获取拖拽后的新父节点
|
||||
new_parent_item = dragged_item.parent()
|
||||
new_parent_node = new_parent_item.data(0, Qt.UserRole) if new_parent_item else None
|
||||
|
||||
# 检查是否是2D GUI元素
|
||||
dragged_type = dragged_item.data(0, Qt.UserRole + 1)
|
||||
is_2d_gui = dragged_type in self.gui_2d_types
|
||||
# 仅当父节点实际发生变化时才执行重新父化
|
||||
if old_parent_node != new_parent_node:
|
||||
print(f"跨层级拖拽:从 {old_parent_node} 移动到 {new_parent_node}")
|
||||
|
||||
# 重新父化到新的父节点
|
||||
if new_parent_node:
|
||||
if is_2d_gui:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if hasattr(new_parent_node, 'getTag') and new_parent_node.getTag("is_gui_element") == "1":
|
||||
# 目标是GUI元素,直接重新父化
|
||||
dragged_node.reparentTo(new_parent_node)
|
||||
# # 保存世界坐标位置
|
||||
# world_pos = dragged_node.getPos(self.world.render)
|
||||
# world_hpr = dragged_node.getHpr(self.world.render)
|
||||
# world_scale = dragged_node.getScale(self.world.render)
|
||||
|
||||
# 检查是否是2D GUI元素
|
||||
dragged_type = dragged_item.data(0, Qt.UserRole + 1)
|
||||
is_2d_gui = dragged_type in self.gui_2d_types
|
||||
|
||||
# 重新父化到新的父节点
|
||||
if new_parent_node:
|
||||
if is_2d_gui:
|
||||
# 2D GUI元素需要特殊处理
|
||||
if hasattr(new_parent_node, 'getTag') and new_parent_node.getTag("is_gui_element") == "1":
|
||||
# 目标是GUI元素,直接重新父化
|
||||
dragged_node.wrtReparentTo(new_parent_node)
|
||||
else:
|
||||
# 目标是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
# from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 保持在aspect2d下")
|
||||
else:
|
||||
# 目标是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.reparentTo(aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 保持在aspect2d下")
|
||||
# 非GUI元素正常重新父化
|
||||
dragged_node.wrtReparentTo(new_parent_node)
|
||||
else:
|
||||
# 非GUI元素正常重新父化
|
||||
dragged_node.reparentTo(new_parent_node)
|
||||
else:
|
||||
# 如果新父节点为None,根据元素类型决定父节点
|
||||
if is_2d_gui:
|
||||
from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.reparentTo(aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 重新父化到aspect2d")
|
||||
else:
|
||||
dragged_node.reparentTo(self.world.render)
|
||||
# 如果新父节点为None,根据元素类型决定父节点
|
||||
if is_2d_gui:
|
||||
# from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 重新父化到aspect2d")
|
||||
else:
|
||||
dragged_node.wrtReparentTo(self.world.render)
|
||||
|
||||
# 恢复世界坐标位置(对于2D GUI可能需要调整)
|
||||
if is_2d_gui:
|
||||
# 2D GUI元素使用屏幕坐标系,可能需要特殊处理
|
||||
dragged_node.setPos(world_pos)
|
||||
dragged_node.setHpr(world_hpr)
|
||||
dragged_node.setScale(world_scale)
|
||||
else:
|
||||
dragged_node.setPos(self.world.render, world_pos)
|
||||
dragged_node.setHpr(self.world.render, world_hpr)
|
||||
dragged_node.setScale(self.world.render, world_scale)
|
||||
# # 恢复世界坐标位置(对于2D GUI可能需要调整)
|
||||
# if is_2d_gui:
|
||||
# # 2D GUI元素使用屏幕坐标系,可能需要特殊处理
|
||||
# dragged_node.setPos(world_pos)
|
||||
# dragged_node.setHpr(world_hpr)
|
||||
# dragged_node.setScale(world_scale)
|
||||
# else:
|
||||
# dragged_node.setPos(self.world.render, world_pos)
|
||||
# dragged_node.setHpr(self.world.render, world_hpr)
|
||||
# dragged_node.setScale(self.world.render, world_scale)
|
||||
|
||||
print(f"✅ Panda3D父子关系已更新")
|
||||
else:
|
||||
print(f"同层级移动:父节点未变化,跳过Panda3D重新父化")
|
||||
print(f"✅ Panda3D父子关系已更新")
|
||||
else:
|
||||
print(f"同层级移动:父节点未变化,跳过Panda3D重新父化")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 同步Panda3D场景图失败: {e}")
|
||||
@ -1648,7 +1641,9 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
# 1. 2D GUI元素的拖拽限制 - 只能拖拽到其他2D GUI元素下
|
||||
if is_dragged_2d_gui:
|
||||
if is_target_2d_gui:
|
||||
if target_type == "SCENE_ROOT":
|
||||
return True
|
||||
elif is_target_2d_gui:
|
||||
print(f"✅ 2D GUI元素 {dragged_item.text(0)} 可以拖拽到2D GUI父节点 {target_item.text(0)}")
|
||||
return True
|
||||
elif is_target_3d_gui:
|
||||
@ -1739,6 +1734,20 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
"""处理拖动事件"""
|
||||
indicator_pos = self.dropIndicatorPosition()
|
||||
indicator_str = "Unknown"
|
||||
|
||||
if indicator_pos == QAbstractItemView.DropIndicatorPosition.OnItem:
|
||||
indicator_str = "OnItem"
|
||||
elif indicator_pos == QAbstractItemView.DropIndicatorPosition.AboveItem:
|
||||
indicator_str = "AboveItem"
|
||||
elif indicator_pos == QAbstractItemView.DropIndicatorPosition.BelowItem:
|
||||
indicator_str = "BelowItem"
|
||||
elif indicator_pos == QAbstractItemView.DropIndicatorPosition.OnViewport:
|
||||
indicator_str = "OnViewport"
|
||||
|
||||
print(f'indicator pos: {indicator_str} (value: {int(indicator_pos)})')
|
||||
|
||||
if event.source() != self:
|
||||
event.ignore()
|
||||
return
|
||||
@ -1789,7 +1798,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
if not selected_items:
|
||||
return
|
||||
|
||||
# 过滤掉不能删除的节点
|
||||
# 1. 过滤掉不能删除的节点
|
||||
deletable_items = []
|
||||
for item in selected_items:
|
||||
node_type = item.data(0, Qt.UserRole + 1)
|
||||
@ -1805,7 +1814,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
QMessageBox.information(self, "提示", "没有可删除的节点")
|
||||
return
|
||||
|
||||
# 确认删除
|
||||
# 2. 确认删除
|
||||
item_count = len(deletable_items)
|
||||
if item_count == 1:
|
||||
message = f"确定要删除节点 \"{deletable_items[0].text(0)}\" 吗?"
|
||||
@ -1820,11 +1829,19 @@ class CustomTreeWidget(QTreeWidget):
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
# 执行删除
|
||||
# 默认选中场景根节点,通常是第一个顶级节点
|
||||
next_item_to_select = self.topLevelItem(0)
|
||||
|
||||
# 3. 执行删除循环
|
||||
deleted_count = 0
|
||||
for item in deletable_items:
|
||||
try:
|
||||
# 在删除前,记录其父节点,作为删除后的新选择
|
||||
# 选择最后一个被删除项的父节点作为新的焦点
|
||||
if item.parent():
|
||||
next_item_to_select = item.parent()
|
||||
panda_node = item.data(0, Qt.UserRole)
|
||||
|
||||
if panda_node:
|
||||
# 清理选择状态
|
||||
if (hasattr(self.world, 'selection') and
|
||||
@ -1840,15 +1857,32 @@ class CustomTreeWidget(QTreeWidget):
|
||||
if hasattr(panda_node, 'getPythonTag'):
|
||||
light_object = panda_node.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world, 'render_pipeline'):
|
||||
print(f'11111111111111111111111111,{light_object.casts_shadows}')
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
|
||||
# 从world列表中移除
|
||||
if hasattr(self.world, 'gui_elements') and panda_node in self.world.gui_elements:
|
||||
self.world.gui_elements.remove(panda_node)
|
||||
if hasattr(self.world, 'models') and panda_node in self.world.models:
|
||||
self.world.models.remove(panda_node)
|
||||
if hasattr(self.world, 'Spotlight') and panda_node in self.world.Spotlight:
|
||||
self.world.Spotlight.remove(panda_node)
|
||||
if hasattr(self.world, 'Pointlight') and panda_node in self.world.Pointlight:
|
||||
self.world.Pointlight.remove(panda_node)
|
||||
if hasattr(self.world, 'terrains') and panda_node in self.world.terrains:
|
||||
self.world.terrains.remove(panda_node)
|
||||
if hasattr(self.world, 'tilesets') and panda_node in self.world.tilesets:
|
||||
# self.world.tilesets.remove(panda_node)
|
||||
# 从 tilesets 列表中移除
|
||||
if hasattr(self.world, 'scene_manager'):
|
||||
tilesets_to_remove = []
|
||||
for i, tileset_info in enumerate(self.world.scene_manager.tilesets):
|
||||
if tileset_info['node'] == panda_node:
|
||||
tilesets_to_remove.append(i)
|
||||
|
||||
# 从后往前删除,避免索引问题
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del self.world.scene_manager.tilesets[i]
|
||||
|
||||
# 从Panda3D场景中移除
|
||||
panda_node.removeNode()
|
||||
@ -1872,7 +1906,144 @@ class CustomTreeWidget(QTreeWidget):
|
||||
# if hasattr(self.world, 'property_panel'):
|
||||
# self.world.property_panel.clearPropertyPanel()
|
||||
|
||||
print(f"✅ 已删除 {deleted_count} 个节点")
|
||||
# 4. 删除操作完成后,更新UI ---
|
||||
if deleted_count > 0:
|
||||
print(f"🎉 成功删除 {deleted_count} 个节点。正在更新UI...")
|
||||
|
||||
# 检查预备选择的节点是否还有效 (例如,父节点可能也一同被删了)
|
||||
# 如果next_item_to_select在树中找不到了,就退回到选择根节点
|
||||
if next_item_to_select and self.indexFromItem(next_item_to_select).isValid():
|
||||
new_selection_item = next_item_to_select
|
||||
else:
|
||||
# 如果之前的父节点也一并被删除了,就默认选择场景根节点
|
||||
new_selection_item = self.topLevelItem(0)
|
||||
|
||||
if new_selection_item:
|
||||
# 设置UI树的选择
|
||||
self.setCurrentItem(new_selection_item)
|
||||
# 获取对应的Panda3D节点
|
||||
new_panda_node = new_selection_item.data(0, Qt.UserRole)
|
||||
# 调用您提供的函数来更新选择状态和属性面板
|
||||
self.update_selection_and_properties(new_panda_node, new_selection_item)
|
||||
else:
|
||||
# 如果连根节点都没有了(例如清空场景),则清空选择
|
||||
self.update_selection_and_properties(None, None)
|
||||
|
||||
def delete_item(self, panda_node):
|
||||
"""删除指定节点 panda3D(node)- 优化和修复版本"""
|
||||
if not panda_node or panda_node.is_empty():
|
||||
print("ℹ️ 尝试删除一个空的或无效的节点,操作取消。")
|
||||
return
|
||||
|
||||
# --- 关键修复:在操作前,安全地获取节点名字 ---
|
||||
node_name_for_logging = panda_node.getName()
|
||||
|
||||
# 1. 寻找对应的Qt Item
|
||||
item = self.world.interface_manager.findTreeItem(panda_node, self._findSceneRoot())
|
||||
|
||||
# 场景清理(无论是否找到item,都应该执行)
|
||||
self._cleanup_panda_node_resources(panda_node)
|
||||
panda_node.removeNode()
|
||||
|
||||
# 如果没有找到item,说明UI已经移除或不同步,清理完Panda3D资源后即可退出
|
||||
if not item:
|
||||
print(f"✅ Panda3D节点 '{node_name_for_logging}' 已清理并移除。UI树中未找到对应项。")
|
||||
return
|
||||
|
||||
try:
|
||||
# 2. 过滤受保护节点
|
||||
node_type = item.data(0, Qt.UserRole + 1)
|
||||
if node_type == "SCENE_ROOT": # 相机检查已包含在panda_node判空中
|
||||
print(f"ℹ️ 节点 {item.text(0)} 是受保护节点,无法删除。")
|
||||
return
|
||||
|
||||
# 3. 从UI树中移除
|
||||
parent_for_next_selection = item.parent()
|
||||
if item.parent():
|
||||
item.parent().removeChild(item)
|
||||
else:
|
||||
index = self.indexOfTopLevelItem(item)
|
||||
if index >= 0:
|
||||
self.takeTopLevelItem(index)
|
||||
|
||||
print(f"✅ 成功删除节点: {node_name_for_logging}")
|
||||
|
||||
# 4. 更新UI
|
||||
print(f"🔄 正在更新UI...")
|
||||
if parent_for_next_selection and self.indexFromItem(parent_for_next_selection).isValid():
|
||||
new_selection_item = parent_for_next_selection
|
||||
else:
|
||||
new_selection_item = self.topLevelItem(0)
|
||||
|
||||
if new_selection_item:
|
||||
self.setCurrentItem(new_selection_item)
|
||||
new_panda_node_to_select = new_selection_item.data(0, Qt.UserRole)
|
||||
self.update_selection_and_properties(new_panda_node_to_select, new_selection_item)
|
||||
else:
|
||||
self.update_selection_and_properties(None, None)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 删除节点 {node_name_for_logging} 时发生意外错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _cleanup_panda_node_resources(self, panda_node):
|
||||
"""一个集中的辅助函数,用于清理与Panda3D节点相关的所有资源。"""
|
||||
if not panda_node or panda_node.is_empty():
|
||||
return
|
||||
try:
|
||||
# 清理选择状态
|
||||
if hasattr(self.world, 'selection') and self.world.selection.selectedNode == panda_node:
|
||||
self.world.selection.updateSelection(None)
|
||||
# 清理属性面板
|
||||
if hasattr(self.world, 'property_panel'):
|
||||
self.world.property_panel.removeActorForModel(panda_node)
|
||||
# 清理灯光
|
||||
if hasattr(panda_node, 'getPythonTag'):
|
||||
light_object = panda_node.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world, 'render_pipeline'):
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
# 从各种world管理列表中移除
|
||||
lists_to_check = ['gui_elements', 'models', 'Spotlight', 'Pointlight', 'terrains']
|
||||
for list_name in lists_to_check:
|
||||
if hasattr(self.world, list_name):
|
||||
world_list = getattr(self.world, list_name)
|
||||
if panda_node in world_list:
|
||||
world_list.remove(panda_node)
|
||||
# 特殊处理tilesets
|
||||
if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'tilesets'):
|
||||
tilesets_to_remove = [i for i, info in enumerate(self.world.scene_manager.tilesets) if
|
||||
info.get('node') == panda_node]
|
||||
for i in reversed(tilesets_to_remove):
|
||||
del self.world.scene_manager.tilesets[i]
|
||||
print(f"🧹 已清理节点 {panda_node.getName()} 的所有关联资源。")
|
||||
except Exception as e:
|
||||
# 即便这里出错,也要打印信息,但不要让整个删除流程中断
|
||||
print(f"⚠️ 清理节点 {panda_node.getName()} 资源时出错: {e}")
|
||||
|
||||
# def mousePressEvent(self, event):
|
||||
# """鼠标按下事件"""
|
||||
# if event.button() == Qt.LeftButton:
|
||||
# if self.currentItem():
|
||||
# print(f"self.currentItem() = {self.currentItem()}")
|
||||
# else:
|
||||
# print(f"self.currentItem() = None")
|
||||
#
|
||||
# # 调用父类处理其他事件
|
||||
# super().mousePressEvent(event)
|
||||
|
||||
def update_item_name(self, text, item):
|
||||
""" 树节点名字 """
|
||||
if not item:
|
||||
return
|
||||
try:
|
||||
# 正确的代码
|
||||
node = item.data(0, Qt.UserRole)
|
||||
|
||||
item.setText(0, text)
|
||||
node.setName(text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
# ==================== 辅助方法 ====================
|
||||
def _findSceneRoot(self):
|
||||
@ -1904,45 +2075,68 @@ class CustomTreeWidget(QTreeWidget):
|
||||
def add_node_to_tree_widget(self, node, parent_item, node_type):
|
||||
"""将node元素添加到树形控件"""
|
||||
|
||||
# BLACK_LIST 和依赖项导入保持不变
|
||||
BLACK_LIST = {'', '**', 'temp', 'collision'}
|
||||
from panda3d.core import CollisionNode, ModelRoot
|
||||
from PyQt5.QtWidgets import QTreeWidgetItem
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from panda3d.core import CollisionNode
|
||||
|
||||
def should_skip(node):
|
||||
name = node.getName()
|
||||
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):
|
||||
# 1. 修改内部函数,让它返回创建的节点
|
||||
def addNodeToTree(node, parentItem, force=False):
|
||||
"""内部递归函数,现在会返回创建的顶级节点项"""
|
||||
if not force and should_skip(node):
|
||||
return
|
||||
nodeItem = QTreeWidgetItem(parentItem,[node.getName()])
|
||||
nodeItem.setData(0,Qt.UserRole, node)
|
||||
return None # 如果跳过,返回None
|
||||
|
||||
nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
|
||||
nodeItem.setData(0, Qt.UserRole, node)
|
||||
nodeItem.setData(0, Qt.UserRole + 1, node_type)
|
||||
|
||||
for child in node.getChildren():
|
||||
addNodeToTree(child,nodeItem,force = False)
|
||||
# 递归调用,但我们只关心顶级的nodeItem
|
||||
addNodeToTree(child, nodeItem, force=False)
|
||||
|
||||
return nodeItem # <-- 新增:返回创建的QTreeWidgetItem
|
||||
|
||||
def should_skip(node):
|
||||
name = node.getName()
|
||||
return name in BLACK_LIST or name.startswith('__') or isinstance(node.node(), CollisionNode) or isinstance(
|
||||
node.node(), ModelRoot) or name == ""
|
||||
|
||||
# 使用一个变量来确保无论哪个分支都有返回值
|
||||
new_qt_item = None
|
||||
node_name = ""
|
||||
|
||||
try:
|
||||
from PyQt5.QtWidgets import QTreeWidgetItem
|
||||
from PyQt5.QtCore import Qt
|
||||
if node_type == "IMPORTED_MODEL_NODE":
|
||||
node_name = node.getTag("file") if hasattr(node, 'getTag') else "model"
|
||||
addNodeToTree(node, parent_item, force=True)
|
||||
# getTag('file') 可能是你自己设置的tag,这里假设它存在
|
||||
node_name = node.getTag("file") if hasattr(node, 'getTag') and node.hasTag("file") else node.getName()
|
||||
|
||||
# 2. 接收 addNodeToTree 的返回值
|
||||
new_qt_item = addNodeToTree(node, parent_item, force=True)
|
||||
|
||||
else:
|
||||
node_name = node.getName() if hasattr(node, 'getName') else "node"
|
||||
new_qt_item = QTreeWidgetItem(parent_item, [node_name])
|
||||
new_qt_item.setData(0, Qt.UserRole, node)
|
||||
new_qt_item.setData(0, Qt.UserRole + 1, node_type)
|
||||
|
||||
# 展开父节点
|
||||
if hasattr(parent_item, 'setExpanded'):
|
||||
parent_item.setExpanded(True)
|
||||
# 确保 new_qt_item 成功创建后再继续操作
|
||||
if new_qt_item:
|
||||
# 展开父节点
|
||||
if hasattr(parent_item, 'setExpanded'):
|
||||
parent_item.setExpanded(True)
|
||||
|
||||
print(f"✅ Qt树节点添加成功: {node_name}")
|
||||
return new_qt_item
|
||||
print(f"✅ Qt树节点添加成功: {node_name}")
|
||||
return new_qt_item
|
||||
else:
|
||||
# 如果 addNodeToTree 因为 should_skip 返回了 None
|
||||
print(f"ℹ️ 节点 {node_name} 被跳过,未添加到树中。")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"❌ 添加node到树形控件失败: {str(e)}")
|
||||
traceback.print_exc() # 打印更详细的错误堆栈,方便调试
|
||||
return None
|
||||
|
||||
def update_selection_and_properties(self, node, qt_item):
|
||||
@ -2016,7 +2210,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
node_type = item.data(0, Qt.UserRole + 1)
|
||||
|
||||
# 场景根节点和普通场景节点可以作为父节点
|
||||
if node_type in self.gui_3d_types and self.scene_3d_types:
|
||||
if node_type in self.valid_3d_parent_types:
|
||||
return True
|
||||
|
||||
# # 模型节点也可以作为父节点
|
||||
|
||||
Loading…
Reference in New Issue
Block a user