You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
12 KiB
273 lines
12 KiB
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 |