# Check meshes

Claim that a mesh is closed, sound and in one piece, and count the parts of an assembly mesh.

Canonical page: https://www.geospec.xyz/docs/guides/check-meshes

A mesh answers for its triangles: whether they close, whether any are degenerate or duplicated, and how many pieces they form. Mesh checks run on GLB files; neither `loadModel` nor `load_model` admits raw triangles.

### 1. Claim closure and integrity [#1-claim-closure-and-integrity]

```ts
expectGeo(part)
  .toBeWatertight();
expectGeo(part)
  .toHaveMeshIntegrity({
    finitePositions: true,
    degenerateTriangles: { count: 0 },
    duplicateFaces: { count: 0 },
    triangleCount: 624,
  });
expectGeo(part)
  .toHaveNoDiagnostics();
```

The gear is a GLB mesh of 624 triangles. Set `triangleCount` to 600 and the integrity claim fails with `GEOSPEC_MESH_INTEGRITY_MISMATCH`, stating `624 triangles, which does not satisfy 600`. `toBeWatertight` reports the edges it checked, `{ "irregularEdgeFraction": 0, "irregularEdges": 0, "nonManifoldEdges": 0, "openBoundaryEdges": 0, "totalEdges": 936, "watertight": true }`, and `toHaveNoDiagnostics` reads what the loader reported at admission, errors and warnings by default, and measures no geometry.

**TypeScript**

The measurement is `report.evidence.measured` on the assertion `expectGeo` returns.

**Python**

The measurement is `report.result["evidence"]["measured"]` on the report `expect_geo` returns.

Switch the model to the plate, a STEP part: closure passes from the exact BRep topology, and the integrity claim is refused with `GEOSPEC_UNSUPPORTED_EVIDENCE`, because its fields would measure a tessellation the engine made; `toHaveMeshIntegrity({ watertight: true })` is the one form STEP answers.

### 2. Count the pieces [#2-count-the-pieces]

`toHaveConnectedComponents` counts closed material shells, and shells closer than `toleranceMm` count as one. The gear is one piece, so `{ count: 2 }` fails with `GEOSPEC_CONNECTED_COMPONENTS_MISMATCH`; the count indexes every triangle, which keeps it out of the live demo. The script counts the two 10 mm cubes, 20 mm apart, of an engine fixture, `packages/geospec-engine/bench/fixtures/performance-lab/generated/assembly.glb`, copied next to it:

**TypeScript**

```ts title="check.ts"
import { readFile } from 'node:fs/promises';
import { expectGeo } from 'geospec';
import { createModelLoader } from 'geospec/model';
import { Engine } from 'geospec-engine/node';

const engine = new Engine();
const loadModel = createModelLoader({
  engine,
  readSource: async (source) => new Uint8Array(await readFile(source as string)),
});
const cubes = await loadModel({ source: 'assembly.glb', format: 'glb', sourceUnit: 'mm' });

const pieces = expectGeo(cubes).toHaveConnectedComponents({ count: 2 });
const merged = expectGeo(cubes).toHaveConnectedComponents({ count: 1, toleranceMm: 21 });
console.log(pieces.report?.status, merged.report?.status);

await loadModel.dispose();
engine.close();
```

```console
$ node check.ts
passed passed
```

**Python**

```python title="check.py"
from geospec import GeoSpecEngine, expect_geo, load_model

with GeoSpecEngine(model_root="."):
    cubes = load_model(file="assembly.glb", format="glb", source_unit="mm")
    pieces = expect_geo(cubes).to_have_connected_components(count=2)
    merged = expect_geo(cubes).to_have_connected_components(count=1, toleranceMm=21)
    print(pieces.result["status"], merged.result["status"])
```

```console
$ python check.py
passed passed
```

Keyword arguments keep the camelCase field names, as `toleranceMm=21` does.

The second claim spans the 20 mm gap with `toleranceMm: 21`, so the cubes count as one. The tolerance states how close two shells may sit and still be separate parts; it is not a way to make a count pass. [Set tolerances and budgets](https://www.geospec.xyz/docs/guides/set-tolerances-and-budgets.mdx) covers it.

## Variations [#variations]

**An open mesh.** Remove one triangle from a closed box mesh and the claims answer differently: closure fails with `GEOSPEC_WATERTIGHT_MISMATCH`, `The mesh has 3 irregular edges (3 open, 0 non-manifold), so it is not a closed manifold surface.`; the component count is refused with `GEOSPEC_INVALID_EVIDENCE` rather than guessed, because shells are counted over closed, consistently oriented triangles; the integrity claim fails on its triangle count. The open box still reports a volume, 5000 mm³ where the closed one has 6000, which is why closure comes before [Check mass properties](https://www.geospec.xyz/docs/guides/check-mass-properties.mdx).

**A STEP subject.** `toHaveConnectedComponents` reads the exact BRep topology and passes on the plate with `{ count: 1 }`.

**The reference.** The [mesh reference](https://www.geospec.xyz/docs/reference/mesh.mdx#tobewatertight) lists every field of the four claims.