1
0
forked from Rowland/EG

3d视频屏幕和2d视频屏幕

This commit is contained in:
Hector 2025-09-02 10:21:46 +08:00
parent d980330d9d
commit 5e68ff5ee6
7 changed files with 1166 additions and 108 deletions

File diff suppressed because one or more lines are too long

View File

@ -98,7 +98,7 @@ class SelectionSystem:
else: else:
main_window.unsetCursor() main_window.unsetCursor()
self._current_cursor = cursor_type self._current_cursor = cursor_type
print(f"光标已设置:{cursor_type}") #print(f"光标已设置:{cursor_type}")
self._current_cursor = cursor_type self._current_cursor = cursor_type
else: else:
print("警告:无法获取主窗口,光标设置失败") print("警告:无法获取主窗口,光标设置失败")
@ -399,19 +399,24 @@ class SelectionSystem:
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False 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 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: if is_scale_tool:
model_paths = [ model_paths = [
"core/UniformScaleHandle.fbx", os.path.join(base_dir,"core/UniformScaleHandle.fbx"),
] ]
elif is_rotate_tool: elif is_rotate_tool:
model_paths = [ model_paths = [
"core/RotationHandleQuarter.fbx", os.path.join(base_dir,"core/RotationHandleQuarter.fbx"),
] ]
else: else:
model_paths = [ 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 = [ # model_paths = [
# "core/TranslateArrowHandle.fbx", # "core/TranslateArrowHandle.fbx",
# "EG/core/TranslateArrowHandle.fbx", # "EG/core/TranslateArrowHandle.fbx",
@ -421,7 +426,7 @@ class SelectionSystem:
for path in model_paths: for path in model_paths:
try: try:
if is_rotate_tool: 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) gizmoRot_model = self.world.loader.loadModel(path)
else: else:
gizmo_model = self.world.loader.loadModel(path) gizmo_model = self.world.loader.loadModel(path)
@ -778,7 +783,7 @@ class SelectionSystem:
self.gizmo.setScale(scale_factor) self.gizmo.setScale(scale_factor)
# 限制缩放范围,避免过大或过小 # 限制缩放范围,避免过大或过小
min_scale = 0.08 min_scale = 0.001
max_scale = 100.0 max_scale = 100.0
final_scale = max(min_scale, min(max_scale, scale_factor)) final_scale = max(min_scale, min(max_scale, scale_factor))
@ -1614,7 +1619,7 @@ class SelectionSystem:
if self.dragGizmoAxis == "x": if self.dragGizmoAxis == "x":
new_hpr = Vec3(start_hpr.x+rotation_amount,start_hpr.y,start_hpr.z) new_hpr = Vec3(start_hpr.x+rotation_amount,start_hpr.y,start_hpr.z)
elif self.dragGizmoAxis == "y": 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": elif self.dragGizmoAxis == "z":
new_hpr = Vec3(start_hpr.x,start_hpr.y,start_hpr.z+rotation_amount) new_hpr = Vec3(start_hpr.x,start_hpr.y,start_hpr.z+rotation_amount)
else: else:

View File

@ -33,12 +33,73 @@ class CoreWorld(Panda3DWorld):
self.mouseRightPressed = False self.mouseRightPressed = False
# 初始化世界 # 初始化世界
self._setupResourcePaths()
self._setupCamera() self._setupCamera()
self._setupLighting() self._setupLighting()
self._setupGround() self._setupGround()
self._loadFont() self._loadFont()
#self.load_and_play_glb_model() #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): def load_and_play_glb_model(self):
"""加载 glTF 模型并播放动画""" """加载 glTF 模型并播放动画"""
try: try:

View File

@ -861,6 +861,830 @@ class GUIManager:
traceback.print_exc() traceback.print_exc()
return None 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="虚拟屏幕"): def createGUIVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
"""创建3D虚拟屏幕 - 支持多选创建,优化版本""" """创建3D虚拟屏幕 - 支持多选创建,优化版本"""
try: try:
@ -1209,7 +2033,7 @@ class GUIManager:
if hasattr(gui_element, 'getScale'): if hasattr(gui_element, 'getScale'):
scale = gui_element.getScale() scale = gui_element.getScale()
scaleEdit = QDoubleSpinBox() scaleEdit = QDoubleSpinBox()
scaleEdit.setRange(0.01, 10) scaleEdit.setRange(0.01, 100)
scaleEdit.setSingleStep(0.1) scaleEdit.setSingleStep(0.1)
scaleEdit.setValue(scale.getX()) scaleEdit.setValue(scale.getX())
form.addRow("缩放:", scaleEdit) form.addRow("缩放:", scaleEdit)
@ -1815,7 +2639,7 @@ class GUIManager:
# 宽度控件 # 宽度控件
transform_layout.addWidget(QLabel("宽度"), 4, 0) transform_layout.addWidget(QLabel("宽度"), 4, 0)
widthSpinBox = QDoubleSpinBox() widthSpinBox = QDoubleSpinBox()
widthSpinBox.setRange(0.1, 10) widthSpinBox.setRange(0.01, 100)
widthSpinBox.setSingleStep(0.1) widthSpinBox.setSingleStep(0.1)
widthSpinBox.setValue(width) widthSpinBox.setValue(width)
widthSpinBox.valueChanged.connect( widthSpinBox.valueChanged.connect(
@ -1825,7 +2649,7 @@ class GUIManager:
# 高度控件 # 高度控件
transform_layout.addWidget(QLabel("高度"), 4, 2) transform_layout.addWidget(QLabel("高度"), 4, 2)
heightSpinBox = QDoubleSpinBox() heightSpinBox = QDoubleSpinBox()
heightSpinBox.setRange(0.1, 10) heightSpinBox.setRange(0.01, 100)
heightSpinBox.setSingleStep(0.1) heightSpinBox.setSingleStep(0.1)
heightSpinBox.setValue(height) heightSpinBox.setValue(height)
heightSpinBox.valueChanged.connect( heightSpinBox.valueChanged.connect(
@ -1885,7 +2709,7 @@ class GUIManager:
# 缩放数值输入框 # 缩放数值输入框
scale_x = QDoubleSpinBox() scale_x = QDoubleSpinBox()
scale_x.setRange(0.01, 10) scale_x.setRange(0.01, 100)
scale_x.setSingleStep(0.1) scale_x.setSingleStep(0.1)
scale_x.setValue( scale_x.setValue(
scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale) 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) transform_layout.addWidget(scale_x, 3, 1)
scale_y = QDoubleSpinBox() scale_y = QDoubleSpinBox()
scale_y.setRange(0.01, 10) scale_y.setRange(0.01, 100)
scale_y.setSingleStep(0.1) scale_y.setSingleStep(0.1)
scale_y.setValue( scale_y.setValue(
scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len( 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) transform_layout.addWidget(scale_y, 3, 2)
scale_z = QDoubleSpinBox() scale_z = QDoubleSpinBox()
scale_z.setRange(0.01, 10) scale_z.setRange(0.01, 100)
scale_z.setSingleStep(0.1) scale_z.setSingleStep(0.1)
scale_z.setValue( scale_z.setValue(
scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len( 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() scale = gui_element.getScale()
scaleSpinBox = QDoubleSpinBox() scaleSpinBox = QDoubleSpinBox()
scaleSpinBox.setRange(0.01, 10) scaleSpinBox.setRange(0.01, 100)
scaleSpinBox.setSingleStep(0.1) scaleSpinBox.setSingleStep(0.1)
scaleSpinBox.setValue(scale.getX()) scaleSpinBox.setValue(scale.getX())
scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v)) scaleSpinBox.valueChanged.connect(lambda v: self.editGUIElement(gui_element, "scale", v))
@ -2099,7 +2923,7 @@ class GUIManager:
try: try:
gui_type = gui_element.getTag("gui_type") 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元素使用屏幕坐标需要转换 # 2D元素使用屏幕坐标需要转换
current_pos = gui_element.getPos() current_pos = gui_element.getPos()
@ -2134,7 +2958,7 @@ class GUIManager:
try: try:
gui_type = gui_element.getTag("gui_type") 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() current_pos = gui_element.getPos()
if axis == "x": if axis == "x":
@ -2168,7 +2992,7 @@ class GUIManager:
if value == 0: if value == 0:
value = 0.01 value = 0.01
if gui_type in ["3d_text", "3d_image"]: if gui_type in ["3d_text", "3d_image","video_screen","virtual_screen"]:
# 3D元素处理 # 3D元素处理
if axis == "x": if axis == "x":
new_scale = (value, current_scale.getY(), current_scale.getZ()) new_scale = (value, current_scale.getY(), current_scale.getZ())
@ -2214,7 +3038,6 @@ class GUIManager:
# 对于其他2D元素使用统一缩放 # 对于其他2D元素使用统一缩放
gui_element.setScale(value) gui_element.setScale(value)
print(f"✓ 更新GUI元素缩放: {axis}={value}")
return True return True
except Exception as e: except Exception as e:
print(f"✗ 更新GUI元素缩放失败: {e}") print(f"✗ 更新GUI元素缩放失败: {e}")

View File

@ -234,6 +234,14 @@ class MyWorld(CoreWorld):
"""创建2D GUI图片""" """创建2D GUI图片"""
return self.gui_manager.createGUI2DImage(pos, image_path, size) 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)): def createSpotLight(self,pos=(0,0,5)):
"""创建聚光灯""" """创建聚光灯"""
return self.scene_manager.createSpotLight(pos) return self.scene_manager.createSpotLight(pos)

View File

@ -404,6 +404,7 @@ class MainWindow(QMainWindow):
self.createImageAction = self.createGUIaddMenu.addAction('创建图片') self.createImageAction = self.createGUIaddMenu.addAction('创建图片')
self.createGUIaddMenu.addSeparator() self.createGUIaddMenu.addSeparator()
self.createVideoScreen = self.createGUIaddMenu.addAction('创建视频屏幕') self.createVideoScreen = self.createGUIaddMenu.addAction('创建视频屏幕')
self.create2DVideoScreen = self.createGUIaddMenu.addAction('创建2D视频屏幕')
self.createSphericalVideo = self.createGUIaddMenu.addAction('创建球形视频') self.createSphericalVideo = self.createGUIaddMenu.addAction('创建球形视频')
self.createGUIaddMenu.addSeparator() self.createGUIaddMenu.addSeparator()
self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕') self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕')
@ -426,7 +427,8 @@ class MainWindow(QMainWindow):
self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel()) self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel())
self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry()) self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry())
self.createImageAction.triggered.connect(lambda: self.world.createGUI2DImage()) 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.createSphericalVideo.triggered.connect(self.world.createSphericalVideo)
self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen()) self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen())
self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight()) self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight())
@ -451,6 +453,7 @@ class MainWindow(QMainWindow):
'createEntry': self.createEntryAction, 'createEntry': self.createEntryAction,
'createImage': self.createImageAction, 'createImage': self.createImageAction,
'createVideoScreen': self.createVideoScreen, 'createVideoScreen': self.createVideoScreen,
'create2DVideoScreen':self.create2DVideoScreen,
'createSphericalVideo': self.createSphericalVideo, 'createSphericalVideo': self.createSphericalVideo,
'createVirtualScreen': self.createVirtualScreenAction, 'createVirtualScreen': self.createVirtualScreenAction,
'createSpotLight': self.createSpotLightAction, 'createSpotLight': self.createSpotLightAction,

View File

@ -6,7 +6,7 @@ from typing import Hashable
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget, QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget,
QVBoxLayout, QGroupBox, QGridLayout, QSpinBox) QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog)
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from deploy_libs.unicodedata import normalize from deploy_libs.unicodedata import normalize
from direct.actor.Actor import Actor from direct.actor.Actor import Actor
@ -299,20 +299,20 @@ class PropertyPanelManager:
transform_layout.addWidget(self.pos_z,1,3) transform_layout.addWidget(self.pos_z,1,3)
transform_layout.addWidget(QLabel("旋转"),2,0) transform_layout.addWidget(QLabel("旋转"),2,0)
self.rot_h = QDoubleSpinBox() self.rot_x = QDoubleSpinBox()
self.rot_p = QDoubleSpinBox() self.rot_y = QDoubleSpinBox()
self.rot_r = 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) rot_widget.setRange(-360,360)
self.rot_h.setValue(terrain_node.getH()) self.rot_x.setValue(terrain_node.getH())
self.rot_p.setValue(terrain_node.getP()) self.rot_y.setValue(terrain_node.getP())
self.rot_r.setValue(terrain_node.getR()) self.rot_z.setValue(terrain_node.getR())
self.rot_h.valueChanged.connect(lambda v:terrain_node.setH(v)) self.rot_x.valueChanged.connect(lambda v:terrain_node.setH(v))
self.rot_p.valueChanged.connect(lambda v:terrain_node.setP(v)) self.rot_y.valueChanged.connect(lambda v:terrain_node.setP(v))
self.rot_r.valueChanged.connect(lambda v:terrain_node.setR(v)) self.rot_z.valueChanged.connect(lambda v:terrain_node.setR(v))
h_label = QLabel("H") h_label = QLabel("H")
p_label = QLabel("P") p_label = QLabel("P")
@ -324,9 +324,9 @@ class PropertyPanelManager:
transform_layout.addWidget(h_label,2,1) transform_layout.addWidget(h_label,2,1)
transform_layout.addWidget(p_label,2,2) transform_layout.addWidget(p_label,2,2)
transform_layout.addWidget(r_label,2,3) transform_layout.addWidget(r_label,2,3)
transform_layout.addWidget(self.rot_h,3,1) transform_layout.addWidget(self.rot_x,3,1)
transform_layout.addWidget(self.rot_p,3,2) transform_layout.addWidget(self.rot_y,3,2)
transform_layout.addWidget(self.rot_r,3,3) transform_layout.addWidget(self.rot_z,3,3)
transform_layout.addWidget(QLabel("缩放"),4,0) transform_layout.addWidget(QLabel("缩放"),4,0)
self.scale_x = QDoubleSpinBox() self.scale_x = QDoubleSpinBox()
@ -633,10 +633,16 @@ class PropertyPanelManager:
self._safeUpdateSpinBox('pos_x', logical_x) self._safeUpdateSpinBox('pos_x', logical_x)
self._safeUpdateSpinBox('pos_z', logical_z) self._safeUpdateSpinBox('pos_z', logical_z)
else: else:
# 3D GUI组件使用世界坐标 # 3D GUI组件使用世界坐标包括 video_screen
self._safeUpdateSpinBox('pos_x', pos.getX()) pos = gui_element.getPos()
self._safeUpdateSpinBox('pos_y', pos.getY())
self._safeUpdateSpinBox('pos_z', pos.getZ()) # 修复:确保 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: if hasattr(self, 'scale_x') and self.scale_x:
@ -1107,22 +1113,22 @@ class PropertyPanelManager:
# 旋转 (Rotation) # 旋转 (Rotation)
transform_layout.addWidget(QLabel("旋转"), 4, 0) transform_layout.addWidget(QLabel("旋转"), 4, 0)
self.rot_h = QDoubleSpinBox() self.rot_x = QDoubleSpinBox()
self.rot_p = QDoubleSpinBox() self.rot_y = QDoubleSpinBox()
self.rot_r = 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) rot_widget.setRange(-360, 360)
self.rot_h.setValue(model.getH()) self.rot_x.setValue(model.getH())
self.rot_p.setValue(model.getP()) self.rot_y.setValue(model.getP())
self.rot_r.setValue(model.getR()) self.rot_z.setValue(model.getR())
# 连接旋转变化事件 # 连接旋转变化事件
self.rot_h.valueChanged.connect(lambda v: model.setH(v)) self.rot_x.valueChanged.connect(lambda v: model.setH(v))
self.rot_p.valueChanged.connect(lambda v: model.setP(v)) self.rot_y.valueChanged.connect(lambda v: model.setP(v))
self.rot_r.valueChanged.connect(lambda v: model.setR(v)) self.rot_z.valueChanged.connect(lambda v: model.setR(v))
# 创建并设置 H, P, R 标签居中 # 创建并设置 H, P, R 标签居中
h_label = QLabel("H") h_label = QLabel("H")
@ -1135,9 +1141,9 @@ class PropertyPanelManager:
transform_layout.addWidget(h_label, 4, 1) transform_layout.addWidget(h_label, 4, 1)
transform_layout.addWidget(p_label, 4, 2) transform_layout.addWidget(p_label, 4, 2)
transform_layout.addWidget(r_label, 4, 3) transform_layout.addWidget(r_label, 4, 3)
transform_layout.addWidget(self.rot_h, 5, 1) transform_layout.addWidget(self.rot_x, 5, 1)
transform_layout.addWidget(self.rot_p, 5, 2) transform_layout.addWidget(self.rot_y, 5, 2)
transform_layout.addWidget(self.rot_r, 5, 3) transform_layout.addWidget(self.rot_z, 5, 3)
# 缩放 (Scale) # 缩放 (Scale)
transform_layout.addWidget(QLabel("缩放"), 6, 0) transform_layout.addWidget(QLabel("缩放"), 6, 0)
@ -1253,6 +1259,8 @@ class PropertyPanelManager:
def updateGUIPropertyPanel(self, gui_element): def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板""" """更新GUI元素属性面板"""
self.clearPropertyPanel()
gui_type = gui_element.getTag("gui_type") gui_type = gui_element.getTag("gui_type")
gui_text = gui_element.getTag("gui_text") gui_text = gui_element.getTag("gui_text")
@ -1289,7 +1297,7 @@ class PropertyPanelManager:
# 变换属性组(合并位置和变换) # 变换属性组(合并位置和变换)
if hasattr(gui_element, 'getPos'): if hasattr(gui_element, 'getPos'):
# 根据GUI类型设置组名—— # 根据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") transform_group = QGroupBox("变换 Rect Transform")
else: else:
transform_group = QGroupBox("变换 Transform") transform_group = QGroupBox("变换 Transform")
@ -1299,7 +1307,7 @@ class PropertyPanelManager:
pos = gui_element.getPos() pos = gui_element.getPos()
# 根据GUI类型决定位置编辑方式 # 根据GUI类型决定位置编辑方式
if gui_type in ["button", "label", "entry","2d_image"]: if gui_type in ["button", "label", "entry","2d_image","2d_video_screen"]:
# 2D GUI组件使用屏幕坐标 # 2D GUI组件使用屏幕坐标
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标 logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
logical_z = pos.getZ() / 0.1 logical_z = pos.getZ() / 0.1
@ -1317,14 +1325,13 @@ class PropertyPanelManager:
transform_layout.addWidget(z_label, 0, 2) transform_layout.addWidget(z_label, 0, 2)
self.pos_x = QDoubleSpinBox() 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.setValue(logical_x)
self.pos_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v)) self.pos_x.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
transform_layout.addWidget(self.pos_x, 1, 1) transform_layout.addWidget(self.pos_x, 1, 1)
self.pos_z = QDoubleSpinBox() 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.setValue(logical_z)
self.pos_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v)) self.pos_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
transform_layout.addWidget(self.pos_z, 1, 2) transform_layout.addWidget(self.pos_z, 1, 2)
@ -1340,13 +1347,10 @@ class PropertyPanelManager:
transform_layout.addWidget(actualXLabel, 3, 1) transform_layout.addWidget(actualXLabel, 3, 1)
transform_layout.addWidget(actualZLabel, 3, 2) transform_layout.addWidget(actualZLabel, 3, 2)
if gui_type == "2d_image": if gui_type in ["2d_image","2d_video_screen"]:
scale = gui_element.getScale() scale = gui_element.getScale()
width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,(tuple, list)) else 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
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) transform_layout.addWidget(QLabel("宽度"),4,0)
self.scale_x = QDoubleSpinBox() self.scale_x = QDoubleSpinBox()
@ -1426,7 +1430,7 @@ class PropertyPanelManager:
# 缩放数值输入框 # 缩放数值输入框
self.scale_x = QDoubleSpinBox() 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.setSingleStep(0.1)
self.scale_x.setValue( self.scale_x.setValue(
scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, (tuple, list)) else scale) 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) transform_layout.addWidget(self.scale_x, 3, 1)
self.scale_y = QDoubleSpinBox() 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.setSingleStep(0.1)
self.scale_y.setValue( self.scale_y.setValue(
scale.getY() if hasattr(scale, 'getY') else scale[1] if isinstance(scale, (tuple, list)) and len( 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) transform_layout.addWidget(self.scale_y, 3, 2)
self.scale_z = QDoubleSpinBox() 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.setSingleStep(0.1)
self.scale_z.setValue( self.scale_z.setValue(
scale.getZ() if hasattr(scale, 'getZ') else scale[2] if isinstance(scale, (tuple, list)) and len( 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) transform_group.setLayout(transform_layout)
self._propertyLayout.addWidget(transform_group) self._propertyLayout.addWidget(transform_group)
# 为2D图像添加Sort属性 # 为2D图像和视频屏幕添加Sort属性
if gui_type == "2d_image": if gui_type in ["2d_image","2d_video_screen"]:
sort_group = QGroupBox("显示顺序") sort_group = QGroupBox("显示顺序")
sort_layout = QGridLayout() sort_layout = QGridLayout()
@ -1474,17 +1478,13 @@ class PropertyPanelManager:
gui_element.setTag("sort", str(value)) gui_element.setTag("sort", str(value))
# 应用sort到节点 # 应用sort到节点
gui_element.setBin("fixed", value) gui_element.setBin("fixed", value)
print(f"✓ 更新2D图像渲染顺序: {value}") print(f"✓ 更新{gui_type}渲染顺序: {value}")
except Exception as e: except Exception as e:
print(f"✗ 更新2D图像渲染顺序失败: {e}") print(f"✗ 更新{gui_type}渲染顺序失败: {e}")
sort_spin.valueChanged.connect(updateSort) sort_spin.valueChanged.connect(updateSort)
sort_layout.addWidget(sort_spin, 0, 1) 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) sort_group.setLayout(sort_layout)
self._propertyLayout.addWidget(sort_group) 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 "未设置" current_texture_path = gui_element.getTag("texture_path") or gui_element.getTag("image_path") or "未设置"
#texture_label = QLabel(current_texture_path) texture_label = QLabel(current_texture_path)
#texture_label.setWordWrap(True) texture_label.setWordWrap(True)
#image_layout.addWidget(texture_label, 0, 1) image_layout.addWidget(texture_label, 0, 1)
# 选择图片按钮 # 选择图片按钮
select_texture_button = QPushButton("选择图片...") select_texture_button = QPushButton("选择图片...")
@ -1595,7 +1595,7 @@ class PropertyPanelManager:
gui_element.setTag("texture_path", file_path) gui_element.setTag("texture_path", file_path)
gui_element.setTag("image_path", file_path) gui_element.setTag("image_path", file_path)
# 更新显示 # 更新显示
#texture_label.setText(file_path) texture_label.setText(file_path)
# 可选:刷新场景树或其他 UI # 可选:刷新场景树或其他 UI
# self.world.scene_manager.updateSceneTree() # self.world.scene_manager.updateSceneTree()
@ -1605,6 +1605,12 @@ class PropertyPanelManager:
self._propertyLayout.addWidget(image_group) 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() self._propertyLayout.addStretch()
# 强制更新布局 # 强制更新布局
@ -1614,7 +1620,188 @@ class PropertyPanelManager:
if propertyWidget: if propertyWidget:
propertyWidget.update() 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): def editGUI2DPosition(self, gui_element, axis, value):
"""编辑2D GUI元素位置""" """编辑2D GUI元素位置"""
@ -1647,35 +1834,6 @@ class PropertyPanelManager:
traceback.print_exc() traceback.print_exc()
return False 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): def editGUIScale(self, gui_element, axis, value):
"""编辑GUI元素缩放""" """编辑GUI元素缩放"""
try: try:
@ -1686,7 +1844,7 @@ class PropertyPanelManager:
if value == 0: if value == 0:
value = 0.01 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元素处理 # 3D元素处理
if axis == "x": if axis == "x":
new_scale = (value, current_scale.getY(), current_scale.getZ()) new_scale = (value, current_scale.getY(), current_scale.getZ())