建模程序 多个定时程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

295 lines
12 KiB

#!/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-all=auto_sliceing_operate', # 使用 collect-all 确保包含所有子模块和依赖
'--collect-all=download_batch_data', # 使用 collect-all 确保包含所有子模块和依赖
'--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')
# 添加 hook 文件路径(如果存在)
hook_path = current_dir
if os.path.exists(os.path.join(current_dir, 'hook-auto_sliceing_operate.py')):
cmd.append(f'--additional-hooks-dir={hook_path}')
# 添加工作目录设置和主脚本
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 <command> [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()