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 10 months 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