diff --git a/script/factory_sliceing_v2/build_exe.bat b/script/factory_sliceing_v2/build_exe.bat new file mode 100644 index 0000000..bb6a397 --- /dev/null +++ b/script/factory_sliceing_v2/build_exe.bat @@ -0,0 +1,57 @@ +@echo off +chcp 65001 >nul +echo ======================================== +echo 开始打包 factory_sliceing 为 Windows EXE +echo ======================================== +echo. + +REM 检查 Python 是否安装 +python --version >nul 2>&1 +if errorlevel 1 ( + echo [错误] 未找到 Python,请先安装 Python + pause + exit /b 1 +) + +echo [1/4] 检查并安装依赖包... +python -m pip install --upgrade pip +python -m pip install redis>=4.0.0 oss2>=2.17.0 requests>=2.28.0 pyinstaller>=5.0.0 + +if errorlevel 1 ( + echo [错误] 依赖包安装失败 + pause + exit /b 1 +) + +echo. +echo [2/4] 清理之前的构建文件... +if exist build rmdir /s /q build +if exist dist rmdir /s /q dist +if exist __pycache__ rmdir /s /q __pycache__ +for /d /r . %%d in (__pycache__) do @if exist "%%d" rmdir /s /q "%%d" + +echo. +echo [3/4] 使用 PyInstaller 打包... +pyinstaller build_exe.spec --clean --noconfirm + +if errorlevel 1 ( + echo [错误] 打包失败 + pause + exit /b 1 +) + +echo. +echo [4/4] 打包完成! +echo. +echo ======================================== +echo 打包结果: +echo ======================================== +echo EXE 文件位置: dist\factory_sliceing.exe +echo. +echo 使用说明: +echo 1. 将 dist\factory_sliceing.exe 复制到目标 Windows 机器 +echo 2. 在命令行中运行: factory_sliceing.exe --work-dir D:\work +echo. +echo ======================================== +pause + diff --git a/script/factory_sliceing_v2/build_exe.py b/script/factory_sliceing_v2/build_exe.py new file mode 100644 index 0000000..19df7cb --- /dev/null +++ b/script/factory_sliceing_v2/build_exe.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +打包脚本 - 将 main.py 打包成 Windows EXE 文件 +使用方法: python build_exe.py +""" + +import os +import sys +import subprocess +import shutil + +def check_python_version(): + """检查 Python 版本""" + if sys.version_info < (3, 7): + print("[错误] 需要 Python 3.7 或更高版本") + return False + print(f"[信息] Python 版本: {sys.version}") + return True + +def install_dependencies(): + """安装依赖包""" + print("\n[1/4] 检查并安装依赖包...") + dependencies = [ + 'redis>=4.0.0', + 'oss2>=2.17.0', + 'requests>=2.28.0', + 'pyinstaller>=5.0.0', + ] + + for dep in dependencies: + print(f" 安装 {dep}...") + result = subprocess.run( + [sys.executable, '-m', 'pip', 'install', '--upgrade', dep], + capture_output=True, + text=True + ) + if result.returncode != 0: + print(f"[错误] 安装 {dep} 失败: {result.stderr}") + return False + + print("[成功] 所有依赖包已安装") + return True + +def clean_build_files(): + """清理之前的构建文件""" + print("\n[2/4] 清理之前的构建文件...") + + dirs_to_remove = ['build', 'dist'] + for dir_name in dirs_to_remove: + if os.path.exists(dir_name): + shutil.rmtree(dir_name) + print(f" 已删除: {dir_name}") + + # 清理 __pycache__ 目录 + for root, dirs, files in os.walk('.'): + if '__pycache__' in dirs: + pycache_path = os.path.join(root, '__pycache__') + shutil.rmtree(pycache_path) + print(f" 已删除: {pycache_path}") + + print("[成功] 清理完成") + return True + +def build_exe(): + """使用 PyInstaller 打包""" + print("\n[3/4] 使用 PyInstaller 打包...") + + spec_file = 'build_exe.spec' + if not os.path.exists(spec_file): + print(f"[错误] 未找到配置文件: {spec_file}") + return False + + result = subprocess.run( + [sys.executable, '-m', 'PyInstaller', spec_file, '--clean', '--noconfirm'], + capture_output=True, + text=True + ) + + if result.returncode != 0: + print(f"[错误] 打包失败:") + print(result.stderr) + return False + + print("[成功] 打包完成") + return True + +def show_result(): + """显示打包结果""" + print("\n" + "="*50) + print("打包结果:") + print("="*50) + + exe_path = os.path.join('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") + else: + print(f"✗ 未找到 EXE 文件: {exe_path}") + + print("\n使用说明:") + print(" 1. 将 dist/factory_sliceing.exe 复制到目标 Windows 机器") + print(" 2. 在命令行中运行: factory_sliceing.exe --work-dir D:\\work") + print("="*50) + +def main(): + """主函数""" + print("="*50) + print("开始打包 factory_sliceing 为 Windows EXE") + print("="*50) + + # 切换到脚本所在目录 + script_dir = os.path.dirname(os.path.abspath(__file__)) + os.chdir(script_dir) + print(f"[信息] 工作目录: {script_dir}") + + # 检查 Python 版本 + if not check_python_version(): + return 1 + + # 安装依赖 + if not install_dependencies(): + return 1 + + # 清理构建文件 + if not clean_build_files(): + return 1 + + # 打包 + if not build_exe(): + return 1 + + # 显示结果 + show_result() + + return 0 + +if __name__ == '__main__': + try: + sys.exit(main()) + except KeyboardInterrupt: + print("\n\n[中断] 用户取消操作") + sys.exit(1) + except Exception as e: + print(f"\n[错误] 发生异常: {str(e)}") + import traceback + traceback.print_exc() + sys.exit(1) + diff --git a/script/factory_sliceing_v2/build_exe.spec b/script/factory_sliceing_v2/build_exe.spec new file mode 100644 index 0000000..5c98a77 --- /dev/null +++ b/script/factory_sliceing_v2/build_exe.spec @@ -0,0 +1,55 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[ + 'redis', + 'oss2', + 'requests', + 'utils.oss_redis', + 'utils.funcs', + 'utils.logs', + 'utils.oss_func', + 'utils.changeFiles', + 'utils.small_machine_transform', + ], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False, +) + +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='factory_sliceing', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, # 显示控制台窗口 + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=None, # 可以指定图标文件路径,例如: 'icon.ico' +) + diff --git a/script/factory_sliceing_v2/main.py b/script/factory_sliceing_v2/main.py index 1cd719c..c49c1e0 100644 --- a/script/factory_sliceing_v2/main.py +++ b/script/factory_sliceing_v2/main.py @@ -116,7 +116,7 @@ def main(work_dir=None): def testMain(): global currentDir currentDir = "/Users/dcx/code/make2/script/factory_sliceing_v2/tempData" - versionId = '10153' #'10153 10158' + versionId = '10158' #'10153 10158' print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 正在处理版次ID={versionId}') res = step1(versionId) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 处理完成,res={res}') diff --git a/script/factory_sliceing_v2/requirements.txt b/script/factory_sliceing_v2/requirements.txt new file mode 100644 index 0000000..05c2252 --- /dev/null +++ b/script/factory_sliceing_v2/requirements.txt @@ -0,0 +1,5 @@ +redis>=4.0.0 +oss2>=2.17.0 +requests>=2.28.0 +pyinstaller>=5.0.0 + diff --git a/script/factory_sliceing_v2/utils/funcs.py b/script/factory_sliceing_v2/utils/funcs.py index 230d0b5..686b129 100644 --- a/script/factory_sliceing_v2/utils/funcs.py +++ b/script/factory_sliceing_v2/utils/funcs.py @@ -3,6 +3,7 @@ import os,platform import shutil import json import shlex +from concurrent.futures import ThreadPoolExecutor, as_completed from .oss_redis import ossClient from .logs import log from .oss_func import download_file_with_check, checkFileExists @@ -226,42 +227,29 @@ def getHomoMatrixByFileName(dirNewName,fileName): return False -#json文件进行下载对应的数据 和转换数据,传递目录路径 -def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False): - listData = getJsonData(dirNewName) - if not listData: - log(f"获取数据失败, dirNewName={dirNewName}") - return False - #遍历数据 - arrPrintId = [] - arrPrintDataInfo = [] - for modelInfo in listData: - arrPrintId.append(modelInfo.get('printId')) - arrPrintDataInfo.append({ - "printId": modelInfo.get('printId'), - "file_name": modelInfo.get('file_name'), - }) - - #调用接口获取下载的路径 - arrDownloadPath = getDownloadDirByPrintId(arrPrintId) - if not arrDownloadPath: - log(f"获取下载路径失败, arrPrintId={arrPrintId}") - return False - - dirPath = os.path.join(dirNewName, 'data') - if not os.path.exists(dirPath): - os.makedirs(dirPath) - - #遍历数据 - for v in listData: +#处理单个数据项:下载文件、修改关联路径、转换数据(如果是小机台) +def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine): + """ + 处理单个数据项的函数,用于多线程处理 + 参数: + v: 单个数据项 + arrDownloadPath: 下载路径数组 + dirNewName: 目录名称 + dirPath: 数据目录路径 + isSmallMachine: 是否是小机台 + 返回: + (success: bool, error_msg: str) + """ + try: + # 查找匹配的下载路径信息 info = {} for tempv in arrDownloadPath: - #print(f"tempv={tempv['print_order_id']} == {v['printId']}") if str(tempv["print_order_id"]) == str(v["printId"]): info = tempv break - #print(f"info={info}") + if not info: + return False, f"未找到匹配的下载路径信息, printId={v['printId']}" filePath = info["path"] pid = info["pid"] @@ -284,20 +272,21 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False): {"ossPath": f"{filePath}/{pid}.obj", "localPath": os.path.join(dirPath, localObjName)}, {"ossPath": f"{filePath}/{pid}.mtl", "localPath": os.path.join(dirPath, loaclMtlName)}, ] + #遍历下载文件 beginTime = time.time() objsLocal = "" for objFiles in arrDownloadFiles: - if "F" in fileName: - #判断 mtl 和 jpg 文件是否存在,存在就不在下载了 - if "mtl" in objFiles["localPath"] or "jpg" in objFiles["localPath"]: - if os.path.exists(objFiles["localPath"]): - continue + #判断 mtl 和 jpg 文件是否存在,存在就不在下载了 + if "mtl" in objFiles["localPath"] or "jpg" in objFiles["localPath"]: + if os.path.exists(objFiles["localPath"]): + continue downloadOk = download_file_with_check(objFiles["ossPath"], objFiles["localPath"]) if not downloadOk: - log(f"下载文件失败, ossPath={objFiles["ossPath"]}, localPath={objFiles["localPath"]}") - return False + error_msg = f"下载文件失败, ossPath={objFiles['ossPath']}, localPath={objFiles['localPath']}" + log(error_msg) + return False, error_msg #下载成功之后要修改文件之间的关联路径 if objFiles["localPath"].endswith(".obj"): @@ -306,42 +295,112 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False): if objFiles["localPath"].endswith(".mtl"): changeMtlFile(objFiles["localPath"], f"{orderId}_{pid}Tex1.jpg") - + endTime = time.time() - log(f"下载文件和修改文件之间的关联路径 : 耗时{endTime - beginTime}秒") + log(f"下载文件和修改文件之间的关联路径 : 耗时{endTime - beginTime}秒 - {fileName}") #如果是小机台,则要转换数据 - if not isSmallMachine: - continue - - timeBegin = time.time() - homo_matrix = getHomoMatrixByFileName(dirNewName, localObjName) - if not homo_matrix: - log(f"获取homo_matrix失败, dirNewName={dirNewName}, objsLocal={objsLocal}") - return False - # transform_save(objFiles["localPath"], homo_matrix) - - #通过blender 调用执行 python 文件 - blender_bin_path = findBpyModule() - # 获取 small_machine_transform.py 的绝对路径 - script_dir = os.path.dirname(os.path.abspath(__file__)) - transform_script_path = os.path.join(script_dir, 'small_machine_transform.py') - # 将 homo_matrix 转换为 JSON 字符串,并对路径进行转义 - homo_matrix_json = json.dumps(homo_matrix) - # 使用 shlex.quote 来安全地转义路径和参数 - transform_script_path_quoted = shlex.quote(transform_script_path) - objsLocal_quoted = shlex.quote(objsLocal) - homo_matrix_quoted = shlex.quote(homo_matrix_json) - error = os.system(f"{blender_bin_path} -b -P {transform_script_path_quoted} -- --objPathName={objsLocal_quoted} --trans={homo_matrix_quoted}") - if error != 0: - log(f"调用blender 执行 python 文件失败, error={error}") - return False - log(f"调用blender 执行 python 文件成功, error={error}") + if isSmallMachine: + timeBegin = time.time() + homo_matrix = getHomoMatrixByFileName(dirNewName, localObjName) + if not homo_matrix: + error_msg = f"获取homo_matrix失败, dirNewName={dirNewName}, objsLocal={objsLocal}" + log(error_msg) + return False, error_msg + + #通过blender 调用执行 python 文件 + blender_bin_path = findBpyModule() + # 获取 small_machine_transform.py 的绝对路径 + script_dir = os.path.dirname(os.path.abspath(__file__)) + transform_script_path = os.path.join(script_dir, 'small_machine_transform.py') + # 将 homo_matrix 转换为 JSON 字符串,并对路径进行转义 + homo_matrix_json = json.dumps(homo_matrix) + # 使用 shlex.quote 来安全地转义路径和参数 + transform_script_path_quoted = shlex.quote(transform_script_path) + objsLocal_quoted = shlex.quote(objsLocal) + homo_matrix_quoted = shlex.quote(homo_matrix_json) + error = os.system(f"{blender_bin_path} -b -P {transform_script_path_quoted} -- --objPathName={objsLocal_quoted} --trans={homo_matrix_quoted}") + if error != 0: + error_msg = f"调用blender 执行 python 文件失败, error={error}, fileName={fileName}" + log(error_msg) + return False, error_msg + log(f"调用blender 执行 python 文件成功, error={error}, fileName={fileName}") + + timeEnd = time.time() + log(f"转换数据时间: 耗时{timeEnd - timeBegin}秒 - {objsLocal}") + + return True, "" + except Exception as e: + error_msg = f"处理数据项时发生异常, fileName={v.get('file_name', 'unknown')}, error={str(e)}" + log(error_msg) + return False, error_msg + + +#json文件进行下载对应的数据 和转换数据,传递目录路径 +def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_workers=10): + listData = getJsonData(dirNewName) + if not listData: + log(f"获取数据失败, dirNewName={dirNewName}") + return False + #遍历数据 + arrPrintId = [] + arrPrintDataInfo = [] + for modelInfo in listData: + arrPrintId.append(modelInfo.get('printId')) + arrPrintDataInfo.append({ + "printId": modelInfo.get('printId'), + "file_name": modelInfo.get('file_name'), + }) - timeEnd = time.time() - log(f"转换数据时间: 耗时{timeEnd - timeBegin}秒 - {objsLocal}") + #调用接口获取下载的路径 + arrDownloadPath = getDownloadDirByPrintId(arrPrintId) + if not arrDownloadPath: + log(f"获取下载路径失败, arrPrintId={arrPrintId}") + return False + dirPath = os.path.join(dirNewName, 'data') + if not os.path.exists(dirPath): + os.makedirs(dirPath) + #使用多线程并发处理数据 + log(f"开始多线程处理数据, 共{len(listData)}个项目, 线程数={max_workers}") + beginTime = time.time() + + # 使用线程池并发处理 + with ThreadPoolExecutor(max_workers=max_workers) as executor: + # 提交所有任务 + future_to_item = { + executor.submit(_process_single_item, v, arrDownloadPath, dirNewName, dirPath, isSmallMachine): v + for v in listData + } + + # 收集结果 + success_count = 0 + fail_count = 0 + for future in as_completed(future_to_item): + v = future_to_item[future] + try: + success, error_msg = future.result() + if success: + success_count += 1 + log(f"处理成功: {v.get('file_name', 'unknown')} ({success_count}/{len(listData)})") + else: + fail_count += 1 + log(f"处理失败: {v.get('file_name', 'unknown')}, 错误: {error_msg} ({fail_count}/{len(listData)})") + # 如果任何一个任务失败,记录错误但继续处理其他任务 + except Exception as e: + fail_count += 1 + error_msg = f"处理数据项时发生异常: {str(e)}" + log(f"处理异常: {v.get('file_name', 'unknown')}, 错误: {error_msg} ({fail_count}/{len(listData)})") + + endTime = time.time() + log(f"多线程处理完成, 总耗时{endTime - beginTime}秒, 成功:{success_count}, 失败:{fail_count}, 总计:{len(listData)}") + + # 如果有任何失败,返回 False + if fail_count > 0: + log(f"部分任务处理失败,共{fail_count}个失败") + return False + return True @@ -350,7 +409,7 @@ def findBpyModule(): blender_bin_path = '/Applications/Blender.app/Contents/MacOS/Blender' # 判断当前是 windows 还是 macOS if platform.system() == 'Windows': - blender_bin_path = 'C:\\Program Files\\Blender Foundation\\Blender 4.4\\blender.exe' + blender_bin_path = 'C:\\Program Files\\Blender Foundation\\Blender 5.0\\blender.exe' else: blender_bin_path = '/Applications/Blender.app/Contents/MacOS/Blender'