July 30, 2025 · essay · 5 min read

Why I built a kinematic simulator from scratch

Last spring I visited a working smock mill outside Leiden with my daughter. She's eight and currently obsessed with how things move — gears especially, after I showed her a clock mechanism YouTube video she has since watched approximately forty times. We spent an hour watching the miller explain the drivetrain, and I made the mistake of promising her I'd "show her the math" when we got home.

I found a few options. Matlab's Simscape can do this, but it costs money and requires writing models in a DSL I don't particularly enjoy. OpenMDAO is Python but oriented toward aerodynamic design optimization — the API felt awkward for a pure kinematics problem. SimPy is discrete-event simulation, not mechanical. FreeCAD has a linkage solver, but exporting timestep data requires a lot of Python scripting against an underdocumented API.

I ended up writing a 200-line NumPy script that handled the specific Leiden mill geometry. It worked. My daughter was briefly impressed, then asked why the SVG animation was "so slow and jerky", which was fair — I was regenerating the entire SVG string on every frame in a Python loop. She went back to her YouTube video.

What I actually wanted

Three things that none of the existing tools got right for my use case:

  1. Declarative assembly. I want to say "this gear meshes with that gear at this ratio" and have the solver figure out the positions and velocities. Not specify transformation matrices.
  2. Exportable animations without JavaScript. Pure SVG with CSS/SMIL animation. Opens in any browser, embeds in documents, doesn't require a web server.
  3. Fast enough for interactive use. The NumPy script took 4 seconds to simulate 10 seconds of motion at 1 kHz. That's fine for a batch job but annoying when you're tweaking tooth counts and re-running.

None of these is technically hard. The constraint-graph approach to kinematics is well-understood. Involute gear geometry has been implemented a thousand times. Writing a fast inner loop in Rust is tedious but not research. I just wanted the thing to exist as a Python library with a reasonable API, and it didn't, so I wrote it.

What I got wrong

The first version had a completely ad-hoc constraint representation. Each assembly type (spur mesh, bevel pair, belt drive) had its own special-cased code path in the solver. Adding a new constraint type meant modifying the solver internals. This was fine until I wanted to add the fixed-pivot constraint for the windmill frame, at which point I rewrote the whole thing to use a proper residual/Jacobian interface. That rewrite became 0.2.0 and it took three weekends.

I also originally stored history as a Python list of dicts (one dict per timestep, keys are shaft names). At 10 kHz over 10 seconds that's 100,000 dicts. Memory usage was comical. The switch to pre-allocated NumPy arrays happened in 0.1.3 and cut memory 40×.

Where it is now

About 4,200 lines of Rust and 1,800 lines of Python. A test suite that runs in 8 seconds. A handful of users I know of, mostly mechanical engineering students and hobby CNC people. My daughter has not looked at any of the SVG exports since the initial demo, but I remain optimistic.

If you're using Molenspin for something interesting, I'd genuinely like to hear about it. There's a contact address on the about page.