GeoSpec
engine 0.4.0
Matcher reference

BRep and STEP structure

Shape validity, topology counts, STEP units, product structure and assembly occurrences.

Open Markdown

These assertions read a STEP file's exact BRep and assembly structure; the occurrence assertions also read glTF nodes. A mesh has no BRep, so BRep assertions refuse on it with GEOSPEC_UNSUPPORTED_EVIDENCE.

plate and gear are the site's demo models; inchCube, cubes and deepNodes are repository fixtures: a STEP cube authored in inches, two 10 mm cubes, cubeA at the origin and cubeB centred at x = 30, and a GLB 66 nodes deep. Python matchers are snake_case; their keywords keep the camelCase field names (toleranceMm=). A failed or refused call raises GeoSpecAssertionError. 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 plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });
const gear = await loadModel({ source: 'involute-gear.glb', format: 'glb', sourceUnit: 'mm' });
const inchCube = await loadModel({
  source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/inch-cube.step',
  format: 'step',
});
const cubes = await loadModel({
  source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/two-cube-assembly.step',
  format: 'step',
});
const deepNodes = await loadModel({
  source: 'packages/geospec-engine/rust/tests/fixtures/gltf-occurrences/deep-nodes.glb',
  format: 'glb',
  sourceUnit: 'mm',
});
check.py
from geospec import GeoSpecEngine, expect_geo, load_model

with GeoSpecEngine(model_root="."):
    plate = load_model(file="plate-with-through-bore.step", format="step")
    gear = load_model(file="involute-gear.glb", format="glb", source_unit="mm")
    inch_cube = load_model(file="packages/geospec-engine/native/occt/rust/tests/fixtures/inch-cube.step", format="step")
    cubes = load_model(file="packages/geospec-engine/native/occt/rust/tests/fixtures/two-cube-assembly.step", format="step")
    deep_nodes = load_model(file="packages/geospec-engine/rust/tests/fixtures/gltf-occurrences/deep-nodes.glb", format="glb", source_unit="mm")

toBeValidBrep

Asserts that the geometry kernel reports the BRep shape valid, with optional limits on its tolerances and closure.

Signature

expectGeo(part).toBeValidBrep(expected?: GeoSpecValidBrepExpectation): GeoSpecAssertion
expect_geo(part).to_be_valid_brep(closedShells=..., maxTolerance=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
maxTolerancenumber, 0 or morenot checkedShape tolerance, at most
freeBounds{ count?: numeric expectation }not checkedFree boundary edges
minEdgeLengthnumber, 0 or morenot checkedEdge length, at least
sameParameterbooleannot checkedEdges agree with their surface parameters
closedShells, closedWiresbooleannot checkedEvery shell, or every wire, is closed

Admitted geometry

  • STEP with exact BRep faces.
  • A declared limit the kernel did not measure refuses rather than passing.

Refusal codes

  • GEOSPEC_UNSUPPORTED_EVIDENCE: the subject carries no BRep, or the kernel did not measure a declared limit.

A failed claim carries GEOSPEC_FEATURE_MISMATCH, naming each limit that failed.

Examples

// passed: plate, closed shells, shape tolerance 1e-7 mm
expectGeo(plate).toBeValidBrep({ closedShells: true, maxTolerance: 0.01 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh carries no BRep
expectGeo(gear).toBeValidBrep();
# passed: plate, closed shells, shape tolerance 1e-7 mm
expect_geo(plate).to_be_valid_brep(closedShells=True, maxTolerance=0.01)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh carries no BRep
expect_geo(gear).to_be_valid_brep()

toHaveTopologyCounts

Asserts how many vertices, edges, wires, faces, shells, solids and compounds the BRep has.

Signature

expectGeo(part).toHaveTopologyCounts(expected: GeoSpecTopologyCountsExpectation): GeoSpecAssertion
expect_geo(part).to_have_topology_counts(faces=..., solids=..., tolerance=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
vertices, edges, wires, faces, shells, solids, compoundsnumeric expectationnot checkedThe kernel's counts
tolerancenumber, 0 or more0Allowed difference per count

Admitted geometry

  • STEP with exact BRep faces.
  • The counts are the kernel's own, by occurrence: a vertex or edge shared by several faces counts once per use, so the plate reports 60 vertices and 30 edges for its 7 faces, six sides and the bore. Assert faces and solids when you mean the part's shape.

Refusal codes

  • GEOSPEC_UNSUPPORTED_EVIDENCE: the subject carries no BRep.

A failed claim carries GEOSPEC_FEATURE_MISMATCH.

Examples

// passed: plate, six sides and the bore in one solid
expectGeo(plate).toHaveTopologyCounts({ faces: 7, solids: 1 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh has no BRep topology
expectGeo(gear).toHaveTopologyCounts({ faces: 6 });
# passed: plate, six sides and the bore in one solid
expect_geo(plate).to_have_topology_counts(faces=7, solids=1)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh has no BRep topology
expect_geo(gear).to_have_topology_counts(faces=6)

toHaveStepUnits

Asserts the length unit the STEP file declares.

Signature

expectGeo(part).toHaveStepUnits(expected: GeoSpecStepUnitsExpectation): GeoSpecAssertion
expect_geo(part).to_have_step_units(unit=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
unit'mm', 'cm', 'm', 'in' or 'ft'requiredCompared as a literal string; TypeScript types it as string

Admitted geometry

  • Any STEP subject, including one whose faces carry no surface: the claim reads the declared unit, not faces. The engine still measures in millimetres.

Refusal codes

  • GEOSPEC_UNSUPPORTED_EVIDENCE: the subject is not a STEP file.

A failed claim carries GEOSPEC_FEATURE_MISMATCH.

Examples

// passed: inchCube, declared in inches
expectGeo(inchCube).toHaveStepUnits({ unit: 'in' });
// failed (GEOSPEC_FEATURE_MISMATCH): inchCube, the file declares 'in', not 'inch'
expectGeo(inchCube).toHaveStepUnits({ unit: 'inch' });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh declares no unit
expectGeo(gear).toHaveStepUnits({ unit: 'mm' });
# passed: inch_cube, declared in inches
expect_geo(inch_cube).to_have_step_units(unit="in")
# failed (GEOSPEC_FEATURE_MISMATCH): inch_cube, the file declares 'in', not 'inch'
expect_geo(inch_cube).to_have_step_units(unit="inch")
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a mesh declares no unit
expect_geo(gear).to_have_step_units(unit="mm")

toHaveProductStructure

Asserts the occurrence paths of a STEP assembly or glTF node tree, and their count.

Signature

expectGeo(part).toHaveProductStructure(expected: GeoSpecProductStructureExpectation): GeoSpecAssertion
expect_geo(part).to_have_product_structure(names=..., count=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
namesarray of strings[]Occurrence paths that must exist
countnumeric expectationnot checkedOccurrences in the file; the single-part plate passes count: 0

Admitted geometry

  • Any STEP subject; the claim reads the assembly structure, not faces.
  • glTF/GLB: the node occurrences of toHaveAssemblyOccurrences.

Refusal codes

  • GEOSPEC_UNSUPPORTED_EVIDENCE and GEOSPEC_RESOURCE_LIMIT: the glTF refusals of toHaveAssemblyOccurrences.

A failed claim carries GEOSPEC_FEATURE_MISMATCH.

Examples

// passed: cubes, occurrences cubeA and cubeB
expectGeo(cubes).toHaveProductStructure({ names: ['cubeA', 'cubeB'], count: 2 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): deepNodes, 66 levels of nodes
expectGeo(deepNodes).toHaveProductStructure({ count: 1 });
# passed: cubes, occurrences cubeA and cubeB
expect_geo(cubes).to_have_product_structure(names=["cubeA", "cubeB"], count=2)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): deep_nodes, 66 levels of nodes
expect_geo(deep_nodes).to_have_product_structure(count=1)

toHaveAssemblyOccurrences

Asserts that named occurrences exist in a STEP assembly or glTF node tree, how many match each name, and where each match sits.

Signature

expectGeo(part).toHaveAssemblyOccurrences(expected: GeoSpecAssemblyOccurrencesExpectation): GeoSpecAssertion
expect_geo(part).to_have_assembly_occurrences(occurrences=..., uniqueNames=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
occurrences[].namestring or regular expressionrequiredPath to match; GeoSpecRegex in Python
occurrences[].countnumeric expectationat least one matchMatches required; a rule that matches none fails
occurrences[].bounds{ min?, max?, center?, within?, tolerance? }not checkedChecked on every match; tolerance is 0.02 per axis
uniqueNamesbooleanfalseFail when an occurrence path repeats

min, max and center compare each match's box axis by axis. within names another occurrence by path or regular expression whose box, widened by tolerance on every face, must contain each match; an occurrence never contains itself.

Admitted geometry

  • A STEP file with assembly occurrences. A file with none refuses rather than reporting zero matches.
  • glTF/GLB: each node with triangles under it, its path built as STEP paths are and its box spanning those triangles.
  • A regular expression feature the engine cannot evaluate faithfully refuses the claim; a malformed one refuses with GEOSPEC_INVALID_EXPECTATION.

Refusal codes

  • GEOSPEC_UNSUPPORTED_EVIDENCE: the subject is a bare mesh, the file has no assembly occurrences, a glTF tree is over 64 levels deep or has a non-finite vertex, a bounds rule meets an occurrence without finite bounds, or a regular expression uses an unsupported feature.
  • GEOSPEC_RESOURCE_LIMIT: a glTF file exceeds the configured occurrence or byte limit.

A failed claim carries GEOSPEC_FEATURE_MISMATCH, listing each rule that failed.

Examples

// passed: cubes, one cubeA; cubeB spans x 25 to 35
expectGeo(cubes).toHaveAssemblyOccurrences({
  occurrences: [
    { name: 'cubeA', count: 1 },
    { name: 'cubeB', bounds: { min: [25, -5, -5], max: [35, 5, 5] } },
  ],
  uniqueNames: true,
});
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): plate, a single part has no occurrences
expectGeo(plate).toHaveAssemblyOccurrences({ occurrences: [{ name: 'cubeA' }] });
# passed: cubes, one cubeA; cubeB spans x 25 to 35
expect_geo(cubes).to_have_assembly_occurrences(
    occurrences=[{"name": "cubeA", "count": 1}, {"name": "cubeB", "bounds": {"min": [25, -5, -5], "max": [35, 5, 5]}}],
    uniqueNames=True,
)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): plate, a single part has no occurrences
expect_geo(plate).to_have_assembly_occurrences(occurrences=[{"name": "cubeA"}])

On this page