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 sizedepth- 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) sectionminor_radius/minor_diameter- Bore (smaller) sectionradius/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 degreesbore_radius/bore_diameter- Cylindrical section sizeradius/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) sectionbore_radius/bore_diameter- Main bore sectioncone_semi_angle/cone_angle/cone_angle_degrees- Transition coneradius/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 pointaxis- 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 |
|---|---|---|
|
float |
Primary hole radius |
|
float |
Primary hole diameter (2 × radius) |
|
Vector3D |
Direction vector of the hole axis |
|
Point3D |
Center point of the hole |
|
float |
Depth of the hole along its axis (excludes drill point) |
|
bool |
True if blind (one entry), False if through (two entries) |
|
list[int] |
Face indices that make up the hole |
|
list[int] |
Face indices of entry/exit surfaces |
|
list[int] |
Face indices of bottom (planar or conical drill point) |
CounterboreHole Properties#
Property |
Type |
Description |
|---|---|---|
|
float |
Radius of the counterbore (entry) section |
|
float |
Diameter of the counterbore section |
|
float |
Radius of the bore section |
|
float |
Diameter of the bore section |
CountersinkHole Properties#
Property |
Type |
Description |
|---|---|---|
|
float |
Half-angle of cone (radians) |
|
float |
Full cone angle (radians) |
|
float |
Full cone angle (degrees) |
|
float |
Radius of cylindrical bore |
|
float |
Diameter of cylindrical bore |
CounterdrillHole Properties#
Property |
Type |
Description |
|---|---|---|
|
float |
Radius of entry (pilot) section |
|
float |
Diameter of entry section |
|
float |
Radius of main bore section |
|
float |
Diameter of main bore section |
|
float |
Half-angle of transition cone (radians) |
|
float |
Full cone angle (radians) |
|
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:
Seed Detection: Find internal cylindrical surfaces (bores, not bosses)
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#
Feature Processor - Feature extraction orchestrator
Attributed Adjacency Graph (AAG) - Attributed Adjacency Graph
Sheet Metal Features - Sheet metal-specific features