#!/usr/bin/env python # -*- coding: utf-8 -*- """ 打包脚本:将 factory_sliceing 项目打包成 exe 文件 使用方法: python build_exe.py """ import os import sys import subprocess import shutil def build_exe(): """使用 PyInstaller 打包项目""" # 获取当前脚本所在目录(factory_sliceing 目录) current_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(current_dir) # make2 目录 # 切换到 factory_sliceing 目录 os.chdir(current_dir) # PyInstaller 命令参数 main_script = os.path.join(current_dir, 'main.py') # 构建 PyInstaller 命令 cmd = [ 'pyinstaller', '--name=factory_sliceing', # 生成的 exe 名称 '--onefile', # 打包成单个 exe 文件 '--console', # 显示控制台窗口(因为需要命令行参数) '--clean', # 清理临时文件 '--noconfirm', # 不询问确认 # 添加隐藏导入(如果 PyInstaller 无法自动检测) '--hidden-import=auto_sliceing_operate', '--hidden-import=auto_sliceing_operate.main_begin_sliceing', '--hidden-import=auto_sliceing_operate.main_download_zip', '--hidden-import=download_batch_data', '--hidden-import=download_batch_data.main_download_batch_data_and_trans', # 收集所有子模块,确保 PyInstaller 能找到所有模块 '--collect-submodules=auto_sliceing_operate', '--collect-submodules=download_batch_data', '--hidden-import=auto_sliceing_operate.utils', '--hidden-import=auto_sliceing_operate.utils.exe_operate', '--hidden-import=auto_sliceing_operate.utils.click_soft_button', '--hidden-import=auto_sliceing_operate.utils.import_all_file', '--hidden-import=auto_sliceing_operate.utils.miniIo', '--hidden-import=auto_sliceing_operate.utils.oss_redis', '--hidden-import=auto_sliceing_operate.utils.request', '--hidden-import=auto_sliceing_operate.utils.logs', '--hidden-import=download_batch_data.utils', '--hidden-import=download_batch_data.utils.funcs', '--hidden-import=download_batch_data.utils.small_machine_transform', '--hidden-import=download_batch_data.utils.oss_redis', '--hidden-import=download_batch_data.utils.oss_func', '--hidden-import=download_batch_data.utils.changeFiles', '--hidden-import=download_batch_data.utils.logs', '--hidden-import=utils', '--hidden-import=utils.config', # UI 自动化相关模块 '--hidden-import=uiautomation', # Windows API 相关模块(pywin32) # 注意:pywin32 需要特殊处理,使用 collect-all 确保包含所有 DLL 和模块 '--hidden-import=win32gui', '--hidden-import=win32con', '--hidden-import=win32api', '--hidden-import=win32process', '--hidden-import=win32timezone', '--hidden-import=win32clipboard', '--hidden-import=pywintypes', '--collect-all=pywin32', # 收集 pywin32 的所有模块和 DLL 文件 # 其他可能需要的模块 '--hidden-import=psutil', # 进程管理(可选,但有 try-except) # NumPy 相关模块(用于 small_machine_transform.py) '--hidden-import=numpy', '--hidden-import=numpy.core', '--hidden-import=numpy.core._methods', '--hidden-import=numpy.lib', '--hidden-import=numpy.lib.format', '--collect-all=numpy', # 收集 numpy 的所有子模块和依赖 # OSS2 客户端相关模块 '--hidden-import=oss2', '--hidden-import=oss2.models', '--hidden-import=oss2.exceptions', '--collect-all=oss2', # 收集 oss2 的所有子模块和依赖 # Redis 客户端相关模块 '--hidden-import=redis', '--hidden-import=redis.connection', '--hidden-import=redis.client', '--collect-all=redis', # 收集 redis 的所有子模块和依赖 # Requests 相关模块 '--hidden-import=requests', '--hidden-import=requests.adapters', '--hidden-import=urllib3', '--collect-all=requests', # 收集 requests 的所有子模块和依赖 # TOML 配置文件解析模块 '--hidden-import=toml', # 如果 Python < 3.11 需要 toml 包 '--hidden-import=tomllib', # Python 3.11+ 内置模块 # MinIO 客户端相关模块 '--hidden-import=minio', '--hidden-import=minio.error', '--hidden-import=minio.api', '--hidden-import=minio.commonconfig', '--hidden-import=minio.credentials', '--hidden-import=minio.deleteobjects', '--hidden-import=minio.helpers', '--hidden-import=minio.select', '--hidden-import=minio.signer', '--hidden-import=minio.time', '--collect-all=minio', # 收集 minio 的所有子模块和依赖 # 收集 uiautomation 的所有子模块(确保完整打包) '--collect-all=uiautomation', # 添加数据文件 # small_machine_transform.py 需要作为数据文件包含,因为它会被 Blender 作为外部脚本调用 # 注意:PyInstaller 会将数据文件放在 _MEIPASS 目录下 ] # 添加 small_machine_transform.py 数据文件 small_transform_file = os.path.join(current_dir, "download_batch_data", "utils", "small_machine_transform.py") if os.path.exists(small_transform_file): # Windows 使用分号,Linux/Mac 使用冒号 path_sep = ';' if sys.platform == 'win32' else ':' # 添加到 utils/ 目录(get_resource_path('utils/small_machine_transform.py') 会查找这里) cmd.append(f'--add-data={small_transform_file}{path_sep}utils') # 也添加到 download_batch_data/utils/ 目录(备用路径) cmd.append(f'--add-data={small_transform_file}{path_sep}download_batch_data/utils') # 添加工作目录设置和主脚本 cmd.extend([ f'--workpath={os.path.join(current_dir, "build")}', f'--distpath={os.path.join(current_dir, "dist")}', f'--specpath={os.path.join(current_dir, "build")}', main_script ]) print("=" * 60) print("开始打包 factory_sliceing 项目...") print("=" * 60) print(f"工作目录: {current_dir}") print(f"主脚本: {main_script}") print() try: # 检查 PyInstaller 是否安装 result = subprocess.run(['pyinstaller', '--version'], capture_output=True, text=True) if result.returncode != 0: print("错误: PyInstaller 未安装") print("请运行: pip install pyinstaller") return False print(f"PyInstaller 版本: {result.stdout.strip()}") print() # 执行打包命令 print("执行打包命令...") print(" ".join(cmd)) print() result = subprocess.run(cmd, check=True) if result.returncode == 0: print() print("=" * 60) print("打包成功!") print("=" * 60) exe_path = os.path.join(current_dir, 'dist', 'factory_sliceing.exe') if os.path.exists(exe_path): file_size = os.path.getsize(exe_path) / (1024 * 1024) # MB print(f"生成的 exe 文件: {exe_path}") print(f"文件大小: {file_size:.2f} MB") print() # 复制配置文件到 dist 目录(如果存在) config_file = os.path.join(current_dir, 'config.toml') dist_config = os.path.join(current_dir, 'dist', 'config.toml') if os.path.exists(config_file): try: shutil.copy2(config_file, dist_config) print(f"配置文件已复制到: {dist_config}") except Exception as e: print(f"警告: 复制配置文件失败: {e}") print(f"请手动将 config.toml 复制到 exe 同级目录: {os.path.dirname(exe_path)}") else: # 如果 config.toml 不存在,尝试复制示例文件 example_config = os.path.join(current_dir, 'config.example.toml') if os.path.exists(example_config): try: shutil.copy2(example_config, dist_config) print(f"配置文件示例已复制到: {dist_config}") print("请根据实际情况修改配置文件") except Exception as e: print(f"警告: 复制配置文件示例失败: {e}") else: print("提示: 请将 config.toml 放在 exe 同级目录") print() print("使用方法:") print(" factory_sliceing.exe [work_dir]") print(" 可用命令:") print(" - batch_download: 批量下载数据并转换") print(" - begin_sliceing: 开始切片处理") print(" - download_zip: 下载切片文件") print() print(" 示例:") print(" factory_sliceing.exe batch_download D:/work") print(" factory_sliceing.exe begin_sliceing") print(" factory_sliceing.exe download_zip") print() print("注意:") print(" - 配置文件 config.toml 需要放在 exe 同级目录") print(" - work_dir 参数可选,如果未提供将从配置文件读取默认值") return True else: print("打包失败!") return False except subprocess.CalledProcessError as e: print(f"打包过程中出现错误: {e}") return False except FileNotFoundError: print("错误: 找不到 PyInstaller") print("请先安装: pip install pyinstaller") return False except Exception as e: print(f"发生未知错误: {e}") return False def clean_build_files(): """清理构建产生的临时文件""" current_dir = os.path.dirname(os.path.abspath(__file__)) dirs_to_remove = [ os.path.join(current_dir, 'build'), os.path.join(current_dir, '__pycache__'), ] files_to_remove = [ os.path.join(current_dir, 'factory_sliceing.spec'), ] print("清理临时文件...") for dir_path in dirs_to_remove: if os.path.exists(dir_path): try: shutil.rmtree(dir_path) print(f"已删除: {dir_path}") except Exception as e: print(f"删除 {dir_path} 失败: {e}") for file_path in files_to_remove: if os.path.exists(file_path): try: os.remove(file_path) print(f"已删除: {file_path}") except Exception as e: print(f"删除 {file_path} 失败: {e}") if __name__ == '__main__': import argparse parser = argparse.ArgumentParser(description='打包 factory_sliceing 项目为 exe') parser.add_argument('--clean', action='store_true', help='打包后清理临时文件') parser.add_argument('--clean-only', action='store_true', help='仅清理临时文件,不打包') args = parser.parse_args() if args.clean_only: clean_build_files() else: success = build_exe() if success and args.clean: print() clean_build_files()