Browse Source

Refactor main.py to enable API call for updating slice status and enhance argument parsing. Update funcs.py for improved code readability and error handling in Blender execution.

master
dongchangxi 3 weeks ago
parent
commit
d43a50e14d
  1. 25
      script/factory_sliceing_v2/main.py
  2. 264
      script/factory_sliceing_v2/utils/funcs.py

25
script/factory_sliceing_v2/main.py

@ -3,6 +3,7 @@ import redis @@ -3,6 +3,7 @@ import redis
import oss2,time,sys
import requests
import argparse,json
from utils.funcs import requestApiToUpdateSliceStatus
# 将当前脚本所在目录添加到 Python 路径,以便导入 utils 模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
@ -50,14 +51,14 @@ def step1(versionId): @@ -50,14 +51,14 @@ def step1(versionId):
objCounts += 1
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 下载处理完成的obj文件数量: {objCounts}')
# requestApiToUpdateSliceStatus(versionId,objCounts)
requestApiToUpdateSliceStatus(versionId,objCounts)
# 读取 队列中一个数据出来
def main(work_dir=None):
global currentDir
# 如果指定了工作目录,使用指定的目录
if work_dir:
work_dir = os.path.abspath(work_dir)
@ -122,13 +123,13 @@ def testMain(): @@ -122,13 +123,13 @@ def testMain():
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 处理完成,res={res}')
if __name__ == '__main__':
testMain()
# parser = argparse.ArgumentParser(description='排版打印订单处理程序')
# parser.add_argument(
# '--work-dir',
# type=str,
# default=None,
# help='指定工作目录(磁盘路径),例如: D:/work 或 /Users/username/work。如果不指定,则使用脚本所在目录'
# )
# args = parser.parse_args()
# main(work_dir=args.work_dir)
#testMain()
parser = argparse.ArgumentParser(description='排版打印订单处理程序')
parser.add_argument(
'--work-dir',
type=str,
default=None,
help='指定工作目录(磁盘路径),例如: D:/work 或 /Users/username/work。如果不指定,则使用脚本所在目录'
)
args = parser.parse_args()
main(work_dir=args.work_dir)

264
script/factory_sliceing_v2/utils/funcs.py

@ -1,14 +1,18 @@ @@ -1,14 +1,18 @@
import requests,time
import os,platform
import requests
import time
import os
import platform
import shutil
import json
import shlex
import subprocess
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
from .changeFiles import changeObjFile, changeMtlFile
from .small_machine_transform import transform_save
ENV = 'prod'
url = 'https://mp.api.suwa3d.com'
if ENV == 'dev':
@ -17,11 +21,11 @@ elif ENV == 'prod': @@ -17,11 +21,11 @@ elif ENV == 'prod':
url = 'https://mp.api.suwa3d.com'
#根据打印ID 获取下载目录
# 根据打印ID 获取下载目录
def getDownloadDirByPrintId(printIds):
#调用接口获取下载的路径
# 调用接口获取下载的路径
api_url = f"{url}/api/printOrder/getInfoByPrintIds"
res = requests.post(api_url,json={"print_ids":printIds})
res = requests.post(api_url, json={"print_ids": printIds})
res = res.json()
print(f"根据打印ID 获取下载目录, res={res}")
if res["code"] == 1000:
@ -29,9 +33,10 @@ def getDownloadDirByPrintId(printIds): @@ -29,9 +33,10 @@ def getDownloadDirByPrintId(printIds):
else:
return False
#根据批次id,进行切片中的状态变更
def requestApiToUpdateSliceStatus(versionId,downloadCounts):
api_url = f"{url}/api/printTypeSettingOrder/updateBatchSliceing?batch_id={versionId}&download_counts="+str(downloadCounts)
# 根据批次id,进行切片中的状态变更
def requestApiToUpdateSliceStatus(versionId, downloadCounts):
api_url = f"{url}/api/printTypeSettingOrder/updateBatchSliceing?batch_id={versionId}&download_counts=" + str(downloadCounts)
log(f'发起状态变更请求url={api_url}, versionId={versionId}')
try:
# 添加超时参数,防止请求时间过长
@ -48,39 +53,40 @@ def requestApiToUpdateSliceStatus(versionId,downloadCounts): @@ -48,39 +53,40 @@ def requestApiToUpdateSliceStatus(versionId,downloadCounts):
log(f'状态变更请求异常, error={str(e)}')
return False
#判断是否上传了 JSON 文件
# 判断是否上传了 JSON 文件
def checkJsonFileExists(versionId):
log(f"检测文件和图片是否存在, versionId={versionId}")
jsonFilePath = f'batchPrint/{versionId}/{versionId}.json'
#判断oss 上是否存在
jsonFilePath = f'batchPrint/{versionId}/{versionId}.json'
# 判断oss 上是否存在
jpgFilePath = f'batchPrint/{versionId}/{versionId}.jpg'
if not ossClient().object_exists(jsonFilePath):
log(f'JSON文件不存在: {jsonFilePath}')
return False,False
return False, False
if not ossClient().object_exists(jpgFilePath):
log(f'JPG文件不存在: {jpgFilePath}')
return False,False
return False, False
log(f"文件和图片检测成功,存在, versionId={versionId}")
return jsonFilePath,jpgFilePath
return jsonFilePath, jpgFilePath
#检测本地文件是否存在
# 检测本地文件是否存在
def checkLocalFileExists(localFilePath):
if not os.path.exists(localFilePath):
return False
return True
#读取JSON文件内容
# 读取JSON文件内容
def readJsonFile(localFilePath):
with open(localFilePath, 'r', encoding='utf-8') as f:
jsonData = json.load(f)
return jsonData
#根据批次ID,获取批次信息
# 根据批次ID,获取批次信息
def getBatchInfo(versionId):
url1 = f"{url}/api/printTypeSettingOrder/getBatchInfoAndPrintMachineInfoByBatchId?batch_id={versionId}"
res = requests.get(url1)
@ -91,13 +97,14 @@ def getBatchInfo(versionId): @@ -91,13 +97,14 @@ def getBatchInfo(versionId):
else:
return False
#下载文件,读取文件内容,并且文件迁移至正确的目录里,不再放在临时目录里
def downloadJsonAndJpgFileAndMoveToCorrectDir(versionId,currentDir):
jsonFilePath,jpgFilePath = checkJsonFileExists(versionId)
# 下载文件,读取文件内容,并且文件迁移至正确的目录里,不再放在临时目录里
def downloadJsonAndJpgFileAndMoveToCorrectDir(versionId, currentDir):
jsonFilePath, jpgFilePath = checkJsonFileExists(versionId)
if jsonFilePath == False or jpgFilePath == False:
return False,False
#将文件下载到临时目录,待会要判断是否什么类型机型
return False, False
# 将文件下载到临时目录,待会要判断是否什么类型机型
tempDir = os.path.join(currentDir, 'batchPrint', 'temp', versionId)
if not os.path.exists(tempDir):
os.makedirs(tempDir)
@ -106,74 +113,73 @@ def downloadJsonAndJpgFileAndMoveToCorrectDir(versionId,currentDir): @@ -106,74 +113,73 @@ def downloadJsonAndJpgFileAndMoveToCorrectDir(versionId,currentDir):
ok = download_file_with_check(jsonFilePath, localFilePath)
if not ok:
log(f"JSON 文件下载失败或不完整, versionId={versionId}")
return False,False
#下载JPG文件
return False, False
# 下载JPG文件
localJpgFilePath = os.path.join(tempDir, f'{versionId}.jpg')
ok = download_file_with_check(jpgFilePath, localJpgFilePath)
if not ok:
log(f"JPG 文件下载失败或不完整, versionId={versionId}")
return False,False
#根据批次ID,获取批次信息和打印机信息
batchMachineInfo = getBatchInfo(versionId)
return False, False
# 根据批次ID,获取批次信息和打印机信息
batchMachineInfo = getBatchInfo(versionId)
if not batchMachineInfo:
log(f"获取批次信息和打印机信息失败, versionId={versionId}")
return False,False
return False, False
# "batch_info": batchInfo,
# "print_machine_info": printMachineInfo,
# "print_machine_info": printMachineInfo,
machineInfo = batchMachineInfo["print_machine_info"]
print(f"machineInfo={machineInfo['id']}")
dirNewName = ""
if str(machineInfo["machine_type"]) == '1':
dirNewName = os.path.join(currentDir, 'batchPrint', versionId + '_small_No'+str(machineInfo['id']))
dirNewName = os.path.join(currentDir, 'batchPrint', versionId + '_small_No' + str(machineInfo['id']))
else:
dirNewName = os.path.join(currentDir, 'batchPrint', versionId + '_big_No'+str(machineInfo['id']))
dirNewName = os.path.join(currentDir, 'batchPrint', versionId + '_big_No' + str(machineInfo['id']))
#判断目录是否存在,存在就删除
# 判断目录是否存在,存在就删除
if os.path.exists(dirNewName):
shutil.rmtree(dirNewName)
#创建目录
# 创建目录
os.makedirs(dirNewName)
#创建json子目录
# 创建json子目录
jsonSubDir = os.path.join(dirNewName, 'json')
if not os.path.exists(jsonSubDir):
os.makedirs(jsonSubDir)
#将数据移动过来
# 将数据移动过来
shutil.move(localFilePath, os.path.join(jsonSubDir, '3DPrintLayout.json'))
shutil.move(localJpgFilePath, os.path.join(jsonSubDir, f'{versionId}.jpg'))
#检测文件是否移动成功
# 检测文件是否移动成功
if not os.path.exists(os.path.join(jsonSubDir, '3DPrintLayout.json')):
log(f"JSON文件不存在, versionId={versionId}")
return False,False
return False, False
if not os.path.exists(os.path.join(jsonSubDir, f'{versionId}.jpg')):
log(f"JPG文件不存在, versionId={versionId}")
return False,False
return False, False
log(f"文件移动成功, versionId={versionId}")
#返回目录路径
return dirNewName,machineInfo
# 返回目录路径
return dirNewName, machineInfo
#整合json文件,读取对应的数据,返回数据结构
# 整合json文件,读取对应的数据,返回数据结构
def getJsonData(dirNewName):
jsonFilePath = os.path.join(dirNewName, 'json', '3DPrintLayout.json')
if not os.path.exists(jsonFilePath):
log(f"JSON文件不存在, dirNewName={dirNewName}")
return False
#读取JSON文件内容
# 读取JSON文件内容
jsonData = readJsonFile(jsonFilePath)
#读取models
# 读取models
models = jsonData.get('models', [])
if not models:
log(f"models不存在, dirNewName={dirNewName}")
return False
listData = []
#遍历models
# 遍历models
for model in models:
file_name = model.get('file_name', '')
#分割数据
# 分割数据
arrFileName = file_name.split('_')
orderId = arrFileName[0]
pid = arrFileName[1]
@ -181,14 +187,12 @@ def getJsonData(dirNewName): @@ -181,14 +187,12 @@ def getJsonData(dirNewName):
size = arrFileName[3]
counts = arrFileName[4].replace("x", "").replace(".obj", "")
#检测这些数据
# 检测这些数据
if not orderId or not pid or not printId or not size or not counts:
log(f"数据不完整, orderId={orderId}, pid={pid}, printId={printId}, size={size}, counts={counts}")
return False
#创建数据结构
# 创建数据结构
modelInfo = {
"orderId": orderId,
"printId": printId,
@ -198,27 +202,27 @@ def getJsonData(dirNewName): @@ -198,27 +202,27 @@ def getJsonData(dirNewName):
"file_name": file_name,
}
listData.append(modelInfo)
#检测数据长度
# 检测数据长度
if len(listData) == 0:
log(f"数据长度为0, dirNewName={dirNewName}")
return False
#返回数据结构
# 返回数据结构
return listData
# 读取 json 文件,获取 homo_matrix 数据,根据 file_name 获取 homo_matrix 数据
def getHomoMatrixByFileName(dirNewName,fileName):
def getHomoMatrixByFileName(dirNewName, fileName):
jsonFilePath = os.path.join(dirNewName, 'json', '3DPrintLayout.json')
if not os.path.exists(jsonFilePath):
log(f"JSON文件不存在, dirNewName={dirNewName}")
return False
#读取JSON文件内容
# 读取JSON文件内容
jsonData = readJsonFile(jsonFilePath)
if not jsonData:
log(f"读取JSON文件内容失败, dirNewName={dirNewName}")
return False
for model in jsonData["models"]:
log(f"jsonData={model['file_name']} == {fileName}")
if model["file_name"] == fileName:
@ -227,7 +231,7 @@ def getHomoMatrixByFileName(dirNewName,fileName): @@ -227,7 +231,7 @@ def getHomoMatrixByFileName(dirNewName,fileName):
return False
#处理单个数据项:下载文件、修改关联路径、转换数据(如果是小机台)
# 处理单个数据项:下载文件、修改关联路径、转换数据(如果是小机台)
def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine):
"""
处理单个数据项的函数用于多线程处理
@ -247,10 +251,10 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -247,10 +251,10 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
if str(tempv["print_order_id"]) == str(v["printId"]):
info = tempv
break
if not info:
return False, f"未找到匹配的下载路径信息, printId={v['printId']}"
filePath = info["path"]
pid = info["pid"]
orderId = info["order_id"]
@ -258,8 +262,8 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -258,8 +262,8 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
size = info["real_size"]
counts = info["quantity"]
fileName = v["file_name"]
#判断文件是否存在
# 判断文件是否存在
ossJpgFilePath = f"{filePath}/printId_{printId}Tex1.jpg"
if not checkFileExists(ossJpgFilePath):
ossJpgFilePath = f"{filePath}/{pid}Tex1.jpg"
@ -272,12 +276,13 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -272,12 +276,13 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
{"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:
#判断 mtl 和 jpg 文件是否存在,存在就不在下载了
downloadBeginTime = time.time()
# 判断 mtl 和 jpg 文件是否存在,存在就不在下载了
if "mtl" in objFiles["localPath"] or "jpg" in objFiles["localPath"]:
if os.path.exists(objFiles["localPath"]):
continue
@ -288,18 +293,20 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -288,18 +293,20 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
log(error_msg)
return False, error_msg
#下载成功之后要修改文件之间的关联路径
log(f"下载文件耗时: {time.time() - downloadBeginTime}秒 - {objFiles['localPath']}")
# 下载成功之后要修改文件之间的关联路径
if objFiles["localPath"].endswith(".obj"):
beginChangeTime = time.time()
objsLocal = objFiles["localPath"]
changeObjFile(objFiles["localPath"], f"{orderId}_{pid}.mtl")
log(f"修改obj关联耗时: {time.time() - beginChangeTime}秒 - {objFiles['localPath']}")
if objFiles["localPath"].endswith(".mtl"):
changeMtlFile(objFiles["localPath"], f"{orderId}_{pid}Tex1.jpg")
endTime = time.time()
log(f"下载文件和修改文件之间的关联路径 : 耗时{endTime - beginTime}秒 - {fileName}")
#如果是小机台,则要转换数据
# 如果是小机台,则要转换数据
if isSmallMachine:
timeBegin = time.time()
homo_matrix = getHomoMatrixByFileName(dirNewName, localObjName)
@ -308,27 +315,65 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -308,27 +315,65 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
log(error_msg)
return False, error_msg
#通过blender 调用执行 python 文件
# 通过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 字符串
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}"
# 构建命令参数列表(使用列表形式,避免 shell 转义问题)
cmd = [
blender_bin_path,
'--background',
'--python',
transform_script_path,
'--',
f'--objPathName={objsLocal}',
f'--trans={homo_matrix_json}'
]
log(f"执行 Blender 命令: {' '.join(cmd[:4])} ... (参数已隐藏)")
try:
# 使用 subprocess 执行命令,捕获输出和错误
# 在 Windows 上,Blender 输出可能是 UTF-8,需要指定编码并处理错误
result = subprocess.run(
cmd,
capture_output=True,
text=True,
encoding='utf-8', # 指定 UTF-8 编码
errors='replace', # 遇到无法解码的字符时替换为占位符,而不是抛出异常
timeout=300, # 5分钟超时
check=False # 不自动抛出异常,手动检查返回码
)
if result.returncode != 0:
error_output = result.stderr if result.stderr else result.stdout
error_msg = f"调用blender 执行 python 文件失败, error={result.returncode}, fileName={fileName}"
if error_output:
error_msg += f", 错误信息: {error_output[:500]}" # 限制错误信息长度
log(error_msg)
return False, error_msg
log(f"调用blender 执行 python 文件成功, fileName={fileName}")
if result.stdout:
log(f"Blender 输出: {result.stdout[:200]}") # 记录部分输出用于调试
except subprocess.TimeoutExpired:
error_msg = f"调用blender 执行超时(超过5分钟), fileName={fileName}"
log(error_msg)
return False, error_msg
except Exception as e:
error_msg = f"调用blender 执行时发生异常: {str(e)}, 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)}"
@ -336,13 +381,13 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine @@ -336,13 +381,13 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
return False, error_msg
#json文件进行下载对应的数据 和转换数据,传递目录路径
def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_workers=10):
# json文件进行下载对应的数据 和转换数据,传递目录路径
def downloadDataByOssAndTransformSave(dirNewName, isSmallMachine=False, max_workers=1):
listData = getJsonData(dirNewName)
if not listData:
log(f"获取数据失败, dirNewName={dirNewName}")
return False
#遍历数据
# 遍历数据
arrPrintId = []
arrPrintDataInfo = []
for modelInfo in listData:
@ -352,28 +397,28 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_worke @@ -352,28 +397,28 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_worke
"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)
#使用多线程并发处理数据
# 使用多线程并发处理数据
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
executor.submit(_process_single_item, v, arrDownloadPath, dirNewName, dirPath, isSmallMachine): v
for v in listData
}
# 收集结果
success_count = 0
fail_count = 0
@ -392,27 +437,48 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_worke @@ -392,27 +437,48 @@ def downloadDataByOssAndTransformSave(dirNewName,isSmallMachine=False, max_worke
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
def findBpyModule():
# 返回 Blender 可执行文件路径(macOS)
# 返回 Blender 可执行文件路径
blender_bin_path = '/Applications/Blender.app/Contents/MacOS/Blender'
# 判断当前是 windows 还是 macOS
if platform.system() == 'Windows':
blender_bin_path = 'C:\\Program Files\\Blender Foundation\\Blender 5.0\\blender.exe'
# Windows 上常见的 Blender 安装路径
possible_paths = [
'C:\\Program Files\\Blender Foundation\\Blender 5.0\\blender.exe',
'C:\\Program Files\\Blender Foundation\\Blender 4.4\\blender.exe',
'C:\\Program Files\\Blender Foundation\\Blender 4.3\\blender.exe',
'C:\\Program Files\\Blender Foundation\\Blender 4.2\\blender.exe',
'C:\\Program Files\\Blender Foundation\\Blender 4.1\\blender.exe',
'C:\\Program Files\\Blender Foundation\\Blender 4.0\\blender.exe',
]
# 查找存在的路径
for path in possible_paths:
if os.path.exists(path):
blender_bin_path = path
break
else:
# 如果都没找到,使用默认路径
blender_bin_path = 'C:\\Program Files\\Blender Foundation\\Blender 5.0\\blender.exe'
log(f"警告: 未找到 Blender 可执行文件,使用默认路径: {blender_bin_path}")
else:
blender_bin_path = '/Applications/Blender.app/Contents/MacOS/Blender'
# 检查路径是否存在
if not os.path.exists(blender_bin_path):
error_msg = f"Blender 可执行文件不存在: {blender_bin_path}"
log(error_msg)
raise FileNotFoundError(error_msg)
return blender_bin_path
return blender_bin_path

Loading…
Cancel
Save