|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import tempfile
|
|
from typing import Union
|
|
|
|
import pymeshlab
|
|
import trimesh
|
|
|
|
from .models.vae import Latent2MeshOutput
|
|
|
|
|
|
def load_mesh(path):
|
|
if path.endswith(".glb"):
|
|
mesh = trimesh.load(path)
|
|
else:
|
|
mesh = pymeshlab.MeshSet()
|
|
mesh.load_new_mesh(path)
|
|
return mesh
|
|
|
|
|
|
def reduce_face(mesh: pymeshlab.MeshSet, max_facenum: int = 200000):
|
|
mesh.apply_filter(
|
|
"meshing_decimation_quadric_edge_collapse",
|
|
targetfacenum=max_facenum,
|
|
qualitythr=1.0,
|
|
preserveboundary=True,
|
|
boundaryweight=3,
|
|
preservenormal=True,
|
|
preservetopology=True,
|
|
autoclean=True
|
|
)
|
|
return mesh
|
|
|
|
|
|
def remove_floater(mesh: pymeshlab.MeshSet):
|
|
mesh.apply_filter("compute_selection_by_small_disconnected_components_per_face",
|
|
nbfaceratio=0.005)
|
|
mesh.apply_filter("compute_selection_transfer_face_to_vertex", inclusive=False)
|
|
mesh.apply_filter("meshing_remove_selected_vertices_and_faces")
|
|
return mesh
|
|
|
|
|
|
def pymeshlab2trimesh(mesh: pymeshlab.MeshSet):
|
|
with tempfile.NamedTemporaryFile(suffix='.ply', delete=True) as temp_file:
|
|
mesh.save_current_mesh(temp_file.name)
|
|
mesh = trimesh.load(temp_file.name)
|
|
|
|
if isinstance(mesh, trimesh.Scene):
|
|
combined_mesh = trimesh.Trimesh()
|
|
|
|
for geom in mesh.geometry.values():
|
|
combined_mesh = trimesh.util.concatenate([combined_mesh, geom])
|
|
mesh = combined_mesh
|
|
return mesh
|
|
|
|
|
|
def trimesh2pymeshlab(mesh: trimesh.Trimesh):
|
|
with tempfile.NamedTemporaryFile(suffix='.ply', delete=True) as temp_file:
|
|
if isinstance(mesh, trimesh.scene.Scene):
|
|
for idx, obj in enumerate(mesh.geometry.values()):
|
|
if idx == 0:
|
|
temp_mesh = obj
|
|
else:
|
|
temp_mesh = temp_mesh + obj
|
|
mesh = temp_mesh
|
|
mesh.export(temp_file.name)
|
|
mesh = pymeshlab.MeshSet()
|
|
mesh.load_new_mesh(temp_file.name)
|
|
return mesh
|
|
|
|
|
|
def export_mesh(input, output):
|
|
if isinstance(input, pymeshlab.MeshSet):
|
|
mesh = output
|
|
elif isinstance(input, Latent2MeshOutput):
|
|
output = Latent2MeshOutput()
|
|
output.mesh_v = output.current_mesh().vertex_matrix()
|
|
output.mesh_f = output.current_mesh().face_matrix()
|
|
mesh = output
|
|
else:
|
|
mesh = pymeshlab2trimesh(output)
|
|
return mesh
|
|
|
|
|
|
def import_mesh(mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str]) -> pymeshlab.MeshSet:
|
|
if isinstance(mesh, str):
|
|
mesh = load_mesh(mesh)
|
|
elif isinstance(mesh, Latent2MeshOutput):
|
|
mesh = pymeshlab.MeshSet()
|
|
mesh_pymeshlab = pymeshlab.Mesh(vertex_matrix=mesh.mesh_v, face_matrix=mesh.mesh_f)
|
|
mesh.add_mesh(mesh_pymeshlab, "converted_mesh")
|
|
|
|
if isinstance(mesh, (trimesh.Trimesh, trimesh.scene.Scene)):
|
|
mesh = trimesh2pymeshlab(mesh)
|
|
|
|
return mesh
|
|
|
|
|
|
class FaceReducer:
|
|
def __call__(
|
|
self,
|
|
mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],
|
|
max_facenum: int = 40000
|
|
) -> Union[pymeshlab.MeshSet, trimesh.Trimesh]:
|
|
ms = import_mesh(mesh)
|
|
ms = reduce_face(ms, max_facenum=max_facenum)
|
|
mesh = export_mesh(mesh, ms)
|
|
return mesh
|
|
|
|
|
|
class FloaterRemover:
|
|
def __call__(
|
|
self,
|
|
mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],
|
|
) -> Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput]:
|
|
ms = import_mesh(mesh)
|
|
ms = remove_floater(ms)
|
|
mesh = export_mesh(mesh, ms)
|
|
return mesh
|
|
|
|
|
|
class DegenerateFaceRemover:
|
|
def __call__(
|
|
self,
|
|
mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],
|
|
) -> Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput]:
|
|
ms = import_mesh(mesh)
|
|
|
|
with tempfile.NamedTemporaryFile(suffix='.ply', delete=True) as temp_file:
|
|
ms.save_current_mesh(temp_file.name)
|
|
ms = pymeshlab.MeshSet()
|
|
ms.load_new_mesh(temp_file.name)
|
|
|
|
mesh = export_mesh(mesh, ms)
|
|
return mesh
|
|
|