Added "Remove Floater" from pymeshlab -> don't forget to install the requirements

This commit is contained in:
Bruno Fargnoli
2025-12-27 23:41:36 +01:00
parent d9de2d40c0
commit e0cf2cc178
2 changed files with 54 additions and 13 deletions
+51 -13
View File
@@ -15,6 +15,8 @@ import cv2
import gc
import copy
import pymeshlab
import cumesh as CuMesh
import meshlib.mrmeshnumpy as mrmeshnumpy
@@ -111,6 +113,35 @@ def simplify_with_meshlib(vertices, faces, target=1000000):
return new_vertices, new_faces
def remove_floater(mesh):
print('Removing floater ...')
faces = mesh.faces.cpu().numpy()
print(f"Current faces: {len(faces)}")
mesh_set = pymeshlab.MeshSet()
mesh_pymeshlab = pymeshlab.Mesh(vertex_matrix=mesh.vertices.cpu().numpy(), face_matrix=faces)
mesh_set.add_mesh(mesh_pymeshlab, "converted_mesh")
mesh_set = pymeshlab_remove_floater(mesh_set)
mesh_pymeshlab = mesh_set.current_mesh()
new_faces = mesh_pymeshlab.face_matrix()
print(f"After removing floater: {len(new_faces)}")
new_vertices = torch.from_numpy(mesh_pymeshlab.vertex_matrix()).contiguous().float()
new_faces = torch.from_numpy(new_faces).contiguous().int()
mesh.vertices = new_vertices
mesh.faces = new_faces
return mesh
def pymeshlab_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
class Trellis2LoadModel:
@classmethod
def INPUT_TYPES(s):
@@ -395,11 +426,11 @@ class Trellis2ExportMesh:
output_glb_path = Path(full_output_folder, f'{filename}_{counter:05}_.{file_format}')
output_glb_path.parent.mkdir(exist_ok=True)
if save_file:
trimesh.export(output_glb_path, file_type=file_format)
trimesh.export(output_glb_path, file_type=file_format, include_normals=True)
relative_path = Path(subfolder) / f'{filename}_{counter:05}_.{file_format}'
else:
temp_file = Path(full_output_folder, f'hy3dtemp_.{file_format}')
trimesh.export(temp_file, file_type=file_format)
trimesh.export(temp_file, file_type=file_format,include_normals=True)
relative_path = Path(subfolder) / f'hy3dtemp_.{file_format}'
return (str(relative_path), )
@@ -409,7 +440,7 @@ class Trellis2PostProcessMesh:
def INPUT_TYPES(s):
return {
"required": {
"mesh": ("MESHWITHVOXEL",),
"mesh": ("MESHWITHVOXEL",),
"fill_holes": ("BOOLEAN", {"default":True}),
"fill_holes_max_perimeter": ("FLOAT",{"default":0.03,"min":0.001,"max":99.999,"step":0.001}),
"remove_duplicate_faces": ("BOOLEAN",{"default":True}),
@@ -418,6 +449,7 @@ class Trellis2PostProcessMesh:
"remove_small_connected_components": ("BOOLEAN", {"default":True}),
"remove_small_connected_components_size": ("FLOAT", {"default":0.00001,"min":0.00001,"max":9.99999,"step":0.00001}),
"unify_faces_orientation": ("BOOLEAN", {"default":True}),
"remove_floaters": ("BOOLEAN",{"default":True}),
},
}
@@ -427,9 +459,12 @@ class Trellis2PostProcessMesh:
CATEGORY = "Trellis2Wrapper"
OUTPUT_NODE = True
def process(self, mesh, fill_holes, fill_holes_max_perimeter, remove_duplicate_faces, repair_non_manifold_edges, remove_non_manifold_faces, remove_small_connected_components, remove_small_connected_components_size,unify_faces_orientation):
def process(self, mesh, fill_holes, fill_holes_max_perimeter, remove_duplicate_faces, repair_non_manifold_edges, remove_non_manifold_faces, remove_small_connected_components, remove_small_connected_components_size,unify_faces_orientation,remove_floaters):
mesh_copy = copy.deepcopy(mesh)
if remove_floaters:
mesh_copy = remove_floater(mesh_copy)
vertices = mesh_copy.vertices
faces = mesh_copy.faces
@@ -742,17 +777,18 @@ class Trellis2PostProcessAndUnWrapAndRasterizer:
"mesh_cluster_refine_iterations": ("INT",{"default":0}),
"mesh_cluster_global_iterations": ("INT",{"default":1}),
"mesh_cluster_smooth_strength": ("INT",{"default":1}),
"texture_size": ("INT",{"default":1024, "min":512, "max":16384}),
"texture_size": ("INT",{"default":2048, "min":512, "max":16384}),
"remesh": ("BOOLEAN",{"default":True}),
"remesh_band": ("FLOAT",{"default":1.0}),
"remesh_project": ("FLOAT",{"default":0.0}),
"target_face_num": ("INT",{"default":1000000,"min":1,"max":16000000}),
"target_face_num": ("INT",{"default":2000000,"min":1,"max":16000000}),
"simplify_method": (["Cumesh","Meshlib"],{"default":"Cumesh"}),
"fill_holes": ("BOOLEAN", {"default":True}),
"fill_holes_max_perimeter": ("FLOAT",{"default":0.03,"min":0.001,"max":99.999,"step":0.001}),
"texture_alpha_mode": (["OPAQUE","MASK","BLEND"],{"default":"OPAQUE"}),
"dual_contouring_resolution": (["Auto","128","256","512","1024","2048"],{"default":"Auto"}),
"dual_contouring_resolution": (["Auto","128","256","512","1024","2048"],{"default":"512"}),
"double_side_material": ("BOOLEAN",{"default":True}),
"remove_floaters": ("BOOLEAN",{"default":True}),
},
}
@@ -762,13 +798,11 @@ class Trellis2PostProcessAndUnWrapAndRasterizer:
CATEGORY = "Trellis2Wrapper"
OUTPUT_NODE = True
def process(self, mesh, mesh_cluster_threshold_cone_half_angle_rad, mesh_cluster_refine_iterations, mesh_cluster_global_iterations, mesh_cluster_smooth_strength, texture_size, remesh, remesh_band, remesh_project, target_face_num, simplify_method, fill_holes, fill_holes_max_perimeter, texture_alpha_mode, dual_contouring_resolution, double_side_material):
def process(self, mesh, mesh_cluster_threshold_cone_half_angle_rad, mesh_cluster_refine_iterations, mesh_cluster_global_iterations, mesh_cluster_smooth_strength, texture_size, remesh, remesh_band, remesh_project, target_face_num, simplify_method, fill_holes, fill_holes_max_perimeter, texture_alpha_mode, dual_contouring_resolution, double_side_material,remove_floaters):
mesh_copy = copy.deepcopy(mesh)
aabb = [[-0.5, -0.5, -0.5], [0.5, 0.5, 0.5]]
vertices = mesh_copy.vertices
faces = mesh_copy.faces
attr_volume = mesh_copy.attrs
coords = mesh_copy.coords
attr_layout = mesh_copy.layout
@@ -798,9 +832,13 @@ class Trellis2PostProcessAndUnWrapAndRasterizer:
grid_size = np.array(grid_size)
if isinstance(grid_size, np.ndarray):
grid_size = torch.tensor(grid_size, dtype=torch.int32, device=coords.device)
voxel_size = (aabb[1] - aabb[0]) / grid_size
voxel_size = (aabb[1] - aabb[0]) / grid_size
print(f"Original mesh: {vertices.shape[0]} vertices, {faces.shape[0]} faces")
if remove_floaters:
mesh_copy = remove_floater(mesh_copy)
vertices = mesh_copy.vertices
faces = mesh_copy.faces
vertices = vertices.cuda()
faces = faces.cuda()
+3
View File
@@ -1,2 +1,5 @@
meshlib
requests
pymeshlab
opencv-python
scipy