Nodes/ComfyUI_Antonioilev_Lightpack/🩺 Mesh Fixer Fast
ComfyUI Node

🩺 Mesh Fixer Fast

The one that deletes the insides and calls pymeshfix

By AntonioilevΒ·Created 2 days agoΒ·Updated a day agoΒ· 0
🩺 Mesh Fixer Fast
  • input_mesh
  • mesh
  • info
β—„remove_internal_geometrytrueβ–Ί
β—„keep_largest_onlytrueβ–Ί
β—„min_component_faces500β–Ί
β—„min_component_area_ratio0.02β–Ί
β—„ray_samples_per_component32β–Ί
β—„normal_offset0.0020β–Ί
β—„ray_distance_limit0.15β–Ί
β—„fill_holestrueβ–Ί
β—„max_hole_edges200β–Ί
β—„clean_meshtrueβ–Ί

If you're going to install exactly one node from this pack for a printing or export pipeline, this is the candidate. It's the closest thing here to a proper repair pass: it works out which shells are the outside, throws away the ones that aren't, and then hands the survivor to a real remeshing library to get sealed.

What happens, in order

Base cleanup. Remove infinities, drop degenerate and duplicate faces, weld vertices, drop unreferenced ones.

Shell triage - the interesting part. The mesh is split into connected components, sorted by area. Then each component is judged:

  • Smaller than min_component_faces (default 500 faces) β†’ out.
  • Smaller than min_component_area_ratio (default 0.02) of the total area, and not the biggest β†’ out.
  • The largest component, when keep_largest_only is on β†’ kept, no questions.
  • Everything else gets a ray test: fire rays outward along a sample of its face normals (ray_samples_per_component, default 32). If more than ~45% of those rays escape past ray_distance_limit without hitting anything, the component is an outer shell and survives.

That's a nicely cheap stand-in for "is this thing inside another thing". Hunyuan3D-style reconstructions leave a shadow shell inside the outer surface, and rays fired from inside the mesh hit the outer wall almost immediately while rays from the outer shell fly off into nothing.

Sealing with pymeshfix. If fill_holes or clean_mesh is on and there are more than four faces, it imports pymeshfix, loads the arrays, runs clean(max_iters=8, inner_loops=2) and fill_small_boundaries(nbe=max_hole_edges, refine=True), and rebuilds a trimesh from the result. max_hole_edges (default 200) is the ceiling on how complex a boundary it will bother sealing.

Final. Weld, de-dupe, drop unreferenced, fix_normals.

The info string reports faces, watertightness, time, and the ops log - components=4, kept 2 outer shells, sealed is what success looks like.

The dependency that decides everything

pymeshfix is optional at import time and mandatory in practice. It's imported inside the function body, inside a try. If it isn't installed, the code appends pymeshfix skip: ... to the info string and carries on - you get the component triage and the cleanup, but no actual sealing, and no error. The node looks like it worked.

python -m pip install pymeshfix

Install it into ComfyUI's own Python environment, then read the info output of the next run: if it contains pymeshfix skip, you're running a weaker node than you think you are, and nothing else about that run will look wrong.

The parameters worth setting

  • input_mesh - TRIMESH. Note the socket is named input_mesh, not mesh, like most of its siblings.
  • min_component_faces (default 500) - an absolute face count. Fine for generated meshes in the tens of thousands of faces; if you point this at a low-poly cutter or a hand-modelled prop, everything gets deleted. Lower it for anything under ~5k faces.
  • min_component_area_ratio (default 0.02) - the relative version of the same test. Raise to 0.05 for a hard "largest part only" attitude.
  • ray_distance_limit (0.15) and normal_offset (0.002) - in mesh units, so they share the scale sensitivity the rest of this pack has. On a unit-tall model, 0.15 is a reasonable "did this ray hit the far wall" threshold; on a 100-unit scene every ray looks like it escaped and everything gets kept as "outer".
  • keep_largest_only (default on) - turn it off the moment your asset is legitimately multi-part. It is the reason your second part disappeared.
  • max_hole_edges (default 200) - lower it and big boundaries are left open (pymeshfix sealing a 4000-edge neck opening is how you get a lid where a neck was); raise it for a genuinely gappy scan.
  • fill_holes, clean_mesh - both default on. clean_mesh is the more aggressive of the two; on a mesh you care about, try it with clean_mesh off first.

Outputs: mesh first, then info - the reverse of the pack's other Fix nodes, because of course it is.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/Antonioilev/ComfyUI_Antonioilev_Lightpack.git
python -m pip install trimesh pymeshfix

Restart. ComfyUI Manager will clone the pack (search Antonioilev Light Pack) but not install anything, because the repo contains no requirements.txt and no install.py - that missing file is why so many people end up with this node running in degraded mode. After restarting, the pack prints [Antonioilev_Light_pack] Loaded N nodes (M failed); pymeshfix won't appear there because it's imported lazily, so the only place its absence shows up is the info string of a run.

Where it sits in a chain

Raw generated mesh β†’ Clean Mesh (skirt and inner shell) β†’ Mesh Fixer Fast (structural repair, sealing) β†’ Refine Mesh (surface quality) β†’ cap if needed β†’ Composite Mesh Saver. For printing, this plus Mesh One Surface are the two nodes that decide whether the slicer's autofix has to rescue you.

CategoryAntonioilev/Mesh/Fix

Inputs (11)

NameTypeDefaultDescription
input_meshTRIMESHβ€”
remove_internal_geometryoptBOOLEANtrueβ€”
keep_largest_onlyoptBOOLEANtrueβ€”
min_component_facesoptINT5000–1000000β€”
min_component_area_ratiooptFLOAT0.020–1β€”
ray_samples_per_componentoptINT324–256β€”
normal_offsetoptFLOAT0.00200.0001–0.05β€”
ray_distance_limitoptFLOAT0.150–10β€”
fill_holesoptBOOLEANtrueβ€”
max_hole_edgesoptINT2000–10000β€”
clean_meshoptBOOLEANtrueβ€”

Outputs (2)

NameTypeDescription
meshTRIMESHβ€”
infoSTRINGβ€”