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 (yaw + pitch) ยท double-click resets ยท slider explodes the assembly. The amber readout is real: shapes sorted and emitted 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 2019, 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 sketch layer: context blocks scope parts, tags and layering โ
// authoring should read like drafting, not like assembling tuples
import { sketch } from "linework/sketch";
const s = sketch({ yaw: .5, pitch: .16, f: 1500, cx: 460, cy: 320 });
s.box(300, 380, 320, 42, 40, 80); // base plate
s.part("housing", 'class="prt"', () => {
s.cyl([460, 218], 70, 34, -34, "ink"); // bearing body
s.bias(.6, () => s.cap([460, 218], 34, 32, "ink")); // bore
});
s.part("shaft", 'class="prt"', () =>
s.cyl([460, 218], 19, 168, 24, "ink")); // pulled out
s.note("320 mm", [460, 452]); // paper space
el.innerHTML = s.render(); // depth-sorted SVG
Lightly abridged (bolts, balloons and the title block omitted for width) โ the full scene is docs/scene.js, the exact module this page executes, written on the same sketch API.
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 hero).