19 changed files with 1932 additions and 56 deletions
@ -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 = 'd:\\' |
||||||
|
|
||||||
|
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 = 'd:\\' |
||||||
|
|
||||||
|
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() |
||||||
@ -0,0 +1,628 @@ |
|||||||
|
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__)))) |
||||||
|
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('最大脚底板面积太小,脚底模型可能有破损,异常退出') |
||||||
|
sys.exit(1) |
||||||
|
# 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 |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
get_obj_max_foot() |
||||||
|
|
||||||
|
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) |
||||||
|
max_qr, qr_location, max_qr_length, rotation = get_max_qr(foot_points) |
||||||
|
|
||||||
|
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' |
||||||
|
# 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 = 'd:\\print\\' |
||||||
|
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) |
||||||
Binary file not shown.
Binary file not shown.
@ -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,486 @@ |
|||||||
|
import os, sys, bpy, math, time, platform, cairosvg, ppf.datamatrix, shutil, requests, json, redis, oss2, cv2 |
||||||
|
from retrying import retry |
||||||
|
import numpy as np |
||||||
|
import matplotlib.pyplot as plt |
||||||
|
from PIL import Image, ImageEnhance |
||||||
|
from addon_utils import enable |
||||||
|
import logging |
||||||
|
logging.basicConfig(filename='foot_update_res.log', level=logging.ERROR) |
||||||
|
enable('io_import_images_as_planes') |
||||||
|
enable('eyek_addon') |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
@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 |
||||||
|
|
||||||
|
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 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 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, 1000) |
||||||
|
high_x_thresh, high_y_frequency = high_find_histogram_range(input_image, high_y_limit) |
||||||
|
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: |
||||||
|
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 |
||||||
|
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) |
||||||
|
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) |
||||||
|
|
||||||
|
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}') |
||||||
|
res = requests.get(f'{get_qr_position_url}?print_id={print_id}') |
||||||
|
print('从云端获取的qr_position:', res.text) |
||||||
|
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_position:', res.text) |
||||||
|
|
||||||
|
qr_position = json.loads(res.text)['data']['position_data'] |
||||||
|
|
||||||
|
if qr_position == '': |
||||||
|
print('qr_position为空,调用cal_foot_position.py计算并上传qr_position') |
||||||
|
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}') |
||||||
|
res = requests.get(f'{get_qr_position_url}?print_id={print_id}') |
||||||
|
print('从云端获取的qr_position:', res.text) |
||||||
|
qr_position = json.loads(res.text)['data']['position_data'] |
||||||
|
else: |
||||||
|
qr_position = json.loads(qr_position) |
||||||
|
|
||||||
|
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 |
||||||
|
# 根据print_id生成qr码 |
||||||
|
qr_path = os.path.join(workdir, f'{pid}_{order_id}' ,'qr.png') |
||||||
|
gen_data_matrix(print_id, 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) |
||||||
|
|
||||||
|
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_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() |
||||||
|
|
||||||
|
# 删除临时文件 |
||||||
|
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 |
||||||
|
|
||||||
|
@retry(stop_max_attempt_number=10, wait_fixed=2000) |
||||||
|
def upload_jpg_mtl(pid, order_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) |
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
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_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 = 'd:\\print\\' |
||||||
|
else: |
||||||
|
workdir = '/data/datasets/foot/' |
||||||
|
|
||||||
|
print("Usage: blender -b -P fill_dm_code.py") |
||||||
|
|
||||||
|
if len(sys.argv) == 5: |
||||||
|
print_ids = sys.argv[-1] |
||||||
|
else: |
||||||
|
print_ids = '0' |
||||||
|
|
||||||
|
for print_id in print_ids.split(','): |
||||||
|
main(workdir, r, print_id) |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,176 @@ |
|||||||
|
from math import radians |
||||||
|
import sys, platform, os, time, bpy, requests, json, bmesh, shutil |
||||||
|
from PIL import Image |
||||||
|
import platform |
||||||
|
if platform.system() == 'Windows': |
||||||
|
sys.path.append('e:\\libs\\') |
||||||
|
else: |
||||||
|
sys.path.append('/data/deploy/make3d/make2/libs/') |
||||||
|
import config, libs |
||||||
|
|
||||||
|
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 fix_link_texture(pid): |
||||||
|
# 修改obj中的mtl文件为pid_original.mtl |
||||||
|
path = os.path.join(workdir, 'print', f'{pid}_{orderId}') |
||||||
|
filename = os.path.join(path, f'{pid}_original.obj') |
||||||
|
|
||||||
|
with open(filename, 'r') as f: |
||||||
|
lines = f.readlines() |
||||||
|
for i in range(len(lines)): |
||||||
|
if lines[i].startswith('mtllib'): |
||||||
|
lines[i] = f'mtllib {pid}_original.mtl\n' |
||||||
|
break |
||||||
|
with open(filename, 'w') as f: |
||||||
|
f.writelines(lines) |
||||||
|
|
||||||
|
f.close() |
||||||
|
|
||||||
|
# 将pid.mtl文件复制为pid_original.mtl _decimate |
||||||
|
shutil.copy(os.path.join(path, f'{pid}.mtl'), os.path.join(path, f'{pid}_original.mtl')) |
||||||
|
shutil.copy(os.path.join(path, f'{pid}Tex1.jpg'), os.path.join(path, f'{pid}Tex1_decimate.jpg')) |
||||||
|
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') |
||||||
|
# 修改pid_original.mtl文件中的贴图为pid_old.jpg |
||||||
|
with open(os.path.join(path, f'{pid}_original.mtl'), 'r') as f: |
||||||
|
lines = f.readlines() |
||||||
|
for i in range(len(lines)): |
||||||
|
if lines[i].startswith('map_Kd'): |
||||||
|
lines[i] = f'map_Kd {pid}Tex1_decimate.jpg\n' |
||||||
|
break |
||||||
|
with open(os.path.join(path, f'{pid}_original.mtl'), 'w') as f: |
||||||
|
f.writelines(lines) |
||||||
|
|
||||||
|
f.close() |
||||||
|
|
||||||
|
|
||||||
|
def main(): |
||||||
|
start = time.time() |
||||||
|
|
||||||
|
get_printsize_url = 'https://mp.api.suwa3d.com/api/printOrder/info' |
||||||
|
upload_obj_volume_url = 'https://mp.api.suwa3d.com/api/physical/add' # ?pid=1&order_id=1&faces=1&volume=1 |
||||||
|
|
||||||
|
|
||||||
|
res = requests.get(f'{get_printsize_url}?id={orderId}') |
||||||
|
print('获取打印尺寸:', res.text) |
||||||
|
|
||||||
|
if res.status_code == 200: |
||||||
|
pid = res.json()['data']['pid'] |
||||||
|
path = os.path.join(workdir, 'print', f'{pid}_{orderId}') |
||||||
|
filename = os.path.join(path, f'{pid}.obj') |
||||||
|
bpy.ops.object.delete(use_global=False, confirm=False) |
||||||
|
print('正在处理:', filename) |
||||||
|
bpy.ops.import_scene.obj(filepath=filename) |
||||||
|
obj = bpy.context.selected_objects[0] |
||||||
|
print('原始模型尺寸:', obj.dimensions) |
||||||
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) |
||||||
|
print('应用后模型尺寸:', obj.dimensions) |
||||||
|
shutil.copy(filename, os.path.join(path, f'{pid}_original.obj')) |
||||||
|
filename_original = os.path.join(path, f'{pid}_original.obj') |
||||||
|
|
||||||
|
for f in res.json()['data']['fileList']: |
||||||
|
height = float(f.split('_')[-2][:-2]) * 10 |
||||||
|
|
||||||
|
obj = bpy.context.selected_objects[0] |
||||||
|
print(f'{f}处理前{height}mm模型尺寸: {obj.dimensions}') |
||||||
|
scale = height / obj.dimensions.z |
||||||
|
obj.scale = (scale, scale, scale) |
||||||
|
bpy.ops.object.transform_apply(scale=True) |
||||||
|
print(f'{f}处理后{height}mm模型尺寸: {obj.dimensions}') |
||||||
|
|
||||||
|
bpy.ops.export_scene.obj(filepath=os.path.join(path, f'{pid}.obj')) |
||||||
|
if os.path.exists(os.path.join(path, f)): |
||||||
|
os.remove(os.path.join(path, f)) |
||||||
|
os.rename(os.path.join(path, f'{pid}.obj'), os.path.join(path, f)) |
||||||
|
config.oss_bucket.put_object_from_file(f'objs/print/{pid}/{f}', os.path.join(path, f)) |
||||||
|
|
||||||
|
# 重新加载模型,然后生成数字模型 |
||||||
|
bpy.ops.object.delete(use_global=False, confirm=False) |
||||||
|
fix_link_texture(pid) |
||||||
|
bpy.ops.import_scene.obj(filepath=filename_original) |
||||||
|
|
||||||
|
obj = bpy.context.selected_objects[0] |
||||||
|
bpy.context.view_layer.objects.active = obj |
||||||
|
obj.select_set(True) |
||||||
|
|
||||||
|
if pid == '85964': bpy.ops.wm.save_as_mainfile(filepath=os.path.join(path, f'{pid}_{orderId}.blend')) |
||||||
|
|
||||||
|
scale = 90 / obj.dimensions.z |
||||||
|
obj.scale = (scale, scale, scale) |
||||||
|
|
||||||
|
headcount = res.json()['data']['headcount'] |
||||||
|
|
||||||
|
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) |
||||||
|
upload_res = requests.get(f'{upload_obj_volume_url}?pid={pid}&order_id={orderId}&faces={faces}&volume={obj_volume}&headcount={headcount}') |
||||||
|
print('上传模型体积:', upload_res.text) |
||||||
|
|
||||||
|
# 生成数字模型 |
||||||
|
|
||||||
|
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.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.export_scene.obj(filepath=os.path.join(path, f'{pid}_decimate.obj')) |
||||||
|
bpy.ops.export_scene.gltf(filepath=os.path.join(path, f'{pid}_decimate.glb'), export_format='GLB', export_apply=True, export_jpeg_quality=80) |
||||||
|
config.oss_bucket.put_object_from_file(f'glbs/3d/{pid}.glb', os.path.join(path, f'{pid}_decimate.glb')) |
||||||
|
bpy.ops.wm.quit_blender() |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
if platform.system() == 'Windows': |
||||||
|
workdir = 'd:\\' |
||||||
|
else: |
||||||
|
workdir = '/data/datasets/' |
||||||
|
|
||||||
|
if len(sys.argv) - (sys.argv.index("--") + 1) < 1: |
||||||
|
print("Usage: blender -b -P resize_model.py -- <orderId>") |
||||||
|
sys.exit(1) |
||||||
|
orderId = sys.argv[sys.argv.index("--") + 1] |
||||||
|
|
||||||
|
main() |
||||||
@ -1,13 +0,0 @@ |
|||||||
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple |
|
||||||
pip config set install.trusted-host pypi.tuna.tsinghua.edu.cn |
|
||||||
|
|
||||||
python -m pip install --upgrade pip |
|
||||||
pip install oss2 redis pillow numpy opencv-python bpy tqdm pyautogui psutil pywin32 pymysql |
|
||||||
|
|
||||||
|
|
||||||
config |
|
||||||
set bin="C:\Program Files\Capturing Reality\RealityCapture\RealityCapture.exe" |
|
||||||
%bin% -disableOnlineCommunication -setInstanceName %pid% |
|
||||||
%bin% -disableOnlineCommunication -delegateTo %pid% |
|
||||||
%bin% -set "appCacheLocation=ProjectFolder" |
|
||||||
|
|
||||||
@ -0,0 +1,3 @@ |
|||||||
|
for /f "skip=1 tokens=3" %%s in ('query user %USERNAME%') do ( |
||||||
|
%windir%\System32\tscon.exe %%s /dest:console |
||||||
|
) |
||||||
Loading…
Reference in new issue