diff --git a/RenderPipelineFile/rpcore/util/movement_controller.py b/RenderPipelineFile/rpcore/util/movement_controller.py index 4dbb91e1..6fb2f4b9 100644 --- a/RenderPipelineFile/rpcore/util/movement_controller.py +++ b/RenderPipelineFile/rpcore/util/movement_controller.py @@ -60,7 +60,7 @@ class MovementController(object): self.keyboard_hpr_speed = 0.4 self.use_hpr = False self.smoothness = 6.0 - self.bobbing_amount = 1.5 + self.bobbing_amount = 0.0 self.bobbing_speed = 0.5 def set_initial_position(self, pos, target): diff --git a/core/CustomMouseController.py b/core/CustomMouseController.py index 87405be1..6ac00945 100644 --- a/core/CustomMouseController.py +++ b/core/CustomMouseController.py @@ -1,4 +1,3 @@ -from PyQt5.QtCore import showbase from direct.task.TaskManagerGlobal import taskMgr diff --git a/gui/gui_manager.py b/gui/gui_manager.py index ba9febdd..0d5123a4 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -1031,51 +1031,97 @@ class GUIManager: # 如果提供了视频路径,则加载视频纹理 movie_texture = None - if video_path and os.path.exists(video_path): - try: - print(f"🔍 尝试加载视频纹理: {video_path}") - # 加载视频纹理 - movie_texture = self._loadMovieTexture(video_path) - if movie_texture: - # 创建纹理阶段 - texture_stage = TextureStage("video") - texture_stage.setSort(0) - texture_stage.setMode(TextureStage.MModulate) - video_screen.setTexture(texture_stage, movie_texture) - print(f"✅ 视频纹理加载成功: {video_path}") + if video_path: + if video_path.startswith(("http://","https://")): + try: + if hasattr(self.world,'property_panel'): + success = self.world.property_panel._loadVideoFromURLWithOpenCV_3D(video_screen,video_path) + if success: + print(f"✅ 视频流URL加载成功: {video_path}") + else: + print(f"⚠️ 视频流URL加载失败: {video_path}") + else: + print("⚠️ property_manager不可用,无法加载视频流") + except Exception as e: + print(f"❌ 加载视频流URL失败: {e}") + import traceback + traceback.print_exc() + elif os.path.exists(video_path): + # 对于本地视频文件,使用原有方法 + try: + print(f"🔍 尝试加载2D视频纹理: {video_path}") + # 加载视频纹理 + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + # 应用纹理到视频屏幕(替换占位符) + video_screen["frameTexture"] = movie_texture + print(f"✅ 2D视频纹理加载成功: {video_path}") - # 保存视频纹理引用以便后续控制 - video_screen.setPythonTag("movie_texture", movie_texture) + # 保存视频纹理引用以便后续控制 + video_screen.setPythonTag("movie_texture", movie_texture) - # 尝试自动播放视频(如果支持) - try: - if hasattr(movie_texture, 'play'): - movie_texture.play() - print("▶️ 视频已开始播放") - except Exception as play_error: - print(f"⚠️ 视频自动播放失败: {play_error}") - else: - print(f"⚠️ 无法加载视频纹理: {video_path}") - # 使用默认颜色作为占位符 - video_screen.setColor(0.1, 0.1, 0.3, 0.8) - except Exception as e: - print(f"❌ 加载视频纹理失败: {e}") - import traceback - traceback.print_exc() - # 使用默认颜色作为占位符 - video_screen.setColor(0.1, 0.1, 0.3, 0.8) - else: - # 没有视频文件时显示默认颜色 - video_screen.setColor(0.1, 0.1, 0.3, 0.8) - if video_path: - print(f"⚠️ 视频文件不存在: {video_path}") + # 尝试自动播放视频(如果支持) + try: + if hasattr(movie_texture, 'play'): + movie_texture.play() + print("▶️ 2D视频已开始播放") + except Exception as play_error: + print(f"⚠️ 2D视频自动播放失败: {play_error}") + else: + print(f"⚠️ 无法加载2D视频纹理: {video_path}") + except Exception as e: + print(f"❌ 加载2D视频纹理失败: {e}") + import traceback + traceback.print_exc() else: - print("ℹ️ 未提供视频文件,显示默认占位符") + print(f"⚠️ 2D视频文件不存在: {video_path}") - # 保存视频纹理引用以便后续控制 - if movie_texture: - video_screen.setPythonTag("movie_texture", movie_texture) + # if video_path and os.path.exists(video_path): + # try: + # print(f"🔍 尝试加载视频纹理: {video_path}") + # # 加载视频纹理 + # movie_texture = self._loadMovieTexture(video_path) + # if movie_texture: + # # 创建纹理阶段 + # texture_stage = TextureStage("video") + # texture_stage.setSort(0) + # texture_stage.setMode(TextureStage.MModulate) + # video_screen.setTexture(texture_stage, movie_texture) + # + # print(f"✅ 视频纹理加载成功: {video_path}") + # + # # 保存视频纹理引用以便后续控制 + # video_screen.setPythonTag("movie_texture", movie_texture) + # + # # 尝试自动播放视频(如果支持) + # try: + # if hasattr(movie_texture, 'play'): + # movie_texture.play() + # print("▶️ 视频已开始播放") + # except Exception as play_error: + # print(f"⚠️ 视频自动播放失败: {play_error}") + # else: + # print(f"⚠️ 无法加载视频纹理: {video_path}") + # # 使用默认颜色作为占位符 + # video_screen.setColor(0.1, 0.1, 0.3, 0.8) + # except Exception as e: + # print(f"❌ 加载视频纹理失败: {e}") + # import traceback + # traceback.print_exc() + # # 使用默认颜色作为占位符 + # video_screen.setColor(0.1, 0.1, 0.3, 0.8) + # else: + # # 没有视频文件时显示默认颜色 + # video_screen.setColor(0.1, 0.1, 0.3, 0.8) + # if video_path: + # print(f"⚠️ 视频文件不存在: {video_path}") + # else: + # print("ℹ️ 未提供视频文件,显示默认占位符") + # + # # 保存视频纹理引用以便后续控制 + # if movie_texture: + # video_screen.setPythonTag("movie_texture", movie_texture) # 添加到GUI元素列表 self.gui_elements.append(video_screen) @@ -1541,36 +1587,81 @@ class GUIManager: # 如果提供了视频路径,则加载视频纹理 movie_texture = None - if video_path and os.path.exists(video_path): - try: - print(f"🔍 尝试加载2D视频纹理: {video_path}") - # 加载视频纹理 - movie_texture = self._loadMovieTexture(video_path) - if movie_texture: - # 应用纹理到视频屏幕(替换占位符) - video_screen["frameTexture"] = movie_texture - print(f"✅ 2D视频纹理加载成功: {video_path}") - # 保存视频纹理引用以便后续控制 - video_screen.setPythonTag("movie_texture", movie_texture) + if video_path: + if video_path.startswith(("http://","https://")): + try: + if hasattr(self.world,'property_panel'): + success = self.world.property_panel._loadVideoFromURLWithOpenCV(video_screen,video_path) + if success: + print(f"✅ 视频流URL加载成功: {video_path}") + else: + print(f"⚠️ 视频流URL加载失败: {video_path}") + else: + print("⚠️ property_manager不可用,无法加载视频流") + except Exception as e: + print(f"❌ 加载视频流URL失败: {e}") + import traceback + traceback.print_exc() + elif os.path.exists(video_path): + try: + print(f"🔍 尝试加载2D视频纹理: {video_path}") + # 加载视频纹理 + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + # 应用纹理到视频屏幕(替换占位符) + video_screen["frameTexture"] = movie_texture + print(f"✅ 2D视频纹理加载成功: {video_path}") - # 尝试自动播放视频(如果支持) - try: - if hasattr(movie_texture, 'play'): - movie_texture.play() - print("▶️ 2D视频已开始播放") - except Exception as play_error: - print(f"⚠️ 2D视频自动播放失败: {play_error}") - else: - print(f"⚠️ 无法加载2D视频纹理: {video_path}") - except Exception as e: - print(f"❌ 加载2D视频纹理失败: {e}") - import traceback - traceback.print_exc() - else: - if not video_path: + # 保存视频纹理引用以便后续控制 + video_screen.setPythonTag("movie_texture", movie_texture) + + # 尝试自动播放视频(如果支持) + try: + if hasattr(movie_texture, 'play'): + movie_texture.play() + print("▶️ 2D视频已开始播放") + except Exception as play_error: + print(f"⚠️ 2D视频自动播放失败: {play_error}") + else: + print(f"⚠️ 无法加载2D视频纹理: {video_path}") + except Exception as e: + print(f"❌ 加载2D视频纹理失败: {e}") + import traceback + traceback.print_exc() + else: print(f"⚠️ 2D视频文件不存在: {video_path}") + # if video_path and os.path.exists(video_path): + # try: + # print(f"🔍 尝试加载2D视频纹理: {video_path}") + # # 加载视频纹理 + # movie_texture = self._loadMovieTexture(video_path) + # if movie_texture: + # # 应用纹理到视频屏幕(替换占位符) + # video_screen["frameTexture"] = movie_texture + # print(f"✅ 2D视频纹理加载成功: {video_path}") + # + # # 保存视频纹理引用以便后续控制 + # video_screen.setPythonTag("movie_texture", movie_texture) + # + # # 尝试自动播放视频(如果支持) + # try: + # if hasattr(movie_texture, 'play'): + # movie_texture.play() + # print("▶️ 2D视频已开始播放") + # except Exception as play_error: + # print(f"⚠️ 2D视频自动播放失败: {play_error}") + # else: + # print(f"⚠️ 无法加载2D视频纹理: {video_path}") + # except Exception as e: + # print(f"❌ 加载2D视频纹理失败: {e}") + # import traceback + # traceback.print_exc() + # else: + # if not video_path: + # print(f"⚠️ 2D视频文件不存在: {video_path}") + # 添加到GUI元素列表 self.gui_elements.append(video_screen) diff --git a/main.py b/main.py index b46c4dcf..17f5dc63 100644 --- a/main.py +++ b/main.py @@ -246,7 +246,7 @@ class MyWorld(CoreWorld): """创建3D空间文本""" return self.gui_manager.createGUI3DText(pos, text, size) - def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(1,1)): + def createGUI3DImage(self,pos=(0,0,0),text="3D图片",size=(1,1,1)): """创建3D图片""" return self.gui_manager.createGUI3DImage(pos,text,size) diff --git a/project/project_manager.py b/project/project_manager.py index 649beadd..88f7ab89 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -355,9 +355,45 @@ class ProjectManager: if not os.path.exists(scene_file): QMessageBox.warning(parent_window, "警告", "请先保存场景!") return False + + if hasattr(self.world,'selection') and self.world.selection: + self.world.selection.clearSelection() + print("已取消场景中的物体选中状态") + + if self.world.scene_manager.saveScene(scene_file,project_path): + print("场景保存成功") + config_file = os.path.join(project_path,"project.json") + if os.path.exists(config_file): + with open(config_file,"r",encoding="utf-8") as f: + project_config = json.load(f) + + project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + project_config["scene_file"] = os.path.relpath(scene_file,project_path) + + with open(config_file,"w",encoding = "utf-8") as f: + json.dump(project_config,f,ensure_ascii = False,indent=4) + + self.project_config = project_config + + project_name = os.path.basename(project_path) + self.updateWindowTitle(parent_window,project_name) + else: + QMessageBox.warning(parent_window,"错误","保存场景失败!") + return False + + build_dir = QFileDialog.getExistingDirectory( + parent_window, + "选择打包输出目录", + project_path, + QFileDialog.ShowDirsOnly + ) + + if not build_dir: + return False # 创建构建目录 - build_dir = os.path.join(project_path, "build") + build_dir = os.path.join(build_dir, "build") + if not os.path.exists(build_dir): os.makedirs(build_dir) @@ -413,7 +449,7 @@ class ProjectManager: shutil.copytree( source_render_pipeline, dest_render_pipeline, - ignore=shutil.ignore_patterns('__pycache__','*.pyc','.git','.vscode','*.log') + ignore=shutil.ignore_patterns('__pycache__','*.pyc','.git','.vscode','*.log','samples') ) print("✓ RenderPipelineFile文件夹已复制到build目录") else: @@ -499,6 +535,7 @@ class ProjectManager: # 使用_collectGUIElementInfo方法收集信息 gui_info = self.world.scene_manager._collectGUIElementInfo(gui_node) if gui_info: + self._updateResourcePaths(gui_info,project_path,build_dir) gui_data.append(gui_info) print(f"收集GUI元素信息: {gui_info['name']}") @@ -516,6 +553,29 @@ class ProjectManager: traceback.print_exc() return False + def _updateResourcePaths(self,gui_info,project_path,build_dir): + def normalize_resource_path(old_path): + if not old_path: + return old_path + + if old_path.startswith(("http://","https://")): + return old_path + + if os.path.isabs(old_path): + try: + rel_path = os.path.relpath(old_path,project_path) + return os.path.join("resources",os.path.basename(rel_path)).replace("\\","/") + except Exception: + return os.path.join("resources",os.path.basename(old_path)).replace("\\","/") + else: + return os.path.join("resources",os.path.basename(old_path)).replace("\\","/") + if "image_path" in gui_info and gui_info["image_path"]: + gui_info["image_path"] = normalize_resource_path(gui_info["image_path"]) + if "video_path" in gui_info and gui_info["video_path"]: + gui_info["video_path"] = normalize_resource_path(gui_info["video_path"]) + if "bg_image_path" in gui_info and gui_info["bg_image_path"]: + gui_info["bg_image_path"] = normalize_resource_path(gui_info["bg_image_path"]) + def _createRequirementsFile(self,build_dir): requirements_content = """panda3d>=1.10.13""" diff --git a/scene/scene_manager.py b/scene/scene_manager.py index fd9fadc0..eb9d0240 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -2010,14 +2010,7 @@ class SceneManager: elif gui_type == "3d_image" and hasattr(gui_manager, 'createGUI3DImage'): # 处理3D图像 # 根据缩放值的数量处理尺寸 - 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 = (absolute_scale[0] * 2, absolute_scale[0] * 2) - else: - size = (1.0, 1.0) + size = (absolute_scale[0] * 0.2, absolute_scale[1] * 0.2, absolute_scale[2] * 0.2) new_element = gui_manager.createGUI3DImage( pos=tuple(absolute_position), @@ -2025,6 +2018,7 @@ class SceneManager: size=size ) elif gui_type == "video_screen" and hasattr(gui_manager, 'createVideoScreen'): + print(f"重建的3d视频屏幕视频地址是{video_path}") new_element = gui_manager.createVideoScreen( pos=tuple(absolute_position), size=absolute_scale, @@ -2032,14 +2026,7 @@ class SceneManager: ) if video_path and new_element: if video_path.startswith("http://") or video_path.startswith("https://"): - from direct.task.TaskManagerGlobal import taskMgr - - def load_video_stream_task(task): - if hasattr(property_manager, '_loadVideoFromURLWithOpenCV_3D'): - property_manager._loadVideoFromURLWithOpenCV_3D(new_element, video_path) - return task.done - - taskMgr.doMethodLater(0.5, load_video_stream_task, 'loadVideoStreamTask') + pass else: if hasattr(gui_manager, 'loadVideoFile'): from direct.task.TaskManagerGlobal import taskMgr @@ -2051,6 +2038,7 @@ class SceneManager: taskMgr.doMethodLater(0.1, load_video_file_task, 'loadVideoFileTask') elif gui_type == "2d_video_screen" and hasattr(gui_manager, 'createGUI2DVideoScreen'): + print(f"重建的2d视频屏幕视频地址是{video_path}") new_element = gui_manager.createGUI2DVideoScreen( pos=tuple(absolute_position), size=absolute_scale, @@ -2059,12 +2047,6 @@ class SceneManager: if video_path and new_element: if video_path.startswith("http://") or video_path.startswith("https://"): pass - # from direct.task.TaskManagerGlobal import taskMgr - # def load_2d_video_stream_task(task): - # if hasattr(property_manager,'_loadVideoFromURLWithOpenCV'): - # property_manager._loadVideoFromURLWithOpenCV(new_element,video_path) - # return task.done - # taskMgr.doMethodLater(0.1,load_2d_video_stream_task,'load2DVideoStreamTask') else: if hasattr(property_manager, 'load2DVideoFile'): from direct.task.TaskManagerGlobal import taskMgr diff --git a/templates/main_template.py b/templates/main_template.py index b7af3776..63a23b38 100644 --- a/templates/main_template.py +++ b/templates/main_template.py @@ -65,7 +65,8 @@ class MainApp(ShowBase): load_prc_file_data("", """ win-size 1380 750 - window-title Render + window-title + support-threads #t """) # 简化 sys.path 设置逻辑 @@ -94,7 +95,7 @@ class MainApp(ShowBase): self.script_manager = ScriptManager(self) self.script_manager.start_system() - # 加载所有脚本 + # 加载所有脚本e self.script_manager.load_all_scripts_from_directory() self.info_panel_manager = InfoPanelManager(self) @@ -106,6 +107,7 @@ class MainApp(ShowBase): self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam self.controller = MovementController(self) + self.camLens.set_fov(80) self.controller.set_initial_position( Vec3(0, -50, 20), Vec3(0, 0, 0)) self.controller.setup() @@ -277,8 +279,6 @@ class MainApp(ShowBase): import traceback traceback.print_exc() - # 在 processSceneElements 方法中修改碰撞体创建部分 - # 修复 processSceneElements 方法中的碰撞体创建 def processSceneElements(self, scene): """处理场景中的各种元素""" try: @@ -290,6 +290,12 @@ class MainApp(ShowBase): # 为模型添加碰撞体 - 修复版本 if nodePath.hasTag("is_scene_element") and not nodePath.hasTag("is_gizmo"): + if nodePath.hasTag("gui_type"): + gui_type = nodePath.getTag("gui_type") + if gui_type in ["video_screen"]: + print(f"移除GUI视频节点: {nodePath.getName()}") + nodePath.removeNode() # 移除视频屏幕节点 + return # 使用更精确的包围盒 from panda3d.core import CollisionNode, CollisionBox bounds = nodePath.getBounds() @@ -315,6 +321,10 @@ class MainApp(ShowBase): if nodePath.hasTag("light_type"): light_type = nodePath.getTag("light_type") + if nodePath.hasTag("is_auxiliary_light") and nodePath.getTag("is_auxiliary_light").lower() == "true": + print(f"跳过辅助灯光节点:{nodePath.getName()}") + return + if nodePath not in processed_lights: if light_type == "spot_light": self._recreateSpotLight(nodePath) @@ -516,12 +526,17 @@ class MainApp(ShowBase): size=absolute_scale ) elif gui_type == "2d_video_screen": - scale_value = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.2 new_element = self.createGUI2DVideoScreen( pos=tuple(absolute_position), video_path=video_path, size=absolute_scale ) + elif gui_type == "video_screen": + new_element = self.createGUIVideoScreen( + pos=tuple(absolute_position), + video_path=video_path, + size=absolute_scale + ) elif gui_type == "info_panel": new_element = self.info_panel_manager.onCreateSampleInfoPanel() @@ -724,13 +739,61 @@ class MainApp(ShowBase): print(f"加载图片时出错: {e}") return image_node + def createGUIVideoScreen(self,pos=(0,0,0),size=1,video_path=None): + import os + from panda3d.core import TransparencyAttrib,Texture,TextureStage + + if isinstance(size,(list,tuple)) and len(size) >= 2: + width_scale = size[0] + height_scale = size[2] + else: + width_scale = size * 0.1 if isinstance(size, (int, float)) else 0.2 + height_scale = width_scale + + cm = CardMaker("gui-video-screen") + cm.setFrame(-width_scale,width_scale,-height_scale,height_scale) + + video_screen = self.render.attachNewNode(cm.generate()) + video_screen.setPos(pos) + + video_screen.setBin("fixed", 0) + + #video_screen.setTransparency(TransparencyAttrib.MAlpha) + + video_screen.setColor(1, 1, 1, 1) + + self._ensureVideoScreenMaterial(video_screen) + + if video_path: + if video_path.startswith(("http://", "https://")): + try: + success = self._loadVideoFromURLWithOpenCV3D(video_screen, video_path) + if success: + print("视频已从URL加载成功") + else: + print("从URL加载视频时出错") + except Exception as e: + print(f"从URL加载视频时出错: {e}") + elif os.path.exists(video_path): + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + video_screen.setTexture(movie_texture, 1) + else: + print(f"视频文件不存在: {video_path}") + else: + # 设置占位符纹理 + placeholder_texture = Texture() + placeholder_texture.setup2dTexture(1, 1, Texture.TUnsignedByte, Texture.FRgb) + placeholder_data = b'\x19\x19\x4c' # 深蓝色占位符 + placeholder_texture.setRamImage(placeholder_data) + video_screen.setTexture(placeholder_texture, 1) + + return video_screen + def createGUI2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): import os - if video_path and not os.path.isabs(video_path): - image_path = self.getResourcePath(video_path) from direct.gui.DirectGui import DirectFrame from panda3d.core import TransparencyAttrib, Texture, TextureStage - import os if isinstance(size,(list,tuple)) and len(size) >= 2: width_scale = size[0]*0.2 @@ -753,10 +816,20 @@ class MainApp(ShowBase): placeholder_data = b'\x19\x19\x4c' placeholder_texture.setRamImage(placeholder_data) - if video_path and os.path.exists(video_path): - movie_texture = self._loadMovieTexture(video_path) - if movie_texture: - video_screen["frameTexture"] = movie_texture + if video_path: + if video_path.startswith(("http://","https://")): + try: + success = self._loadVideoFromURLWithOpenCV(video_screen,video_path) + if success: + print("视频已从URL加载成功") + else: + print("从URL加载视频时出错") + except Exception as e: + print(f"从URL加载视频时出错: {e}") + elif os.path.exists(video_path): + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + video_screen["frameTexture"] = movie_texture return video_screen @@ -776,10 +849,197 @@ class MainApp(ShowBase): texture.setMinfilter(Texture.FT_linear) texture.setMagfilter(Texture.FT_linear) + texture.setFormat(Texture.FRgb8) + if hasattr(texture, 'set_loop') and hasattr(texture, 'set_play_rate'): texture.set_loop(True) texture.set_play_rate(1.0) + def _loadVideoFromURLWithOpenCV(self, video_screen, url): + try: + import cv2 + import threading + from panda3d.core import Texture,PNMImage + import numpy as np + import time + + def video_stream_thread(): + cap = cv2.VideoCapture(url) + + if not cap.isOpened(): + print(f"无法打开视频流: {url}") + return False + + cap.set(cv2.CAP_PROP_BUFFERSIZE,1) + + fps = cap.get(cv2.CAP_PROP_FPS) + frame_delay = 1.0 / fps if fps > 0 else 0.033 # 默认30fps + + while hasattr(self, 'video_stream_active') and self.video_stream_active: + ret, frame = cap.read() + + if not ret: + print("视频流读取失败,尝试重新连接...") + cap.release() + time.sleep(1) + cap = cv2.VideoCapture(url) + continue + + frame_height,frame_width = frame.shape[:2] + if frame_width > 256: + scale = 256.0/frame_width + new_width = int(frame_width * scale) + new_height = int(frame_height * scale) + frame = cv2.resize(frame,(new_width,new_height)) + + frame_rgb = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) + + height,width = frame_rgb.shape[:2] + + img = PNMImage(width,height,3) + img.set_maxval(255) + + for y in range(height): + for x in range(width): + r,g,b = frame_rgb[y,x] + img.setXelVal(x,y,r,g,b) + + texture = Texture("video_texture") + texture.setMagfilter(Texture.FTLinear) + texture.setMinfilter(Texture.FTLinear) + texture.setWrapU(Texture.WMClamp) + texture.setWrapV(Texture.WMClamp) + texture.load(img) + + # 在主线程中更新GUI纹理 + def update_texture(): + if hasattr(self, 'aspect2d') and not video_screen.isEmpty(): + video_screen["frameTexture"] = texture + + # 使用taskMgr在主线程中更新纹理 + if hasattr(self, 'taskMgr'): + self.taskMgr.doMethodLater(0, lambda task: update_texture() or task.done, + f"updateVideoTexture_{time.time()}") + + # 控制帧率 + time.sleep(frame_delay) + + cap.release() + print("视频流线程结束") + return True + + # 启动视频流线程 + self.video_stream_active = True + self.video_thread = threading.Thread(target=video_stream_thread, daemon=True) + self.video_thread.start() + + return True + + except Exception as e: + print(f"加载视频流失败: {e}") + import traceback + traceback.print_exc() + return False + + def _loadVideoFromURLWithOpenCV3D(self,video_screen,url): + try: + import cv2 + import threading + from panda3d.core import Texture,PNMImage + import time + + def video_stream_thread(): + cap = cv2.VideoCapture(url) + + if not cap.isOpened(): + return False + + cap.set(cv2.CAP_PROP_BUFFERSIZE,1) + + fps = cap.get(cv2.CAP_PROP_FPS) + frame_delay = 1.0 / fps if fps > 0 else 0.033 + + while hasattr(self,'video_stream_active') and self.video_stream_active: + ret,frame = cap.read() + + if not ret: + cap.release() + time.sleep(1) + cap = cv2.VideoCapture(url) + continue + + frame_height,frame_width = frame.shape[:2] + if frame_width > 256: + scale = 256.0/frame_width + new_width = int(frame_width*scale) + new_height = int(frame_height*scale) + frame = cv2.resize(frame,(new_width,new_height)) + + frame_rgb = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) + + height,width = frame_rgb.shape[:2] + + img = PNMImage(width,height,3) + img.set_maxval(255) + + for y in range(height): + for x in range(width): + r,g,b = frame_rgb[y,x] + img.setXelVal(x,y,r,g,b) + + texture = Texture("video_texture_3d") + texture.setMagfilter(Texture.FTLinear) + texture.setMinfilter(Texture.FTLinear) + texture.setWrapU(Texture.WMClamp) + texture.setWrapV(Texture.WMClamp) + texture.load(img) + + def update_texture(): + if hasattr(self,'render') and not video_screen.isEmpty(): + video_screen.setTexture(texture,1) + + if hasattr(self,'taskMgr'): + self.taskMgr.doMethodLater(0,lambda task:update_texture() or task.done,f"updateVideoTexture_{time.time()}") + + time.sleep(frame_delay) + + cap.release() + return True + self.video_stream_active = True + self.video_thread_3d = threading.Thread(target=video_stream_thread,daemon=True) + self.video_thread_3d.start() + + return True + except Exception as e: + print(f"加载3D视频流失败: {e}") + import traceback + traceback.print_exc() + return False + + def _ensureVideoScreenMaterial(self, video_screen): + """确保视频屏幕有正确的材质设置""" + try: + from panda3d.core import Material, LColor + + if not video_screen.hasMaterial(): + material = Material(f"video-material-{video_screen.getName()}") + material.setBaseColor(LColor(1, 1, 1, 1)) + material.setDiffuse(LColor(1, 1, 1, 1)) + material.setAmbient(LColor(1, 1, 1, 1)) # 确保环境光为白色 + material.setEmission(LColor(0, 0, 0, 1)) + material.setSpecular(LColor(0, 0, 0, 1)) + material.setShininess(0) + video_screen.setMaterial(material, 1) + else: + # 更新现有材质确保正确设置 + material = video_screen.getMaterial() + material.setBaseColor(LColor(1, 1, 1, 1)) + material.setAmbient(LColor(1, 1, 1, 1)) # 确保环境光为白色 + video_screen.setMaterial(material, 1) + + except Exception as e: + print(f"⚠️ 设置视频屏幕材质时出错: {e}") + def playModelAnimation(self): """播放场景中所有 .glb 模型的动画(仅转换有动画的模型为 Actor)""" try: diff --git a/ui/property_panel.py b/ui/property_panel.py index f7e4bb02..927e5e88 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -3482,7 +3482,8 @@ class PropertyPanelManager: if video_path.startswith("http://") or video_path.startswith("https://"): # 显示URL信息 video_info_layout.addWidget(QLabel("视频流URL:"), 0, 0) - path_label = QLabel(video_path) + display_path = video_path if len(video_path)<=50 else video_path[:47]+"..." + path_label = QLabel(display_path) path_label.setWordWrap(True) path_label.setStyleSheet("color: #00AAFF;") video_info_layout.addWidget(path_label, 0, 1)