Hole Feature Recognition#

Automatically detect and classify different types of holes in CAD models.

Note

Prerequisites: Attributed Adjacency Graph (AAG)

The HoleExtractor requires an Attributed Adjacency Graph (AAG) representation of the shape. If you’re new to AAG, start with the concepts documentation.

Hole Feature Recognition#

Holes are among the most common features in machined parts. volmdlr_tools provides the HoleExtractor to automatically detect and classify different types of holes in CAD models.

Why Detect Holes?#

Automatic hole detection is valuable for:

Manufacturing Analysis

  • Identify drilling and boring operations needed

  • Estimate machining time and tool requirements

  • Generate manufacturing process plans

Design Validation

  • Verify hole dimensions match fastener specifications

  • Check that counterbores/countersinks are appropriately sized

  • Ensure consistent hole patterns

Cost Estimation

  • Different hole types require different tools and operations

  • A simple round hole is cheaper than a counterbore

  • Accurate feature detection enables accurate costing

Hole Types#

volmdlr_tools recognizes five types of holes, each corresponding to different manufacturing operations and fastener types.

RoundHole#

A simple cylindrical hole, either through (open at both ends) or blind (closed at one end).

Characteristics:

  • Single cylindrical surface

  • Constant diameter throughout

  • Optional bottom face for blind holes (planar or conical drill point)

Common Uses:

  • Clearance holes for bolts

  • Mounting holes

  • Pin holes

  • Vent holes

Key Properties:

  • radius / diameter - Hole size

  • depth - Hole depth (for blind holes)

  • axis - Direction of the hole

CounterboreHole (Symbol: ⌴)#

A stepped hole with two different diameters: a larger cylinder at the entry (the counterbore) and a smaller cylinder below (the bore).

Characteristics:

  • Two coaxial cylindrical surfaces with different radii

  • Flat annular transition face between them

  • The larger section is at the entry/surface

  • Optional bottom face for blind holes (planar or conical drill point)

Common Uses:

  • Socket head cap screws (Allen bolts)

  • Hex head screws that need to sit below the surface

  • Fillister head screws

Key Properties:

  • major_radius / major_diameter - Counterbore (larger) section

  • minor_radius / minor_diameter - Bore (smaller) section

  • radius / diameter - Returns the bore (minor) values for compatibility

CountersinkHole (Symbol: ⌵)#

A hole with a conical entry section transitioning to a cylindrical bore below.

Characteristics:

  • Conical surface at entry (tapered)

  • Cylindrical surface below the cone

  • Common angles: 82°, 90°, 100°, 110°, 120°

Common Uses:

  • Flat-head countersunk screws

  • Rivets with tapered heads

  • Flush surface fastening

Key Properties:

  • cone_semi_angle - Half-angle of the cone (from axis to surface)

  • cone_angle - Full cone angle (2 × semi_angle)

  • cone_angle_degrees - Full angle in degrees

  • bore_radius / bore_diameter - Cylindrical section size

  • radius / diameter - Returns bore values for compatibility

CounterdrillHole#

A three-section hole combining features of both counterbore and countersink: a small entry cylinder, a conical transition, and a larger main bore.

Characteristics:

  • Entry cylindrical section (smallest diameter)

  • Conical transition section

  • Main bore cylindrical section (largest diameter)

Common Uses:

  • Complex fastening applications

  • Specialized drilling sequences

  • Custom fastener requirements

Key Properties:

  • entry_radius / entry_diameter - Entry (pilot) section

  • bore_radius / bore_diameter - Main bore section

  • cone_semi_angle / cone_angle / cone_angle_degrees - Transition cone

  • radius / diameter - Returns bore values for compatibility

Note: entry_radius < bore_radius always holds.

TaperHole#

A purely conical hole with no cylindrical sections.

Characteristics:

  • Single conical surface

  • Diameter varies along the axis

Common Uses:

  • Tapered pin holes

  • Self-centering locating features

  • Specialty applications

Key Properties:

  • radius / diameter - Measured at a reference point

  • axis - Direction of the taper

Using HoleExtractor#

Quick Start#

from volmdlr.model import VolumeModel
from volmdlr_tools.features import HoleExtractor
from volmdlr_tools.graph.faces import AttributedAdjacencyGraph

# Load a CAD model
vm = VolumeModel.from_step("part_with_holes.step")
solid = vm.primitives[0]

# Create the AAG and extract holes
aag = AttributedAdjacencyGraph(shape=solid)
extractor = HoleExtractor(aag=aag)
extractor.perform()

# Access the results
holes = extractor.result
print(f"Found {len(holes)} holes")

for hole in holes:
    print(f"  {type(hole).__name__}: diameter={hole.diameter:.2f}")

Filtering by Radius#

Use max_radius to ignore large cylindrical features that aren’t holes:

# Only detect holes with radius <= 10mm
extractor = HoleExtractor(aag=aag, max_radius=10.0)
extractor.perform()

Classifying by Type#

from volmdlr_tools.features.feature_types import (
    RoundHole,
    CounterboreHole,
    CountersinkHole,
    CounterdrillHole,
    TaperHole,
)

# Filter holes by type
round_holes = [h for h in holes if isinstance(h, RoundHole)]
counterbores = [h for h in holes if isinstance(h, CounterboreHole)]
countersinks = [h for h in holes if isinstance(h, CountersinkHole)]
counterdrills = [h for h in holes if isinstance(h, CounterdrillHole)]
taper_holes = [h for h in holes if isinstance(h, TaperHole)]

print(f"Round holes: {len(round_holes)}")
print(f"Counterbores: {len(counterbores)}")
print(f"Countersinks: {len(countersinks)}")
print(f"Counterdrills: {len(counterdrills)}")
print(f"Taper holes: {len(taper_holes)}")

Property Reference#

Common Properties (All Hole Types)#

Property

Type

Description

radius

float

Primary hole radius

diameter

float

Primary hole diameter (2 × radius)

axis

Vector3D

Direction vector of the hole axis

location

Point3D

Center point of the hole

depth

float

Depth of the hole along its axis (excludes drill point)

is_blind

bool

True if blind (one entry), False if through (two entries)

nodes

list[int]

Face indices that make up the hole

base_nodes

list[int]

Face indices of entry/exit surfaces

bottom_nodes

list[int]

Face indices of bottom (planar or conical drill point)

CounterboreHole Properties#

Property

Type

Description

major_radius

float

Radius of the counterbore (entry) section

major_diameter

float

Diameter of the counterbore section

minor_radius

float

Radius of the bore section

minor_diameter

float

Diameter of the bore section

CountersinkHole Properties#

Property

Type

Description

cone_semi_angle

float

Half-angle of cone (radians)

cone_angle

float

Full cone angle (radians)

cone_angle_degrees

float

Full cone angle (degrees)

bore_radius

float

Radius of cylindrical bore

bore_diameter

float

Diameter of cylindrical bore

CounterdrillHole Properties#

Property

Type

Description

entry_radius

float

Radius of entry (pilot) section

entry_diameter

float

Diameter of entry section

bore_radius

float

Radius of main bore section

bore_diameter

float

Diameter of main bore section

cone_semi_angle

float

Half-angle of transition cone (radians)

cone_angle

float

Full cone angle (radians)

cone_angle_degrees

float

Full cone angle (degrees)

Complete Example#

"""
Hole recognition example: Load a part, extract holes, and analyze them.
"""
from volmdlr.model import VolumeModel

from volmdlr_tools.features import HoleExtractor
from volmdlr_tools.features.feature_types import (
    CounterboreHole,
    CounterdrillHole,
    CountersinkHole,
    RoundHole,
    TaperHole,
)
from volmdlr_tools.graph.faces import AttributedAdjacencyGraph

# Load the model
vm = VolumeModel.from_step("data/step/holes_types.step")
solid = vm.primitives[0]
print(f"Loaded model with {len(list(solid.faces))} faces")

# Create AAG and extract holes
aag = AttributedAdjacencyGraph(shape=solid)
extractor = HoleExtractor(aag=aag, max_radius=50.0)
extractor.perform()

holes = extractor.result
print(f"\nFound {len(holes)} holes:\n")

# Analyze each hole
for i, hole in enumerate(holes, 1):
    hole_type = type(hole).__name__
    blind_status = "blind" if hole.is_blind else "through"
    print(f"Hole {i}: {hole_type} ({blind_status})")
    print(f"  Diameter: {hole.diameter:.3f} mm")
    print(f"  Depth: {hole.depth:.3f} mm")
    print(f"  Faces: {len(hole.nodes)}")

    if isinstance(hole, CounterboreHole):
        print(f"  Counterbore diameter: {hole.major_diameter:.3f} mm")
        print(f"  Bore diameter: {hole.minor_diameter:.3f} mm")

    elif isinstance(hole, CountersinkHole):
        print(f"  Cone angle: {hole.cone_angle_degrees:.1f}°")
        print(f"  Bore diameter: {hole.bore_diameter:.3f} mm")

    elif isinstance(hole, CounterdrillHole):
        print(f"  Entry diameter: {hole.entry_diameter:.3f} mm")
        print(f"  Bore diameter: {hole.bore_diameter:.3f} mm")
        print(f"  Cone angle: {hole.cone_angle_degrees:.1f}°")

    print()

# Summary by type
type_counts = {}
for hole in holes:
    type_name = type(hole).__name__
    type_counts[type_name] = type_counts.get(type_name, 0) + 1

print("Summary:")
for type_name, count in type_counts.items():
    print(f"  {type_name}: {count}")

Visualizing Holes#

You can visualize detected holes by coloring them:

from volmdlr.model import VolumeModel
from volmdlr.utils.common_operations import random_color

# Assign colors by hole type
colors = {
    "RoundHole": (0.0, 1.0, 0.0),       # Green
    "CounterboreHole": (1.0, 1.0, 0.0),  # Yellow
    "CountersinkHole": (1.0, 0.5, 0.0),  # Orange
    "CounterdrillHole": (1.0, 0.0, 0.0), # Red
    "TaperHole": (0.5, 0.0, 1.0),        # Purple
}

prims = []
for hole in holes:
    hole_type = type(hole).__name__
    hole.color = colors.get(hole_type, random_color())
    prims.extend(hole.volmdlr_primitives())

# Display with the original shape
VolumeModel([solid, *prims]).babylonjs()

How Classification Works#

The HoleExtractor uses a two-stage process:

  1. Seed Detection: Find internal cylindrical surfaces (bores, not bosses)

  2. Propagation: Collect all coaxial faces that form the complete hole

Cone Position Analysis#

Classification depends on where conical faces are located, not just whether they exist:

Cone Position

Detection Method

Meaning

Entry cone

Adjacent to base face

Countersink at hole opening

Intermediate cone

Between cylinders of different radii

Counterdrill transition

Bottom cone

At end of blind hole (drill point)

Does NOT affect classification

Classification Rules#

Condition

Resulting Type

Cylinders only, single radius

RoundHole

Cylinders only, multiple radii

CounterboreHole

Entry cone + cylinder

CountersinkHole

Entry cylinder + intermediate cone + bore cylinder

CounterdrillHole

Cone only, no cylinders

TaperHole

Important: Conical bottom faces (drill points) are ignored during classification. A RoundHole with a drill point bottom is still classified as RoundHole, not CountersinkHole.

The algorithm handles complex cases like:

  • Holes with annular transition faces (counterbores)

  • Holes with conical entry (countersinks) vs conical bottom (drill points)

  • Through holes and blind holes

  • Holes at various orientations

See Also#