脚本保存和移除

This commit is contained in:
Hector 2025-09-18 17:44:32 +08:00
parent 7a621a3a51
commit fc2550ce03
7 changed files with 544 additions and 116 deletions

View File

@ -73,7 +73,6 @@ 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")

View File

@ -253,7 +253,7 @@ class ScriptLoader:
for component in components_to_remove:
self.script_manager.remove_script_from_object(component.game_object, script_name)
# 从sys.modules中移除
module = self.loaded_modules[script_name]
if module.__name__ in sys.modules:
@ -696,18 +696,62 @@ class {class_name}(ScriptBase):
return False
script_components = self.object_scripts[game_object]
removed = False
for component in script_components[:]: # 复制列表以避免修改时出错
if component.script_instance.__class__.__name__ == script_name:
# 从引擎移除
self.engine.remove_script_component(component)
# 从对象脚本列表移除
script_components.remove(component)
removed = True
print(f"✓ 从对象 {game_object.getName()} 移除脚本: {script_name}")
return True
if not script_components:
del self.object_scripts[game_object]
# 更新节点上保存的脚本信息标签
if removed:
self._update_node_script_tags_after_removal(game_object, script_name)
return False
return removed
def _update_node_script_tags_after_removal(self, game_object, removed_script_name):
"""在移除脚本后更新节点标签"""
try:
# 获取对象上剩余的脚本
remaining_scripts = self.get_scripts_on_object(game_object)
if not remaining_scripts:
# 如果没有其他脚本,清除所有脚本标签
if game_object.hasTag("has_scripts"):
game_object.clearTag("has_scripts")
if game_object.hasTag("scripts_info"):
game_object.clearTag("scripts_info")
print(f"✓ 清除节点 {game_object.getName()} 的所有脚本标签")
else:
# 如果还有其他脚本,更新脚本信息标签
script_info_list = []
for script_component in remaining_scripts:
script_name = script_component.script_name
script_class = script_component.script_instance.__class__
script_file = self.loader.find_script_file(script_name) or ""
script_info_list.append({
"name": script_name,
"file": script_file
})
import json
game_object.setTag("has_scripts", "true")
game_object.setTag("scripts_info", json.dumps(script_info_list, ensure_ascii=False))
print(f"✓ 更新节点 {game_object.getName()} 的脚本标签信息,剩余 {len(script_info_list)} 个脚本")
except Exception as e:
print(f"更新节点标签失败: {e}")
def get_scripts_on_object(self, game_object) -> List[ScriptComponent]:
"""获取对象上的所有脚本"""
return self.object_scripts.get(game_object, [])

View File

@ -1926,15 +1926,6 @@ class SelectionSystem:
node_name = nodePath.getName()
print(f"新选择的节点: {node_name}")
# 检查是否为双击
is_double_click = self.checkDoubleClick(nodePath)
if is_double_click:
print(f"检测到双击 {node_name},执行聚焦")
# 双击时直接执行聚焦,不执行选择逻辑
self.focusCameraOnSelectedNodeAdvanced()
print("=== 选择状态更新完成 ===\n")
return # 直接返回,不执行下面的选择逻辑
self.selectedNode = nodePath
# 添加兼容性属性
self.selectedObject = nodePath
@ -1985,6 +1976,45 @@ class SelectionSystem:
import traceback
traceback.print_exc()
def _updateSelectionVisuals(self, nodePath):
"""更新选择的视觉效果(选择框和坐标轴)"""
try:
if nodePath and not nodePath.isEmpty():
node_name = nodePath.getName()
print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
# 创建选择框
print("创建选择框...")
self.createSelectionBox(nodePath)
if self.selectionBox:
box_name = "Unknown"
if self.selectionBox and not self.selectionBox.isEmpty():
box_name = self.selectionBox.getName()
print(f"✓ 选择框创建成功: {box_name}")
else:
print("× 选择框创建失败")
# 创建坐标轴
print("创建坐标轴...")
self.createGizmo(nodePath)
if self.gizmo:
gizmo_name = "Unknown"
if self.gizmo and not self.gizmo.isEmpty():
gizmo_name = self.gizmo.getName()
print(f"✓ 坐标轴创建成功: {gizmo_name}")
else:
print("× 坐标轴创建失败")
print(f"✓ 选中了节点: {node_name}")
else:
print("清除选择...")
self.clearSelectionBox()
self.clearGizmo()
print("✓ 取消选择")
except Exception as e:
print(f"更新选择视觉效果失败: {e}")
def getSelectedNode(self):
"""获取当前选中的节点"""
return self.selectedNode
@ -2529,6 +2559,7 @@ class SelectionSystem:
# 检查是否为双击(同一节点且在时间阈值内)
is_double_click = (self._last_clicked_node == target_node and
target_node is not None and
current_time - self._last_click_time < self._double_click_threshold)
if is_double_click:
@ -2539,8 +2570,12 @@ class SelectionSystem:
# 无论是点击模型还是坐标轴,都执行聚焦
if target_node and not target_node.isEmpty():
print(f"双击聚焦到节点: {target_node.getName()}")
# 执行聚焦
self.focusCameraOnSelectedNodeAdvanced()
if self.selectedNode != target_node:
self.updateSelection(target_node)
else:
self.focusCameraOnSelectedNodeAdvanced()
else:
print("双击事件:没有有效的目标节点")
# 重置状态以避免三击等误触发
self._last_click_time = 0
@ -2598,21 +2633,20 @@ class SelectionSystem:
import time
current_time = time.time()
# 检查节点和时间
time_diff = current_time - self._last_click_time
is_same_node = (self._last_clicked_node == nodePath)
# 必须是同一节点且在时间阈值内
is_double_click = (self._last_clicked_node == nodePath and
nodePath is not None and
current_time - self._last_click_time < self._double_click_threshold)
# 如果是同一节点且在时间阈值内,认为是双击
if is_same_node and time_diff < self._double_click_threshold:
# 只有在双击时才重置状态
if is_double_click:
# 重置状态
self._last_click_time = 0
self._last_clicked_node = None
return True
else:
# 只有在非双击情况下才更新状态
if not is_same_node:
self._last_click_time = current_time
self._last_clicked_node = nodePath
# 更新状态
self._last_click_time = current_time
self._last_clicked_node = nodePath
return False
except Exception as e:

View File

@ -235,7 +235,7 @@ class MyWorld(CoreWorld):
"""创建2D GUI文本输入框"""
return self.gui_manager.createGUIEntry(pos, placeholder, size)
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5):
def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=1):
"""创建3D空间文本"""
return self.gui_manager.createGUI3DText(pos, text, size)

View File

@ -9,6 +9,7 @@
import os
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTreeWidgetItem
from panda3d.core import (
ModelPool, ModelRoot, Filename, NodePath, GeomNode, Material, Vec4, Vec3,
MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere,
@ -22,6 +23,7 @@ from pathlib import Path
from panda3d.egg import EggData, EggVertexPool
from direct.actor.Actor import Actor
from QPanda3D.Panda3DWorld import get_render_pipeline
from RenderPipelineFile.rpplugins.smaa.jitters import halton_seq
from scene import util
class CesiumIntegration:
@ -141,24 +143,20 @@ class SceneManager:
print("加载模型失败")
return None
# 设置模型名称
model_name = os.path.basename(filepath)
# 确保名称有效
if not model_name:
model_name = "imported_model"
model.setName(model_name)
# 使用安全方法将模型添加到场景
#self._safeReparentTo(model, self.world.render)
# 将模型添加到场景
model.reparentTo(self.world.render)
# 设置模型名称
model_name = os.path.basename(filepath)
model.setName(model_name)
# 将模型添加到场景
model.reparentTo(self.world.render)
# 保存原始路径和转换后的路径
model.setTag("model_path", filepath)
model.setTag("original_path", original_filepath)
@ -166,18 +164,31 @@ class SceneManager:
model.setTag("converted_from", os.path.splitext(original_filepath)[1])
model.setTag("converted_to_glb", "true")
# 可选的单位转换主要针对FBX
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
#print("应用FBX单位转换厘米到米...")
self._applyUnitConversion(model, 0.01)
# 特殊处理FBX模型
if filepath.lower().endswith('.fbx'):
print("检测到FBX模型应用特殊处理...")
# 智能缩放标准化处理FBX子节点的大缩放值
if normalize_scales and filepath.lower().endswith('.fbx'):
#print("标准化FBX模型缩放层级...")
self._normalizeModelScales(model)
# 将模型缩放设置为原来的1/100
model.setScale(0.01)
print("设置模型缩放为 0.01 (原始大小的1/100)")
# 设置模型旋转为 (0, 90, 0)
model.setHpr(0, 90, 0)
print("设置模型旋转为 (0, 90, 0)")
# # 可选的单位转换主要针对FBX
# if apply_unit_conversion and filepath.lower().endswith('.fbx'):
# #print("应用FBX单位转换厘米到米...")
# self._applyUnitConversion(model, 0.01)
#
# # 智能缩放标准化处理FBX子节点的大缩放值
# if normalize_scales and filepath.lower().endswith('.fbx'):
# #print("标准化FBX模型缩放层级...")
# self._normalizeModelScales(model)
# 调整模型位置到地面
self._adjustModelToGround(model)
model.setPos(0,0,0)
#self._adjustModelToGround(model)
# 创建并设置基础材质
print("\n=== 开始设置材质 ===")
@ -887,9 +898,16 @@ class SceneManager:
"position": list(gui_node.getPos()),
"rotation": list(gui_node.getHpr()),
"scale": list(gui_node.getScale()),
"tags": {}
"tags": {},
"parent_name":None
}
parent = gui_node.getParent()
if parent and not parent.isEmpty():
parent_name = parent.getName()
if parent_name not in ["render","aspect2d","render2d"]:
gui_info["parent_name"] = parent_name
# 收集所有标签仅对NodePath类型的对象
if hasattr(gui_node, 'getTagNames'):
for tag in gui_node.getTagNames():
@ -1205,6 +1223,30 @@ class SceneManager:
import json
node.setTag("info_panel_data", json.dumps(panel_data, ensure_ascii=False))
if hasattr(self.world,'script_manager') and self.world.script_manager:
script_manager = self.world.script_manager
scripts = script_manager.get_scripts_on_object(node)
if scripts:
node.setTag("has_scripts", "true")
script_info_list = []
for script_component in scripts:
script_name = script_component.script_name
print(f"保存脚本信息: {script_name}")
# 获取脚本类的文件路径
script_class = script_component.script_instance.__class__
script_file = self._get_script_file_path(script_class, script_name)
script_info_list.append({
"name": script_name,
"file": script_file
})
# 将脚本信息保存为JSON字符串
import json
node.setTag("scripts_info", json.dumps(script_info_list, ensure_ascii=False))
print(f"为节点 {node.getName()} 保存了 {len(script_info_list)} 个脚本")
try:
print("--- 打印当前场景图 (render) ---")
self.world.render.ls()
@ -1459,6 +1501,51 @@ class SceneManager:
nodePath.setScale(scale)
print(f"{indent}恢复缩放: {scale}")
if nodePath.hasTag("has_scripts") and nodePath.getTag("has_scripts") == "true":
if hasattr(self.world,'script_manager') and self.world.script_manager:
try:
import json
scripts_info = json.loads(nodePath.getTag("scripts_info"))
print(f"节点 {nodePath.getName()} 需要重新挂载 {len(scripts_info)} 个脚本")
script_manager = self.world.script_manager
for script_info in scripts_info:
script_name = script_info["name"]
script_file = script_info.get("file","")
print(f"尝试重新挂载脚本{script_name}from {script_file}")
if script_name not in script_manager.loader.script_classes:
if script_file and os.path.exists(script_file):
print(f"从文件加载脚本:{script_file}")
loaded_class = script_manager.load_script_from_file(script_file)
if loaded_class is None:
print(f"从文件加载脚本失败{script_file}")
script_path = self._find_scrip_in_directory(script_name)
if script_path:
print(f"从目录找到脚本并加载{script_path}")
script_manager.load_script_from_file(script_path)
else:
script_path = self._find_script_in_directory(script_name)
if script_path:
print(f"从目录找到脚本并加载: {script_path}")
script_manager.load_script_from_file(script_path)
else:
print(f"找不到脚本文件: {script_name}")
if script_name in script_manager.loader.script_classes:
script_component = script_manager.add_script_to_object(nodePath,script_name)
if script_component:
print(f"成功为 {nodePath.getName()} 添加脚本: {script_name}")
else:
print(f"{nodePath.getName()} 添加脚本失败: {script_name}")
else:
print(f"脚本 {script_name} 不可用,跳过挂载")
except Exception as e:
print(f"重新挂载脚本失败: {e}")
import traceback
traceback.print_exc()
# 恢复材质属性
def parseColor(color_str):
"""解析颜色字符串为Vec4"""
@ -1554,11 +1641,11 @@ class SceneManager:
self._fixModelStructure(nodePath)
if self.world.property_panel._hasCollision(nodePath):
print(f"{indent}模型{nodePath.getName()}已有碰撞体,跳过碰撞体设置")
else:
print(f"{indent}为模型{nodePath.getName()}设置碰撞检测")
self.setupCollision(nodePath)
# if self.world.property_panel._hasCollision(nodePath):
# print(f"{indent}模型{nodePath.getName()}已有碰撞体,跳过碰撞体设置")
# else:
# print(f"{indent}为模型{nodePath.getName()}设置碰撞检测")
# self.setupCollision(nodePath)
self.models.append(nodePath)
# 递归处理子节点
@ -1599,7 +1686,7 @@ class SceneManager:
# 更新场景树
#self.updateSceneTree()
# self._get_tree_widget().create_model_items(scene)
#self._get_tree_widget().create_model_items(scene)
print(f"加载完成GUI元素数量: {len(self.world.gui_elements)}")
if len(self.world.gui_elements) > 0:
@ -1653,6 +1740,18 @@ class SceneManager:
print(f"开始重建 {len(gui_data)} 个GUI元素...")
processed_names = set()
created_elements = {}
#存储原始的缩放和位置信息,用于后续计算
element_original_data = {}
# 第一遍:收集所有元素信息
for i, gui_info in enumerate(gui_data):
name = gui_info.get("name", f"gui_element_{i}")
element_original_data[name] = {
"scale": gui_info.get("scale", [1, 1, 1]),
"position": gui_info.get("position", [0, 0, 0]),
"parent_name": gui_info.get("parent_name")
}
pos = (0, 0, 0)
for i, gui_info in enumerate(gui_data):
@ -1668,6 +1767,7 @@ class SceneManager:
bg_image_path = gui_info.get("bg_image_path", "") # 背景图片路径
panel_id = gui_info.get("panel_id", name) # 信息面板ID
panel_data = gui_info.get("panel_data", None) # 面板数据
parent_name = gui_info.get("parent_name")
# 检查是否已经处理过同名元素
if name in processed_names:
@ -1684,63 +1784,83 @@ class SceneManager:
print(f" 背景图片路径: {bg_image_path}")
print(f"视频路径:{video_path}")
absolute_position = list(position)
absolute_scale = list(scale)
if parent_name and parent_name in element_original_data:
parent_data = element_original_data[parent_name]
parent_scale = parent_data["scale"]
if gui_type in ["3d_text", "3d_image", "button", "label", "entry", "2d_image",
"2d_video_screen"]:
# 位置需要乘以父级缩放来得到绝对位置
for j in range(min(len(absolute_position), len(parent_scale))):
absolute_position[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]
# 缩放需要乘以父级缩放来得到绝对缩放
for j in range(min(len(absolute_scale), len(parent_scale))):
absolute_scale[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0]
print(f" 绝对位置: {absolute_position}")
print(f" 绝对缩放: {absolute_scale}")
# 根据类型创建相应的GUI元素
new_element = None
if gui_type == "button" and hasattr(gui_manager, 'createGUIButton'):
new_element = gui_manager.createGUIButton(
pos=tuple(position),
pos=tuple(absolute_position),
text=text,
size=scale[0] if scale and len(scale) > 0 else 1.0
size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0
)
elif gui_type == "label" and hasattr(gui_manager, 'createGUILabel'):
scale_value = scale[0] if scale and len(scale) > 0 else 1.0
scale_value = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0
new_element = gui_manager.createGUILabel(
pos=tuple(position),
pos=tuple(absolute_position),
text=text,
size=scale_value
)
elif gui_type == "entry" and hasattr(gui_manager, 'createGUIEntry'):
new_element = gui_manager.createGUIEntry(
pos=tuple(position),
pos=tuple(absolute_position),
placeholder=text,
size=scale[0] if scale and len(scale) > 0 else 1.0
size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0
)
elif gui_type == "2d_image" and hasattr(gui_manager, 'createGUI2DImage'):
scale_value = scale[0] if scale and len(scale) > 0 else 0.2
scale_value = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.2
new_element = gui_manager.createGUI2DImage(
pos=tuple(position),
pos=tuple(absolute_position),
image_path=image_path,
size=scale_value*0.2
)
elif gui_type == "3d_text" and hasattr(gui_manager,'createGUI3DText'):
size = scale[0] if scale and len(scale) > 0 else 0.5
size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5
new_element = gui_manager.createGUI3DText(
pos=tuple(position),
pos=tuple(absolute_position),
text=text,
size=size
)
elif gui_type == "3d_image" and hasattr(gui_manager, 'createGUI3DImage'):
# 处理3D图像
# 根据缩放值的数量处理尺寸
if len(scale) >= 3:
size = (scale[0] * 2, scale[1] * 2)
elif len(scale) >= 2:
size = (scale[0] * 2, scale[1] * 2)
if len(absolute_scale) >= 3:
size = (absolute_scale[0] * 2, absolute_scale[1] * 2)
elif len(absolute_scale) >= 2:
size = (absolute_scale[0] * 2, absolute_scale[1] * 2)
elif len(scale) >= 1:
size = (scale[0] * 2, scale[0] * 2)
size = (absolute_scale[0] * 2, absolute_scale[0] * 2)
else:
size = (1.0, 1.0)
new_element = gui_manager.createGUI3DImage(
pos=tuple(position),
pos=tuple(absolute_position),
image_path=image_path,
size=size
)
elif gui_type == "video_screen" and hasattr(gui_manager,'createVideoScreen'):
new_element = gui_manager.createVideoScreen(
pos=tuple(position),
size=scale,
pos=tuple(absolute_position),
size=absolute_scale,
video_path=video_path
)
if video_path and new_element and hasattr(gui_manager, 'loadVideoFile'):
@ -1754,8 +1874,8 @@ class SceneManager:
elif gui_type == "2d_video_screen" and hasattr(gui_manager,'createGUI2DVideoScreen'):
new_element = gui_manager.createGUI2DVideoScreen(
pos=tuple(position),
size=scale,
pos=tuple(absolute_position),
size=absolute_scale,
video_path=video_path
)
elif gui_type in ["info_panel", "info_panel_3d"]:
@ -1836,9 +1956,146 @@ class SceneManager:
elif hasattr(new_element, '_tags'):
new_element._tags.update(tags)
created_elements[name] = new_element
# 重新挂载脚本(如果有的话)
# 在 _recreateGUIElementsFromData 方法中找到重新挂载脚本的部分,替换为以下代码:
# 重新挂载脚本(如果有的话)
# if "scripts" in gui_info and hasattr(self.world,
# 'script_manager') and self.world.script_manager:
# script_manager = self.world.script_manager
# for script_info in gui_info["scripts"]:
# script_name = script_info["name"]
# script_file = script_info.get("file", "")
#
# print(f"尝试重新挂载脚本: {script_name} from {script_file}")
#
# # 检查脚本是否已加载
# if script_name not in script_manager.loader.script_classes:
# # 如果脚本未加载,尝试从保存的文件路径加载
# if script_file and os.path.exists(script_file):
# print(f"从文件加载脚本: {script_file}")
# loaded_class = script_manager.load_script_from_file(script_file)
# if loaded_class is None:
# print(f"从文件加载脚本失败: {script_file}")
# # 如果从文件加载失败,尝试在脚本目录中查找
# script_path = self._find_script_in_directory(script_name)
# if script_path:
# print(f"从目录找到脚本并加载: {script_path}")
# script_manager.load_script_from_file(script_path)
# else:
# # 如果没有文件路径或文件不存在,尝试在脚本目录中查找
# script_path = self._find_script_in_directory(script_name)
# if script_path:
# print(f"从目录找到脚本并加载: {script_path}")
# script_manager.load_script_from_file(script_path)
# else:
# print(f"找不到脚本文件: {script_name}")
#
# # 为元素添加脚本
# script_component = script_manager.add_script_to_object(new_element, script_name)
# if script_component:
# print(f"成功为 {name} 添加脚本: {script_name}")
# else:
# print(f"为 {name} 添加脚本失败: {script_name}")
print(f"GUI元素重建成功: {name}")
else:
print(f"无法重建GUI元素: {name} (类型: {gui_type})")
except Exception as e:
print(f"重建GUI元素失败 {name}: {e}")
import traceback
traceback.print_exc()
continue
# 第二遍设置父子级关系并更新Qt树
print("开始设置父子级关系...")
try:
# 创建父子级关系映射
parent_child_map = {}
for gui_info in gui_data:
name = gui_info.get("name")
parent_name = gui_info.get("parent_name")
if name and parent_name and parent_name in created_elements:
parent_child_map[name] = parent_name
print(f"父子级关系映射: {parent_name} -> {name}")
# 按正确的顺序设置父子级关系并更新Qt树
tree_widget = self._get_tree_widget()
if tree_widget:
# 先将所有元素添加到Qt树中
qt_tree_items = {}
for name, element in created_elements.items():
# 尝试在Qt树中找到对应的项如果找不到则创建
qt_item = self._findOrCreateQtTreeItem(tree_widget, element, name)
if qt_item:
qt_tree_items[name] = qt_item
# 然后设置父子级关系
for child_name, parent_name in parent_child_map.items():
try:
if child_name in created_elements and parent_name in created_elements:
child_element = created_elements[child_name]
parent_element = created_elements[parent_name]
# 设置父子级关系
if hasattr(child_element, 'reparentTo'):
child_element.reparentTo(parent_element)
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
# 更新Qt树显示
if child_name in qt_tree_items and parent_name in qt_tree_items:
child_item = qt_tree_items[child_name]
parent_item = qt_tree_items[parent_name]
# 从当前位置移除子项
if child_item.parent():
child_item.parent().removeChild(child_item)
else:
# 如果是顶级项,从树中移除
index = tree_widget.indexOfTopLevelItem(child_item)
if index >= 0:
tree_widget.takeTopLevelItem(index)
# 将子项添加到新的父项下
parent_item.addChild(child_item)
print(f"Qt树更新: {child_name} 移动到 {parent_name}")
else:
print(f"元素 {child_name} 不支持 reparentTo 操作")
else:
print(f"元素未找到: 父级={parent_name}, 子级={child_name}")
except Exception as e:
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
continue
else:
# 如果没有tree_widget只设置父子级关系
for child_name, parent_name in parent_child_map.items():
try:
if child_name in created_elements and parent_name in created_elements:
child_element = created_elements[child_name]
parent_element = created_elements[parent_name]
# 设置父子级关系
if hasattr(child_element, 'reparentTo'):
child_element.reparentTo(parent_element)
print(f"成功设置父子级关系: {parent_name} -> {child_name}")
except Exception as e:
print(f"设置父子级关系失败 {parent_name} -> {child_name}: {e}")
continue
except Exception as e:
print(f"设置父子级关系时出错: {e}")
# 第三遍:重新挂载脚本
print("开始重新挂载脚本...")
for gui_info in gui_data:
try:
name = gui_info.get("name")
if name in created_elements and "scripts" in gui_info:
new_element = created_elements[name]
# 重新挂载脚本(如果有的话)
if "scripts" in gui_info and hasattr(self.world,
'script_manager') and self.world.script_manager:
@ -1877,15 +2134,8 @@ class SceneManager:
print(f"成功为 {name} 添加脚本: {script_name}")
else:
print(f"{name} 添加脚本失败: {script_name}")
print(f"GUI元素重建成功: {name}")
else:
print(f"无法重建GUI元素: {name} (类型: {gui_type})")
except Exception as e:
print(f"重建GUI元素失败 {name}: {e}")
import traceback
traceback.print_exc()
print(f"重新挂载脚本失败 {gui_info.get('name', 'unknown')}: {e}")
continue
print("GUI元素重建完成")
@ -1895,6 +2145,69 @@ class SceneManager:
import traceback
traceback.print_exc()
def _findOrCreateQtTreeItem(self, tree_widget, target_element, element_name):
"""在Qt树中查找或创建指定元素对应的项"""
try:
# 首先尝试查找现有的项
existing_item = self._findQtTreeItem(tree_widget, target_element)
if existing_item:
return existing_item
# 如果找不到,创建新的项
# 找到场景根节点
scene_root = None
for i in range(tree_widget.topLevelItemCount()):
top_item = tree_widget.topLevelItem(i)
if top_item.data(0, Qt.UserRole + 1) == "SCENE_ROOT":
scene_root = top_item
break
if not scene_root:
print("无法找到场景根节点")
return None
# 创建新的Qt树项
new_item = QTreeWidgetItem(scene_root, [element_name])
new_item.setData(0, Qt.UserRole, target_element)
new_item.setData(0, Qt.UserRole + 1, "SCENE_NODE") # 或根据元素类型设置适当的类型
print(f"为元素 {element_name} 创建了新的Qt树项")
return new_item
except Exception as e:
print(f"查找或创建Qt树项失败: {e}")
return None
def _findQtTreeItem(self, tree_widget, target_element):
"""在Qt树中查找指定元素对应的项"""
try:
def search_recursive(parent_item):
# 检查当前项
if parent_item:
item_element = parent_item.data(0, Qt.UserRole)
if item_element == target_element:
return parent_item
# 递归检查子项
for i in range(parent_item.childCount()):
child_item = parent_item.child(i)
result = search_recursive(child_item)
if result:
return result
return None
# 从根节点开始搜索
root = tree_widget.invisibleRootItem()
for i in range(root.childCount()):
top_item = root.child(i)
result = search_recursive(top_item)
if result:
return result
return None
except Exception as e:
print(f"查找Qt树项失败: {e}")
return None
def _find_script_in_directory(self, script_name):
"""在脚本目录中查找脚本文件"""
try:

View File

@ -768,6 +768,7 @@ class PropertyPanelManager:
if spinbox and not spinbox.isHidden(): # 检查控件是否仍然存在且可见
# 检查对象是否仍然有效
spinbox.blockSignals(True)
spinbox.setKeyboardTracking(False) # 确保禁用键盘跟踪
spinbox.setValue(value)
spinbox.blockSignals(False)
except RuntimeError as e:
@ -790,42 +791,79 @@ class PropertyPanelManager:
# 位置控件
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)
# 位置数值输入框
self.pos_x = self._createSafeSpinBox(-1000, 1000)
self.pos_y = self._createSafeSpinBox(-1000, 1000)
self.pos_z = self._createSafeSpinBox(-1000, 1000)
# X坐标
transform_layout.addWidget(QLabel("X:"), 1, 0)
self.pos_x = QLineEdit()
self.pos_x.setText(str(round(nodePath.getX(), 6)))
self.pos_x.editingFinished.connect(lambda: self._onPositionEditFinished(nodePath, 'x'))
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.world_pos_x = self._createSafeSpinBox(-10000, 10000, True) # 只读
self.world_pos_y = self._createSafeSpinBox(-10000, 10000, True)
self.world_pos_z = self._createSafeSpinBox(-10000, 10000, True)
# Y坐标
transform_layout.addWidget(QLabel("Y:"), 1, 2)
self.pos_y = QLineEdit()
self.pos_y.setText(str(round(nodePath.getY(), 6)))
self.pos_y.editingFinished.connect(lambda: self._onPositionEditFinished(nodePath, 'y'))
transform_layout.addWidget(self.pos_y, 1, 3)
transform_layout.addWidget(self.world_pos_x, 2, 1)
transform_layout.addWidget(self.world_pos_y, 2, 2)
transform_layout.addWidget(self.world_pos_z, 2, 3)
# Z坐标
transform_layout.addWidget(QLabel("Z:"), 1, 4)
self.pos_z = QLineEdit()
self.pos_z.setText(str(round(nodePath.getZ(), 6)))
self.pos_z.editingFinished.connect(lambda: self._onPositionEditFinished(nodePath, 'z'))
transform_layout.addWidget(self.pos_z, 1, 5)
return transform_layout
except Exception as e:
print(f"创建变换控件失败: {e}")
print(f"创建变换控件时出错: {e}")
return None
def _onPositionEditFinished(self, nodePath, axis):
"""位置编辑完成时的处理"""
try:
# 检查控件是否仍然有效
if not hasattr(self, f'pos_{axis}') or getattr(self, f'pos_{axis}') is None:
return
line_edit = getattr(self, f'pos_{axis}')
if line_edit is None or line_edit.isHidden():
return
# 获取文本并转换为数值
text = line_edit.text()
try:
new_value = float(text)
except ValueError:
print(f"无效的数值输入: {text}")
# 恢复原来的值
if axis == 'x':
line_edit.setText(str(round(nodePath.getX(), 6)))
elif axis == 'y':
line_edit.setText(str(round(nodePath.getY(), 6)))
elif axis == 'z':
line_edit.setText(str(round(nodePath.getZ(), 6)))
return
# 根据轴设置位置
if axis == 'x':
nodePath.setX(new_value)
elif axis == 'y':
nodePath.setY(new_value)
elif axis == 'z':
nodePath.setZ(new_value)
print(f"位置已更新: {nodePath.getName()} {axis.upper()} = {new_value}")
# 如果是坐标轴节点,需要更新坐标轴位置
if hasattr(self.world, 'selection_manager'):
selection_manager = self.world.selection_manager
if (hasattr(selection_manager, 'gizmoTarget') and
selection_manager.gizmoTarget == nodePath):
# 更新坐标轴位置
selection_manager._updateGizmoPositionAndOrientation()
except Exception as e:
print(f"更新位置时出错: {e}")
def _createSafeSpinBox(self, min_val, max_val, read_only=False):
"""创建安全的数值框"""
try:
@ -8805,9 +8843,9 @@ except Exception as e:
current_row += 1
# X, Y, Z 位置调整
self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2)
self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2)
self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2)
self.collision_pos_x = self._createCollisionSpinBox(-1000000, 1000000, 2)
self.collision_pos_y = self._createCollisionSpinBox(-1000000, 1000000, 2)
self.collision_pos_z = self._createCollisionSpinBox(-1000000, 1000000, 2)
# 只在没有现有碰撞时设置默认值否则由_loadCurrentCollisionParameters加载实际值
if not self._hasCollision(model):
@ -8868,7 +8906,7 @@ except Exception as e:
radius_label = QLabel("半径:")
layout.addWidget(radius_label, current_row, 0)
self.collision_radius = self._createCollisionSpinBox(0.1, 100, 2)
self.collision_radius = self._createCollisionSpinBox(0.01, 1000000, 2)
# 只在没有现有碰撞时设置默认值否则由_loadCurrentCollisionParameters加载实际值
if not self._hasCollision(model):
@ -8900,9 +8938,9 @@ except Exception as e:
current_row += 1
# 宽度、长度、高度
self.collision_width = self._createCollisionSpinBox(0.1, 100, 2)
self.collision_length = self._createCollisionSpinBox(0.1, 100, 2)
self.collision_height = self._createCollisionSpinBox(0.1, 100, 2)
self.collision_width = self._createCollisionSpinBox(0.01, 1000000, 2)
self.collision_length = self._createCollisionSpinBox(0.1, 1000000, 2)
self.collision_height = self._createCollisionSpinBox(0.1, 1000000, 2)
# 只在没有现有碰撞时设置默认值否则由_loadCurrentCollisionParameters加载实际值
if not self._hasCollision(model):
@ -8950,7 +8988,7 @@ except Exception as e:
radius_label = QLabel("半径:")
layout.addWidget(radius_label, current_row, 0)
self.collision_capsule_radius = self._createCollisionSpinBox(0.1, 100, 2)
self.collision_capsule_radius = self._createCollisionSpinBox(0.01, 1000000, 2)
# 只在没有现有碰撞时设置默认值否则由_loadCurrentCollisionParameters加载实际值
if not self._hasCollision(model):

View File

@ -1873,7 +1873,7 @@ class CustomTreeWidget(QTreeWidget):
elif is_dragged_3d_gui:
if is_target_3d_scene:
print(f"✅ 3D GUI元素 {dragged_item.text(0)} 可以拖拽到3D场景节点 {target_item.text(0)}")
return True
return False
elif is_target_2d_gui:
print(f"❌ 3D GUI元素 {dragged_item.text(0)} 不能拖拽到2D GUI元素 {target_item.text(0)}")
print(" 💡 提示: 3D GUI元素不能与2D GUI元素建立父子关系")
@ -1890,7 +1890,7 @@ class CustomTreeWidget(QTreeWidget):
elif is_dragged_3d_scene:
if is_target_3d_scene:
print(f"✅ 3D场景元素 {dragged_item.text(0)} 可以拖拽到3D场景节点 {target_item.text(0)}")
return True
return False
elif is_target_2d_gui:
print(f"❌ 3D场景元素 {dragged_item.text(0)} 不能拖拽到2D GUI元素 {target_item.text(0)}")
print(" 💡 提示: 3D场景元素不能与2D GUI元素建立父子关系")