diff --git a/config/exportRegistration.xml b/config/exportRegistration.xml new file mode 100644 index 0000000..cba6483 --- /dev/null +++ b/config/exportRegistration.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/libs/common.py b/libs/common.py index 754f93b..4a2fb20 100644 --- a/libs/common.py +++ b/libs/common.py @@ -1,5 +1,8 @@ -import redis,sys,os,re,oss2,shutil,time +import redis,sys,os,re,oss2,shutil,time,cv2 +import json +import requests import platform +import numpy as np import xml.etree.ElementTree as ET from PIL import ImageGrab if platform.system() == 'Windows': @@ -222,14 +225,14 @@ def uploadControlPointsOss(pid): #截屏保存 def saveScreenImg(pid): #获取当前的日志 - if not os.path.exists(os.path.join(config.workdir,"screen", time.strftime("%y%m%d" time.localtime()))): - os.makedirs(os.path.join(config.workdir,"screen", time.strftime("%y%m%d" time.localtime()))) + if not os.path.exists(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))): + os.makedirs(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))) screenshot = ImageGrab .grab() - screenshot.save(os.path.join(config.workdir,"screen", time.strftime("%y%m%d" time.localtime())),str(pid)+".png") + screenshot.save(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))+"/"+str(pid)+".png") #移动到e盘 - if not os.path.exists(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d" time.localtime()))): - os.makedirs(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d" time.localtime()))) - shutil.copytree(os.path.join(config.workdir,"screen", time.strftime("%y%m%d" time.localtime())), os.path.join(config.sharedir,"screen", time.strftime("%y%m%d" time.localtime()))) + if not os.path.exists(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))): + os.makedirs(os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))) + shutil.copy(os.path.join(config.workdir,"screen", time.strftime("%y%m%d",time.localtime()))+"\\"+str(pid)+".png", os.path.join(config.sharedir,"screen", time.strftime("%y%m%d",time.localtime()))) #文件夹的移动和删除 @@ -247,4 +250,156 @@ def removeFolder(pid): file_time = os.path.getmtime(os.path.join(config.workdir, 'finished', file)) now_time = time.time() if (now_time - file_time) > 259200: - shutil.rmtree(os.path.join(config.workdir, 'finished', file), ignore_errors=True) \ No newline at end of file + shutil.rmtree(os.path.join(config.workdir, 'finished', file), ignore_errors=True) + + +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 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 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 remove_gray_and_sharpening(jpg_path): + #low_y_limit = 25000 + high_y_limit = 13000 + 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) + cv2.imwrite(jpg_path, high_output_image, [cv2.IMWRITE_JPEG_QUALITY, 95]) # 保存图片的质量是原图的 95% + +#读取指定路径判断是否对齐成功 +def isAlignNums(strPid): + #拼接路径 + csvPath = os.path.join(config.workdir, strPid, strPid+"_align.csv") + print(csvPath) + #判断文件是否存在 + if os.path.exists(csvPath) == False: + return "rebuild" + #读取文件行数 + lines = "" + with open(csvPath, 'r') as f: + lines = f.readlines() + #获取长度 + lines = len(lines) - 1 + #获取pid对应的 photo1 和 photo2 的数量 + photo1Num = 0 + photo2Num = 0 + for file in os.listdir(os.path.join(config.workdir, strPid, 'photo1')): + if file.endswith('.jpg'): + photo1Num += 1 + + for file in os.listdir(os.path.join(config.workdir, strPid, 'photo2')): + if file.endswith('.jpg'): + photo2Num += 1 + #获取图片总数 + totalPhotos = photo1Num + photo2Num + #比对对齐数量 + if totalPhotos - lines >= 4: + return "rebuild" + + return True + + +#消息通知 +def notify(content): + + if content == "": + return "content 不能为空" + + for user_agent_id in config.notify_user_Ids: + data = { + 'userId': user_agent_id, + 'message': content, + } + headers = {'Content-Type': 'application/json'} + message_send_url = "https://mp.api.suwa3d.com/api/qyNotify/sendMessage?userId="+user_agent_id+"&message="+content + response = requests.post(message_send_url, data=json.dumps(data), headers=headers) \ No newline at end of file diff --git a/libs/foot_mark_seam.py b/libs/foot_mark_seam.py new file mode 100644 index 0000000..b9def8f --- /dev/null +++ b/libs/foot_mark_seam.py @@ -0,0 +1,149 @@ +import bpy +import bmesh + + +def active_object(obj): + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + + +def get_obj_max_foot(workdir,filename,filename_tex): + # 1.模型导入和初始化 + # 删除当前场景中的所有对象: + # use_global=False表示只删除当前场景中的对象,而不会影响到其他场景中的对象;confirm=False表示删除时不需要确认。 + bpy.ops.object.delete(use_global=False, confirm=False) + bpy.ops.import_scene.obj(filepath=filename) # 导入指定路径的 OBJ 格式模型文件 + + bpy.context.scene.unit_settings.scale_length = 0.001 # 将场景的长度单位缩放为0.001,相当于将长度单位从默认的米缩小为毫米 + bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS' # 将场景的长度单位设置为厘米 + bpy.context.scene.unit_settings.mass_unit = 'GRAMS' # 将场景的质量单位设置为克 + + obj = bpy.context.selected_objects[0] # 获取了当前选中的对象列表,然后通过 [0] 取得列表中的第一个对象 + bpy.context.view_layer.objects.active = obj # 将变量 obj 设置为当前活动对象 + obj.select_set(True) # 将变量 obj 的选择状态设置为 True,表示选中该对象 + pid = obj.name # 获取该对象的名字 + + # 对选定的对象进行对齐操作 + bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Z'}) + # 设置选中对象的原点,参数1:将原点设置为对象的质心,参数2:使用对象的几何中心作为参考 + bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN') + # 将选中对象的位置坐标分别设置为 (0, 0),即将对象移动到世界坐标系的原点位置 + obj.location[0] = 0 + obj.location[1] = 0 + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) # 将选中对象的位置、旋转和缩放应用到对象的数据中 + + # 2.选择要复制的对象 + obj_duplicate = obj.copy() + obj_duplicate.data = obj.data.copy() + bpy.context.collection.objects.link(obj_duplicate) + # obj_duplicate.location.x += 5.0 + bpy.ops.object.select_all(action='DESELECT') # 取消选中全部对象 + + # 3.处理复制的对象的脚底缝合边 + # 选中复制对象 + bpy.context.view_layer.objects.active = obj_duplicate + obj_duplicate.select_set(True) + + selected_obj = bpy.context.active_object # 获取当前选中的对象 + bpy.context.view_layer.objects.active = selected_obj # 将对象转换到编辑模式 + + # 切换到3D视图编辑模式 + bpy.context.area.type = 'VIEW_3D' + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') # 选择所有的边 + + # 切换到UV编辑器 + bpy.context.area.type = 'IMAGE_EDITOR' + pid_img = f"{pid}Tex1.jpg" + bpy.ops.image.open(filepath=filename_tex, directory=workdir, + files=[{"name": pid_img, "name": pid_img}], relative_path=True, + show_multiview=False) + bpy.context.area.ui_type = 'UV' + bpy.ops.uv.select_all(action='SELECT') # 选择所有UV贴图顶点 + # 标记所有沿孤岛的边为缝合边 + bpy.ops.uv.seams_from_islands() + bpy.context.area.type = 'VIEW_3D' + bpy.ops.object.mode_set(mode='OBJECT') + + # 获取世界坐标系下z轴接近0的顶点的索引 + z_zero_vertex_indices = [] + for i, vertex in enumerate(selected_obj.data.vertices): + world_vertex = selected_obj.matrix_world @ vertex.co + if abs(world_vertex.z) < 0.2: + z_zero_vertex_indices.append(i) + # 将对象转换回对象模式 + bpy.ops.object.mode_set(mode='OBJECT') + # 创建一个新的顶点组,并将z_zero_vertices中的顶点添加到该顶点组中 + vg = selected_obj.vertex_groups.new(name="z_zero_vertices") + for index in z_zero_vertex_indices: + vg.add([index], 1.0, 'REPLACE') + # 将选中的顶点设置为活动顶点 + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.vertex_group_select() # 选中待处理的顶点 + bpy.ops.mesh.mark_seam(clear=True) # 取消所选区域内的缝合边 + bpy.ops.mesh.region_to_loop() # 选择选定面周围的边界边!!! + bpy.ops.mesh.select_mode(type="EDGE") # 转换为线模式 + bpy.ops.mesh.mark_seam(clear=False) # 标记所选的线为缝合边 + + # 选中脚底顶点组 + bpy.ops.uv.select_all(action='DESELECT') + bpy.ops.object.vertex_group_set_active(group='z_zero_vertices') # 设置活动顶点组 + bpy.ops.object.vertex_group_select() # 选择分配给活动顶点组的所有顶点 + bpy.ops.uv.unwrap() + + # 处理贴图脚底部分孤岛,其他孤岛保持不变 + # (1)反选模型顶点,方便贴图固定不需要处理的区域 + bpy.ops.mesh.select_all(action='INVERT') + bpy.context.area.type = 'IMAGE_EDITOR' # 切换到贴图模式 + bpy.context.area.ui_type = 'UV' + bpy.ops.uv.pin(clear=False) + bpy.ops.object.vertex_group_set_active(group='z_zero_vertices') # 设置活动顶点组 + bpy.ops.object.vertex_group_select() # 选择分配给活动顶点组的所有顶点 + # (2)脚底部位UV展开,平均孤岛比例,重新排列孤岛 + bpy.ops.uv.select_all(action='SELECT') + bpy.ops.uv.average_islands_scale() + bpy.ops.uv.pack_islands(margin=0.001) + + bpy.context.area.type = 'VIEW_3D' + bpy.ops.object.mode_set(mode='OBJECT') + + # 4. 烘焙模式,参数设置 + bpy.ops.object.select_all(action='DESELECT') # 取消选中全部对象 + # # 选中原始对象 + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + # 选中复制对象 + bpy.context.view_layer.objects.active = obj_duplicate + obj_duplicate.select_set(True) + bpy.context.scene.render.engine = 'CYCLES' + bpy.context.scene.cycles.device = 'GPU' + bpy.context.scene.cycles.preview_samples = 1 + bpy.context.scene.cycles.samples = 1 + bpy.context.scene.cycles.bake_type = 'DIFFUSE' + bpy.context.scene.render.bake.use_pass_direct = False + bpy.context.scene.render.bake.use_pass_indirect = False + bpy.context.scene.render.bake.use_selected_to_active = True + bpy.context.scene.render.bake.cage_extrusion = 0.01 + bpy.ops.object.bake(type='DIFFUSE') # 开始 Bake + + # 5. 导出模型和贴图 + bpy.ops.object.select_all(action='DESELECT') # 取消选中全部对象 + # 选中复制对象 + bpy.context.view_layer.objects.active = obj_duplicate + obj_duplicate.select_set(True) + bpy.ops.wm.obj_export(filepath=filename, export_selected_objects=True) + bpy.context.area.type = 'IMAGE_EDITOR' # 切换到 + bpy.ops.image.save_as(filepath=filename_tex) + bpy.context.area.type = 'TEXT_EDITOR' # 切换到文本编辑器 + + +if __name__ == '__main__': + workdir = 'E:\\117080\\print_model' + # filename = f'{workdir}\\117080_12cm_x1.obj' + filename = f'{workdir}\\117080.obj' + filename_tex = f'{workdir}\\117080Tex1.jpg' + save_obj = f"{workdir}\\bake\\117080Tex1.obj" + save_tex = f"{workdir}\\bake\\117080Tex1.jpg" + + get_obj_max_foot() \ No newline at end of file diff --git a/libs/libs.py b/libs/libs.py index 5b7f7ec..70f12a6 100644 --- a/libs/libs.py +++ b/libs/libs.py @@ -1,7 +1,9 @@ import os, time, json, requests, shutil, oss2, psutil from tqdm import tqdm from PIL import Image, ImageEnhance -import config,libs_db +import config,libs_db,common +import threading +from concurrent.futures import ThreadPoolExecutor def find_blender_bin_path(): base_path = 'C:\\Program Files\\Blender Foundation\\' @@ -222,7 +224,7 @@ def set_photo_join_type(workdir, pid, photoN, camera_id, mesh = '0', texture='0' with open(filename, 'w') as f: f.writelines(lines) -def down_from_oss(oss_client, workdir, pid, per=100): +def down_from_oss(oss_client, workdir, pid, per=100,photoPath=""): start_time = time.time() path = os.path.join(workdir, pid) if os.path.exists(path): @@ -237,20 +239,52 @@ def down_from_oss(oss_client, workdir, pid, per=100): filelist = oss2.ObjectIteratorV2(oss_client, prefix=prefix) for file in tqdm(filelist): filename = file.key.split('/')[-1] + localfile = "" # print('正在下载:', file.key) - if filename.endswith('_1.jpg'): - localfile = os.path.join(path, 'photo1', filename) + if photoPath == "": + if filename.endswith('_1.jpg'): + localfile = os.path.join(path, 'photo1', filename) + else: + localfile = os.path.join(path, 'photo2', filename) else: - localfile = os.path.join(path, 'photo2', filename) - + if photoPath=="1": + if filename.endswith('_1.jpg'): + localfile = os.path.join(path, 'photo1', filename) + else: + if filename.endswith('_8.jpg'): + localfile = os.path.join(path, 'photo2', filename) + if localfile == "": + continue style = f'image/resize,p_{per}' if per == 100: oss_client.get_object_to_file(file.key, localfile) else: oss_client.get_object_to_file(file.key, localfile, process=style) + + #判断localfile 是否有包含 photo2 + + + #遍历处理photo2的数数据 + # if str(psid) == "96" or str(pid) == "118994": + # path = os.path.join(workdir, pid, 'photo2') + # files = [] + # for fileName in os.listdir(path): + # if ".jpg" in fileName: + # files.append(path+"\\"+fileName) + # beginTime = time.time() + # with ThreadPoolExecutor(max_workers=6) as executor: + # executor.map(process_image, files) + + # print(f'{localfile}灰度处理费时{diff_time(beginTime)}') + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 图片下载完成, 共费时{diff_time(start_time)}') +#灰度处理图片 +def process_image(localfile): + if ".jpg" in localfile: + common.remove_gray_and_sharpening(localfile) + def get_defineDistances(psid): res = '' distances = libs_db.get_floor_sticker_distances(psid).split(';') diff --git a/libs/libs_db.py b/libs/libs_db.py index abbcfc9..1a43a4b 100644 --- a/libs/libs_db.py +++ b/libs/libs_db.py @@ -17,8 +17,7 @@ def get_task(task_type): try: with pymysqlAlias() as conn: cursor = conn.cursor() - - sql = f'select task_key from tasks where status = 0 order by priority desc, id asc limit 1' + sql = f'select task_key from tasks where status = 0 order by priority desc, id asc limit 1 for update' # print(f'sql: {sql}') cursor.execute(sql) data = cursor.fetchone() @@ -49,6 +48,9 @@ def add_task(data): #新增任务到分布式处理任务表中 {"task_type":key,"task_key":pid,"psid":psid} def add_task_distributed(data): + flag = isInTaskDistributed(data["task_key"]) + if flag: + return "already" try: with pymysqlAlias() as conn: cursor = conn.cursor() @@ -66,8 +68,8 @@ def start_task(data): cursor = conn.cursor() hostname = socket.gethostname() - sql = f'update tasks set status = 1, hostname = "{hostname}", started_at = now(), updated_at = now() where task_key = "{data["task_key"]} and status = 0"' - # print(f'sql: {sql}') + sql = f'update tasks set status = 1, hostname = "{hostname}", started_at = now(), updated_at = now() where status = 0 and task_key = {data["task_key"]}' + print(f'开始任务sql: {sql}') cursor.execute(sql) conn.commit() except Exception as e: @@ -158,4 +160,20 @@ def isStudioConfigDistribute(psid): return True except Exception as e: print(f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} 执行change_to_new_make_psid()异常: {str(e)}") + return "error" + +#判断是否已经存在了 +def isInTaskDistributed(task_key): + try: + with pymysqlAlias() as conn: + cursor = conn.cursor() + sql = f'select count(*) from task_distributed where status = 0 and task_key = "{task_key}"' + cursor.execute(sql) + result = cursor.fetchone() + if result[0] == 0: + return False + else: + return True + except Exception as e: + print(f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())} 执行isInTaskDistributed()异常: {str(e)}") return "error" \ No newline at end of file diff --git a/libs/main_service_db.py b/libs/main_service_db.py index 9849e79..f945492 100644 --- a/libs/main_service_db.py +++ b/libs/main_service_db.py @@ -192,10 +192,7 @@ def get_task_distributed_step1(): try: with pymysqlAlias() as conn: cursor = conn.cursor(pymysql.cursors.DictCursor) - sql = 'select * from task_distributed where status =0 order by priority desc limit 1' - if where: - sql += f' and {where}' - + sql = 'select * from task_distributed where status =0 order by priority desc limit 1 for update' cursor.execute(sql) result = cursor.fetchone() # 关闭游标和连接 diff --git a/logic/logic_main_service.py b/logic/logic_main_service.py index b9c9f75..d917e66 100644 --- a/logic/logic_main_service.py +++ b/logic/logic_main_service.py @@ -43,9 +43,10 @@ def get_task_distributed(): return 'no_data' else: #优先处理step1 和 step3 + resultData = main_service_db.get_task_distributed_step1() #R11 R12的主机如果已经有在处理step2了,则不能再处理step2,只能处理step1 step3 - resultData = need_run_step_no_step2() - if resultData == "no": + #resultData = need_run_step_no_step2() + if resultData is None: #return "no" #R11 R12的主机 可以执行step1 2 3 的任务 #如果R11 R12的主机目前没有正在执行step2,则优先处理step2, @@ -62,8 +63,9 @@ def get_task_distributed(): return resultData #没有任何可执行的 return "no" - - resultData["hostname"] = hostname + + resultData = {"hostname":hostname,"run_step":"step1","task_distributed_id":resultData["id"],"task_key":resultData["task_key"]} + #resultData["hostname"] = hostname flagRes = update_main_and_add_detail(resultData) if flagRes == "error": print(f'出现错误,有可能是多个进程获取同一个任务了') @@ -213,8 +215,8 @@ def need_run_step_no_step1(): xstep = need_run_stepx(row["id"]) #print("查询非step1的任务列表",xstep,row["id"]) if xstep == "step2": - if common.task_need_high_model_or_photo3(row["task_key"]) and hostname != "R11" and hostname != "R12": - continue + # if common.task_need_high_model_or_photo3(row["task_key"]) and hostname != "R11" and hostname != "R12": + # continue #没有正在执行的step1,则返回该任务 return {"hostname":hostname,"run_step":xstep,"task_distributed_id":row["id"],"task_key":row["task_key"]} return "no" diff --git a/main_service.py b/main_service.py index 94eae47..8837a14 100644 --- a/main_service.py +++ b/main_service.py @@ -1,6 +1,6 @@ import sys,socket,time,platform from logic import logic_main_service -import logging +import logging,atexit import main_step1,main_step2,main_step3 if platform.system() == 'Windows': #线上正式运行 @@ -8,8 +8,9 @@ if platform.system() == 'Windows': #本地测试 else: sys.path.append('/data/deploy/make3d/make2/libs/') -import main_service_db,config +import main_service_db,config,common if __name__ == '__main__': + atexit.register(common.notify,socket.gethostname()+"分布式建模任务已经停止") if len(sys.argv) == 2: print(sys.argv[1]) os.system(f'python main_step1.py {sys.argv[1]}') diff --git a/main_step1.py b/main_step1.py index a90a313..f9f07a1 100644 --- a/main_step1.py +++ b/main_step1.py @@ -1,12 +1,12 @@ import os, sys, time, shlex, subprocess, shutil, requests, cv2, numpy as np from PIL import Image -import platform +import platform,socket,atexit if platform.system() == 'Windows': sys.path.append('e:\\libs\\') #sys.path.append('libs') else: sys.path.append('/data/deploy/make3d/make2/libs/') -import config, libs, libs_db,main_service_db +import config, libs, libs_db,main_service_db,common def filter_dark_texture_image(pid): start_time = time.time() @@ -204,6 +204,7 @@ def main(pid, experience=False, makeloop=True,task_distributed_id="",isNoColorTe if __name__ == '__main__': # 取云端redis任务,完成第一步的数据预处理后,将数据放入共享存储目录,将第二步任务塞入本地mysql队列 # 默认循环值守,可传参数运行单一任务,以方便调试 + atexit.register(common.notify,socket.gethostname()+"建模任务已经停止") pid = '0' isNoColorTexture = "" if len(sys.argv) == 2: diff --git a/main_step2.py b/main_step2.py index 65d9e33..a1b758b 100644 --- a/main_step2.py +++ b/main_step2.py @@ -29,7 +29,7 @@ def make3d(pid): # psid = libs.getPSid(pid) # if int(psid) == 80: # change_rcbox_deepth(str(pid),0.03) - + psid = libs.getPSid(pid) simplify_value = 1000000 * libs.getHeadCount(pid) add_photo3 = ' ' #判断是否存在photo3 @@ -64,48 +64,43 @@ def make3d(pid): cmd = f'{config.rcbin} {config.r1["init"]} \ -addFolder "{os.path.join(config.workdir, pid, "photo1")}" -addFolder "{os.path.join(config.workdir, pid, "photo2")}" {add_photo3} \ -importControlPointsMeasurements "{os.path.join(config.workdir, pid, f"{pid}.controlPoints.csv")}" \ - -align -save' + -align -exportRegistration "{os.path.join(config.workdir,"make2", "config","exportRegistration.xml")}" "{os.path.join(config.workdir, pid,str(pid)+"_align.csv")}" -save' - #不存在point文件的时候就要用自动点击的方式 - if isExistPoint == False: - cmd = cmd + f' "{os.path.join(config.workdir, pid, f"{pid}_wait.rcproj")}"' - else: - cmd = cmd + f' "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' + cmd = cmd + f' "{os.path.join(config.workdir, pid, f"{pid}_wait.rcproj")}"' print(cmd) cmd = shlex.split(cmd) res = subprocess.run(cmd) time.sleep(2) - if isExistPoint == False: - print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 定位点导入完成') + + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 定位点导入完成') + time.sleep(3) + # defind_distance + #死循环阻塞获取 + while True: + print("循环阻塞开始") time.sleep(3) - # defind_distance - #死循环阻塞获取 - while True: - print("循环阻塞开始") - time.sleep(3) - #判断是否有 pid_1 的的值 - print(pid+"_1") - if redisLocal.lpos('model:auto_distance',pid+"_1") == None: - continue - shutil.move(os.path.join(config.workdir, pid, f'{pid}_wait.rcproj'), os.path.join(config.workdir, pid, f'{pid}.rcproj')) - print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 定义定位点距离完成') - #将 controlpoints_0.dat 文件拷贝到 oss 上作为公共的使用 - common.uploadControlPointsOss(pid) + #判断是否有 pid_1 的的值 + print(pid+"_1") + if redisLocal.lpos('model:auto_distance',pid+"_1") == None: + continue + shutil.move(os.path.join(config.workdir, pid, f'{pid}_wait.rcproj'), os.path.join(config.workdir, pid, f'{pid}.rcproj')) + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 定义定位点距离完成') + #将 controlpoints_0.dat 文件拷贝到 oss 上作为公共的使用 + common.uploadControlPointsOss(pid) + + #最后处理掉redis中的值 + redisLocal.lrem('model:auto_distance', 0, pid+"_1") + break + print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"循环阻塞结束,判断是否对齐成功") - #最后处理掉redis中的值 - redisLocal.lrem('model:auto_distance', 0, pid+"_1") - break - print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),"循环阻塞结束,开始建模") - else: - #修改rcproj文件 controlpoints fileName 的引入 - flag = common.changeRcprojControlpointsFile(pid) - if flag == False: - print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 获取及修改controlpoints失败') - return - - print("使用公共point文件的方式进行建模") + #查看对齐的数量 + alignRes = common.isAlignNums(str(pid)) + if alignRes == "rebuild": + os.system(f'python d:\\make2\\tools\push_cmd.py rebuild {pid}') + os.system(f'python d:\\make2\\main_service.py') + return #区域的设置 建模 #update cmdSmall = "-align" @@ -115,7 +110,10 @@ def make3d(pid): #{config.r1["init"]} if common.task_need_high_model(pid): - calulate_type = '-mvsHigh' + if str(psid) == "41" or str(psid) == "85": + calulate_type = "-mvs" + else: + calulate_type = '-mvsHigh' else: calulate_type = '-mvs' @@ -164,7 +162,10 @@ def make3d(pid): else: # new version #判断是否要进行高精模 if common.task_need_high_model(pid): - calulate_type = 'calculateHighModel' + if str(psid) == "41" or str(psid) == "85": + calulate_type = "calculateNormalModel" + else: + calulate_type = 'calculateHighModel' else: calulate_type = 'calculateNormalModel' diff --git a/main_step3.py b/main_step3.py index 344b441..7d0a0c5 100644 --- a/main_step3.py +++ b/main_step3.py @@ -6,7 +6,7 @@ if platform.system() == 'Windows': #sys.path.append('libs') else: sys.path.append('/data/deploy/make3d/make2/libs/') -import config, libs, libs_db,main_service_db,common +import config, libs, libs_db,main_service_db,common,foot_mark_seam def bmesh_copy_from_object(obj, transform=True, triangulate=True, apply_modifiers=False): """Returns a transformed, triangulated copy of the mesh""" @@ -88,6 +88,10 @@ def base_fix(pid): bpy.context.object.location[0] = 0 bpy.context.object.location[1] = 0 bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + + #处理脚底UV + # basepath = os.path.join(config.workdir, pid, 'output') + # foot_mark_seam.get_obj_max_foot(basepath,os.path.join(basepath, f'{pid}.obj'),os.path.join(basepath, f'{pid}.jpg')) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} pid: {pid} 模型基础校正完成,共费时{libs.diff_time(start_time)}') @@ -153,7 +157,7 @@ def export_and_update_glbs(pid): # f.close() # 先生成审核模型 - faces_dest = 300000 * headcount + faces_dest = 500000 * headcount # 减面 faces_current = len(bpy.data.objects[pid_objname].data.polygons) print(f'当前面数:{faces_current},目标面数:{faces_dest}') diff --git a/manual_service.py b/manual_service.py index 50a98be..8aef172 100644 --- a/manual_service.py +++ b/manual_service.py @@ -30,18 +30,21 @@ def check_pid_file(pid): else: shutil.rmtree(os.path.join(path, file)) - #判断photo1 和 photo2 目录里的文件是否存在xmp 文件,不存在的话就删除 + #判断photo1 和 photo2 目录里的文件是否存在xmp 文件,存在的话就删除 for file in os.listdir(os.path.join(path, 'photo1')): - if not file.endswith('.xmp'): + if file.endswith('.xmp'): os.remove(os.path.join(path, 'photo1', file)) for file in os.listdir(os.path.join(path, 'photo2')): - if not file.endswith('.xmp'): + if file.endswith('.xmp'): os.remove(os.path.join(path, 'photo2', file)) #根据参数初始化操作 def cmd_run(pid,usePhoto = "1",lock=False): + pid = str(pid) + #检测文件并且下载处理文件 + check_pid_file(pid) start_time = time.time() #文件路径 @@ -61,7 +64,7 @@ def cmd_run(pid,usePhoto = "1",lock=False): usePhoto = "photo"+str(usePhoto) #执行命令 - cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName {pid} \ + cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName "{pid}" \ -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" \ -addFolder "{os.path.join(config.workdir, pid, usePhoto)}" -selectAllImages \ -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" \ @@ -81,20 +84,36 @@ def cmd_run(pid,usePhoto = "1",lock=False): #复制xmp文件 for xmp in os.listdir(sourceFile): if xmp.endswith('.xmp'): - shutil.copy(os.path.join(sourceFile, xmp), os.path.join(targetFile,xmp)) + if usePhoto == "photo1": + shutil.copy(os.path.join(sourceFile, xmp), os.path.join(targetFile,xmp.replace('_1.xmp', '_8.xmp'))) + + if usePhoto == "photo2": + shutil.copy(os.path.join(sourceFile, xmp), os.path.join(targetFile,xmp.replace('_8.xmp', '_1.xmp'))) + #如果是photo2的话,就要将photo2 的 xmp 重命名成 _8.xmp + # if usePhoto == "photo2": + # for xmp in os.listdir(sourceFile): + # if xmp.endswith('.xmp'): + # #重名名.xmp 结尾的文件 + # os.rename(os.path.join(sourceFile, xmp), os.path.join(sourceFile,xmp.replace('_1.xmp', '_8.xmp'))) + # print("坐标复制完成") + # exit() #将两组图片进行重新对齐 然后重建区域 psid = libs.getPSid(pid) - cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName {pid} \ + cmd = f'{config.rcbin} -setInstanceName {pid} \ -load "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" {config.r["setTextureFalse"]} \ -addFolder "{targetFile}" -selectAllImages \ -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" \ {libs.get_defineDistances(psid)} -update -align -align {config.r2["setRegion"]} \ {exportxmp} \ -exportReconstructionRegion "{os.path.join(config.workdir, pid, f"{pid}.rcbox")}" \ - -selectImage "'+os.path.join(config.workdir,pid,'photo2')+'\*" -enableTexturingAndColoring true' - -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}"' + -selectImage "{os.path.join(config.workdir,pid,"photo2")}/*" -enableTexturingAndColoring true \ + -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' + + + + print(cmd) cmd = shlex.split(cmd) res = subprocess.run(cmd) diff --git a/manual_single.py b/manual_single.py new file mode 100644 index 0000000..26700d8 --- /dev/null +++ b/manual_single.py @@ -0,0 +1,147 @@ +import os, sys, time, shlex, subprocess, shutil, requests, cv2, numpy as np +from PIL import Image +import platform +if platform.system() == 'Windows': + sys.path.append('e:\\libs\\') + #sys.path.append('libs') +else: + sys.path.append('/data/deploy/make3d/make2/libs/') +import config, libs, libs_db,main_service_db +# 2. 手动操作建模做成建模 +# 2.0 检测是否存在项目,不存在就下载,存在就清除项目其它无用的文件 +# 2.1 初始化工程, 根据参数 加入 photo1 或者 photo 2 的 照片, 对齐 , 导出坐标, 将坐标复制到 另外一个文件夹, 测距, 重建区域, 然后调用step2 + +#根据pid 检测是否存在项目,不存在就下载,存在就清除项目其它无用的文件 +def check_pid_file(pid): + #检测是否存在目录 + path = os.path.join(config.workdir, pid) + if not os.path.exists(path): + #不存在就在就下载目录 + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 开始计算相机位姿...') + start_time = time.time() + libs.down_from_oss(config.oss_bucket, config.workdir, pid,100,"2") + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} 图片下载完成,共费时{libs.diff_time(start_time)}') + else: + #存在的话就检测是否有photo1 和 photo2 之外的目录或者文件,有的话就删除 + for file in os.listdir(path): + if file != 'photo1' and file != 'photo2': + if os.path.isfile(os.path.join(path, file)): + os.remove(os.path.join(path, file)) + else: + shutil.rmtree(os.path.join(path, file)) + + #判断photo1 和 photo2 目录里的文件是否存在xmp 文件,存在的话就删除 + for file in os.listdir(os.path.join(path, 'photo1')): + if file.endswith('.xmp'): + os.remove(os.path.join(path, 'photo1', file)) + + for file in os.listdir(os.path.join(path, 'photo2')): + if file.endswith('.xmp'): + os.remove(os.path.join(path, 'photo2', file)) + + +#根据参数初始化操作 +def cmd_run(pid,usePhoto = "2",lock=False): + pid = str(pid) + #检测文件并且下载处理文件 + check_pid_file(pid) + + start_time = time.time() + #文件路径 + #photo1_path = os.path.join(config.workdir, pid, 'photo1') + photo2_path = os.path.join(config.workdir, pid, 'photo2') + #计算文件里的数量 + #photos1_count = len(os.listdir(photo1_path)) + photos2_count = len(os.listdir(photo2_path)) + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} photo2数量{photos2_count}') + + #xmp 坐标是要用 lock的 还是 unlock 的 + if lock: + exportxmp = ' -exportXMP "D:\\make2\\config\\exportXMP.config.lock.xml" ' + else: + exportxmp = ' -exportXMP "D:\\make2\\config\\exportXMP.config.xml" ' + + usePhoto = "photo"+str(usePhoto) + + #执行命令 + cmd = f'{config.rcbin} {config.r2["init"]} -setInstanceName "{pid}" \ + -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" \ + -addFolder "{os.path.join(config.workdir, pid, usePhoto)}" -selectAllImages \ + -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" \ + -align -align \ + {exportxmp} \ + -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' + print(cmd) + cmd = shlex.split(cmd) + res = subprocess.run(cmd) + + #根据参数转变路劲,复制photo1 里的xmp 文件到 photo2 里,或者 photo2 里的xmp 文件到 photo1 里 + # sourceFile = photo1_path + targetFile = photo2_path + # if usePhoto == "photo2": + # sourceFile = photo2_path + # targetFile = photo1_path + # #复制xmp文件 + # for xmp in os.listdir(sourceFile): + # if xmp.endswith('.xmp'): + # if usePhoto == "photo1": + # shutil.copy(os.path.join(sourceFile, xmp), os.path.join(targetFile,xmp.replace('_1.xmp', '_8.xmp'))) + + # if usePhoto == "photo2": + # shutil.copy(os.path.join(sourceFile, xmp), os.path.join(targetFile,xmp.replace('_8.xmp', '_1.xmp'))) + + #如果是photo2的话,就要将photo2 的 xmp 重命名成 _8.xmp + # if usePhoto == "photo2": + # for xmp in os.listdir(sourceFile): + # if xmp.endswith('.xmp'): + # #重名名.xmp 结尾的文件 + # os.rename(os.path.join(sourceFile, xmp), os.path.join(sourceFile,xmp.replace('_1.xmp', '_8.xmp'))) + # print("坐标复制完成") + # exit() + + #将两组图片进行重新对齐 然后重建区域 + psid = libs.getPSid(pid) + cmd = f'{config.rcbin} -setInstanceName {pid} \ + -load "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}"\ + -detectMarkers "D:\\make2\\config\\detectMarkers.config.xml" \ + {libs.get_defineDistances(psid)} -update -align -align -align -align -align -align {config.r2["setRegion"]} \ + {exportxmp} \ + -exportReconstructionRegion "{os.path.join(config.workdir, pid, f"{pid}.rcbox")}" \ + -selectImage "{os.path.join(config.workdir,pid,"photo2")}/*" -enableTexturingAndColoring true \ + -save "{os.path.join(config.workdir, pid, f"{pid}.rcproj")}" -quit' + + + + + print(cmd) + cmd = shlex.split(cmd) + res = subprocess.run(cmd) + print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} {pid} step1完成,共费时{libs.diff_time(start_time)}') + + #调用step2 + time.sleep(2) + os.system(f'python main_step2.py {pid}') + + +if __name__ == '__main__': + if len(sys.argv) == 2: + pids = sys.argv[1].split(',') + for pid in pids: + cmd_run(pid,usePhoto = "1",lock=False) + elif len(sys.argv) == 3: + pids = sys.argv[1].split(',') + for pid in pids: + if sys.argv[2] == '2': + cmd_run(pid,usePhoto = "2",lock=False) + else: + cmd_run(pid,usePhoto = "1",lock=False) + elif len(sys.argv) == 4: + pids = sys.argv[1].split(',') + usePhoto = sys.argv[2] + lock = sys.argv[3] + for pid in pids: + cmd_run(pid,usePhoto = usePhoto,lock=lock) + + else: + print(f'useage: python {sys.argv[0]} pid1,pid2,pid3 photo = 1/2 lock = True/False') + sys.exit(1) \ No newline at end of file diff --git a/timer/get_task_to_db.py b/timer/get_task_to_db.py index 2a47c44..703d181 100644 --- a/timer/get_task_to_db.py +++ b/timer/get_task_to_db.py @@ -1,10 +1,10 @@ -import platform,sys,redis,time,requests,json +import platform,sys,redis,time,requests,json,atexit if platform.system() == 'Windows': sys.path.append('e:\\libs\\') else: sys.path.append('/data/deploy/make3d/make2/libs/') -import config,libs,libs_db +import config,libs,libs_db,common r = redis.Redis(host="106.14.158.208",password="kcV2000",port=6379,db=6) def getPSid(pid): res = requests.get("https://mp.api.suwa3d.com/api/customerP3dLog/photoStudio",params={"pid":pid}) @@ -43,21 +43,17 @@ def readTask(key): print("走新的建模系统不是分布式插入-重建工单",key,pid,psid) libs_db.add_task(taskData) else: - - if int(psid) == 41 or int(psid) == 85: - taskData["priority"] = 0 - print("走分布式建模系统插入",key,pid,psid) - libs_db.add_task_distributed(taskData) - if libs_db.isStudioConfigDistribute(psid): - print("走分布式建模系统插入",key,pid,psid) + if int(psid) <= 90 and int(psid) != 29 and int(psid) != 56: + print("走分布式建模",key,pid,psid) + if int(psid) == 41 or int(psid) == 85: + taskData["priority"] = 0 libs_db.add_task_distributed(taskData) else: - print("走新的建模系统不是分布式插入",key,pid,psid) + print("非分布式建模",key,pid,psid) libs_db.add_task(taskData) - #程序主入口 if __name__ == '__main__': - + atexit.register(common.notify,"定时读取建模任务已经停止") #print(r.llen('model:make10')) #开启死循环 while True: diff --git a/tools/push_cmd.py b/tools/push_cmd.py index 1092449..c8f9223 100644 --- a/tools/push_cmd.py +++ b/tools/push_cmd.py @@ -17,6 +17,8 @@ def main(cmd, order_id): key = 'model:make10' elif cmd == 'foot': # print_id key = 'model:foot' + elif cmd == 'rebuild': #pid + key = 'model:rebuild' if order_id == 'view': for i in r.lrange(key, 0, -1): @@ -142,7 +144,7 @@ if __name__ == '__main__': r = config.redis_remote #pid 可能是多个的,用逗号分隔,查询的时候接口应该也要支持多个的 - if cmd == 'make3d' or cmd == 'make3d10': + if cmdName == 'make3d' or cmdName == 'make3d10' or cmdName == 'rebuild': main(cmdName, pid) elif cmd != "": cmd(cmdName,pid) diff --git a/建模碎片步骤.txt b/建模碎片步骤.txt new file mode 100644 index 0000000..dfeb31b --- /dev/null +++ b/建模碎片步骤.txt @@ -0,0 +1,3 @@ + +#建模完成,需要减面 smooth 封闭洞 清理模型 贴图 +%bin% -delegateTo %pid% -simplify 3000000 -smooth -closeHoles -cleanModel -calculateTexture -save "D:\123446\123446.rcproj" -exportSelectedModel "D:\123446\output\123446.obj" "d:\make2\config\ModelExportParams.xml" -quit \ No newline at end of file