273 lines
12 KiB
Python
273 lines
12 KiB
Python
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 |