Measurement
Measurement functions return Result<number> — null shapes and other inputs that prevent measurement surface as BrepError rather than throwing. For valid inputs, unwrap() extracts the number; in production code prefer isOk() / match(). You ask "what is the volume of this thing?" and you get a Result. This chapter is short because the API is small.
The four core measurements
import {
box,
cylinder,
sketchCircle,
edgeFinder,
measureVolume,
measureArea,
measureLength,
unwrap,
} from 'brepjs/quick';
const b = box(10, 10, 10);
const cyl = cylinder(5, 20);
console.log('Box volume:', unwrap(measureVolume(b))); // 1000
console.log('Cylinder volume:', unwrap(measureVolume(cyl)).toFixed(3)); // 1570.796
console.log('Box area:', unwrap(measureArea(b))); // 600
console.log('Cylinder area:', unwrap(measureArea(cyl)).toFixed(3)); // 785.398
// Edge length
const cylinder2 = sketchCircle(10).extrude(20);
const circularEdge = edgeFinder().ofCurveType('CIRCLE').findAll(cylinder2)[0];
if (circularEdge) {
console.log('Edge length:', unwrap(measureLength(circularEdge)).toFixed(3)); // 62.832
}
export default cylinder2;| Function | Argument | Returns |
|---|---|---|
measureVolume(shape) | Shape3D | Result<number> — total volume in cubic units |
measureArea(shape) | Shape3D or Face | Result<number> — total surface area in square units |
measureLength(edge) | Edge or Wire | Result<number> — length along the curve |
These are the workhorses. The fluent wrapper exposes them as methods: shape(s).volume(), shape(s).area().
Bounding box
The axis-aligned bounding box of a shape:
import { box, getBoundingBox } from 'brepjs/quick';
const b = box(30, 20, 10, { at: [5, 5, 5] });
const bbox = getBoundingBox(b);
console.log('Min:', bbox.min); // [5, 5, 5]
console.log('Max:', bbox.max); // [35, 25, 15]
console.log('Centre:', bbox.center); // [20, 15, 10]
console.log('Size:', bbox.size); // [30, 20, 10]
export default b;The bounding box is in world coordinates and updates after every transform. Useful for layout and packing.
Centre of mass
import { box, cylinder, fuse, getCenterOfMass, unwrap } from 'brepjs/quick';
const compound = unwrap(fuse(box(20, 20, 20), cylinder(5, 30, { at: [10, 10, 0] })));
const com = getCenterOfMass(compound);
console.log(
'Centre of mass:',
com.map((c) => c.toFixed(3))
);
export default compound;Returns [x, y, z]. Computed via the kernel's mass-property analysis assuming uniform density.
Distance and projection
The closest point between two shapes:
import { box, sphere, distanceTo } from 'brepjs/quick';
const a = box(10, 10, 10);
const b = sphere(3, { at: [50, 0, 0] });
const result = distanceTo(a, b);
console.log('Distance:', result.distance.toFixed(3)); // ~36
console.log('Closest on A:', result.pointOnA);
console.log('Closest on B:', result.pointOnB);Returns { distance, pointOnA, pointOnB }. The points are the closest pair; the distance is |pointOnA - pointOnB|.
A negative distance (or zero) means the shapes overlap. For exact overlap detection use distanceTo(a, b).distance <= 0.
Projecting a point onto a surface
import { sketchCircle, faceFinder, projectPointToFace } from 'brepjs/quick';
const cyl = sketchCircle(10).extrude(20);
const sideFace = faceFinder().ofSurfaceType('CYLINDER').findAll(cyl)[0];
if (sideFace) {
const proj = projectPointToFace(sideFace, [15, 0, 5]);
console.log('Projected to:', proj.point); // [10, 0, 5]
console.log('Distance:', proj.distance); // 5
}Useful for placing features (logos, fasteners) at a specific position on a curved surface.
Curvature
import { sketchCircle, edgeFinder, curveCurvatureAt } from 'brepjs/quick';
const cyl = sketchCircle(10).extrude(20);
const circ = edgeFinder().ofCurveType('CIRCLE').findAll(cyl)[0];
if (circ) {
const k = curveCurvatureAt(circ, 0.5); // mid-point of the edge
console.log('Curvature:', k.value); // 0.1 = 1/radius
}For circles, curvature is 1/r. For straight edges, 0. For B-splines, varies along the edge.
Surface curvature
import { sketchCircle, faceFinder, faceCurvatureAt } from 'brepjs/quick';
const cyl = sketchCircle(10).extrude(20);
const side = faceFinder().ofSurfaceType('CYLINDER').findAll(cyl)[0];
if (side) {
const c = faceCurvatureAt(side, 0.5, 0.5); // u, v parameters in [0,1]
console.log('Min curvature:', c.minCurvature); // 0 (along axis)
console.log('Max curvature:', c.maxCurvature); // 0.1 = 1/r (around)
console.log('Gaussian:', c.gaussian); // 0
console.log('Mean:', c.mean); // 0.05
}Returns principal curvatures, Gaussian, and mean. Useful for detecting flat regions (curvature ~0) or sharp regions (high max).
Counts
import { box, vertexFinder, edgeFinder, faceFinder } from 'brepjs/quick';
const b = box(20, 20, 20);
console.log({
vertices: vertexFinder().findAll(b).length, // 8
edges: edgeFinder().findAll(b).length, // 12
faces: faceFinder().findAll(b).length, // 6
});For a quick "how complex is this shape" check, count entities at each level. A simple part has tens of edges; a heavily-filleted assembly has thousands.
Performance notes
- Volume and area are cheap (kernel mass-properties query).
- Distance between two shapes can be expensive — proportional to face counts. For repeated distance queries, build a spatial index (
flatbushis included as a dependency) over your shapes' bounding boxes. - Curvature queries are constant-time at a single parameter, but evaluating a dense grid of points is naive — there's no built-in batched API.
Common patterns
Volume check after a boolean
import { box, cylinder, cut, measureVolume, isOk, unwrap } from 'brepjs/quick';
const block = box(20, 20, 20);
const hole = cylinder(5, 25);
const result = cut(block, hole);
if (isOk(result)) {
const before = unwrap(measureVolume(block));
const after = unwrap(measureVolume(result.value));
const removed = before - after;
console.log(`Cut removed ${removed.toFixed(2)} mm³`);
}
export default isOk(result) ? result.value : block;Sanity-check that booleans actually changed the volume by the expected amount.
Tightest fit packing
For a layout tool, you need bounding boxes:
import { box, getBoundingBox } from 'brepjs/quick';
const parts = [box(10, 20, 5), box(15, 10, 5), box(25, 15, 5)];
const bboxes = parts.map(getBoundingBox);
const totalWidth = bboxes.reduce((sum, bb) => sum + bb.size[0], 0);
console.log('Total width if linear-packed:', totalWidth);Mass with non-unit density
The kernel computes everything assuming density 1. Multiply at the end:
import { box, measureVolume, unwrap } from 'brepjs/quick';
const part = box(30, 20, 10);
const volumeMm3 = unwrap(measureVolume(part));
const volumeCm3 = volumeMm3 / 1000;
const aluminiumDensity = 2.7; // g/cm³
const massGrams = volumeCm3 * aluminiumDensity;
console.log(`Mass: ${massGrams.toFixed(2)} g`);Next steps
- Finders & Queries — selecting specific entities to measure
- Import & Export — measuring shapes you imported from STEP
- The Topology Hierarchy — what each shape kind contributes to area / volume