Diffusers
Safetensors
icip_source_2 / midi /utils /mesh_process.py
hansQAQ's picture
Upload folder using huggingface_hub
278bf35 verified
import numpy as np
import open3d as o3d
import pymeshlab
import torch
import trimesh
from pymeshlab import Percentage
### Mesh Utils ###
##### read mesh
def read_mesh_from_path(mesh_path):
ms = pymeshlab.MeshSet()
ms.load_new_mesh(mesh_path)
return ms
def mesh_to_meshlab(vertices, faces):
mesh = pymeshlab.Mesh(vertex_matrix=vertices, face_matrix=faces)
ms = pymeshlab.MeshSet()
ms.add_mesh(mesh)
return ms
def meshlab_to_mesh(ms):
m = ms.current_mesh()
return m.vertex_matrix(), m.face_matrix(), m.vertex_normal_matrix()
##### decimation
def decimate_quadric_edge_collapse_with_texture(
ms, targetfacenum=None, preservenormal=True, verbose=False
):
# targetfacenum: int, Target number of faces.
# preservenormal: bool, Preserve the normals of the original mesh.
if verbose:
print("Starting decimation ... ")
m = ms.current_mesh()
if targetfacenum is None:
targetfacenum = int(m.face_number() * 0.5)
if verbose:
print("... Initial face number is %d ... " % m.face_number())
ms.meshing_decimation_quadric_edge_collapse_with_texture(
targetfacenum=targetfacenum, preservenormal=preservenormal
)
if verbose:
print("... Decimated face number is %d ... " % m.face_number())
print("Decimation done!\n ")
def decimate_quadric_edge_collapse(
ms, targetfacenum=None, preservenormal=True, verbose=False
):
# targetfacenum: int, Target number of faces.
# preservenormal: bool, Preserve the normals of the original mesh.
if verbose:
print("Starting decimation ... ")
m = ms.current_mesh()
if targetfacenum is None:
targetfacenum = int(m.face_number() * 0.5)
if verbose:
print("... Initial face number is %d ... " % m.face_number())
ms.meshing_decimation_quadric_edge_collapse(
targetfacenum=targetfacenum, preservenormal=preservenormal
)
if verbose:
print("... Decimated face number is %d ... " % m.face_number())
print("Decimation done!\n ")
##### vertex merge
def merge_close_vertices(ms, threshold=0.0001, verbose=False):
# threshold: float, Merge together all the vertices that are nearer than the specified threshold.
if verbose:
print("Starting merge vertices ... ")
m = ms.current_mesh()
if verbose:
print("... Initial vertex number is %d ... " % m.vertex_number())
ms.meshing_merge_close_vertices(threshold=Percentage(threshold * 100))
if verbose:
print("... Merged vertex number is %d ... " % m.vertex_number())
print("Merge vertices done!\n ")
##### Island Removal
def remove_isolated_pieces(ms, mincomponentsize=25, diameter=None, verbose=False):
# mincomponentsize: Delete isolated connected components composed by a limited number of triangles
# diameter: Delete isolated connected components whose diameter is smaller than the specified constant
if verbose:
print("Starting remove isolated pieces ... ")
m = ms.current_mesh()
if verbose:
print("... Initial face number is %d ... " % m.face_number())
if diameter is None:
ms.meshing_remove_connected_component_by_face_number(
mincomponentsize=mincomponentsize, removeunref=True
)
else:
ms.meshing_remove_connected_component_by_diameter(
mincomponentdiag=Percentage(diameter), removeunref=True
)
if verbose:
print("... Isolated removed face number is %d ... " % m.face_number())
print("Remove isolated pieces done!\n ")
##### hole filling
def fix_hole(ms, maxholesize=30, verbose=False):
# maxholesize: int, Maximum size of the hole to be filled.
if verbose:
print("Starting fix holes ... ")
m = ms.current_mesh()
if verbose:
print("... Initial face number is %d ... " % m.face_number())
ms.meshing_close_holes(maxholesize=maxholesize)
if verbose:
print("... Fixed hole face number is %d ... " % m.face_number())
print("Fix holes done!\n ")
##### repair non manifold edges
def repair_non_manifold(ms, verbose=False):
if verbose:
print("Starting repair non manifold edges ... ")
m = ms.current_mesh()
if verbose:
print("... Initial face number is %d ... " % m.face_number())
ms.meshing_repair_non_manifold_edges()
ms.meshing_repair_non_manifold_vertices(vertdispratio=0.1)
ms.meshing_remove_duplicate_faces()
if verbose:
print("... Fixed non manifold edges face number is %d ... " % m.face_number())
print("Repair non manifold edges done!\n ")
##### laplacian_smooth
def laplacian_smooth(ms, stepsmoothnum=3, verbose=False):
# stepsmoothnum: int, Number of smoothing steps to be performed
if verbose:
print("Starting laplacian smooth ... ")
m = ms.current_mesh()
ms.apply_coord_laplacian_smoothing(stepsmoothnum=stepsmoothnum)
if verbose:
print("Laplacian smooth done!\n ")
##### taubin_smooth
def taubin_smooth(ms, stepsmoothnum=3, verbose=False):
if verbose:
print("Starting Taubin smooth ... ")
m = ms.current_mesh()
ms.apply_coord_taubin_smoothing(stepsmoothnum=stepsmoothnum)
if verbose:
print("Taubin smooth done!\n ")
##### compute_normal
def compute_normal(ms, weightmode="Simple Average", verbose=False):
if verbose:
print("Starting compute_normal_per_vertex ... ")
m = ms.current_mesh()
ms.compute_normal_per_vertex(weightmode=weightmode)
if verbose:
print("compute_normal_per_vertex done!\n ")
### Pre-process Mesh ###
def process_mesh(
vertices,
faces,
threshold=0.0001,
mincomponentRatio=0.02,
targetfacenum=50000,
maxholesize=30,
stepsmoothnum=10,
verbose=False,
):
ms = mesh_to_meshlab(vertices, faces)
### Vertex Merge
merge_close_vertices(ms, threshold=threshold, verbose=verbose)
### Island Removal
faces = ms.current_mesh().face_matrix()
remove_isolated_pieces(
ms, mincomponentsize=int(len(faces) * mincomponentRatio), verbose=verbose
)
### Hole Filling
repair_non_manifold(ms) # repair before fix hole
fix_hole(ms, maxholesize=maxholesize, verbose=verbose)
### Taubin Smoothing
taubin_smooth(ms, stepsmoothnum=stepsmoothnum, verbose=verbose)
vertices, faces, _ = meshlab_to_mesh(ms)
if faces.shape[0] > targetfacenum:
device = o3d.core.Device("CPU:0")
dtype_f = o3d.core.float32
dtype_i = o3d.core.int64
mesh = o3d.t.geometry.TriangleMesh(device)
mesh.vertex.positions = o3d.core.Tensor(
vertices.astype(np.float32), dtype_f, device
)
mesh.triangle.indices = o3d.core.Tensor(faces.astype(np.int64), dtype_i, device)
simplified_mesh = mesh.simplify_quadric_decimation(
target_reduction=1.0 - float(targetfacenum) / faces.shape[0]
)
ms.clear()
vertices = simplified_mesh.vertex.positions.numpy()
faces = simplified_mesh.triangle.indices.numpy()
mesh = pymeshlab.Mesh(vertex_matrix=vertices, face_matrix=faces)
ms.add_mesh(mesh)
### Mesh Simplification/Decimation
# decimate_quadric_edge_collapse(ms, targetfacenum=targetfacenum, verbose=verbose)
taubin_smooth(ms, stepsmoothnum=stepsmoothnum, verbose=verbose)
repair_non_manifold(ms, verbose=verbose)
compute_normal(ms, verbose=verbose)
return meshlab_to_mesh(ms)
### UV Un-Warp ###
def uv_parameterize_uvatlas(
vertices,
faces,
size=1024,
gutter=2.5,
max_stretch=0.1666666716337204,
parallel_partitions=16,
nthreads=0,
):
device = o3d.core.Device("CPU:0")
dtype_f = o3d.core.float32
dtype_i = o3d.core.int64
mesh = o3d.t.geometry.TriangleMesh(device)
mesh.vertex.positions = o3d.core.Tensor(
vertices.astype(np.float32), dtype_f, device
)
mesh.triangle.indices = o3d.core.Tensor(faces.astype(np.int64), dtype_i, device)
mesh.compute_uvatlas(
size=size,
gutter=gutter,
max_stretch=max_stretch,
parallel_partitions=parallel_partitions,
nthreads=nthreads,
)
return mesh.triangle.texture_uvs.numpy() # (#F, 3, 2)
### Pack All ###
def process_raw(mesh_path, save_path, preprocess=True, device="cpu"):
scene = trimesh.load(mesh_path, force="mesh", process=False)
if isinstance(scene, trimesh.Trimesh):
mesh = scene
elif isinstance(scene, trimesh.scene.Scene):
mesh = trimesh.Trimesh()
for obj in scene.geometry.values():
mesh = trimesh.util.concatenate([mesh, obj])
else:
raise ValueError(f"Unknown mesh type at {mesh_path}.")
vertices = mesh.vertices
faces = mesh.faces
mesh_post_process_options = {
"mincomponentRatio": 0.02,
"targetfacenum": 50000,
"maxholesize": 100,
"stepsmoothnum": 10,
"verbose": False,
}
if preprocess:
v_pos, t_pos_idx, normals = process_mesh(
vertices=vertices,
faces=faces,
**mesh_post_process_options,
)
else:
v_pos, t_pos_idx, normals = vertices, faces, mesh.vertex_normals
v_tex_np = (
uv_parameterize_uvatlas(v_pos, t_pos_idx).reshape(-1, 2).astype(np.float32)
)
v_pos = torch.from_numpy(v_pos).to(device=device, dtype=torch.float32)
t_pos_idx = torch.from_numpy(t_pos_idx).to(device=device, dtype=torch.long)
v_tex = torch.from_numpy(v_tex_np).to(device=device, dtype=torch.float32)
normals = torch.from_numpy(normals).to(device=device, dtype=torch.float32)
assert v_tex.shape[0] == t_pos_idx.shape[0] * 3
t_tex_idx = torch.arange(
t_pos_idx.shape[0] * 3,
device=device,
dtype=torch.long,
).reshape(-1, 3)
# uv, index = torch.unique(v_tex, dim=0, return_inverse=True) # 这样实现是2毫秒
# super efficient de-duplication
v_tex_u_uint32 = v_tex_np[..., 0].view(np.uint32)
v_tex_v_uint32 = v_tex_np[..., 1].view(np.uint32)
v_hashed = (v_tex_u_uint32.astype(np.uint64) << 32) | v_tex_v_uint32
v_hashed = torch.from_numpy(v_hashed.view(np.int64)).to(v_pos.device)
t_pos_idx_f3 = torch.arange(
t_pos_idx.shape[0] * 3, device=t_pos_idx.device, dtype=torch.long
).reshape(-1, 3)
v_pos_f3 = v_pos[t_pos_idx].reshape(-1, 3)
normals_f3 = normals[t_pos_idx].reshape(-1, 3)
v_hashed_dedup, inverse_indices = torch.unique(v_hashed, return_inverse=True)
dedup_size, full_size = v_hashed_dedup.shape[0], inverse_indices.shape[0]
indices = torch.scatter_reduce(
torch.full(
[dedup_size],
fill_value=full_size,
device=inverse_indices.device,
dtype=torch.long,
),
index=inverse_indices,
src=torch.arange(full_size, device=inverse_indices.device, dtype=torch.int64),
dim=0,
reduce="amin",
)
v_tex = v_tex[indices]
t_tex_idx = inverse_indices.reshape(-1, 3)
v_pos = v_pos_f3[indices]
normals = normals_f3[indices]
normals = normals.to(dtype=torch.float32, device=device)
# either flip uv or flip texture
# here we flip uv
uv_to_save = v_tex.clone()
uv_to_save[:, 1] = 1.0 - uv_to_save[:, 1]
visual = trimesh.visual.TextureVisuals(uv=uv_to_save.cpu().numpy())
tmesh = trimesh.Trimesh(
vertices=v_pos.cpu().numpy(),
faces=t_tex_idx.cpu().numpy(),
vertex_normals=normals.cpu().numpy(),
visual=visual,
process=False,
)
tmesh.export(save_path)