建模程序 多个定时程序
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

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