first commit
This commit is contained in:
commit
fb98172573
287 files changed
+43090
No files matched your search
@@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
blender/3.6
|
||||
@@ -0,0 +1,324 @@
|
||||
from ctypes import util
|
||||
import os, oss2, time, redis, requests, shutil, sys, subprocess, json, platform
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from retrying import retry
|
||||
import atexit,platform
|
||||
import get_preview_image
|
||||
# if platform.system() == 'Windows':
|
||||
# sys.path.append('libs\\')
|
||||
sys.path.append('/home/acprint/code/libs/')
|
||||
import common
|
||||
|
||||
|
||||
#创建文本文件
|
||||
def creatDoingLog(order_id):
|
||||
#在指定路径创建一条 order_id.txt 文本
|
||||
file_path = f"doingLog/{order_id}.txt"
|
||||
if os.path.exists(file_path):
|
||||
return
|
||||
#创建
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write("1")
|
||||
|
||||
#移除指定的文本文件
|
||||
def removeDoingLog(order_id):
|
||||
file_path = f"doingLog/{order_id}.txt"
|
||||
if not os.path.exists(file_path):
|
||||
return
|
||||
os.remove(file_path)
|
||||
|
||||
#遍历文件夹,返回指定的order_id
|
||||
def get_order_id_by_txt():
|
||||
#遍历文件夹
|
||||
for file in os.listdir("doingLog"):
|
||||
if file != "":
|
||||
arrFile = file.split(".")
|
||||
return arrFile[0]
|
||||
return None
|
||||
|
||||
def find_blender_bin_path():
|
||||
if platform.system() == 'Linux': return '/home/acprint/code/blender/blender'
|
||||
base_path = 'C:\\Program Files\\Blender Foundation\\'
|
||||
if os.path.exists(base_path):
|
||||
for dir in os.listdir(base_path):
|
||||
if dir.startswith('Blender'):
|
||||
blender_bin_path = base_path + dir + '\\blender.exe'
|
||||
return f'"{blender_bin_path}"'
|
||||
else:
|
||||
print('未找到blender安装目录')
|
||||
exit(1)
|
||||
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=3000)
|
||||
def down_obj_fromoss(pid, print_type=1, order_id=None,download_flag="print_build"):
|
||||
# print_type:// 打印状态 1:正常打印 2:重打 3:加打,4: 样品
|
||||
print('开始下载obj文件...' , pid)
|
||||
|
||||
if not order_id is None:
|
||||
path = os.path.join(workdir, 'print', f'{pid}_{order_id}')
|
||||
else:
|
||||
path = os.path.join(workdir, 'print', pid)
|
||||
if not os.path.exists(path): os.makedirs(path)
|
||||
|
||||
# 根据前缀获取文件列表
|
||||
prefix = f'objs/print/{pid}/'
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
find = False
|
||||
findTex1Jpg = False
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'{pid}.obj'):
|
||||
find = True
|
||||
|
||||
if download_flag == "print_build":
|
||||
if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
|
||||
if not find:
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'.obj'):
|
||||
find = True
|
||||
|
||||
# if download_flag == "print_build":
|
||||
# if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
# continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
break
|
||||
|
||||
if find:
|
||||
os.rename(localfile, os.path.join(path,f"{pid}.obj"))
|
||||
#重命名文件
|
||||
|
||||
#判断是否有Tex1.jpg
|
||||
if not os.path.exists(os.path.join(path,f"{pid}Tex1.jpg")):
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'{pid}.jpg'):
|
||||
findTex1Jpg = True
|
||||
|
||||
# if download_flag == "print_build":
|
||||
# if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
# continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
break
|
||||
|
||||
if findTex1Jpg:
|
||||
os.rename(localfile, os.path.join(path,f"{pid}Tex1.jpg"))
|
||||
#重命名文件
|
||||
|
||||
# for file in filelist:
|
||||
# filename = file.key.split('/')[-1]
|
||||
# if filename == '': continue
|
||||
# if filename.endswith(f'{pid}.obj'):
|
||||
# find = True
|
||||
# if filename.endswith('.obj'):
|
||||
# print('找到其他obj文件,采用这个文件来生成需要的尺寸', file)
|
||||
# shutil.copy(os.path.join(path, file), os.path.join(path, f'{pid}.obj'))
|
||||
# find = True
|
||||
# break
|
||||
if not find:
|
||||
print('找不到obj文件,跳过')
|
||||
common.notify(f"{pid}-构建打印文件,找不到obj文件,异常,跳过该任务处理")
|
||||
# sys.exit(1)
|
||||
os.system(f'python auto_convert3d.py')
|
||||
return
|
||||
|
||||
# print('下载完成后静默10秒,等待文件写入完成')
|
||||
# time.sleep(10)
|
||||
#根据pid获取orderId
|
||||
def getPidFromOrderId(orderId):
|
||||
getPidFromOrderId_url = 'https://mp.api.suwa3d.com/api/printOrder/info'
|
||||
print(f'{getPidFromOrderId_url}?id={orderId}')
|
||||
res = requests.get(f'{getPidFromOrderId_url}?id={orderId}')
|
||||
resCode = res.json()['code']
|
||||
if int(resCode) != 1000:
|
||||
return -1
|
||||
print(res.text)
|
||||
return res.json()['data']
|
||||
|
||||
def detect_obj4print(pid, orderId):
|
||||
for file in os.listdir(os.path.join(workdir, 'print', f'{pid}_{orderId}')):
|
||||
if file.endswith('.obj') and 'x' in file:
|
||||
return True
|
||||
|
||||
|
||||
def restart_current_process(new_command):
|
||||
try:
|
||||
# 保存新进程的命令
|
||||
command = new_command.split()
|
||||
|
||||
# 启动新进程
|
||||
new_process = subprocess.Popen(command)
|
||||
|
||||
# 打印新进程的PID
|
||||
print(f"New process started with PID: {new_process.pid}")
|
||||
|
||||
# 终止当前进程
|
||||
os._exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
|
||||
def make3d4print_task(orderId):
|
||||
res = getPidFromOrderId(orderId)
|
||||
if res == -1:
|
||||
print("查询打印订单信息失败,重新开启进程")
|
||||
#os.system(f'python auto_convert3d.py')
|
||||
return
|
||||
pid = str(res['pid'])
|
||||
if pid == "88985":
|
||||
return
|
||||
if pid == "":
|
||||
return
|
||||
#创建正在处理的文本内容
|
||||
creatDoingLog(orderId)
|
||||
|
||||
print_type = res['print_type']
|
||||
digital_type = res['digital_type'] # 0: 只有手办 1: 只有数字模型 2: 手办+数字模型
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成待打印模型 start', )
|
||||
isFindObj = False
|
||||
down_obj_fromoss(pid, print_type, orderId,download_flag="print_build")
|
||||
# 获取程序运行当前目录
|
||||
resize_py_path = os.path.join(os.getcwd(), 'blender', 'resize_model.py')
|
||||
print(f'{blenderbin} -b -P {resize_py_path} -- {orderId}')
|
||||
os.system(f'{blenderbin} -b -P {resize_py_path} -- {orderId}')
|
||||
if not detect_obj4print(pid, orderId):
|
||||
print('obj文件生成异常,退出,重新执行')
|
||||
restart_current_process("python auto_convert3d.py")
|
||||
return
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成待打印模型 end')
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 处理鼻孔 start')
|
||||
if os.path.exists(f'{workdir}/print/{pid}_{orderId}/{pid}Tex1_old.jpg'):
|
||||
print('已经处理过鼻孔,跳过')
|
||||
else:
|
||||
os.system(f'python fix_nose.py {pid}_{orderId}')
|
||||
#上传jpg文件
|
||||
oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1.jpg', os.path.join(workdir, 'print', f'{pid}_{orderId}', f'{pid}Tex1.jpg'))
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1_old.jpg', os.path.join(workdir, 'print', f'{pid}_{orderId}', f'{pid}Tex1_old.jpg'))
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 处理鼻孔 end')
|
||||
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成脚底板二维码 start')
|
||||
# os.system(f'{blenderbin} -b -P d:\\apps\\blender\\auto_dm.py -- {pid}_{orderId}')
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成脚底板二维码 end')
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 上传生成的模型 start')
|
||||
|
||||
path = os.path.join(workdir, 'print', f'{pid}_{orderId}')
|
||||
|
||||
|
||||
# 如果指定文件夹目标文件存在,则先删除
|
||||
if os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/')):
|
||||
delete_files_in_directory(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'))
|
||||
|
||||
for file in os.listdir(path):
|
||||
# 跳过一些不需要上传的文件
|
||||
if file in [f'{pid}.png',f'{pid}_old.jpg', f'{pid}.obj', f'{pid}_decimate.glb', f'{pid}_decimate.obj', f'{pid}_decimate.mtl', f'{pid}Tex1_decimate.jpg', f'{pid}_original.obj', f'{pid}_original.mtl']: continue
|
||||
print("当前目录",os.path.join(path, file))
|
||||
|
||||
#将文件移动到指定目录
|
||||
if not os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/')):
|
||||
os.makedirs(os.path.join(workdir, f'complate/objs/{pid}/'),mode=0o777, exist_ok=True)
|
||||
|
||||
if not os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/')):
|
||||
os.makedirs(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'),mode=0o777, exist_ok=True)
|
||||
|
||||
shutil.move(os.path.join(path, file), os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'))
|
||||
|
||||
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{file}', os.path.join(path, file))
|
||||
|
||||
# texture_file = os.path.join(path, f'{pid}Tex1_decimate.jpg')
|
||||
# if os.path.exists(texture_file):
|
||||
# img = Image.open(texture_file)
|
||||
# img = img.resize((int(img.size[0] * 0.5), int(img.size[1] * 0.5)))
|
||||
# img.save(texture_file, quality=90, optimize=True)
|
||||
# print('resize texture file to 50% success')
|
||||
|
||||
input = os.path.join(path, f'{pid}_decimate.obj')
|
||||
output = os.path.join(path, f'{pid}_decimate.glb')
|
||||
# os.system(f'gltfpack -c -i {input} -o {output}')
|
||||
# oss_client.put_object_from_file(f'glbs/3d/{pid}.glb', output)
|
||||
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
print(f'{update_makeprintobj_status_url}?id={orderId}')
|
||||
if digital_type == 1:
|
||||
print('只有数字模型,不需要推送手办打印任务,仍有调用接口')
|
||||
|
||||
res = requests.get(f'{update_makeprintobj_status_url}?id={orderId}')
|
||||
print('更新打印状态:', res.text)
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 上传生成的模型 end')
|
||||
removeDoingLog(orderId)
|
||||
#生成封面图片
|
||||
print('小票封面图处理中....')
|
||||
get_preview_image.createImage(pid)
|
||||
#os.system(f'python get_preview_image.py {pid}')
|
||||
|
||||
print(f"{pid}-已处理结束")
|
||||
|
||||
|
||||
def delete_files_in_directory(directory):
|
||||
for file_name in os.listdir(directory):
|
||||
file_path = os.path.join(directory, file_name)
|
||||
try:
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Deleted: {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Error deleting {file_path}: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# def main(r):
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '模型生成程序 start')
|
||||
# while True:
|
||||
#构建打印文件
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# atexit.register(common.notify,"虚拟机,生成打印任务程序停止")
|
||||
AccessKeyId = 'LTAI5tSReWm8hz7dSYxxth8f'
|
||||
AccessKeySecret = '8ywTDF9upPAtvgXtLKALY2iMYHIxdS'
|
||||
Endpoint = 'oss-cn-shanghai.aliyuncs.com'
|
||||
Bucket = 'suwa3d-securedata'
|
||||
oss_client = oss2.Bucket(oss2.Auth(AccessKeyId, AccessKeySecret), Endpoint, Bucket)
|
||||
update_check_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateStatusToWaitingPlatformCheckingStatus'
|
||||
update_team_check_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateStatusToWaitingTeamCheckingStatus'
|
||||
update_status_printstatus_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateBuildPrintModelStatus'
|
||||
update_makeprintobj_status_url = 'https://mp.api.suwa3d.com/api/printOrder/updateMakePrintObjSucceed'
|
||||
getRepairInfo_url = 'https://repair.api.suwa3d.com/api/modelRepairOrder/teamCheckGLBInfo'
|
||||
update_repair_status_url = 'https://repair.api.suwa3d.com/api/modelRepairOrder/updateStatusToWaitingTeamCheckingStatus'
|
||||
if platform.system() == 'Windows':
|
||||
workdir = 'E:\\'
|
||||
else:
|
||||
workdir = '/data/datasets/'
|
||||
blenderbin = find_blender_bin_path()
|
||||
|
||||
#r = redis.Redis(host='106.14.158.208', password='kcV2000', port=6379, db=6)
|
||||
#E:\\complate/objs/147852_54579/
|
||||
# os.remove(os.path.join(workdir, f'complate/objs/147852_54579/'))
|
||||
orderId = sys.argv[1]
|
||||
print(sys.argv)
|
||||
if orderId == 0:
|
||||
print("请输入order_id")
|
||||
exit()
|
||||
|
||||
make3d4print_task(orderId)
|
||||
@@ -0,0 +1,368 @@
|
||||
from ctypes import util
|
||||
import os, oss2, time, redis, requests, shutil, sys, subprocess, json, platform,random
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from retrying import retry
|
||||
import atexit,platform
|
||||
import get_preview_image
|
||||
# if platform.system() == 'Windows':
|
||||
# sys.path.append('libs\\')
|
||||
sys.path.append('/home/acprint/code/libs/')
|
||||
import common
|
||||
|
||||
|
||||
#创建文本文件
|
||||
def creatDoingLog(order_id):
|
||||
#在指定路径创建一条 order_id.txt 文本
|
||||
file_path = f"doingLog/{order_id}.txt"
|
||||
if os.path.exists(file_path):
|
||||
return
|
||||
#创建
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write("1")
|
||||
|
||||
#移除指定的文本文件
|
||||
def removeDoingLog(order_id):
|
||||
file_path = f"doingLog/{order_id}.txt"
|
||||
if not os.path.exists(file_path):
|
||||
return
|
||||
os.remove(file_path)
|
||||
|
||||
#遍历文件夹,返回指定的order_id
|
||||
def get_order_id_by_txt():
|
||||
#遍历文件夹
|
||||
for file in os.listdir("doingLog"):
|
||||
if file != "":
|
||||
arrFile = file.split(".")
|
||||
return arrFile[0]
|
||||
return None
|
||||
|
||||
def find_blender_bin_path():
|
||||
if platform.system() == 'Linux': return '/home/acprint/code/blender/blender'
|
||||
base_path = 'C:\\Program Files\\Blender Foundation\\'
|
||||
if os.path.exists(base_path):
|
||||
for dir in os.listdir(base_path):
|
||||
if dir.startswith('Blender'):
|
||||
blender_bin_path = base_path + dir + '\\blender.exe'
|
||||
return f'"{blender_bin_path}"'
|
||||
else:
|
||||
print('未找到blender安装目录')
|
||||
exit(1)
|
||||
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=3000)
|
||||
def down_obj_fromoss(pid, print_type=1, order_id=None,download_flag="print_build"):
|
||||
# print_type:// 打印状态 1:正常打印 2:重打 3:加打,4: 样品
|
||||
print('开始下载obj文件...' , pid)
|
||||
|
||||
if not order_id is None:
|
||||
path = os.path.join(workdir, 'print', f'{pid}_{order_id}')
|
||||
else:
|
||||
path = os.path.join(workdir, 'print', pid)
|
||||
if not os.path.exists(path): os.makedirs(path)
|
||||
|
||||
# 根据前缀获取文件列表
|
||||
prefix = f'objs/print/{pid}/'
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
find = False
|
||||
findTex1Jpg = False
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'{pid}.obj'):
|
||||
find = True
|
||||
|
||||
if download_flag == "print_build":
|
||||
if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
|
||||
if not find:
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'.obj'):
|
||||
find = True
|
||||
|
||||
# if download_flag == "print_build":
|
||||
# if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
# continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
break
|
||||
|
||||
if find:
|
||||
os.rename(localfile, os.path.join(path,f"{pid}.obj"))
|
||||
#重命名文件
|
||||
|
||||
#判断是否有Tex1.jpg
|
||||
if not os.path.exists(os.path.join(path,f"{pid}Tex1.jpg")):
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'{pid}.jpg'):
|
||||
findTex1Jpg = True
|
||||
|
||||
# if download_flag == "print_build":
|
||||
# if filename != str(pid)+".obj" and filename != str(pid)+".mtl" and filename != str(pid)+"Tex1.jpg":
|
||||
# continue
|
||||
localfile = os.path.join(path, filename)
|
||||
res = oss_client.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
break
|
||||
|
||||
if findTex1Jpg:
|
||||
os.rename(localfile, os.path.join(path,f"{pid}Tex1.jpg"))
|
||||
#重命名文件
|
||||
|
||||
# for file in filelist:
|
||||
# filename = file.key.split('/')[-1]
|
||||
# if filename == '': continue
|
||||
# if filename.endswith(f'{pid}.obj'):
|
||||
# find = True
|
||||
# if filename.endswith('.obj'):
|
||||
# print('找到其他obj文件,采用这个文件来生成需要的尺寸', file)
|
||||
# shutil.copy(os.path.join(path, file), os.path.join(path, f'{pid}.obj'))
|
||||
# find = True
|
||||
# break
|
||||
if not find:
|
||||
print('找不到obj文件,跳过')
|
||||
common.notify(f"{pid}-构建打印文件,找不到obj文件,异常,跳过该任务处理")
|
||||
# sys.exit(1)
|
||||
os.system(f'python auto_convert3d.py')
|
||||
return
|
||||
|
||||
# print('下载完成后静默10秒,等待文件写入完成')
|
||||
# time.sleep(10)
|
||||
#根据pid获取orderId
|
||||
def getPidFromOrderId(orderId):
|
||||
getPidFromOrderId_url = 'https://mp.api.suwa3d.com/api/printOrder/info'
|
||||
print(f'{getPidFromOrderId_url}?id={orderId}')
|
||||
res = requests.get(f'{getPidFromOrderId_url}?id={orderId}')
|
||||
resCode = res.json()['code']
|
||||
if int(resCode) != 1000:
|
||||
return -1
|
||||
print(res.text)
|
||||
return res.json()['data']
|
||||
|
||||
def detect_obj4print(pid, orderId):
|
||||
for file in os.listdir(os.path.join(workdir, 'print', f'{pid}_{orderId}')):
|
||||
if file.endswith('.obj') and 'x' in file:
|
||||
return True
|
||||
|
||||
|
||||
def restart_current_process(new_command):
|
||||
try:
|
||||
# 保存新进程的命令
|
||||
command = new_command.split()
|
||||
|
||||
# 启动新进程
|
||||
new_process = subprocess.Popen(command)
|
||||
|
||||
# 打印新进程的PID
|
||||
print(f"New process started with PID: {new_process.pid}")
|
||||
|
||||
# 终止当前进程
|
||||
os._exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
|
||||
def make3d4print_task(r):
|
||||
|
||||
|
||||
tempOrderId = "0"
|
||||
try:
|
||||
if r.llen('model:printOrder') == 0:
|
||||
time.sleep(5)
|
||||
|
||||
# tempOrderId = get_order_id_by_txt()
|
||||
# if tempOrderId == "0":
|
||||
# return
|
||||
except Exception as e:
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), 'redis连接异常,重新连接')
|
||||
print(e)
|
||||
time.sleep(5)
|
||||
r = redis.Redis(host='106.14.158.208', password='kcV2000', port=6379, db=6)
|
||||
return
|
||||
orderId = None
|
||||
if tempOrderId == "0":
|
||||
orderId = r.lpop('model:printOrder')
|
||||
else:
|
||||
orderId = tempOrderId
|
||||
|
||||
if orderId is None: return
|
||||
|
||||
if type(orderId) != str:
|
||||
orderId = orderId.decode('utf-8')
|
||||
# orderId = 56077
|
||||
res = getPidFromOrderId(orderId)
|
||||
if res == -1:
|
||||
print("查询打印订单信息失败,重新开启进程")
|
||||
#os.system(f'python auto_convert3d.py')
|
||||
return
|
||||
pid = str(res['pid'])
|
||||
if pid == "88985":
|
||||
return
|
||||
if pid == "":
|
||||
return
|
||||
#创建正在处理的文本内容
|
||||
creatDoingLog(orderId)
|
||||
|
||||
print_type = res['print_type']
|
||||
digital_type = res['digital_type'] # 0: 只有手办 1: 只有数字模型 2: 手办+数字模型
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成待打印模型 start', )
|
||||
isFindObj = False
|
||||
down_obj_fromoss(pid, print_type, orderId,download_flag="print_build")
|
||||
# 获取程序运行当前目录
|
||||
resize_py_path = os.path.join(os.getcwd(), 'blender', 'resize_model.py')
|
||||
print(f'{blenderbin} -b -P {resize_py_path} -- {orderId}')
|
||||
os.system(f'{blenderbin} -b -P {resize_py_path} -- {orderId}')
|
||||
if not detect_obj4print(pid, orderId):
|
||||
print('obj文件生成异常,退出,重新执行')
|
||||
restart_current_process("python auto_convert3d.py")
|
||||
return
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成待打印模型 end')
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 处理鼻孔 start')
|
||||
if os.path.exists(f'{workdir}/print/{pid}_{orderId}/{pid}Tex1_old.jpg'):
|
||||
print('已经处理过鼻孔,跳过')
|
||||
else:
|
||||
os.system(f'python fix_nose.py {pid}_{orderId}')
|
||||
#上传jpg文件
|
||||
oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1.jpg', os.path.join(workdir, 'print', f'{pid}_{orderId}', f'{pid}Tex1.jpg'))
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1_old.jpg', os.path.join(workdir, 'print', f'{pid}_{orderId}', f'{pid}Tex1_old.jpg'))
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 处理鼻孔 end')
|
||||
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成脚底板二维码 start')
|
||||
# os.system(f'{blenderbin} -b -P d:\\apps\\blender\\auto_dm.py -- {pid}_{orderId}')
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 生成脚底板二维码 end')
|
||||
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 上传生成的模型 start')
|
||||
|
||||
path = os.path.join(workdir, 'print', f'{pid}_{orderId}')
|
||||
|
||||
|
||||
# 如果指定文件夹目标文件存在,则先删除
|
||||
if os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/')):
|
||||
delete_files_in_directory(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'))
|
||||
|
||||
for file in os.listdir(path):
|
||||
# 跳过一些不需要上传的文件
|
||||
if file in [f'{pid}.png',f'{pid}_old.jpg', f'{pid}.obj', f'{pid}_decimate.glb', f'{pid}_decimate.obj', f'{pid}_decimate.mtl', f'{pid}Tex1_decimate.jpg', f'{pid}_original.obj', f'{pid}_original.mtl']: continue
|
||||
print("当前目录",os.path.join(path, file))
|
||||
|
||||
#将文件移动到指定目录
|
||||
if not os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/')):
|
||||
os.makedirs(os.path.join(workdir, f'complate/objs/{pid}/'),mode=0o777, exist_ok=True)
|
||||
|
||||
if not os.path.exists(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/')):
|
||||
os.makedirs(os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'),mode=0o777, exist_ok=True)
|
||||
|
||||
shutil.move(os.path.join(path, file), os.path.join(workdir, f'complate/objs/{pid}/order_{orderId}/'))
|
||||
|
||||
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{file}', os.path.join(path, file))
|
||||
|
||||
# texture_file = os.path.join(path, f'{pid}Tex1_decimate.jpg')
|
||||
# if os.path.exists(texture_file):
|
||||
# img = Image.open(texture_file)
|
||||
# img = img.resize((int(img.size[0] * 0.5), int(img.size[1] * 0.5)))
|
||||
# img.save(texture_file, quality=90, optimize=True)
|
||||
# print('resize texture file to 50% success')
|
||||
|
||||
input = os.path.join(path, f'{pid}_decimate.obj')
|
||||
output = os.path.join(path, f'{pid}_decimate.glb')
|
||||
# os.system(f'gltfpack -c -i {input} -o {output}')
|
||||
# oss_client.put_object_from_file(f'glbs/3d/{pid}.glb', output)
|
||||
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
print(f'{update_makeprintobj_status_url}?id={orderId}')
|
||||
if digital_type == 1:
|
||||
print('只有数字模型,不需要推送手办打印任务,仍有调用接口')
|
||||
|
||||
res = requests.get(f'{update_makeprintobj_status_url}?id={orderId}')
|
||||
print('更新打印状态:', res.text)
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f'orderId:{orderId} pid:{pid} 上传生成的模型 end')
|
||||
removeDoingLog(orderId)
|
||||
#生成封面图片
|
||||
print('小票封面图处理中....')
|
||||
get_preview_image.createImage(pid)
|
||||
#os.system(f'python get_preview_image.py {pid}')
|
||||
|
||||
print(f"{pid}-已处理结束")
|
||||
|
||||
restart_current_process("python auto_convert3d.py")
|
||||
|
||||
|
||||
def delete_files_in_directory(directory):
|
||||
for file_name in os.listdir(directory):
|
||||
file_path = os.path.join(directory, file_name)
|
||||
try:
|
||||
if os.path.isfile(file_path):
|
||||
os.remove(file_path)
|
||||
print(f"Deleted: {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Error deleting {file_path}: {e}")
|
||||
|
||||
|
||||
def create_redis_connection():
|
||||
"""创建 Redis 连接,若连接失败则重试"""
|
||||
while True:
|
||||
try:
|
||||
r = redis.Redis(host="106.14.158.208", password="kcV2000", port=6379, db=6)
|
||||
# 尝试进行一次操作,检查连接是否有效
|
||||
r.ping() # ping 操作是一个简单的连接测试
|
||||
print("Redis连接成功!")
|
||||
return r
|
||||
except ConnectionError:
|
||||
print("Redis连接失败,正在重试...")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def main(r):
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '模型生成程序 start')
|
||||
while True:
|
||||
try:
|
||||
#随机休眠 1- 9
|
||||
time.sleep(random.uniform(1, 9))
|
||||
|
||||
|
||||
#构建打印文件
|
||||
make3d4print_task(r)
|
||||
except Exception as e:
|
||||
print(f'出现异常:{e}')
|
||||
time.sleep(15)
|
||||
r = create_redis_connection()
|
||||
continue
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# atexit.register(common.notify,"虚拟机,生成打印任务程序停止")
|
||||
AccessKeyId = 'LTAI5tSReWm8hz7dSYxxth8f'
|
||||
AccessKeySecret = '8ywTDF9upPAtvgXtLKALY2iMYHIxdS'
|
||||
Endpoint = 'oss-cn-shanghai.aliyuncs.com'
|
||||
Bucket = 'suwa3d-securedata'
|
||||
oss_client = oss2.Bucket(oss2.Auth(AccessKeyId, AccessKeySecret), Endpoint, Bucket)
|
||||
update_check_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateStatusToWaitingPlatformCheckingStatus'
|
||||
update_team_check_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateStatusToWaitingTeamCheckingStatus'
|
||||
update_status_printstatus_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/updateBuildPrintModelStatus'
|
||||
update_makeprintobj_status_url = 'https://mp.api.suwa3d.com/api/printOrder/updateMakePrintObjSucceed'
|
||||
getRepairInfo_url = 'https://repair.api.suwa3d.com/api/modelRepairOrder/teamCheckGLBInfo'
|
||||
update_repair_status_url = 'https://repair.api.suwa3d.com/api/modelRepairOrder/updateStatusToWaitingTeamCheckingStatus'
|
||||
if platform.system() == 'Windows':
|
||||
workdir = 'E:\\'
|
||||
else:
|
||||
workdir = '/data/datasets/'
|
||||
blenderbin = find_blender_bin_path()
|
||||
|
||||
r = create_redis_connection()
|
||||
#E:\\complate/objs/147852_54579/
|
||||
# os.remove(os.path.join(workdir, f'complate/objs/147852_54579/'))
|
||||
main(r)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
@@ -0,0 +1,148 @@
|
||||
import bpy, sys, os, math, bmesh
|
||||
# from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
def gen_qrcode(pid):
|
||||
fontHeightMax = 40
|
||||
fontsize = 1
|
||||
qr = qrcode.QRCode()
|
||||
qr.border = 2
|
||||
qr.add_data(pid)
|
||||
img = qr.make_image(fit=True)
|
||||
img = img.transform((250, 294), Image.Transform.EXTENT, (0, 0, 250, 294), fillcolor='white')
|
||||
|
||||
cwd = os.path.dirname(os.path.abspath(__file__))
|
||||
fontfile = os.path.join(cwd, 'fonts', 'Helvetica.ttf')
|
||||
font = ImageFont.truetype(fontfile, fontsize)
|
||||
while font.getsize(pid)[1] <= fontHeightMax and font.getsize(pid)[0] <= 240:
|
||||
fontsize += 1
|
||||
font = ImageFont.truetype(fontfile, fontsize)
|
||||
fontsize -= 1
|
||||
|
||||
captionx = (250 - font.getsize(pid)[0]) / 2
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.text((captionx, 242), pid, font=font)
|
||||
img.show()
|
||||
img.save(f'{workdir}{pid}.png')
|
||||
|
||||
def auto_rotate(pid):
|
||||
# 坐标复位
|
||||
obj = bpy.context.selected_objects[0]
|
||||
obj.rotation_euler[0] = 0
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Y', 'Z'})
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
# bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_align_yz.obj')
|
||||
|
||||
# 躺平到打印机排版需要的坐标与角度
|
||||
obj.rotation_euler = (math.radians(90), math.radians(90), 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
# bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_rotate_y90.obj')
|
||||
|
||||
heights = {}
|
||||
min_height = 999999
|
||||
min_i = 0
|
||||
max_height = -999999
|
||||
max_i = 0
|
||||
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_3', align_axis={'X', 'Y', 'Z'})
|
||||
|
||||
# 步进精度2旋转X轴到180度,找到Y轴最低点和最高点,其中最低点为打印
|
||||
step = 2
|
||||
i = 0
|
||||
while i <= 180:
|
||||
obj.rotation_euler = (math.radians(step), 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
if obj.dimensions[1] < min_height:
|
||||
min_height = obj.dimensions[1]
|
||||
min_i = i
|
||||
if obj.dimensions[1] > max_height:
|
||||
max_height = obj.dimensions[1]
|
||||
max_i = i
|
||||
heights[i] = (obj.dimensions[0], obj.dimensions[1], obj.dimensions[2])
|
||||
print(i, heights[i])
|
||||
i += step
|
||||
|
||||
obj.rotation_euler = (0, 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
obj.rotation_euler = (math.radians(min_i), 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}.obj')
|
||||
|
||||
# obj.rotation_euler = (0, 0, 0)
|
||||
# bpy.ops.object.transform_apply(rotation=True)
|
||||
# obj.rotation_euler = (math.radians(max_i), 0, 0)
|
||||
# bpy.ops.object.transform_apply(rotation=True)
|
||||
# bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_maxz.obj')
|
||||
print(f'最小高度: {min_height} @ {heights[min_i]}min_i:{min_i}' , f'最大高度: {max_height} @ {heights[max_i]}max_i:{max_i}')
|
||||
|
||||
def cut_obj(pid):
|
||||
# 根据定位用一个面切割模型
|
||||
offset = 45.5
|
||||
radian = math.radians(90)
|
||||
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(offset, 0, 0), rotation=(0, radian, 0), scale=(1, 1, 1))
|
||||
|
||||
# 布尔切割,保留交集切面
|
||||
bpy.ops.object.modifier_add(type='BOOLEAN')
|
||||
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[pid]
|
||||
bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT'
|
||||
bpy.context.object.modifiers["Boolean"].solver = 'FAST'
|
||||
bpy.ops.object.modifier_apply(modifier="Boolean")
|
||||
|
||||
# 拆分切割面为多个多边形,然后遍历多边形,找到最大的面积
|
||||
bpy.ops.mesh.separate(type='LOOSE')
|
||||
|
||||
max_area = 0
|
||||
max_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.name.startswith('Plane'):
|
||||
area = obj.data.polygons[0].area
|
||||
if area > max_area:
|
||||
max_area = area
|
||||
max_obj = obj
|
||||
|
||||
# 选中最大面积的多边形,然后计算中心点
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
max_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = max_obj
|
||||
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
|
||||
|
||||
return max_obj
|
||||
|
||||
def main():
|
||||
filename = f'{workdir}{pid}.obj'
|
||||
print('正在处理:', filename)
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
|
||||
auto_rotate(pid)
|
||||
# gen_qrcode(pid)
|
||||
|
||||
# 脚底切片,查找最大面积,计算中心点,计算坐标位置,怼入二维码贴片
|
||||
max_obj = cut_obj(pid)
|
||||
bpy.ops.import_scene.obj(filepath=f'{workdir}qr.obj')
|
||||
qr_obj = bpy.data.objects['Cube']
|
||||
shore_obj = bpy.data.objects['Cube.001']
|
||||
# bpy.data.objects['Cube'].origin_set(type='ORIGIN_GEOMETRY')
|
||||
# bpy.data.objects['Cube.001'].origin_set(type='ORIGIN_GEOMETRY')
|
||||
# bpy.data.objects['Cube.002'].origin_set(type='ORIGIN_GEOMETRY')
|
||||
# bpy.data.objects['Cube.003'].origin_set(type='ORIGIN_GEOMETRY')
|
||||
bpy.data.objects['Cube'] = (math.radians(90), math.radians(90), 0)
|
||||
bpy.data.objects['Cube.001'].rotation_euler = (math.radians(90), math.radians(90), 0)
|
||||
bpy.data.objects['Cube.002'].rotation_euler = (math.radians(90), math.radians(90), 0)
|
||||
bpy.data.objects['Cube.003'].rotation_euler = (math.radians(90), math.radians(90), 0)
|
||||
qr_obj.location = (max_obj.location[0] - qr_obj.dimensions[1] / 2 - shore_obj.dimensions[1]/2, max_obj.location[1], max_obj.location[2])
|
||||
shore_obj.location = (qr_obj.location[0] - shore_obj.dimensions[1]/2, max_obj.location[1], max_obj.location[2])
|
||||
bpy.data.objects['Cube.002'].location = (shore_obj.location[0], shore_obj.location[1]+0.2, shore_obj.location[2])
|
||||
bpy.data.objects['Cube.003'].location = (shore_obj.location[0], shore_obj.location[1]-0.2, shore_obj.location[2])
|
||||
|
||||
bpy.ops.object.transform_apply(rotation=True, location=True, scale=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
workdir = '/home/water/Downloads/'
|
||||
if len(sys.argv) - (sys.argv.index("--") +1) < 1:
|
||||
print("Usage: blender -b -P auto_qrcode.py -- <pid>")
|
||||
sys.exit(1)
|
||||
pid = sys.argv[sys.argv.index("--") + 1]
|
||||
main()
|
||||
@@ -0,0 +1,160 @@
|
||||
from math import radians
|
||||
import sys, os, time, bpy, requests, json, bmesh
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import platform
|
||||
if platform.system() == 'Windows':
|
||||
sys.path.append('e:\\libs\\')
|
||||
else:
|
||||
sys.path.append('/data/deploy/make3d/make2/libs/')
|
||||
import config
|
||||
|
||||
def get_obj_version(filename):
|
||||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('# Engine version'):
|
||||
return float(line.split(' ')[-1][1:].strip()[:3])
|
||||
exit(0)
|
||||
return None
|
||||
|
||||
def delete_lines_in_file(filename, count):
|
||||
with open(filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
lines = lines[count:]
|
||||
with open(filename, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def diff_minutes_and_seconds(start):
|
||||
hours = int((time.time() - start) / 3600)
|
||||
minutes = int((time.time() - start) / 60)
|
||||
seconds = int((time.time() - start) % 60)
|
||||
microseconds = int(int((time.time() - start) * 1000000) % 1000000 / 1000)
|
||||
return f'{hours}:{minutes}:{seconds}.{microseconds}'
|
||||
|
||||
def get_headcount(pid):
|
||||
res = requests.get(config.urls['get_printinfo_url'], params={'id': pid})
|
||||
print('get_printsize_url:', res.url)
|
||||
print('res:', res.text)
|
||||
if res.status_code != 200:
|
||||
print('获取人数失败,程序退出')
|
||||
exit(1)
|
||||
res = json.loads(res.text)
|
||||
return res['data']['headcount']
|
||||
|
||||
def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifiers=False):
|
||||
"""Returns a transformed, triangulated copy of the mesh"""
|
||||
assert obj.type == 'MESH'
|
||||
if apply_modifiers and obj.modifiers:
|
||||
import bpy
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
obj_eval = obj.evaluated_get(depsgraph)
|
||||
me = obj_eval.to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
obj_eval.to_mesh_clear()
|
||||
else:
|
||||
me = obj.data
|
||||
if obj.mode == 'EDIT':
|
||||
bm_orig = bmesh.from_edit_mesh(me)
|
||||
bm = bm_orig.copy()
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
if transform:
|
||||
matrix = obj.matrix_world.copy()
|
||||
if not matrix.is_identity:
|
||||
bm.transform(matrix)
|
||||
matrix.translation.zero()
|
||||
if not matrix.is_identity:
|
||||
bm.normal_update()
|
||||
if triangulate:
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
return bm
|
||||
|
||||
def getPSid(pid):
|
||||
get_psid_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/photoStudio'
|
||||
res = requests.get(get_psid_url, params={'pid': pid})
|
||||
res = json.loads(res.text)
|
||||
return str(res['data'])
|
||||
|
||||
def getPSRotation(pid):
|
||||
get_ps_rotation_url = 'https://mp.api.suwa3d.com/api/takephotoOrder/angle'
|
||||
res = requests.get(get_ps_rotation_url, params={'pid': pid})
|
||||
res = json.loads(res.text)
|
||||
rotation = (radians(0), radians(0), radians(int(res['data'])))
|
||||
return rotation
|
||||
|
||||
def main():
|
||||
start = time.time()
|
||||
workdir = 'e:\\'
|
||||
|
||||
if len(sys.argv) - (sys.argv.index("--") +1) < 1:
|
||||
print("Usage: blender -b -P autofix.py -- <pids>")
|
||||
sys.exit(1)
|
||||
input_file = sys.argv[sys.argv.index("--") + 1]
|
||||
|
||||
for pid in input_file.split(','):
|
||||
psid = getPSid(pid)
|
||||
|
||||
bpy.ops.wm.read_homefile()
|
||||
# bpy.context.scene.unit_settings.scale_length = 0.001
|
||||
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
|
||||
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
|
||||
bpy.ops.object.delete(use_global=False, confirm=False)
|
||||
|
||||
filename = f'{workdir}{pid}\\output\{pid}.obj'
|
||||
print('正在处理:', filename)
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_2', align_axis={'Z'})
|
||||
print('import obj time:', diff_minutes_and_seconds(start))
|
||||
|
||||
# rotate obj
|
||||
obj = bpy.context.selected_objects[0]
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
rotation = getPSRotation(pid)
|
||||
print('rotation:', rotation)
|
||||
obj.rotation_euler = rotation
|
||||
print('rotate obj time:', diff_minutes_and_seconds(start))
|
||||
# resize object
|
||||
scale = 90 / obj.dimensions.z
|
||||
obj.scale = (scale, scale, scale)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
|
||||
bpy.context.object.location[0] = 0
|
||||
bpy.context.object.location[1] = 0
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
# bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}\\output\{pid}_4.blend')
|
||||
|
||||
bm = bmesh_copy_from_object(obj)
|
||||
obj_volume = round(bm.calc_volume() / 1000, 3)
|
||||
print('volume:', obj_volume)
|
||||
print('weight:', obj_volume * 1.2, 'g')
|
||||
|
||||
faces = len(obj.data.polygons)
|
||||
print('faces:', faces)
|
||||
|
||||
# save object
|
||||
bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}\\output\{pid}.obj')
|
||||
|
||||
# 生成数字模型
|
||||
headcount = get_headcount(pid)
|
||||
faces_dest = 120000 * headcount
|
||||
|
||||
# 减面
|
||||
faces_current = len(obj.data.polygons)
|
||||
bpy.ops.object.modifier_add(type='DECIMATE')
|
||||
bpy.context.object.modifiers["Decimate"].ratio = faces_dest / faces_current
|
||||
bpy.ops.object.modifier_apply(modifier="Decimate")
|
||||
|
||||
bpy.ops.export_scene.gltf(filepath=os.path.join(workdir, pid, 'output', f'{pid}_decimate.glb'), export_format='GLB', export_apply=True, export_jpeg_quality=75, export_draco_mesh_compression_enable=False)
|
||||
|
||||
config.oss_bucket.put_object_from_file(f'glbs/3d/{pid}.glb', os.path.join(workdir, pid, 'output', f'{pid}_decimate.glb'))
|
||||
print('免费体验3d相册已生成,上传glb文件:', f'glbs/3d/{pid}.glb 完成')
|
||||
# render scene to a file
|
||||
# bpy.context.scene.render.filepath = f'{workdir}{pid}_fixed.png'
|
||||
# bpy.ops.render.render(write_still=True, use_viewport=True)
|
||||
print('render time:', diff_minutes_and_seconds(start))
|
||||
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,144 @@
|
||||
from math import radians
|
||||
import sys, os, time, bpy, requests, json, bmesh
|
||||
|
||||
def get_obj_version(filename):
|
||||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('# Engine version'):
|
||||
return float(line.split(' ')[-1][1:].strip()[:3])
|
||||
exit(0)
|
||||
return None
|
||||
|
||||
def delete_lines_in_file(filename, count):
|
||||
with open(filename, 'r') as f:
|
||||
lines = f.readlines()
|
||||
lines = lines[count:]
|
||||
with open(filename, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
def diff_minutes_and_seconds(start):
|
||||
hours = int((time.time() - start) / 3600)
|
||||
minutes = int((time.time() - start) / 60)
|
||||
seconds = int((time.time() - start) % 60)
|
||||
microseconds = int(int((time.time() - start) * 1000000) % 1000000 / 1000)
|
||||
return f'{hours}:{minutes}:{seconds}.{microseconds}'
|
||||
|
||||
def getPSid(pid):
|
||||
get_psid_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/photoStudio'
|
||||
res = requests.get(get_psid_url, params={'pid': pid})
|
||||
res = json.loads(res.text)
|
||||
return str(res['data'])
|
||||
|
||||
def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifiers=False):
|
||||
"""Returns a transformed, triangulated copy of the mesh"""
|
||||
assert obj.type == 'MESH'
|
||||
if apply_modifiers and obj.modifiers:
|
||||
import bpy
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
obj_eval = obj.evaluated_get(depsgraph)
|
||||
me = obj_eval.to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
obj_eval.to_mesh_clear()
|
||||
else:
|
||||
me = obj.data
|
||||
if obj.mode == 'EDIT':
|
||||
bm_orig = bmesh.from_edit_mesh(me)
|
||||
bm = bm_orig.copy()
|
||||
else:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
if transform:
|
||||
matrix = obj.matrix_world.copy()
|
||||
if not matrix.is_identity:
|
||||
bm.transform(matrix)
|
||||
matrix.translation.zero()
|
||||
if not matrix.is_identity:
|
||||
bm.normal_update()
|
||||
if triangulate:
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
return bm
|
||||
|
||||
|
||||
def main():
|
||||
start = time.time()
|
||||
config = {
|
||||
'0': {
|
||||
'rotation': (radians(0), radians(0), radians(0)),
|
||||
},
|
||||
'1': {
|
||||
'rotation': (radians(0), radians(0), radians(66)),
|
||||
},
|
||||
'29': {
|
||||
'rotation': (radians(0), radians(0), radians(180)),
|
||||
},
|
||||
'45': {
|
||||
'rotation': (radians(0), radians(0), radians(105)),
|
||||
},
|
||||
'46': {
|
||||
'rotation': (radians(0), radians(0), radians(-10)),
|
||||
},
|
||||
'74': {
|
||||
'rotation': (radians(0), radians(0), radians(110)),
|
||||
},
|
||||
'75': {
|
||||
'rotation': (radians(0), radians(0), radians(210)),
|
||||
},
|
||||
}
|
||||
workdir = 'e:\\'
|
||||
|
||||
if len(sys.argv) - (sys.argv.index("--") +1) < 1:
|
||||
print("Usage: blender -b -P autofix.py -- <pids>")
|
||||
sys.exit(1)
|
||||
input_file = sys.argv[sys.argv.index("--") + 1]
|
||||
|
||||
for pid in input_file.split(','):
|
||||
psid = getPSid(pid)
|
||||
|
||||
bpy.ops.object.delete(use_global=False, confirm=False)
|
||||
|
||||
filename = f'{workdir}{pid}\\output\{pid}.obj'
|
||||
print('正在处理:', filename)
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
bpy.ops.object.align(relative_to='OPT_1', align_axis={'X'})
|
||||
# bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
print('import obj time:', diff_minutes_and_seconds(start))
|
||||
|
||||
# rotate obj
|
||||
obj = bpy.context.selected_objects[0]
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
if psid in config:
|
||||
obj.rotation_euler = config[psid]['rotation']
|
||||
else:
|
||||
obj.rotation_euler = config['0']['rotation']
|
||||
print('rotate obj time:', diff_minutes_and_seconds(start))
|
||||
# bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
# resize object
|
||||
obj_scale = 90 / obj.dimensions.z
|
||||
print('scale:', obj_scale)
|
||||
|
||||
obj.scale = (obj_scale, obj_scale, obj_scale)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
bm = bmesh_copy_from_object(obj)
|
||||
obj_volume = round(bm.calc_volume() / 1000, 3)
|
||||
print('volume:', obj_volume)
|
||||
print('weight:', obj_volume * 1.2, 'g')
|
||||
|
||||
faces = len(obj.data.polygons)
|
||||
print('faces:', faces)
|
||||
|
||||
# save object
|
||||
bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}\\output\{pid}.obj')
|
||||
# render scene to a file
|
||||
# bpy.context.scene.render.filepath = f'{workdir}{pid}_fixed.png'
|
||||
# bpy.ops.render.render(write_still=True, use_viewport=True)
|
||||
print('render time:', diff_minutes_and_seconds(start))
|
||||
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
BIN
Binary file not shown.
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
BF_DIST_BIN=$(dirname "$0")
|
||||
BF_PROGRAM="blender"
|
||||
|
||||
# Add own lib folder first, because Steam or other environments may set an
|
||||
# LD_LIBRARY_PATH that has priority over the runpath in the Blender excutable,
|
||||
# but contains incompatible libraries.
|
||||
LD_LIBRARY_PATH=${BF_DIST_BIN}/lib:${LD_LIBRARY_PATH}
|
||||
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
exec "$BF_DIST_BIN/$BF_PROGRAM" ${1+"$@"}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
BF_DIST_BIN=$(dirname "$0")
|
||||
BF_PROGRAM="blender" # BF_PROGRAM=$(basename "$0")-bin
|
||||
|
||||
LD_LIBRARY_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARY_PATH}
|
||||
|
||||
if [ -n "$LD_LIBRARYN32_PATH" ]; then
|
||||
LD_LIBRARYN32_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARYN32_PATH}
|
||||
fi
|
||||
if [ -n "$LD_LIBRARYN64_PATH" ]; then
|
||||
LD_LIBRARYN64_PATH=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARYN64_PATH}
|
||||
fi
|
||||
if [ -n "$LD_LIBRARY_PATH_64" ]; then
|
||||
LD_LIBRARY_PATH_64=${BF_DIST_BIN}/lib/mesa:${LD_LIBRARY_PATH_64}
|
||||
fi
|
||||
|
||||
# Workaround for half-transparent windows when compiz is enabled
|
||||
XLIB_SKIP_ARGB_VISUALS=1
|
||||
|
||||
export LD_LIBRARY_PATH LD_LIBRARYN32_PATH LD_LIBRARYN64_PATH LD_LIBRARY_PATH_64 LD_PRELOAD XLIB_SKIP_ARGB_VISUALS
|
||||
|
||||
exec "$BF_DIST_BIN/$BF_PROGRAM" ${1+"$@"}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="blender.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="640"
|
||||
inkscape:window-height="480"
|
||||
id="namedview10"
|
||||
showgrid="true"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="5.4078582"
|
||||
inkscape:cy="9.1165338"
|
||||
inkscape:current-layer="svg8">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid821" />
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="path6"
|
||||
style="fill:#000000;stroke-width:0.31751338"
|
||||
d="m 5.025875,9.973537 c 0.00317,0.186699 0.063186,0.550569 0.1527243,0.835379 0.188285,0.601368 0.5077036,1.157969 0.9525398,1.648847 a 4.9239967,4.9239967 0 0 0 1.6659926,1.19607 5.3567674,5.3567674 0 0 0 2.1883012,0.455315 5.3732778,5.3732778 0 0 0 2.1876651,-0.462615 c 0.648364,-0.29021 1.209411,-0.696309 1.665361,-1.201474 0.443884,-0.492145 0.762985,-1.050334 0.951584,-1.652018 0.09495,-0.304181 0.154313,-0.612485 0.178761,-0.9220612 0.02382,-0.3048124 0.01365,-0.6105787 -0.02954,-0.9157088 A 4.2584888,4.2584888 0 0 0 14.332188,7.2934066 4.6572855,4.6572855 0 0 0 13.224385,6.071933 l 6.35e-4,-6.349e-4 -4.4826535,-3.4424796 -0.011436,-0.0092 C 8.4362856,2.3941763 7.9416,2.3948112 7.6183715,2.6211983 7.2910154,2.8501252 7.2541836,3.2282837 7.5450257,3.4673714 l -9.522e-4,9.522e-4 1.8695183,1.5208892 -5.6993642,0.00571 h -0.00793 c -0.470881,3.218e-4 -0.9233375,0.3098971 -1.0135114,0.700121 -0.091444,0.3981618 0.2286095,0.7280583 0.7178977,0.7302809 v 0.00126 l 2.8884195,-0.00538 -5.1551462,3.9565333 -0.019687,0.01492 C 0.63847452,10.764779 0.48098788,11.384246 0.78707079,11.776057 1.098234,12.174855 1.758979,12.175173 2.2504895,11.777957 L 5.0636584,9.476049 c 0,0 -0.040641,0.3111643 -0.037785,0.4975444 z m 7.228825,1.040175 c -0.579463,0.591208 -1.390388,0.925869 -2.2692671,0.927453 -0.8788765,0.0019 -1.6904406,-0.330212 -2.2702198,-0.920152 A 2.696958,2.696958 0 0 1 7.0954272,10.05101 2.5229608,2.5229608 0 0 1 6.9528636,8.9676543 2.5626502,2.5626502 0 0 1 7.2640265,7.9471662 2.8182481,2.8182481 0 0 1 7.9536654,7.1270299 C 8.5169342,6.667588 9.2338791,6.4192928 9.9847985,6.4183401 10.735719,6.4173874 11.452664,6.6637778 12.015931,7.1213144 a 2.8049127,2.8049127 0 0 1 0.689641,0.8172796 c 0.172407,0.3124333 0.279411,0.6572522 0.311159,1.0198523 A 2.5248661,2.5248661 0 0 1 12.874168,10.041804 2.7115636,2.7115636 0 0 1 12.2547,11.013712 M 8.1117872,9.0054378 C 8.1381408,8.527263 8.372783,8.1052877 8.7261755,7.8065081 9.0732176,7.5128085 9.5396447,7.3334137 10.048937,7.3334137 c 0.50929,0 0.97572,0.1793948 1.322758,0.4730944 0.353393,0.2987796 0.587719,0.7207549 0.614706,1.1986127 0.02699,0.492146 -0.170501,0.9493659 -0.517227,1.2875182 -0.353393,0.3445 -0.85697,0.561045 -1.420237,0.561045 -0.5632696,0 -1.0671632,-0.216545 -1.4205557,-0.561045 C 8.2816566,9.9541697 8.0844812,9.4972668 8.1117872,9.0054378" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
Executable
BIN
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
[Desktop Entry]
|
||||
Name=Blender
|
||||
GenericName=3D modeler
|
||||
GenericName[ar]=3D المنمذج ثلاثي الأبعاد
|
||||
GenericName[ca]=Modelador 3D
|
||||
GenericName[cs]=3D modelování
|
||||
GenericName[da]=3D-modellering
|
||||
GenericName[de]=3D-Modellierer
|
||||
GenericName[el]=Μοντελοποιητής 3D
|
||||
GenericName[es]=Modelador 3D
|
||||
GenericName[et]=3D modelleerija
|
||||
GenericName[fi]=3D-mallintaja
|
||||
GenericName[fr]=Modeleur 3D
|
||||
GenericName[gl]=Modelador 3D
|
||||
GenericName[hu]=3D modellező
|
||||
GenericName[it]=Modellatore 3D
|
||||
GenericName[ja]=3D モデラー
|
||||
GenericName[lt]=3D modeliavimas
|
||||
GenericName[nb]=3D-modellering
|
||||
GenericName[nl]=3D-modeller
|
||||
GenericName[pl]=Modelowanie 3D
|
||||
GenericName[pt_BR]=Modelador 3D
|
||||
GenericName[ro]=Arhitect 3D
|
||||
GenericName[ru]=Редактор 3D-моделей
|
||||
GenericName[tr]=3D modelleyici
|
||||
GenericName[uk]=Редактор 3D-моделей
|
||||
GenericName[wa]=Modeleu 3D
|
||||
GenericName[zh_CN]=3D 建模
|
||||
GenericName[zh_TW]=3D 模型
|
||||
Comment=3D modeling, animation, rendering and post-production
|
||||
Comment[ar]=3D النمذجة، الرسوم المتحركة، والتجسيد، وما بعد الإنتاج
|
||||
Comment[ast]=Modeláu 3D, animación, renderizáu y postproducción
|
||||
Comment[eu]=3D modelatzea, animazioa, errendatzea eta post-produkzioa
|
||||
Comment[be]=Праграма прасторавага мадэлявання, анімацыі, апрацоўкі відэа і давядзення відэапрадукцыі
|
||||
Comment[bn]=ত্রিমাত্রিক মডেল, অ্যানিমেশন, রেন্ডারিং এবং পোস্ট-উৎপাদন
|
||||
Comment[bs]=3D modeliranje, animacija, obrada i postprodukcija
|
||||
Comment[bg]=3D моделиране, анимиране, рендиране и пост-продукция
|
||||
Comment[ca]=Modelat 3D, animació, renderització i post-producció
|
||||
Comment[ca@valencia]=Modelat 3D, animació, renderització i post-producció
|
||||
Comment[crh]=3B modelleme, animasyon, işleme ve son üretim
|
||||
Comment[cs]=3D modelování, animace, rederování a postprodukce
|
||||
Comment[da]=3D-modellering, animation, rendering og efterbehandling
|
||||
Comment[de]=3D-Modellierung, Animation, Rendering und Nachbearbeitung
|
||||
Comment[nl]=3d-modelleren, animeren, renderen en post-productie
|
||||
Comment[el]=Μοντελοποίηση 3D, κινούμενα σχέδια, αποτύπωση και οργάνωση διαδικασίας μετά-την-παραγωγή
|
||||
Comment[eo]=3D-modelado, animacio, renderado kaj postproduktado
|
||||
Comment[es]=Modelado 3D, animación, renderizado y post-producción
|
||||
Comment[et]=Kolmemõõtmeline modelleerimine, animeerimine, esitlemine ja järeltöötlemine
|
||||
Comment[fi]=3D-mallinnus, -animaatiot, -renderöinti ja -tuotanto
|
||||
Comment[fr]=Modélisation 3D, animation, rendu et post-production
|
||||
Comment[fr_CA]=Modélisation 3D, animation, rendu et post-production
|
||||
Comment[gl]=Modelado 3D, animación, renderizado e postprodución
|
||||
Comment[hu]=3D modellek és animációk létrehozása és szerkesztése
|
||||
Comment[is]=Þrívíddarmódel, hreyfimyndir, myndgerð og frágangur myndskeiða
|
||||
Comment[it]=Modellazione 3D, animazione, rendering e post-produzione
|
||||
Comment[ja]=3Dモデリング、アニメーション、レンダリング、ポストプロダクションのツール
|
||||
Comment[ko]=3D 모델링, 애니메이션, 렌더링과 포스트 프로덕션
|
||||
Comment[lt]=3D modeliavimas, animacijų kūrimas, atvaizdavimas ir tobulinimas
|
||||
Comment[lv]=3D modelēšana, animācija, renderēšana un pēcapstrāde
|
||||
Comment[ms]=Pemodelan, animasi, penerapan dan post-produksi 3D
|
||||
Comment[nb]=3D-modellering, animasjon, rendering og postproduksjon
|
||||
Comment[oc]=Modelizacion 3D, animacion, rendut e post-produccion
|
||||
Comment[pl]=Modelowanie 3D, animacja, renderowanie i postprodukcja
|
||||
Comment[pt]=Modelação 3D, animação, renderização e pós-produção
|
||||
Comment[pt_BR]=Modelagem 3D, animação, renderização e pós-produção
|
||||
Comment[ro]=Modelare, animare, afișare și post-producție 3D
|
||||
Comment[ru]=3D-моделирование, анимация, рендеринг и компоновка
|
||||
Comment[sl]=3D modeliranje, animacija, izrisovanje in nadaljnje obdelovanje
|
||||
Comment[sq]=Animacion i modeleve 3D, rregullim dhe më pas prodhim
|
||||
Comment[sr]=3Д моделовање, анимација, исцртавање и постпродукција
|
||||
Comment[sv]=3d-modellering, animering, rendering och efterbehandling
|
||||
Comment[ta]=முப்பரிமாண ஒப்புருவாக்கம், அசைவூட்டம், காட்சியாக்கம் மற்றும் உருவாக்கத்துக்கு பிந்தைய செயல்பாடுகள்
|
||||
Comment[tg]=Моделсозии 3D, аниматсия, пешниҳод ва истеҳсоли баъдӣ
|
||||
Comment[tr]=3B modelleme, animasyon, işleme ve son üretim
|
||||
Comment[uk]=Програма просторового моделювання, анімації, обробки відео та доведення відеопродуктів
|
||||
Comment[vi]=Tạo hình mẫu 3D, hoạt họa, dựng hình và các công việc hậu kỳ
|
||||
Comment[wa]=Modelaedje 3D, animåcion, rindou eyet après-produccion
|
||||
Comment[zh_HK]=3D 模型、動畫、算圖和後製
|
||||
Comment[zh_CN]=3D 建模、动画、渲染和后期制作
|
||||
Comment[zh_TW]=3D 模型、動畫、算圖和後製
|
||||
Keywords=3d;cg;modeling;animation;painting;sculpting;texturing;video editing;video tracking;rendering;render engine;cycles;game engine;python;
|
||||
Exec=blender %f
|
||||
Icon=blender
|
||||
Terminal=false
|
||||
Type=Application
|
||||
PrefersNonDefaultGPU=true
|
||||
X-KDE-RunOnDiscreteGpu=true
|
||||
Categories=Graphics;3DGraphics;
|
||||
MimeType=application/x-blender;
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><path d="M84.665 108.417c-17.386 0-31.479-13.294-31.479-29.691 0-16.395 14.093-29.689 31.479-29.689 17.387 0 31.48 13.294 31.48 29.689 0 16.397-14.093 29.691-31.48 29.691z" fill="#fff"/><path d="M66.54 75.381c.23-4.165 2.275-7.841 5.353-10.444 3.024-2.559 7.087-4.122 11.524-4.122 4.437 0 8.5 1.563 11.524 4.122 3.078 2.603 5.12 6.279 5.355 10.442.235 4.287-1.486 8.27-4.506 11.216-3.079 3.002-7.466 4.888-12.373 4.888-4.907 0-9.297-1.886-12.376-4.888-3.02-2.948-4.738-6.929-4.5-11.214" fill="#265787"/><path d="M39.657 83.815c.028 1.627.55 4.797 1.33 7.278 1.64 5.24 4.424 10.088 8.299 14.364a42.897 42.897 0 0 0 14.514 10.42 46.667 46.667 0 0 0 19.064 3.967 46.811 46.811 0 0 0 19.058-4.03c5.649-2.528 10.536-6.066 14.509-10.467 3.867-4.288 6.647-9.15 8.29-14.392a36.59 36.59 0 0 0 1.557-8.033c.207-2.656.119-5.32-.257-7.978a37.1 37.1 0 0 0-5.29-14.477 40.573 40.573 0 0 0-9.65-10.642l.006-.005-39.052-29.99-.1-.08c-2.567-1.965-6.876-1.96-9.692.013-2.852 1.995-3.173 5.289-.64 7.372l-.007.008 16.286 13.25-49.651.05h-.07c-4.102.002-8.043 2.7-8.829 6.099-.797 3.469 1.992 6.343 6.254 6.362v.011l25.163-.047L5.84 87.337l-.172.13C1.435 90.709.063 96.105 2.729 99.519c2.711 3.474 8.467 3.477 12.75.016l24.507-20.054s-.354 2.71-.329 4.334zm62.976 9.062c-5.048 5.15-12.113 8.066-19.77 8.08-7.656.017-14.726-2.877-19.777-8.016a23.495 23.495 0 0 1-5.4-8.45 21.98 21.98 0 0 1-1.241-9.439 22.325 22.325 0 0 1 2.71-8.89 24.552 24.552 0 0 1 6.008-7.145c4.907-4.002 11.153-6.166 17.695-6.174 6.542-.008 12.788 2.138 17.695 6.124a24.436 24.436 0 0 1 6.008 7.12 22.375 22.375 0 0 1 2.71 8.885 21.996 21.996 0 0 1-1.241 9.438 23.623 23.623 0 0 1-5.397 8.467" fill="#e87d0d"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,678 @@
|
||||
import os, sys, bpy, math, time, platform, cairosvg, ppf.datamatrix, shutil, requests, json, redis, oss2, heapq
|
||||
import matplotlib.pyplot as plt
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from addon_utils import enable
|
||||
enable('io_import_images_as_planes')
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.append('../libs\\')
|
||||
import config
|
||||
|
||||
def gen_data_matrix(print_id, qr_path, size = 300):
|
||||
svg = ppf.datamatrix.DataMatrix(f'p{print_id}').svg()
|
||||
cairosvg.svg2png(bytestring=svg, write_to=qr_path, output_width=size, output_height=size, background_color='white')
|
||||
|
||||
def active_object(obj):
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
def down_obj_fromoss(pid, print_type=1, order_id=None):
|
||||
# print_type:// 打印状态 1:正常打印 2:重打 3:加打,4: 样品
|
||||
print('开始下载obj文件...' , pid)
|
||||
|
||||
if not order_id is None:
|
||||
path = os.path.join(workdir, f'{pid}_{order_id}')
|
||||
else:
|
||||
path = os.path.join(workdir, pid)
|
||||
if not os.path.exists(path): os.makedirs(path)
|
||||
|
||||
# 根据前缀获取文件列表
|
||||
prefix = f'objs/print/{pid}/'
|
||||
filelist = oss2.ObjectIteratorV2(config.oss_bucket, prefix=prefix)
|
||||
find = False
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'{pid}.obj'):
|
||||
find = True
|
||||
localfile = os.path.join(path, filename)
|
||||
res = config.oss_bucket.get_object_to_file(file.key, localfile)
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
|
||||
if not find:
|
||||
for file in os.listdir(path):
|
||||
if file.endswith('.obj'):
|
||||
print('找到其他obj文件,采用这个文件来生成需要的尺寸', file)
|
||||
shutil.copy(os.path.join(path, file), os.path.join(path, f'{pid}.obj'))
|
||||
find = True
|
||||
break
|
||||
if not find:
|
||||
print('找不到obj文件,异常退出')
|
||||
sys.exit(1)
|
||||
|
||||
def find_obj(pid, order_id):
|
||||
find = False
|
||||
if not os.path.exists(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl')):
|
||||
print('没有找到obj模型文件,开始下载')
|
||||
down_obj_fromoss(pid, order_id=order_id)
|
||||
if os.path.exists(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.jpg')):
|
||||
shutil.move(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.jpg'), os.path.join(workdir, f'{pid}_{order_id}', f'{pid}Tex1.jpg'))
|
||||
with open(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl'), 'r') as f:
|
||||
lines = f.readlines()
|
||||
lines = [line.replace(f'map_Kd {pid}.jpg', f'map_Kd {pid}Tex1.jpg') for line in lines]
|
||||
with open(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl'), 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
filelist = os.listdir(os.path.join(workdir, f'{pid}_{order_id}'))
|
||||
for filename in filelist:
|
||||
if '9cm' in filename:
|
||||
find = True
|
||||
return filename
|
||||
for filename in filelist:
|
||||
if f'{pid}.obj' in filename:
|
||||
find = True
|
||||
return filename
|
||||
for filename in filelist:
|
||||
if '.obj' in filename:
|
||||
find = True
|
||||
return filename
|
||||
print('没有找到obj模型文件')
|
||||
return ''
|
||||
|
||||
def find_pid_objname(pid):
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name.startswith(str(pid)):
|
||||
return obj.name
|
||||
|
||||
def get_obj_max_foot():
|
||||
filename = find_obj(pid, order_id)
|
||||
|
||||
filename = os.path.join(workdir, f'{pid}_{order_id}', filename)
|
||||
bpy.ops.wm.read_homefile()
|
||||
bpy.context.preferences.view.language = 'en_US'
|
||||
bpy.ops.object.delete(use_global=False, confirm=False)
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
bpy.context.scene.unit_settings.scale_length = 0.001
|
||||
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
|
||||
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
|
||||
|
||||
obj = bpy.context.selected_objects[0]
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
pid_objname = find_pid_objname(pid)
|
||||
|
||||
scale = 90 / obj.dimensions.y
|
||||
obj.scale = (scale, scale, scale)
|
||||
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Z'})
|
||||
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
obj.location[0] = 0
|
||||
obj.location[1] = 0
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
if pid in ('76461', '98871', '112139'):
|
||||
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(0, 0, 0.6), scale=(1, 1, 1))
|
||||
else:
|
||||
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(0, 0, 0.2), scale=(1, 1, 1))
|
||||
# bpy.ops.wm.save_as_mainfile(filepath=os.path.join(workdir, f'{pid}_{order_id}', f'{pid}_{order_id}.blend'))
|
||||
bpy.ops.object.modifier_add(type='BOOLEAN')
|
||||
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[pid_objname]
|
||||
bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT'
|
||||
bpy.context.object.modifiers["Boolean"].solver = 'FAST'
|
||||
bpy.ops.object.modifier_apply(modifier="Boolean")
|
||||
|
||||
bpy.ops.mesh.separate(type='LOOSE')
|
||||
|
||||
max_area = 0
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.name.startswith('Plane'):
|
||||
if len(obj.data.polygons) == 0: continue
|
||||
area = obj.data.polygons[0].area
|
||||
if area > max_area:
|
||||
max_area = area
|
||||
obj.name = 'foot'
|
||||
print(f'最大脚底板面积: {max_area} cm²')
|
||||
if max_area < 5:
|
||||
print('最大脚底板面积太小,脚底模型可能有破损,进行再次处理')
|
||||
numsTemp = 0
|
||||
#最多执行三次 重新处理
|
||||
while numsTemp < 3:
|
||||
#每次削的比例要加上去
|
||||
tempArea = 0.2+numsTemp*0.2
|
||||
max_area = check_and_deal_foot_area(tempArea,pid_objname)
|
||||
if max_area >= 5:
|
||||
break
|
||||
numsTemp += 1
|
||||
|
||||
if max_area < 8:
|
||||
print('最大脚底板面积处理多次还没有得到理想的面积,退出')
|
||||
#移除该脚底板的面积处理
|
||||
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
# bpy.ops.wm.save_as_mainfile(filepath=os.path.join(workdir, f'{pid}_{order_id}', f'{pid}_{order_id}.blend'))
|
||||
active_object(bpy.data.objects['foot'])
|
||||
foot_points = get_plane_points(bpy.data.objects['foot'])
|
||||
# plot(get_plane_points(bpy.data.objects['foot']), 'blue')
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
|
||||
# print(f"location: {bpy.data.objects['foot'].location}")
|
||||
bpy.ops.import_image.to_plane(files=[{"name":"qr.png"}], directory=f"{workdir}{pid}_{order_id}", relative=False)
|
||||
# bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
|
||||
# print(f"new_location: {bpy.data.objects['foot'].location}")
|
||||
|
||||
active_object(bpy.data.objects['qr'])
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
bpy.data.objects['qr'].rotation_euler[0] = 0
|
||||
bpy.data.objects['qr'].location = bpy.data.objects['foot'].location
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
# bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
# print(f"qr_location: {bpy.data.objects['qr'].location}")
|
||||
# print(f'qr_points: {get_plane_points(bpy.data.objects["qr"])}')
|
||||
# plot(get_plane_points(bpy.data.objects['qr']), 'red')
|
||||
return foot_points
|
||||
|
||||
#检测到脚底板面积小于5cm²,重新调几次程序再次处理,如果还不行就退出
|
||||
def check_and_deal_foot_area(tempArea,pid_objname):
|
||||
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(0, 0, tempArea), scale=(1, 1, 1))
|
||||
bpy.ops.object.modifier_add(type='BOOLEAN')
|
||||
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[pid_objname]
|
||||
bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT'
|
||||
bpy.context.object.modifiers["Boolean"].solver = 'FAST'
|
||||
bpy.ops.object.modifier_apply(modifier="Boolean")
|
||||
|
||||
bpy.ops.mesh.separate(type='LOOSE')
|
||||
|
||||
max_area = 0
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.name.startswith('Plane'):
|
||||
if len(obj.data.polygons) == 0: continue
|
||||
area = obj.data.polygons[0].area
|
||||
if area > max_area:
|
||||
max_area = area
|
||||
obj.name = 'foot'
|
||||
print("再次处理脚底板得到的面积:" + str(max_area) + "cm²")
|
||||
return max_area
|
||||
|
||||
|
||||
def euclidean_distance(p1, p2):
|
||||
return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5
|
||||
|
||||
def nearest_neighbor_sort(points):
|
||||
print('nearest neighbor sort')
|
||||
n = len(points)
|
||||
visited = set()
|
||||
sorted_points = []
|
||||
i = 1
|
||||
# Start from the first point
|
||||
current_point = points[0]
|
||||
|
||||
#while len(visited) < n:
|
||||
while i < n:
|
||||
i += 1
|
||||
sorted_points.append(current_point)
|
||||
visited.add(current_point)
|
||||
|
||||
# Create a priority queue to store distances and points
|
||||
distance_queue = []
|
||||
for point in points:
|
||||
if point not in visited:
|
||||
distance = euclidean_distance(current_point, point)
|
||||
heapq.heappush(distance_queue, (distance, point))
|
||||
|
||||
# Find the nearest unvisited point
|
||||
while distance_queue:
|
||||
distance, next_point = heapq.heappop(distance_queue)
|
||||
if next_point not in visited:
|
||||
current_point = next_point
|
||||
break
|
||||
|
||||
|
||||
return sorted_points
|
||||
|
||||
def get_max_qr(foot_points):
|
||||
|
||||
def dis_flag(square, foot_points):
|
||||
for point in foot_points:
|
||||
dis0 = get_distance_from_point_to_line(point, square[0], square[1])
|
||||
dis1 = get_distance_from_point_to_line(point, square[1], square[2])
|
||||
dis2 = get_distance_from_point_to_line(point, square[2], square[3])
|
||||
dis3 = get_distance_from_point_to_line(point, square[3], square[0])
|
||||
min_dis = min([dis0, dis1, dis2, dis3])
|
||||
return min_dis > 0.5
|
||||
|
||||
def get_distance_from_point_to_line(point, line_point1, line_point2):
|
||||
# 对于两点坐标为同一点时,返回点与点的距离
|
||||
if line_point1 == line_point2:
|
||||
point_array = np.array(point)
|
||||
point1_array = np.array(line_point1)
|
||||
return np.linalg.norm(point_array - point1_array)
|
||||
# 计算直线的三个参数
|
||||
A = line_point2[1] - line_point1[1]
|
||||
B = line_point1[0] - line_point2[0]
|
||||
C = (line_point1[1] - line_point2[1]) * line_point1[0] + \
|
||||
(line_point2[0] - line_point1[0]) * line_point1[1]
|
||||
# 根据点到直线的距离公式计算距离
|
||||
distance = np.abs(A * point[0] + B * point[1] + C) / (np.sqrt(A ** 2 + B ** 2))
|
||||
return distance
|
||||
|
||||
# 判断方形是否在轮廓内
|
||||
def square_in_polygon_default(square, polygon):
|
||||
for point in square:
|
||||
if not point_in_polygon(point, polygon):
|
||||
return False
|
||||
return True
|
||||
|
||||
# 自定义二维码初始坐标
|
||||
def get_default_qr_points(foot_points):
|
||||
max_x = max([x[0] for x in foot_points])
|
||||
min_x = min([x[0] for x in foot_points])
|
||||
max_y = max([x[1] for x in foot_points])
|
||||
min_y = min([x[1] for x in foot_points])
|
||||
|
||||
center_x, center_y = (max_x + min_x) / 2, (max_y + min_y) / 2
|
||||
flag_default = point_in_polygon((center_x, center_y), foot_points)
|
||||
if not flag_default:
|
||||
index_move = 0
|
||||
while not flag_default and index_move < 5:
|
||||
center_x = (center_x + min_x) / 2
|
||||
index_move += 1
|
||||
flag_default = point_in_polygon((center_x, center_y), foot_points)
|
||||
if not flag_default:
|
||||
while not flag_default:
|
||||
center_y = (center_y + min_y) / 2
|
||||
flag_default = point_in_polygon((center_x, center_y), foot_points)
|
||||
|
||||
length = min((center_x - min_x) / 2, (center_y - min_y) / 2) / 2
|
||||
# 在不规则平面中心位置初始化一个方形
|
||||
qr_points = [(center_x - length, center_y + length), (center_x + length, center_y + length), (center_x + length, center_y - length), (center_x - length, center_y - length)]
|
||||
qr_points = scale_qr_new(foot_points, qr_points, length, (center_x, center_y), scale=1.05)
|
||||
return qr_points
|
||||
|
||||
def scale_qr_new(foot_points, qr_points, length, center, scale=1.1):
|
||||
default_flag = flag = square_in_polygon(qr_points, foot_points)
|
||||
center_x, center_y = center[0], center[1]
|
||||
if flag:
|
||||
while default_flag == flag:
|
||||
length *= scale
|
||||
# 对每个点进行放大操作并更新坐标
|
||||
qr_points = [((x - center_x) * scale + center_x, (y - center_y) * scale + center_y) for x, y in qr_points]
|
||||
flag = square_in_polygon_default(qr_points, foot_points) and square_in_polygon(qr_points, foot_points)
|
||||
else:
|
||||
while default_flag == flag:
|
||||
length /= scale
|
||||
# 对每个点进行缩小操作并更新坐标
|
||||
qr_points = [((x - center_x) / scale + center_x, (y - center_y) / scale + center_y) for x, y in qr_points]
|
||||
flag = square_in_polygon_default(qr_points, foot_points) and square_in_polygon(qr_points, foot_points)
|
||||
return qr_points
|
||||
|
||||
# 获取旋转后方形 根据方形原坐标旋转
|
||||
def cal_rota_points(qr_points, center, angle):
|
||||
center_x, center_y = center[0], center[1]
|
||||
if angle > 0:
|
||||
qr_points_after_rotate = []
|
||||
for point in qr_points:
|
||||
new_x = (point[0] - center_x) * math.cos(angle) - (point[1] - center_y) * math.sin(angle) + center_x
|
||||
new_y = (point[0] - center_x) * math.sin(angle) + (point[1] - center_y) * math.cos(angle) + center_y
|
||||
qr_points_after_rotate.append((new_x, new_y))
|
||||
return qr_points_after_rotate
|
||||
else:
|
||||
return qr_points
|
||||
|
||||
# 取中点
|
||||
def cal_middle_point(p1, p2):
|
||||
x1, y1 = p1
|
||||
x2, y2 = p2
|
||||
# 中点
|
||||
a1 = (x1 + x2) / 2
|
||||
b1 = (y1 + y2) / 2
|
||||
return a1, b1
|
||||
|
||||
def make_points(qr_points):
|
||||
new_points = []
|
||||
index = [0, 1, 2, 3, 0]
|
||||
for i in range(4):
|
||||
a, b = cal_middle_point(qr_points[index[i]], qr_points[index[i + 1]])
|
||||
new_points.append((a, b))
|
||||
new_points.append((cal_middle_point(qr_points[index[i]], (a, b))))
|
||||
new_points.append((cal_middle_point(qr_points[index[i + 1]], (a, b))))
|
||||
return new_points
|
||||
|
||||
#qr_points = get_default_qr_points(foot_points)
|
||||
|
||||
min_qr_length = 0.5
|
||||
|
||||
minx = min([p[0] for p in foot_points]) + min_qr_length
|
||||
maxx = max([p[0] for p in foot_points]) - min_qr_length
|
||||
miny = min([p[1] for p in foot_points]) + min_qr_length
|
||||
maxy = max([p[1] for p in foot_points]) - min_qr_length
|
||||
|
||||
def rotate_qr_v3(foot_points, qr_points, scale, angle=1):
|
||||
best_length = length = cal_square_length(qr_points)
|
||||
best_angle, default_angle = 0, 0
|
||||
center_x, center_y = calculate_center(qr_points)
|
||||
best_qr_points = qr_points
|
||||
# 循环1 求最佳angle 不断增大angle角度
|
||||
while default_angle <= 90:
|
||||
qr_points_after_rotate = cal_rota_points(qr_points, (center_x, center_y), default_angle)
|
||||
# 在当前angle下增加边长
|
||||
while square_in_polygon(qr_points_after_rotate, foot_points) and dis_flag(qr_points_after_rotate, foot_points):
|
||||
flag = True
|
||||
best_qr_points = qr_points_after_rotate
|
||||
best_angle = default_angle
|
||||
best_length = length
|
||||
# 对每个点进行放大(或缩小)操作并更新坐标
|
||||
qr_points = [((x - center_x) * scale + center_x, (y - center_y) * scale + center_y) for x, y in qr_points]
|
||||
length *= scale
|
||||
qr_points_after_rotate = cal_rota_points(qr_points, (center_x, center_y), default_angle)
|
||||
# 限制最大边长
|
||||
if best_length > 5:
|
||||
return best_qr_points, best_angle, best_length
|
||||
|
||||
default_angle += angle
|
||||
return best_qr_points, best_angle, best_length
|
||||
|
||||
if maxx - minx < maxy - miny:
|
||||
step = (maxx - minx) / 15
|
||||
else:
|
||||
step = (maxy - miny) / 15
|
||||
|
||||
x, y = minx, miny
|
||||
locations = []
|
||||
|
||||
while x <= maxx:
|
||||
while y <= maxy:
|
||||
locations.append((x, y))
|
||||
y += step
|
||||
x += step
|
||||
y = miny
|
||||
|
||||
# print(f'locations: {locations}')
|
||||
locations = [point for point in locations if all(cal_distance(point, f) >= min_qr_length for f in foot_points)]
|
||||
location = locations[0]
|
||||
qr_points = [(location[0] - 0.5, location[1] - 0.5), (location[0] + 0.5, location[1] - 0.5), (location[0] + 0.5, location[1] + 0.5), (location[0] - 0.5, location[1] + 0.5)]
|
||||
plot(foot_points)
|
||||
plot(qr_points, 'yellow')
|
||||
plt.savefig(f'{workdir}{pid}_{order_id}/fig.png')
|
||||
|
||||
best_qr, max_qr_length, best_location, best_rotation = None, 0, None, 0
|
||||
for location in locations:
|
||||
plt.plot(location[0], location[1], 'ro')
|
||||
qr_points = move_square(qr_points, location)
|
||||
if not square_in_polygon(qr_points, foot_points) or not square_in_polygon_default(qr_points, foot_points):
|
||||
continue
|
||||
else:
|
||||
# qr_points = scale_qr(foot_points, qr_points, 1.1)
|
||||
# qrs.append(qr_points)
|
||||
rotate_qr, rotate_angle, qr_length = rotate_qr_v3(foot_points, qr_points, 1.1, 1)
|
||||
if qr_length > max_qr_length:
|
||||
max_qr_length = qr_length
|
||||
best_location = location
|
||||
best_rotation = rotate_angle
|
||||
best_qr = rotate_qr
|
||||
|
||||
rd = max_qr_length / 1.1 / 2
|
||||
x, y = best_location[0], best_location[1]
|
||||
new_qr_points = [(x - rd, y + rd), (x + rd, y + rd), (x + rd, y - rd), (x - rd, y - rd)]
|
||||
new_qr_points = cal_rota_points(new_qr_points, best_location, best_rotation)
|
||||
return new_qr_points, best_location, max_qr_length / 1.1, best_rotation
|
||||
|
||||
def get_plane_points(plane, print_points = False):
|
||||
points = []
|
||||
for edge in plane.data.edges:
|
||||
point_index = edge.vertices[0]
|
||||
point3d = plane.data.vertices[point_index].co
|
||||
if print_points: print(point3d)
|
||||
points.append((point3d[0], point3d[1]))
|
||||
return points
|
||||
|
||||
def point_in_polygon(point, polygon):
|
||||
num_intersections = 0
|
||||
for i in range(len(polygon)):
|
||||
p1, p2 = polygon[i], polygon[(i + 1) % len(polygon)]
|
||||
if (p1[1] > point[1]) != (p2[1] > point[1]):
|
||||
if point[0] < (p2[0] - p1[0]) * (point[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]:
|
||||
num_intersections += 1
|
||||
return num_intersections % 2 == 1
|
||||
|
||||
def square_iou_polygon(square, polygon):
|
||||
for point in square:
|
||||
if point_in_polygon(point, polygon):
|
||||
return True
|
||||
return False
|
||||
|
||||
def square_in_polygon(square, polygon):
|
||||
for point in polygon:
|
||||
if point_in_polygon(point, square):
|
||||
return False
|
||||
return True
|
||||
|
||||
def plot(points, color='blue'):
|
||||
x = [point[0] for point in points]
|
||||
y = [point[1] for point in points]
|
||||
if points[-1] != points[0]:
|
||||
x.append(points[0][0])
|
||||
y.append(points[0][1])
|
||||
plt.plot(x, y, color=color)
|
||||
|
||||
def scale_qr(foot_points, qr_points, scale = 1.1):
|
||||
while True:
|
||||
old_points = qr_points
|
||||
# 计算正方形的中心坐标
|
||||
center_x = sum(x for x, y in qr_points) / len(qr_points)
|
||||
center_y = sum(y for x, y in qr_points) / len(qr_points)
|
||||
|
||||
# 对每个点进行放大(或缩小)操作并更新坐标
|
||||
qr_points = [((x - center_x) * scale + center_x, (y - center_y) * scale + center_y) for x, y in qr_points]
|
||||
|
||||
if not square_in_polygon(qr_points, foot_points):
|
||||
qr_points = old_points
|
||||
break
|
||||
return qr_points
|
||||
|
||||
def rotate_qr(foot_points, qr_points, angle = 0.1):
|
||||
while True:
|
||||
old_points = qr_points
|
||||
# 计算正方形的中心坐标
|
||||
center_x = sum(x for x, y in qr_points) / len(qr_points)
|
||||
center_y = sum(y for x, y in qr_points) / len(qr_points)
|
||||
|
||||
# 对每个点进行放大(或缩小)操作并更新坐标
|
||||
qr_points = [(x - center_x, y - center_y) for x, y in qr_points]
|
||||
|
||||
qr_points = [(x * math.cos(angle) - y * math.sin(angle), x * math.sin(angle) + y * math.cos(angle)) for x, y in qr_points]
|
||||
|
||||
qr_points = [(x + center_x, y + center_y) for x, y in qr_points]
|
||||
|
||||
if not square_in_polygon(qr_points, foot_points):
|
||||
qr_points = old_points
|
||||
break
|
||||
return qr_points
|
||||
|
||||
def scale_square(scale, foot_points, back = 0.0):
|
||||
while True:
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
old_dimensions = bpy.data.objects['qr'].dimensions.copy()
|
||||
active_object(bpy.data.objects['qr'])
|
||||
bpy.data.objects['qr'].scale = (scale, scale, 1)
|
||||
max_square = get_plane_points(bpy.data.objects['qr'])
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
if not square_in_polygon(max_square, foot_points):
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
bpy.data.objects['qr'].dimensions = (old_dimensions[0] - back, old_dimensions[1] - back, 0)
|
||||
max_square = get_plane_points(bpy.data.objects['qr'])
|
||||
location, size = get_square_center_size()
|
||||
break
|
||||
return max_square, location, size
|
||||
|
||||
def zoom_square(foot_points, qr_points, center, step_length=0.1):
|
||||
while True:
|
||||
old_dimensions = bpy.data.objects['qr'].dimensions.copy()
|
||||
active_object(bpy.data.objects['qr'])
|
||||
# print(f'old_dimensions: {old_dimensions}')
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
bpy.data.objects['qr'].dimensions = (old_dimensions[0] + step_length, old_dimensions[1] + step_length, 0)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
# print(f'new_dimensions: {bpy.data.objects["qr"].dimensions}')
|
||||
max_square = get_plane_points(bpy.data.objects['qr'])
|
||||
if not square_in_polygon(max_square, foot_points):
|
||||
bpy.data.objects['qr'].dimensions = (old_dimensions[0], old_dimensions[1], 0)
|
||||
max_square = get_plane_points(bpy.data.objects['qr'])
|
||||
location, size, length = get_square_center_size()
|
||||
break
|
||||
return max_square, location, size, length
|
||||
|
||||
def get_square_center_size():
|
||||
active_object(bpy.data.objects['qr'])
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
location = bpy.data.objects['qr'].location
|
||||
size = bpy.data.objects['qr'].dimensions
|
||||
length = size[0]
|
||||
return location, size, length
|
||||
|
||||
def min_x(plane):
|
||||
return min([p[0] for p in plane])
|
||||
def min_y(plane):
|
||||
return min([p[1] for p in plane])
|
||||
def max_x(plane):
|
||||
return max([p[0] for p in plane])
|
||||
def max_y(plane):
|
||||
return max([p[1] for p in plane])
|
||||
|
||||
def cal_square_length(square):
|
||||
return abs(square[0][0] - square[1][0])
|
||||
|
||||
def cal_square_area(square):
|
||||
return abs(square[0][0] - square[1][0]) * abs(square[0][1] - square[3][1])
|
||||
|
||||
def cal_distance(point1, point2):
|
||||
return math.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)
|
||||
|
||||
def calculate_center(vertices):
|
||||
x_sum = sum(x for x, y in vertices)
|
||||
y_sum = sum(y for x, y in vertices)
|
||||
center_x = x_sum / len(vertices)
|
||||
center_y = y_sum / len(vertices)
|
||||
return center_x, center_y
|
||||
|
||||
def move_square(vertices, new_center):
|
||||
center_x, center_y = calculate_center(vertices)
|
||||
# print(f'center_x: {center_x}, center_y: {center_y}')
|
||||
# print(f'new_center: {new_center}')
|
||||
x_diff = center_x - new_center[0]
|
||||
y_diff = center_y - new_center[1]
|
||||
# print(f'x_diff: {x_diff}, y_diff: {y_diff}')
|
||||
# print(f'vertices: {vertices}')
|
||||
new_vertices = [(x - x_diff, y - y_diff) for x, y in vertices]
|
||||
# print(f'new_vertices: {new_vertices}')
|
||||
return new_vertices
|
||||
|
||||
def main(workdir, pid, order_id, print_id):
|
||||
if not os.path.exists(os.path.join(workdir, f'{pid}_{order_id}')):
|
||||
os.makedirs(os.path.join(workdir, f'{pid}_{order_id}'))
|
||||
qr_path = os.path.join(workdir, f'{pid}_{order_id}' ,'qr.png')
|
||||
gen_data_matrix(print_id, qr_path)
|
||||
try:
|
||||
get_obj_max_foot()
|
||||
except Exception as e:
|
||||
print(f"get obj max foot err {e}")
|
||||
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
|
||||
qr_points = get_plane_points(bpy.data.objects['qr'])
|
||||
active_object(bpy.data.objects['foot'])
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
foot_points = get_plane_points(bpy.data.objects['foot'])
|
||||
# print('foot_points:', foot_points)
|
||||
foot_points = nearest_neighbor_sort(foot_points)
|
||||
try:
|
||||
max_qr, qr_location, max_qr_length, rotation = get_max_qr(foot_points)
|
||||
except Exception as e:
|
||||
print("异常处理错误")
|
||||
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
print(f'qr_location: {qr_location}')
|
||||
plt.plot(qr_location[0], qr_location[1], 'black')
|
||||
plot(max_qr, 'green')
|
||||
plt.axis('equal')
|
||||
plt.savefig(os.path.join(workdir, f'{pid}_{order_id}', 'fig.png'))
|
||||
|
||||
bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}_{order_id}/{pid}_qr_start.blend')
|
||||
|
||||
qr_position = {}
|
||||
qr_location = (qr_location[0], qr_location[1], 0)
|
||||
qr_dimensions = (max_qr_length, max_qr_length, 0)
|
||||
# print(f'qr_location: {qr_location}')
|
||||
# print(f'qr_dimensions: {qr_dimensions}')
|
||||
qr_position["location"] = qr_location
|
||||
qr_position["dimensions"] = qr_dimensions
|
||||
qr_position["rotation"] = rotation
|
||||
print(f'qr_position: {qr_position}')
|
||||
# with open(os.path.join(workdir, f'{pid}_{order_id}', 'qr_position.txt'), 'w') as f:
|
||||
# f.write(json.dumps(qr_position))
|
||||
|
||||
res = requests.get(f'{upload_qr_position_url}?print_id={print_id}&position_data={json.dumps(qr_position)}')
|
||||
print(f'update_qr_position_url {upload_qr_position_url}:{res.text}')
|
||||
|
||||
bpy.ops.object.load_reference_image(filepath=os.path.join(workdir, f'{pid}_{order_id}', 'qr.png'))
|
||||
bpy.context.object.rotation_euler = (math.radians(-180), math.radians(0), rotation)
|
||||
bpy.ops.transform.translate(value=qr_location, orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements={'INCREMENT'}, use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False, release_confirm=True)
|
||||
|
||||
bpy.context.object.empty_display_size = qr_dimensions[0]
|
||||
|
||||
# for obj in bpy.data.objects:
|
||||
# if obj.type == 'MESH' and obj.name != pid:
|
||||
# bpy.data.objects.remove(obj)
|
||||
|
||||
# qr_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}_{order_id}Tex1_qr.png")
|
||||
# jpg_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg")
|
||||
# jpg_img = Image.open(jpg_path)
|
||||
# shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_noqr.jpg"))
|
||||
|
||||
# bpy.context.scene.eyek.res_x = jpg_img.width
|
||||
# bpy.context.scene.eyek.res_y = jpg_img.height
|
||||
# bpy.context.scene.eyek.path_export_image = qr_path
|
||||
# bpy.data.objects[f'{pid}'].select_set(True)
|
||||
# bpy.data.objects['Empty'].select_set(True)
|
||||
# bpy.context.view_layer.objects.active = bpy.data.objects[f'{pid}']
|
||||
# bpy.ops.eyek.exe()
|
||||
|
||||
# qr_img = Image.open(qr_path)
|
||||
# jpg_img.paste(qr_img, (0, 0), qr_img)
|
||||
# jpg_img.save(jpg_path)
|
||||
|
||||
# plt.axis('equal')
|
||||
# plt.show()
|
||||
|
||||
# 保存blend文件
|
||||
bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}_{order_id}/{pid}_qr_end.blend')
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
get_qr_position_url = 'https://mp.api.suwa3d.com/api/printOrder/getFootCodePositionData'
|
||||
upload_qr_position_url = 'https://mp.api.suwa3d.com/api/printOrder/updateFootCodeStatus'
|
||||
get_pid_by_printid_url = 'https://mp.api.suwa3d.com/api/printOrder/getPidByPrintId'
|
||||
delete_form_foot_code_by_pid = 'https://mp.api.suwa3d.com/api/printOrder/deleteFormFootCodeByPid'
|
||||
# get_qr_position_url = 'http://172.31.1.254:8199/api/printOrder/getFootCodePositionData'
|
||||
# upload_qr_position_url = 'http://172.31.1.254:8199/api/printOrder/updateFootCodeStatus'
|
||||
# get_pid_by_printid_url = 'http://172.31.1.254:8199/api/printOrder/getPidByPrintId'
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
workdir = 'E:\\print\\foot\\'
|
||||
else:
|
||||
workdir = '/data/datasets/foot/'
|
||||
|
||||
print(sys.argv)
|
||||
if len(sys.argv) - (sys.argv.index("--") + 1) < 1:
|
||||
print("Usage: blender -b -P auto_dm.py -- <pid_order_id_print_id>")
|
||||
sys.exit(1)
|
||||
pid, order_id, print_id = sys.argv[sys.argv.index("--") + 1].split('_')
|
||||
|
||||
main(workdir, pid, order_id, print_id)
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
This version of Blender has been originally released at www.blender.org.
|
||||
It is subject to the GNU GPL license, which is part of this download.
|
||||
|
||||
Blender, the free and open source 3D creation suite
|
||||
Copyright (C) 2023 Blender Foundation
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
The Blender project uses code and libraries which are licensed under terms of
|
||||
licenses which are compatible with the GNU GPL 3 or later.
|
||||
|
||||
The license of each individual Blender source file can be found in the first
|
||||
few lines after "SPDX-License-Identifier:". The license text of every such
|
||||
license can be found in license/.
|
||||
|
||||
The complete overview of copyright and licenses of third-party libraries used
|
||||
by Blender can be found in THIRD-PARTY-LICENSES.txt.
|
||||
|
||||
Stichting Blender Foundation
|
||||
Amsterdam
|
||||
the Netherlands
|
||||
Chamber of Commerce: 34176425
|
||||
|
||||
http://www.blender.org
|
||||
foundation@blender.org
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import bpy, sys, os, math
|
||||
|
||||
pid = '26385'
|
||||
workdir = '/home/water/Downloads/'
|
||||
|
||||
filename = f'{workdir}{pid}/{pid}_9cm_x1.obj'
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
# 坐标复位
|
||||
obj = bpy.context.selected_objects[0]
|
||||
obj.rotation_euler[0] = 0
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Y', 'Z'})
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
# bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_align_yz.obj')
|
||||
|
||||
# 躺平到打印机排版需要的坐标与角度
|
||||
obj.rotation_euler = (math.radians(90), math.radians(90), 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
# bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_rotate_y90.obj')
|
||||
|
||||
heights = {}
|
||||
min_height = 999999
|
||||
min_i = 0
|
||||
max_height = -999999
|
||||
max_i = 0
|
||||
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_VOLUME', center='MEDIAN')
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_3', align_axis={'X', 'Y', 'Z'})
|
||||
|
||||
# 步进精度2旋转X轴到180度,找到Y轴最低点和最高点,其中最低点为打印
|
||||
step = 2
|
||||
i = 0
|
||||
while i <= 180:
|
||||
obj.rotation_euler = (math.radians(step), 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
if obj.dimensions[1] < min_height:
|
||||
min_height = obj.dimensions[1]
|
||||
min_i = i
|
||||
if obj.dimensions[1] > max_height:
|
||||
max_height = obj.dimensions[1]
|
||||
max_i = i
|
||||
heights[i] = (obj.dimensions[0], obj.dimensions[1], obj.dimensions[2])
|
||||
print(i, heights[i])
|
||||
i += step
|
||||
|
||||
obj.rotation_euler = (0, 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
obj.rotation_euler = (math.radians(min_i), 0, 0)
|
||||
bpy.ops.object.transform_apply(rotation=True)
|
||||
#bpy.ops.export_scene.obj(filepath=f'{workdir}{pid}_miny.obj')
|
||||
print(f'最小高度: {min_height} @ {heights[min_i]}min_i:{min_i}' , f'最大高度: {max_height} @ {heights[max_i]}max_i:{max_i}')
|
||||
|
||||
offset = 45.5
|
||||
radian = math.radians(90)
|
||||
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(offset, 0, 0), rotation=(0, radian, 0), scale=(1, 1, 1))
|
||||
|
||||
# 布尔切割,保留交集切面
|
||||
bpy.ops.object.modifier_add(type='BOOLEAN')
|
||||
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[pid]
|
||||
bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT'
|
||||
bpy.context.object.modifiers["Boolean"].solver = 'FAST'
|
||||
bpy.ops.object.modifier_apply(modifier="Boolean")
|
||||
|
||||
# 拆分切割面为多个多边形,然后遍历多边形,找到最大的面积
|
||||
bpy.ops.mesh.separate(type='LOOSE')
|
||||
|
||||
max_area = 0
|
||||
max_obj = None
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.name.startswith('Plane'):
|
||||
area = obj.data.polygons[0].area
|
||||
if area > max_area:
|
||||
max_area = area
|
||||
max_obj = obj
|
||||
|
||||
# 选中最大面积的多边形,然后计算中心点
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
max_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = max_obj
|
||||
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
|
||||
|
||||
bpy.ops.import_scene.obj(filepath=f'{workdir}{pid}/qrcode.obj')
|
||||
qr_obj = bpy.data.objects['qrcode']
|
||||
shore_obj = bpy.data.objects['Cube.001']
|
||||
qr_obj.location = (max_obj.location[0] - qr_obj.dimensions[0] / 2 - shore_obj.dimensions[0], max_obj.location[1], max_obj.location[2])
|
||||
shore_obj.location = (qr_obj.location[0]-0.01, max_obj.location[1], max_obj.location[2])
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH' and obj.name.startswith('Plane'):
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -0,0 +1 @@
|
||||
{"data": [{"location": {"x": 7.40227746963501, "y": -0.10000000149011612, "z": 4.669338226318359}, "rotation_euler": {"x": 1.570796251296997, "y": 3.0, "z": -0.0}, "scale": {"x": 4.177248001098633, "y": 4.177248001098633, "z": 4.177248001098633}, "fov_x": -4.177248001098633, "limit_near": 0.009999999776482582, "limit_far": 200.0, "image_path": "E:\\print\\foot\\152696_56145\\qr.png"}]}
|
||||
@@ -0,0 +1,715 @@
|
||||
import os, sys, bpy, math, time, platform, cairosvg, ppf.datamatrix, shutil, requests, json, redis, oss2, cv2,qrcode
|
||||
from retrying import retry
|
||||
import subprocess
|
||||
import random
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from PIL import Image, ImageEnhance
|
||||
from addon_utils import enable
|
||||
import logging,atexit,platform
|
||||
# if platform.system() == 'Windows':
|
||||
sys.path.append('/home/acprint/code/libs/')
|
||||
import common
|
||||
logging.basicConfig(filename='foot_update_res.log', level=logging.ERROR)
|
||||
enable('io_import_images_as_planes')
|
||||
enable('eyek_addon')
|
||||
|
||||
|
||||
#查询影棚ID
|
||||
def getPSid(pid):
|
||||
res = requests.get("https://mp.api.suwa3d.com/api/customerP3dLog/photoStudio",params={"pid":pid})
|
||||
res = json.loads(res.text)
|
||||
return str(res['data'])
|
||||
|
||||
|
||||
def restart_current_process(new_command):
|
||||
try:
|
||||
# 保存新进程的命令
|
||||
command = new_command.split()
|
||||
|
||||
# 启动新进程
|
||||
new_process = subprocess.Popen(command)
|
||||
|
||||
# 打印新进程的PID
|
||||
print(f"New process started with PID: {new_process.pid}")
|
||||
|
||||
# 终止当前进程
|
||||
os._exit(0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
#生成二维码图片
|
||||
def gen_data_matrix(print_id,pid, qr_path, size = 300):
|
||||
psid = getPSid(pid)
|
||||
if int(psid) == 0:
|
||||
print("费工夫构建脚底二维码")
|
||||
# if use_foot_type == "short_url":
|
||||
#调用接口获取短网址信息
|
||||
short_url = get_short_url(print_id)
|
||||
if short_url == False:
|
||||
return
|
||||
temp_foot_data = short_url
|
||||
|
||||
#生成二维码
|
||||
qr = qrcode.QRCode(
|
||||
version=1,
|
||||
error_correction = qrcode.constants.ERROR_CORRECT_L,
|
||||
box_size=10,
|
||||
border=2,
|
||||
)
|
||||
qr.add_data(temp_foot_data)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black",back_color="white").resize((size,size))
|
||||
img.save(qr_path)
|
||||
else:
|
||||
#正常生成不是常规的二维码
|
||||
print("排除费工夫正常构建二维码")
|
||||
svg = ppf.datamatrix.DataMatrix(f'p{print_id}').svg()
|
||||
cairosvg.svg2png(bytestring=svg, write_to=qr_path, output_width=size, output_height=size, background_color='white')
|
||||
|
||||
def active_object(obj):
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
#下载oss的文件
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=2000)
|
||||
def down_obj_fromoss(pid, print_type=1, order_id=None):
|
||||
# print_type:// 打印状态 1:正常打印 2:重打 3:加打,4: 样品
|
||||
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 开始下载模型,若网络异常将每间隔2秒重试10次...')
|
||||
if not order_id is None:
|
||||
path = os.path.join(workdir, f'{pid}_{order_id}')
|
||||
else:
|
||||
path = os.path.join(workdir, pid)
|
||||
if os.path.exists(path):
|
||||
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 已存在模型文件,删除后重新下载')
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
os.makedirs(path)
|
||||
|
||||
# 下载分2种情况,一种是第一次打印,下载标准{pid}.obj,{pid}.mtl,{pid}Tex1.jpg,另一种是重打或加打,obj文件名可以从oss上任意获取一个,但是mtl和jpg文件名是固定的
|
||||
res = oss_client.get_object_to_file(f'objs/print/{pid}/{pid}.mtl', os.path.join(path, f'{pid}.mtl'))
|
||||
last_modified = oss_client.get_object_meta(f"objs/print/{pid}/{pid}.mtl").last_modified
|
||||
print(f'mtl文件最后修改时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_modified))}')
|
||||
print(f'下载文件:objs/print/{pid}/{pid}.mtl,状态:{res.status}')
|
||||
if oss_client.object_exists(f'objs/print/{pid}/{pid}Tex1.jpg'):
|
||||
res = oss_client.get_object_to_file(f'objs/print/{pid}/{pid}Tex1.jpg', os.path.join(path, f'{pid}Tex1.jpg'))
|
||||
last_modified = oss_client.get_object_meta(f"objs/print/{pid}/{pid}Tex1.jpg").last_modified
|
||||
print(f'jpg文件最后修改时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_modified))}')
|
||||
print(f'下载文件:objs/print/{pid}/{pid}Tex1.jpg,状态:{res.status}')
|
||||
else:
|
||||
res = oss_client.get_object_to_file(f'objs/print/{pid}/{pid}.jpg', os.path.join(path, f'{pid}Tex1.jpg'))
|
||||
last_modified = oss_client.get_object_meta(f"objs/print/{pid}/{pid}.jpg").last_modified
|
||||
print(f'jpg文件最后修改时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_modified))}')
|
||||
print(f'下载文件:objs/print/{pid}/{pid}.jpg,状态:{res.status}')
|
||||
if oss_client.object_exists(f'objs/print/{pid}/{pid}.obj'):
|
||||
res = oss_client.get_object_to_file(f'objs/print/{pid}/{pid}.obj', os.path.join(path, f'{pid}.obj'))
|
||||
last_modified = oss_client.get_object_meta(f"objs/print/{pid}/{pid}.obj").last_modified
|
||||
print(f'obj文件最后修改时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_modified))}')
|
||||
print(f'下载文件:objs/print/{pid}/{pid}.obj,状态:{res.status}')
|
||||
else:
|
||||
prefix = f'objs/print/{pid}/'
|
||||
filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix)
|
||||
for file in filelist:
|
||||
filename = file.key.split('/')[-1]
|
||||
if filename == '': continue
|
||||
if filename.endswith(f'.obj'):
|
||||
res = oss_client.get_object_to_file(file.key, os.path.join(path, f'{pid}.obj'))
|
||||
last_modified = oss_client.get_object_meta(file.key).last_modified
|
||||
print(f'obj文件最后修改时间:{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_modified))}')
|
||||
print(f'下载文件:{file.key},状态:{res.status}')
|
||||
break
|
||||
#查找obj
|
||||
def find_obj(pid, order_id):
|
||||
find = False
|
||||
if os.path.exists(os.path.join(sourceFilePath, f'{pid}_{order_id}', f'{pid}.obj')):
|
||||
|
||||
if not os.path.exists(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.obj')):
|
||||
#复制文件到 指定目录
|
||||
shutil.copytree(os.path.join(sourceFilePath, f'{pid}_{order_id}'), os.path.join(workdir,f'{pid}_{order_id}'))
|
||||
|
||||
# shutil.copy(f"/data/datasets/print/{pid}_{order_id}/{pid}.obj", os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.obj'))
|
||||
# shutil.copy(f"/data/datasets/print/{pid}_{order_id}/{pid}.mtl", os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl'))
|
||||
# shutil.copy(f"/data/datasets/print/{pid}_{order_id}/{pid}.Tex1.jpg", os.path.join(workdir, f'{pid}_{order_id}', f'{pid}Tex1.jpg'))
|
||||
else:
|
||||
print('没有找到obj模型文件,开始下载')
|
||||
down_obj_fromoss(pid, order_id=order_id)
|
||||
if os.path.exists(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.jpg')):
|
||||
shutil.move(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.jpg'), os.path.join(workdir, f'{pid}_{order_id}', f'{pid}Tex1.jpg'))
|
||||
with open(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl'), 'r') as f:
|
||||
lines = f.readlines()
|
||||
lines = [line.replace(f'map_Kd {pid}.jpg', f'map_Kd {pid}Tex1.jpg') for line in lines]
|
||||
with open(os.path.join(workdir, f'{pid}_{order_id}', f'{pid}.mtl'), 'w') as f:
|
||||
f.writelines(lines)
|
||||
filelist = os.listdir(os.path.join(workdir, f'{pid}_{order_id}'))
|
||||
for filename in filelist:
|
||||
if f'{pid}.obj' in filename:
|
||||
find = True
|
||||
return filename
|
||||
print('没有找到obj模型文件')
|
||||
return ''
|
||||
|
||||
def find_pid_objname(pid):
|
||||
for obj in bpy.data.objects:
|
||||
if obj.name.startswith(str(pid)):
|
||||
return obj.name
|
||||
|
||||
def ps_color_scale_adjustment(image, shadow=0, highlight=255, midtones=1):
|
||||
'''
|
||||
模拟 PS 的色阶调整; 0 <= Shadow < Highlight <= 255
|
||||
:param image: 传入的图片
|
||||
:param shadow: 黑场(0-Highlight)
|
||||
:param highlight: 白场(Shadow-255)
|
||||
:param midtones: 灰场(9.99-0.01)
|
||||
:return: 图片
|
||||
'''
|
||||
if highlight > 255:
|
||||
highlight = 255
|
||||
if shadow < 0:
|
||||
shadow = 0
|
||||
if shadow >= highlight:
|
||||
shadow = highlight - 2
|
||||
if midtones > 9.99:
|
||||
midtones = 9.99
|
||||
if midtones < 0.01:
|
||||
midtones = 0.01
|
||||
image = np.array(image, dtype=np.float16)
|
||||
# 计算白场 黑场离差
|
||||
Diff = highlight - shadow
|
||||
image = image - shadow
|
||||
image[image < 0] = 0
|
||||
image = (image / Diff) ** (1 / midtones) * 255
|
||||
image[image > 255] = 255
|
||||
image = np.array(image, dtype=np.uint8)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def show_histogram(image, image_id, save_hist_dir, min_threshold, max_threshold):
|
||||
'''
|
||||
画出直方图展示
|
||||
:param image: 导入图片
|
||||
:param image_id: 图片id编号
|
||||
:param save_hist_dir: 保存路径
|
||||
:param min_threshold: 最小阈值
|
||||
:param max_threshold: 最大阈值
|
||||
:return: 原图image,和裁剪原图直方图高低阈值后的图片image_change
|
||||
'''
|
||||
plt.rcParams['font.family'] = 'SimHei'
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
plt.hist(image.ravel(), 254, range=(2, 256), density=False)
|
||||
plt.hist(image.ravel(), 96, range=(2, 50), density=False) # 放大 range(0, 50),bins值最好是range的两倍,显得更稀疏,便于对比
|
||||
plt.hist(image.ravel(), 110, range=(200, 255), density=False) # 放大 range(225, 255)
|
||||
plt.annotate('thresh1=' + str(min_threshold), # 文本内容
|
||||
xy=(min_threshold, 0), # 箭头指向位置 # 阈值设定值!
|
||||
xytext=(min_threshold, 500000), # 文本位置 # 阈值设定值!
|
||||
arrowprops=dict(facecolor='black', width=1, shrink=5, headwidth=2)) # 箭头
|
||||
plt.annotate('thresh2=' + str(max_threshold), # 文本内容
|
||||
xy=(max_threshold, 0), # 箭头指向位置 # 阈值设定值!
|
||||
xytext=(max_threshold, 500000), # 文本位置 # 阈值设定值!
|
||||
arrowprops=dict(facecolor='black', width=1, shrink=5, headwidth=2)) # 箭头
|
||||
# 在y轴上绘制一条直线
|
||||
# plt.axhline(y=10000, color='r', linestyle='--', linewidth=0.5)
|
||||
plt.title(str(image_id))
|
||||
# plt.show()
|
||||
# 保存直方图
|
||||
save_hist_name = os.path.join(save_hist_dir, f'{image_id}_{min_threshold}&{max_threshold}.jpg')
|
||||
plt.savefig(save_hist_name)
|
||||
# 清空画布, 防止重叠展示
|
||||
plt.clf()
|
||||
|
||||
|
||||
def low_find_histogram_range(image, target_frequency):
|
||||
'''
|
||||
循环查找在 target_frequency (y)频次限制下的直方图区间值(x)
|
||||
:param image: 导入图片
|
||||
:param target_frequency: 直方图 y 频次限制条件
|
||||
:return: 直方图区间 x,和 该区间频次 y
|
||||
'''
|
||||
# 计算灰度直方图
|
||||
hist, bins = np.histogram(image, bins=256, range=[0, 256])
|
||||
# 初始化区间和频次
|
||||
interval = 2
|
||||
frequency = hist[255]
|
||||
while frequency < target_frequency:
|
||||
# 更新区间和频次
|
||||
interval += 1
|
||||
# 检查直方图的频次是否为None,如果频次是None,则将其设为0,这样可以避免将None和int进行比较报错。
|
||||
frequency = hist[interval] if hist[interval] is not None else 0
|
||||
frequency += hist[interval] if hist[interval] is not None else 0
|
||||
# 如果频次接近10000则停止循环
|
||||
if target_frequency - 2000 <= frequency <= target_frequency + 1000:
|
||||
break
|
||||
|
||||
return interval, frequency
|
||||
|
||||
|
||||
def high_find_histogram_range(image, target_frequency):
|
||||
'''
|
||||
循环查找在 target_frequency (y)频次限制下的直方图区间值(x)
|
||||
:param image: 导入图片
|
||||
:param target_frequency: 直方图 y 频次限制条件
|
||||
:return: 直方图区间 x,和 该区间频次 y
|
||||
'''
|
||||
# 计算灰度直方图
|
||||
hist, bins = np.histogram(image, bins=256, range=[0, 256])
|
||||
# 初始化区间和频次
|
||||
interval = 255
|
||||
frequency = hist[255]
|
||||
while frequency < target_frequency:
|
||||
# 更新区间和频次
|
||||
interval -= 1
|
||||
# 检查直方图的频次是否为None,如果频次是None,则将其设为0,这样可以避免将None和int进行比较报错。
|
||||
frequency = hist[interval] if hist[interval] is not None else 0
|
||||
frequency += hist[interval] if hist[interval] is not None else 0
|
||||
# 如果频次接近10000则停止循环
|
||||
if target_frequency - 2000 <= frequency <= target_frequency + 2000:
|
||||
break
|
||||
|
||||
return interval, frequency
|
||||
|
||||
def reduce_sharpness(image, factor):
|
||||
'''
|
||||
使用PIL库减弱图像锐度
|
||||
:param image: 图像
|
||||
:param factor: 锐度因子,0表示最大程度减弱锐度,1表示原始图像
|
||||
:return: 减弱锐度后的图像
|
||||
'''
|
||||
# OpenCV 格式的图像转换为 PIL 的 Image 对象
|
||||
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
pil_image = Image.fromarray(image_rgb)
|
||||
enhancer = ImageEnhance.Sharpness(pil_image)
|
||||
reduced_image = enhancer.enhance(factor)
|
||||
# PIL 的 Image 对象转换为 OpenCV 的图像格式
|
||||
image_array = np.array(reduced_image)
|
||||
sharpened_image = cv2.cvtColor(image_array, cv2.COLOR_RGB2BGR)
|
||||
|
||||
return sharpened_image
|
||||
|
||||
def sharpening_filter(image):
|
||||
'''
|
||||
锐化滤波器对图片进行锐化,增强图像中的边缘和细节
|
||||
:param image: 导入图片
|
||||
:return: 锐化后的图片
|
||||
'''
|
||||
sharp_kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
|
||||
sharpened_image = cv2.filter2D(image, -1, sharp_kernel)
|
||||
return sharpened_image
|
||||
|
||||
def find_last_x(image, slope_threshold = 1000):
|
||||
x = []
|
||||
y = []
|
||||
hist, bins = np.histogram(image, bins=256, range=[0, 256])
|
||||
|
||||
#找到50以内的最高峰
|
||||
max_y = 0
|
||||
max_i = 5
|
||||
for i in range(5, 50):
|
||||
if hist[i] > max_y:
|
||||
max_y = hist[i]
|
||||
max_i = i
|
||||
print(f'50以内最高峰值y:{max_y},最高峰位置x:{max_i}')
|
||||
|
||||
for i in range(2, max_i):
|
||||
x.append(i)
|
||||
y.append(hist[i])
|
||||
slopes = [abs(y[i + 1] - y[i]) for i in range(len(x) - 1)]
|
||||
|
||||
current_interval = []
|
||||
max_interval = []
|
||||
max_x = {}
|
||||
for i, slope in enumerate(slopes):
|
||||
current_interval.append(slope)
|
||||
if slope >= slope_threshold:
|
||||
if len(current_interval) > len(max_interval):
|
||||
max_interval = current_interval.copy()
|
||||
max_x[x[i]] = slope
|
||||
current_interval = []
|
||||
|
||||
print(max_x)
|
||||
last_x = list(max_x)[-1]
|
||||
last_y = max_x[last_x]
|
||||
return last_x, last_y
|
||||
|
||||
def find_last_high(image, slope_threshold = 2500):
|
||||
x = []
|
||||
y = []
|
||||
hist, bins = np.histogram(image, bins=255, range=[2, 255])
|
||||
|
||||
#找到200以上的最高峰
|
||||
max_y = 0
|
||||
max_i = 254
|
||||
for i in range(220, 255):
|
||||
if hist[i] > max_y:
|
||||
max_y = hist[i]
|
||||
max_i = i
|
||||
print(f'200以上的最高峰值y:{max_y},最高峰位置x:{max_i}')
|
||||
|
||||
for i in range(max_i, 255):
|
||||
x.append(i)
|
||||
y.append(hist[i])
|
||||
slopes = [abs(y[i + 1] - y[i]) for i in range(len(x) - 1)]
|
||||
|
||||
current_interval = []
|
||||
max_interval = []
|
||||
max_x = {}
|
||||
find = False
|
||||
for i in range(len(slopes) - 1, -1, -1):
|
||||
slope = slopes[i]
|
||||
current_interval.append(slope)
|
||||
if slope >= slope_threshold:
|
||||
find = True
|
||||
if len(current_interval) > len(max_interval):
|
||||
max_interval = current_interval.copy()
|
||||
max_x[x[i]] = slope
|
||||
current_interval = []
|
||||
#如果没有找到200以上很平,而且高度小于5000,就按220位置削平
|
||||
if not find and hist[220] < 5000:
|
||||
max_x[220] = hist[220]
|
||||
|
||||
print(max_x)
|
||||
if len(max_x) > 0:
|
||||
last_x = list(max_x)[0]
|
||||
last_y = max_x[last_x]
|
||||
else:
|
||||
print(f'找不到200以上曲线较平的区间,使用254作为最高峰')
|
||||
last_x = 254
|
||||
last_y = hist[254]
|
||||
return last_x, last_y
|
||||
|
||||
def remove_gray_and_sharpening(jpg_path):
|
||||
input_image = cv2.imread(jpg_path)
|
||||
# low_x_thresh, low_y_frequency = low_find_histogram_range(input_image, low_y_limit)
|
||||
low_x_thresh, low_y_frequency = find_last_x(input_image)
|
||||
# high_x_thresh, high_y_frequency = high_find_histogram_range(input_image, high_y_limit)
|
||||
high_x_thresh, high_y_frequency = find_last_high(input_image)
|
||||
print(f"{low_x_thresh} 区间, {low_y_frequency} 频次")
|
||||
print(f"{high_x_thresh} 区间, {high_y_frequency} 频次")
|
||||
high_output_image = ps_color_scale_adjustment(input_image, shadow=low_x_thresh, highlight=high_x_thresh, midtones=1)
|
||||
# high_output_image = ps_color_scale_adjustment(low_ouput_image, shadow=0, highlight=high_x_thresh, midtones=1)
|
||||
|
||||
# # 人体贴图和黑色背景交界处不进行锐化
|
||||
# gray = cv2.cvtColor(input_image, cv2.COLOR_BGR2GRAY)
|
||||
# _, thresh = cv2.threshold(gray, 2, 255, cv2.THRESH_BINARY)
|
||||
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))
|
||||
# gradient = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel)
|
||||
# roi_gradient = cv2.bitwise_and(high_output_image, high_output_image, mask=gradient)
|
||||
|
||||
# # 锐化滤波器
|
||||
# # sharpened_image = sharpening_filter(high_output_image)
|
||||
# sharpened_image = reduce_sharpness(high_output_image, factor=4)
|
||||
# # 将原图边界替换锐化后的图片边界
|
||||
# sharpened_image[gradient != 0] = roi_gradient[gradient != 0]
|
||||
|
||||
# 直方图标记并保存
|
||||
# show_histogram(input_image, img_id, low_x_thresh, high_x_thresh)
|
||||
cv2.imwrite(jpg_path, high_output_image, [cv2.IMWRITE_JPEG_QUALITY, 95]) # 保存图片的质量是原图的 95%
|
||||
|
||||
def main(workdir, r, print_id):
|
||||
print('脚底板二维码程序开始运行...')
|
||||
only_one = False
|
||||
while True:
|
||||
#随机休眠 1- 9
|
||||
time.sleep(random.uniform(1, 9))
|
||||
if print_id == '0':
|
||||
try:
|
||||
if r.llen('model:foot') == 0:
|
||||
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '队列为空,5秒后重试')
|
||||
time.sleep(5)
|
||||
continue
|
||||
except Exception as e:
|
||||
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), 'redis连接异常,5秒后重试')
|
||||
print(e)
|
||||
time.sleep(5)
|
||||
r = redis.Redis(host='106.14.158.208', password='kcV2000', port=6379, db=6)
|
||||
# r = redis.Redis(host='172.31.1.254', password='', port=6379, db=6)
|
||||
# continue
|
||||
# 打印队列里面的全部内容
|
||||
print(f'当前model:foot队列长度:{r.llen("model:foot")}')
|
||||
for i in r.lrange('model:foot', 0, -1):
|
||||
print(i)
|
||||
print_id = r.lpop('model:foot')
|
||||
if print_id is None:
|
||||
print_id = '0'
|
||||
continue
|
||||
#判断是否存在相同的值
|
||||
isHaveAlready = 0
|
||||
for i in r.lrange('model:foot', 0, -1):
|
||||
if i == print_id:
|
||||
isHaveAlready = 1
|
||||
|
||||
if isHaveAlready == 1:
|
||||
print_id = '0'
|
||||
continue
|
||||
|
||||
print_id = print_id.decode('utf-8')
|
||||
else:
|
||||
print(f'接收到运行一个{print_id}任务')
|
||||
only_one = True
|
||||
|
||||
res = requests.get(f'{get_pid_by_printid_url}?print_id={print_id}')
|
||||
print('获取pid:', f'{get_pid_by_printid_url}?print_id={print_id}', res.text)
|
||||
resCode = json.loads(res.text)['code']
|
||||
#该笔订单的获取信息有误,可能信息还没有成功
|
||||
if int(resCode) != 1000:
|
||||
tempMesg = json.loads(res.text)['message']
|
||||
#判断是否包含 不存在
|
||||
if tempMesg == "打印订单不存在":
|
||||
print(f"打印ID{print_id}打印订单不存在,跳过")
|
||||
# res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
|
||||
print("获取打印任务信息有问题,重新任务队列,打印id-",print_id,"重新执行脚底板任务,等待20s")
|
||||
time.sleep(20)
|
||||
#将pid 重新扔进队列
|
||||
r.lpush("model:foot", print_id)
|
||||
#重新调用脚底板程序
|
||||
#os.system(f'blender -b -P fill_dm_code.py')
|
||||
continue
|
||||
use_foot_type = json.loads(res.text)['data']['use_foot_type']
|
||||
pid = json.loads(res.text)['data']['pid']
|
||||
order_id = json.loads(res.text)['data']['order_id']
|
||||
|
||||
filename = os.path.join(workdir, f'{pid}_{order_id}', find_obj(pid, order_id))
|
||||
print('导入obj文件:', filename)
|
||||
# return
|
||||
if only_one:
|
||||
print(f'接收到运行一个{print_id}任务,强制调用cal_foot_position.py计算并上传qr_position')
|
||||
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}')
|
||||
print("延时20s,等待计算脚底板坐标")
|
||||
time.sleep(20)
|
||||
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
|
||||
print('从云端获取的qr_position1:', res.text)
|
||||
codeTemp = json.loads(res.text)['code']
|
||||
if str(codeTemp) == "-1":
|
||||
#移除该脚底板的面积处理
|
||||
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
qr_position = json.loads(res.text)['data']['position_data']
|
||||
else:
|
||||
#从云端获取qr_position,如果获取为空,调用cal_foot_position.py计算并上传qr_position,再重新读取qr_position.txt
|
||||
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
|
||||
print('从云端获取的qr_position2:', res.text)
|
||||
|
||||
codeTemp = json.loads(res.text)['code']
|
||||
if str(codeTemp) == "-1":
|
||||
continue
|
||||
|
||||
qr_position = json.loads(res.text)['data']['position_data']
|
||||
print("云端获取的坐标数据",qr_position)
|
||||
if qr_position == '':
|
||||
time.sleep(3)
|
||||
print('qr_position为空,调用cal_foot_position.py计算并上传qr_position')
|
||||
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}')
|
||||
print("延时20s,等待计算脚底板坐标")
|
||||
time.sleep(20)
|
||||
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
|
||||
print('从云端获取的qr_position3:', res.text)
|
||||
qr_position = json.loads(res.text)['data']['position_data']
|
||||
else:
|
||||
qr_position = json.loads(qr_position)
|
||||
|
||||
if qr_position == "":
|
||||
print("获取脚底坐标数据为空,重新任务队列,打印id-",print_id,"重新执行脚底板任务")
|
||||
#将pid 重新扔进队列
|
||||
r.lpush("model:foot", print_id)
|
||||
#重新调用脚底板程序
|
||||
#os.system(f'blender -b -P fill_dm_code.py')
|
||||
continue
|
||||
|
||||
if type(qr_position) == str: qr_position = json.loads(qr_position)
|
||||
print(f'type of qr_position:{type(qr_position)}')
|
||||
print(f'qr_position:{qr_position}')
|
||||
|
||||
qr_position['location'][2] = -0.1
|
||||
|
||||
temp_foot_data = print_id
|
||||
# 根据print_id生成qr码
|
||||
qr_path = os.path.join(workdir, f'{pid}_{order_id}' ,'qr.png')
|
||||
# if use_foot_type == "short_url":
|
||||
# #调用接口获取短网址信息
|
||||
# short_url = get_short_url(print_id)
|
||||
# if short_url == False:
|
||||
# return
|
||||
# temp_foot_data = short_url
|
||||
# print(f'temp_foot_data---{temp_foot_data}')
|
||||
gen_data_matrix(print_id,pid, qr_path)
|
||||
|
||||
|
||||
# 导入obj文件,重置到标准单位
|
||||
bpy.ops.wm.read_homefile()
|
||||
bpy.context.preferences.view.language = 'en_US'
|
||||
bpy.ops.object.delete(use_global=False, confirm=False)
|
||||
bpy.context.scene.unit_settings.scale_length = 0.001
|
||||
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
|
||||
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
|
||||
|
||||
bpy.ops.import_scene.obj(filepath=filename)
|
||||
#bpy.ops.wm.obj_import(filepath=filename)
|
||||
obj = bpy.context.selected_objects[0]
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
pid_objname = find_pid_objname(pid)
|
||||
|
||||
scale = 90 / obj.dimensions.y
|
||||
obj.scale = (scale, scale, scale)
|
||||
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Z'})
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
|
||||
obj.location[0] = 0
|
||||
obj.location[1] = 0
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
print(f'qr_position:{qr_position}')
|
||||
print(f'qr_position_type:{type(qr_position)}')
|
||||
|
||||
# 根据qr_position的值,恢复qr的位置和尺寸,重新生成贴图
|
||||
bpy.ops.object.load_reference_image(filepath=os.path.join(workdir, f'{pid}_{order_id}', 'qr.png'))
|
||||
bpy.context.object.rotation_euler = (math.radians(-180), math.radians(0), qr_position['rotation'])
|
||||
bpy.ops.transform.translate(value=qr_position['location'], orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements={'INCREMENT'}, use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False, release_confirm=True)
|
||||
|
||||
bpy.context.object.empty_display_size = qr_position['dimensions'][0]
|
||||
|
||||
qr_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}_{order_id}Tex1_qr.png")
|
||||
jpg_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg")
|
||||
#判断是否存在 jpg_path,不存在就执行下一个任务
|
||||
if not os.path.exists(jpg_path):
|
||||
#移除该脚底板的面积处理
|
||||
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
|
||||
os.system(f'blender -b -P fill_dm_code.py')
|
||||
return
|
||||
|
||||
jpg_img = Image.open(jpg_path)
|
||||
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_noqr.jpg"))
|
||||
|
||||
bpy.context.scene.eyek.res_x = jpg_img.width
|
||||
bpy.context.scene.eyek.res_y = jpg_img.height
|
||||
bpy.context.scene.eyek.path_export_image = qr_path
|
||||
bpy.data.objects[pid_objname].select_set(True)
|
||||
bpy.data.objects['Empty'].select_set(True)
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[pid_objname]
|
||||
bpy.ops.eyek.exe()
|
||||
|
||||
qr_img = Image.open(qr_path)
|
||||
jpg_img.paste(qr_img, (0, 0), qr_img)
|
||||
jpg_img.save(jpg_path, quality=90)
|
||||
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_qr.jpg"))
|
||||
|
||||
# 加入去灰、锐化
|
||||
remove_gray_and_sharpening(jpg_path)
|
||||
|
||||
#上传脚底板文件 -》 修改为移动脚底板文件
|
||||
upload_jpg_mtl(pid, order_id, print_id)
|
||||
|
||||
# plt.axis('equal')
|
||||
# plt.show()
|
||||
|
||||
# 保存blend文件
|
||||
# bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}_{order_id}/{pid}_qr_end.blend')
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
# 删除临时文件
|
||||
print("workdirworkdir",workdir)
|
||||
shutil.rmtree(os.path.join(workdir, f'{pid}_{order_id}'))
|
||||
if only_one:
|
||||
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,退出程序')
|
||||
break
|
||||
else:
|
||||
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,继续运行下一个任务')
|
||||
print_id = '0'
|
||||
continue
|
||||
|
||||
restart_current_process("blender -b -P fill_dm_code.py")
|
||||
|
||||
#根据print_id 获取 短网址
|
||||
def get_short_url(print_id):
|
||||
res = requests.get(f'{get_short_url_by_print_id}?print_id={print_id}')
|
||||
resCode = json.loads(res.text)['code']
|
||||
#该笔订单的获取信息有误,可能信息还没有成功
|
||||
if int(resCode) != 1000:
|
||||
print("获取短网址信息失败,延时1分钟,重新扔入脚底板队列,打印id-",print_id,"重新执行脚底板任务")
|
||||
time.sleep(60)
|
||||
#将pid 重新扔进队列
|
||||
r.lpush("model:foot", print_id)
|
||||
#重新调用脚底板程序
|
||||
return False
|
||||
return json.loads(res.text)['data']
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=10, wait_fixed=2000)
|
||||
def upload_jpg_mtl(pid, order_id, print_id):
|
||||
try:
|
||||
print("移动脚底板文件")
|
||||
#判断文件夹是否存在,不存在则创建
|
||||
if not os.path.exists(os.path.join(f'{resFilePath}/{pid}/foot_print_id_{print_id}/')):
|
||||
os.makedirs(os.path.join(f'{resFilePath}/{pid}/foot_print_id_{print_id}/'),mode=0o777, exist_ok=True)
|
||||
else:
|
||||
#移除路径下的文件
|
||||
if os.path.exists(os.path.join(f'{resFilePath}/{pid}/foot_print_id_{print_id}/{pid}Tex1.jpg')):
|
||||
os.remove(os.path.join(f'{resFilePath}/{pid}/foot_print_id_{print_id}/{pid}Tex1.jpg'))
|
||||
print(f"文件 '{pid}' 已成功删除。")
|
||||
|
||||
shutil.move(os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg"),os.path.join(f'{resFilePath}/{pid}/foot_print_id_{print_id}/'))
|
||||
|
||||
|
||||
#print('生成贴图完成,开始上传...')
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1.{print_id}.jpg', os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg"))
|
||||
# oss_client.put_object_from_file(f'objs/print/{pid}/{pid}.mtl', os.path.join(workdir,f'{pid}_{order_id}', f"{pid}.mtl"))
|
||||
# # oss_client.put_object_from_file(f'objs/print/{pid}/{pid}Tex1_noqr.jpg', os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_noqr.jpg"))
|
||||
|
||||
print('更新状态为已生成脚底板二维码')
|
||||
res = requests.post(f'{upload_qr_position_url}?print_id={print_id}')
|
||||
# #记录日志
|
||||
# logging.error(f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}-结果:pid:{pid}-print_id:{print_id} {str(res.text)}")
|
||||
print('更新返回状态:', f'{upload_qr_position_url}?print_id={print_id}', res.text)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print("迁移文件出 yichang ")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
atexit.register(common.notify,"打印工厂-本地虚拟木脚底板处理程序已停止一个")
|
||||
low_y_limit = 25000
|
||||
high_y_limit = 13000
|
||||
|
||||
get_qr_position_url = 'https://mp.api.suwa3d.com/api/printOrder/getFootCodePositionData'
|
||||
upload_qr_position_url = 'https://mp.api.suwa3d.com/api/printOrder/updateFootCodeStatus'
|
||||
get_pid_by_printid_url = 'https://mp.api.suwa3d.com/api/printOrder/getPidByPrintId'
|
||||
get_short_url_by_print_id = 'https://mp.api.suwa3d.com/api/footCode/qrcode'
|
||||
# get_qr_position_url = 'http://172.31.1.254:8199/api/printOrder/getFootCodePositionData'
|
||||
# upload_qr_position_url = 'http://172.31.1.254:8199/api/printOrder/updateFootCodeStatus'
|
||||
# get_pid_by_printid_url = 'http://172.31.1.254:8199/api/printOrder/getPidByPrintId'
|
||||
|
||||
|
||||
r = redis.Redis(host='106.14.158.208', password='kcV2000', port=6379, db=6)
|
||||
# r = redis.Redis(host='172.31.1.254', password='', port=6379, db=6)
|
||||
AccessKeyId = 'LTAI5tSReWm8hz7dSYxxth8f'
|
||||
AccessKeySecret = '8ywTDF9upPAtvgXtLKALY2iMYHIxdS'
|
||||
Endpoint = 'oss-cn-shanghai.aliyuncs.com'
|
||||
Bucket = 'suwa3d-securedata'
|
||||
oss_client = oss2.Bucket(oss2.Auth(AccessKeyId, AccessKeySecret), Endpoint, Bucket)
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
workdir = 'E:\\print\\foot\\'
|
||||
else:
|
||||
workdir = '/data/datasets/foot/'
|
||||
|
||||
#构建好的obj文件目录
|
||||
sourceFilePath = '/data/datasets/print'
|
||||
#脚底板最终保存的路径
|
||||
resFilePath = '/data/datasets/complate/objs'
|
||||
|
||||
print("Usage: blender -b -P fill_dm_code.py")
|
||||
|
||||
|
||||
if len(sys.argv) == 5:
|
||||
print_ids = sys.argv[-1]
|
||||
else:
|
||||
print_ids = '0'
|
||||
|
||||
print(f"print_ids--{print_ids}")
|
||||
for print_id in print_ids.split(','):
|
||||
main(workdir, r, print_id)
|
||||
|
||||
|
||||
Whitespace-only changes.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libIex.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libIex.so.30.7.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libIlmThread.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libIlmThread.so.30.7.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libImath.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libImath.so.30.0.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libMaterialXCore.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXCore.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXFormat.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXFormat.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenGlsl.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenGlsl.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenMdl.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenMdl.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenOsl.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenOsl.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenShader.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXGenShader.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRender.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRender.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderGlsl.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderGlsl.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderHw.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderHw.so.1.38.6
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderOsl.so.1
|
||||
+1
@@ -0,0 +1 @@
|
||||
libMaterialXRenderOsl.so.1.38.6
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenColorIO.so.2.2
|
||||
+1
@@ -0,0 +1 @@
|
||||
libOpenColorIO.so.2.2.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXR.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXR.so.30.7.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXRCore.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXRCore.so.30.7.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXRUtil.so.30
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenEXRUtil.so.30.7.1
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libOpenImageIO.so.2.4
|
||||
+1
@@ -0,0 +1 @@
|
||||
libOpenImageIO.so.2.4.11
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libOpenImageIO_Util.so.2.4
|
||||
+1
@@ -0,0 +1 @@
|
||||
libOpenImageIO_Util.so.2.4.11
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_atomic.so.1.80.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_chrono.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_date_time.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_filesystem.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_iostreams.so.1.80.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_locale.so.1.80.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_numpy310.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_program_options.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_python310.so.1.80.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_regex.so.1.80.0
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
libboost_serialization.so.1.80.0
|
||||
Binary file not shown.
Symlink
+1
@@ -0,0 +1 @@
|
||||
libboost_system.so.1.80.0
|
||||
Loaded 100 of 287 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user