Showing posts with label manim. Show all posts
Showing posts with label manim. Show all posts

2026-08-17

The gear train of a Bulova pocketwatch

Many years ago, Bulova sold mechanical pocketwatches like this one:
It's a Bulova 1937 Chesterfield and I've taken the case-back off so you can see the movement is a 17AE. The dial looks something like this one from mybulova.com. You can see various gears, held in place by a screw and/or an odd-shaped metal plate (called a bridge). So you can understand the terms, here's a photo from a parts catalogue:
The center rod of the gear is called the "arbor". The small gear attached to the arbor is called a "pinion" and its cogs are called "leaves". The outer gear attached to the arbor is sometimes called a "wheel" and its cogs are called "teeth". None of the catalogues I can access list the size of each gear nor the number of teeth/leaves. All this data was guessed at using AI (chatGPT or Gemini, usually) using photos I could find online.

The mainspring powers the watch, causing the hands to move around the dial in a complicated mechanical process. You wind the watch, which tightens a spring wound around the arbor of a hollowed out gear called the "barrel" (these barrels are very small and flat, unlike a typical barrel you might see at a winery or a western movie). Inside the hollowed space of the barrel is the mainspring, a very thin flat piece of alloy metal coiled up like a snake. As the spring unwinds, it slowly turns the barrel. To speed up this turn, the clever idea of a "gear train" is used. The teeth of the barrel mesh with the leaves of the pinion for the center wheel, which turn the teeth of the center wheel a bit faster than the teeth of the barrel. To speed this up further, the teeth of the center wheel mesh with the leaves of the pinion for the third wheel, which turn the teeth of the third wheel a bit faster than the teeth of the center. Adding a fourth wheel to this "train" gets the rotation up to the speed needed for a watch. You get the idea, but better is to use an animation.

Using AI, I created a manim Python module to animate this process.

This animation is a simplified mathematical model of the gear train of a Bulova 17AE mechanical watch movement, showing the barrel wheel, center wheel and pinion, third wheel and pinion, and fourth wheel and pinion rotating in mesh. The mainspring (unshown) inside the barrel is slowly unwinding and that energy turns the pinion of the center wheel, which turns the third wheel pinion, and so on.

The large wheels are partially transparent so that overlapping components on different mechanical planes remain visible, and each large wheel has a white reference tooth to make its rotation easier to follow. Over the main 224-second animation, the barrel wheel turns only about one-quarter revolution, while the gear ratios progressively increase the rotational speed through the train. With the tooth counts used in this model, the fourth wheel rotates 450 times as fast as the barrel, completing about 105 revolutions during the 3:49 animation.

2026-07-12

PAM's first feature animation.

PAM stands for Poses Audio Motion (pam github repo). See the reference manual for more.

Stills from a nerdy movie, and the even nerdier way it was made.

The animation embedded below was not edited together in the usual sense. Nobody dragged a clip onto a timeline, nudged a keyframe, or clicked a render button in an animation package. The entire film — every character, prop, line of dialogue, camera focus, scene transition, and sound cue — is described in a single text file: a screenplay written in JSON. A Python module reads that file and produces the finished video. The movie is compiled, not edited.

"Too Nice to Die" on Youtube

Click the above link to watch the 100 minute film.

The screenplay is the source code

The screenplay is an ordered list of actions. Here is an actual excerpt — a character enters, walks to her mark, and speaks:

{"action": "fade_in", "who": "nona", "offset": [-6.0, -2.0, 0.0]},
{
   "action": "walk_to", 
   "who": "nona", 
   "x": -2.25,
   "sfx": "sfx/cue-nona.mp3", 
   "sfx_duration": 2.0
},
{
   "action": "say", 
   "who": "nona",
   "text": "Chekov will confirm.",
   "hold": 2.0,
   "sfx": "sfx/cue-nona.m4a", 
   "sfx_duration": 1.0
}

That (and about 45000 lines not shown here:-) is the whole authoring interface. There is no hidden project file, no binary scene format, no GUI state. If it isn't in the JSON, it isn't in the movie. Even the sound design works this way: each action can carry an inline sound cue (with gain and trim parameters), so the film's roughly 1600 audio events are just more keys in the same text file.

The player: one command, whole film

The Python module that interprets the screenplay is PAM (Python Animation Maker), built on top of the Manim mathematical animation library — yes, the one used for math-explainer videos. PAM adds stick-figure characters with poses, faces, walk cycles, speech bubbles, props, a dog, and a talking dodecahedron, all driven by the action vocabulary above. Rendering the feature is a single shell command:

PAM_SCRIPT=SCREENPLAY_FILENAME manim --disable_caching -ql pam_player.py PAMPlayer

No mouse. No further keyboard input. Some time later, out comes an MP4 with the soundtrack already mixed in.

Why do it this way?

The film is versionable. The screenplay lives in a git repository like any other source file. A change to the movie is a diff — readable, reviewable, revertible. When I recently tightened all the scene transitions, the entire edit was a script transforming one JSON file into another, and it could be verified the same way software is verified: by checking, mechanically, that every scene still starts with exactly the same characters on stage as before.

The film is reproducible. Anyone with the repository and the sound files can render the identical movie. There is no "project file that only opens on my machine."

Fixes are surgical. When a character faced the wrong way in one scene — a stick figure looking left while talking to a dog standing to her right — the fix was changing the string "lside" to "rside" on two lines. Re-render, done.

And honestly: it's the natural medium. A screenplay was already a formal, structured document before computers existed. Sluglines, action lines, dialogue — screenwriters invented a domain-specific language a century ago. JSON just makes the compiler possible.

The pipeline, end to end

Scenes start life in Fountain, the plain-text screenwriting format, extended with annotation keys for moods, camera focus, sounds, and captions. A converter (fountain2pam.py) turns that into the JSON action list; the player turns the JSON into video; the video goes to YouTube. Text all the way down, until the last step.

The screenplay for the film above, the player, and the reference manual are in the PAM repository if you want to see how deep the rabbit hole goes — or render your own. Bring your own dodecahedron.

2026-03-22

A fun stick figure animation

This post is on a work-in-progress called PAM. PAM is a Pose, Audio, Motion library for manim (here Audio really means text-in-a-speech-bubble at this point). See https://github.com/wdjoyner/pam for a detailed readme and example code. PAM animates humanoid graphs against a black background.

The code from a specific type of json file called a PAM screenplay is fed into a python module (pam_player.py) directing the animation. The output is below (click on the lower corner to enlarge).

2026-03-12

Visualizing Chess Engine Heuristics with Python and Manim

Chess animator

I have recently been developing a Python-based toolset designed to translate chess game data (PGN) into structured video via the Manim animation engine. The project, chess-animator, provides a programmatic framework for visualizing moves alongside underlying evaluation metrics produced by engine analysis.

The package integrates python-chess for logic and Stockfish for centipawn evaluation, generating a multi-panel animation that includes a real-time evaluation bar and comparative metrics for various positional factors.

The source code and documentation are available on GitHub: https://github.com/wdjoyner/chess-animator


Technical Note: Metric Definitions and Scaling

A primary objective of this visualization is to map engine heuristics to an intuitive geometric scale. The evaluation bar and the positional metrics strip follow these specific definitions:

  • Evaluation Bar Scaling: The bar is normalized such that a 100-centipawn advantage (+1.0) corresponds to the height of exactly one square on the adjacent 8x8 chessboard. The visual range is clamped at ±5.0 to maintain resolution for positional play while clearly indicating decisive tactical advantages.
  • Space: This metric quantifies territorial control by counting squares controlled by a side, with higher weights assigned to squares in the opponent's half of the board (ranks 5–8 for White, 1–4 for Black).
  • Mobility: Calculated as the number of legal moves available to a side's pieces, weighted by piece type to reflect the relative importance of activity for different units.
  • King Safety: A structural assessment based on the defensive integrity of the pawn shield and the proximity of friendly versus enemy pieces to the king's square.

By utilizing Manim’s ability to render mathematical objects, these metrics are updated move-by-move in synchronization with the piece animations. This provides a granular view of how the character of a position evolves through the interaction of these heuristics.

Here is an example of the game below

[Event "Candidates Tournament"]
[Site "Toronto CAN"]
[Date "2024.04.04"]
[Round "5"]
[White "Caruana, Fabiano"]
[Black "Nepomniachtchi, Ian"]
[Result "1-0"]
[WhiteElo "2803"]
[BlackElo "2758"]
[ECO "C54"]
[Opening "Italian Game"]

1. e4 e5 2. Nf3 Nc6 3. Bc4 Bc5 4. c3 Nf6 5. d3 d6 
6. O-O O-O 7. Re1 a6 8. Bb3 Ba7 9. h3 Re8 10. Nbd2 Be6 
11. Bc2 d5 12. exd5 Bxd5 13. Nf1 h6 14. Ng3 Qd7 
15. Be3 Bxe3 16. Rxe3 Rad8 17. Qe2 Qc8 18. Rae1 b5 
19. Qd2 Ne7 20. d4 exd4 21. cxd4 Ng6 22. Bb3 Bxb3 
23. axb3 Rxe3 24. Rxe3 Nf4 25. Qc3 Rd5 26. Ne5 N6h5 
27. Nxh5 Nxh5 28. Re1 Nf6 29. Nc6 Qd7 30. Ne5 Qc8 
31. Nc6 Qd7 32. Ne5 1-0