GeoSpec
engine 0.4.0
Matcher reference

Soundness

Connected components, watertightness, mesh integrity and subject diagnostics.

Open Markdown

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.

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',
});
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

Asserts how many separate pieces of material the subject has.

Signature

expectGeo(part).toHaveConnectedComponents(expected: GeoSpecConnectedComponentsExpectation): GeoSpecAssertion
expect_geo(part).to_have_connected_components(count=..., toleranceMm=..., tolerance=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
countinteger, 0 or morerequiredSeparate pieces of material
toleranceMmnumber, 0 or moretolerancePieces closer than this count as one; wins when both are given
tolerancenumber, 0 or more0.02Fallback 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

// 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 });
# 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

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

Signature

expectGeo(part).toBeWatertight(): GeoSpecAssertion
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

// 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();
# 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

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

Signature

expectGeo(part).toHaveMeshIntegrity(expected: GeoSpecMeshIntegrityExpectation): GeoSpecAssertion
expect_geo(part).to_have_mesh_integrity(finitePositions=..., triangleCount=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
finitePositionsbooleannot checkedEvery vertex coordinate is finite
degenerateTriangles{ count?, maxCount?, areaTolerance? }not checkedZero-area triangles, or at most areaTolerance
duplicateFaces{ count?, maxCount? }not checkedTriangles that repeat another's vertices
watertightbooleannot checkedClosed edge incidence, as toBeWatertight
triangleCountnumeric expectationnot checkedTriangles 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

// 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 });
# 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

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

Signature

expectGeo(part).toHaveNoDiagnostics(expected?: GeoSpecNoDiagnosticsExpectation): GeoSpecAssertion
expect_geo(part).to_have_no_diagnostics(severities=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
severitiesarray 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

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

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

# 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()

On this page