Face picking gave us the hard part for free: a raycast that returns exact world-space intersection points on the model. Measuring a distance is what you get when you stop throwing those points away.
The interaction
Check measure in /viewer's panel and clicks switch modes —
instead of selecting a B-rep face, each click drops a marker at
hit.point, the precise spot the ray pierced the surface. Second click draws
the segment and reports the distance. Third click starts a fresh measurement.
Uncheck and it all clears.
The distance itself is a.distanceTo(b) — the interesting parts are
everything around it.
Marker scale is model scale
A marker sphere needs a radius, and "1 unit" means nothing when models range
from a 10 mm cube to a 2 m assembly. So markers derive from the model's
bounding sphere at load time: radius * 0.008. Same visual size on every
model, no fiddling.
The floating label
The distance tag isn't in the 3D scene — it's a plain DOM element. Each frame,
the segment's midpoint goes through Vector3.project(camera) into normalized
device coordinates, then converts to pixels:
_mid.addVectors(measurePoints[0], measurePoints[1]).multiplyScalar(0.5);
_mid.project(camera);
measureLabel.style.left = `${((_mid.x + 1) / 2) * rect.width}px`;
measureLabel.style.top = `${((-_mid.y + 1) / 2) * rect.height}px`;
That's the standard trick for annotations: the label stays crisp, selectable size, immune to perspective — and it costs one matrix multiply per frame.
Units are a convention, not a fact
STEP files carry their own units and OCCT tessellates in them — in practice,
millimeters. The readout auto-scales like the face areas do: under 100 mm it
shows 42.3 mm, beyond that 14.2 cm. If a model arrives in inches the
numbers will be off by 25.4× — a real fix means reading SI_UNIT from the
STEP header, which is on the list.
One detail worth keeping: markers and the measure line carry the same
clippingPlanes as the model, so measurements on a sectioned surface clip
exactly like the geometry they sit on.
What this unlocks
Distance-between-points is the smallest useful measurement. The same infrastructure extends naturally: edge lengths once true B-rep edges land, face-to-face distances, maybe a tape along a surface. The pick pipeline is done — the rest is choosing which answer to compute.
Six milestones in: parse, parts, edges, sections, face data, measurements. The remaining roadmap is honest — true B-rep edges, then WebGPU when a workload earns it.