forked from Rowland/EG
1373 lines
54 KiB
Python
1373 lines
54 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
项目管理器 - 负责项目的生命周期管理
|
||
处理项目创建、打开、保存、打包等功能
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import datetime
|
||
import subprocess
|
||
import shutil
|
||
from PyQt5.QtWidgets import (
|
||
QDialog, QVBoxLayout, QHBoxLayout, QGroupBox, QLineEdit, QPushButton,
|
||
QLabel, QDialogButtonBox, QFileDialog, QMessageBox, QMainWindow
|
||
)
|
||
from PyQt5.QtCore import Qt
|
||
|
||
# 导入自定义对话框
|
||
from ui.widgets import NewProjectDialog
|
||
|
||
class ProjectManager:
|
||
"""项目管理器 - 统一管理项目的生命周期"""
|
||
|
||
def __init__(self, world):
|
||
"""初始化项目管理器
|
||
|
||
Args:
|
||
world: 主程序world对象引用
|
||
"""
|
||
self.world = world
|
||
self.current_project_path = None
|
||
self.project_config = None
|
||
|
||
print("✓ 项目管理系统初始化完成")
|
||
|
||
# ==================== 项目生命周期管理 ====================
|
||
|
||
def createNewProject(self, parent_window):
|
||
"""创建新项目"""
|
||
dialog = NewProjectDialog(parent_window)
|
||
if dialog.exec_() != QDialog.Accepted:
|
||
return False
|
||
|
||
project_path = dialog.projectPath
|
||
project_name = dialog.projectName
|
||
# full_project_path = os.path.join(project_path, project_name)
|
||
full_project_path = os.path.normpath(os.path.join(project_path, project_name))
|
||
print(f"full_project_path: {full_project_path}")
|
||
|
||
try:
|
||
# 创建项目文件夹结构
|
||
os.makedirs(full_project_path)
|
||
os.makedirs(os.path.join(full_project_path, "models")) # 模型文件夹
|
||
os.makedirs(os.path.join(full_project_path, "textures")) # 贴图文件夹
|
||
scenes_path = os.path.join(full_project_path, "scenes") # 场景文件夹
|
||
os.makedirs(scenes_path)
|
||
|
||
# 创建项目配置文件
|
||
project_config = {
|
||
"name": project_name,
|
||
"path": full_project_path,
|
||
"created_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"last_modified": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"version": "1.0.0",
|
||
"engine_version": "1.0.0"
|
||
}
|
||
|
||
config_file = os.path.join(full_project_path, "project.json")
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
|
||
print(f"项目配置文件已创建: {config_file}")
|
||
|
||
# 清空当前场景
|
||
self._clearCurrentScene()
|
||
|
||
# 自动保存初始场景
|
||
scene_file = os.path.join(scenes_path, "scene.bam")
|
||
if self.world.scene_manager.saveScene(scene_file, project_path):
|
||
# 更新配置文件中的场景路径
|
||
project_config["scene_file"] = os.path.relpath(scene_file, full_project_path)
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
print(f"初始场景已保存到: {scene_file}")
|
||
else:
|
||
print("初始场景保存失败")
|
||
|
||
# 更新项目状态
|
||
self.current_project_path = full_project_path
|
||
self.project_config = project_config
|
||
|
||
# 更新文件浏览器到新项目路径
|
||
if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'):
|
||
parent_window.fileView.setRootIndex(parent_window.fileModel.index(full_project_path))
|
||
|
||
# 更新窗口标题
|
||
self.updateWindowTitle(parent_window, project_name)
|
||
|
||
# 保存当前项目路径到主窗口
|
||
parent_window.current_project_path = full_project_path
|
||
|
||
# 显示成功消息
|
||
QMessageBox.information(parent_window, "成功", f"项目 '{project_name}' 创建成功!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(parent_window, "错误", f"创建项目失败: {str(e)}")
|
||
return False
|
||
|
||
def openProject(self, parent_window):
|
||
"""打开项目"""
|
||
try:
|
||
# 获取项目路径
|
||
project_path = QFileDialog.getExistingDirectory(
|
||
parent_window,
|
||
"选择项目文件夹",
|
||
"",
|
||
QFileDialog.ShowDirsOnly
|
||
)
|
||
|
||
if not project_path:
|
||
return False
|
||
|
||
# 检查是否是有效的项目文件夹
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if not os.path.exists(config_file):
|
||
QMessageBox.warning(parent_window, "警告", "选择的不是有效的项目文件夹!")
|
||
return False
|
||
|
||
# 读取项目配置
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
|
||
# 检查场景文件
|
||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
if os.path.exists(scene_file):
|
||
# 加载场景
|
||
if self.world.scene_manager.loadScene(scene_file):
|
||
# 更新项目配置
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
|
||
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
|
||
# 更新项目状态
|
||
self.current_project_path = project_path
|
||
self.project_config = project_config
|
||
|
||
# 保存当前项目路径到主窗口
|
||
parent_window.current_project_path = project_path
|
||
|
||
# 更新窗口标题
|
||
project_name = os.path.basename(project_path)
|
||
self.updateWindowTitle(parent_window, project_name)
|
||
|
||
# 更新文件浏览器
|
||
if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'):
|
||
parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path))
|
||
|
||
QMessageBox.information(parent_window, "成功", "项目加载成功!")
|
||
return True
|
||
# 检查场景文件
|
||
# scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
# if not os.path.exists(scene_file):
|
||
# QMessageBox.warning(parent_window, "警告", "没有找到场景文件!")
|
||
# return False
|
||
#
|
||
# # 加载场景
|
||
# if self.world.scene_manager.loadScene(scene_file):
|
||
# # 更新项目配置
|
||
# project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
# project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
#
|
||
# with open(config_file, "w", encoding="utf-8") as f:
|
||
# json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
#
|
||
# # 更新项目状态
|
||
# self.current_project_path = project_path
|
||
# self.project_config = project_config
|
||
#
|
||
# # 保存当前项目路径到主窗口
|
||
# parent_window.current_project_path = project_path
|
||
#
|
||
# # 更新窗口标题
|
||
# project_name = os.path.basename(project_path)
|
||
# self.updateWindowTitle(parent_window, project_name)
|
||
#
|
||
# # 更新文件浏览器
|
||
# if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'):
|
||
# parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path))
|
||
#
|
||
# QMessageBox.information(parent_window, "成功", "项目加载成功!")
|
||
# return True
|
||
# else:
|
||
# QMessageBox.warning(parent_window, "错误", "加载场景失败!")
|
||
# return False
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(parent_window, "错误", f"加载项目时发生错误:{str(e)}")
|
||
return False
|
||
|
||
def openProjectForPath(self, project_path, parent_window=None):
|
||
"""通过路径打开项目
|
||
|
||
Args:
|
||
project_path: 项目路径
|
||
parent_window: 父窗口对象(可选)
|
||
"""
|
||
try:
|
||
if not project_path:
|
||
return False
|
||
# 检查是否是有效的项目文件夹
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if not os.path.exists(config_file):
|
||
if parent_window:
|
||
QMessageBox.warning(parent_window, "警告", f"选择的不是有效的项目文件夹!{project_path}")
|
||
else:
|
||
print("警告: 选择的不是有效的项目文件夹!")
|
||
return False
|
||
|
||
# 读取项目配置
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
|
||
# 检查场景文件
|
||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
if os.path.exists(scene_file):
|
||
# 加载场景
|
||
if self.world.scene_manager.loadScene(scene_file):
|
||
# 更新项目配置
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
|
||
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
|
||
# 更新项目状态
|
||
self.current_project_path = project_path
|
||
self.project_config = project_config
|
||
|
||
# 如果有父窗口,更新相关UI元素
|
||
if parent_window:
|
||
# 保存当前项目路径到主窗口
|
||
parent_window.current_project_path = project_path
|
||
|
||
# 更新窗口标题
|
||
project_name = os.path.basename(project_path)
|
||
self.updateWindowTitle(parent_window, project_name)
|
||
|
||
# 更新文件浏览器
|
||
if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'):
|
||
parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path))
|
||
|
||
QMessageBox.information(parent_window, "成功", "项目加载成功!")
|
||
|
||
print(f"项目 '{project_path}' 加载成功!")
|
||
return True
|
||
else:
|
||
if parent_window:
|
||
QMessageBox.warning(parent_window, "错误", "加载场景失败!")
|
||
else:
|
||
print("错误: 加载场景失败!")
|
||
return False
|
||
|
||
except Exception as e:
|
||
error_msg = f"加载项目时发生错误:{str(e)}"
|
||
if parent_window:
|
||
QMessageBox.critical(parent_window, "错误", error_msg)
|
||
else:
|
||
print(error_msg)
|
||
return False
|
||
|
||
def saveProject(self, parent_window):
|
||
"""保存项目"""
|
||
try:
|
||
# 检查是否有当前项目路径
|
||
if not self.current_project_path:
|
||
# 尝试从主窗口获取
|
||
if hasattr(parent_window, 'current_project_path'):
|
||
self.current_project_path = parent_window.current_project_path
|
||
else:
|
||
QMessageBox.warning(parent_window, "警告", "请先创建或打开一个项目!")
|
||
return False
|
||
|
||
project_path = self.current_project_path
|
||
scenes_path = os.path.join(project_path, "scenes")
|
||
|
||
# 固定的场景文件名
|
||
scene_file = os.path.join(scenes_path, "scene.bam")
|
||
|
||
# 如果存在旧文件,先删除
|
||
if os.path.exists(scene_file):
|
||
try:
|
||
os.remove(scene_file)
|
||
print(f"已删除旧场景文件: {scene_file}")
|
||
except Exception as e:
|
||
print(f"删除旧场景文件失败: {str(e)}")
|
||
return False
|
||
|
||
# 保存场景
|
||
if self.world.scene_manager.saveScene(scene_file, project_path):
|
||
# 更新项目配置文件
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if os.path.exists(config_file):
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
|
||
# 更新最后修改时间
|
||
project_config["last_modified"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
# 记录场景文件路径
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
|
||
with open(config_file, "w", encoding="utf-8") as f:
|
||
json.dump(project_config, f, ensure_ascii=False, indent=4)
|
||
|
||
# 更新项目配置
|
||
self.project_config = project_config
|
||
|
||
# 更新窗口标题
|
||
project_name = os.path.basename(project_path)
|
||
self.updateWindowTitle(parent_window, project_name)
|
||
|
||
QMessageBox.information(parent_window, "成功", "项目保存成功!")
|
||
return True
|
||
else:
|
||
QMessageBox.warning(parent_window, "错误", "保存场景失败!")
|
||
return False
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(parent_window, "错误", f"保存项目时发生错误:{str(e)}")
|
||
return False
|
||
|
||
# ==================== 项目打包功能 ====================
|
||
|
||
def buildPackage(self, parent_window):
|
||
"""打包项目为可执行文件 - 按照Panda3D官方标准方法"""
|
||
try:
|
||
# 检查是否有当前项目路径
|
||
if not self.current_project_path:
|
||
if hasattr(parent_window, 'current_project_path'):
|
||
self.current_project_path = parent_window.current_project_path
|
||
else:
|
||
QMessageBox.warning(parent_window, "警告", "请先创建或打开一个项目!")
|
||
return False
|
||
|
||
project_path = self.current_project_path
|
||
scenes_path = os.path.join(project_path, "scenes")
|
||
scene_file = os.path.join(scenes_path, "scene.bam")
|
||
|
||
# 检查场景文件是否存在
|
||
if not os.path.exists(scene_file):
|
||
QMessageBox.warning(parent_window, "警告", "请先保存场景!")
|
||
return False
|
||
|
||
# 创建构建目录
|
||
build_dir = os.path.join(project_path, "build")
|
||
if not os.path.exists(build_dir):
|
||
os.makedirs(build_dir)
|
||
|
||
# 创建标准的打包文件
|
||
self._createStandardBuildFiles(build_dir, project_path, scene_file)
|
||
|
||
# 执行打包命令
|
||
success = self._executeStandardBuild(build_dir, parent_window)
|
||
|
||
if success:
|
||
QMessageBox.information(parent_window, "成功",
|
||
"打包完成!\n可执行文件在 build/dist/ 目录中。\n"
|
||
"支持的格式:\n"
|
||
"- Windows: .exe 安装程序\n"
|
||
"- Linux: .tar.gz 压缩包\n"
|
||
"- 通用: .zip 压缩包")
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(parent_window, "错误", f"打包过程出错:{str(e)}")
|
||
return False
|
||
|
||
def _createStandardBuildFiles(self, build_dir, project_path, scene_file):
|
||
"""创建标准的Panda3D打包文件"""
|
||
project_name = os.path.basename(project_path)
|
||
|
||
# 确保构建目录存在
|
||
if not os.path.exists(build_dir):
|
||
os.makedirs(build_dir)
|
||
|
||
# 复制场景文件到构建目录
|
||
shutil.copy2(scene_file, os.path.join(build_dir, "scene.bam"))
|
||
|
||
# 复制Resources文件夹到build目录
|
||
source_resources = os.path.join(project_path, "scenes", "resources")
|
||
self.copy_folder(source_resources, build_dir)
|
||
|
||
self._saveGUIElementsToJSON(build_dir, project_path)
|
||
|
||
self._copyScriptsToBuild(build_dir, project_path)
|
||
|
||
self._copyScriptSystemToBuild(build_dir)
|
||
|
||
self._copyInfoPanelSystemToBuild(build_dir)
|
||
|
||
source_render_pipeline = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),"RenderPipelineFile")
|
||
dest_render_pipeline = os.path.join(build_dir,"RenderPipelineFile")
|
||
|
||
if os.path.exists(source_render_pipeline):
|
||
if os.path.exists(dest_render_pipeline):
|
||
shutil.rmtree(dest_render_pipeline)
|
||
|
||
shutil.copytree(
|
||
source_render_pipeline,
|
||
dest_render_pipeline,
|
||
ignore=shutil.ignore_patterns('__pycache__','*.pyc','.git','.vscode','*.log')
|
||
)
|
||
print("✓ RenderPipelineFile文件夹已复制到build目录")
|
||
else:
|
||
print("⚠️ RenderPipelineFile文件夹未找到")
|
||
|
||
# 创建标准的应用程序入口文件
|
||
self._createAppFile(build_dir, project_name)
|
||
|
||
# 创建标准的setup.py文件
|
||
self._createStandardSetupFile(build_dir, project_name)
|
||
|
||
#创建requirements.txt文件
|
||
self._createRequirementsFile(build_dir)
|
||
|
||
def _copyScriptsToBuild(self, build_dir, project_path):
|
||
"""复制脚本文件到构建目录的scripts文件夹"""
|
||
try:
|
||
# 创建目标scripts目录
|
||
scripts_dest = os.path.join(build_dir, "scripts")
|
||
|
||
# 正确的源scripts目录路径
|
||
scripts_src = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts")
|
||
|
||
# 如果上面的路径不存在,尝试项目路径下的scripts目录
|
||
if not os.path.exists(scripts_src):
|
||
scripts_src = os.path.join(project_path, "scripts")
|
||
|
||
if os.path.exists(scripts_src):
|
||
# 直接复制整个scripts目录
|
||
if os.path.exists(scripts_dest):
|
||
shutil.rmtree(scripts_dest)
|
||
|
||
shutil.copytree(
|
||
scripts_src,
|
||
scripts_dest,
|
||
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
|
||
)
|
||
print("✓ Scripts目录已复制到build/scripts")
|
||
else:
|
||
# 创建空的scripts目录
|
||
if not os.path.exists(scripts_dest):
|
||
os.makedirs(scripts_dest)
|
||
print("⚠️ 项目中没有scripts目录")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 复制脚本文件时出错: {str(e)}")
|
||
|
||
def _copyScriptSystemToBuild(self, build_dir):
|
||
"""复制core/script_system.py文件到构建目录"""
|
||
try:
|
||
# 源文件路径
|
||
source_script_system = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "core",
|
||
"script_system.py")
|
||
|
||
# 目标目录
|
||
core_dest = os.path.join(build_dir, "core")
|
||
|
||
# 如果源文件存在
|
||
if os.path.exists(source_script_system):
|
||
# 确保目标目录存在
|
||
if not os.path.exists(core_dest):
|
||
os.makedirs(core_dest)
|
||
|
||
# 复制文件
|
||
shutil.copy2(source_script_system, os.path.join(core_dest, "script_system.py"))
|
||
print("✓ core/script_system.py文件已复制到build目录")
|
||
else:
|
||
print("⚠️ core/script_system.py文件未找到")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 复制core/script_system.py文件时出错: {str(e)}")
|
||
|
||
def _copyInfoPanelSystemToBuild(self, build_dir):
|
||
"""复制core/script_system.py文件到构建目录"""
|
||
try:
|
||
# 源文件路径
|
||
source_InfoPanel_system = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "core",
|
||
"InfoPanelManager.py")
|
||
|
||
# 目标目录
|
||
core_dest = os.path.join(build_dir, "core")
|
||
|
||
# 如果源文件存在
|
||
if os.path.exists(source_InfoPanel_system):
|
||
# 确保目标目录存在
|
||
if not os.path.exists(core_dest):
|
||
os.makedirs(core_dest)
|
||
|
||
# 复制文件
|
||
shutil.copy2(source_InfoPanel_system, os.path.join(core_dest, "InfoPanelManager.py"))
|
||
print("✓ core/InfoPanelManager.py文件已复制到build目录")
|
||
else:
|
||
print("⚠️ core/InfoPanelManager.py文件未找到")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 复制core/InfoPanelManager.py文件时出错: {str(e)}")
|
||
|
||
def _saveGUIElementsToJSON(self, build_dir, project_path):
|
||
"""保存GUI元素到JSON文件,内容与_collectGUIElementInfo保持一致"""
|
||
try:
|
||
# 创建目标gui目录
|
||
gui_dest = os.path.join(build_dir, "gui")
|
||
if not os.path.exists(gui_dest):
|
||
os.makedirs(gui_dest)
|
||
|
||
# 收集所有GUI元素信息
|
||
gui_data = []
|
||
|
||
# 获取当前场景中的GUI元素
|
||
if hasattr(self.world, 'gui_elements'):
|
||
for gui_node in self.world.gui_elements:
|
||
if gui_node and not gui_node.isEmpty():
|
||
# 使用_collectGUIElementInfo方法收集信息
|
||
gui_info = self.world.scene_manager._collectGUIElementInfo(gui_node)
|
||
if gui_info:
|
||
gui_data.append(gui_info)
|
||
print(f"收集GUI元素信息: {gui_info['name']}")
|
||
|
||
# 保存GUI信息到JSON文件
|
||
gui_file_path = os.path.join(gui_dest, "gui_elements.json")
|
||
with open(gui_file_path, "w", encoding="utf-8") as f:
|
||
json.dump(gui_data, f, ensure_ascii=False, indent=4)
|
||
|
||
print(f"✓ GUI元素数据已保存到 {gui_file_path},共 {len(gui_data)} 个元素")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 保存GUI元素时出错: {str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def _createRequirementsFile(self,build_dir):
|
||
requirements_content = """panda3d>=1.10.13"""
|
||
|
||
requirements_path = os.path.join(build_dir,"requirements.txt")
|
||
with open(requirements_path,"w",encoding="utf-8") as f:
|
||
f.write(requirements_content)
|
||
|
||
def copy_folder(self, source_folder, destination_folder):
|
||
"""将一个文件夹从源路径复制到目标路径下的resources文件夹中
|
||
|
||
Args:
|
||
source_folder (str): 源文件夹路径
|
||
destination_folder (str): 目标文件夹路径
|
||
"""
|
||
try:
|
||
# 创建resources文件夹作为目标
|
||
resources_dest = os.path.join(destination_folder, "resources")
|
||
|
||
# 确保目标目录存在
|
||
if not os.path.exists(destination_folder):
|
||
os.makedirs(destination_folder)
|
||
|
||
# 如果目标resources文件夹已存在,先删除
|
||
if os.path.exists(resources_dest):
|
||
shutil.rmtree(resources_dest)
|
||
|
||
# 检查源文件夹是否存在
|
||
if os.path.exists(source_folder):
|
||
# 复制整个文件夹到resources目录下
|
||
shutil.copytree(
|
||
source_folder,
|
||
resources_dest,
|
||
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
|
||
)
|
||
print(f"✓ 文件夹已从 {source_folder} 复制到 {resources_dest}")
|
||
return True
|
||
else:
|
||
print(f"⚠️ 源文件夹不存在: {source_folder}")
|
||
# 即使源文件夹不存在,也创建空的resources目录
|
||
if not os.path.exists(resources_dest):
|
||
os.makedirs(resources_dest)
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 复制文件夹时出错: {str(e)}")
|
||
return False
|
||
|
||
def _copyResourcesToBuild(self, build_dir, project_path):
|
||
"""复制GUI资源到构建目录的resources文件夹"""
|
||
try:
|
||
# 创建目标resources目录
|
||
resources_dest = os.path.join(build_dir, "resources")
|
||
|
||
# 源Resources目录
|
||
resources_src = os.path.join(project_path, "Resources")
|
||
|
||
if os.path.exists(resources_src):
|
||
# 直接复制整个Resources目录
|
||
if os.path.exists(resources_dest):
|
||
shutil.rmtree(resources_dest)
|
||
|
||
shutil.copytree(
|
||
resources_src,
|
||
resources_dest,
|
||
ignore=shutil.ignore_patterns('__pycache__', '*.pyc', '.git', '.vscode', '*.log')
|
||
)
|
||
print("✓ Resources目录已复制到build/resources")
|
||
|
||
# 统计复制的文件数量
|
||
file_count = 0
|
||
for root, dirs, files in os.walk(resources_dest):
|
||
file_count += len(files)
|
||
print(f"✓ 共复制了 {file_count} 个资源文件")
|
||
else:
|
||
# 创建空的resources目录
|
||
if not os.path.exists(resources_dest):
|
||
os.makedirs(resources_dest)
|
||
print("⚠️ 项目中没有Resources目录")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 复制资源文件时出错: {str(e)}")
|
||
|
||
def _collectResourceFiles(self, project_path):
|
||
"""收集项目中GUI使用的资源文件"""
|
||
resource_files = set()
|
||
|
||
try:
|
||
# 收集Resources目录中的所有文件(这是最主要的资源来源)
|
||
resources_dir = os.path.join(project_path, "Resources")
|
||
if os.path.exists(resources_dir):
|
||
for root, dirs, files in os.walk(resources_dir):
|
||
for file in files:
|
||
file_path = os.path.join(root, file)
|
||
# 收集所有文件,不仅仅是媒体文件
|
||
resource_files.add(file_path)
|
||
|
||
# 同时收集场景中引用的特定资源
|
||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
if os.path.exists(scene_file):
|
||
# 从场景文件中提取资源引用
|
||
referenced_files = self._extractResourcesFromScene(scene_file, project_path)
|
||
for file_path in referenced_files:
|
||
if os.path.isabs(file_path):
|
||
if os.path.exists(file_path):
|
||
resource_files.add(file_path)
|
||
else:
|
||
# 相对路径
|
||
full_path = os.path.join(project_path, file_path)
|
||
if os.path.exists(full_path):
|
||
resource_files.add(full_path)
|
||
|
||
except Exception as e:
|
||
print(f"收集资源文件时出错: {str(e)}")
|
||
|
||
return list(resource_files)
|
||
|
||
def _extractResourcesFromScene(self, scene_file, project_path):
|
||
"""从场景文件中提取资源引用"""
|
||
referenced_files = []
|
||
|
||
try:
|
||
# 这里应该实现从BAM文件中提取贴图、视频等资源引用的逻辑
|
||
# 由于直接解析BAM文件比较复杂,我们采用间接方式
|
||
|
||
# 检查项目配置文件或其他元数据文件中可能包含的资源引用
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if os.path.exists(config_file):
|
||
try:
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
|
||
# 如果配置中有资源列表信息,可以在这里处理
|
||
# 这里暂时保持简单实现
|
||
except Exception as e:
|
||
print(f"读取项目配置时出错: {str(e)}")
|
||
|
||
except Exception as e:
|
||
print(f"从场景提取资源引用时出错: {str(e)}")
|
||
|
||
return referenced_files
|
||
|
||
def _isMediaFile(self, file_path):
|
||
"""判断是否为媒体文件(图片或视频)"""
|
||
media_extensions = {
|
||
# 图片格式
|
||
'.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tga', '.tiff',
|
||
# 视频格式
|
||
'.mp4', '.avi', '.mov', '.wmv', '.mkv', '.webm', '.flv'
|
||
}
|
||
|
||
_, ext = os.path.splitext(file_path.lower())
|
||
return ext in media_extensions
|
||
|
||
def _createAppFile(self, build_dir, project_name):
|
||
"""创建应用程序主文件 - 通过复制模板文件"""
|
||
# 获取模板文件路径(假设模板文件在项目根目录下的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")
|
||
|
||
# 检查模板文件是否存在
|
||
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官方文档"""
|
||
setup_code = f'''#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
{project_name} 打包配置文件
|
||
使用 Panda3D 标准打包工具
|
||
"""
|
||
|
||
from setuptools import setup
|
||
|
||
# 应用程序配置
|
||
APP_NAME = "{project_name}"
|
||
APP_VERSION = "1.0.0"
|
||
MAIN_SCRIPT = "main.py"
|
||
|
||
setup(
|
||
name=APP_NAME,
|
||
version=APP_VERSION,
|
||
|
||
# Panda3D 打包选项
|
||
options={{
|
||
'build_apps': {{
|
||
# GUI应用程序
|
||
'gui_apps': {{
|
||
APP_NAME: MAIN_SCRIPT,
|
||
}},
|
||
|
||
# 包含的文件模式
|
||
'include_patterns': [
|
||
'*.bam', # 场景文件
|
||
'*.egg', # 模型文件
|
||
'*.jpg', '*.png', # 纹理文件
|
||
'*.wav', '*.ogg', # 音频文件
|
||
'*.ttf', '*.otf', # 字体文件
|
||
],
|
||
|
||
# 排除的文件模式
|
||
'exclude_patterns': [
|
||
'*.pyc',
|
||
'__pycache__/**',
|
||
'.git/**',
|
||
'.vscode/**',
|
||
'*.log',
|
||
],
|
||
|
||
# Panda3D 插件
|
||
'plugins': [
|
||
'pandagl', # OpenGL渲染器
|
||
'pandaegg', # Egg文件支持
|
||
'p3openal_audio', # OpenAL音频
|
||
],
|
||
|
||
# 包含的Python模块
|
||
'include_modules': {{
|
||
'*': [
|
||
'direct.showbase.ShowBase',
|
||
'direct.task',
|
||
'direct.actor',
|
||
'direct.interval',
|
||
'direct.stdpy.file',
|
||
'direct.stdpy.pickle',
|
||
'panda3d.core',
|
||
'panda3d.direct',
|
||
'rpcore',
|
||
'rpcore.util.movement_controller',
|
||
'rpcore.native',
|
||
'rpcore.render_pipeline',
|
||
'rplibs',
|
||
'rpplugins',
|
||
'rpplugins.scattering',
|
||
'rpplugins.pssm',
|
||
'rpplugins.godrays',
|
||
'json',
|
||
'os',
|
||
'sys',
|
||
'six',
|
||
'collections',
|
||
'collections.abs',
|
||
'weakref',
|
||
'copy',
|
||
'itertools',
|
||
'importlib',
|
||
'importlib.util',
|
||
'importlib.machinery',
|
||
],
|
||
}},
|
||
|
||
# 排除的Python模块(减小体积)
|
||
'exclude_modules': {{
|
||
'*': [
|
||
'tkinter', # Tkinter GUI
|
||
'matplotlib', # 绘图库
|
||
'numpy', # 数值计算(如果不需要)
|
||
'scipy', # 科学计算(如果不需要)
|
||
'PIL', # 图像处理(如果不需要)
|
||
'wx', # wxPython
|
||
'PyQt5', # Qt界面库
|
||
'setuptools', # 安装工具
|
||
'distutils', # 分发工具
|
||
],
|
||
}},
|
||
|
||
# 平台设置
|
||
'platforms': [
|
||
'win_amd64', # Windows 64位
|
||
'linux_x86_64', # Linux 64位
|
||
# 'macosx_10_9_x86_64', # macOS(如果需要)
|
||
],
|
||
|
||
# 优化设置
|
||
'strip_docstrings': True, # 移除文档字符串
|
||
}},
|
||
}},
|
||
|
||
# 标准setuptools选项
|
||
author="Panda3D 引擎编辑器",
|
||
author_email="user@example.com",
|
||
description=f"{{APP_NAME}} - 使用Panda3D创建的3D应用程序",
|
||
long_description="这是一个使用Panda3D引擎编辑器创建的3D应用程序。",
|
||
|
||
# 依赖项
|
||
install_requires=[
|
||
'panda3d>=1.10.13',
|
||
],
|
||
|
||
# Python版本要求
|
||
python_requires='>=3.7',
|
||
|
||
# 分类信息
|
||
classifiers=[
|
||
'Development Status :: 4 - Beta',
|
||
'Intended Audience :: End Users/Desktop',
|
||
'Programming Language :: Python :: 3',
|
||
'Programming Language :: Python :: 3.7',
|
||
'Programming Language :: Python :: 3.8',
|
||
'Programming Language :: Python :: 3.9',
|
||
'Programming Language :: Python :: 3.10',
|
||
'Topic :: Games/Entertainment',
|
||
'Topic :: Multimedia :: Graphics :: 3D Rendering',
|
||
],
|
||
|
||
# 使用现代的许可证表达式
|
||
license='MIT',
|
||
)
|
||
'''
|
||
|
||
setup_path = os.path.join(build_dir, "setup.py")
|
||
with open(setup_path, "w", encoding="utf-8") as f:
|
||
f.write(setup_code)
|
||
|
||
def _executeStandardBuild(self, build_dir, parent_window):
|
||
"""执行标准的Panda3D打包命令"""
|
||
try:
|
||
print(f"开始打包,工作目录: {build_dir}")
|
||
|
||
# 首先尝试 bdist_apps(推荐方式)
|
||
print("执行标准打包命令: python setup.py bdist_apps")
|
||
|
||
process = subprocess.Popen(
|
||
[sys.executable, "setup.py", "bdist_apps"],
|
||
cwd=build_dir,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
universal_newlines=True,
|
||
encoding='utf-8'
|
||
)
|
||
|
||
# 实时显示输出
|
||
stdout_lines = []
|
||
stderr_lines = []
|
||
|
||
while True:
|
||
output = process.stdout.readline()
|
||
if output == '' and process.poll() is not None:
|
||
break
|
||
if output:
|
||
print(output.strip())
|
||
stdout_lines.append(output.strip())
|
||
|
||
# 获取错误输出
|
||
stderr = process.stderr.read()
|
||
if stderr:
|
||
print("错误输出:", stderr)
|
||
stderr_lines.append(stderr)
|
||
|
||
# 检查返回码
|
||
if process.returncode == 0:
|
||
print("✓ 打包成功完成")
|
||
return True
|
||
else:
|
||
# 如果bdist_apps失败,尝试build_apps
|
||
print(f"bdist_apps 失败 (返回码: {process.returncode}),尝试 build_apps...")
|
||
return self._tryBuildApps(build_dir, parent_window)
|
||
|
||
except Exception as e:
|
||
print(f"执行打包命令时出错: {str(e)}")
|
||
QMessageBox.critical(parent_window, "错误", f"打包失败:{str(e)}")
|
||
return False
|
||
|
||
def _tryBuildApps(self, build_dir, parent_window):
|
||
"""尝试使用 build_apps 命令"""
|
||
try:
|
||
print("执行备用打包命令: python setup.py build_apps")
|
||
|
||
process = subprocess.Popen(
|
||
[sys.executable, "setup.py", "build_apps"],
|
||
cwd=build_dir,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
universal_newlines=True,
|
||
encoding='utf-8'
|
||
)
|
||
|
||
# 实时显示输出
|
||
while True:
|
||
output = process.stdout.readline()
|
||
if output == '' and process.poll() is not None:
|
||
break
|
||
if output:
|
||
print(output.strip())
|
||
|
||
stderr = process.stderr.read()
|
||
if stderr:
|
||
print("错误输出:", stderr)
|
||
|
||
if process.returncode == 0:
|
||
print("✓ build_apps 成功完成")
|
||
return True
|
||
else:
|
||
error_msg = f"打包失败,返回码:{process.returncode}"
|
||
if stderr:
|
||
error_msg += f"\\n错误信息:{stderr}"
|
||
QMessageBox.critical(parent_window, "错误", error_msg)
|
||
return False
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(parent_window, "错误", f"执行 build_apps 失败:{str(e)}")
|
||
return False
|
||
|
||
# ==================== 工具方法 ====================
|
||
|
||
def updateWindowTitle(self, window, project_name=None):
|
||
"""更新窗口标题"""
|
||
base_title = "引擎编辑器"
|
||
if project_name:
|
||
window.setWindowTitle(f"{base_title} - {project_name}")
|
||
else:
|
||
window.setWindowTitle(base_title)
|
||
|
||
def _clearCurrentScene(self):
|
||
"""清空当前场景"""
|
||
# 移除所有模型
|
||
for model in self.world.models:
|
||
model.removeNode()
|
||
self.world.models.clear()
|
||
|
||
# 清空GUI元素
|
||
for gui_element in self.world.gui_elements:
|
||
gui_element.removeNode()
|
||
self.world.gui_elements.clear()
|
||
|
||
# 更新场景树
|
||
if hasattr(self.world, 'updateSceneTree'):
|
||
self.world.updateSceneTree()
|
||
|
||
def getCurrentProjectPath(self):
|
||
"""获取当前项目路径"""
|
||
return self.current_project_path
|
||
|
||
def getCurrentProjectConfig(self):
|
||
"""获取当前项目配置"""
|
||
return self.project_config
|
||
|
||
def isProjectOpen(self):
|
||
"""检查是否有项目打开"""
|
||
return self.current_project_path is not None
|
||
|
||
def getProjectName(self):
|
||
"""获取当前项目名称"""
|
||
if self.current_project_path:
|
||
return os.path.basename(self.current_project_path)
|
||
return None
|
||
|
||
def getProjectInfo(self):
|
||
"""获取项目信息"""
|
||
if not self.project_config:
|
||
return None
|
||
|
||
return {
|
||
"name": self.project_config.get("name", "未知项目"),
|
||
"path": self.project_config.get("path", ""),
|
||
"created_at": self.project_config.get("created_at", ""),
|
||
"last_modified": self.project_config.get("last_modified", ""),
|
||
"version": self.project_config.get("version", "1.0.0"),
|
||
"engine_version": self.project_config.get("engine_version", "1.0.0")
|
||
}
|
||
|
||
# ==================== 便利函数 ====================
|
||
|
||
def updateWindowTitle(window, project_name=None):
|
||
"""更新窗口标题 - 独立便利函数"""
|
||
base_title = "引擎编辑器"
|
||
if project_name:
|
||
window.setWindowTitle(f"{base_title} - {project_name}")
|
||
else:
|
||
window.setWindowTitle(base_title) |