# Remove holes

> Use this feature to delete holes in your mesh model to reduce its polygon count, with options to select hole types (through, blind, or surface), maximum size, and sealing material.

API function: [algo.removeHoles](/asset-transformer-sdk/2026.4/api/python/algo_functions.md#removeholes)

As part of your mesh optimization process, you can delete holes in your mesh model to reduce its polygon count. This feature deletes holes from identified patch data.

This example shows a model before and after processing:

![Remove Holes](/api/media?file=/asset-transformer-sdk/media/images/removeholes.png)

You can choose which types of holes to delete:

![Some holes have depth and others don’t.](/api/media?file=/asset-transformer-sdk/media/images/holes.png)

1. Through holes entirely go through the volume of an object and come out on the other side.
2. Blind holes partly go through the volume of an object.
3. Surface holes are cut into a surface and don’t have depth. Their edges are boundaries (shown in blue when viewing edges). [Read more](remeshsurfacicholes).

You can choose the maximum size of holes to be processed and the material with which to seal holes.

This example illustrates the deletion of holes with a diameter of at most 30 mm. Close-up images show which holes are processed:

![Remove Holes](/api/media?file=/asset-transformer-sdk/media/images/removeholes_closeup.png)

> **Tip:**
>
> Before you delete holes, we recommend that you use the patch identification feature, to identify more through holes and blind holes before processing. Because this operation increases the number of patches, we recommend that you use the patch deletion feature afterward, to reduce the number of patches back to an acceptable amount and improve performance.

The topology around the processed holes remains unchanged. For example, the polygon count may be unnecessarily high in areas where new polygons are flush with the existing mesh.

> **Tip:**
>
> We recommend that you the quality-driven decimation feature, with light parameters, to confine decimation to areas where new polygons are flush with the existing mesh. [Read more](decimatetotarget).

This example shows the results of decimation with such parameters:

![Remove Holes](/api/media?file=/asset-transformer-sdk/media/images/removeholes_decimate.png)

## Code example

The following sample will remove holes and replace them with disk-shaped, instanced meshes to reduce polygon count:

```python title="Python"
import math
from pxz import *

# algo.removeHoles parameters
through_holes = True
blind_holes = True
max_diameter = -1
# distance between hole surface and disk to avoid z-fighting
z_fighting_offset = 0.1


def create_circle():
    circle_occ = scene.createOccurrence("Circle", scene.getRoot())
    part = scene.addComponent(circle_occ, scene.ComponentType.Part)

    model = cad.createModel()
    scene.setPartModel(part, model)

    matrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
    circle = cad.createCircleCurve(0.5, matrix)
    cad.setCurveLimits(circle, cad.Bounds1D(0, 2*math.pi))

    edge = cad.createEdgeFromCurve(circle)
    coEdge = cad.createCoEdge(edge, True)
    loop = cad.createLoop([coEdge])

    plane = cad.createPlaneSurface(matrix)
    face = cad.createFace(plane, [loop])
    shell = cad.createOpenShell([face], [True])
    cad.addOpenShellToModel(shell, model)

    algo.tessellate([circle_occ], 0.01, -1, -1, True, 0, 1, 0, False, False, True, False)
    return circle_occ


def replace_holes_with_disks(selection):
    global z_fighting_offset, through_holes, blind_holes, max_diameter
    disks = []
    # replace holes with disks
    original_circle = None
    occurrence_features_list = algo.listFeatures(selection, through_holes, blind_holes, max_diameter)
    for occurrence_feature in occurrence_features_list:
        for feature in occurrence_feature.features:
            if feature.type == algo.FeatureType.Unknown:
                # replace both holes and blind holes
                continue
            for feature_input in feature.inputs:
                circle = None
                if original_circle is None:
                    original_circle = create_circle()
                    circle = original_circle
                else:
                    circle = scene.prototypeSubTree(original_circle)
                    scene.setParent(circle, scene.getRoot())
                
                diameter = feature_input.diameter
                scale = diameter
                direction = feature_input.direction
                
                # transform direction vector to euler
                length = math.sqrt(direction.x**2 + direction.y**2 + direction.z**2)
                dx, dy, dz = direction.x/length, direction.y/length, direction.z/length
                yaw_y = math.atan2(dx, dz)
                pitch_x = math.atan2(-dy, math.sqrt(dx*dx + dz*dz))
                roll_z = 0.0
                rad2deg = 180.0 / math.pi
                euler_deg = geom.Point3(pitch_x*rad2deg, yaw_y*rad2deg, roll_z)
                # slight offset to avoid z-fighting
                position = geom.Point3(feature_input.position.x + dx*z_fighting_offset,
                                           feature_input.position.y + dy*z_fighting_offset,
                                           feature_input.position.z + dz*z_fighting_offset)
                
                M_TRS = geom.fromTRS(position, euler_deg, geom.Point3(scale, scale, scale))
                core.setProperty(circle, "Transform", str(M_TRS))
                disks.append(circle)
    return disks


selection = scene.getChildren(scene.getRoot())
algo.repairMesh(selection, 0.1, True, False)
algo.identifyPatches(selection, True, 45, True, True, True, False)
disks = replace_holes_with_disks(selection)
algo.removeHoles(selection, through_holes, blind_holes, False, max_diameter)
algo.deletePatches(selection, True)
algo.decimate(selection, 0.1, 0.1, 1, -1, False) # light decimation to flatten the mesh

```
