import os, sys, time, argparse, requests, json, re, oss2 import bpy, bmesh import open3d as o3d import numpy as np # import cupy as cp from mathutils import Matrix import math import logging import matplotlib.pyplot as plt def remove_nan_verts(obj): '''删除模型中空坐标的顶点''' # obj = bpy.context.active_object if obj.mode == 'EDIT': bm = bmesh.from_edit_mesh(obj.data) else: bm = bmesh.new() bm.from_mesh(obj.data) nans = [v for v in bm.verts if any(c != c for c in v.co)] bmesh.ops.delete(bm, geom=nans, context='VERTS') degenerates = [f for f in bm.faces if len(f.verts) < 3] bmesh.ops.delete(bm, geom=degenerates, context='FACES') if obj.mode == 'EDIT': bmesh.update_edit_mesh(obj.data) else: bm.to_mesh(obj.data) obj.data.update() print("NAN vertices and degenerate faces removed.") def getHeadCount(pid): get_printinfo_url = 'https://mp.api.suwa3d.com/api/customerP3dLog/printInfo' res = requests.get(get_printinfo_url, params={'id': pid}) print('get_printinfo_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 get_obj_bake_vertex_ply(obj_filepath, save_ply_path): start_time = time.time() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Getting ply vertex from {obj_filepath}') bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_by_type(type='MESH') bpy.ops.object.delete(use_global=False, confirm=False) bpy.ops.wm.obj_import(filepath=obj_filepath) obj_original = [obj for obj in bpy.data.objects if obj.type == 'MESH'][0] bpy.context.view_layer.objects.active = obj_original obj_original.select_set(True) pid = obj_original.name obj_num_vertices = len(obj_original.data.vertices) # print(f"模型 {pid} 顶点数量:", len(obj_original.data.vertices)) # 选择新分离出来的网格对象,并将该对象设为进活动对象 bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = obj_original obj_original.select_set(True) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Split foot time: ', time.time() - start_time) start_time = time.time() # 表面细分Subdivision Surface,如果细分后定点数目大于目标数目,停止循环细分 # target_verts = len(obj_original.data.vertices) * 8 try: target_verts = getHeadCount(pid) * 2000000 except TypeError as e: return 0 mesh_data = obj_original.data obj_original_name = obj_original.name # Blender 网格对象的顶点数据转换为 Open3D 格式 o3d_mesh = o3d.geometry.TriangleMesh() o3d_mesh.vertices = o3d.utility.Vector3dVector([v.co for v in mesh_data.vertices]) o3d_mesh.triangles = o3d.utility.Vector3iVector([f.vertices for f in mesh_data.polygons]) o3d_mesh.compute_vertex_normals() # 计算顶点法线 # open3d 细分操作 for i in range(2, 8): mesh_subdivided = o3d_mesh.subdivide_loop(number_of_iterations=i) subdivision_vertex_count = len(mesh_subdivided.vertices) if subdivision_vertex_count > target_verts: break print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Subdivision time: ', time.time() - start_time) start_time = time.time() # open3d 减面操作 # 官网文档介绍简化网格不能保证一定会达到这个数字 target_triangles = target_verts * 2 mesh_smp = mesh_subdivided.simplify_quadric_decimation(target_number_of_triangles=target_triangles) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Decimation time: ', time.time() - start_time) start_time = time.time() # 将open3d网格转换回blender网格 o3d_to_blender_mesh = bpy.data.meshes.new(name=f"{obj_original_name}_decimation") o3d_to_blender_mesh.from_pydata(mesh_smp.vertices, [], mesh_smp.triangles) o3d_to_blender_mesh.update() obj_open3d = bpy.data.objects.new(f"{obj_original_name}_decimation", o3d_to_blender_mesh) # 设置新对象的位置和旋转信息,等于原始模型的信息 obj_open3d.location = obj_original.location obj_open3d.rotation_euler = obj_original.rotation_euler obj_open3d.scale = obj_original.scale bpy.context.collection.objects.link(obj_open3d) # 开始将obj_mesh的贴图颜色烘焙到open3d模型上 # 复制对象作为罩体 obj_cage = obj_open3d.copy() obj_cage.data = obj_open3d.data.copy() bpy.context.collection.objects.link(obj_cage) obj_cage.name = f"{obj_original_name}_cage" bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = obj_open3d obj_open3d.select_set(True) bpy.ops.object.mode_set(mode='EDIT') bpy.context.scene.tool_settings.use_uv_select_sync = True # 同步3D_View和UV编辑模式选中顶点部分 bpy.ops.uv.smart_project() bpy.ops.uv.select_overlap() bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0) bpy.ops.uv.select_all(action='SELECT') bpy.ops.uv.average_islands_scale() bpy.ops.uv.pack_islands(margin=0) bpy.ops.object.mode_set(mode='OBJECT') # 新建open3d模型的材质 open3dMat = bpy.data.materials.new(name='Mat_open3d') obj_open3d.data.materials.append(open3dMat) open3dMat.use_nodes = True nodes = open3dMat.node_tree.nodes prin_bsdf_node = nodes.get("Principled BSDF") # 创建新的贴图对象 tex_image_node = nodes.new("ShaderNodeTexImage") tex_image = bpy.data.images.new(name="o3d_teximage", width=8192, height=8192) tex_image_node.image = tex_image tex_image_node.location = (-400, 200) tex_image_node.select = True # 节点选中状态 tex_image_node.select = True # 节点为活动状态 links = open3dMat.node_tree.links bpy.context.object.active_material = open3dMat bpy.ops.object.material_slot_select() bpy.ops.object.material_slot_assign() print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Prepare bake data time: ', time.time() - start_time) start_time = time.time() # 烘焙操作 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_pass_color = True bpy.context.scene.render.bake.use_selected_to_active = True bpy.context.scene.render.bake.use_cage = True bpy.context.scene.render.bake.cage_object = bpy.data.objects[obj_cage.name] bpy.context.scene.render.bake.target = 'IMAGE_TEXTURES' # 添加置换修改器,不能应用 bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = obj_cage obj_cage.select_set(True) bpy.ops.object.modifier_add(type='DISPLACE') bpy.context.object.modifiers["Displace"].mid_level = 0.995 bpy.ops.object.modifier_apply(modifier="Displace") bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = obj_original obj_original.select_set(True) bpy.context.view_layer.objects.active = obj_open3d obj_open3d.select_set(True) bpy.ops.object.bake(type='DIFFUSE') # 开始 Bake print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Bake DIFFUSE time: ', time.time() - start_time) start_time = time.time() links.new(tex_image_node.outputs[0], prin_bsdf_node.inputs[0]) bpy.data.objects.remove(obj_original, do_unlink=True) bpy.data.objects.remove(obj_cage, do_unlink=True) # 添加颜色属性 bpy.ops.geometry.color_attribute_add() # 节点的创建: # nodes = bpy.context.active_object.active_material.node_tree.nodes # links = bpy.context.active_object.active_material.node_tree.links tex_coord = nodes.new("ShaderNodeTexCoord") Mapping = nodes.new('ShaderNodeMapping') vertex_color = nodes.new("ShaderNodeVertexColor") tex_coord.location = (-800, 600) Mapping.location = (-500, 200) vertex_color.location = (-200, 800) # 节点之间的连接: # 纹理坐标的UV 连接 映射的矢量;映射的矢量 连接 纹理贴图的矢量;原理化BSDF删除;纹理贴图的矢量 连接 表(曲)面。 links.new(tex_coord.outputs[2], nodes['Mapping'].inputs[0]) links.new(Mapping.outputs[0], nodes['Image Texture'].inputs[0]) principled_bsdf = nodes.get("Principled BSDF") nodes.remove(principled_bsdf) links.new(nodes['Image Texture'].outputs[0], nodes['Material Output'].inputs[0]) # 4.渲染烘焙相关操作 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 = 'EMIT' bpy.context.scene.render.bake.use_selected_to_active = False bpy.context.scene.render.bake.target = 'VERTEX_COLORS' bpy.ops.object.bake(type='EMIT') # 开始 Bake # 连接节点 Color Attribute 和 节点 Material Output links.new(nodes['Color Attribute'].outputs[0], nodes['Material Output'].inputs[0]) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Bake EMIT time: ', time.time() - start_time) start_time = time.time() # 清除顶点为nan的无效数据 bpy.ops.object.select_all(action='DESELECT') bpy.context.view_layer.objects.active = obj_open3d obj_open3d.select_set(True) remove_nan_verts(obj_open3d) # 5.输出模型 # bpy.ops.wm.save_as_mainfile(filepath='/data/datasets/plys/121614.bend') bpy.ops.wm.ply_export(filepath=save_ply_path, apply_modifiers=True, export_selected_objects=True, export_uv=False, export_normals=False, export_colors='SRGB', export_triangulated_mesh=False, ascii_format=True) bpy.data.objects.remove(obj_open3d, do_unlink=True) print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: Save ply time: ', time.time() - start_time) return obj_num_vertices if __name__ == '__main__': # workdir = '/data/datasets/suwa_cut/data/auto/' # output = '/data/datasets/plys/auto/' workdir = 'G:\obj_fix\print' output = 'G:\obj_fix\ply\whole_obj_bake_vertex_high_ply' directory = f'G:\obj_fix/print/right' #遍历获取 G:\obj_fix\print\right 目录的文件,根据文件名获取 pid for file_name in os.listdir(directory): # 获取文件的完整路径 file_path = os.path.join(directory, file_name) # 检查是否是文件 if os.path.isfile(file_path): # arrFileName = file_name.split('_') pid = arrFileName[0] if int(pid) == 0: continue print(f"File: {file_name}, PID: {pid}") #判断文件是否存在 file_path = os.path.join(output, f'{pid}.ply') if os.path.exists(file_path) and os.path.isfile(file_path): print("文件已存在,跳过循环!!!") continue obj_filepath = os.path.join(workdir, f'{pid}/{pid}.obj') save_ply_path = os.path.join(output, f'{pid}.ply') if not os.path.exists(obj_filepath): print(f"{obj_filepath} 文件不存在,跳过循环!!!") continue obj_num_vertices = get_obj_bake_vertex_ply(obj_filepath, save_ply_path) if obj_num_vertices == 0: print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}: 模型 {pid} 程序getHeadCount()无法找到模型人数!总共运行时间: ', time.time() - start_time0) logging.info(f'模型 {pid} 顶点数量: {obj_num_vertices},程序getHeadCount()无法找到模型人数!,程序总共运行时间: {time.time() - start_time0:.2f} 秒') continue