1
0
forked from Rowland/EG

保存脚本...

This commit is contained in:
Hector 2025-09-15 16:31:35 +08:00
parent 8a2f1f802c
commit 9f1879cf95
4 changed files with 171 additions and 294 deletions

View File

@ -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接口"""

View File

@ -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}")

View File

@ -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):
"""重新创建聚光灯"""

View File

@ -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}")