From fc2550ce03afd7697560ca571c6fd85383db016e Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Thu, 18 Sep 2025 17:44:32 +0800 Subject: [PATCH] =?UTF-8?q?=E8=84=9A=E6=9C=AC=E4=BF=9D=E5=AD=98=E5=92=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/rpcore/native/__init__.py | 1 - core/script_system.py | 52 ++- core/selection.py | 76 +++- main.py | 2 +- scene/scene_manager.py | 415 ++++++++++++++++--- ui/property_panel.py | 110 +++-- ui/widgets.py | 4 +- 7 files changed, 544 insertions(+), 116 deletions(-) diff --git a/RenderPipelineFile/rpcore/native/__init__.py b/RenderPipelineFile/rpcore/native/__init__.py index 16fa5411..a2f219a3 100644 --- a/RenderPipelineFile/rpcore/native/__init__.py +++ b/RenderPipelineFile/rpcore/native/__init__.py @@ -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") diff --git a/core/script_system.py b/core/script_system.py index 2191077c..d9aa465c 100644 --- a/core/script_system.py +++ b/core/script_system.py @@ -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, []) diff --git a/core/selection.py b/core/selection.py index c84b8eee..9e497bb7 100644 --- a/core/selection.py +++ b/core/selection.py @@ -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: diff --git a/main.py b/main.py index c52d28c4..77e82536 100644 --- a/main.py +++ b/main.py @@ -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) diff --git a/scene/scene_manager.py b/scene/scene_manager.py index cd031d65..c1243ec2 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -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: diff --git a/ui/property_panel.py b/ui/property_panel.py index d37a91b2..ad927e1e 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -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): diff --git a/ui/widgets.py b/ui/widgets.py index e6ef58f6..e623621f 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -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元素建立父子关系")