1
0
forked from Rowland/EG
EG/templates/main_template.py
2025-09-26 17:50:04 +08:00

651 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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=absolute_scale
)
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
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
video_screen = DirectFrame(
frameSize=(-width_scale, width_scale, -height_scale, height_scale),
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
return video_screen
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("按回车键退出...")