843 lines
33 KiB
Python
843 lines
33 KiB
Python
import os
|
||
import sys
|
||
import json
|
||
import datetime
|
||
import subprocess
|
||
import shutil
|
||
|
||
class ProjectManager:
|
||
|
||
def __init__(self, world):
|
||
self.world = world
|
||
self.current_project_path = None
|
||
self.project_config = None
|
||
|
||
print("✓ 项目管理系统初始化完成")
|
||
|
||
# ==================== 项目生命周期管理 ====================
|
||
|
||
def createNewProject(self, project_path, project_name):
|
||
"""创建新项目
|
||
|
||
Args:
|
||
project_path: 项目路径
|
||
project_name: 项目名称
|
||
|
||
Returns:
|
||
bool: 创建是否成功
|
||
"""
|
||
if not project_path or not project_name:
|
||
print("错误: 项目路径和名称不能为空")
|
||
return False
|
||
|
||
# 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
|
||
|
||
print(f"项目 '{project_name}' 创建成功!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"创建项目失败: {str(e)}")
|
||
return False
|
||
|
||
def openProject(self, project_path):
|
||
"""打开项目
|
||
|
||
Args:
|
||
project_path: 项目路径
|
||
|
||
Returns:
|
||
bool: 打开是否成功
|
||
"""
|
||
# print(f"\n[DEBUG] ===== 开始打开项目: {project_path} =====")
|
||
try:
|
||
if not project_path:
|
||
print("错误: 项目路径不能为空")
|
||
return False
|
||
|
||
# 检查是否是有效的项目文件夹
|
||
config_file = os.path.join(project_path, "project.json")
|
||
# print(f"[DEBUG] 项目配置文件路径: {config_file}")
|
||
# print(f"[DEBUG] 配置文件是否存在: {os.path.exists(config_file)}")
|
||
|
||
if not os.path.exists(config_file):
|
||
print("错误: 选择的不是有效的项目文件夹!")
|
||
return False
|
||
|
||
# 读取项目配置
|
||
# print(f"[DEBUG] 开始读取项目配置文件...")
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
# print(f"[DEBUG] 项目配置读取成功: {project_config.get('name', 'Unknown')}")
|
||
|
||
# 检查场景文件
|
||
scene_file = os.path.join(project_path, "scenes", "scene.bam")
|
||
# print(f"[DEBUG] 场景文件路径: {scene_file}")
|
||
# print(f"[DEBUG] 场景文件是否存在: {os.path.exists(scene_file)}")
|
||
|
||
if os.path.exists(scene_file):
|
||
# 加载场景前的安全检查
|
||
# print(f"[DEBUG] 开始加载场景文件...")
|
||
# print(f"[DEBUG] 当前world状态检查:")
|
||
# print(f"[DEBUG] - render节点存在: {hasattr(self.world, 'render') and self.world.render is not None}")
|
||
# print(f"[DEBUG] - loader存在: {hasattr(self.world, 'loader') and self.world.loader is not None}")
|
||
# print(f"[DEBUG] - scene_manager存在: {hasattr(self.world, 'scene_manager') and self.world.scene_manager is not None}")
|
||
|
||
# 检查world的基本状态
|
||
if not hasattr(self.world, 'render') or self.world.render is None:
|
||
# print(f"[DEBUG] 错误: world.render不存在或为None")
|
||
return False
|
||
|
||
if not hasattr(self.world, 'loader') or self.world.loader is None:
|
||
# print(f"[DEBUG] 错误: world.loader不存在或为None")
|
||
return False
|
||
|
||
# 清理可能的残留状态
|
||
try:
|
||
# 清理选择状态
|
||
if hasattr(self.world, 'selection') and self.world.selection:
|
||
self.world.selection.clearSelection()
|
||
# print(f"[DEBUG] 选择状态已清理")
|
||
|
||
# 清理GUI状态
|
||
if hasattr(self.world, 'gui_elements'):
|
||
for gui_elem in self.world.gui_elements:
|
||
if gui_elem and not gui_elem.isEmpty():
|
||
gui_elem.detachNode()
|
||
self.world.gui_elements.clear()
|
||
# print(f"[DEBUG] GUI元素已清理")
|
||
|
||
# 验证BAM文件
|
||
# print(f"[DEBUG] 验证BAM文件完整性...")
|
||
if os.path.getsize(scene_file) == 0:
|
||
# print(f"[DEBUG] 错误: BAM文件为空")
|
||
return False
|
||
|
||
# 检查文件权限
|
||
if not os.access(scene_file, os.R_OK):
|
||
# print(f"[DEBUG] 错误: BAM文件不可读")
|
||
return False
|
||
|
||
# print(f"[DEBUG] BAM文件验证通过")
|
||
|
||
except Exception as e:
|
||
# print(f"[DEBUG] 清理状态时发生异常: {e}")
|
||
return False
|
||
|
||
if self.world.scene_manager.loadScene(scene_file):
|
||
# print(f"[DEBUG] 场景加载成功")
|
||
# 更新项目配置
|
||
project_config["scene_file"] = os.path.relpath(scene_file, project_path)
|
||
else:
|
||
# print(f"[DEBUG] 场景加载失败")
|
||
return False
|
||
else:
|
||
# print(f"[DEBUG] 场景文件不存在,跳过场景加载")
|
||
pass
|
||
|
||
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
|
||
|
||
# print(f"[DEBUG] ===== 项目打开成功: {project_path} =====")
|
||
print("项目加载成功!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
# print(f"[DEBUG] ===== 项目打开失败 =====")
|
||
print(f"加载项目时发生错误:{str(e)}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
return False
|
||
|
||
def openProjectForPath(self, project_path):
|
||
"""通过路径打开项目
|
||
|
||
Args:
|
||
project_path: 项目路径
|
||
|
||
Returns:
|
||
bool: 打开是否成功
|
||
"""
|
||
try:
|
||
if not project_path:
|
||
return False
|
||
# 检查是否是有效的项目文件夹
|
||
config_file = os.path.join(project_path, "project.json")
|
||
if not os.path.exists(config_file):
|
||
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
|
||
|
||
print(f"项目 '{project_path}' 加载成功!")
|
||
return True
|
||
|
||
except Exception as e:
|
||
error_msg = f"加载项目时发生错误:{str(e)}"
|
||
print(error_msg)
|
||
return False
|
||
|
||
def saveProject(self):
|
||
"""保存项目
|
||
|
||
Returns:
|
||
bool: 保存是否成功
|
||
"""
|
||
try:
|
||
# 检查是否有当前项目路径
|
||
if not self.current_project_path:
|
||
print("错误: 请先创建或打开一个项目!")
|
||
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
|
||
|
||
print("项目保存成功!")
|
||
return True
|
||
else:
|
||
print("错误: 保存场景失败!")
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(f"保存项目时发生错误:{str(e)}")
|
||
return False
|
||
|
||
# ==================== 项目打包功能 ====================
|
||
|
||
def buildPackage(self, build_dir):
|
||
"""打包项目为可执行文件 - 按照Panda3D官方标准方法
|
||
|
||
Args:
|
||
build_dir: 打包输出目录
|
||
|
||
Returns:
|
||
bool: 打包是否成功
|
||
"""
|
||
try:
|
||
# 检查是否有当前项目路径
|
||
if not self.current_project_path:
|
||
print("错误: 请先创建或打开一个项目!")
|
||
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):
|
||
print("错误: 请先保存场景!")
|
||
return False
|
||
|
||
if hasattr(self.world,'selection') and self.world.selection:
|
||
self.world.selection.clearSelection()
|
||
print("已取消场景中的物体选中状态")
|
||
|
||
if self.world.scene_manager.saveScene(scene_file, project_path):
|
||
print("场景保存成功")
|
||
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)
|
||
else:
|
||
print("错误: 保存场景失败!")
|
||
return False
|
||
|
||
if not build_dir:
|
||
print("错误: 请指定打包输出目录!")
|
||
return False
|
||
|
||
# 创建构建目录
|
||
build_dir = os.path.join(build_dir, "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)
|
||
|
||
if success:
|
||
# 根据操作系统创建对应的启动脚本文件
|
||
try:
|
||
import stat
|
||
# 检查操作系统类型
|
||
if os.name == 'nt': # Windows系统
|
||
run_bat_path = os.path.join(build_dir, "run.bat")
|
||
with open(run_bat_path, "w") as f:
|
||
f.write("@echo off\n")
|
||
f.write("python main.py %*\n")
|
||
else: # Unix-like系统 (Linux, macOS等)
|
||
run_sh_path = os.path.join(build_dir, "run.sh")
|
||
with open(run_sh_path, "w") as f:
|
||
f.write("#!/bin/bash\n")
|
||
f.write("python3.10 main.py \"$@\"\n")
|
||
|
||
# 为Unix-like系统添加执行权限
|
||
st = os.stat(run_sh_path)
|
||
os.chmod(run_sh_path, st.st_mode | stat.S_IEXEC)
|
||
except Exception as e:
|
||
print(f"创建启动脚本文件失败: {str(e)}")
|
||
|
||
print("打包完成!可执行文件在 build 目录中。")
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
except Exception as e:
|
||
print(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)
|
||
|
||
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','samples')
|
||
)
|
||
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_files = [
|
||
"script_system.py",
|
||
"InfoPanelManager.py",
|
||
"CustomMouseController.py"
|
||
]
|
||
|
||
source_core_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),"core")
|
||
|
||
core_dest = os.path.join(build_dir,"core")
|
||
|
||
if not os.path.exists(core_dest):
|
||
os.makedirs(core_dest)
|
||
|
||
for file_name in core_files:
|
||
source_file = os.path.join(source_core_dir,file_name)
|
||
|
||
if os.path.exists(source_file):
|
||
shutil.copy2(source_file,os.path.join(core_dest,file_name))
|
||
|
||
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:
|
||
self._updateResourcePaths(gui_info,project_path,build_dir)
|
||
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 _updateResourcePaths(self,gui_info,project_path,build_dir):
|
||
def normalize_resource_path(old_path):
|
||
if not old_path:
|
||
return old_path
|
||
|
||
if old_path.startswith(("http://","https://")):
|
||
return old_path
|
||
|
||
if os.path.isabs(old_path):
|
||
try:
|
||
rel_path = os.path.relpath(old_path,project_path)
|
||
return os.path.join("resources",os.path.basename(rel_path)).replace("\\","/")
|
||
except Exception:
|
||
return os.path.join("resources",os.path.basename(old_path)).replace("\\","/")
|
||
else:
|
||
return os.path.join("resources",os.path.basename(old_path)).replace("\\","/")
|
||
if "image_path" in gui_info and gui_info["image_path"]:
|
||
gui_info["image_path"] = normalize_resource_path(gui_info["image_path"])
|
||
if "video_path" in gui_info and gui_info["video_path"]:
|
||
gui_info["video_path"] = normalize_resource_path(gui_info["video_path"])
|
||
if "bg_image_path" in gui_info and gui_info["bg_image_path"]:
|
||
gui_info["bg_image_path"] = normalize_resource_path(gui_info["bg_image_path"])
|
||
|
||
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):
|
||
with open(config_file, "r", encoding="utf-8") as f:
|
||
project_config = json.load(f)
|
||
# 这里可以根据实际需要提取资源引用
|
||
|
||
except Exception as e:
|
||
print(f"从场景文件提取资源引用时出错: {str(e)}")
|
||
|
||
return referenced_files
|
||
|
||
# ==================== 辅助方法 ====================
|
||
|
||
def _clearCurrentScene(self):
|
||
"""清空当前场景"""
|
||
try:
|
||
if hasattr(self.world, 'scene_manager') and self.world.scene_manager:
|
||
self.world.scene_manager.clearScene()
|
||
print("✓ 当前场景已清空")
|
||
except Exception as e:
|
||
print(f"清空场景失败: {str(e)}")
|
||
|
||
def updateWindowTitle(self, parent_window, project_name):
|
||
"""更新窗口标题(保留方法以兼容旧代码)"""
|
||
# 这个方法现在不需要做任何事情,因为我们不再处理UI
|
||
pass
|
||
|
||
def _createAppFile(self, build_dir, project_name):
|
||
"""创建标准的应用程序入口文件"""
|
||
try:
|
||
app_content = f'''#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
{project_name} - Panda3D应用程序
|
||
自动生成的入口文件
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
|
||
# 添加当前目录到Python路径
|
||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
try:
|
||
from panda3d.core import loadPrcFileData
|
||
|
||
# 设置基本的Panda3D配置
|
||
loadPrcFileData("", """
|
||
window-title {project_name}
|
||
sync-video false
|
||
show-frame-rate-meter true
|
||
""")
|
||
|
||
# 导入主应用程序类
|
||
# 注意:这里需要根据实际情况修改导入路径
|
||
from main import MyWorld
|
||
|
||
# 创建并运行应用程序
|
||
app = MyWorld()
|
||
app.run()
|
||
|
||
except ImportError as e:
|
||
print(f"导入错误: {{e}}")
|
||
print("请确保已正确安装Panda3D")
|
||
sys.exit(1)
|
||
except Exception as e:
|
||
print(f"运行错误: {{e}}")
|
||
sys.exit(1)
|
||
'''
|
||
|
||
app_file = os.path.join(build_dir, "main.py")
|
||
with open(app_file, "w", encoding="utf-8") as f:
|
||
f.write(app_content)
|
||
print(f"✓ 应用程序入口文件已创建: {app_file}")
|
||
|
||
except Exception as e:
|
||
print(f"创建应用程序入口文件失败: {str(e)}")
|
||
|
||
def _createStandardSetupFile(self, build_dir, project_name):
|
||
"""创建标准的setup.py文件用于打包"""
|
||
try:
|
||
setup_content = f'''#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
{project_name} - Panda3D项目打包配置
|
||
"""
|
||
|
||
from setuptools import setup, find_packages
|
||
import sys
|
||
|
||
# 确保Python版本兼容性
|
||
if sys.version_info < (3, 6):
|
||
sys.exit("Python 3.6或更高版本是必需的")
|
||
|
||
setup(
|
||
name="{project_name}",
|
||
version="1.0.0",
|
||
description="基于Panda3D的3D应用程序",
|
||
author="",
|
||
author_email="",
|
||
url="",
|
||
|
||
# 包含所有Python包
|
||
packages=find_packages(),
|
||
|
||
# 包含非Python文件
|
||
include_package_data=True,
|
||
package_data={{
|
||
"": ["*.bam", "*.egg", "*.png", "*.jpg", "*.jpeg", "*.ogg", "*.wav", "*.mp3"]
|
||
}},
|
||
|
||
# 依赖项
|
||
install_requires=[
|
||
"panda3d>=1.10.13",
|
||
],
|
||
|
||
# Python版本要求
|
||
python_requires=">=3.6",
|
||
|
||
# 分类信息
|
||
classifiers=[
|
||
"Development Status :: 4 - Beta",
|
||
"Intended Audience :: Developers",
|
||
"License :: OSI Approved :: MIT License",
|
||
"Programming Language :: Python :: 3",
|
||
"Programming Language :: Python :: 3.6",
|
||
"Programming Language :: Python :: 3.7",
|
||
"Programming Language :: Python :: 3.8",
|
||
"Programming Language :: Python :: 3.9",
|
||
"Programming Language :: Python :: 3.10",
|
||
],
|
||
|
||
# 入口点
|
||
entry_points={{
|
||
"console_scripts": [
|
||
"{project_name}=main:main",
|
||
],
|
||
}},
|
||
)
|
||
'''
|
||
|
||
setup_file = os.path.join(build_dir, "setup.py")
|
||
with open(setup_file, "w", encoding="utf-8") as f:
|
||
f.write(setup_content)
|
||
print(f"✓ setup.py文件已创建: {setup_file}")
|
||
|
||
except Exception as e:
|
||
print(f"创建setup.py文件失败: {str(e)}")
|
||
|
||
def _executeStandardBuild(self, build_dir):
|
||
"""执行标准的Panda3D打包命令"""
|
||
try:
|
||
# 这里可以实现具体的打包逻辑
|
||
# 例如使用pdeploy或其他打包工具
|
||
|
||
print(f"✓ 项目文件已准备到: {build_dir}")
|
||
print("注意: 请使用Panda3D的pdeploy工具进行最终打包")
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"执行打包命令失败: {str(e)}")
|
||
return False |