GeoSpec
engine 0.4.0

Quick start

Install geospec, claim the size of a STEP part and read passed, failed or refused in TypeScript or Python.

Open Markdown

Install, claim the size of one STEP part and read the verdict: in the page, then as a script.

The API is the geospec package, published on the beta tag, over the geospec-engine package. Node 24 or later.

npm install geospec@beta geospec-engine

One wheel installs the geospec package and the engine. CPython 3.13 or 3.14.

pip install geospec-engine

Install covers what each package delivers, browsers and bundlers.

Claim the size of a plate

The plate is a 40 × 40 × 20 mm STEP part with a 10 mm bore. toHaveBoundingBox compares the measured bounds with the size you state, at a tolerance of 0.02 mm. Change z: 20 to z: 25 and the claim fails with the measured size in its diagnostic.

Python spells each matcher in snake case: to_have_bounding_box.

const part = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });

Tab indents. Press Escape, then Tab, to leave the editor.

The spec as a file, with the model loaded over the native addon
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 part = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });expectGeo(part)  .toHaveBoundingBox({ size: { x: 40, y: 40, z: 20 } });await loadModel.dispose();engine.close();

The engine loads in this tab when you run the spec.

The model’s bounds (solid); this claim pins no axis the view can draw.

plate-with-through-bore.step · 19 KB · copied from an engine test fixture

Run the spec to see what the engine answers for each claim. Then change an expected value and run it again.

 

Run it as a script

The same claim over the WASM build, which runs on every Node host. Save the script next to the plate and run node check.ts.

check.ts
import { readFile } from 'node:fs/promises';
import { expectGeo } from 'geospec';
import { createModelLoader } from 'geospec/model';
import { Engine, initialize } from 'geospec-engine';

await initialize();
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 size = expectGeo(plate).toHaveBoundingBox({ size: { x: 40, y: 40, z: 20 } });
console.log(size.report?.status);

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

Save the script next to the plate and run python check.py.

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")
    size = expect_geo(plate).to_have_bounding_box(size={"x": 40, "y": 40, "z": 20})
    print(size.result["status"])

Expected output

passed

A matcher returns an assertion with passed, report.status and report.result.evidence.measured, here size: [40, 40, 20]. A failed or refused claim throws GeoSpecAssertionError from geospec/assertion-client: its message is the diagnostic and diagnostics[0].code the code.

expect_geo returns a report with passed, result["status"] and result["evidence"]["measured"], here size: [40, 40, 20]. A failed or refused claim raises GeoSpecAssertionError: its message is the diagnostic and .code the code.

A missed size reads GEOSPEC_BOUNDING_BOX_MISMATCH; a refusal names the evidence the subject lacks. Read a refusal tells the two apart.

What it does not do

  • Meshes carry no feature evidence. A GLB or glTF subject refuses hole, face, chamfer and fillet claims with GEOSPEC_UNSUPPORTED_EVIDENCE; load a STEP export, as Find holes and features does.
  • No unit or frame inference. A STEP file is read in its own unit. A GLB is metres unless the load says millimetres (sourceUnit: 'mm', in Python source_unit="mm"). Measurements are millimetres. How it works has what each format carries.
  • No rendering or repair. The engine measures the geometry it is given and changes nothing.

On this page