| import torch |
| import numpy as np |
| import pymeshlab as ml |
| from pytorch3d.renderer import TexturesVertex |
| from pytorch3d.structures import Meshes |
| import torch |
| import torch.nn.functional as F |
| import trimesh |
| from pymeshlab import PercentageValue |
| import open3d as o3d |
|
|
|
|
| def tensor2variable(tensor, device): |
| |
| return torch.tensor(tensor, device=device, requires_grad=True) |
|
|
| def rot6d_to_rotmat(x): |
| """Convert 6D rotation representation to 3x3 rotation matrix. |
| Based on Zhou et al., "On the Continuity of Rotation Representations in Neural Networks", CVPR 2019 |
| Input: |
| (B,6) Batch of 6-D rotation representations |
| Output: |
| (B,3,3) Batch of corresponding rotation matrices |
| """ |
| x = x.view(-1, 3, 2) |
| a1 = x[:, :, 0] |
| a2 = x[:, :, 1] |
| b1 = F.normalize(a1) |
| b2 = F.normalize(a2 - torch.einsum("bi,bi->b", b1, a2).unsqueeze(-1) * b1) |
| b3 = torch.cross(b1, b2) |
| return torch.stack((b1, b2, b3), dim=-1) |
|
|
|
|
| def fix_vert_color_glb(mesh_path): |
| from pygltflib import GLTF2, Material, PbrMetallicRoughness |
| obj1 = GLTF2().load(mesh_path) |
| obj1.meshes[0].primitives[0].material = 0 |
| obj1.materials.append(Material( |
| pbrMetallicRoughness = PbrMetallicRoughness( |
| baseColorFactor = [1.0, 1.0, 1.0, 1.0], |
| metallicFactor = 0., |
| roughnessFactor = 1.0, |
| ), |
| emissiveFactor = [0.0, 0.0, 0.0], |
| doubleSided = True, |
| )) |
| obj1.save(mesh_path) |
|
|
| def srgb_to_linear(c_srgb): |
| c_linear = np.where(c_srgb <= 0.04045, c_srgb / 12.92, ((c_srgb + 0.055) / 1.055) ** 2.4) |
| return c_linear.clip(0, 1.) |
|
|
| def save_py3dmesh_with_trimesh_fast(meshes: Meshes, save_glb_path, apply_sRGB_to_LinearRGB=True): |
| |
| vertices = meshes.verts_packed().cpu().float().numpy() |
| triangles = meshes.faces_packed().cpu().long().numpy() |
| np_color = meshes.textures.verts_features_packed().cpu().float().numpy() |
| if save_glb_path.endswith(".glb"): |
| |
| vertices[:, [0, 2]] = -vertices[:, [0, 2]] |
|
|
| if apply_sRGB_to_LinearRGB: |
| np_color = srgb_to_linear(np_color) |
| assert vertices.shape[0] == np_color.shape[0] |
| assert np_color.shape[1] == 3 |
| assert 0 <= np_color.min() and np_color.max() <= 1, f"min={np_color.min()}, max={np_color.max()}" |
| mesh = trimesh.Trimesh(vertices=vertices, faces=triangles, vertex_colors=np_color) |
| mesh.remove_unreferenced_vertices() |
| |
| mesh.export(save_glb_path) |
| if save_glb_path.endswith(".glb"): |
| fix_vert_color_glb(save_glb_path) |
| print(f"saving to {save_glb_path}") |
|
|
| def load_mesh_with_trimesh(file_name, file_type=None): |
| mesh: trimesh.Trimesh = trimesh.load(file_name, file_type=file_type) |
| if isinstance(mesh, trimesh.Scene): |
| assert len(mesh.geometry) > 0 |
| |
| from io import BytesIO |
| with BytesIO() as f: |
| mesh.export(f, file_type="obj") |
| f.seek(0) |
| mesh = trimesh.load(f, file_type="obj") |
| if isinstance(mesh, trimesh.Scene): |
| |
| mesh = trimesh.util.concatenate( |
| tuple(trimesh.Trimesh(vertices=g.vertices, faces=g.faces) |
| for g in mesh.geometry.values())) |
| assert isinstance(mesh, trimesh.Trimesh) |
|
|
| vertices = torch.from_numpy(mesh.vertices).T |
| faces = torch.from_numpy(mesh.faces).T |
| colors = None |
| if mesh.visual is not None: |
| if hasattr(mesh.visual, 'vertex_colors'): |
| colors = torch.from_numpy(mesh.visual.vertex_colors)[..., :3].T / 255. |
| if colors is None: |
| |
| colors = torch.ones_like(vertices) * 0.5 |
| return vertices, faces, colors |
|
|
| def meshlab_mesh_to_py3dmesh(mesh: ml.Mesh) -> Meshes: |
| verts = torch.from_numpy(mesh.vertex_matrix()).float() |
| faces = torch.from_numpy(mesh.face_matrix()).long() |
| colors = torch.from_numpy(mesh.vertex_color_matrix()[..., :3]).float() |
| textures = TexturesVertex(verts_features=[colors]) |
| return Meshes(verts=[verts], faces=[faces], textures=textures) |
|
|
|
|
| def py3dmesh_to_meshlab_mesh(meshes: Meshes) -> ml.Mesh: |
| colors_in = F.pad(meshes.textures.verts_features_packed().cpu().float(), [0,1], value=1).numpy().astype(np.float64) |
| m1 = ml.Mesh( |
| vertex_matrix=meshes.verts_packed().cpu().float().numpy().astype(np.float64), |
| face_matrix=meshes.faces_packed().cpu().long().numpy().astype(np.int32), |
| v_normals_matrix=meshes.verts_normals_packed().cpu().float().numpy().astype(np.float64), |
| v_color_matrix=colors_in) |
| return m1 |
|
|
|
|
| def to_pyml_mesh(vertices,faces): |
| m1 = ml.Mesh( |
| vertex_matrix=vertices.cpu().float().numpy().astype(np.float64), |
| face_matrix=faces.cpu().long().numpy().astype(np.int32), |
| ) |
| return m1 |
|
|
|
|
| def to_py3d_mesh(vertices, faces, normals=None): |
| from pytorch3d.structures import Meshes |
| from pytorch3d.renderer.mesh.textures import TexturesVertex |
| mesh = Meshes(verts=[vertices], faces=[faces], textures=None) |
| if normals is None: |
| normals = mesh.verts_normals_packed() |
| |
| mesh.textures = TexturesVertex(verts_features=[normals / 2 + 0.5]) |
| return mesh |
|
|
|
|
| def from_py3d_mesh(mesh): |
| return mesh.verts_list()[0], mesh.faces_list()[0], mesh.textures.verts_features_packed() |
|
|
|
|
| def simple_clean_mesh(pyml_mesh: ml.Mesh, apply_smooth=True, stepsmoothnum=1, apply_sub_divide=False, sub_divide_threshold=0.25): |
| ms = ml.MeshSet() |
| ms.add_mesh(pyml_mesh, "cube_mesh") |
| |
| if apply_smooth: |
| ms.apply_filter("apply_coord_laplacian_smoothing", stepsmoothnum=stepsmoothnum, cotangentweight=False) |
| if apply_sub_divide: |
| ms.apply_filter("meshing_repair_non_manifold_vertices") |
| ms.apply_filter("meshing_repair_non_manifold_edges", method='Remove Faces') |
| ms.apply_filter("meshing_surface_subdivision_loop", iterations=2, threshold=PercentageValue(sub_divide_threshold)) |
| return meshlab_mesh_to_py3dmesh(ms.current_mesh()) |
|
|
|
|
|
|
| def post_process_mesh(mesh, cluster_to_keep=1000): |
| """ |
| Post-process a mesh to filter out floaters and disconnected parts |
| """ |
| import copy |
| print("post processing the mesh to have {} clusterscluster_to_kep".format(cluster_to_keep)) |
| mesh_0 = copy.deepcopy(mesh) |
| with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm: |
| triangle_clusters, cluster_n_triangles, cluster_area = (mesh_0.cluster_connected_triangles()) |
|
|
| triangle_clusters = np.asarray(triangle_clusters) |
| cluster_n_triangles = np.asarray(cluster_n_triangles) |
| cluster_area = np.asarray(cluster_area) |
| n_cluster = np.sort(cluster_n_triangles.copy())[-cluster_to_keep] |
| n_cluster = max(n_cluster, 50) |
| triangles_to_remove = cluster_n_triangles[triangle_clusters] < n_cluster |
| mesh_0.remove_triangles_by_mask(triangles_to_remove) |
| mesh_0.remove_unreferenced_vertices() |
| mesh_0.remove_degenerate_triangles() |
| print("num vertices raw {}".format(len(mesh.vertices))) |
| print("num vertices post {}".format(len(mesh_0.vertices))) |
| return mesh_0 |