GeoSpec
engine 0.4.0
Matcher reference

Relationships and interference

Component interference and spatial relationships between selected parts, faces and bodies.

Open Markdown

Each route here is narrow: a pair the engine has no exact method for refuses, and never falls back to an estimate.

assembly and gapBoxes are repository fixtures: two 10 mm cubes as GLB nodes cubeA and cubeB, 20 mm apart, and two 10 mm STEP boxes named subject and target, 2 mm apart along x. Python matchers are snake_case; their keywords keep the camelCase field names (maxVolume=). 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 assembly = await loadModel({
  source: 'packages/geospec-engine/bench/fixtures/performance-lab/generated/assembly.glb',
  format: 'glb',
  sourceUnit: 'mm',
});
const gapBoxes = await loadModel({
  source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/box-clearance/two-mm.step',
  format: 'step',
});
check.py
from geospec import GeoSpecEngine, expect_geo, load_model

with GeoSpecEngine(model_root="."):
    assembly = load_model(
        file="packages/geospec-engine/bench/fixtures/performance-lab/generated/assembly.glb",
        format="glb",
        source_unit="mm",
    )
    gap_boxes = load_model(
        file="packages/geospec-engine/native/occt/rust/tests/fixtures/box-clearance/two-mm.step",
        format="step",
    )

toHaveNoComponentInterference

Asserts that no two components of an assembly share material beyond a declared allowance.

Signature

expectGeo(part).toHaveNoComponentInterference(expected?: GeoSpecComponentInterferenceExpectation): GeoSpecAssertion
expect_geo(part).to_have_no_component_interference(tolerance=..., pairs=..., allowances=...) -> GeoSpecAssertionReport

Fields

FieldTypeDefaultMeaning
tolerancenumber, 0 or more0.001Recorded in the evidence; the volume comparison is exact
pairsarray of { left, right }every pairPairs to check
allowancesarray of { left, right, reason, maxVolume?, kind? }[]Pairs allowed up to maxVolume mm³; without maxVolume the pair is waived and not measured

A component selector is an exact label or a RegExp (GeoSpecRegex in Python); kind is 'intentionalInterference'.

Admitted geometry

  • Two or more components. The components are the leaf occurrences with faces in a STEP file, or the named nodes of a glTF or GLB file, labelled node#primitive (cubeA#0). A mesh-buffer-v1 mesh has one primitive and no components.
  • glTF/GLB: each pair's shared volume is computed exactly over closed, consistently oriented shells and compared with maxVolume, or with zero.
  • STEP: a pair of solids whose faces stay more than twice the larger shape tolerance apart, neither inside the other, shares no material. A touching or overlapping pair needs an allowance; a maxVolume allowance is decided only for a bore-and-shaft pair (a cylindrical bore through a slab, and a finite cylinder) by bounding the shared volume from above. A STEP claim passes or refuses, never fails.

Refusal codes

  • GEOSPEC_COMPONENT_PARTITION_INCONCLUSIVE: fewer than two components.
  • GEOSPEC_SELECTOR_UNMATCHED: a pairs entry matches no pair of distinct components.
  • GEOSPEC_UNSUPPORTED_EVIDENCE: a STEP pair is not shown apart and has no allowance, or its maxVolume allowance is not decided.
  • GEOSPEC_INVALID_EVIDENCE: a mesh component is not a closed, consistently oriented shell.
  • GEOSPEC_UNSUPPORTED_SELECTOR: a RegExp with the v flag, outside the engine's regex profile.
  • GEOSPEC_ANALYSIS_LIMIT: a bore-and-shaft bound needs more refinement or exact arithmetic than its proof profile fixes, which no host limit raises, or a mesh pair's exact intersection needs more scratch than maxMeshBytes allows.

A failed claim carries GEOSPEC_COMPONENT_INTERFERENCE.

Examples

// passed: assembly, cubeA#0 and cubeB#0 share no material
expectGeo(assembly).toHaveNoComponentInterference();
// passed: gapBoxes, the STEP boxes sit 2 mm apart
expectGeo(gapBoxes).toHaveNoComponentInterference();
// refused (GEOSPEC_SELECTOR_UNMATCHED): assembly has no component cubeC#0
expectGeo(assembly).toHaveNoComponentInterference({ pairs: [{ left: 'cubeA#0', right: 'cubeC#0' }] });
# passed: assembly, cubeA#0 and cubeB#0 share no material
expect_geo(assembly).to_have_no_component_interference()
# passed: gap_boxes, the STEP boxes sit 2 mm apart
expect_geo(gap_boxes).to_have_no_component_interference()
# refused (GEOSPEC_SELECTOR_UNMATCHED): assembly has no component cubeC#0
expect_geo(assembly).to_have_no_component_interference(pairs=[{"left": "cubeA#0", "right": "cubeC#0"}])

toHaveSpatialRelationships

Asserts declared relationships between selected parts, faces or bodies.

Signature

expectGeo(part).toHaveSpatialRelationships(expected: GeoSpecSpatialRelationshipsExpectation): GeoSpecAssertion
expect_geo(part).to_have_spatial_relationships(relationships=[...]) -> GeoSpecAssertionReport

Fields

Each relationships entry:

FieldTypeDefaultMeaning
kindrelationship kindrequiredcontact, clearance, coaxial, concentric, coplanar, parallel, perpendicular, angle, containment, insertion or interference
subject, targetselectorrequiredThe two endpoints
tolerancenumber, 0 or more, mm0.02; 0 for clearanceAllowed distance error
angularToleranceDegreesnumber, 0 or more0.5Allowed direction error
angleDegreesnumber0; 90 for perpendicularExpected angle for angle, parallel and perpendicular
axis[x, y, z]noneInsertion axis
min, maxnumber, mmmin 0 for insertionBand of clearance or insertion depth
minVolume, maxVolumenumber, mm³0Band of shared volume for interference
id, reasonstringnoneLabels carried into the evidence

A selector is an occurrence path string, a RegExp (GeoSpecRegex in Python) over occurrence names, or a catalog object with kind (occurrence, body, face, axis, plane, datum, interface or group), of, query and expect. The engine runs the catalog forms: { kind: 'axis', query: { axis: { direction: [0, 0, 1] } } } on both sides passes coaxial on the demo plate. The published types give axis and plane explicit axis, center, direction, radius, normal and offset members instead, which the engine refuses as unknown fields (GEOSPEC_INVALID_EXPECTATION); tsc reports an excess property on the forms that run, and as GeoSpecGeometrySelector compiles them. Strings, RegExp, body and face selectors compile and run as written.

Admitted geometry

  • STEP, by kind: clearance, contact, insertion and interference take two complete box-shaped occurrences, and insertion a Cartesian axis; coaxial, concentric, coplanar, parallel, perpendicular and angle take one analytic face, axis or plane per endpoint; containment takes closed solids. Clearance must lie in [min - tolerance, max + tolerance], shared volume in [minVolume, maxVolume].
  • glTF/GLB bodies ({ kind: 'body', of: 'cubeA#0' }) admit contact, clearance, containment and interference over closed shells; the other kinds refuse on a mesh.

Refusal codes

  • GEOSPEC_SELECTOR_UNMATCHED, GEOSPEC_SELECTOR_AMBIGUOUS: a catalog selector or RegExp resolves to no entity, or to more than its cardinality allows.
  • GEOSPEC_SELECTOR_UNSUPPORTED_EVIDENCE: an endpoint outside the kind's shapes (a whole occurrence for parallel), a mesh endpoint that is not a body, or a bare string naming no occurrence, which the engine reads as an interface name the file does not carry.
  • GEOSPEC_UNSUPPORTED_EVIDENCE: an insertion outside the route above, such as boxes without axis, or geometry outside a proof's domain, named in details.applicability.condition.
  • GEOSPEC_INVALID_EVIDENCE: a mesh body that is not a closed, consistently oriented shell.
  • GEOSPEC_UNSUPPORTED_SELECTOR: a RegExp with the v flag.
  • GEOSPEC_ANALYSIS_LIMIT: an insertion, clearance, cylindrical band or finite contact proof needs more than a capacity its profile fixes, such as 64 faces on a contact target. details carry limit and required, and no host limit raises them.

A failed claim carries GEOSPEC_SPATIAL_RELATIONSHIP_MISMATCH.

Examples

// passed: gapBoxes, the boxes sit 2 mm apart in x
expectGeo(gapBoxes).toHaveSpatialRelationships({
  relationships: [{ kind: 'clearance', subject: 'subject', target: 'target', min: 2 }],
});
// refused (GEOSPEC_SELECTOR_UNSUPPORTED_EVIDENCE): gapBoxes, parallel needs a face, axis or plane per side
expectGeo(gapBoxes).toHaveSpatialRelationships({
  relationships: [{ kind: 'parallel', subject: 'subject', target: 'target' }],
});
# passed: gap_boxes, the boxes sit 2 mm apart in x
expect_geo(gap_boxes).to_have_spatial_relationships(
    relationships=[dict(kind="clearance", subject="subject", target="target", min=2)],
)
# refused (GEOSPEC_SELECTOR_UNSUPPORTED_EVIDENCE): gap_boxes, parallel needs a face, axis or plane per side
expect_geo(gap_boxes).to_have_spatial_relationships(
    relationships=[dict(kind="parallel", subject="subject", target="target")],
)

On this page