# Find holes and features

Claim holes, faces, edge treatments and wall thickness from the exact BRep of a STEP part.

Canonical page: https://www.geospec.xyz/docs/guides/find-holes-and-features

Feature claims read the exact BRep of a STEP file: the faces, their surfaces and how they meet. A mesh carries none of that, so every claim on this page is refused on a mesh subject rather than guessed; [Read a refusal](https://www.geospec.xyz/docs/guides/read-a-refusal.mdx) covers what a refusal asks for.

### 1. Find the bore [#1-find-the-bore]

```ts
expectGeo(part)
  .toHaveCircularHole({ diameter: 10, axis: 'z' });
expectGeo(part)
  .toHavePlanarFace({ normal: [0, 0, 1], offset: 20 });
expectGeo(part)
  .toHaveCylindricalFace({ radius: 5, axis: 'z' });
```

The plate has one 10 mm hole through its centre along z. Set `diameter` to 12 and the hole claim fails, listing the hole it did find. Switch the model to the gear, a GLB mesh with a 4 mm centre hole, and all three are refused with `GEOSPEC_UNSUPPORTED_EVIDENCE`: the mesh has no faces to classify, so neither a pass nor a failure would be an answer.

### 2. Read a hole result [#2-read-a-hole-result]

A failed hole claim carries `GEOSPEC_FEATURE_MISMATCH`, and the diagnostic's `details` list the holes the engine qualified:

```json
{
  "circularHoles": [
    {
      "axis": "z",
      "axisRange": { "max": 20, "min": 0 },
      "center": [3.2862601528904634e-13, 5.731384959986079e-17, 10],
      "diameter": 10,
      "through": true
    }
  ],
  "expected": { "diameter": 12, "tolerance": 0.02 },
  "matcher": "toHaveCircularHole"
}
```

`diameter` is measured within the tolerance; `axis`, `center` and `through` narrow which hole may match. `through` compares the engine's reading of the bore's two ends, each a mouth or a floor, so `through: false` fails on the plate. A bore the engine cannot qualify refuses the claim with `GEOSPEC_UNSUPPORTED_EVIDENCE` when no qualified hole matches, because the unqualified bore could be the one.

### 3. Claim a pattern, a chamfer and a fillet [#3-claim-a-pattern-a-chamfer-and-a-fillet]

```ts
expectGeo(part)
  .toHaveCircularHolePattern({ count: 2, holeDiameter: 10 });
expectGeo(part)
  .toHaveChamferFeature({ distance: 1 });
expectGeo(part)
  .toHaveFilletFeature({ radius: 1 });
```

The plate has one hole and sharp edges, so each claim fails with `GEOSPEC_FEATURE_MISMATCH` and its message says what it looked for: `No circular-hole pattern has 2 holes of diameter 10 within 0.02 mm.` A pattern needs two or more qualified holes with the same axis and diameter, `count` must match exactly, and `boltCircleDiameter` checks the spacing. The chamfer and fillet failures carry `mismatchKind: 'geometricMismatch'`; `selection` names one feature by the label its STEP source gives it, and a face whose membership the engine cannot settle refuses with `GEOSPEC_UNSUPPORTED_EVIDENCE`.

### 4. Claim wall thickness [#4-claim-wall-thickness]

```ts
expectGeo(part)
  .toHaveMinimumWallThickness({ value: { greaterThanOrEqual: 2 } });
```

The engine computes the exact minimum over the whole part for a complete box or a right circular cylinder only; it does not estimate thickness from samples or from a mesh. The 10 × 20 × 30 mm box is 10 mm thin across x: set `greaterThanOrEqual` to 12 and it fails with `The minimum wall thickness is 10 mm, which does not satisfy >= 12.` The plate, with its bore, is outside that domain and is refused with `GEOSPEC_UNSUPPORTED_EVIDENCE`: `The subject is outside the qualified continuous wall-thickness domains.`

## Variations [#variations]

**A planar face with an area.** Add `area: 1521.46` to the top-face claim: the plate's top is 40 × 40 mm less the bore's 78.54 mm². A face whose normal points the other way matches when both the normal and the offset flip: `normal: [0, 0, -1], offset: -20` passes too.

**Boss or bore.** `toHaveCylindricalFace` matches any cylindrical face of the radius on the axis; it does not tell a boss from a bore. Claim the hole when the hole is what you mean.

**The inventory without a claim.** `analyzeBrep` reports every hole, face, chamfer and fillet the engine found in one query; [Query geometry](https://www.geospec.xyz/docs/guides/query-geometry.mdx) shows it on the plate. The [features reference](https://www.geospec.xyz/docs/reference/features.mdx#tohavecircularhole) lists every field and refusal code.