microsoft / microsoft/TRELLIS.2

Trellis generating duplicate inner mesh walls

Open
#140 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
11.3k
Forks
1.4k
PR merge metrics
No merged PRs in 30d

Description

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')

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing a TRELLIS.2 generation at 1024 resolution with a 2048 texture, then inspect the resulting mesh in Blender 5.1.0. Compare the duplicate inner walls with the supplied healing script and determine whether the defect originates in generation or mesh cleanup; done means the generated mesh no longer contains duplicate inner shells without introducing the random cleanup issues shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
blender, python
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.