Skip to content

Trellis generating duplicate inner mesh walls #140

@firesightstudios

Description

@firesightstudios

Most generations add an entire inner shell of the mesh.

I was able to create a function in blender that can 99% remove the inner shell but results in some random issues

Image Image

Stats on Model post Trellis2 Generation at 1024 Resolution and 2048 Texture Size

POST TRELLIS 2 Generation:

Image

Post Healing Script - Removing Inner Walls:

Image

Blender 5.1.0
Base Blender Script used:

import bpy
import bmesh
from mathutils.bvhtree import BVHTree

class GeometryAlgorithm:
    Mathematical targeted healing of AI mesh anomalies.

    @staticmethod
    def heal_geometry(obj):
        if bpy.context.active_object and bpy.context.active_object.mode != 'OBJECT':
            bpy.ops.object.mode_set(mode='OBJECT')

        bpy.ops.object.select_all(action='DESELECT')
        obj.hide_set(False)
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj

        # 1. Apply Transforms (Crucial for Mixamo auto-rigger)
        bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
        bpy.ops.mesh.customdata_custom_splitnormals_clear()

        bpy.ops.object.mode_set(mode='EDIT')


        # ==========================================
        # --- PASS 1: LINE-OF-SIGHT SEED & SPREAD ---
        # ==========================================
        bm = bmesh.from_edit_mesh(obj.data)
        bm.faces.ensure_lookup_table()
        
        # Deselect everything to prepare for seeding
        for f in bm.faces:
            f.select = False

        verts_co = [v.co for v in bm.verts]
        faces_indices = [[v.index for v in f.verts] for f in bm.faces]
        tree = BVHTree.FromPolygons(verts_co, faces_indices)

        seed_count = 0
        
        # 1. Raycast Seed: If a face's normal points directly at the sky, it's 100% outer shell.
        for f in bm.faces:
            # Nudge the origin slightly to prevent hitting the face itself
            origin = f.calc_center_median() + (f.normal * 0.0001)
            loc, _, _, _ = tree.ray_cast(origin, f.normal)
            
            if loc is None:
                f.select = True
                seed_count += 1
                
        # 2. Extreme Bounds Seed: An absolute guarantee to find the outer limits of the mesh
        if bm.verts:
            extremes = [
                min(bm.verts, key=lambda v: v.co.z), max(bm.verts, key=lambda v: v.co.z),
                min(bm.verts, key=lambda v: v.co.x), max(bm.verts, key=lambda v: v.co.x),
                min(bm.verts, key=lambda v: v.co.y), max(bm.verts, key=lambda v: v.co.y)
            ]
            for v in extremes:
                for f in v.link_faces:
                    if not f.select:
                        f.select = True
                        seed_count += 1

        bmesh.update_edit_mesh(obj.data)

        if seed_count > 0:
            # 3. The Spread: Select everything connected to the outer shell seeds!
            # This perfectly protects backpacks, deep armpits, and tight crevices.
            bpy.ops.mesh.select_linked()
            
            # 4. The Purge: Delete everything that is detached/inside (The Phantom Inner Wall)
            bpy.ops.mesh.select_all(action='INVERT')
            bpy.ops.mesh.delete(type='FACE')


        # ==========================================
        # --- PASS 2: SURGICAL BMESH STRAY EDGE SNIP ---
        # ==========================================
        # Re-initialize bmesh after the face deletion
        bm = bmesh.from_edit_mesh(obj.data)
        for _ in range(2):
            bm.edges.ensure_lookup_table()
            bm.verts.ensure_lookup_table()
            
            stray_edges = [e for e in bm.edges if len(e.link_faces) == 0]
            if stray_edges:
                bmesh.ops.delete(bm, geom=stray_edges, context='EDGES')

            stray_verts = [v for v in bm.verts if len(v.link_faces) == 0]
            if stray_verts:
                bmesh.ops.delete(bm, geom=stray_verts, context='VERTS')
                
        bmesh.update_edit_mesh(obj.data)


        # ==========================================
        # --- PASS 3: SAFE MACRO GEOMETRY HEALING ---
        # ==========================================
        for _ in range(3):
            bpy.ops.mesh.select_all(action='SELECT')
            
            # Since the phantom inner shell was vaporized, it is now 
            # 100% safe to run the vertex merge without the walls pinching together!
            bpy.ops.mesh.remove_doubles(threshold=0.0005)

            # Blender's native interior face selector handles any remaining micro-webbing
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.mesh.select_interior_faces()
            bpy.ops.mesh.delete(type='FACE')

            # Instantly bridges any holes shut left by the purge.
            bpy.ops.mesh.select_all(action='SELECT')
            bpy.ops.mesh.fill_holes(sides=100)


        # ==========================================
        # --- PASS 4: DEGENERATE FACE CLEANUP ---
        # ==========================================
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete_loose()
        
        # Safely eliminates the 88k micro-faces AI tools generate
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.dissolve_degenerate(threshold=0.002)

        # Force all normals outward to fix Mixamo voxelizer holes
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.normals_make_consistent(inside=False)


        # ==========================================
        # --- CLEANUP ---
        # ==========================================
        try:
            bpy.ops.object.mode_set(mode='SCULPT')
            bpy.ops.paint.mask_clear()
        except Exception:
            pass

        bpy.ops.object.mode_set(mode='OBJECT')

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions