Features
Planar and cylindrical faces, holes, hole patterns, chamfers, fillets, wall thickness and void continuity.
These assertions find manufacturing features in a STEP file's exact BRep. A mesh has no surface types, so each refuses on a mesh subject. Every feature claim refuses when the engine cannot classify every face it would need: an unclassified face could be the missing feature, so a pass or a failure would be a guess.
plate, box and gear are the site's demo models; coupling, chamferBlock, filletBlock and guide are repository fixtures: six 5 mm holes on a 45 mm bolt circle, one 1 mm planar chamfer, one 2 mm fillet, and a valve guide with a straight bore along z in its guide occurrence. Python matchers are snake_case; their keywords keep the camelCase field names (holeDiameter=). A failed or refused call throws Geo in TypeScript and raises it in Python. Refusal codes add to the common refusals.
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 box = await loadModel({ source: 'box-10x20x30.step', format: 'step' });
const gear = await loadModel({ source: 'involute-gear.glb', format: 'glb', sourceUnit: 'mm' });
const coupling = await loadModel({
source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/kernel-skill-acceptance/build123d-coupling.step',
format: 'step',
});
const chamferBlock = await loadModel({
source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/edge-treatments/01-planar-chamfer.step',
format: 'step',
});
const filletBlock = await loadModel({
source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/edge-treatments/02-cylindrical-fillet.step',
format: 'step',
});
const guide = await loadModel({
source: 'packages/geospec-engine/native/occt/rust/tests/fixtures/nominal-bore-void/guide.step',
format: 'step',
});from geospec import GeoSpecEngine, expect_geo, load_model
with GeoSpecEngine(model_root="."):
plate = load_model(file="plate-with-through-bore.step", format="step")
box = load_model(file="box-10x20x30.step", format="step")
gear = load_model(file="involute-gear.glb", format="glb", source_unit="mm")
coupling = load_model(
file="packages/geospec-engine/native/occt/rust/tests/fixtures/kernel-skill-acceptance/build123d-coupling.step",
format="step",
)
chamfer_block = load_model(
file="packages/geospec-engine/native/occt/rust/tests/fixtures/edge-treatments/01-planar-chamfer.step",
format="step",
)
fillet_block = load_model(
file="packages/geospec-engine/native/occt/rust/tests/fixtures/edge-treatments/02-cylindrical-fillet.step",
format="step",
)
guide = load_model(
file="packages/geospec-engine/native/occt/rust/tests/fixtures/nominal-bore-void/guide.step",
format="step",
)to Have Planar Face
Asserts that at least one planar face lies on the declared plane.
Signature
expectGeo(part).toHavePlanarFace(expected: GeoSpecPlanarFaceExpectation): GeoSpecAssertionexpect_geo(part).to_have_planar_face(normal=..., offset=..., area=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
normal | point | required | Plane normal |
offset | number, mm | required | Signed distance from the origin along normal |
area | numeric expectation, mm² | not checked | Face area |
tolerance | number, 0 or more | 0.02 | Error per normal component, offset and area |
The plane is the set of points p with normal · p = offset. A face whose outward normal points the other way matches when both the normal and the offset flip.
Admitted geometry
- STEP with exact BRep faces whose surface is a plane.
Refusal codes
GEOSPEC_: the subject carries no BRep.UNSUPPORTED_ EVIDENCE
A failed claim carries GEOSPEC_, listing every planar face it found.
Examples
// passed: plate, its top face is z = 20
expectGeo(plate).toHavePlanarFace({ normal: [0, 0, 1], offset: 20 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHavePlanarFace({ normal: [0, 0, 1], offset: 5 });# passed: plate, its top face is z = 20
expect_geo(plate).to_have_planar_face(normal=[0, 0, 1], offset=20)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_planar_face(normal=[0, 0, 1], offset=5)to Have Cylindrical Face
Asserts that at least one cylindrical face has the declared radius along the declared axis.
Signature
expectGeo(part).toHaveCylindricalFace(expected: GeoSpecCylindricalFaceExpectation): GeoSpecAssertionexpect_geo(part).to_have_cylindrical_face(radius=..., axis=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
radius | number, 0 or more, mm | required | Face radius |
axis | 'x', 'y' or 'z' | required | Principal axis the cylinder axis points most along |
tolerance | number, 0 or more | 0.02 | Error on radius |
Admitted geometry
- STEP with exact BRep faces whose surface is a cylinder. The face can be a boss or a bore; the claim does not tell them apart.
Refusal codes
GEOSPEC_: the subject carries no BRep.UNSUPPORTED_ EVIDENCE
A failed claim carries GEOSPEC_.
Examples
// passed: plate, the bore's face has radius 5 along z
expectGeo(plate).toHaveCylindricalFace({ radius: 5, axis: 'z' });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHaveCylindricalFace({ radius: 2, axis: 'z' });# passed: plate, the bore's face has radius 5 along z
expect_geo(plate).to_have_cylindrical_face(radius=5, axis="z")
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_cylindrical_face(radius=2, axis="z")to Have Circular Hole
Asserts that the part has a circular hole of the declared diameter.
Signature
expectGeo(part).toHaveCircularHole(expected: GeoSpecCircularHoleExpectation): GeoSpecAssertionexpect_geo(part).to_have_circular_hole(diameter=..., through=..., axis=..., center=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
diameter | number, 0 or more, mm | required | Bore diameter |
through | boolean | not checked | true when both ends open onto outside faces |
axis | 'x', 'y' or 'z' | not checked | Bore axis |
center | point | not checked | Centre of the bore's cylindrical face |
tolerance | number, 0 or more | 0.02 | Error on diameter and center |
Admitted geometry
- STEP with exact BRep faces. A hole is a cylindrical bore the engine qualifies from the solid's topology: material all round it, and each end a mouth, a floor or, opposite a mouth, a counterbore shoulder. A chamfered, countersunk or rounded rim still opens the bore, and its cone or torus counts as part of that mouth. The bore may be one seamed cylinder or a band of coaxial partial cylinders that closes the full turn, which is what a sketched circle drawn as two arcs leaves. It measures diameter, axis, centre and ends, not manufacturability.
- A cross-drilled hole may pierce a bore's wall between its rims, and the bore still counts. Where a cross hole leaves a pin or a hole leaves a slope, its rim is an oblique mouth. A curved floor or a tangent hole stays undecided. An outer chamfer is an exterior cone, and a rounded corner, slot end or flute an open arc, never part of a hole.
- One qualified matching hole passes. A failure needs every bore candidate qualified: an unqualified bore could be the hole, so the claim refuses.
Refusal codes
GEOSPEC_: the subject carries no BRep, or no qualified hole matches and at least one bore candidate is unqualified.UNSUPPORTED_ EVIDENCE
A failed claim carries GEOSPEC_: the plate fails diameter: 12.
Examples
// passed: plate, a 10 mm through bore along z
expectGeo(plate).toHaveCircularHole({ diameter: 10, through: true, axis: 'z' });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHaveCircularHole({ diameter: 4 });# passed: plate, a 10 mm through bore along z
expect_geo(plate).to_have_circular_hole(diameter=10, through=True, axis="z")
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_circular_hole(diameter=4)to Have Circular Hole Pattern
Asserts that the part has a circular pattern of equal holes.
Signature
expectGeo(part).toHaveCircularHolePattern(expected: GeoSpecCircularHolePatternExpectation): GeoSpecAssertionexpect_geo(part).to_have_circular_hole_pattern(count=..., holeDiameter=..., boltCircleDiameter=..., axis=..., center=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
count | integer, 0 or more | required | Holes in the pattern, matched exactly |
holeDiameter | number, 0 or more, mm | required | Diameter of every hole |
boltCircleDiameter | number, 0 or more, mm | not checked | Bolt circle diameter |
axis | 'x', 'y' or 'z' | not checked | Common axis |
center | point | not checked | Bolt circle centre |
tolerance | number, 0 or more | 0.02 | Error on the diameters and the centre |
A pattern is two or more qualified holes with the same axis, diameter and ends. Holes more than 20 mm apart along the axis start a new pattern.
Admitted geometry
- STEP with exact BRep faces, as for
toHaveCircularHole. - A pattern needs the complete hole inventory, so any unqualified bore candidate refuses the claim.
Refusal codes
GEOSPEC_: the subject carries no BRep, or at least one bore candidate is unqualified.UNSUPPORTED_ EVIDENCE
A failed claim carries GEOSPEC_.
Examples
// passed: coupling, six 5 mm holes on a 45 mm bolt circle along z
expectGeo(coupling).toHaveCircularHolePattern({ count: 6, holeDiameter: 5, boltCircleDiameter: 45, axis: 'z' });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHaveCircularHolePattern({ count: 6, holeDiameter: 5 });# passed: coupling, six 5 mm holes on a 45 mm bolt circle along z
expect_geo(coupling).to_have_circular_hole_pattern(count=6, holeDiameter=5, boltCircleDiameter=45, axis="z")
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_circular_hole_pattern(count=6, holeDiameter=5)to Have Chamfer Feature
Asserts that the part has a chamfer of the declared distance.
Signature
expectGeo(part).toHaveChamferFeature(expected: GeoSpecChamferFeatureExpectation): GeoSpecAssertionexpect_geo(part).to_have_chamfer_feature(distance=..., selection=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
distance | number, 0 or more, mm | required | Chamfer setback |
selection | string | any chamfer | The unique label the STEP source gives the chamfer |
tolerance | number, 0 or more | 0.02 | Error on distance |
Admitted geometry
- STEP with exact BRep faces. A chamfer is a planar or conical face the engine qualifies between two adjacent faces, with a measured setback.
Refusal codes
GEOSPEC_: the subject carries no BRep; no qualified chamfer matches and a face's membership is unknown; or the matching chamfer has no unique label forUNSUPPORTED_ EVIDENCE selection.
A failed claim carries GEOSPEC_. Its details.mismatchKind separates a wrong label (selectorMismatch) from a wrong distance (geometricMismatch).
Examples
// passed: chamferBlock, one 1 mm planar chamfer
expectGeo(chamferBlock).toHaveChamferFeature({ distance: 1 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHaveChamferFeature({ distance: 1 });# passed: chamfer_block, one 1 mm planar chamfer
expect_geo(chamfer_block).to_have_chamfer_feature(distance=1)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_chamfer_feature(distance=1)to Have Fillet Feature
Asserts that the part has a fillet of the declared radius.
Signature
expectGeo(part).toHaveFilletFeature(expected: GeoSpecFilletFeatureExpectation): GeoSpecAssertionexpect_geo(part).to_have_fillet_feature(radius=..., selection=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
radius | number, 0 or more, mm | required | Fillet radius |
selection | string | any fillet | The unique label the STEP source gives the fillet |
tolerance | number, 0 or more | 0.02 | Error on radius |
Admitted geometry
- STEP with exact BRep faces. A fillet is a cylindrical or toroidal face the engine qualifies as tangent to its two neighbours.
Refusal codes
GEOSPEC_: as forUNSUPPORTED_ EVIDENCE toHaveChamferFeature.
A failed claim carries GEOSPEC_.
Examples
// passed: filletBlock, one 2 mm fillet
expectGeo(filletBlock).toHaveFilletFeature({ radius: 2 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expectGeo(gear).toHaveFilletFeature({ radius: 2 });# passed: fillet_block, one 2 mm fillet
expect_geo(fillet_block).to_have_fillet_feature(radius=2)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): gear, a GLB mesh
expect_geo(gear).to_have_fillet_feature(radius=2)to Have Minimum Wall Thickness
Asserts the thinnest wall of the part.
Signature
expectGeo(part).toHaveMinimumWallThickness(expected: GeoSpecMinimumWallThicknessExpectation): GeoSpecAssertionexpect_geo(part).to_have_minimum_wall_thickness(value=..., tolerance=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
value | numeric expectation, mm | required | Thinnest wall; { greaterThanOrEqual: 2 } states a lower limit |
tolerance | number, 0 or more | 0.02 | Error on value |
Admitted geometry
- A STEP part whose whole shape is a complete axis-aligned box or a right circular cylinder. The thickness is the exact minimum over the whole part, not a sample.
- Any other shape refuses, the bored plate included. The engine does not estimate wall thickness from samples or a mesh.
Refusal codes
GEOSPEC_: the subject carries no BRep, or its shape is outside the qualified box and cylinder domains.UNSUPPORTED_ EVIDENCE
A failed claim carries GEOSPEC_.
Examples
// passed: box, 10 × 20 × 30 mm, thinnest across x
expectGeo(box).toHaveMinimumWallThickness({ value: 10 });
// refused (GEOSPEC_UNSUPPORTED_EVIDENCE): plate, a bored plate is outside the box and cylinder domains
expectGeo(plate).toHaveMinimumWallThickness({ value: { greaterThanOrEqual: 2 } });# passed: box, 10 × 20 × 30 mm, thinnest across x
expect_geo(box).to_have_minimum_wall_thickness(value=10)
# refused (GEOSPEC_UNSUPPORTED_EVIDENCE): plate, a bored plate is outside the box and cylinder domains
expect_geo(plate).to_have_minimum_wall_thickness(value={"greaterThanOrEqual": 2})to Have Void Continuity
Asserts that an open channel runs through the material along a declared path, such as a bore, duct or passage.
Signature
expectGeo(part).toHaveVoidContinuity(expected: GeoSpecVoidContinuityExpectation): GeoSpecAssertionexpect_geo(part).to_have_void_continuity(path=..., material=..., bounds=..., minCrossSection=..., isolatedFrom=...) -> GeoSpecAssertionReportFields
| Field | Type | Default | Meaning |
|---|---|---|---|
path | array of points or { occurrence } | required | Ordered waypoints in the void; { occurrence } means the centre of its bounds |
material | array of occurrence names | required unless bounds | Occurrences whose solids bound the void |
bounds | { min, max } | required unless material | Region decided in; without it, the material bounds padded by 2 mm |
minCrossSection | number, 0 or more, mm² | not checked | Smallest cross-section along the path |
isolatedFrom | array of points | [] | Points the void must not reach |
Every point must lie inside the region. The published types declare defaults for material and bounds; the engine refuses a claim with neither. The path's shape picks the route:
- Points and
bounds: the engine follows the void through a complete box or right circular cylinder. Elsewhere, each straight segment must clear the material: grazing orisolatedFromrefuses, and a point in material fails. - Points without
bounds: the path must run along one straight bore in exactly onematerialoccurrence, with 2 to 16 points and noisolatedFrom. This route passes or refuses; it never fails. - Any
{ occurrence }waypoint: the engine decides the path on an exact solid model of the material.minCrossSectionrefuses here, because the sections are sampled.
Admitted geometry
- STEP with exact BRep faces and the named material occurrences.
- A path the engine cannot decide refuses. An unresolved section does not show a blocked channel.
Refusal codes
GEOSPEC_: neitherVOID_ CONTINUITY_ UNSUPPORTED materialnorbounds, an absent occurrence, a point outside the region, or a path outside the route's limits.GEOSPEC_:UNSUPPORTED_ EVIDENCE minCrossSectionon the occurrence route, the subject carries no BRep, a material is not a complete solid, or the occurrence route has no exact solid model.GEOSPEC_: the arrangement needs more than a capacity the proof profile fixes, such as 16 distinct materials, or a cell, record or exact-arithmetic capacity.ANALYSIS_ LIMIT detailscarrylimitandrequired, and no host limit raises them.
A failed claim carries GEOSPEC_.
Examples
// passed: guide, a straight bore along z through the 'guide' occurrence
expectGeo(guide).toHaveVoidContinuity({
material: ['guide'],
path: [
[0, 0, 3],
[0, 0, 42],
],
});
// refused (GEOSPEC_VOID_CONTINUITY_UNSUPPORTED): gear, a GLB mesh has no 'guide' occurrence
expectGeo(gear).toHaveVoidContinuity({
material: ['guide'],
path: [
[0, 0, 3],
[0, 0, 42],
],
});# passed: guide, a straight bore along z through the 'guide' occurrence
expect_geo(guide).to_have_void_continuity(material=["guide"], path=[[0, 0, 3], [0, 0, 42]])
# refused (GEOSPEC_VOID_CONTINUITY_UNSUPPORTED): gear, a GLB mesh has no 'guide' occurrence
expect_geo(gear).to_have_void_continuity(material=["guide"], path=[[0, 0, 3], [0, 0, 42]])