# Soundness

Connected components, watertightness, mesh integrity and subject diagnostics.

Canonical page: https://www.geospec.xyz/docs/reference/mesh

These assertions check whether the geometry is sound enough for other measurements: how many pieces it has, whether it is closed, whether its mesh is fit to measure, and what its loader reported.

`gear` and `box` are the site's demo models; `tessellated` and `gearbox` are repository fixtures: a STEP box whose faces carry no surface, and a planetary gearbox exported as a GLB with four non-manifold edges. Python adds `y_up_plate`, the demo plate admitted in a y-up frame through `engine.ingest_subject`; no loader in `geospec@beta` admits one. Python matchers are snake\_case; their keywords keep the camelCase field names (`toleranceMm=`). A failed or refused call throws `GeoSpecAssertionError` in TypeScript and raises it in Python. Refusal codes add to the [common refusals](https://www.geospec.xyz/docs/reference/overview.mdx#common-refusals).

**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 gear = await loadModel({ source: 'involute-gear.glb', format: 'glb', sourceUnit: 'mm' });
const box = await loadModel({ source: 'box-10x20x30.step', format: 'step' });
const tessellated = await loadModel({
  source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/read-profile/box-tessellated-only.step',
  format: 'step',
});
const gearbox = await loadModel({
  source: 'packages/geospec-engine/bench/fixtures/performance-lab/generated/planetary-gearbox-cad.glb',
  format: 'glb',
  sourceUnit: 'mm',
});
```

**Python**

```python title="check.py"
from pathlib import Path

from geospec import GeoSpecEngine, expect_geo, load_model

with GeoSpecEngine(model_root=".") as engine:
    gear = load_model(file="involute-gear.glb", format="glb", source_unit="mm")
    box = load_model(file="box-10x20x30.step", format="step")
    tessellated = load_model(
        file="packages/geospec-engine/native/occt/rust/tests/fixtures/read-profile/box-tessellated-only.step",
        format="step",
    )
    gearbox = load_model(
        file="packages/geospec-engine/bench/fixtures/performance-lab/generated/planetary-gearbox-cad.glb",
        format="glb",
        source_unit="mm",
    )
    y_up_plate = engine.ingest_subject(
        Path("plate-with-through-bore.step").read_bytes(),
        format="step", coordinate_system="y-up", source_unit="auto", output_unit="mm",
    )
```

## toHaveConnectedComponents [#tohaveconnectedcomponents]

Asserts how many separate pieces of material the subject has.

**Signature**

**TypeScript**

```ts
expectGeo(part).toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion
```

**Python**

```python
expect_geo(part).to_have_connected_components(count=..., toleranceMm=..., tolerance=...) -> GeoSpecAssertionReport
```

**Fields**

| Field         | Type               | Default     | Meaning                                                        |
| ------------- | ------------------ | ----------- | -------------------------------------------------------------- |
| `count`       | integer, 0 or more | required    | Separate pieces of material                                    |
| `toleranceMm` | number, 0 or more  | `tolerance` | Pieces closer than this count as one; wins when both are given |
| `tolerance`   | number, 0 or more  | `0.02`      | Fallback for `toleranceMm`                                     |

**Admitted geometry**

* STEP: clusters of BRep bodies.
* Mesh and glTF/GLB: closed material shells. Every edge must be shared by exactly two oppositely directed triangles; an open or inconsistently oriented mesh refuses rather than being counted.
* Touching that only looks connected in a render is not connectivity, and does not justify a larger tolerance.

**Refusal codes**

* `GEOSPEC_INVALID_EVIDENCE`: the mesh is not made of closed, consistently oriented shells.
* `GEOSPEC_ANALYSIS_LIMIT`: the exact material classification needs more scratch than `maxMeshBytes` allows. The reservation is sized from the mesh, and `details` add `reservation`, `positions` and `triangles` beside `limit` and `required`.

A failed claim carries `GEOSPEC_CONNECTED_COMPONENTS_MISMATCH`.

**Examples**

**TypeScript**

```ts
// passed: gear, one shell of 624 triangles
expectGeo(gear).toHaveConnectedComponents({ count: 1 });
// refused (GEOSPEC_INVALID_EVIDENCE): gearbox, four non-manifold edges
expectGeo(gearbox).toHaveConnectedComponents({ count: 1 });
```

**Python**

```python
# passed: gear, one shell of 624 triangles
expect_geo(gear).to_have_connected_components(count=1)
# refused (GEOSPEC_INVALID_EVIDENCE): gearbox, four non-manifold edges
expect_geo(gearbox).to_have_connected_components(count=1)
```

## toBeWatertight [#tobewatertight]

Asserts that the subject's surface is closed: every edge borders exactly two faces.

**Signature**

**TypeScript**

```ts
expectGeo(part).toBeWatertight(): GeoSpecAssertion
```

**Python**

```python
expect_geo(part).to_be_watertight() -> GeoSpecAssertionReport
```

**Fields**

None.

**Admitted geometry**

* STEP: edge incidence in the exact BRep topology. A BRep subject is never tessellated for this claim.
* Mesh and glTF/GLB: edge incidence in the triangles.
* The claim checks closure only. It does not establish manifold validity, fusion or material connectivity, and bounds establish none of these.

**Refusal codes**

* `GEOSPEC_ANALYSIS_LIMIT`: the watertight analysis needs more working memory than `maxMeshBytes` allows. A closed mesh needs about 200 bytes per triangle, so the 256 MiB entry limit decides meshes up to roughly 1.3 million triangles; raise `maxMeshBytes` for larger ones.

A failed claim carries `GEOSPEC_WATERTIGHT_MISMATCH`, with the counts of open and non-manifold edges.

**Examples**

**TypeScript**

```ts
// passed: gear, 936 edges, each on two triangles
expectGeo(gear).toBeWatertight();
// failed (GEOSPEC_WATERTIGHT_MISMATCH): gearbox, 4 non-manifold edges, 0 open
expectGeo(gearbox).toBeWatertight();
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): tessellated, its six faces have no surface
expectGeo(tessellated).toBeWatertight();
```

**Python**

```python
# passed: gear, 936 edges, each on two triangles
expect_geo(gear).to_be_watertight()
# failed (GEOSPEC_WATERTIGHT_MISMATCH): gearbox, 4 non-manifold edges, 0 open
expect_geo(gearbox).to_be_watertight()
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): tessellated, its six faces have no surface
expect_geo(tessellated).to_be_watertight()
```

## toHaveMeshIntegrity [#tohavemeshintegrity]

Asserts that the mesh evidence is sound enough for the checks that follow it.

**Signature**

**TypeScript**

```ts
expectGeo(part).toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion
```

**Python**

```python
expect_geo(part).to_have_mesh_integrity(finitePositions=..., triangleCount=...) -> GeoSpecAssertionReport
```

**Fields**

| Field                 | Type                                    | Default     | Meaning                                         |
| --------------------- | --------------------------------------- | ----------- | ----------------------------------------------- |
| `finitePositions`     | boolean                                 | not checked | Every vertex coordinate is finite               |
| `degenerateTriangles` | `{ count?, maxCount?, areaTolerance? }` | not checked | Zero-area triangles, or at most `areaTolerance` |
| `duplicateFaces`      | `{ count?, maxCount? }`                 | not checked | Triangles that repeat another's vertices        |
| `watertight`          | boolean                                 | not checked | Closed edge incidence, as `toBeWatertight`      |
| `triangleCount`       | numeric expectation                     | not checked | Triangles in the mesh                           |

**Admitted geometry**

* Mesh and glTF/GLB: every field.
* STEP: `watertight` only, answered from the exact BRep topology. The other fields would measure a tessellation GeoSpec made, not the subject, so a STEP claim that declares them refuses.

**Refusal codes**

* `GEOSPEC_UNSUPPORTED_EVIDENCE`: a STEP claim declares a field other than `watertight`.
* `GEOSPEC_ANALYSIS_LIMIT`: a declared `watertight` needs more working memory than `maxMeshBytes` allows, as for `toBeWatertight`.

A failed claim carries `GEOSPEC_MESH_INTEGRITY_MISMATCH`.

**Examples**

**TypeScript**

```ts
// passed: gear, 624 finite triangles and no duplicates
expectGeo(gear).toHaveMeshIntegrity({ finitePositions: true, duplicateFaces: { count: 0 }, triangleCount: 624 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): box, a STEP subject answers watertight only
expectGeo(box).toHaveMeshIntegrity({ finitePositions: true });
```

**Python**

```python
# passed: gear, 624 finite triangles and no duplicates
expect_geo(gear).to_have_mesh_integrity(finitePositions=True, duplicateFaces={"count": 0}, triangleCount=624)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): box, a STEP subject answers watertight only
expect_geo(box).to_have_mesh_integrity(finitePositions=True)
```

## toHaveNoDiagnostics [#tohavenodiagnostics]

Asserts that the subject was admitted without diagnostics at the rejected severities.

**Signature**

**TypeScript**

```ts
expectGeo(part).toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion
```

**Python**

```python
expect_geo(part).to_have_no_diagnostics(severities=...) -> GeoSpecAssertionReport
```

**Fields**

| Field        | Type                                      | Default                | Meaning                        |
| ------------ | ----------------------------------------- | ---------------------- | ------------------------------ |
| `severities` | array of `'error'`, `'warning'`, `'info'` | `['error', 'warning']` | Severities that fail the claim |

**Admitted geometry**

* Any admitted subject in a z-up frame. The claim reads the diagnostics the host attached when it ingested the subject; it measures no geometry, so a STEP box whose faces carry no surface still answers.

**Refusal codes**

The common refusals only. A failed claim carries `GEOSPEC_DIAGNOSTICS_PRESENT`, listing each rejected diagnostic.

**Examples**

**TypeScript**

```ts
// passed: gear, admitted without diagnostics
expectGeo(gear).toHaveNoDiagnostics();
```

The refused case is Python only: no loader in geospec\@beta admits `y_up_plate`.

**Python**

```python
# passed: gear, admitted without diagnostics
expect_geo(gear).to_have_no_diagnostics()
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): y_up_plate, a y-up subject answers minimumDistance only
expect_geo(y_up_plate).to_have_no_diagnostics()
```