1
0
forked from Rowland/EG
EG/templates/main_template.py
2025-09-29 09:16:37 +08:00

805 lines
31 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
from core.InfoPanelManager import InfoPanelManager
# 获取渲染管线路径
# 在文件开头添加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 os.path.exists(pipeline_path) and pipeline_path not in sys.path:
sys.path.insert(0, pipeline_path)
# 同时添加子目录以确保所有模块都能正确导入
for root, dirs, files in os.walk(pipeline_path):
if root not in sys.path:
sys.path.insert(0, root)
import math
from random import random, randint, seed
from panda3d.core import Vec3, load_prc_file_data, Filename
from direct.showbase.ShowBase import ShowBase
from direct.task.TaskManagerGlobal import taskMgr
# os.chdir(os.path.dirname(os.path.realpath(__file__)))
from core.script_system import ScriptManager
class MainApp(ShowBase):
def __init__(self):
# 在调用父类构造函数前确保必要的属性存在
if not hasattr(self, 'appRunner'):
self.appRunner = None
load_prc_file_data("", """
win-size 1380 750
window-title Render
""")
# 简化 sys.path 设置逻辑
pipeline_path = os.path.join(project_root, "RenderPipelineFile")
if os.path.exists(pipeline_path):
if pipeline_path not in sys.path:
sys.path.insert(0, pipeline_path)
else:
print(f"错误: 找不到渲染管线目录: {pipeline_path}")
return
try:
from rpcore import RenderPipeline
self.render_pipeline = RenderPipeline()
self.render_pipeline.create(self)
#self.render_pipeline.pre_show_base_init()
#ShowBase.__init__(self)
except ImportError as e:
print(f"导入RenderPipeline模块失败: {e}")
import traceback
traceback.print_exc()
ShowBase.__init__(self)
self.render_pipeline = None
return
self.script_manager = ScriptManager(self)
self.script_manager.start_system()
# 加载所有脚本
self.script_manager.load_all_scripts_from_directory()
self.info_panel_manager = InfoPanelManager(self)
try:
# 再导入controller模块
from rpcore.util.movement_controller import MovementController
self.controller = MovementController(self)
self.controller.set_initial_position(
Vec3(0, -50, 20), Vec3(0, 0, 0))
self.controller.setup()
except ImportError as e:
print(f"导入MovementController失败: {e}")
self.controller = None
self._loadFont()
self.loadFullScene()
self.loadGUIFromJSON()
if hasattr(self, 'accept'):
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 getResourcePath(self,relative_path):
if getattr(sys,'frozen',False):
base_path = os.path.dirname(sys.executable)
else:
base_path = os.path.dirname(os.path.abspath(__file__))
return os.path.join(base_path,relative_path)
def loadFullScene(self):
if not hasattr(self, 'loader') or not hasattr(self, 'render'):
print("错误: Panda3D核心组件未正确初始化")
return
try:
scene_file = self.getResourcePath("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._processModelAnimations(scene)
# 处理场景中的各种元素
self.processSceneElements(scene)
else:
print("⚠️ 场景文件加载失败")
else:
print("⚠️ 未找到场景文件")
except Exception as e:
print(f"加载完整场景时出错: {str(e)}")
import traceback
traceback.print_exc()
def _processModelAnimations(self, node_path):
"""处理节点中的动画模型"""
try:
# 查找场景中所有可能的动画模型
char_nodes = node_path.findAllMatches("**/+Character")
for char_node in char_nodes:
try:
# 获取父节点(通常是模型根节点)
model_root = char_node.getParent()
model_name = model_root.getName()
print(f"检测到可能的动画模型: {model_name}")
# 尝试创建Actor
actor = Actor(model_root)
actor.reparentTo(self.render)
actor.setPos(node_path.getPos())
actor.setHpr(node_path.getHpr())
actor.setScale(node_path.getScale())
# 获取动画名称
anim_names = actor.getAnimNames()
if anim_names:
print(f"✓ 成功创建动画模型: {model_name}")
print(f" 可用动画: {anim_names}")
# 循环播放所有动画
for anim_name in anim_names:
print(f" 循环播放动画: {anim_name}")
actor.loop(anim_name)
break # 只播放第一个动画,避免同时播放多个动画
# 替换原始模型
model_root.detachNode()
else:
# 没有动画,使用原始模型
print(f"模型 {model_name} 不包含动画")
actor.detachNode() # 移除创建的Actor
except Exception as e:
print(f"处理动画模型 {char_node.getName()} 时出错: {str(e)}")
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("scripts_info"):
try:
import json
scripts_info = json.loads(nodePath.getTag("scripts_info"))
self.processScripts(nodePath,scripts_info)
except Exception as e:
print(f"处理节点 {nodePath.getName()} 的脚本时出错: {str(e)}")
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):
try:
gui_json_path = self.getResourcePath("gui/gui_elements.json")
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)
else:
print("GUI配置文件为空 ")
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
)
elif gui_type == "info_panel":
new_element = self.info_panel_manager.onCreateSampleInfoPanel()
if "scripts" in gui_info and new_element:
self.processScripts(new_element,gui_info["scripts"])
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 not hasattr(self, 'aspect2d'):
print("错误: aspect2d未初始化")
return None
if image_path and not os.path.isabs(image_path):
image_path = self.getResourcePath(image_path)
# 处理非均匀缩放
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}")
return image_node
def createGUI2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None):
import os
if video_path and not os.path.isabs(video_path):
image_path = self.getResourcePath(video_path)
from direct.gui.DirectGui import DirectFrame
from panda3d.core import TransparencyAttrib, Texture, TextureStage
import os
if isinstance(size,(list,tuple)) and len(size) >= 2:
width_scale = size[0]*0.2
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()
def processScripts(self, element, script_info_list):
"""处理元素上挂载的脚本 - 使用新的脚本系统"""
try:
print(f"正在为元素 {element.getName()} 挂载脚本")
print(f"可用脚本列表: {self.script_manager.get_available_scripts()}")
for script_info in script_info_list:
script_name = script_info["name"]
script_file = script_info.get("file", "")
# 从文件路径中提取脚本类名
if script_file:
# 获取脚本文件名(不含路径和扩展名)
script_filename = os.path.basename(script_file)
script_class_name = os.path.splitext(script_filename)[0]
print(f"尝试挂载脚本: {script_class_name} (来自文件: {script_file})")
# 使用脚本管理器为元素添加脚本
script_component = self.script_manager.add_script_to_object(element, script_class_name)
if script_component:
print(f"✓ 脚本 {script_class_name} 已挂载到元素 {element.getName()}")
else:
print(f"⚠️ 脚本 {script_class_name} 挂载失败")
# 列出可用脚本帮助调试
available_scripts = self.script_manager.get_available_scripts()
print(f"当前可用脚本: {available_scripts}")
else:
print(f"⚠️ 脚本信息不完整: {script_name}")
except Exception as e:
print(f"挂载脚本到元素 {element.getName()} 失败: {str(e)}")
import traceback
traceback.print_exc()
# 在 main.py 的最后部分,修改为:
if __name__ == "__main__":
try:
app = MainApp()
if hasattr(app, 'run'):
app.run()
else:
print("应用程序初始化失败")
except Exception as e:
print(f"应用程序启动失败: {str(e)}")
import traceback
traceback.print_exc()