A tiny true-3D renderer for annotated technical drawings. 3D points in โ depth-sorted SVG strings out.
Every drawing on this page โ including the one to the right โ is rendered live by the library when the page loads.
Drag to orbit ยท double-click resets ยท the slider raises the crease threshold. The amber readout is real: feature edges extracted and depth-sorted this frame.
three.js makes shaded surfaces easy and technical drawing painful: 1px line limits, dash patterns via shaders, text callouts as a separate DOM overlay system. Every signature element of a service manual is an uphill fight.
Zdog nailed the flat-shaded look โ and shipped its last release in early 2022, with text as a third-party plugin. Dimension lines, balloons and leader text are the load-bearing half of a technical drawing.
Output is plain markup: themeable with CSS variables, crawlable, printable, screen-reader friendly โ and renderable server-side at build time with zero client JavaScript.
// the lantern above โ a whole glTF mesh becomes linework in three calls
import { parseGLB, meshToShapes } from "linework/import";
import { render } from "linework";
const meshes = parseGLB(await (await fetch("lantern.glb")).arrayBuffer());
const shapes = meshToShapes(meshes, { angle: 25, fit: { cx: 400, cy: 300, size: 440 } });
el.innerHTML = render(shapes, { yaw: .6, pitch: .35, f: 1400, cx: 400, cy: 300 });
Feature-edge extraction keeps the outline and hard creases, nothing from the smooth interior of a face โ the lines a draftsperson would draw. Prefer to hand-author instead? The sketch layer builds annotated scenes โ boxes, cylinders, dimension notes, exploded parts โ with scoped context blocks; see docs/scene.js and the sketch docs.
Paint order is computed every frame, never authored โ members interleave correctly at any angle. Shapes sharing a part key sort as one object and emit inside one <g>, so CSS transforms on animated parts keep working. Known painter's-algorithm caveat: cyclic overlaps can't sort โ split long members into segments if you construct one.
| Field | Meaning |
|---|---|
x | right, in your SVG's user units |
y | down โ screen convention, not math convention |
z | toward the viewer; negative z recedes and depth-dims |
view.yaw | rotation about the vertical axis through cx (radians) |
view.pitch | rotation about the horizontal axis through cy; positive looks down |
view.f | perspective focal distance โ k = f / (f โ z); larger = flatter |
bias | per-shape depth nudge for deliberate layering of coplanar shapes |
// render() is a pure string function โ run it at build time
import { render } from "linework";
import { writeFileSync } from "node:fs";
writeFileSync("diagram.svg", wrap(render(shapes, view)));
Static site generators can ship finished 3D-looking exploded diagrams with zero client JavaScript โ the README hero image in this repo is generated exactly this way (npm run images).