870 lines
32 KiB
Python
870 lines
32 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"))
|
||
|
||
# 创建标准的应用程序入口文件
|
||
self._createAppFile(build_dir, project_name)
|
||
|
||
# 创建标准的setup.py文件
|
||
self._createStandardSetupFile(build_dir, project_name)
|
||
|
||
def _createAppFile(self, build_dir, project_name):
|
||
"""创建应用程序主文件"""
|
||
app_code = f'''#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
{project_name} - Panda3D应用程序
|
||
使用Panda3D引擎编辑器创建
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
from direct.showbase.ShowBase import ShowBase
|
||
from panda3d.core import (loadPrcFileData, WindowProperties, AmbientLight,
|
||
DirectionalLight, Point3, Vec3)
|
||
|
||
# 配置Panda3D
|
||
loadPrcFileData("", """
|
||
win-size 1280 720
|
||
window-title {project_name}
|
||
show-frame-rate-meter 1
|
||
sync-video 1
|
||
want-directtools #f
|
||
want-tk #f
|
||
audio-library-name p3openal_audio
|
||
""")
|
||
|
||
class {project_name.replace(' ', '').replace('-', '')}App(ShowBase):
|
||
"""应用程序主类"""
|
||
|
||
def __init__(self):
|
||
ShowBase.__init__(self)
|
||
|
||
print(f"启动 {project_name}...")
|
||
|
||
# 设置窗口属性
|
||
self.setupWindow()
|
||
|
||
# 设置光照
|
||
self.setupLighting()
|
||
|
||
# 加载场景
|
||
self.loadScene()
|
||
|
||
# 设置相机控制
|
||
self.setupControls()
|
||
|
||
print("✓ 应用程序初始化完成")
|
||
|
||
def setupWindow(self):
|
||
"""设置窗口"""
|
||
# 设置背景色
|
||
self.setBackgroundColor(0.2, 0.2, 0.2)
|
||
|
||
# 设置窗口属性
|
||
props = WindowProperties()
|
||
props.setTitle("{project_name}")
|
||
self.win.requestProperties(props)
|
||
|
||
def setupLighting(self):
|
||
"""设置光照系统"""
|
||
# 环境光
|
||
alight = AmbientLight('alight')
|
||
alight.setColor((0.3, 0.3, 0.3, 1))
|
||
alnp = self.render.attachNewNode(alight)
|
||
self.render.setLight(alnp)
|
||
|
||
# 定向光(模拟太阳光)
|
||
dlight = DirectionalLight('dlight')
|
||
dlight.setColor((0.8, 0.8, 0.8, 1))
|
||
dlight.setDirection(Vec3(-1, -1, -1))
|
||
dlnp = self.render.attachNewNode(dlight)
|
||
self.render.setLight(dlnp)
|
||
|
||
def loadScene(self):
|
||
"""加载场景"""
|
||
try:
|
||
# 查找场景文件
|
||
scene_file = "scene.bam"
|
||
if not os.path.exists(scene_file):
|
||
print("警告: 没有找到场景文件,创建默认场景")
|
||
self.createDefaultScene()
|
||
return
|
||
|
||
# 加载场景
|
||
scene = self.loader.loadModel(scene_file)
|
||
if scene:
|
||
scene.reparentTo(self.render)
|
||
print("✓ 场景加载成功")
|
||
|
||
# 自动调整相机位置
|
||
self.adjustCamera()
|
||
else:
|
||
print("警告: 场景加载失败,创建默认场景")
|
||
self.createDefaultScene()
|
||
|
||
except Exception as e:
|
||
print(f"加载场景时出错: {{str(e)}}")
|
||
self.createDefaultScene()
|
||
|
||
def createDefaultScene(self):
|
||
"""创建默认场景"""
|
||
# 加载默认的环境模型
|
||
env = self.loader.loadModel("models/environment")
|
||
if env:
|
||
env.reparentTo(self.render)
|
||
env.setScale(0.25)
|
||
env.setPos(-8, 42, 0)
|
||
|
||
# 创建一个简单的立方体作为示例
|
||
from panda3d.core import CardMaker
|
||
cm = CardMaker("ground")
|
||
cm.setFrame(-10, 10, -10, 10)
|
||
ground = self.render.attachNewNode(cm.generate())
|
||
ground.setP(-90)
|
||
ground.setColor(0.5, 0.8, 0.5, 1)
|
||
|
||
def adjustCamera(self):
|
||
"""调整相机位置以查看场景"""
|
||
# 计算场景边界
|
||
bounds = self.render.getBounds()
|
||
if bounds and not bounds.isEmpty():
|
||
center = bounds.getCenter()
|
||
radius = bounds.getRadius()
|
||
|
||
# 设置相机位置
|
||
distance = radius * 3
|
||
self.cam.setPos(center.x, center.y - distance, center.z + radius)
|
||
self.cam.lookAt(center)
|
||
else:
|
||
# 默认相机位置
|
||
self.cam.setPos(0, -20, 5)
|
||
self.cam.lookAt(0, 0, 0)
|
||
|
||
def setupControls(self):
|
||
"""设置相机控制"""
|
||
# 启用鼠标控制
|
||
self.accept("wheel_up", self.zoomIn)
|
||
self.accept("wheel_down", self.zoomOut)
|
||
|
||
# 键盘控制说明
|
||
print("\\n=== 控制说明 ===")
|
||
print("鼠标滚轮: 缩放")
|
||
print("ESC: 退出")
|
||
print("================\\n")
|
||
|
||
# ESC键退出
|
||
self.accept("escape", sys.exit)
|
||
|
||
def zoomIn(self):
|
||
"""放大"""
|
||
pos = self.cam.getPos()
|
||
lookAt = Point3(0, 0, 0) # 假设看向原点
|
||
direction = (lookAt - pos).normalized()
|
||
newPos = pos + direction * 2
|
||
self.cam.setPos(newPos)
|
||
|
||
def zoomOut(self):
|
||
"""缩小"""
|
||
pos = self.cam.getPos()
|
||
lookAt = Point3(0, 0, 0) # 假设看向原点
|
||
direction = (lookAt - pos).normalized()
|
||
newPos = pos - direction * 2
|
||
self.cam.setPos(newPos)
|
||
|
||
def main():
|
||
"""主函数"""
|
||
try:
|
||
app = {project_name.replace(' ', '').replace('-', '')}App()
|
||
app.run()
|
||
except Exception as e:
|
||
print(f"应用程序启动失败: {{str(e)}}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
input("按Enter键退出...")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
'''
|
||
|
||
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',
|
||
'panda3d.core',
|
||
'panda3d.direct',
|
||
],
|
||
}},
|
||
|
||
# 排除的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) |