|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import tempfile |
|
import os |
|
from typing import Union |
|
|
|
import pymeshlab |
|
import trimesh |
|
|
|
from .models.vae import Latent2MeshOutput |
|
|
|
import folder_paths |
|
|
|
|
|
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): |
|
|
|
temp_dir = folder_paths.temp_directory |
|
os.makedirs(temp_dir, exist_ok=True) |
|
|
|
try: |
|
temp_path = os.path.join(temp_dir, 'temp_mesh.ply') |
|
|
|
|
|
mesh.save_current_mesh(temp_path) |
|
loaded_mesh = trimesh.load(temp_path) |
|
|
|
|
|
if isinstance(loaded_mesh, trimesh.Scene): |
|
combined_mesh = trimesh.Trimesh() |
|
|
|
for geom in loaded_mesh.geometry.values(): |
|
combined_mesh = trimesh.util.concatenate([combined_mesh, geom]) |
|
loaded_mesh = combined_mesh |
|
|
|
|
|
if os.path.exists(temp_path): |
|
os.remove(temp_path) |
|
|
|
return loaded_mesh |
|
|
|
except Exception as e: |
|
if os.path.exists(temp_path): |
|
os.remove(temp_path) |
|
raise Exception(f"Error in pymeshlab2trimesh: {str(e)}") |
|
|
|
|
|
def trimesh2pymeshlab(mesh: trimesh.Trimesh): |
|
|
|
temp_dir = folder_paths.temp_directory |
|
os.makedirs(temp_dir, exist_ok=True) |
|
|
|
try: |
|
temp_path = os.path.join(temp_dir, 'temp_mesh.ply') |
|
|
|
|
|
if isinstance(mesh, trimesh.scene.Scene): |
|
temp_mesh = None |
|
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_path) |
|
mesh_set = pymeshlab.MeshSet() |
|
mesh_set.load_new_mesh(temp_path) |
|
|
|
|
|
if os.path.exists(temp_path): |
|
os.remove(temp_path) |
|
|
|
return mesh_set |
|
|
|
except Exception as e: |
|
if os.path.exists(temp_path): |
|
os.remove(temp_path) |
|
raise Exception(f"Error in trimesh2pymeshlab: {str(e)}") |
|
|
|
|
|
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) |
|
|
|
|
|
temp_file = tempfile.NamedTemporaryFile(suffix='.ply', delete=False) |
|
temp_file_path = temp_file.name |
|
temp_file.close() |
|
|
|
try: |
|
ms.save_current_mesh(temp_file_path) |
|
ms = pymeshlab.MeshSet() |
|
ms.load_new_mesh(temp_file_path) |
|
finally: |
|
|
|
if os.path.exists(temp_file_path): |
|
try: |
|
os.remove(temp_file_path) |
|
except: |
|
pass |
|
|
|
mesh = export_mesh(mesh, ms) |
|
return mesh |
|
|