Link Search Menu Expand Document

Introspection & measurement

These tools read the current scene without modifying it: validating geometry, querying topology, computing physical properties, measuring distances and surface deviations, and inspecting assembly hierarchies. Reach for them after execute_script builds a body, or any time you need ground-truth numbers before making a design decision.

Tools

validate_geometry · compute_metrics · query_topology · measure_distance · measure_deviation · measure_vertex_fit · deviation_histogram · cross_section_compare · symmetric_difference_volume · recognize_features · inspect_assembly

Signed / spatially-resolved surface comparison — the certify-a-reconstruction toolset (#61–#63, #66, #70) — also renders to PNG via signed_deviation_heatmap and overlay_render.


validate_geometry

Per-body topology validation against OCCTSwift’s TopologyGraph.validate().

Server: Swift + Node

Parameters

name type required description
bodyId string no Specific body to validate. If omitted, validates every BREP body in the scene.

Returns — JSON object with a per-body validity report: valid boolean and a list of any violations found (open shells, bad orientation, degenerate edges, etc.). Returns an error string if the body is not found.

Example

// tool call arguments
{ "bodyId": "housing" }
// example result
{ "bodyId": "housing", "valid": true, "issues": [] }

DrivesGraphIO + TopologyGraph.validate() in-process (no subprocess).


compute_metrics

Compute volume, surface area, center of mass, bounding box, and/or principal axes for a scene body.

Server: Swift + Node

Parameters

name type required description
bodyId string yes Body to measure.
metrics string[] no Subset to compute. Default: all except boundingBoxOptimal. Items: volume, surfaceArea, centerOfMass, boundingBox, boundingBoxOptimal, principalAxes. boundingBoxOptimal (tight AddOptimal extent) is opt-in — list it explicitly.

Returns — JSON object keyed by requested metric name. boundingBox and boundingBoxOptimal each return { min: [x,y,z], max: [x,y,z] }. centerOfMass returns [x,y,z]. principalAxes returns three orthogonal unit vectors. Returns an error string if the body is not found.

Example

// tool call arguments
{ "bodyId": "part", "metrics": ["volume", "surfaceArea", "boundingBoxOptimal"] }
// example result
{
  "volume": 5890.3,
  "surfaceArea": 2104.7,
  "boundingBoxOptimal": { "min": [0.0, 0.0, 0.0], "max": [25.0, 20.0, 15.0] }
}

NotesboundingBox uses Bnd_Box and over-reports extents for curved B-spline faces (it encloses the control-point hull, not the actual surface). Use boundingBoxOptimal (BRepBndLib::AddOptimal) when you need the tight envelope, at a small extra compute cost. boundingBoxOptimal is intentionally excluded from the default-all set.

Drives — direct OCCTSwift property calls (no occtkit subprocess).


query_topology

Find faces, edges, or vertices on a body matching optional criteria. Returns stable index-based IDs (face[N], edge[N], vertex[N]) that can be passed to selection tools.

Server: Swift + Node

Parameters

name type required description
bodyId string yes Body to query.
entity string (enum) yes Entity kind: face, edge, or vertex.
filter object no Optional filter: surfaceType, curveType, minArea, maxArea.
limit integer (≥1) no Maximum number of results to return.

Returns — Array of matching topology entries, each with its stable ID, geometric properties (type, area/length as applicable), and centroid. Edge entries (#119) also carry endpoints ([start, end], every edge kind) plus a unit direction for LINE edges, and circleCenter/radius/axis/startAngle/endAngle (radians, measured from the circle’s own xAxis) for CIRCULAR edges. Returns an empty array when no entities match the filter.

Example

// tool call arguments
{ "bodyId": "bracket", "entity": "face", "filter": { "surfaceType": "plane" }, "limit": 10 }
// example result
[
  { "id": "face[0]", "surfaceType": "plane", "area": 400.0, "centroid": [0.0, 0.0, 10.0] },
  { "id": "face[2]", "surfaceType": "plane", "area": 400.0, "centroid": [0.0, 0.0, -10.0] }
]

Notes — The returned IDs can be passed directly to select_topology to mint a selectionId for use in remap and annotation workflows.


measure_distance

Minimum distance between two scene bodies. Returns ≈0 if the bodies overlap or touch.

Server: Swift + Node

Parameters

name type required description
fromBodyId string yes First body.
toBodyId string yes Second body.
computeContacts boolean no Also return up to 32 contact pairs (closest point pairs).

Returns — JSON object with distance (minimum gap in model units). If computeContacts is true, also includes contacts: array of up to 32 pairs, each with pointOnFrom and pointOnTo coordinates.

Example

// tool call arguments
{ "fromBodyId": "shaft", "toBodyId": "bearing", "computeContacts": true }
// example result
{
  "distance": 0.05,
  "contacts": [
    { "pointOnFrom": [12.5, 0.0, 30.0], "pointOnTo": [12.55, 0.0, 30.0] }
  ]
}

Notes — This is the minimum gap metric — not surface deviation. For comparing a reconstruction against a reference mesh use measure_deviation instead. A result of ≈0 means bodies are touching or penetrating; it does not indicate the amount of overlap.


measure_deviation

Signed, spatially-resolved surface deviation between two scene bodies — the primary metric for certifying a reconstruction against its source mesh. As of #62 the report is a full vector (not just a min gap), with an optional per-station sweep along an axis.

Server: Swift + Node

Parameters

name type required description
fromBodyId string yes Source body (e.g. the reconstruction).
toBodyId string yes Reference body (e.g. the input mesh).
deflection number no Mesh linear deflection in model units. Smaller = finer tessellation = tighter bound. Default: 0.5% of the from-body bbox diagonal.
maxSamples integer no Max source surface samples per direction (stride-subsampled). Default 20000.
sectionAxis number[3] no [x,y,z] axis to bin the forward samples along. With sections, adds a per-station signedMean sweep.
sections integer no Number of along-axis bins for the per-section sweep (≥2). Requires sectionAxis.
signMode string no robust (default) or nearest — see Which way is out?.

Returns — JSON object:

  • fromToTo / toToFrom — directed deviation each way: { max, rms, mean, p95, signedMean, signedMin, signedMax, worstPoint, samples, signedSamples, ambiguousSamples, ambiguousFraction }. signedMean ≠ 0 reveals a systematic proud(+) / shy(−) bias a Hausdorff hides; fromToTo catches over-extension, toToFrom under-coverage. The signed trio is null when no sample had a trustworthy sign (signedSamples: 0) — read that as unavailable, never as no bias.
  • symmetricHausdorff — the single worst-case nearest-surface distance in either direction. Unaffected by signMode.
  • signMode — which correspondence rule ran.
  • sections (optional) — when sectionAxis+sections given, an array of { offset, signedMean, rms, samples } per station; a near-constant non-zero signedMean across stations is the systematic section-error fingerprint. Sign-ambiguous samples are excluded from a station’s figures but still define the axis span, so offset always measures from the body’s minimum projection.

All distances are in model units. Sign convention: + proud (from outside the reference), − shy.

Example

// tool call arguments
{ "fromBodyId": "recon", "toBodyId": "source_mesh", "deflection": 0.1, "sectionAxis": [0,0,1], "sections": 6 }
// example result (abridged)
{
  "fromToTo": { "max": 0.18, "rms": 0.06, "mean": 0.04, "p95": 0.15, "signedMean": -0.03, "signedMin": -0.18, "signedMax": 0.05, "worstPoint": [42.1, 7.3, 0.0], "samples": 17230, "signedSamples": 17230, "ambiguousSamples": 0, "ambiguousFraction": 0.0 },
  "toToFrom": { "max": 0.22, "rms": 0.08, "mean": 0.05, "p95": 0.19, "signedMean": 0.02, "signedMin": -0.06, "signedMax": 0.22, "worstPoint": [41.9, 7.1, 0.0], "samples": 17230, "signedSamples": 17230, "ambiguousSamples": 0, "ambiguousFraction": 0.0 },
  "symmetricHausdorff": 0.22,
  "signMode": "robust",
  "sections": [ { "offset": 5.0, "signedMean": -0.03, "rms": 0.05, "samples": 2900 } ]
}

Notes — Unlike measure_distance (minimum gap, ≈0 for overlapping bodies), this samples each body’s tessellated surface. Fidelity scales with deflection. Import the reference mesh with import_file(format: "stl") or load an invalid in-progress reconstruction with read_brep(allowInvalid: true) before calling.


measure_vertex_fit

Exact per-vertex distance table from fromBodyId’s own vertices to toBodyId’s real BRep geometry (#118). Every scene body is stored as BRep (an STL import is a facet shell, one planar face per triangle), so fromBodyId’s own vertices ARE its mesh corner points; each is measured via exact BRepExtrema (no re-tessellation), with the nearest entity kind on toBodyId classified per vertex. Swift-only.

Server: Swift

Parameters

name type required description
fromBodyId string yes Body whose own vertices are measured (typically a mesh/STL import). Must differ from toBodyId.
toBodyId string yes Body each vertex is measured against (typically a BRep solid, e.g. a reconstruction).
maxVertices integer (≥1) no Cap on vertices measured (stride-subsampled if fromBodyId has more). Default 2000.
worstN integer (≥0) no Worst-N vertices (by distance, largest-first) included in the response. Default 20.
includeAllVertices boolean no Return every sampled vertex’s entry, not just the worst-N. Default false.

Returns: JSON object: vertexCount (total vertices on fromBodyId), sampledCount, stride, mean/rms/max/p95 (all sampled distances), worst (array of { index, point, distance, nearestKind }, largest-first), vertices (same shape, every sampled entry, only when includeAllVertices: true), and warnings.

Example

// tool call arguments
{ "fromBodyId": "scan_mesh", "toBodyId": "recon", "worstN": 5 }
// example result (abridged)
{
  "vertexCount": 812, "sampledCount": 812, "stride": 1,
  "mean": 0.021, "rms": 0.034, "max": 0.211, "p95": 0.098,
  "worst": [
    { "index": 403, "point": [12.4, -3.1, 8.0], "distance": 0.211, "nearestKind": "edge" }
  ],
  "warnings": []
}

Notes: the instrument neither measure_distance (body-to-body, capped at 32 pairs), measure_deviation (mesh-to-mesh, approximate re-tessellation, no raw per-vertex table), nor find_correspondences (only matches topological vertices, a handful on a typical solid) provides. Entity INDEX (which specific face/edge) isn’t resolved, only the kind (vertex/edge/face): resolving the index would multiply the per-vertex BRepExtrema cost by the target’s face/edge count for a “nice to have.” The tool spends its second (expensive) BRepExtrema call only on entries that reach the response, not every sampled vertex.


deviation_histogram

Signed point-to-surface deviation distribution between two bodies — the statistical companion to measure_deviation, with an optional histogram PNG (#62). Swift-only.

Server: Swift

Parameters

name type required description
fromBodyId string yes Candidate body.
referenceBodyId string yes Reference body.
deflection number no Mesh linear deflection. Default 0.5% of the from-body bbox diagonal.
tolerance number no If set, the report includes withinTolerance (fraction of samples within tolerance of the nearest reference surface).
signMode string no robust (default) or nearest — see Which way is out?.
outputPath string no Write a histogram PNG here. Omit for numbers only.

Returns{ mean, std, median, p95, signedMin, signedMax, maxAbs, withinTolerance?, buckets: [{ lo, hi, count }], samples, signedSamples, ambiguousSamples, ambiguousFraction, signMode }. Sign convention: + proud, − shy.

The distribution — mean / std / median / signedMin / signedMax / buckets — is built from sign-reliable samples only, because one flipped sign plants a mirror hump at −d and reads as exactly the bimodal systematic error this tool exists to spot. They come back null (and buckets empty, and no PNG) when nothing had a trustworthy sign. p95 / maxAbs / withinTolerance are magnitudes to the nearest surface and keep every sample, so maxAbs can be smaller than |signedMin| against an open thin-walled reference — see below.


Which way is out? (signMode)

measure_deviation, deviation_histogram and signed_deviation_heatmap share one signed-distance engine, so they share a signMode knob.

A deviation’s sign depends on which reference triangle a sample is judged against, and against an open, thin-walled reference (a raw scan / STL skin) the nearest one is often the wrong one. A candidate flank sitting 4.5 mm inside a 2 mm wall is only 2.5 mm from that wall’s inner surface, so that surface wins on proximity and — its outward normal facing the cavity — reports +2.5 proud for a part that is 4.5 shy. Wrong side, wrong magnitude, and nothing ties, so no coin-flip check catches it (#72).

mode behaviour
robust (default, v1.17.0+) Rejects reference triangles whose outward normal opposes the sample’s own before the nearest survivor wins, recovering both the side and the magnitude. Samples with no compatible surface in reach are reported ambiguous and withheld from the signed statistics rather than guessed.
nearest The nearest triangle wins, whatever it is — the pre-v1.17 rule. Correct against a watertight / single-surface reference.

Two families of number, and signMode moves only one:

family measures to moved by signMode?
max / rms / mean / p95 / worstPoint / symmetricHausdorff / maxAbs / withinTolerance the nearest reference surface no — same meaning as pre-v1.17
signedMean / signedMin / signedMax / sections / histogram buckets / heatmap colours the surface the sample corresponds to yes

Against a watertight reference these are the same surface and the two families agree. Against an open thin-walled one they diverge on purpose: max: 2.5 next to signedMin: -4.5 says the nearest reference geometry is an inner wall 2.5 away while the skin that flank belongs to is 4.5 above it. Both true. A gap between them is itself the tell that the reference is thin-walled.

An ambiguousFraction near 1.0 means the reference’s winding is likely inverted relative to the sampled body; the signed figures then come back null rather than a zero that would read as perfectly centred.


cross_section_compare

Slice both bodies at N stations across their shared axis-extent overlap and compare the 2D profiles — the highest-leverage detector of a reconstruction whose cross-section is the wrong shape everywhere yet whose 3D mean looks fine (#61, #66, #70). Swift-only.

Server: Swift

Parameters

name type required description
fromBodyId string yes Candidate body (e.g. the reconstruction).
referenceBodyId string yes Reference body (e.g. the source mesh).
axis number[3] yes [x,y,z] section sweep axis (e.g. the carbody longitudinal axis).
stations integer no Number of evenly-spaced cut planes across the shared overlap. Default 12.
through number[3] no A point the axis passes through. Default: from-body bbox centre.
deflection number no Mesh linear deflection. Default 0.5% of the from-body bbox diagonal.
outerEnvelope boolean no Compare against the reference’s outer boundary per angular direction (default true) so inner window-return / frame paths of a thin-wall or scanned part don’t pollute the metric. false = raw point-to-main-loop.
outputDir string no Directory for per-station overlay PNGs. Omit for numbers only.
imagePrefix string no Filename prefix for station PNGs. Default "section".
Returns — a report with overlap ([lo,hi] shared axis extent), referenceMode ("envelope" "profile"), meanSignedAcrossSections, maxAbsSignedSection, worstStation / worstAxisCoord, warnings[], and a sections[] array. Each section carries station, axisCoord (world position along the axis), offset (overlap-relative), signedMean / rms / maxAbs, centroidOffset, areaRatio, shapeL2 (pose-invariant shape scalar — defined for open profiles too), fromContours / referenceContours / fromOpenPaths / referenceOpenPaths, openProfile, registrationSmell, and imagePath.

Notes — Handles open-shell references (raw scan / STL skin) whose sections are open arcs. registrationSmell flags a station that sliced only one body (mis-registration / differing extents). Pair with import_file(format: "stl") to get the reference mesh into the scene.


symmetric_difference_volume

The two ONE-SIDED volumes between a candidate and a reference body, and their sum: the direct geometric fidelity figure a mean/RMS surface deviation can hide via cancellation (a part undersized in one place and oversized in another reads better on a mean). boolean_op cannot produce a true symmetric-difference solid against a mesh-only, possibly non-watertight reference (empirically: subtract fails both directions on a real STL reference); this instead classifies sample points against both bodies via OCCTSwiftMesh.Mesh.windingNumber(at:), the generalized winding number, which stays a well-defined real number on open/self-intersecting meshes rather than the undefined result a parity/ray test gives (#122). Swift-only.

Server: Swift

Parameters

name type required description
fromBodyId string yes Candidate body (e.g. the reconstruction).
referenceBodyId string yes Reference body (e.g. the source mesh); may be a non-watertight mesh.
deflection number no Mesh linear deflection. Default 0.5% of the from-body bbox diagonal.
maxSamples integer no Monte Carlo sample points (deterministic Halton low-discrepancy sequence) tested against both meshes. Default 300.

Returns: { samples, ambiguousSamples, ambiguousFraction, boundingBoxVolumeMm3, fromVolumeMm3, referenceVolumeMm3, intersectionVolumeMm3, unionVolumeMm3, fromOnlyVolumeMm3, referenceOnlyVolumeMm3, symmetricDifferenceVolumeMm3, symmetricDifferenceFraction, estimatedStdErrMm3, fromExactVolumeMm3, referenceExactVolumeMm3, fromWatertight, referenceWatertight, reliable, warnings }.

  • fromOnlyVolumeMm3: excess / over-build material (inside the candidate, not the reference).
  • referenceOnlyVolumeMm3: missing / under-build material (inside the reference, not the candidate).
  • symmetricDifferenceVolumeMm3: the sum of the two; symmetricDifferenceFraction is that divided by the union volume (1 minus IoU: 0 = identical, 1 = disjoint), null when the union itself sampled to ~0.
  • estimatedStdErrMm3: Monte Carlo standard error on symmetricDifferenceVolumeMm3. A measured value under ~2x this is noise-dominated at the current maxSamples, not necessarily a well-registered pair; raise maxSamples to resolve it.
  • fromExactVolumeMm3 / referenceExactVolumeMm3: exact BREP mass-properties volume where computable, reported as a cross-check only (silently unreliable for a non-closed shape, which is exactly the reference-body case this tool exists to handle).
  • reliable: false when ambiguousFraction exceeds 15% (the winding-number classification itself was too uncertain at this sample count), or when a body’s sampled volume disagrees with its own exact BREP volume by more than sampling noise can explain.

Example

// tool call arguments
{ "fromBodyId": "recon", "referenceBodyId": "source_mesh", "maxSamples": 2000 }
// example result (abridged)
{
  "fromVolumeMm3": 58900.3, "referenceVolumeMm3": 58750.1,
  "fromOnlyVolumeMm3": 210.4, "referenceOnlyVolumeMm3": 60.2,
  "symmetricDifferenceVolumeMm3": 270.6, "symmetricDifferenceFraction": 0.0046,
  "estimatedStdErrMm3": 38.1, "reliable": true, "warnings": []
}

Notes: Cost is O(fromTriangles + referenceTriangles) per sample point, so total cost scales with maxSamples × (fromTriangles + referenceTriangles): windingNumber has no spatial acceleration upstream (fine at the diagnostic sample counts this tool targets, not fine at the 5-digit maxSamples values measure_deviation defaults to). Keep maxSamples modest for scan-scale meshes; raise it only when estimatedStdErrMm3 says the current sample count can’t resolve the difference you’re looking for. Classification is orientation-agnostic (a point classifies as inside when the winding number is close to any nonzero integer, not specifically +1), so an inverted-winding reference still reports correctly without a separate orientation pre-pass. fromVolumeMm3/referenceVolumeMm3 are each estimated from their OWN body’s confidently-classified samples only, independent of the other body’s mesh quality; the joint figures (intersectionVolumeMm3, fromOnlyVolumeMm3, referenceOnlyVolumeMm3, symmetricDifferenceVolumeMm3) need both bodies confident at the same sample point and extrapolate from that jointly-confident subset when some samples are ambiguous.


recognize_features

Detect pockets and holes via OCCTSwift’s Attributed Adjacency Graph (AAG) heuristics.

Server: Swift + Node

Parameters

name type required description
bodyId string yes Body to analyse.
kinds string[] (enum) no Feature kinds to detect: pocket, hole. Default: both.

Returns — JSON object with a features array. Each entry includes the feature kind, the face indices that make up the feature, and geometric properties (e.g. diameter for holes, depth for pockets).

Example

// tool call arguments
{ "bodyId": "flange", "kinds": ["hole"] }
// example result
{
  "features": [
    { "kind": "hole", "faces": ["face[4]", "face[5]"], "diameter": 6.0, "depth": 12.0 },
    { "kind": "hole", "faces": ["face[8]", "face[9]"], "diameter": 6.0, "depth": 12.0 }
  ]
}

Notes — For the full graph-level feature recognition pipeline (B-Rep graph output, featureNodeIds) see feature_recognize in the Topology graph family. This tool returns a lightweight per-body feature list; feature_recognize writes a labelled TopologyGraph for downstream reconstruction use.


inspect_assembly

Walk an XCAF assembly hierarchy and return the component tree with transforms.

Server: Swift + Node

Parameters

name type required description
bodyId string no Scene body (BREP — returns a degenerate single-node response).
inputPath string no Absolute path to a STEP, IGES, or XBF file for the full tree.
depth integer (≥0) no Maximum tree depth to traverse. Default: unlimited.

Returns — JSON assembly tree: each node has name, shape (if a leaf), transform (4×4 matrix), and a children array. A BREP bodyId returns a single-node tree.

Example

// tool call arguments
{ "inputPath": "/Users/me/Downloads/assembly.step", "depth": 3 }
// example result
{
  "name": "Assembly",
  "transform": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],
  "children": [
    { "name": "BaseFrame", "shape": "BaseFrame", "transform": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]], "children": [] },
    { "name": "Lid", "shape": "Lid", "transform": [[1,0,0,50],[0,1,0,0],[0,0,1,0],[0,0,0,1]], "children": [] }
  ]
}

Notes — Pass inputPath (not bodyId) to get the full multi-level component tree from a STEP/IGES/XBF file. Use import_file first if you want the assembly bodies added to the scene.