From 5e68ff5ee6faafad87b2ce6f247c5f117c46b7b5 Mon Sep 17 00:00:00 2001 From: Hector <2055590199@qq.com> Date: Tue, 2 Sep 2025 10:21:46 +0800 Subject: [PATCH] =?UTF-8?q?3d=E8=A7=86=E9=A2=91=E5=B1=8F=E5=B9=95=E5=92=8C?= =?UTF-8?q?2d=E8=A7=86=E9=A2=91=E5=B1=8F=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RenderPipelineFile/config/daytime.yaml | 2 +- core/selection.py | 19 +- core/world.py | 61 ++ gui/gui_manager.py | 845 ++++++++++++++++++++++++- main.py | 8 + ui/main_window.py | 5 +- ui/property_panel.py | 334 +++++++--- 7 files changed, 1166 insertions(+), 108 deletions(-) diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 3c38890e..af9861ed 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -18,7 +18,7 @@ control_points: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] sun_azimuth: [[[0.5000000000,0.5000000000]]] - sun_altitude: [[[0.5000000000,0.9222222222]]] + sun_altitude: [[[0.5000000000,1.0000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/core/selection.py b/core/selection.py index c93422de..e0d2629d 100644 --- a/core/selection.py +++ b/core/selection.py @@ -98,7 +98,7 @@ class SelectionSystem: else: main_window.unsetCursor() self._current_cursor = cursor_type - print(f"光标已设置:{cursor_type}") + #print(f"光标已设置:{cursor_type}") self._current_cursor = cursor_type else: print("警告:无法获取主窗口,光标设置失败") @@ -399,19 +399,24 @@ class SelectionSystem: is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False + import os + base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + if is_scale_tool: model_paths = [ - "core/UniformScaleHandle.fbx", + os.path.join(base_dir,"core/UniformScaleHandle.fbx"), ] elif is_rotate_tool: model_paths = [ - "core/RotationHandleQuarter.fbx", + os.path.join(base_dir,"core/RotationHandleQuarter.fbx"), ] else: model_paths = [ - "core/TranslateArrowHandle.fbx", + os.path.join(base_dir, "core/TranslateArrowHandle.fbx"), ] + arrow_path = os.path.join(base_dir, "core/TranslateArrowHandle.fbx") + # model_paths = [ # "core/TranslateArrowHandle.fbx", # "EG/core/TranslateArrowHandle.fbx", @@ -421,7 +426,7 @@ class SelectionSystem: for path in model_paths: try: if is_rotate_tool: - gizmo_model = self.world.loader.loadModel("core/TranslateArrowHandle.fbx") + gizmo_model = self.world.loader.loadModel(arrow_path) gizmoRot_model = self.world.loader.loadModel(path) else: gizmo_model = self.world.loader.loadModel(path) @@ -778,7 +783,7 @@ class SelectionSystem: self.gizmo.setScale(scale_factor) # 限制缩放范围,避免过大或过小 - min_scale = 0.08 + min_scale = 0.001 max_scale = 100.0 final_scale = max(min_scale, min(max_scale, scale_factor)) @@ -1614,7 +1619,7 @@ class SelectionSystem: if self.dragGizmoAxis == "x": new_hpr = Vec3(start_hpr.x+rotation_amount,start_hpr.y,start_hpr.z) elif self.dragGizmoAxis == "y": - new_hpr = Vec3(start_hpr.x,start_hpr.y+rotation_amount,start_hpr.z) + new_hpr = Vec3(start_hpr.x,start_hpr.y-rotation_amount,start_hpr.z) elif self.dragGizmoAxis == "z": new_hpr = Vec3(start_hpr.x,start_hpr.y,start_hpr.z+rotation_amount) else: diff --git a/core/world.py b/core/world.py index ab69cff0..4c6aacd9 100644 --- a/core/world.py +++ b/core/world.py @@ -33,12 +33,73 @@ class CoreWorld(Panda3DWorld): self.mouseRightPressed = False # 初始化世界 + self._setupResourcePaths() self._setupCamera() self._setupLighting() self._setupGround() self._loadFont() #self.load_and_play_glb_model() + def _setupResourcePaths(self): + """设置Panda3D资源搜索路径,确保能正确找到Resources文件夹中的模型和贴图""" + try: + import os + from panda3d.core import getModelPath, DSearchPath, Filename + + # 获取项目根目录 + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + resources_dir = os.path.join(project_root, "Resources") + + # 确保Resources目录存在 + if not os.path.exists(resources_dir): + os.makedirs(resources_dir, exist_ok=True) + print(f"✓ 创建Resources目录: {resources_dir}") + + # 添加Resources目录到Panda3D模型搜索路径 + model_path = getModelPath() + resources_filename = Filename.from_os_specific(resources_dir) + + # 检查路径是否已存在,避免重复添加 + if not model_path.findFile(resources_filename): + model_path.appendDirectory(resources_filename) + print(f"✓ 添加Resources到模型搜索路径: {resources_dir}") + + # 同时添加各个子目录到搜索路径 + subdirs = ['models', 'textures', 'animations', 'icons', 'materials'] + for subdir in subdirs: + subdir_path = os.path.join(resources_dir, subdir) + if os.path.exists(subdir_path): + subdir_filename = Filename.from_os_specific(subdir_path) + if not model_path.findFile(subdir_filename): + model_path.appendDirectory(subdir_filename) + print(f"✓ 添加子目录到搜索路径: {subdir}") + else: + # 创建不存在的子目录 + os.makedirs(subdir_path, exist_ok=True) + subdir_filename = Filename.from_os_specific(subdir_path) + model_path.appendDirectory(subdir_filename) + print(f"✓ 创建并添加子目录: {subdir}") + + # 设置纹理搜索路径 + from panda3d.core import getTexturePath + texture_path = getTexturePath() + if not texture_path.findFile(resources_filename): + texture_path.appendDirectory(resources_filename) + + for subdir in ['textures', 'materials', 'icons']: + subdir_path = os.path.join(resources_dir, subdir) + if os.path.exists(subdir_path): + subdir_filename = Filename.from_os_specific(subdir_path) + if not texture_path.findFile(subdir_filename): + texture_path.appendDirectory(subdir_filename) + + print(f"✓ 资源路径设置完成") + print(f" 项目根目录: {project_root}") + print(f" Resources目录: {resources_dir}") + + except Exception as e: + print(f"⚠️ 设置资源路径失败: {e}") + def load_and_play_glb_model(self): """加载 glTF 模型并播放动画""" try: diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 60b58ec9..e2ebab6a 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -861,6 +861,830 @@ class GUIManager: traceback.print_exc() return None + def createVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): + """创建视频播放屏幕 - 改进版本""" + try: + from panda3d.core import CardMaker, TransparencyAttrib, Texture, TextureStage + from PyQt5.QtCore import Qt + import os + + # 确保 pos 是有效的三维坐标元组 + if not isinstance(pos, (tuple, list)) or len(pos) != 3: + print(f"⚠️ 位置参数无效,使用默认值 (0, 0, 0),原始值: {pos}") + pos = (0, 0, 0) + else: + # 确保所有坐标都是数值类型 + try: + pos = (float(pos[0]), float(pos[1]), float(pos[2])) + except (ValueError, TypeError): + print(f"⚠️ 位置参数包含非数值,使用默认值 (0, 0, 0),原始值: {pos}") + pos = (0, 0, 0) + + # 确保 size 是有效数值 + try: + size = float(size) + except (ValueError, TypeError): + print(f"⚠️ 尺寸参数无效,使用默认值 0.2,原始值: {size}") + size = 0.2 + + print(f"📺 开始创建视频屏幕,位置: {pos}, 尺寸: {size}, 视频路径: {video_path}") + + # 获取树形控件 + tree_widget = self._get_tree_widget() + if not tree_widget: + print("❌ 无法访问树形控件") + return None + + # 获取目标父节点列表 + target_parents = tree_widget.get_target_parents_for_gui_creation() + if not target_parents: + print("❌ 没有找到有效的父节点") + return None + + created_videoscreens = [] + + # 为每个有效的父节点创建视频屏幕 + for parent_item, parent_node in target_parents: + try: + # 生成唯一名称 + screen_name = f"VideoScreen_{len(self.gui_elements)}" + + # 使用CardMaker创建视频屏幕框架 + cm = CardMaker('video-screen') + cm.setFrame(-size, size, -size, size) + + # 创建挂载节点 - 挂载到选中的父节点 + video_screen = parent_node.attachNewNode(cm.generate()) + video_screen.setPos(*pos) + video_screen.setName(screen_name) + video_screen.setBin('fixed', 10) + + # 设置透明度支持 + video_screen.setTransparency(TransparencyAttrib.MAlpha) + + # 设置初始颜色为白色,确保纹理能正确显示 + video_screen.setColor(1, 1, 1, 1) + + # 如果提供了视频路径,则加载视频纹理 + 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}") + + # 尝试自动播放视频(如果支持) + 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("ℹ️ 未提供视频文件,显示默认占位符") + + # 确保视频屏幕有正确的材质 + self._ensureVideoScreenMaterial(video_screen) + + # 设置节点标签 + video_screen.setTag("gui_type", "video_screen") + video_screen.setTag("gui_id", f"video_screen_{len(self.gui_elements)}") + video_screen.setTag("gui_text", f"视频屏幕_{len(self.gui_elements)}") + video_screen.setTag("is_gui_element", "1") + video_screen.setTag("is_scene_element", "1") + video_screen.setTag("created_by_user", "1") + if video_path and os.path.exists(video_path): + video_screen.setTag("video_path", video_path) + + # 保存视频纹理引用以便后续控制 + if movie_texture: + video_screen.setPythonTag("movie_texture", movie_texture) + + # 添加到GUI元素列表 + self.gui_elements.append(video_screen) + + print(f"✅ 为 {parent_item.text(0)} 创建视频屏幕成功: {screen_name}") + + # 在Qt树形控件中添加对应节点 + qt_item = tree_widget.add_node_to_tree_widget(video_screen, parent_item, "GUI_VIDEO_SCREEN") + if qt_item: + created_videoscreens.append((video_screen, qt_item)) + else: + created_videoscreens.append((video_screen, None)) + print("⚠️ Qt树节点添加失败,但Panda3D对象已创建") + + except Exception as e: + print(f"❌ 为 {parent_item.text(0)} 创建视频屏幕失败: {str(e)}") + import traceback + traceback.print_exc() + continue + + # 处理创建结果 + if not created_videoscreens: + print("❌ 没有成功创建任何视频屏幕") + return None + + # 选中最后创建的视频屏幕 + if created_videoscreens: + last_screen_np, last_qt_item = created_videoscreens[-1] + if last_qt_item: + tree_widget.setCurrentItem(last_qt_item) + # 更新选择和属性面板 + tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) + + print(f"🎉 总共创建了 {len(created_videoscreens)} 个视频屏幕") + + # 返回值处理 + if len(created_videoscreens) == 1: + return created_videoscreens[0][0] # 单个屏幕返回NodePath + else: + return [screen_np for screen_np, _ in created_videoscreens] # 多个屏幕返回列表 + + except Exception as e: + print(f"❌ 创建视频屏幕过程失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + 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) + print(f"✅ 为视频屏幕创建了新材质: {video_screen.getName()}") + else: + # 更新现有材质确保正确设置 + material = video_screen.getMaterial() + material.setBaseColor(LColor(1, 1, 1, 1)) + material.setAmbient(LColor(1, 1, 1, 1)) # 确保环境光为白色 + video_screen.setMaterial(material, 1) + print(f"✅ 更新了视频屏幕材质: {video_screen.getName()}") + + except Exception as e: + print(f"⚠️ 设置视频屏幕材质时出错: {e}") + + def _debugVideoScreenTextures(self, video_screen): + """调试视频屏幕的纹理状态""" + try: + print(f"调试视频屏幕 {video_screen.getName()}:") + + # 检查PythonTag + movie_texture = video_screen.getPythonTag("movie_texture") + if movie_texture: + print(f" - PythonTag movie_texture: {type(movie_texture)}") + if hasattr(movie_texture, 'is_playable'): + print(f" - is_playable: {movie_texture.is_playable()}") + else: + print(" - PythonTag movie_texture: None") + + # 检查所有纹理阶段 + texture_stages = video_screen.findAllTextureStages() + print(f" - 纹理阶段数: {texture_stages.getNumStages()}") + for i in range(texture_stages.getNumStages()): + stage = texture_stages.getStage(i) + texture = video_screen.getTexture(stage) + print(f" - 阶段 {i}: {stage.getName()}, 纹理: {texture.getName() if texture else 'None'}") + + except Exception as e: + print(f"调试视频屏幕纹理时出错: {e}") + + def playVideo(self, video_screen): + """播放视频 - 改进版本,支持从暂停处继续播放""" + try: + # 获取视频纹理 + movie_texture = self._getMovieTextureFromScreen(video_screen) + + if movie_texture: + # 检查是否有播放方法 + if hasattr(movie_texture, 'play'): + try: + movie_texture.play() + print(f"▶️ 继续播放视频: {video_screen.getName()}") + return True + except Exception as play_error: + print(f"⚠️ 播放视频时出错: {play_error}") + return False + else: + print(f"⚠️ 纹理对象没有播放方法: {video_screen.getName()}") + return False + else: + print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") + self._debugVideoScreenTextures(video_screen) + return False + except Exception as e: + print(f"❌ 播放视频失败: {e}") + import traceback + traceback.print_exc() + return False + + def _getMovieTextureFromScreen(self, video_screen): + """从视频屏幕获取视频纹理""" + try: + # 方法1: 从PythonTag获取 + movie_texture = video_screen.getPythonTag("movie_texture") + if movie_texture: + return movie_texture + + # 方法2: 从纹理阶段获取 + from panda3d.core import TextureStage + texture_stage = video_screen.findTextureStage("video") + if texture_stage: + movie_texture = video_screen.getTexture(texture_stage) + if movie_texture: + return movie_texture + + # 方法3: 获取第一个纹理 + if video_screen.hasTexture(): + movie_texture = video_screen.getTexture() + if movie_texture: + return movie_texture + + return None + except Exception as e: + print(f"获取视频纹理时出错: {e}") + return None + + def pauseVideo(self, video_screen): + """暂停视频""" + try: + movie_texture = self._getMovieTextureFromScreen(video_screen) + + if movie_texture: + # 检查是否有暂停方法 + if hasattr(movie_texture, 'stop'): # MovieTexture使用stop来暂停 + try: + movie_texture.stop() + print(f"⏸️ 视频已暂停: {video_screen.getName()}") + return True + except Exception as stop_error: + print(f"⚠️ 暂停视频时出错: {stop_error}") + return False + elif hasattr(movie_texture, 'set play rate'): # 某些版本支持设置播放速率 + try: + movie_texture.setPlayRate(0.0) + print(f"⏸️ 视频已暂停(播放速率设为0): {video_screen.getName()}") + return True + except Exception as rate_error: + print(f"⚠️ 设置播放速率时出错: {rate_error}") + return False + else: + print(f"⚠️ 纹理对象没有暂停方法: {video_screen.getName()}") + return False + else: + print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 暂停视频失败: {e}") + import traceback + traceback.print_exc() + return False + + def stopVideo(self, video_screen): + """停止视频(回到开头)""" + try: + movie_texture = self._getMovieTextureFromScreen(video_screen) + + if movie_texture: + # 停止并重置到开头 + if hasattr(movie_texture, 'stop'): + try: + movie_texture.stop() + # 如果有重置方法,调用它 + if hasattr(movie_texture, 'setTime'): + movie_texture.setTime(0.0) + print(f"⏹️ 视频已停止: {video_screen.getName()}") + return True + except Exception as stop_error: + print(f"⚠️ 停止视频时出错: {stop_error}") + return False + else: + print(f"⚠️ 纹理对象没有停止方法: {video_screen.getName()}") + return False + else: + print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 停止视频失败: {e}") + import traceback + traceback.print_exc() + return False + + + def setVideoTime(self, video_screen, time_seconds): + """设置视频播放时间""" + try: + movie_texture = video_screen.getPythonTag("movie_texture") + + # 备用获取方法 + if not movie_texture: + from panda3d.core import TextureStage + texture_stage = video_screen.findTextureStage("video") + if texture_stage: + movie_texture = video_screen.getTexture(texture_stage) + + if not movie_texture and video_screen.hasTexture(): + movie_texture = video_screen.getTexture() + + if movie_texture and hasattr(movie_texture, 'set_time'): + movie_texture.set_time(time_seconds) + print(f"🕒 设置视频时间 {time_seconds}s: {video_screen.getName()}") + return True + else: + print(f"❌ 视频屏幕没有关联的视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 设置视频时间失败: {e}") + return False + + def loadVideoFile(self, video_screen, video_path): + """为视频屏幕加载新的视频文件""" + try: + from panda3d.core import Texture, TextureStage + import os + + if not os.path.exists(video_path): + print(f"❌ 视频文件不存在: {video_path}") + return False + + # 加载新的视频纹理 + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + # 清除现有的纹理 + video_screen.clearTexture() + + # 设置视频纹理属性 + movie_texture.setWrapU(Texture.WM_clamp) + movie_texture.setWrapV(Texture.WM_clamp) + movie_texture.setMinfilter(Texture.FT_linear) + movie_texture.setMagfilter(Texture.FT_linear) + + # 如果视频纹理支持循环播放,设置循环 + if hasattr(movie_texture, 'set_loop'): + movie_texture.set_loop(True) + + # 如果视频纹理支持播放速率控制,设置正常速率 + if hasattr(movie_texture, 'set_play_rate'): + movie_texture.set_play_rate(1.0) + + # 重要:为视频纹理创建专用的纹理阶段 + texture_stage = TextureStage("video") + texture_stage.setSort(0) # 使用第一个纹理槽 + texture_stage.setMode(TextureStage.MModulate) + + # 应用纹理到视频屏幕 + video_screen.setTexture(texture_stage, movie_texture) + + # 保存新的视频纹理引用到PythonTag + video_screen.setPythonTag("movie_texture", movie_texture) + video_screen.setTag("video_path", video_path) + + # 确保视频屏幕有正确的材质 + self._ensureVideoScreenMaterial(video_screen) + + print(f"✅ 成功加载新视频: {video_path}") + return True + else: + print(f"❌ 无法加载视频文件: {video_path}") + return False + + except Exception as e: + print(f"❌ 加载视频文件失败: {e}") + import traceback + traceback.print_exc() + return False + + def _loadMovieTexture(self, video_path): + """加载视频纹理的兼容方法""" + try: + from panda3d.core import Texture, MovieTexture + import os + + # 检查文件是否存在 + if not os.path.exists(video_path): + print(f"❌ 视频文件不存在: {video_path}") + return None + + print(f"🔍 尝试加载视频文件: {video_path}") + + # 方法1: 尝试使用 MovieTexture(专门用于视频) + try: + movie_texture = MovieTexture(video_path) + if movie_texture.read(video_path): + print("✅ 使用 MovieTexture 成功加载视频") + self._configureVideoTexture(movie_texture) + return movie_texture + else: + print("⚠️ MovieTexture.read() 返回失败") + except Exception as e: + print(f"⚠️ MovieTexture 方法失败: {e}") + + # 方法2: 尝试使用 loader.loadTexture + try: + movie_texture = self.world.loader.loadTexture(video_path) + if movie_texture and hasattr(movie_texture, 'is_playable') and movie_texture.is_playable(): + print("✅ 使用 loader.loadTexture 成功加载视频纹理") + self._configureVideoTexture(movie_texture) + return movie_texture + else: + print("⚠️ loader.loadTexture 加载的不是可播放的视频纹理") + except Exception as e: + print(f"⚠️ loader.loadTexture 方法失败: {e}") + + # 方法3: 尝试使用 Texture.read(作为最后备选) + try: + texture = Texture() + if texture.read(video_path): + print("✅ 使用 Texture.read 成功加载(可能作为静态纹理)") + self._configureVideoTexture(texture) + return texture + except Exception as e: + print(f"⚠️ Texture.read 方法失败: {e}") + + print("❌ 所有视频纹理加载方法都失败") + return None + + except Exception as e: + print(f"❌ 加载视频纹理时发生未知错误: {e}") + import traceback + traceback.print_exc() + return None + + def _configureVideoTexture(self, texture): + """配置视频纹理属性""" + try: + from panda3d.core import Texture + + # 设置纹理属性 + texture.setWrapU(Texture.WM_clamp) + texture.setWrapV(Texture.WM_clamp) + texture.setMinfilter(Texture.FT_linear) + texture.setMagfilter(Texture.FT_linear) + + # 如果是可播放的视频纹理,设置播放属性 + if hasattr(texture, 'set_loop') and hasattr(texture, 'set_play_rate'): + texture.set_loop(True) + texture.set_play_rate(1.0) + + print(f"✅ 视频纹理配置完成: {texture.getName()}") + + except Exception as e: + print(f"⚠️ 配置视频纹理时出错: {e}") + + def createGUI2DVideoScreen(self, pos=(0, 0), size=0.2, video_path=None): + """创建2D视频播放屏幕 - 使用2D坐标""" + try: + from direct.gui.DirectGui import DirectFrame + from panda3d.core import TransparencyAttrib, Texture, TextureStage + from PyQt5.QtCore import Qt + import os + + # 确保 pos 是有效的二维坐标元组 + if pos is None or pos is False or not isinstance(pos, (tuple, list)) or len(pos) != 2: + print(f"⚠️ 位置参数无效,使用默认值 (0, 0),原始值: {pos}") + pos = (0, 0) + else: + # 确保所有坐标都是数值类型 + try: + pos = (float(pos[0]), float(pos[1])) + except (ValueError, TypeError, IndexError) as e: + print(f"⚠️ 位置参数包含非数值或索引错误,使用默认值 (0, 0),原始值: {pos}, 错误: {e}") + pos = (0, 0) + + # 确保 size 是有效数值 + try: + size = float(size) + except (ValueError, TypeError) as e: + print(f"⚠️ 尺寸参数无效,使用默认值 0.2,原始值: {size}, 错误: {e}") + size = 0.2 + + print(f"📺 开始创建2D视频屏幕,位置: {pos}, 尺寸: {size}, 视频路径: {video_path}") + + # 获取树形控件 + tree_widget = self._get_tree_widget() + if not tree_widget: + print("❌ 无法访问树形控件") + return None + + # 获取目标父节点列表 + target_parents = tree_widget.get_target_parents_for_gui_creation() + if not target_parents: + print("❌ 没有找到有效的父节点") + return None + + created_videoscreens = [] + + # 为每个有效的父节点创建2D视频屏幕 + for parent_item, parent_node in target_parents: + try: + # 生成唯一名称 + screen_name = f"GUI2DVideoScreen_{len(self.gui_elements)}" + + # 使用DirectFrame创建2D视频屏幕 + video_screen = DirectFrame( + frameSize=(-size, size, -size, size), + frameColor=(1, 1, 1, 1), # 默认背景色 + 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) + + # 设置透明度支持 + video_screen.setTransparency(TransparencyAttrib.MAlpha) + + # 设置2D视频屏幕特有的标签 + video_screen.setTag("gui_type", "2d_video_screen") + video_screen.setTag("gui_id", f"2d_video_screen_{len(self.gui_elements)}") + video_screen.setTag("gui_text", f"2D视频屏幕_{len(self.gui_elements)}") + video_screen.setTag("is_gui_element", "1") + video_screen.setTag("is_scene_element", "1") + video_screen.setTag("created_by_user", "1") + + if video_path and os.path.exists(video_path): + video_screen.setTag("video_path", video_path) + + # 如果提供了视频路径,则加载视频纹理 + 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) + + # 尝试自动播放视频(如果支持) + 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 video_path: + print(f"⚠️ 2D视频文件不存在: {video_path}") + + # 添加到GUI元素列表 + self.gui_elements.append(video_screen) + + print(f"✅ 为 {parent_item.text(0)} 创建2D视频屏幕成功: {screen_name}") + + # 在Qt树形控件中添加对应节点 + qt_item = tree_widget.add_node_to_tree_widget(video_screen, parent_item, "GUI_2D_VIDEO_SCREEN") + if qt_item: + created_videoscreens.append((video_screen, qt_item)) + else: + created_videoscreens.append((video_screen, None)) + print("⚠️ Qt树节点添加失败,但Panda3D对象已创建") + + except Exception as e: + print(f"❌ 为 {parent_item.text(0)} 创建2D视频屏幕失败: {str(e)}") + import traceback + traceback.print_exc() + continue + + # 处理创建结果 + if not created_videoscreens: + print("❌ 没有成功创建任何2D视频屏幕") + return None + + # 选中最后创建的视频屏幕 + if created_videoscreens: + last_screen_np, last_qt_item = created_videoscreens[-1] + if last_qt_item: + tree_widget.setCurrentItem(last_qt_item) + # 更新选择和属性面板 + tree_widget.update_selection_and_properties(last_screen_np, last_qt_item) + + print(f"🎉 总共创建了 {len(created_videoscreens)} 个2D视频屏幕") + + # 返回值处理 + if len(created_videoscreens) == 1: + return created_videoscreens[0][0] # 单个屏幕返回NodePath + else: + return [screen_np for screen_np, _ in created_videoscreens] # 多个屏幕返回列表 + + except Exception as e: + print(f"❌ 创建2D视频屏幕过程失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def load2DVideoFile(self, video_screen, video_path): + """为2D视频屏幕加载新的视频文件""" + try: + import os + + if not os.path.exists(video_path): + print(f"❌ 2D视频文件不存在: {video_path}") + return False + + # 加载新的视频纹理 + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + # 应用纹理到2D视频屏幕 + video_screen["frameTexture"] = movie_texture + + # 保存视频纹理引用 + video_screen.setPythonTag("movie_texture", movie_texture) + video_screen.setTag("video_path", video_path) + + print(f"✅ 成功加载新2D视频: {video_path}") + return True + else: + print(f"❌ 无法加载2D视频文件: {video_path}") + return False + + except Exception as e: + print(f"❌ 加载2D视频文件失败: {e}") + import traceback + traceback.print_exc() + return False + + def _loadMovieTexture(self, video_path): + """加载视频纹理的兼容方法""" + try: + from panda3d.core import Texture, MovieTexture + import os + + # 检查文件是否存在 + if not os.path.exists(video_path): + print(f"❌ 视频文件不存在: {video_path}") + return None + + print(f"🔍 尝试加载视频文件: {video_path}") + + # 方法1: 尝试使用 MovieTexture(专门用于视频) + try: + movie_texture = MovieTexture(video_path) + if movie_texture.read(video_path): + print("✅ 使用 MovieTexture 成功加载视频") + self._configureVideoTexture(movie_texture) + return movie_texture + else: + print("⚠️ MovieTexture.read() 返回失败") + except Exception as e: + print(f"⚠️ MovieTexture 方法失败: {e}") + + # 方法2: 尝试使用 loader.loadTexture + try: + movie_texture = self.world.loader.loadTexture(video_path) + if movie_texture and hasattr(movie_texture, 'is_playable') and movie_texture.is_playable(): + print("✅ 使用 loader.loadTexture 成功加载视频纹理") + self._configureVideoTexture(movie_texture) + return movie_texture + else: + print("⚠️ loader.loadTexture 加载的不是可播放的视频纹理") + except Exception as e: + print(f"⚠️ loader.loadTexture 方法失败: {e}") + + # 方法3: 尝试使用 Texture.read(作为最后备选) + try: + texture = Texture() + if texture.read(video_path): + print("✅ 使用 Texture.read 成功加载(可能作为静态纹理)") + self._configureVideoTexture(texture) + return texture + except Exception as e: + print(f"⚠️ Texture.read 方法失败: {e}") + + print("❌ 所有视频纹理加载方法都失败") + return None + + except Exception as e: + print(f"❌ 加载视频纹理时发生未知错误: {e}") + import traceback + traceback.print_exc() + return None + + def play2DVideo(self, video_screen): + """播放2D视频""" + try: + # 获取视频纹理 + movie_texture = video_screen.getPythonTag("movie_texture") + + # 备用获取方法 + if not movie_texture: + # 尝试从frameTexture获取 + if hasattr(video_screen, 'frameTexture'): + movie_texture = video_screen.frameTexture + + if movie_texture and hasattr(movie_texture, 'play'): + try: + movie_texture.play() + print(f"▶️ 继续播放2D视频: {video_screen.getName()}") + return True + except Exception as play_error: + print(f"⚠️ 播放2D视频时出错: {play_error}") + return False + else: + print(f"⚠️ 2D视频屏幕没有可播放的视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 播放2D视频失败: {e}") + return False + + def pause2DVideo(self, video_screen): + """暂停2D视频""" + try: + movie_texture = video_screen.getPythonTag("movie_texture") + + # 备用获取方法 + if not movie_texture: + # 尝试从frameTexture获取 + if hasattr(video_screen, 'frameTexture'): + movie_texture = video_screen.frameTexture + + if movie_texture and hasattr(movie_texture, 'stop'): # MovieTexture使用stop来暂停 + try: + movie_texture.stop() + print(f"⏸️ 2D视频已暂停: {video_screen.getName()}") + return True + except Exception as stop_error: + print(f"⚠️ 暂停2D视频时出错: {stop_error}") + return False + else: + print(f"⚠️ 2D视频屏幕没有可暂停的视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 暂停2D视频失败: {e}") + return False + + def stop2DVideo(self, video_screen): + """停止2D视频(回到开头)""" + try: + movie_texture = video_screen.getPythonTag("movie_texture") + + # 备用获取方法 + if not movie_texture: + # 尝试从frameTexture获取 + if hasattr(video_screen, 'frameTexture'): + movie_texture = video_screen.frameTexture + + if movie_texture: + try: + if hasattr(movie_texture, 'stop'): + movie_texture.stop() + # 重置到开头 + if hasattr(movie_texture, 'setTime'): + movie_texture.setTime(0.0) + print(f"⏹️ 2D视频已停止: {video_screen.getName()}") + return True + except Exception as stop_error: + print(f"⚠️ 停止2D视频时出错: {stop_error}") + return False + else: + print(f"⚠️ 2D视频屏幕没有视频纹理: {video_screen.getName()}") + return False + except Exception as e: + print(f"❌ 停止2D视频失败: {e}") + return False + def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"): """创建3D虚拟屏幕 - 支持多选创建,优化版本""" try: @@ -1209,7 +2033,7 @@ class GUIManager: if hasattr(gui_element, 'getScale'): scale = gui_element.getScale() scaleEdit = QDoubleSpinBox() - scaleEdit.setRange(0.01, 10) + scaleEdit.setRange(0.01, 100) scaleEdit.setSingleStep(0.1) scaleEdit.setValue(scale.getX()) form.addRow("缩放:", scaleEdit) @@ -1815,7 +2639,7 @@ class GUIManager: # 宽度控件 transform_layout.addWidget(QLabel("宽度"), 4, 0) widthSpinBox = QDoubleSpinBox() - widthSpinBox.setRange(0.1, 10) + widthSpinBox.setRange(0.01, 100) widthSpinBox.setSingleStep(0.1) widthSpinBox.setValue(width) widthSpinBox.valueChanged.connect( @@ -1825,7 +2649,7 @@ class GUIManager: # 高度控件 transform_layout.addWidget(QLabel("高度"), 4, 2) heightSpinBox = QDoubleSpinBox() - heightSpinBox.setRange(0.1, 10) + heightSpinBox.setRange(0.01, 100) heightSpinBox.setSingleStep(0.1) heightSpinBox.setValue(height) heightSpinBox.valueChanged.connect( @@ -1885,7 +2709,7 @@ class GUIManager: # 缩放数值输入框 scale_x = QDoubleSpinBox() - scale_x.setRange(0.01, 10) + scale_x.setRange(0.01, 100) scale_x.setSingleStep(0.1) scale_x.setValue( scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale) @@ -1893,7 +2717,7 @@ class GUIManager: transform_layout.addWidget(scale_x, 3, 1) scale_y = QDoubleSpinBox() - scale_y.setRange(0.01, 10) + scale_y.setRange(0.01, 100) scale_y.setSingleStep(0.1) scale_y.setValue( scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len( @@ -1902,7 +2726,7 @@ class GUIManager: transform_layout.addWidget(scale_y, 3, 2) scale_z = QDoubleSpinBox() - scale_z.setRange(0.01, 10) + scale_z.setRange(0.01, 100) scale_z.setSingleStep(0.1) scale_z.setValue( scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len( @@ -1917,7 +2741,7 @@ class GUIManager: scale = gui_element.getScale() scaleSpinBox = QDoubleSpinBox() - scaleSpinBox.setRange(0.01, 10) + scaleSpinBox.setRange(0.01, 100) scaleSpinBox.setSingleStep(0.1) scaleSpinBox.setValue(scale.getX()) scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v)) @@ -2099,7 +2923,7 @@ class GUIManager: try: gui_type = gui_element.getTag("gui_type") - if gui_type in ["button", "label", "entry", "2d_image"]: + if gui_type in ["button", "label", "entry", "2d_image","2d_video_screen"]: # 2D元素使用屏幕坐标,需要转换 current_pos = gui_element.getPos() @@ -2134,7 +2958,7 @@ class GUIManager: try: gui_type = gui_element.getTag("gui_type") - if gui_type in ["3d_text", "3d_image"]: + if gui_type in ["3d_text", "3d_image", "video_screen"]: current_pos = gui_element.getPos() if axis == "x": @@ -2168,7 +2992,7 @@ class GUIManager: if value == 0: value = 0.01 - if gui_type in ["3d_text", "3d_image"]: + if gui_type in ["3d_text", "3d_image","video_screen","virtual_screen"]: # 3D元素处理 if axis == "x": new_scale = (value, current_scale.getY(), current_scale.getZ()) @@ -2214,7 +3038,6 @@ class GUIManager: # 对于其他2D元素,使用统一缩放 gui_element.setScale(value) - print(f"✓ 更新GUI元素缩放: {axis}={value}") return True except Exception as e: print(f"✗ 更新GUI元素缩放失败: {e}") diff --git a/main.py b/main.py index ed81d628..b930bef5 100644 --- a/main.py +++ b/main.py @@ -234,6 +234,14 @@ class MyWorld(CoreWorld): """创建2D GUI图片""" return self.gui_manager.createGUI2DImage(pos, image_path, size) + def createVideoScreen(self,pos=(0,0,0),size=1,video_path=None): + """创建视频屏幕""" + return self.gui_manager.createVideoScreen(pos,size,video_path) + + def create2DVideoScreen(self,pos=(0,0,0),size=0.2,video_path=None): + """创建2D视频屏幕""" + return self.gui_manager.createGUI2DVideoScreen(pos,size,video_path) + def createSpotLight(self,pos=(0,0,5)): """创建聚光灯""" return self.scene_manager.createSpotLight(pos) diff --git a/ui/main_window.py b/ui/main_window.py index 873c84ec..f9891d46 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -404,6 +404,7 @@ class MainWindow(QMainWindow): self.createImageAction = self.createGUIaddMenu.addAction('创建图片') self.createGUIaddMenu.addSeparator() self.createVideoScreen = self.createGUIaddMenu.addAction('创建视频屏幕') + self.create2DVideoScreen = self.createGUIaddMenu.addAction('创建2D视频屏幕') self.createSphericalVideo = self.createGUIaddMenu.addAction('创建球形视频') self.createGUIaddMenu.addSeparator() self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕') @@ -426,7 +427,8 @@ class MainWindow(QMainWindow): self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel()) self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry()) self.createImageAction.triggered.connect(lambda: self.world.createGUI2DImage()) - # self.createVideoScreen.triggered.connect(self.world.createVideoScreen) + self.createVideoScreen.triggered.connect(self.world.createVideoScreen) + self.create2DVideoScreen.triggered.connect(self.world.create2DVideoScreen) # self.createSphericalVideo.triggered.connect(self.world.createSphericalVideo) self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen()) self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight()) @@ -451,6 +453,7 @@ class MainWindow(QMainWindow): 'createEntry': self.createEntryAction, 'createImage': self.createImageAction, 'createVideoScreen': self.createVideoScreen, + 'create2DVideoScreen':self.create2DVideoScreen, 'createSphericalVideo': self.createSphericalVideo, 'createVirtualScreen': self.createVirtualScreenAction, 'createSpotLight': self.createSpotLightAction, diff --git a/ui/property_panel.py b/ui/property_panel.py index d50eb34b..7355478b 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -6,7 +6,7 @@ from typing import Hashable from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget, - QVBoxLayout, QGroupBox, QGridLayout, QSpinBox) + QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog) from PyQt5.QtCore import Qt from deploy_libs.unicodedata import normalize from direct.actor.Actor import Actor @@ -299,20 +299,20 @@ class PropertyPanelManager: transform_layout.addWidget(self.pos_z,1,3) transform_layout.addWidget(QLabel("旋转"),2,0) - self.rot_h = QDoubleSpinBox() - self.rot_p = QDoubleSpinBox() - self.rot_r = QDoubleSpinBox() + self.rot_x = QDoubleSpinBox() + self.rot_y = QDoubleSpinBox() + self.rot_z = QDoubleSpinBox() - for rot_widget in [self.rot_h,self.rot_p,self.rot_r]: + for rot_widget in [self.rot_x,self.rot_y,self.rot_z]: rot_widget.setRange(-360,360) - self.rot_h.setValue(terrain_node.getH()) - self.rot_p.setValue(terrain_node.getP()) - self.rot_r.setValue(terrain_node.getR()) + self.rot_x.setValue(terrain_node.getH()) + self.rot_y.setValue(terrain_node.getP()) + self.rot_z.setValue(terrain_node.getR()) - self.rot_h.valueChanged.connect(lambda v:terrain_node.setH(v)) - self.rot_p.valueChanged.connect(lambda v:terrain_node.setP(v)) - self.rot_r.valueChanged.connect(lambda v:terrain_node.setR(v)) + self.rot_x.valueChanged.connect(lambda v:terrain_node.setH(v)) + self.rot_y.valueChanged.connect(lambda v:terrain_node.setP(v)) + self.rot_z.valueChanged.connect(lambda v:terrain_node.setR(v)) h_label = QLabel("H") p_label = QLabel("P") @@ -324,9 +324,9 @@ class PropertyPanelManager: transform_layout.addWidget(h_label,2,1) transform_layout.addWidget(p_label,2,2) transform_layout.addWidget(r_label,2,3) - transform_layout.addWidget(self.rot_h,3,1) - transform_layout.addWidget(self.rot_p,3,2) - transform_layout.addWidget(self.rot_r,3,3) + transform_layout.addWidget(self.rot_x,3,1) + transform_layout.addWidget(self.rot_y,3,2) + transform_layout.addWidget(self.rot_z,3,3) transform_layout.addWidget(QLabel("缩放"),4,0) self.scale_x = QDoubleSpinBox() @@ -633,10 +633,16 @@ class PropertyPanelManager: self._safeUpdateSpinBox('pos_x', logical_x) self._safeUpdateSpinBox('pos_z', logical_z) else: - # 3D GUI组件使用世界坐标 - self._safeUpdateSpinBox('pos_x', pos.getX()) - self._safeUpdateSpinBox('pos_y', pos.getY()) - self._safeUpdateSpinBox('pos_z', pos.getZ()) + # 3D GUI组件使用世界坐标(包括 video_screen) + pos = gui_element.getPos() + + # 修复:确保 video_screen 也能正确更新位置控件 + if hasattr(self, 'pos_x') and self.pos_x: + self._safeUpdateSpinBox('pos_x', pos.getX()) + if hasattr(self, 'pos_y') and self.pos_y: + self._safeUpdateSpinBox('pos_y', pos.getY()) + if hasattr(self, 'pos_z') and self.pos_z: + self._safeUpdateSpinBox('pos_z', pos.getZ()) # 更新缩放属性 if hasattr(self, 'scale_x') and self.scale_x: @@ -1107,22 +1113,22 @@ class PropertyPanelManager: # 旋转 (Rotation) transform_layout.addWidget(QLabel("旋转"), 4, 0) - self.rot_h = QDoubleSpinBox() - self.rot_p = QDoubleSpinBox() - self.rot_r = QDoubleSpinBox() + self.rot_x = QDoubleSpinBox() + self.rot_y = QDoubleSpinBox() + self.rot_z = QDoubleSpinBox() # 设置旋转控件属性 - for rot_widget in [self.rot_h, self.rot_p, self.rot_r]: + for rot_widget in [self.rot_x, self.rot_y, self.rot_z]: rot_widget.setRange(-360, 360) - self.rot_h.setValue(model.getH()) - self.rot_p.setValue(model.getP()) - self.rot_r.setValue(model.getR()) + self.rot_x.setValue(model.getH()) + self.rot_y.setValue(model.getP()) + self.rot_z.setValue(model.getR()) # 连接旋转变化事件 - self.rot_h.valueChanged.connect(lambda v: model.setH(v)) - self.rot_p.valueChanged.connect(lambda v: model.setP(v)) - self.rot_r.valueChanged.connect(lambda v: model.setR(v)) + self.rot_x.valueChanged.connect(lambda v: model.setH(v)) + self.rot_y.valueChanged.connect(lambda v: model.setP(v)) + self.rot_z.valueChanged.connect(lambda v: model.setR(v)) # 创建并设置 H, P, R 标签居中 h_label = QLabel("H") @@ -1135,9 +1141,9 @@ class PropertyPanelManager: transform_layout.addWidget(h_label, 4, 1) transform_layout.addWidget(p_label, 4, 2) transform_layout.addWidget(r_label, 4, 3) - transform_layout.addWidget(self.rot_h, 5, 1) - transform_layout.addWidget(self.rot_p, 5, 2) - transform_layout.addWidget(self.rot_r, 5, 3) + transform_layout.addWidget(self.rot_x, 5, 1) + transform_layout.addWidget(self.rot_y, 5, 2) + transform_layout.addWidget(self.rot_z, 5, 3) # 缩放 (Scale) transform_layout.addWidget(QLabel("缩放"), 6, 0) @@ -1253,6 +1259,8 @@ class PropertyPanelManager: def updateGUIPropertyPanel(self, gui_element): """更新GUI元素属性面板""" + self.clearPropertyPanel() + gui_type = gui_element.getTag("gui_type") gui_text = gui_element.getTag("gui_text") @@ -1289,7 +1297,7 @@ class PropertyPanelManager: # 变换属性组(合并位置和变换) if hasattr(gui_element, 'getPos'): # 根据GUI类型设置组名—— - if gui_type in ["button", "label", "entry","2d_image"]: + if gui_type in ["button", "label", "entry","2d_image","2d_video_screen"]: transform_group = QGroupBox("变换 Rect Transform") else: transform_group = QGroupBox("变换 Transform") @@ -1299,7 +1307,7 @@ class PropertyPanelManager: pos = gui_element.getPos() # 根据GUI类型决定位置编辑方式 - if gui_type in ["button", "label", "entry","2d_image"]: + if gui_type in ["button", "label", "entry","2d_image","2d_video_screen"]: # 2D GUI组件使用屏幕坐标 logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标 logical_z = pos.getZ() / 0.1 @@ -1317,14 +1325,13 @@ class PropertyPanelManager: transform_layout.addWidget(z_label, 0, 2) self.pos_x = QDoubleSpinBox() - self.pos_x.setRange(-50, 50) + self.pos_x.setRange(-100, 100) self.pos_x.setValue(logical_x) self.pos_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v)) - transform_layout.addWidget(self.pos_x, 1, 1) self.pos_z = QDoubleSpinBox() - self.pos_z.setRange(-50, 50) + self.pos_z.setRange(-100, 100) self.pos_z.setValue(logical_z) self.pos_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v)) transform_layout.addWidget(self.pos_z, 1, 2) @@ -1340,13 +1347,10 @@ class PropertyPanelManager: transform_layout.addWidget(actualXLabel, 3, 1) transform_layout.addWidget(actualZLabel, 3, 2) - if gui_type == "2d_image": + if gui_type in ["2d_image","2d_video_screen"]: scale = gui_element.getScale() - width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, - (tuple, list)) else scale - height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale, - (tuple, list)) and len( - scale) > 1 else scale + width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,(tuple, list)) else scale + height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,(tuple, list)) and len(scale) > 1 else scale transform_layout.addWidget(QLabel("宽度"),4,0) self.scale_x = QDoubleSpinBox() @@ -1426,7 +1430,7 @@ class PropertyPanelManager: # 缩放数值输入框 self.scale_x = QDoubleSpinBox() - self.scale_x.setRange(0.01, 10) + self.scale_x.setRange(0.01, 100) self.scale_x.setSingleStep(0.1) self.scale_x.setValue( scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale) @@ -1434,7 +1438,7 @@ class PropertyPanelManager: transform_layout.addWidget(self.scale_x, 3, 1) self.scale_y = QDoubleSpinBox() - self.scale_y.setRange(0.01, 10) + self.scale_y.setRange(0.01, 100) self.scale_y.setSingleStep(0.1) self.scale_y.setValue( scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len( @@ -1443,7 +1447,7 @@ class PropertyPanelManager: transform_layout.addWidget(self.scale_y, 3, 2) self.scale_z = QDoubleSpinBox() - self.scale_z.setRange(0.01, 10) + self.scale_z.setRange(0.01, 100) self.scale_z.setSingleStep(0.1) self.scale_z.setValue( scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len( @@ -1455,8 +1459,8 @@ class PropertyPanelManager: transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) - # 为2D图像添加Sort属性 - if gui_type == "2d_image": + # 为2D图像和视频屏幕添加Sort属性 + if gui_type in ["2d_image","2d_video_screen"]: sort_group = QGroupBox("显示顺序") sort_layout = QGridLayout() @@ -1474,17 +1478,13 @@ class PropertyPanelManager: gui_element.setTag("sort", str(value)) # 应用sort到节点 gui_element.setBin("fixed", value) - print(f"✓ 更新2D图像渲染顺序: {value}") + print(f"✓ 更新{gui_type}渲染顺序: {value}") except Exception as e: - print(f"✗ 更新2D图像渲染顺序失败: {e}") + print(f"✗ 更新{gui_type}渲染顺序失败: {e}") sort_spin.valueChanged.connect(updateSort) sort_layout.addWidget(sort_spin, 0, 1) - # sort_help = QLabel("数值越小越先渲染\n负数在背景,正数在前景") - # sort_help.setStyleSheet("font-size: 10px; color: gray;") - # sort_layout.addWidget(sort_help, 1, 0, 1, 2) - sort_group.setLayout(sort_layout) self._propertyLayout.addWidget(sort_group) @@ -1570,9 +1570,9 @@ class PropertyPanelManager: # 显示当前贴图路径(简化显示) current_texture_path = gui_element.getTag("texture_path") or gui_element.getTag("image_path") or "未设置" - #texture_label = QLabel(current_texture_path) - #texture_label.setWordWrap(True) - #image_layout.addWidget(texture_label, 0, 1) + texture_label = QLabel(current_texture_path) + texture_label.setWordWrap(True) + image_layout.addWidget(texture_label, 0, 1) # 选择图片按钮 select_texture_button = QPushButton("选择图片...") @@ -1595,7 +1595,7 @@ class PropertyPanelManager: gui_element.setTag("texture_path", file_path) gui_element.setTag("image_path", file_path) # 更新显示 - #texture_label.setText(file_path) + texture_label.setText(file_path) # 可选:刷新场景树或其他 UI # self.world.scene_manager.updateSceneTree() @@ -1605,6 +1605,12 @@ class PropertyPanelManager: self._propertyLayout.addWidget(image_group) # 添加弹性空间 + + if gui_type == "video_screen": + self._addVideoScreenProperties(gui_element) + if gui_type == "2d_video_screen": + self._add2DVideoScreenProperties(gui_element) + self._propertyLayout.addStretch() # 强制更新布局 @@ -1614,7 +1620,188 @@ class PropertyPanelManager: if propertyWidget: propertyWidget.update() - # 在gui_manager.py或其他相关文件中添加以下方法 + def _add2DVideoScreenProperties(self, video_screen): + """为2D视频屏幕添加属性控制面板""" + try: + from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, + QFileDialog, QHBoxLayout) + import os + + # 视频信息组 + video_info_group = QGroupBox("2D视频信息") + video_info_layout = QGridLayout() + + # 显示当前视频文件路径 + video_path = video_screen.getTag("video_path") + if video_path and os.path.exists(video_path): + video_info_layout.addWidget(QLabel("视频文件:"), 0, 0) + path_label = QLabel(os.path.basename(video_path)) + path_label.setWordWrap(True) + path_label.setStyleSheet("color: #00AAFF;") + video_info_layout.addWidget(path_label, 0, 1) + else: + video_info_layout.addWidget(QLabel("状态:"), 0, 0) + status_label = QLabel("无视频文件" if not video_path else "文件不存在") + status_label.setStyleSheet("color: red;") + video_info_layout.addWidget(status_label, 0, 1) + video_screen.setBin("fixed",0) + + video_info_group.setLayout(video_info_layout) + self._propertyLayout.addWidget(video_info_group) + + # 视频控制组 + video_control_group = QGroupBox("2D视频控制") + video_control_layout = QHBoxLayout() + + # 播放按钮 + play_btn = QPushButton("▶️ 播放") + play_btn.clicked.connect(lambda: self.world.gui_manager.play2DVideo(video_screen)) + video_control_layout.addWidget(play_btn) + + # 暂停按钮 + pause_btn = QPushButton("⏸️ 暂停") + pause_btn.clicked.connect(lambda: self.world.gui_manager.pause2DVideo(video_screen)) + video_control_layout.addWidget(pause_btn) + + # 停止按钮 + stop_btn = QPushButton("⏹️ 停止") + stop_btn.clicked.connect(lambda: self.world.gui_manager.stop2DVideo(video_screen)) + video_control_layout.addWidget(stop_btn) + + video_control_group.setLayout(video_control_layout) + self._propertyLayout.addWidget(video_control_group) + + # 加载新视频按钮 + load_btn = QPushButton("📁 加载新视频...") + load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen)) + self._propertyLayout.addWidget(load_btn) + + except Exception as e: + print(f"添加2D视频屏幕属性失败: {e}") + + def _loadNew2DVideo(self, video_screen): + """为2D视频屏幕加载新视频文件""" + try: + file_path, _ = QFileDialog.getOpenFileName( + None, + "选择视频文件", + "", + "视频文件 (*.mp4 *.avi *.mov *.mkv *.webm *.ogg)" + ) + if file_path: + success = self.world.gui_manager.load2DVideoFile(video_screen, file_path) + if success: + print(f"成功加载新视频: {file_path}") + # 刷新属性面板以显示新视频信息 + self.updateGUIPropertyPanel(video_screen) + return True + except Exception as e: + print(f"加载新视频失败: {e}") + return False + + def load2DVideoFile(self, video_screen, video_path): + """为2D视频屏幕加载新的视频文件""" + try: + import os + + if not os.path.exists(video_path): + print(f"❌ 2D视频文件不存在: {video_path}") + return False + + # 加载新的视频纹理 + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + # 应用纹理到2D视频屏幕 + video_screen["frameTexture"] = movie_texture + + # 保存视频纹理引用 + video_screen.setPythonTag("movie_texture", movie_texture) + video_screen.setTag("video_path", video_path) + + print(f"✅ 成功加载新2D视频: {video_path}") + return True + else: + print(f"❌ 无法加载2D视频文件: {video_path}") + return False + + except Exception as e: + print(f"❌ 加载2D视频文件失败: {e}") + import traceback + traceback.print_exc() + return False + + def _addVideoScreenProperties(self,video_screen): + try: + from PyQt5.QtWidgets import (QGroupBox,QGridLayout,QPushButton,QLabel,QSlider,QFileDialog,QHBoxLayout,QCheckBox) + import os + + video_info_group = QGroupBox("视频信息") + video_info_layout = QGridLayout() + + video_path = video_screen.getTag("video_path") + if video_path and os.path.exists(video_path): + video_info_layout.addWidget(QLabel("视频文件:"),0,0) + path_label = QLabel(os.path.basename(video_path)) + path_label.setWordWrap(True) + path_label.setStyleSheet("color:#00AAFF;") + video_info_layout.addWidget(path_label,0,1) + else: + video_info_layout.addWidget(QLabel("状态:"),0,0) + status_label = QLabel("无视频文件"if not video_path else "文件不存在") + status_label.setStyleSheet("color:red;") + video_info_layout.addWidget(status_label,0,1) + + video_info_group.setLayout(video_info_layout) + self._propertyLayout.addWidget(video_info_group) + + video_info_group.setLayout(video_info_layout) + self._propertyLayout.addWidget(video_info_group) + + video_control_group = QGroupBox("视频控制") + video_control_layout = QHBoxLayout() + + # 播放按钮 + play_btn = QPushButton("▶️ 播放") + play_btn.clicked.connect(lambda: self.world.gui_manager.playVideo(video_screen)) + video_control_layout.addWidget(play_btn) + + # 暂停按钮 + pause_btn = QPushButton("⏸️ 暂停") + pause_btn.clicked.connect(lambda: self.world.gui_manager.pauseVideo(video_screen)) + video_control_layout.addWidget(pause_btn) + + # 停止按钮 + stop_btn = QPushButton("⏹️ 停止") + stop_btn.clicked.connect(lambda: self.world.gui_manager.stopVideo(video_screen)) + video_control_layout.addWidget(stop_btn) + + video_control_group.setLayout(video_control_layout) + self._propertyLayout.addWidget(video_control_group) + + # 加载新视频按钮 + load_btn = QPushButton("📁 加载新视频...") + load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen)) + self._propertyLayout.addWidget(load_btn) + except Exception as e: + print(f"添加视频屏幕属性失败{e}") + + def _loadNewVideo(self,video_screen): + try: + file_path, _ = QFileDialog.getOpenFileName( + None, + "选择视频文件", + "", + "视频文件(*.mp4 *.avi *.mov *.mkv *.webm *.ogg)" + ) + if file_path: + success = self.world.gui_manager.loadVideoFile(video_screen,file_path) + if success: + print(f"成功加载新视频{file_path}") + self.updateGUIPropertyPanel(video_screen) + return True + except Exception as e: + print(f"加载新视频失败{e}") + return False def editGUI2DPosition(self, gui_element, axis, value): """编辑2D GUI元素位置""" @@ -1647,35 +1834,6 @@ class PropertyPanelManager: traceback.print_exc() return False - def editGUI3DPosition(self, gui_element, axis, value): - """编辑3D GUI元素位置""" - try: - gui_type = gui_element.getTag("gui_type") - - if gui_type in ["3d_text", "3d_image"]: - current_pos = gui_element.getPos() - - if axis == "x": - new_pos = (value, current_pos.getY(), current_pos.getZ()) - elif axis == "y": - new_pos = (current_pos.getX(), value, current_pos.getZ()) - elif axis == "z": - new_pos = (current_pos.getX(), current_pos.getY(), value) - else: - return False - - gui_element.setPos(*new_pos) - print(f"✓ 更新3D GUI元素位置: {axis}={value}") - return True - else: - print(f"✗ 不支持的GUI类型进行3D位置编辑: {gui_type}") - return False - except Exception as e: - print(f"✗ 更新3D GUI元素位置失败: {e}") - import traceback - traceback.print_exc() - return False - def editGUIScale(self, gui_element, axis, value): """编辑GUI元素缩放""" try: @@ -1686,7 +1844,7 @@ class PropertyPanelManager: if value == 0: value = 0.01 - if gui_type in ["3d_text", "3d_image","2d_image"]: + if gui_type in ["3d_text", "3d_image","2d_image","video_screen","2d_video_screen"]: # 3D元素处理 if axis == "x": new_scale = (value, current_scale.getY(), current_scale.getZ())