diff --git a/core/script_system.py b/core/script_system.py index abb206d3..07d6e96e 100644 --- a/core/script_system.py +++ b/core/script_system.py @@ -294,6 +294,31 @@ class ScriptLoader: return len(changed_scripts) > 0 + def find_script_file(self, script_name: str) -> Optional[str]: + """根据脚本名称查找脚本文件路径""" + # 首先检查已加载的脚本 + if script_name in self.loaded_modules: + module = self.loaded_modules[script_name] + if hasattr(module, '__file__') and module.__file__: + return module.__file__ + + # 在已知的文件路径中查找 + for file_path in self.file_mtimes.keys(): + file_name = os.path.splitext(os.path.basename(file_path))[0] + if file_name == script_name: + return file_path + + # 在脚本目录中查找 + scripts_dir = self.script_manager.scripts_directory + if os.path.exists(scripts_dir): + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + return os.path.join(scripts_dir, file_name) + + return None + class ScriptAPI: """脚本API - 提供给脚本使用的API接口""" diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 7b4aea60..dbf9e4e6 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -1149,6 +1149,7 @@ class GUIManager: # 检查是否有播放方法 if hasattr(movie_texture, 'play'): try: + self.loadVideoFile(video_screen, video_screen.getTag("video_path")) movie_texture.play() print(f"▶️ 继续播放视频: {video_screen.getName()}") return True @@ -1159,9 +1160,7 @@ class GUIManager: print(f"⚠️ 纹理对象没有播放方法: {video_screen.getName()}") return False else: - print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") - self._debugVideoScreenTextures(video_screen) - return False + self.loadVideoFile(video_screen,video_screen.getTag("video_path")) except Exception as e: print(f"❌ 播放视频失败: {e}") import traceback @@ -1472,8 +1471,6 @@ class GUIManager: pos=(pos[0] * 0.1, 0, pos[1] * 0.1), # 转换为屏幕坐标 parent=parent_node if tree_widget.is_gui_element(parent_node) else self.world.aspect2d, suppressMouse=True, - - ) video_screen.setName(screen_name) @@ -1590,7 +1587,8 @@ class GUIManager: import os video_screen.setTag("video_path",video_path) - print(f"🔧 更新2D视频屏幕标签 - video_path: {video_path if video_path else '空'}") + path = video_screen.getTag("video_path") + print(f"🔧 更新2D视频屏幕标签 - video_path: {path}") if not video_path or not os.path.exists(video_path): print(f"❌ 2D视频文件不存在: {video_path}") diff --git a/scene/scene_manager.py b/scene/scene_manager.py index fa4bc075..d52c5ba5 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -757,6 +757,7 @@ class SceneManager: "name": gui_node.getName(), "type": gui_type, "position": list(gui_node.getPos()), + "rotation": list(gui_node.getHpr()), "scale": list(gui_node.getScale()), "tags": {} } @@ -801,21 +802,10 @@ class SceneManager: elif gui_type == "3d_image": if hasattr(gui_node,'hasTag') and gui_node.hasTag("gui_image_path"): gui_info["image_path"] = gui_node.getTag("gui_image_path") - elif gui_type == "video_screen": - if hasattr(gui_node,'hasTag') and gui_node.hasTag("video_path"): - gui_info["video_path"] = gui_node.getTag("video_path") - elif gui_type == "2d_video_screen": + elif gui_type in ["video_screen", "2d_video_screen"]: if hasattr(gui_node, 'hasTag') and gui_node.hasTag("video_path"): gui_info["video_path"] = gui_node.getTag("video_path") - else: - print(f"无法保存2D视频屏幕: {gui_node.getName()}") - if hasattr(gui_node,'hasTag'): - video_path = gui_node.getTag("video_path") - try: - print(f"正在保存2D视频路径: {video_path}") - except Exception as e: - print(f"保存2D视频屏幕失败: {e}") - + gui_info["video_path"] = gui_node.getTag("video_path") elif gui_type == "virtual_screen": if hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): gui_info["text"] = gui_node.getTag("gui_text") @@ -823,6 +813,7 @@ class SceneManager: if hasattr(gui_node, 'hasTag') and gui_node.hasTag("panel_data"): gui_info["panel_data"] = gui_node.getTag("panel_data") + # 修改 _collectGUIElementInfo 方法中的脚本收集部分 if hasattr(self.world, 'script_manager') and self.world.script_manager: script_manager = self.world.script_manager # 获取挂载在此节点上的所有脚本 @@ -831,16 +822,17 @@ class SceneManager: gui_info["scripts"] = [] for script_component in scripts: script_name = script_component.script_name # 使用脚本组件的名称 + print(f"收集脚本信息: {script_name}") + # 获取脚本类的文件路径 script_class = script_component.script_instance.__class__ - try: - script_file = inspect.getfile(script_class) - except: - script_file = "" + script_file = self._get_script_file_path(script_class, script_name) + gui_info["scripts"].append({ "name": script_name, "file": script_file }) + print(f"脚本 {script_name} 来自文件: {script_file}") print(f"成功收集GUI元素信息: {gui_info}") return gui_info @@ -850,6 +842,74 @@ class SceneManager: traceback.print_exc() return None + def _get_script_file_path(self, script_class, script_name): + """ + 获取脚本文件路径的可靠方法 + """ + script_file = "" + + # 方法1: 使用 inspect.getfile + try: + script_file = inspect.getfile(script_class) + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法2: 使用 __file__ 属性 + try: + if hasattr(script_class, '__file__') and script_class.__file__: + script_file = script_class.__file__ + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法3: 使用模块的 __file__ 属性 + try: + module = inspect.getmodule(script_class) + if module and hasattr(module, '__file__') and module.__file__: + script_file = module.__file__ + if script_file and os.path.exists(script_file): + return script_file + except: + pass + + # 方法4: 从脚本管理器中查找 + try: + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + # 查找脚本类对应的文件路径 + for file_path, file_mtime in script_manager.loader.file_mtimes.items(): + # 检查文件名是否匹配脚本名 + file_name = os.path.splitext(os.path.basename(file_path))[0] + if file_name == script_name: + if os.path.exists(file_path): + return file_path + except: + pass + + # 方法5: 在脚本目录中查找 + try: + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + scripts_dir = script_manager.scripts_directory + + # 查找匹配的脚本文件 + if os.path.exists(scripts_dir): + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + full_path = os.path.join(scripts_dir, file_name) + if os.path.exists(full_path): + return full_path + except: + pass + + print(f"警告: 无法获取脚本 {script_name} 的文件路径") + return script_file + def saveScene(self, filename): """保存场景到BAM文件 - 完整版,支持GUI元素,地形""" try: @@ -910,6 +970,7 @@ class SceneManager: # 创建用于保存GUI信息的JSON文件路径 gui_info_file = filename.replace('.bam', '_gui.json') + print(self.world.gui_elements) # 收集GUI元素信息(排除3D文本和3D图像) gui_data = [] for gui_node in gui_elements: @@ -1112,7 +1173,7 @@ class SceneManager: print("场景加载失败") return False - # tree_widget.create_model_items(scene) + tree_widget.create_model_items(scene) # 遍历场景中的所有模型节点 # 用于存储处理后的灯光节点,避免重复处理 processed_lights = [] @@ -1345,7 +1406,7 @@ class SceneManager: scene.removeNode() # 更新场景树 - self.updateSceneTree() + #self.updateSceneTree() # self._get_tree_widget().create_model_items(scene) print(f"加载完成,GUI元素数量: {len(self.world.gui_elements)}") @@ -1461,7 +1522,7 @@ class SceneManager: size=scale, video_path=video_path ) - if video_path and new_element and hasattr(gui_manager,'loadVideoFile'): + if video_path and new_element and hasattr(gui_manager, 'loadVideoFile'): # 延迟一帧执行,确保节点完全初始化 from direct.task.TaskManagerGlobal import taskMgr def load_video_task(task): @@ -1507,20 +1568,46 @@ class SceneManager: new_element._tags.update(tags) # 重新挂载脚本(如果有的话) + # 在 _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: - script_file = script_info.get("file", "") + # 如果脚本未加载,尝试从保存的文件路径加载 if script_file and os.path.exists(script_file): - script_manager.load_script_from_file(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_manager.add_script_to_object(new_element, 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: @@ -1539,263 +1626,31 @@ class SceneManager: import traceback traceback.print_exc() - def _recreate3DText(self, text_node): - """重新创建3D文本元素 - 使用gui_manager中的createGUI3DText方法""" + def _find_script_in_directory(self, script_name): + """在脚本目录中查找脚本文件""" try: - print(f"重新创建3D文本: {text_node.getName()}") + if hasattr(self.world, 'script_manager') and self.world.script_manager: + script_manager = self.world.script_manager + scripts_dir = script_manager.scripts_directory - # 获取保存的文本内容 - 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") + if os.path.exists(scripts_dir): + # 首先精确匹配 + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if base_name == script_name: + return os.path.join(scripts_dir, file_name) + # 如果没有精确匹配,尝试模糊匹配 + for file_name in os.listdir(scripts_dir): + if file_name.endswith('.py'): + base_name = os.path.splitext(file_name)[0] + if script_name.lower() in base_name.lower() or base_name.lower() in script_name.lower(): + return os.path.join(scripts_dir, file_name) except Exception as e: - print(f"重新创建3D文本失败: {str(e)}") - import traceback - traceback.print_exc() - return None + print(f"查找脚本文件时出错: {e}") - def _recreate3DImage(self, image_node): - """重新创建3D图像元素 - 使用gui_manager中的createGUI3DImage方法""" - try: - print(f"重新创建3D图像: {image_node.getName()}") - - # 获取保存的图像路径 - image_path = None - if image_node.hasTag("gui_image_path"): - image_path = image_node.getTag("gui_image_path") - - # 获取位置 - 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, 1.0) # 默认尺寸为1x1 - 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(',')] - - # 根据缩放值的数量处理尺寸 - if len(scales) >= 3: - # 3D缩放 (x, y, z) - size = (scales[0]*2, scales[1]*2) # 取前两个值作为宽度和高度 - elif len(scales) >= 2: - # 2D缩放 (x, y) - size = (scales[0]*2, scales[1]*2) - elif len(scales) >= 1: - # 均匀缩放 - size = (scales[0]*2, scales[0]*2) - else: - size = (1.0, 1.0) - - print(f"恢复尺寸: {size} (来自缩放: {scales})") - except Exception as e: - print(f"恢复尺寸失败: {e}") - - # 使用gui_manager中的createGUI3DImage方法创建新的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, 'createGUI3DImage'): - # 调用gui_manager的createGUI3DImage方法创建新的3D图像 - new_image_node = gui_manager.createGUI3DImage(pos=pos, image_path=image_path, size=size) - - if new_image_node: - # 如果返回的是列表(多选创建),取第一个 - if isinstance(new_image_node, list): - created_node = new_image_node[0] - else: - created_node = new_image_node - - print(f"3D图像重建完成: {created_node.getName()}, 路径: '{image_path}', 尺寸: {size}") - return created_node - else: - print("gui_manager.createGUI3DImage返回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") - - # 根据类型重新创建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) - else: - return None - - # 如果没有特定的重建方法,至少确保基本属性 - 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) - print(f"将{target_node}添加至gui_elements") - - # 恢复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 _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 + return None def _recreateSpotLight(self, light_node): """重新创建聚光灯""" diff --git a/ui/property_panel.py b/ui/property_panel.py index 3305cd7f..c62aa2e4 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -1692,9 +1692,9 @@ class PropertyPanelManager: # 添加弹性空间 if gui_type == "video_screen": - self._addVideoScreenProperties(gui_element) + self._addVideoScreenProperties(gui_element,item) elif gui_type == "2d_video_screen": - self._add2DVideoScreenProperties(gui_element) + self._add2DVideoScreenProperties(gui_element,item) elif gui_type == "spherical_video": self._addSphericalVideoProperties(gui_element) elif gui_type in ['info_panel','info_panel_3d']: @@ -3294,7 +3294,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _addVideoScreenProperties(self, video_screen): + def _addVideoScreenProperties(self, video_screen,item): """添加视频屏幕属性面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3361,7 +3361,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen)) + load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen,item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3384,7 +3384,7 @@ class PropertyPanelManager: except Exception as e: print(f"添加视频屏幕属性失败: {e}") - def _add2DVideoScreenProperties(self, video_screen): + def _add2DVideoScreenProperties(self, video_screen,item): """为2D视频屏幕添加属性控制面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3455,7 +3455,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen)) + load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen,item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3753,7 +3753,7 @@ class PropertyPanelManager: except Exception as e: print(f"❌ 停止视频失败: {e}") return False - def _loadNew2DVideo(self, video_screen): + def _loadNew2DVideo(self, video_screen,item): """为2D视频屏幕加载新视频文件""" try: file_path, _ = QFileDialog.getOpenFileName( @@ -3768,7 +3768,7 @@ class PropertyPanelManager: print(f"成功加载新视频: {file_path}") # 刷新属性面板以显示新视频信息 self._stop2DVideo(video_screen) - self.updateGUIPropertyPanel(video_screen) + self.updateGUIPropertyPanel(video_screen,item) return True except Exception as e: print(f"加载新视频失败: {e}") @@ -4031,7 +4031,7 @@ class PropertyPanelManager: return False - def _loadNewVideo(self,video_screen): + def _loadNewVideo(self,video_screen,item): try: file_path, _ = QFileDialog.getOpenFileName( None, @@ -4042,8 +4042,7 @@ class PropertyPanelManager: if file_path: success = self.world.gui_manager.loadVideoFile(video_screen,file_path) if success: - print(f"成功加载新视频{file_path}") - self.updateGUIPropertyPanel(video_screen) + self.updateGUIPropertyPanel(video_screen,item) return True except Exception as e: print(f"加载新视频失败{e}")