diff --git a/gui/gui_manager.py b/gui/gui_manager.py index cf695161..7f7fdfb8 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -188,9 +188,12 @@ class GUIManager: button.setTag("gui_id", f"button_{len(self.gui_elements)}") button.setTag("gui_text", text) button.setTag("is_gui_element", "1") - button.setTag("is_scene_element", "1") + button.setTag("is_scene_element", "1") # 确保这个标签被设置 + button.setTag("saved_gui_type", "button") # 添加这个标签以确保兼容性 + button.setTag("gui_element_type","button") button.setTag("created_by_user", "1") button.setTag("gui_parent_type", "gui" if parent_gui_node else "3d") + button.setTag("name", button_name) button.setName(button_name) # 如果有GUI父节点,建立引用关系 @@ -200,6 +203,7 @@ class GUIManager: # 添加到GUI元素列表 self.gui_elements.append(button) + button.reparentTo(self.world.aspect2d) print(f"✅ 为 {parent_item.text(0)} 创建GUI按钮成功: {button_name}") diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 4f5544dd..3ed0eda8 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -179,7 +179,29 @@ class SceneManager: self.models.append(model) # 更新场景树 - self.updateSceneTree() + # 获取树形控件并添加到Qt树中 + tree_widget = self._get_tree_widget() + if tree_widget: + # 找到根节点项 + root_item = None + for i in range(tree_widget.topLevelItemCount()): + item = tree_widget.topLevelItem(i) + if item.text(0) == "render" or item.data(0, Qt.UserRole) == self.world.render: + root_item = item + break + + if root_item: + qt_item = tree_widget.add_node_to_tree_widget(model, root_item, "IMPORTED_MODEL_NODE") + if qt_item: + tree_widget.setCurrentItem(qt_item) + # 更新选择和属性面板 + tree_widget.update_selection_and_properties(model, qt_item) + print("✅ Qt树节点添加成功") + else: + print("⚠️ Qt树节点添加失败,但Panda3D对象已创建") + else: + print("⚠️ 未找到根节点项,无法添加到Qt树") + #self.updateSceneTree() print(f"=== 模型导入成功: {model_name} ===\n") return model @@ -702,10 +724,18 @@ class SceneManager: # ==================== 场景保存和加载 ==================== def saveScene(self, filename): - """保存场景到BAM文件""" + """保存场景到BAM文件 - 完整版,支持GUI元素""" try: print(f"\n=== 开始保存场景到: {filename} ===") + # 确保文件路径是规范化的 + filename = os.path.normpath(filename) + + # 确保目录存在 + directory = os.path.dirname(filename) + if directory and not os.path.exists(directory): + os.makedirs(directory) + # 存储需要临时隐藏的节点,以便保存后恢复 nodes_to_restore = [] @@ -727,19 +757,54 @@ class SceneManager: node.hide() print(f"临时隐藏选择框节点: {node.getName()}") - # 遍历所有模型,保存材质状态和变换信息 - for model in self.models: - # 保存变换信息(关键!) - model.setTag("transform_pos", str(model.getPos())) - model.setTag("transform_hpr", str(model.getHpr())) - model.setTag("transform_scale", str(model.getScale())) - print(f"保存模型 {model.getName()} 的变换信息:") - print(f" 位置: {model.getPos()}") - print(f" 旋转: {model.getHpr()}") - print(f" 缩放: {model.getScale()}") + # 收集所有需要保存的节点 + all_nodes = [] + all_nodes.extend(self.models) + all_nodes.extend(self.Spotlight) + all_nodes.extend(self.Pointlight) + + # 添加GUI元素节点(关键修改) + gui_elements = [] + if hasattr(self.world, 'gui_elements'): + # 过滤掉空的或重复的GUI元素 + unique_gui_elements = [] + seen_names = set() + + for elem in self.world.gui_elements: + if elem and not elem.isEmpty(): + elem_name = elem.getName() if hasattr(elem, 'getName') else str(id(elem)) + if elem_name not in seen_names: + unique_gui_elements.append(elem) + seen_names.add(elem_name) + + gui_elements = unique_gui_elements + all_nodes.extend(gui_elements) + print(f"找到 {len(gui_elements)} 个唯一的GUI元素待保存") + + # 添加tilesets节点 + for tileset_info in self.tilesets: + if tileset_info.get('node') and not tileset_info['node'].isEmpty(): + all_nodes.append(tileset_info['node']) + + # 添加Cesium tilesets节点 + for tileset_name, tileset_info in self.cesium_integration.tilesets.items(): + if tileset_info.get('node') and not tileset_info['node'].isEmpty(): + all_nodes.append(tileset_info['node']) + + + # 保存所有节点的信息 + for node in all_nodes: + if node.isEmpty(): + continue + + # 保存变换信息 + node.setTag("transform_pos", str(node.getPos())) + node.setTag("transform_hpr", str(node.getHpr())) + node.setTag("transform_scale", str(node.getScale())) + print(f"保存节点 {node.getName()} 的变换信息") # 获取当前状态 - state = model.getState() + state = node.getState() # 如果有材质属性,保存为标签 if state.hasAttrib(MaterialAttrib.getClassType()): @@ -747,26 +812,70 @@ class SceneManager: material = mat_attrib.getMaterial() if material: # 保存材质属性到标签 - model.setTag("material_ambient", str(material.getAmbient())) - model.setTag("material_diffuse", str(material.getDiffuse())) - model.setTag("material_specular", str(material.getSpecular())) - model.setTag("material_emission", str(material.getEmission())) - model.setTag("material_shininess", str(material.getShininess())) + node.setTag("material_ambient", str(material.getAmbient())) + node.setTag("material_diffuse", str(material.getDiffuse())) + node.setTag("material_specular", str(material.getSpecular())) + node.setTag("material_emission", str(material.getEmission())) + node.setTag("material_shininess", str(material.getShininess())) if material.hasBaseColor(): - model.setTag("material_basecolor", str(material.getBaseColor())) + node.setTag("material_basecolor", str(material.getBaseColor())) # 如果有颜色属性,保存为标签 - if state.hasAttrib(ColorAttrib.getClassType()): - color_attrib = state.getAttrib(ColorAttrib.getClassType()) - if not color_attrib.isOff(): - model.setTag("color", str(color_attrib.getColor())) + # if state.hasAttrib(ColorAttrib.getClassType()): + # color_attrib = state.getAttrib(ColorAttrib.getClassType()) + # if not color_attrib.isOff(): + # node.setTag("color", str(color_attrib.getColor())) + + # 保存特定类型节点的额外信息 + if node.hasTag("light_type"): + # 保存光源特定信息 + light_obj = node.getPythonTag("rp_light_object") + if light_obj: + node.setTag("light_energy", str(light_obj.energy)) + node.setTag("light_radius", str(getattr(light_obj, 'radius', 0))) + if hasattr(light_obj, 'fov'): + node.setTag("light_fov", str(light_obj.fov)) + elif node.hasTag("element_type"): + element_type = node.getTag("element_type") + if element_type == "cesium_tileset": + # 保存tileset特定信息 + if node.hasTag("tileset_url"): + node.setTag("saved_tileset_url", node.getTag("tileset_url")) + elif node.hasTag("gui_type") or node.hasTag("is_gui_element"): + # 保存GUI元素特定信息 + gui_type = node.getTag("gui_type") if node.hasTag("gui_type") else \ + node.getTag("saved_gui_type") if node.hasTag("saved_gui_type") else "unknown" + node.setTag("saved_gui_type", gui_type) + + # 特别记录3D文本的内容 + if gui_type == "3d_text": + text_content = "未知内容" + if node.hasTag("gui_text"): + text_content = node.getTag("gui_text") + elif hasattr(node.node(), 'getText'): + text_content = node.node().getText() + print(f"保存3D文本信息: {node.getName()} (内容: '{text_content}')") + + # 保存GUI元素的通用属性 + if hasattr(node, 'getPythonTag'): + # 保存任何Python标签数据 + for tag_name in node.getPythonTagKeys(): + try: + tag_value = node.getPythonTag(tag_name) + node.setTag(f"python_tag_{tag_name}", str(tag_value)) + except: + pass + print(f"保存GUI元素信息: {node.getName()} (类型: {gui_type})") + print(f"11111111111111{self.world.gui_elements}") try: print("--- 打印当前场景图 (render) ---") self.world.render.ls() print("---------------------------------") + # 保存场景 - success = self.world.render.writeBamFile(filename) + success = self.world.render.writeBamFile(Filename.fromOsSpecific(filename)) + #success_2d = self.world.render2d.writeBamFile(Filename.fromOsSpecific(filename)) if success: print(f"✓ 场景保存成功: {filename}") @@ -774,6 +883,7 @@ class SceneManager: print("✗ 场景保存失败") return success + finally: # 恢复之前隐藏的节点 for item in nodes_to_restore: @@ -792,28 +902,71 @@ class SceneManager: return False def loadScene(self, filename): - """从BAM文件加载场景""" + """从BAM文件加载场景 - 修复版""" try: print(f"\n=== 开始加载场景: {filename} ===") + # 确保文件路径是规范化的 + filename = os.path.normpath(filename) + + # 检查文件是否存在 + if not os.path.exists(filename): + print(f"场景文件不存在: {filename}") + return False + # 清除当前场景 print("\n清除当前场景...") for model in self.models: - model.removeNode() + if not model.isEmpty(): + model.removeNode() self.models.clear() - # 清理可能存在的辅助节点(坐标轴、选择框等) + for light in self.Spotlight: + if not light.isEmpty(): + light.removeNode() + self.Spotlight.clear() + + for light in self.Pointlight: + if not light.isEmpty(): + light.removeNode() + self.Pointlight.clear() + + # 清理tilesets + for tileset_info in self.tilesets: + if tileset_info['node'] and not tileset_info['node'].isEmpty(): + tileset_info['node'].removeNode() + self.tilesets.clear() + + # 清理Cesium tilesets + for tileset_name, tileset_info in list(self.cesium_integration.tilesets.items()): + if tileset_info['node'] and not tileset_info['node'].isEmpty(): + tileset_info['node'].removeNode() + self.cesium_integration.tilesets.clear() + + for gui in self.world.gui_elements: + if not gui.isEmpty(): + gui.removeNode() + self.world.gui_elements.clear() + + + # 清理可能存在的辅助节点 self._cleanupAuxiliaryNodes() # 加载场景 - scene = self.world.loader.loadModel(filename) + scene = self.world.loader.loadModel(Filename.fromOsSpecific(filename)) if not scene: + print("场景加载失败") return False - # 遍历场景中的所有模型节点 + # 用于存储处理后的灯光节点,避免重复处理 + processed_lights = [] + # 用于存储处理后的GUI元素,避免重复处理 + processed_gui_elements = [] + + # 遍历场景中的所有节点 def processNode(nodePath, depth=0): indent = " " * depth - print(f"{indent}处理节点: {nodePath.getName()}") + print(f"{indent}处理节点: {nodePath.getName()} (类型: {type(nodePath.node()).__name__})") # 跳过render节点的递归 if nodePath.getName() == "render" and depth > 0: @@ -830,68 +983,67 @@ class SceneManager: print(f"{indent}跳过相机节点: {nodePath.getName()}") return - # 跳过辅助节点(坐标轴和选择框) + # 跳过辅助节点 if nodePath.getName().startswith(("gizmo", "selectionBox")): print(f"{indent}跳过辅助节点: {nodePath.getName()}") return if nodePath.getName() in ['SceneRoot'] or \ - any(keyword in nodePath.getName() for keyword in ["Skybox","skybox"]): + any(keyword in nodePath.getName() for keyword in ["Skybox", "skybox"]): print(f"{indent}跳过环境节点:{nodePath.getName()}") return - if isinstance(nodePath.node(), ModelRoot): - print(f"{indent}找到模型根节点!") + # 检查是否是用户创建的场景元素 + is_scene_element = ( + nodePath.hasTag("is_scene_element") or + nodePath.hasTag("is_model_root") or + nodePath.hasTag("light_type") or + nodePath.hasTag("gui_type") or # 检查gui_type标签 + nodePath.hasTag("is_gui_element") or + nodePath.hasTag("saved_gui_type") + ) + + # 特殊处理:检查节点名称是否包含GUI相关关键词 + is_potential_gui = any(keyword in nodePath.getName().lower() for keyword in + ["gui", "button", "label", "entry", "image", "video", "screen", "text"]) + + if is_scene_element or is_potential_gui: + print(f"{indent}找到场景元素节点: {nodePath.getName()}") + + # 如果是潜在的GUI元素但没有标签,添加基本标签 + if is_potential_gui and not (nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element")): + print(f"{indent}为潜在GUI元素添加标签: {nodePath.getName()}") + nodePath.setTag("is_gui_element", "1") + nodePath.setTag("is_scene_element", "1") + # 尝试从名称推断类型 + name_lower = nodePath.getName().lower() + if "button" in name_lower: + nodePath.setTag("gui_type", "button") + elif "label" in name_lower: + nodePath.setTag("gui_type", "label") + elif "entry" in name_lower: + nodePath.setTag("gui_type", "entry") + elif "image" in name_lower: + nodePath.setTag("gui_type", "image") + elif "video" in name_lower or "screen" in name_lower: + nodePath.setTag("gui_type", "video_screen") + else: + nodePath.setTag("gui_type", "unknown") # 清除现有材质状态 nodePath.clearMaterial() nodePath.clearColor() - # 创建新材质 - material = Material() - - # 从标签恢复材质属性 - def parseColor(color_str): - """解析颜色字符串为Vec4""" - try: - # 移除LVecBase4f标记,只保留数值 - color_str = color_str.replace('LVecBase4f', '').strip('()') - r, g, b, a = map(float, color_str.split(',')) - return Vec4(r, g, b, a) - except: - return Vec4(1, 1, 1, 1) # 默认白色 - - if nodePath.hasTag("material_ambient"): - material.setAmbient(parseColor(nodePath.getTag("material_ambient"))) - if nodePath.hasTag("material_diffuse"): - material.setDiffuse(parseColor(nodePath.getTag("material_diffuse"))) - if nodePath.hasTag("material_specular"): - material.setSpecular(parseColor(nodePath.getTag("material_specular"))) - if nodePath.hasTag("material_emission"): - material.setEmission(parseColor(nodePath.getTag("material_emission"))) - if nodePath.hasTag("material_shininess"): - material.setShininess(float(nodePath.getTag("material_shininess"))) - if nodePath.hasTag("material_basecolor"): - material.setBaseColor(parseColor(nodePath.getTag("material_basecolor"))) - - # 应用材质 - nodePath.setMaterial(material) - - # 恢复颜色属性 - if nodePath.hasTag("color"): - nodePath.setColor(parseColor(nodePath.getTag("color"))) - - # 恢复变换信息(关键!) + # 恢复变换信息 def parseVec3(vec_str): """解析向量字符串为Vec3""" try: - # 移除LVecBase3f标记,只保留数值 vec_str = vec_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') x, y, z = map(float, vec_str.split(',')) return Vec3(x, y, z) except Exception as e: print(f"解析向量失败: {vec_str}, 错误: {e}") - return Vec3(0, 0, 0) # 默认值 + return Vec3(0, 0, 0) if nodePath.hasTag("transform_pos"): pos = parseVec3(nodePath.getTag("transform_pos")) @@ -908,13 +1060,143 @@ class SceneManager: nodePath.setScale(scale) print(f"{indent}恢复缩放: {scale}") - # 将模型重新挂载到render下 - nodePath.wrtReparentTo(self.world.render) + # 恢复材质属性 + def parseColor(color_str): + """解析颜色字符串为Vec4""" + try: + color_str = color_str.replace('LVecBase4f', '').strip('()') + r, g, b, a = map(float, color_str.split(',')) + return Vec4(r, g, b, a) + except: + return Vec4(1, 1, 1, 1) - # 为加载的模型设置碰撞检测 - self.setupCollision(nodePath) + # 创建并恢复材质 + material = Material() + material_changed = False - self.models.append(nodePath) + if nodePath.hasTag("material_ambient"): + material.setAmbient(parseColor(nodePath.getTag("material_ambient"))) + material_changed = True + + if nodePath.hasTag("material_diffuse"): + material.setDiffuse(parseColor(nodePath.getTag("material_diffuse"))) + material_changed = True + + if nodePath.hasTag("material_specular"): + material.setSpecular(parseColor(nodePath.getTag("material_specular"))) + material_changed = True + + if nodePath.hasTag("material_emission"): + material.setEmission(parseColor(nodePath.getTag("material_emission"))) + material_changed = True + + if nodePath.hasTag("material_shininess"): + material.setShininess(float(nodePath.getTag("material_shininess"))) + material_changed = True + + if nodePath.hasTag("material_basecolor"): + material.setBaseColor(parseColor(nodePath.getTag("material_basecolor"))) + material_changed = True + + if material_changed: + nodePath.setMaterial(material) + + # 恢复颜色属性 + if nodePath.hasTag("color"): + nodePath.setColor(parseColor(nodePath.getTag("color"))) + + # 处理特定类型的节点 + if nodePath.hasTag("light_type"): + light_type = nodePath.getTag("light_type") + print(f"{indent}检测到光源类型: {light_type}") + + # 检查是否已经处理过这个灯光 + if nodePath not in processed_lights: + # 重新创建RP光源对象 + if light_type == "spot_light": + self._recreateSpotLight(nodePath) + elif light_type == "point_light": + self._recreatePointLight(nodePath) + # 标记为已处理 + processed_lights.append(nodePath) + + elif nodePath.hasTag("element_type"): + element_type = nodePath.getTag("element_type") + if element_type == "cesium_tileset": + tileset_url = nodePath.getTag("saved_tileset_url") if nodePath.hasTag( + "saved_tileset_url") else "" + tileset_info = { + 'url': tileset_url, + 'node': nodePath, + 'position': nodePath.getPos(), + 'tiles': {} + } + self.tilesets.append(tileset_info) + self.cesium_integration.tilesets[nodePath.getName()] = tileset_info + + elif nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element") or nodePath.hasTag( + "saved_gui_type"): + # 获取GUI元素类型 + gui_type = "unknown" + if nodePath.hasTag("gui_type"): + gui_type = nodePath.getTag("gui_type") + elif nodePath.hasTag("saved_gui_type"): + gui_type = nodePath.getTag("saved_gui_type") + else: + gui_type = "unknown_gui_element" + + print(f"{indent}检测到GUI元素类型: {gui_type}") + + # 检查是否已经处理过这个GUI元素 + if nodePath not in processed_gui_elements: + # 重新创建GUI元素 + recreate_success = self._recreateGUIElement(nodePath) + + # 确保GUI元素被正确标记 + if not nodePath.hasTag("is_scene_element"): + nodePath.setTag("is_scene_element", "1") + + # 检查是否已经存在于gui_elements列表中,避免重复添加 + already_in_list = False + if hasattr(self.world, 'gui_elements'): + # 检查节点是否已经在列表中(通过名称或对象引用) + for existing_gui in self.world.gui_elements: + if existing_gui == nodePath or (hasattr(existing_gui, 'getName') and + hasattr(nodePath, 'getName') and + existing_gui.getName() == nodePath.getName()): + already_in_list = True + break + + # 只有当元素不在列表中时才添加 + if not already_in_list: + if hasattr(self.world, 'gui_elements'): + self.world.gui_elements.append(nodePath) + print( + f"{indent}GUI元素已添加到管理列表,当前列表大小: {len(self.world.gui_elements)}") + else: + print(f"{indent}GUI元素已存在于管理列表中,跳过添加") + + # 标记为已处理 + processed_gui_elements.append(nodePath) + print(f"{indent}GUI元素已处理并标记") + + # 将节点重新挂载到render下(如果需要) + # 注意:GUI元素可能需要挂载到特定的父节点上 + if nodePath.hasTag("gui_type") or nodePath.hasTag("is_gui_element"): + # GUI元素通常应该挂载到aspect2d或特定的GUI父节点上 + # 这里我们先保持原挂载关系 + pass + else: + # 其他节点确保挂载到render下 + if nodePath.getParent() != self.world.render and not nodePath.getName() in ["render", + "aspect2d", + "render2d"]: + nodePath.wrtReparentTo(self.world.render) + + # 为模型节点设置碰撞检测 + if nodePath.hasTag("is_model_root"): + self.setupCollision(nodePath) + self.models.append(nodePath) # 递归处理子节点 for child in nodePath.getChildren(): @@ -924,11 +1206,19 @@ class SceneManager: processNode(scene) # 移除临时场景节点 - scene.removeNode() + if not scene.isEmpty(): + scene.removeNode() # 更新场景树 self.updateSceneTree() + print(f"加载完成,GUI元素数量: {len(self.world.gui_elements)}") + if len(self.world.gui_elements) > 0: + print("GUI元素列表:") + for i, elem in enumerate(self.world.gui_elements): + print( + f" {i + 1}. {elem.getName()} (类型: {elem.getTag('gui_type') if elem.hasTag('gui_type') else 'unknown'})") + print("=== 场景加载完成 ===\n") return True @@ -938,6 +1228,446 @@ class SceneManager: traceback.print_exc() return False + def _recreate3DText(self, text_node): + """重新创建3D文本元素 - 使用gui_manager中的createGUI3DText方法""" + try: + print(f"重新创建3D文本: {text_node.getName()}") + + # 获取保存的文本内容 + text_content = "默认文本" + if text_node.hasTag("gui_text"): + text_content = text_node.getTag("gui_text") + elif text_node.hasTag("text"): + text_content = text_node.getTag("text") + + print(f"3D文本内容: {text_content}") + + # 获取位置 + pos = (0, 0, 0) + if text_node.hasTag("transform_pos"): + try: + pos_str = text_node.getTag("transform_pos") + pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + pos_values = [float(x.strip()) for x in pos_str.split(',')] + if len(pos_values) >= 3: + pos = (pos_values[0], pos_values[1], pos_values[2]) + except Exception as e: + print(f"恢复位置失败: {e}") + + # 获取尺寸 + size = 0.5 + if text_node.hasTag("transform_scale"): + try: + scale_str = text_node.getTag("transform_scale") + scale_str = scale_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + scales = [float(x.strip()) for x in scale_str.split(',')] + size = scales[0] if scales else 0.5 + except Exception as e: + print(f"恢复缩放失败: {e}") + + # 使用gui_manager中的createGUI3DText方法创建新的3D文本 + # 首先需要找到gui_manager + gui_manager = None + if hasattr(self.world, 'gui_manager'): + gui_manager = self.world.gui_manager + + if gui_manager and hasattr(gui_manager, 'createGUI3DText'): + # 记录创建前的GUI元素数量 + original_count = len(gui_manager.gui_elements) + + # 调用gui_manager的createGUI3DText方法创建新的3D文本 + new_text_node = gui_manager.createGUI3DText(pos=pos, text=text_content, size=size) + + if new_text_node: + # 如果返回的是列表(多选创建),取第一个 + if isinstance(new_text_node, list): + created_node = new_text_node[0] + else: + created_node = new_text_node + + print(f"3D文本重建完成: {created_node.getName()}, 内容: '{text_content}'") + return created_node + else: + print("gui_manager.createGUI3DText返回None") + + except Exception as e: + print(f"重新创建3D文本失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def _recreateGUIElement(self, gui_node): + """重新创建GUI元素的通用方法""" + try: + # 获取GUI元素类型 + gui_type = "unknown" + if gui_node.hasTag("gui_type"): + gui_type = gui_node.getTag("gui_type") + elif gui_node.hasTag("saved_gui_type"): + gui_type = gui_node.getTag("saved_gui_type") + else: + gui_type = "unknown_gui_element" + + print(f"重新创建GUI元素: {gui_node.getName()} (类型: {gui_type})") + + # 根据类型重新创建GUI元素 + recreated_node = None + if gui_type == "3d_text": + recreated_node = self._recreate3DText(gui_node) + elif gui_type == "3d_image": + recreated_node = self._recreate3DImage(gui_node) + # 可以在这里添加其他类型的GUI元素重建逻辑 + else: + # 对于其他类型的GUI元素,使用备用方法 + recreated_node = self._recreateGUIElementFallback(gui_node, gui_type) + + # 如果没有特定的重建方法,至少确保基本属性 + target_node = recreated_node if recreated_node else gui_node + + # 确保节点被正确标记 + if not target_node.hasTag("is_scene_element"): + target_node.setTag("is_scene_element", "1") + + # 注意:这里不再直接添加到gui_elements列表,因为gui_manager.createGUI3DText已经添加了 + # 只有在使用备用方法且元素不在列表中时才添加 + + # 恢复GUI特定属性 + self._restoreGUIProperties(target_node) + + print(f"GUI元素 {target_node.getName()} 重建完成") + return True + except Exception as e: + print(f"重新创建GUI元素失败: {str(e)}") + return False + def _recreate3DImage(self, image_node): + """重新创建3D图像元素""" + try: + from panda3d.core import Texture, TextureStage + from panda3d.core import CardMaker, Material, LColor, TransparencyAttrib + + print(f"重新创建3D图像: {image_node.getName()}") + + # 获取保存的属性 + image_path = image_node.getTag("gui_image_path") if image_node.hasTag("gui_image_path") else None + + # 获取位置 + pos = (0, 0, 0) + if image_node.hasTag("transform_pos"): + try: + pos_str = image_node.getTag("transform_pos") + pos_str = pos_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + pos_values = [float(x.strip()) for x in pos_str.split(',')] + if len(pos_values) >= 3: + pos = (pos_values[0], pos_values[1], pos_values[2]) + except Exception as e: + print(f"解析位置失败: {e}") + + # 获取尺寸 + size = 1.0 + if image_node.hasTag("transform_scale"): + try: + scale_str = image_node.getTag("transform_scale") + scale_str = scale_str.replace('LVecBase3f', '').replace('LPoint3f', '').strip('()') + scales = [float(x.strip()) for x in scale_str.split(',')] + size = scales[0] if scales else 1.0 + except Exception as e: + print(f"解析尺寸失败: {e}") + + # 创建卡片 + cm = CardMaker('gui_3d_image_recreated') + x_size = y_size = float(size) + cm.setFrame(-x_size / 2, x_size / 2, -y_size / 2, y_size / 2) + + # 创建3D图像节点 + new_image_node = image_node.getParent().attachNewNode(cm.generate()) + new_image_node.setName(image_node.getName()) + new_image_node.setPos(*pos) + + # 设置材质 + material = Material(f"image-material-recreated-{len(getattr(self, '_recreated_image_count', [0]))}") + material.setBaseColor(LColor(1, 1, 1, 1)) + material.setDiffuse(LColor(1, 1, 1, 1)) + material.setAmbient(LColor(0.5, 0.5, 0.5, 1)) + material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0)) + material.setShininess(10.0) + material.setEmission(LColor(0, 0, 0, 1)) + new_image_node.setMaterial(material, 1) + + new_image_node.setTransparency(TransparencyAttrib.MAlpha) + + # 如果有图像路径,加载纹理 + if image_path: + try: + texture = self.world.loader.loadTexture(image_path) + if texture: + new_image_node.setTexture(texture, 1) + texture.setWrapU(Texture.WM_clamp) + texture.setWrapV(Texture.WM_clamp) + texture.setMinfilter(Texture.FT_linear) + texture.setMagfilter(Texture.FT_linear) + except Exception as e: + print(f"加载纹理失败: {e}") + + # 设置标签 + new_image_node.setTag("gui_type", "3d_image") + new_image_node.setTag("gui_id", image_node.getTag("gui_id") if image_node.hasTag( + "gui_id") else f"3d_image_{len(getattr(self.world, 'gui_elements', []))}") + if image_path: + new_image_node.setTag("gui_image_path", image_path) + new_image_node.setTag("is_gui_element", "1") + new_image_node.setTag("is_scene_element", "1") + new_image_node.setTag("created_by_user", "1") + + # 复制变换标签 + for tag in ["transform_pos", "transform_hpr", "transform_scale"]: + if image_node.hasTag(tag): + new_image_node.setTag(tag, image_node.getTag(tag)) + + # 添加到GUI元素列表 + if hasattr(self.world, 'gui_elements'): + if new_image_node not in self.world.gui_elements: + self.world.gui_elements.append(new_image_node) + + print(f"3D图像重建完成: {new_image_node.getName()}") + return new_image_node + + except Exception as e: + print(f"重新创建3D图像失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def _recreateGUIElement(self, gui_node): + """重新创建GUI元素的通用方法""" + try: + # 获取GUI元素类型 + gui_type = "unknown" + if gui_node.hasTag("gui_type"): + gui_type = gui_node.getTag("gui_type") + elif gui_node.hasTag("saved_gui_type"): + gui_type = gui_node.getTag("saved_gui_type") + else: + gui_type = "unknown_gui_element" + + print(f"重新创建GUI元素: {gui_node.getName()} (类型: {gui_type})") + + # 根据类型重新创建GUI元素 + recreated_node = None + if gui_type == "3d_text": + recreated_node = self._recreate3DText(gui_node) + elif gui_type == "3d_image": + recreated_node = self._recreate3DImage(gui_node) + # 可以在这里添加其他类型的GUI元素重建逻辑 + else: + # 对于其他类型的GUI元素,使用备用方法 + recreated_node = self._recreateGUIElementFallback(gui_node, gui_type) + + # 如果没有特定的重建方法,至少确保基本属性 + target_node = recreated_node if recreated_node else gui_node + + # 确保节点被正确标记 + if not target_node.hasTag("is_scene_element"): + target_node.setTag("is_scene_element", "1") + + # 添加到GUI元素列表(如果还没有添加) + if hasattr(self.world, 'gui_elements'): + if target_node not in self.world.gui_elements: + self.world.gui_elements.append(target_node) + + # 恢复GUI特定属性 + self._restoreGUIProperties(target_node) + + print(f"GUI元素 {target_node.getName()} 重建完成") + return True + except Exception as e: + print(f"重新创建GUI元素失败: {str(e)}") + return False + + def _recreateGUIElementFallback(self, gui_node, gui_type): + """GUI元素重建的备用方法""" + try: + # 对于3D文本,确保使用正确的重建方法 + if gui_type == "3d_text": + return self._recreate3DText(gui_node) + + # 对于其他类型,保持原有节点但确保标签正确 + # 确保必要的标签 + required_tags = { + "gui_type": gui_type, + "is_gui_element": "1", + "is_scene_element": "1", + "created_by_user": "1" + } + + for tag, value in required_tags.items(): + if not gui_node.hasTag(tag): + gui_node.setTag(tag, value) + + return gui_node + except Exception as e: + print(f"GUI元素备用重建失败: {str(e)}") + return gui_node + + def _restoreGUIProperties(self, gui_node): + """恢复GUI元素的特定属性""" + try: + # 恢复文本内容(对于文本相关的GUI元素) + if gui_node.hasTag("gui_text"): + text_content = gui_node.getTag("gui_text") + # 如果节点有setText方法,则设置文本 + if hasattr(gui_node, 'setText'): + gui_node.setText(text_content) + # 如果是TextNode,则设置文本 + elif hasattr(gui_node, 'node') and hasattr(gui_node.node(), 'setText'): + gui_node.node().setText(text_content) + + # 恢复大小 + if gui_node.hasTag("gui_size"): + try: + size_str = gui_node.getTag("gui_size") + # 解析并设置大小 + pass # 根据具体需要实现 + except: + pass + + # 恢复颜色 + if gui_node.hasTag("gui_color"): + try: + color_str = gui_node.getTag("gui_color") + # 解析并设置颜色 + pass # 根据具体需要实现 + except: + pass + + print(f"已恢复GUI元素 {gui_node.getName()} 的属性") + except Exception as e: + print(f"恢复GUI属性时出错: {str(e)}") + + def createGUIElement(self, element_type, name=None, **kwargs): + """创建GUI元素的通用方法""" + try: + from panda3d.core import NodePath + + # 生成唯一名称 + if name is None: + name = f"{element_type}_{len(getattr(self.world, 'gui_elements', []))}" + + # 创建GUI元素节点 + element_node = NodePath(name) + + # 设置GUI元素标签 + element_node.setTag("is_gui_element", "1") + element_node.setTag("gui_type", element_type) + element_node.setTag("is_scene_element", "1") + element_node.setTag("saved_gui_type", element_type) + + # 添加到场景 + element_node.reparentTo(self.world.render) + + # 添加到GUI元素列表 + if not hasattr(self.world, 'gui_elements'): + self.world.gui_elements = [] + self.world.gui_elements.append(element_node) + print(f"1111111111111111111{self.world.gui_elements}") + + # 保存额外属性 + for key, value in kwargs.items(): + element_node.setTag(f"gui_{key}", str(value)) + + # 更新场景树 + self.updateSceneTree() + + print(f"创建GUI元素: {name} (类型: {element_type})") + return element_node + except Exception as e: + print(f"创建GUI元素失败: {str(e)}") + return None + + def _recreateSpotLight(self, light_node): + """重新创建聚光灯""" + try: + from RenderPipelineFile.rpcore import SpotLight + from QPanda3D.Panda3DWorld import get_render_pipeline + + # 创建聚光灯对象 + light = SpotLight() + light.direction = Vec3(0, 0, -1) + light.fov = 70 + light.set_color_from_temperature(5 * 1000.0) + + # 恢复保存的属性 + if light_node.hasTag("light_energy"): + light.energy = float(light_node.getTag("light_energy")) + else: + light.energy = 5000 + + light.radius = 1000 + light.casts_shadows = True + light.shadow_map_resolution = 256 + + # 设置位置 + light.setPos(light_node.getPos()) + + # 添加到渲染管线 + render_pipeline = get_render_pipeline() + render_pipeline.add_light(light) + + # 保存光源对象引用 + light_node.setPythonTag("rp_light_object", light) + + # 添加到管理列表 + self.Spotlight.append(light_node) + + # 确保灯光节点有正确的标签,以便在场景树更新时被识别 + if not light_node.hasTag("is_scene_element"): + light_node.setTag("is_scene_element", "1") + + print(f"重新创建聚光灯: {light_node.getName()}") + except Exception as e: + print(f"重新创建聚光灯失败: {str(e)}") + + def _recreatePointLight(self, light_node): + """重新创建点光源""" + try: + from RenderPipelineFile.rpcore import PointLight + from QPanda3D.Panda3DWorld import get_render_pipeline + + # 创建点光源对象 + light = PointLight() + + # 恢复保存的属性 + if light_node.hasTag("light_energy"): + light.energy = float(light_node.getTag("light_energy")) + else: + light.energy = 5000 + + light.radius = 1000 + light.inner_radius = 0.4 + light.set_color_from_temperature(5 * 1000.0) + light.casts_shadows = True + light.shadow_map_resolution = 256 + + # 设置位置 + light.setPos(light_node.getPos()) + + # 添加到渲染管线 + render_pipeline = get_render_pipeline() + render_pipeline.add_light(light) + + # 保存光源对象引用 + light_node.setPythonTag("rp_light_object", light) + + # 添加到管理列表 + self.Pointlight.append(light_node) + + # 确保灯光节点有正确的标签,以便在场景树更新时被识别 + if not light_node.hasTag("is_scene_element"): + light_node.setTag("is_scene_element", "1") + + print(f"重新创建点光源: {light_node.getName()}") + except Exception as e: + print(f"重新创建点光源失败: {str(e)}") + def _cleanupAuxiliaryNodes(self): """清理场景中可能存在的辅助节点""" try: diff --git a/ui/interface_manager.py b/ui/interface_manager.py index 75f4324a..da36ce6d 100644 --- a/ui/interface_manager.py +++ b/ui/interface_manager.py @@ -296,6 +296,7 @@ class InterfaceManager: addNodeToTree(model, sceneRoot,force=True) # 添加所有GUI元素 + print(f"GUIGUIGUIGUIGUIGUIGUIGUIGUIGUIGUIGUIUGIUGI{self.world.gui_elements}") for gui in self.world.gui_elements: # 检查是否是有效的GUI节点(具有getTag方法的NodePath) if hasattr(gui, 'getTag') and hasattr(gui, 'getName'): @@ -314,10 +315,19 @@ class InterfaceManager: groundItem.setData(0,Qt.UserRole + 1, "SCENE_NODE") #添加灯光节点 - for light in self.world.Spotlight + self.world.Pointlight: - if not light.isEmpty: + for light in self.world.Spotlight: + if light: addNodeToTree(light, sceneRoot, force=True) + for light in self.world.Pointlight: + if light: + addNodeToTree(light, sceneRoot, force=True) + + # for light in self.world.Spotlight + self.world.Pointlight: + # if not light.isEmpty: + # print(f"33333333333333333333333333333{light}") + # addNodeToTree(light, sceneRoot, force=True) + #添加 Cesium tilesets if hasattr(self.world,'scene_manager') and hasattr(self.world.scene_manager,'tilesets'): for i , tileset_info in enumerate(self.world.scene_manager.tilesets): diff --git a/ui/property_panel.py b/ui/property_panel.py index 21a173e2..0678dd6b 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -168,7 +168,7 @@ class PropertyPanelManager: elif model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset": self._showCesiumTilesetProperties(model,item) elif model and hasattr(model, 'getTag') and model.getTag("gui_type"): - self.updateGUIPropertyPanel(model) + self.updateGUIPropertyPanel(model, item) elif model and hasattr(model, 'getTag') and model.getTag("light_type"): self.updateLightPropertyPanel(model) elif model: @@ -1282,11 +1282,11 @@ class PropertyPanelManager: - def updateGUIPropertyPanel(self, gui_element): + def updateGUIPropertyPanel(self, gui_element,item): """更新GUI元素属性面板""" self.clearPropertyPanel() - itemText = gui_element.getTag("gui_type") or "未命名GUI元素" + itemText = gui_element.getTag("name") or "未命名GUI元素" user_visible = True user_visible = gui_element.getPythonTag("user_visible") @@ -1305,9 +1305,11 @@ class PropertyPanelManager: def updateGUIName(text): # 更新GUI元素的标签 gui_element.setTag("name", text) + self.world.treeWidget.update_item_name(self.name_input.text(), item) + # gui_element.setName(text) # 如果有场景管理器,也需要更新场景树 - 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() self.name_input.returnPressed.connect(lambda: updateGUIName(self.name_input.text())) @@ -1351,10 +1353,10 @@ class PropertyPanelManager: def updateText(): text = textEdit.text() success = self.world.gui_manager.editGUIElement(gui_element, "text", text) - if success: - # 更新场景树显示的名称 - if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'): - self.world.scene_manager.updateSceneTree() + # if success: + # # 更新场景树显示的名称 + # if hasattr(self.world, 'scene_manager') and hasattr(self.world.scene_manager, 'updateSceneTree'): + # self.world.scene_manager.updateSceneTree() # 只在按下回车键或失去焦点时更新 textEdit.returnPressed.connect(updateText) diff --git a/ui/widgets.py b/ui/widgets.py index bd5ba590..4f0fc48f 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -2216,7 +2216,6 @@ 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