diff --git a/gui/gui_manager.py b/gui/gui_manager.py index 22bb47ef..d4c3a84d 100644 --- a/gui/gui_manager.py +++ b/gui/gui_manager.py @@ -423,7 +423,7 @@ class GUIManager: entry = DirectEntry( text="", - pos=gui_pos, + pos=(pos[0],pos[1],pos[2]), scale=size, command=self.onGUIEntrySubmit, extraArgs=[f"entry_{len(self.gui_elements)}"], @@ -431,6 +431,12 @@ class GUIManager: numLines=1, width=12, focus=0, + frameColor = (0,0,0,0), + text_fg=(1,1,1,1), + text_align=TextNode.ACenter, + text_wordwrap=None, + rolloverSound=None, + clickSound=None, parent=parent_gui_node, # 设置GUI父节点 text_font = font, frameSize=(-0.1,0.1,-0.05,0.05) @@ -496,7 +502,7 @@ class GUIManager: traceback.print_exc() return None - def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2): + def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=1): """创建2D GUI图片""" try: from direct.gui.DirectGui import DirectButton @@ -536,9 +542,17 @@ class GUIManager: parent_gui_node = None # 使用默认的aspect2d print(f"📎 挂载到3D父节点: {parent_item.text(0)}") - # 使用CardMaker创建一个更可靠的图片框架 - cm = CardMaker('gui-2d-image') - cm.setFrame(-size, size, -size, size) + if isinstance(size, (list, tuple)) and len(size) >= 2: + # 分别处理宽度和高度的缩放 + width_scale = size[0] * 0.05 + height_scale = size[2] * 0.05 + else: + # 如果只提供了一个缩放值,则使用相同值 + width_scale = size * 0.1 if isinstance(size, (int, float)) else 0.2 + height_scale = width_scale + + cm = CardMaker("gui-2d-image") + cm.setFrame(-width_scale, width_scale, -height_scale, height_scale) # image_node = self.world.aspect2d.attachNewNode(cm.generate()) if parent_gui_node: @@ -1509,11 +1523,7 @@ class GUIManager: video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN") video_screen.setTag("created_by_user", "1") video_screen.setTag("name",screen_name) - # 修复后 - if video_path is not None: - video_screen.setTag("video_path", video_path) - else: - video_screen.setTag("video_path", "") + video_screen.setTag("video_path",video_path or "") # 关键修改:预先创建一个占位符纹理,为后续视频播放做准备 placeholder_texture = Texture(f"placeholder_video_texture_{len(self.gui_elements)}") @@ -1558,7 +1568,7 @@ class GUIManager: import traceback traceback.print_exc() else: - if video_path: + if not video_path: print(f"⚠️ 2D视频文件不存在: {video_path}") # 添加到GUI元素列表 @@ -1614,6 +1624,7 @@ class GUIManager: video_screen.setTag("video_path",video_path) path = video_screen.getTag("video_path") + print({video_screen.getTag("gui_type")}) print(f"🔧 更新2D视频屏幕标签 - video_path: {path}") if not video_path or not os.path.exists(video_path): diff --git a/main.py b/main.py index d6dddde0..ba87d0cf 100644 --- a/main.py +++ b/main.py @@ -248,9 +248,9 @@ class MyWorld(CoreWorld): """创建3D图片""" return self.gui_manager.createGUI3DImage(pos,text,size) - def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=1): + def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=2): """创建2D GUI图片""" - return self.gui_manager.createGUI2DImage(pos, image_path, size*0.2) + return self.gui_manager.createGUI2DImage(pos, image_path, size) def createVideoScreen(self,pos=(0,0,0),size=1,video_path=None): """创建视频屏幕""" diff --git a/project/project_manager.py b/project/project_manager.py index f4613fbd..a78704e8 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -613,207 +613,366 @@ class ProjectManager: return ext in media_extensions def _createAppFile(self, build_dir, project_name): - """创建应用程序主文件""" - app_code = f'''#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -{project_name} - Panda3D应用程序 -使用Panda3D引擎编辑器创建 -""" - -from __future__ import print_function -#获取渲染管线路径 -import sys -import os - -render_pipeline_path = 'RenderPipelineFile' -project_root = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0,project_root) -sys.path.insert(0,render_pipeline_path) - -import math -from random import random,randint,seed -from panda3d.core import Vec3,load_prc_file_data,Filename -from direct.showbase.ShowBase import ShowBase - -os.chdir(os.path.dirname(os.path.realpath(__file__))) - -class MainApp(ShowBase): - def __init__(self): - load_prc_file_data("",""" - win-size 1200 720 - window-title Render - """) - - pipeline_path = "../../" - - if not os.path.isfile(os.path.join(pipeline_path,"setup.py")): - pipeline_path = "../../RenderPipeline" - - sys.path.insert(0,pipeline_path) - - from rpcore import RenderPipeline,SpotLight - self.render_pipeline = RenderPipeline() - self.render_pipeline.create(self) - - from rpcore.util.movement_controller import MovementController - - self.render_pipeline.daytime_mgr.time = "12:00" - - self.loadFullScene() - - self.controller = MovementController(self) - self.controller.set_initial_position( - Vec3(-7.5,-5.3,1.8),Vec3(-5.9,-4.0,1.6)) - self.controller.setup() - - base.accept("l",self.tour) - - def loadFullScene(self): - """加载完整场景,包括所有元素""" - try: - scene_file = "scene.bam" - if os.path.exists(scene_file): - # 使用readBamFile加载完整场景 - from panda3d.core import BamCache - BamCache.getGlobalPtr().setActive(False) # 禁用缓存以避免问题 - - scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file)) - if scene: - scene.reparentTo(self.render) - self.render_pipeline.prepare_scene(scene) - print("✓ 完整场景加载成功") - - # 处理场景中的各种元素 - self.processSceneElements(scene) - else: - print("⚠️ 场景文件加载失败") - else: - print("⚠️ 未找到场景文件") - except Exception as e: - print(f"加载完整场景时出错: {{str(e)}}") - import traceback - traceback.print_exc() - - def processSceneElements(self, scene): - """处理场景中的各种元素""" - try: - # 处理光源 - self.processLights(scene) - - # 处理GUI元素 - self.processGUIElements(scene) - - # 处理其他特殊元素 - self.processSpecialElements(scene) - - except Exception as e: - print(f"处理场景元素时出错: {{str(e)}}") - - def processLights(self, scene): - """处理场景中的光源""" - try: - # 查找并处理点光源 - point_lights = scene.findAllMatches("**/=element_type=point_light") - for light_node in point_lights: - try: - from RenderPipelineFile.rpcore import PointLight - light = PointLight() - - # 恢复光源属性 - if light_node.hasTag("light_energy"): - light.energy = float(light_node.getTag("light_energy")) - else: - light.energy = 5000 - - light.radius = 1000 - light.inner_radius = 0.4 - light.set_color_from_temperature(5 * 1000.0) - light.casts_shadows = True - light.shadow_map_resolution = 256 - - light.setPos(light_node.getPos()) - self.render_pipeline.add_light(light) - print(f"✓ 点光源 {{light_node.getName()}} 恢复成功") - except Exception as e: - print(f"恢复点光源 {{light_node.getName()}} 失败: {{str(e)}}") - - # 查找并处理聚光灯 - spot_lights = scene.findAllMatches("**/=element_type=spot_light") - for light_node in spot_lights: - try: - from RenderPipelineFile.rpcore import SpotLight - light = SpotLight() - - # 恢复光源属性 - if light_node.hasTag("light_energy"): - light.energy = float(light_node.getTag("light_energy")) - else: - light.energy = 5000 - - light.radius = 1000 - light.inner_radius = 0.4 - light.set_color_from_temperature(5 * 1000.0) - light.casts_shadows = True - light.shadow_map_resolution = 256 - - light.setPos(light_node.getPos()) - self.render_pipeline.add_light(light) - print(f"✓ 聚光灯 {{light_node.getName()}} 恢复成功") - except Exception as e: - print(f"恢复聚光灯 {{light_node.getName()}} 失败: {{str(e)}}") - - except Exception as e: - print(f"处理光源时出错: {{str(e)}}") - - def processGUIElements(self, scene): - """处理场景中的GUI元素""" - try: - # 查找并处理2D图像 - images_2d = scene.findAllMatches("**/=gui_type=image_2d") - for img_node in images_2d: - try: - # GUI元素通常在场景加载时自动处理 - print(f"✓ 2D图像 {{img_node.getName()}} 已加载") - except Exception as e: - print(f"处理2D图像 {{img_node.getName()}} 失败: {{str(e)}}") - - except Exception as e: - print(f"处理GUI元素时出错: {{str(e)}}") - - def processSpecialElements(self, scene): - """处理特殊元素""" - try: - # 处理Cesium Tilesets - tilesets = scene.findAllMatches("**/=element_type=cesium_tileset") - for tileset_node in tilesets: - try: - # Tilesets需要特殊处理,这里只是标记 - print(f"✓ Cesium Tileset {{tileset_node.getName()}} 已识别") - except Exception as e: - print(f"处理Cesium Tileset {{tileset_node.getName()}} 失败: {{str(e)}}") - - except Exception as e: - print(f"处理特殊元素时出错: {{str(e)}}") - - def tour(self): - mopath = ( - (Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)), - (Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)), - (Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)), - (Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)), - (Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)), - (Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)), - ) - self.controller.play_motion_path(mopath,3.0) - -MainApp().run() -''' + """创建应用程序主文件 - 通过复制模板文件""" + # 获取模板文件路径(假设模板文件在项目根目录下的templates文件夹中) + template_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "templates", "main_template.py") + # 目标文件路径 app_path = os.path.join(build_dir, "main.py") - with open(app_path, "w", encoding="utf-8") as f: - f.write(app_code) + + # 检查模板文件是否存在 + if os.path.exists(template_path): + # 直接复制模板文件 + shutil.copy2(template_path, app_path) + print(f"✓ 应用程序主文件已从模板创建: {app_path}") + + # def _createAppFile(self, build_dir, project_name): +# """创建应用程序主文件""" +# app_code = f'''#!/usr/bin/env python3 +# # -*- coding: utf-8 -*- +# +# """ +# {project_name} - Panda3D应用程序 +# 使用Panda3D引擎编辑器创建 +# """ +# +# from __future__ import print_function +# +# import json +# +# from direct.actor.Actor import Actor +# from panda3d.core import TextNode, CardMaker, TextureStage, NodePath +# #获取渲染管线路径 +# import sys +# import os +# +# render_pipeline_path = 'RenderPipelineFile' +# project_root = os.path.dirname(os.path.abspath(__file__)) +# sys.path.insert(0,project_root) +# sys.path.insert(0,render_pipeline_path) +# +# import math +# from random import random,randint,seed +# from panda3d.core import Vec3,load_prc_file_data,Filename +# from direct.showbase.ShowBase import ShowBase +# +# os.chdir(os.path.dirname(os.path.realpath(__file__))) +# +# class MainApp(ShowBase): +# def __init__(self): +# load_prc_file_data("",""" +# win-size 1200 720 +# window-title Render +# """) +# +# pipeline_path = "../../" +# +# if not os.path.isfile(os.path.join(pipeline_path,"setup.py")): +# pipeline_path = "../../RenderPipeline" +# +# sys.path.insert(0,pipeline_path) +# +# from rpcore import RenderPipeline,SpotLight +# self.render_pipeline = RenderPipeline() +# self.render_pipeline.create(self) +# +# from rpcore.util.movement_controller import MovementController +# +# self.render_pipeline.daytime_mgr.time = "12:00" +# self._loadFont() +# +# self.loadFullScene() +# self.loadGUIFromJSON() +# +# self.controller = MovementController(self) +# self.controller.set_initial_position( +# Vec3(-7.5,-5.3,1.8),Vec3(-5.9,-4.0,1.6)) +# self.controller.setup() +# +# base.accept("l",self.tour) +# +# def _loadFont(self): +# """加载中文字体""" +# self.chinese_font = None +# try: +# self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc') +# if not self.chinese_font: +# print("警告: 无法加载中文字体,将使用默认字体") +# else: +# print("✓ 中文字体加载成功") +# except: +# print("警告: 无法加载中文字体,将使用默认字体") +# self.chinese_font = None +# +# def getChineseFont(self): +# """获取中文字体""" +# return self.chinese_font +# +# def loadFullScene(self): +# """加载完整场景,包括所有元素""" +# try: +# scene_file = "scene.bam" +# if os.path.exists(scene_file): +# # 使用readBamFile加载完整场景 +# from panda3d.core import BamCache +# BamCache.getGlobalPtr().setActive(False) # 禁用缓存以避免问题 +# +# scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file)) +# if scene: +# scene.reparentTo(self.render) +# self.render_pipeline.prepare_scene(scene) +# print("✓ 完整场景加载成功") +# +# # 处理场景中的各种元素 +# self.processSceneElements(scene) +# else: +# print("⚠️ 场景文件加载失败") +# else: +# print("⚠️ 未找到场景文件") +# except Exception as e: +# print(f"加载完整场景时出错: {{str(e)}}") +# import traceback +# traceback.print_exc() +# +# def processSceneElements(self, scene): +# """处理场景中的各种元素""" +# try: +# # 处理光源 +# self.processLights(scene) +# +# # 处理GUI元素 +# self.processGUIElements(scene) +# +# except Exception as e: +# print(f"处理场景元素时出错: {{str(e)}}") +# +# def processLights(self, scene): +# """处理场景中的光源""" +# try: +# # 查找并处理点光源 +# point_lights = scene.findAllMatches("**/=element_type=point_light") +# for light_node in point_lights: +# try: +# from RenderPipelineFile.rpcore import PointLight +# light = PointLight() +# +# # 恢复光源属性 +# if light_node.hasTag("light_energy"): +# light.energy = float(light_node.getTag("light_energy")) +# else: +# light.energy = 5000 +# +# light.radius = 1000 +# light.inner_radius = 0.4 +# light.set_color_from_temperature(5 * 1000.0) +# light.casts_shadows = True +# light.shadow_map_resolution = 256 +# +# light.setPos(light_node.getPos()) +# self.render_pipeline.add_light(light) +# print(f"✓ 点光源 {{light_node.getName()}} 恢复成功") +# except Exception as e: +# print(f"恢复点光源 {{light_node.getName()}} 失败: {{str(e)}}") +# +# # 查找并处理聚光灯 +# spot_lights = scene.findAllMatches("**/=element_type=spot_light") +# for light_node in spot_lights: +# try: +# from RenderPipelineFile.rpcore import SpotLight +# light = SpotLight() +# +# # 恢复光源属性 +# if light_node.hasTag("light_energy"): +# light.energy = float(light_node.getTag("light_energy")) +# else: +# light.energy = 5000 +# +# light.radius = 1000 +# light.inner_radius = 0.4 +# light.set_color_from_temperature(5 * 1000.0) +# light.casts_shadows = True +# light.shadow_map_resolution = 256 +# +# light.setPos(light_node.getPos()) +# self.render_pipeline.add_light(light) +# print(f"✓ 聚光灯 {{light_node.getName()}} 恢复成功") +# except Exception as e: +# print(f"恢复聚光灯 {{light_node.getName()}} 失败: {{str(e)}}") +# +# except Exception as e: +# print(f"处理光源时出错: {{str(e)}}") +# +# def processGUIElements(self, scene): +# """处理场景中的GUI元素""" +# try: +# # 查找并处理2D图像 +# images_2d = scene.findAllMatches("**/=gui_type=image_2d") +# for img_node in images_2d: +# try: +# # GUI元素通常在场景加载时自动处理 +# print(f"✓ 2D图像 {{img_node.getName()}} 已加载") +# except Exception as e: +# print(f"处理2D图像 {{img_node.getName()}} 失败: {{str(e)}}") +# +# except Exception as e: +# print(f"处理GUI元素时出错: {{str(e)}}") +# +# def tour(self): +# mopath = ( +# (Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)), +# (Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)), +# (Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)), +# (Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)), +# (Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)), +# (Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)), +# ) +# self.controller.play_motion_path(mopath,3.0) +# +# def loadGUIFromJSON(self): +# gui_json_path = "gui/gui_elements.json" +# +# try: +# if os.path.exists(gui_json_path): +# with open(gui_json_path, "r", encoding="utf-8") as f: +# content = f.read().strip() +# if content: +# gui_data = json.loads(content) +# self.createGUIElement(gui_data) +# except Exception as e: +# print(f"加载GUI元素失败: {{str(e)}}") +# import traceback +# traceback.print_exc() +# +# def createGUIElement(self,element_data): +# try: +# processed_names = set() +# element_original_data={{}} +# for i, gui_info in enumerate(element_data): +# name = gui_info.get("name", f"gui_element_{{i}}") +# element_original_data[name] = { +# "scale": gui_info.get("scale", [1, 1, 1]), +# "position": gui_info.get("position", [0, 0, 0]), +# "parent_name": gui_info.get("parent_name") +# } +# valid_parents = set() +# for gui_info in element_data: +# name = gui_info.get("name", f"gui_element_{{gui_info.get('index', 0)}}") +# valid_parents.add(name) +# +# for i ,gui_info in enumerate(element_data): +# try: +# gui_type = gui_info.get("type","unknown") +# name = gui_info.get("name",f"gui_element_{{i}}") +# position = gui_info.get("position",[0,0,0]) +# scale = gui_info.get("scale",[1,1,1]) +# tags = gui_info.get("tags",{{}}) +# text = gui_info.get("text","") +# image_path = gui_info.get("image_path","") +# video_path = gui_info.get("video_path","") +# bg_image_path = gui_info.get("bg_image_path","") +# parent_name = gui_info.get("parent_name") +# +# if name in processed_names: +# continue +# +# processed_names.add(name) +# +# absolute_position = list(position) +# absolute_scale = list(scale) +# +# if parent_name and parent_name in element_original_data: +# parent_data = element_original_data[parent_name] +# parent_scale = parent_data["scale"] +# +# if gui_type in ["3d_text", "3d_image", "button", "label", "entry", "2d_image", +# "2d_video_screen"]: +# # 位置需要乘以父级缩放来得到绝对位置 +# for j in range(min(len(absolute_position), len(parent_scale))): +# absolute_position[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0] +# +# # 缩放需要乘以父级缩放来得到绝对缩放 +# for j in range(min(len(absolute_scale), len(parent_scale))): +# absolute_scale[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0] +# +# new_element = None +# +# if gui_type =="3d_text": +# size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5 +# new_element = self.createGUI3DText( +# pos = tuple(absolute_position), +# text = text, +# size = size +# ) +# elif gui_type == "button": +# # 确保传入正确的参数类型 +# new_element = self.createGUIButton( +# pos=tuple(absolute_position), +# text=text, +# size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0, +# ) +# except Exception as e: +# print(f"重建GUI元素失败 {{name}}: {{e}}") +# import traceback +# traceback.print_exc() +# continue +# except Exception as e: +# print(f"重建GUI元素失败: {{str(e)}}") +# +# def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1,command=None): +# from direct.gui.DirectGui import DirectButton +# +# button = DirectButton( +# text=text, +# pos=(pos[0], pos[1], pos[2]), # 保持正确的坐标格式 +# scale=size, # size 应该是数值而不是元组 +# frameColor=(0.2, 0.6, 0.8, 1), +# text_font=self.getChineseFont() if self.getChineseFont() else None, +# rolloverSound=None, +# clickSound=None, +# parent=None, +# command=command +# ) +# +# def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5): +# """创建3D文本GUI元素""" +# try: +# # 创建文本节点 +# text_node = TextNode("gui_3d_text") +# text_node.setText(text) +# text_node.setAlign(TextNode.ACenter) +# +# # 设置字体(如果可用) +# if self.getChineseFont(): +# text_node.setFont(self.getChineseFont()) +# +# # 创建节点路径并添加到场景 +# text_np = self.render.attachNewNode(text_node) +# +# # 设置位置和大小 +# text_np.setPos(Vec3(pos[0], pos[1], pos[2])) +# text_np.setScale(size) +# +# # 设置面向摄像机 +# #text_np.setBillboardPointEye() +# +# # 设置渲染属性 +# text_np.setBin("fixed", 40) +# text_np.setDepthWrite(False) +# +# return text_np +# except Exception as e: +# print(f"❌ 创建3D文本失败: {{str(e)}}") +# import traceback +# traceback.print_exc() +# return None +# +# MainApp().run() +# ''' +# +# app_path = os.path.join(build_dir, "main.py") +# with open(app_path, "w", encoding="utf-8") as f: +# f.write(app_code) def _createStandardSetupFile(self, build_dir, project_name): """创建标准的setup.py文件 - 按照Panda3D官方文档""" @@ -878,6 +1037,8 @@ setup( 'direct.interval', 'panda3d.core', 'panda3d.direct', + 'rpcore', + 'rpcore.util.movement_controller', ], }}, diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 37ee239a..a8004d45 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -900,7 +900,8 @@ class SceneManager: "rotation": list(gui_node.getHpr()), "scale": list(gui_node.getScale()), "tags": {}, - "parent_name":None + "parent_name":None, + "video_path":gui_node.getTag("video_path") if gui_node.hasTag("video_path") else None, } parent = gui_node.getParent() @@ -910,13 +911,13 @@ class SceneManager: gui_info["parent_name"] = parent_name # 收集所有标签(仅对NodePath类型的对象) - if hasattr(gui_node, 'getTagNames'): - for tag in gui_node.getTagNames(): - gui_info["tags"][tag] = gui_node.getTag(tag) - elif hasattr(gui_node, 'getTags'): # 对于DirectGUI对象 - # DirectGUI对象使用不同的方法存储标签 - if hasattr(gui_node, '_tags'): - gui_info["tags"] = gui_node._tags.copy() + # if hasattr(gui_node, 'getTagNames'): + # for tag in gui_node.getTagNames(): + # gui_info["tags"][tag] = gui_node.getTag(tag) + # elif hasattr(gui_node, 'getTags'): # 对于DirectGUI对象 + # # DirectGUI对象使用不同的方法存储标签 + # if hasattr(gui_node, '_tags'): + # gui_info["tags"] = gui_node._tags.copy() # 根据类型收集特定信息 if gui_type == "button": @@ -949,10 +950,12 @@ class SceneManager: elif gui_type == "3d_image": if hasattr(gui_node,'hasTag') and gui_node.hasTag("gui_image_path"): gui_info["image_path"] = gui_node.getTag("gui_image_path") - elif gui_type in ["video_screen", "2d_video_screen"]: + elif gui_type == "video_screen": + if hasattr(gui_node, 'hasTag') and gui_node.hasTag("video_path"): + gui_info["video_path"] = gui_node.getTag("video_path") + elif gui_type == "2d_video_screen": if hasattr(gui_node, 'hasTag') and gui_node.hasTag("video_path"): gui_info["video_path"] = gui_node.getTag("video_path") - gui_info["video_path"] = gui_node.getTag("video_path") elif gui_type == "virtual_screen": if hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"): gui_info["text"] = gui_node.getTag("gui_text") @@ -1991,11 +1994,12 @@ class SceneManager: size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0 ) elif gui_type == "2d_image" and hasattr(gui_manager, 'createGUI2DImage'): - scale_value = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.2 + scale_value = absolute_scale[0] + print(f"2d_image{scale_value}") new_element = gui_manager.createGUI2DImage( pos=tuple(absolute_position), image_path=image_path, - size=scale_value * 0.2 + size=absolute_scale ) elif gui_type == "3d_text" and hasattr(gui_manager, 'createGUI3DText'): size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5 @@ -2342,7 +2346,6 @@ class SceneManager: """重新创建聚光灯""" try: from RenderPipelineFile.rpcore import SpotLight - from QPanda3D.Panda3DWorld import get_render_pipeline from panda3d.core import Vec3 # 创建聚光灯对象 diff --git a/templates/main_template.py b/templates/main_template.py new file mode 100644 index 00000000..cd97fddc --- /dev/null +++ b/templates/main_template.py @@ -0,0 +1,641 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +测试动画模型 - Panda3D应用程序 +使用Panda3D引擎编辑器创建 +""" + +from __future__ import print_function + +import json + +from direct.actor.Actor import Actor +from panda3d.core import TextNode, CardMaker, TextureStage, NodePath, Texture, TransparencyAttrib +# 获取渲染管线路径 +# 在文件开头添加sys导入(如果还没有的话) +import sys +import os + +# 修改工作目录设置部分 +if getattr(sys, 'frozen', False): + # 打包后的环境 + project_root = os.path.dirname(sys.executable) +else: + # 开发环境 + try: + project_root = os.path.dirname(os.path.abspath(__file__)) + except NameError: + project_root = os.getcwd() + +os.chdir(project_root) + +render_pipeline_path = 'RenderPipelineFile' +sys.path.insert(0, render_pipeline_path) + +# 修改管线路径查找 +pipeline_path = os.path.join(project_root, "RenderPipelineFile") +if not os.path.isfile(os.path.join(pipeline_path, "setup.py")): + pipeline_path = os.path.join(project_root, "RenderPipelineFile") + +import math +from random import random, randint, seed +from panda3d.core import Vec3, load_prc_file_data, Filename +from direct.showbase.ShowBase import ShowBase + + +# os.chdir(os.path.dirname(os.path.realpath(__file__))) + + +class MainApp(ShowBase): + def __init__(self): + load_prc_file_data("", """ + win-size 1380 750 + window-title Render + """) + + pipeline_path = "../../" + + if not os.path.isfile(os.path.join(pipeline_path, "setup.py")): + pipeline_path = "../../RenderPipeline" + + sys.path.insert(0, pipeline_path) + + from rpcore import RenderPipeline, SpotLight + self.render_pipeline = RenderPipeline() + self.render_pipeline.create(self) + + from rpcore.util.movement_controller import MovementController + + self.render_pipeline.daytime_mgr.time = "12:00" + self._loadFont() + + self.loadFullScene() + self.loadGUIFromJSON() + + self.controller = MovementController(self) + self.controller.set_initial_position( + Vec3(0, -50, 20), Vec3(0, 0, 0)) + self.controller.setup() + + base.accept("l", self.tour) + + def _loadFont(self): + """加载中文字体""" + self.chinese_font = None + try: + self.chinese_font = self.loader.loadFont('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc') + if not self.chinese_font: + print("警告: 无法加载中文字体,将使用默认字体") + else: + print("✓ 中文字体加载成功") + except: + print("警告: 无法加载中文字体,将使用默认字体") + self.chinese_font = None + + def getChineseFont(self): + """获取中文字体""" + return self.chinese_font + + def loadFullScene(self): + """加载完整场景,包括所有元素""" + try: + import sys + # 获取场景文件路径 + if getattr(sys, 'frozen', False): + scene_file = os.path.join(os.path.dirname(sys.executable), "scene.bam") + else: + scene_file = "scene.bam" + + if os.path.exists(scene_file): + # 使用readBamFile加载完整场景 + from panda3d.core import BamCache + BamCache.getGlobalPtr().setActive(False) # 禁用缓存以避免问题 + + scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file)) + if scene: + scene.reparentTo(self.render) + self.render_pipeline.prepare_scene(scene) + print("✓ 完整场景加载成功") + + # 处理场景中的各种元素 + self.processSceneElements(scene) + else: + print("⚠️ 场景文件加载失败") + else: + print("⚠️ 未找到场景文件") + except Exception as e: + print(f"加载完整场景时出错: {str(e)}") + import traceback + traceback.print_exc() + + def processSceneElements(self, scene): + """处理场景中的各种元素""" + try: + processed_lights = [] + loaded_nodes = {} + def processNode(nodePath,depth=0): + loaded_nodes[nodePath.getName()] = nodePath + + if nodePath.hasTag("light_type"): + light_type = nodePath.getTag("light_type") + + if nodePath not in processed_lights: + if light_type == "spot_light": + self._recreateSpotLight(nodePath) + elif light_type == "point_light": + self._recreatePointLight(nodePath) + processed_lights.append(nodePath) + + for child in nodePath.getChildren(): + processNode(child,depth+1) + + processNode(scene) + + # 处理GUI元素 + #self.processGUIElements(scene) + + except Exception as e: + print(f"处理场景元素时出错: {str(e)}") + + def _recreateSpotLight(self,light_node): + try: + from RenderPipelineFile.rpcore import SpotLight + from panda3d.core import Vec3 + + light = SpotLight() + light.direction = Vec3(0,0,-1) + light.fov = 70 + light.set_color_from_temperature(5*1000.0) + + if light_node.hasTag("light_energy"): + light.energy = float(light_node.getTag("light_energy")) + else: + light.energy = 5000 + + light.radius = 1000 + light.casts_shadows = True + light.shadow_map_resolution = 256 + + light_pos = light_node.getPos() + light.setPos(light_pos) + + self.render_pipeline.add_light(light) + except Exception as e: + print(f"创建点光源 {light_node.getName()} 失败: {str(e)}") + import traceback + traceback.print_exc() + + def _recreatePointLight(self,light_node): + try: + from RenderPipelineFile.rpcore import PointLight + + light = PointLight() + + if light_node.hasTag("light_energy"): + light.energy = float(light_node.getTag("light_energy")) + else: + light.energy = 5000 + + light.radius = 1000 + light.inner_radius = 0.4 + light.set_color_from_temperature(5*1000.0) + light.casts_shadows = True + light.shadow_map_resolution = 256 + + light.setPos(light_node.getPos()) + + self.render_pipeline.add_light(light) + except Exception as e: + print(f"创建点光源 {light_node.getName()} 失败: {str(e)}") + import traceback + traceback.print_exc() + + def processGUIElements(self, scene): + """处理场景中的GUI元素""" + try: + # 查找并处理2D图像 + images_2d = scene.findAllMatches("**/=gui_type=image_2d") + for img_node in images_2d: + try: + # GUI元素通常在场景加载时自动处理 + print(f"✓ 2D图像 {img_node.getName()} 已加载") + except Exception as e: + print(f"处理2D图像 {img_node.getName()} 失败: {str(e)}") + + except Exception as e: + print(f"处理GUI元素时出错: {str(e)}") + + def tour(self): + mopath = ( + (Vec3(-10.8645000458, 9.76458263397, 2.13306283951), Vec3(-133.556228638, -4.23447799683, 0.0)), + (Vec3(-10.6538448334, -5.98406457901, 1.68028640747), Vec3(-59.3999938965, -3.32706642151, 0.0)), + (Vec3(9.58458328247, -5.63625621796, 2.63269257545), Vec3(58.7906494141, -9.40668964386, 0.0)), + (Vec3(6.8135137558, 11.0153560638, 2.25509500504), Vec3(148.762527466, -6.41223621368, 0.0)), + (Vec3(-9.07093334198, 3.65908527374, 1.42396306992), Vec3(245.362503052, -3.59927511215, 0.0)), + (Vec3(-8.75390911102, -3.82727789879, 0.990055501461), Vec3(296.090484619, -0.604830980301, 0.0)), + ) + self.controller.play_motion_path(mopath, 3.0) + + def loadGUIFromJSON(self): + import sys + # 获取GUI元素JSON文件路径 + if getattr(sys, 'frozen', False): + gui_json_path = os.path.join(os.path.dirname(sys.executable), "gui", "gui_elements.json") + else: + gui_json_path = "gui/gui_elements.json" + + try: + if os.path.exists(gui_json_path): + with open(gui_json_path, 'r', encoding='utf-8') as f: + content = f.read().strip() + if content: + gui_data = json.loads(content) + self.createGUIElement(gui_data) + except Exception as e: + print(f"加载GUI元素时出错: {str(e)}") + import traceback + traceback.print_exc() + + def createGUIElement(self, element_data): + try: + processed_names = set() + element_original_data = {} + + for i, gui_info in enumerate(element_data): + name = gui_info.get("name", f"gui_element_{i}") + element_original_data[name] = { + "scale": gui_info.get("scale", [1, 1, 1]), + "position": gui_info.get("position", [0, 0, 0]), + "parent_name": gui_info.get("parent_name") + } + valid_parents = set() + for gui_info in element_data: + name = gui_info.get("name", f"gui_element_{gui_info.get('index', 0)}") + valid_parents.add(name) + + for i, gui_info in enumerate(element_data): + try: + gui_type = gui_info.get("type", "unknown") + name = gui_info.get("name", f"gui_element_{i}") + position = gui_info.get("position", [0, 0, 0]) + scale = gui_info.get("scale", [1, 1, 1]) + tags = gui_info.get("tags", {}) + text = gui_info.get("text", "") + image_path = gui_info.get("image_path", "") + video_path = gui_info.get("video_path", "") + bg_image_path = gui_info.get("bg_image_path", "") + parent_name = gui_info.get("parent_name") + + if name in processed_names: + continue + + processed_names.add(name) + + absolute_position = list(position) + absolute_scale = list(scale) + + if parent_name and parent_name in element_original_data: + parent_data = element_original_data[parent_name] + parent_scale = parent_data["scale"] + + if gui_type in ["3d_text", "3d_image", "button", "label", "entry", "2d_image", + "2d_video_screen"]: + # 位置需要乘以父级缩放来得到绝对位置 + for j in range(min(len(absolute_position), len(parent_scale))): + absolute_position[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0] + + # 缩放需要乘以父级缩放来得到绝对缩放 + for j in range(min(len(absolute_scale), len(parent_scale))): + absolute_scale[j] *= parent_scale[j] if len(parent_scale) > j else parent_scale[0] + + new_element = None + + if gui_type == "3d_text": + size = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.5 + new_element = self.createGUI3DText( + pos=tuple(absolute_position), + text=text, + size=size + ) + elif gui_type == "button": + new_element = self.createGUIButton( + pos=tuple(absolute_position), + text=text, + size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0, + command=self.resetWomenModel + ) + elif gui_type == "label": + new_element = self.createGUILabel( + pos=tuple(absolute_position), + text=text, + size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0 + ) + elif gui_type == "entry": + new_element = self.createGUIEntry( + pos=tuple(absolute_position), + placeholder=text, + size=absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 1.0, + command=self.onGUIEntrySubmit + ) + elif gui_type == "2d_image": + + scale_value = absolute_scale[0] + print(f"2d_image{scale_value}") + new_element = self.createGUI2DImage( + pos=tuple(absolute_position), + image_path=image_path, + size=absolute_scale + ) + elif gui_type == "2d_video_screen": + scale_value = absolute_scale[0] if absolute_scale and len(absolute_scale) > 0 else 0.2 + new_element = self.createGUI2DVideoScreen( + pos=tuple(absolute_position), + video_path=video_path, + size=scale_value * 0.2 + ) + except Exception as e: + print(f"重建GUI元素失败 {name}: {e}") + import traceback + traceback.print_exc() + continue + + except Exception as e: + print(f"创建GUI元素时出错: {str(e)}") + + def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1, command=None): + from direct.gui.DirectGui import DirectButton + + button = DirectButton( + text=text, + pos=(pos[0], pos[1], pos[2]), # 保持正确的坐标格式 + scale=size, # size 应该是数值而不是元组 + frameColor=(0.2, 0.6, 0.8, 1), + text_font=self.getChineseFont() if self.getChineseFont() else None, + rolloverSound=None, + clickSound=None, + parent=None, + command=command + ) + + def createGUI3DText(self, pos=(0, 0, 0), text="3D文本", size=0.5): + """创建3D文本GUI元素""" + try: + # 创建文本节点 + text_node = TextNode("gui_3d_text") + text_node.setText(text) + text_node.setAlign(TextNode.ACenter) + + # 设置字体(如果可用) + if self.getChineseFont(): + text_node.setFont(self.getChineseFont()) + + # 创建节点路径并添加到场景 + text_np = self.render.attachNewNode(text_node) + + # 设置位置和大小 + text_np.setPos(Vec3(pos[0], pos[1], pos[2])) + text_np.setScale(size) + + # 设置面向摄像机 + # text_np.setBillboardPointEye() + + # 设置渲染属性 + text_np.setBin("fixed", 40) + text_np.setDepthWrite(False) + + return text_np + except Exception as e: + print(f"❌ 创建3D文本失败: {str(e)}") + import traceback + traceback.print_exc() + return None + + def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08): + from direct.gui.DirectGui import DirectLabel + label = DirectLabel( + text=text, + pos=(pos[0], pos[1], pos[2]), + scale=size, + frameColor=(0, 0, 0, 0), + text_fg=(1, 1, 1, 1), + text_font=self.getChineseFont() if self.getChineseFont() else None, + text_align=TextNode.ACenter, + text_wordwrap=None, + text_mayChange=True, + parent=None + ) + + def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08, command=None): + from direct.gui.DirectGui import DirectEntry + + entry = DirectEntry( + text="", + pos=(pos[0], pos[1], pos[2]), + scale=size, + command=command, + initialText=placeholder, + numLines=1, + width=12, + focus=0, + frameColor=(0, 0, 0, 0), + text_fg=(1, 1, 1, 1), + text_font=self.getChineseFont() if self.getChineseFont() else None, + text_align=TextNode.ACenter, + text_wordwrap=None, + text_mayChange=True, + parent=None, + rolloverSound=None, + clickSound=None, + # 添加焦点管理命令 + focusInCommand=self.disableCameraControl, + focusOutCommand=self.enableCameraControl, + # 确保输入框能正确捕获所有键盘事件 + suppressKeys=True, # 这个参数很重要,它会阻止按键事件传播到其他处理器 + suppressMouse=True + ) + return entry + + def disableCameraControl(self): + """禁用相机控制""" + try: + if hasattr(self, 'controller'): + # 如果控制器有内置的禁用方法 + if hasattr(self.controller, 'disable'): + self.controller.disable() + else: + # 否则手动禁用事件监听 + self.controller.ignoreAll() # 忽略所有已注册的事件 + print("相机控制已禁用") + except Exception as e: + print(f"禁用相机控制时出错: {e}") + + def enableCameraControl(self): + """启用相机控制""" + try: + if hasattr(self, 'controller'): + # 如果控制器有内置的启用方法 + if hasattr(self.controller, 'enable'): + self.controller.enable() + else: + # 重新设置控制器 + self.controller.setup() + print("相机控制已启用") + except Exception as e: + print(f"启用相机控制时出错: {e}") + + def onGUIEntrySubmit(self, text, entry_id=None): + """GUI输入框提交事件处理""" + try: + print(f"GUI输入框提交: {entry_id} = {text}") + + # 重新启用相机控制 + self.enableCameraControl() + + # 清除输入框焦点(如果需要) + # base.win.focus() + + # 在这里添加您需要的文本处理逻辑 + # 例如保存文本、更新UI等 + + except Exception as e: + print(f"处理输入框提交时出错: {e}") + import traceback + traceback.print_exc() + + def createGUI2DImage(self, pos=(0, 0, 0), image_path=None, size=0.2): + # 处理非均匀缩放 + if isinstance(size, (list, tuple)) and len(size) >= 2: + # 分别处理宽度和高度的缩放 + width_scale = size[0] * 0.2 + height_scale = size[2] * 0.2 + else: + # 如果只提供了一个缩放值,则使用相同值 + width_scale = size * 0.1 if isinstance(size, (int, float)) else 0.2 + height_scale = width_scale + + cm = CardMaker("gui-2d-image") + cm.setFrame(-width_scale, width_scale, -height_scale, height_scale) + #cm.setFrame(-size, size, -size, size) + + image_node = self.aspect2d.attachNewNode(cm.generate()) + image_node.setPos(pos) + image_node.setBin("fixed", 0) + image_node.setDepthWrite(False) + image_node.setDepthTest(False) + image_node.setColor(1, 1, 1, 1) + + # 设置透明度支持 + image_node.setTransparency(TransparencyAttrib.MAlpha) + + if image_path: + try: + texture = self.loader.loadTexture(image_path) + if texture: + image_node.setTexture(texture, 1) + texture.setWrapU(Texture.WM_clamp) + texture.setWrapV(Texture.WM_clamp) + texture.setMinfilter(Texture.FT_linear) + texture.setMagfilter(Texture.FT_linear) + image_node.setColor(1, 1, 1, 1) + else: + print(f"无法加载图片: {image_path}") + except Exception as e: + print(f"加载图片时出错: {e}") + + def createGUI2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): + from direct.gui.DirectGui import DirectFrame + from panda3d.core import TransparencyAttrib, Texture, TextureStage + import os + + video_screen = DirectFrame( + frameSize=(-size, size, -size, size), + frameColor=(1, 1, 1, 1), + pos=pos, + parent=None, + suppressMouse=True + ) + video_screen.setTransparency(TransparencyAttrib.MAlpha) + + placeholder_texture = Texture() + placeholder_texture.setup2dTexture(1, 1, Texture.TUnsignedByte, Texture.FRgb) + placeholder_data = b'\x19\x19\x4c' + placeholder_texture.setRamImage(placeholder_data) + + if video_path and os.path.exists(video_path): + movie_texture = self._loadMovieTexture(video_path) + if movie_texture: + video_screen["frameTexture"] = movie_texture + + def _loadMovieTexture(self, video_path): + from panda3d.core import Texture, MovieTexture + import os + movie_texture = MovieTexture(video_path) + if movie_texture.read(video_path): + self._configureVideoTexture(movie_texture) + return movie_texture + + def _configureVideoTexture(self, texture): + 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) + + def resetWomenModel(self): + """调整 Women_1.glb 模型大小,实现从小到大再到小的完整循环效果""" + try: + # 查找 Women_1.glb 模型 + women_models = self.render.findAllMatches("**/Women_1.glb*") + + if women_models: + for model in women_models: + # 定义完整的缩放级别序列(从0.5到3.0再回到0.5) + scale_levels = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 2.5, 2.0, 1.5, 1.0, 0.5] + + # 获取当前缩放值 + current_scale = model.getScale() + + # 查找当前最接近的缩放级别索引 + current_index = 0 + min_diff = float('inf') + for i, scale in enumerate(scale_levels): + diff = abs(current_scale.x - scale) + if diff < min_diff: + min_diff = diff + current_index = i + + # 计算下一个缩放级别(循环) + next_index = (current_index + 1) % len(scale_levels) + next_scale = scale_levels[next_index] + + # 应用新的缩放 + model.setScale(next_scale) + print(f"✓ 调整模型 {model.getName()} 大小: {current_scale.x:.1f} -> {next_scale:.1f}") + print(f" 当前索引: {current_index}, 下一个索引: {next_index}") + else: + print("⚠️ 未找到 Women_1.glb 模型") + + except Exception as e: + print(f"调整模型大小时出错: {str(e)}") + import traceback + traceback.print_exc() + + +if __name__ == "__main__": + try: + app = MainApp() + app.run() + except Exception as e: + print(f"应用程序启动失败: {str(e)}") + import traceback + + traceback.print_exc() + # 在Windows上可以使用下面的代码显示错误对话框 + # input("按回车键退出...") + diff --git a/ui/property_panel.py b/ui/property_panel.py index 2ee965f4..f7e4bb02 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -3493,6 +3493,16 @@ class PropertyPanelManager: path_label.setWordWrap(True) path_label.setStyleSheet("color: #00AAFF;") video_info_layout.addWidget(path_label, 0, 1) + + # 添加文件大小信息 + try: + file_size = os.path.getsize(video_path) + file_size_mb = file_size / (1024 * 1024) + size_label = QLabel(f"大小: {file_size_mb:.2f} MB") + size_label.setStyleSheet("font-size: 10px; color: gray;") + video_info_layout.addWidget(size_label, 1, 1) + except Exception as e: + pass else: # 文件不存在 video_info_layout.addWidget(QLabel("状态:"), 0, 0) @@ -3849,8 +3859,9 @@ class PropertyPanelManager: if success: print(f"成功加载新视频: {file_path}") # 刷新属性面板以显示新视频信息 - self._stop2DVideo(video_screen) + self.updateGUIPropertyPanel(video_screen, item) + #self._stop2DVideo(video_screen) return True except Exception as e: print(f"加载新视频失败: {e}")