buttery
Agent-friendly 2D explainer animations. The scene is a pure function of time: state(t) resolves every
property at t, nothing accumulates between frames, every frame renders independently and in parallel.
Pydantic models are the schema. JSON is the product contract; the Python API is sugar over the same models.
Agent tool surface validate / state / preview / render (tools.py, cli.py, mcp_server.py)
Authoring Python sugar <-> JSON (objects.py, expr.py, parse.py)
Core Scene, primitives, expression DAG (scene.py, evaluate.py)
Renderer skia-python, motion blur via sub-frames (render.py)
Install
pip install buttery # or: uv add buttery
brew install ffmpeg # for .mp4 output (PNG sequences work without it)
The renderer is skia-python, which ships large platform wheels; expect a heavier install than the code size suggests.
From a clone:
uv sync # Python 3.11+, pydantic, skia-python, numpy, mcp
uv run pytest
Python
from buttery import *
dot = Circle("dot", r=0.3, fill="coral")
dot.x = tween(-3, 3, at=0, dur=1.5, ease="out_cubic")
dot.y = 0.2 * sin(6 * T)
ring = Circle("ring", fill=None, stroke="white")
ring.x = dot.ref.x
ring.r = dot.ref.r + 0.5
scene = Scene(duration=3).add(dot, ring, Text("label", content="Hello", y=-1.6))
scene.state(1.0) # plain data
scene.preview(1.0, path="p.png") # quick low-res PNG
scene.render("out.mp4") # motion blur, all cores, ffmpeg
scene.save("scene.json") # the same thing as JSON
obj.prop reads the stored expression; obj.ref.prop makes a reference to it (the spec's dot.r + 0.5
became dot.ref.r + 0.5 because a plain attribute read cannot be both a value and a reference).
JSON
{
"duration": 3,
"objects": [
{"id": "dot", "type": "circle", "r": 0.3, "fill": "coral",
"x": {"op": "tween", "keys": [[0, -3], [1.5, 3]], "ease": "out_cubic"},
"y": {"op": "mul", "args": [0.2, {"op": "sin", "args": [{"op": "mul", "args": [6, "t"]}]}]}},
{"id": "ring", "type": "circle", "fill": null, "stroke": "white", "x": "dot.x", "r": "dot.r + 0.5"}
]
}
Shorthand strings like "dot.r + 0.5" parse (no eval) into the same op tree. uv run buttery schema
prints the JSON schema from Scene.model_json_schema().
Coordinates: world units, origin at center, y up. The frame is view_width (default 8) units wide.
Primitives: circle rect line text group. Ops: add sub mul div neg sin cos abs min max clamp smoothstep noise.
Eases: linear in_quad out_quad in_out_quad out_cubic in_out_cubic spring. Colors: hex or CSS names;
fill/stroke can be tweened between colors.
Agent surface
uv run buttery validate scene.json
uv run buttery state scene.json --t 1.25
uv run buttery preview scene.json --t 1.25 --out p.png --scale 0.25
uv run buttery render scene.json out.mp4 [--no-motion-blur --samples 8 --shutter 0.5 --workers N]
uv run buttery mcp # MCP server on stdio: same four tools + scene://schema resource
Every call returns {"ok": true, ...} or {"ok": false, "errors": [{"path", "message", "object", "property", "t"}]}.
Never a bare stack trace.
Claude Code skill: skill/ (symlink or copy it into ~/.claude/skills/buttery). Register the MCP server with
claude mcp add buttery -- uv run --directory /path/to/this/repo buttery mcp.
Validation and evaluation rules
- Structural validation is pydantic (unknown fields rejected, arity checked, colors checked).
- Semantic validation: unique ids (global across groups), references resolve to numeric animatable properties, no dependency cycles, tween key kinds match the property kind.
- Evaluation compiles each expression to a closure once, walks the DAG in dependency order, and never mutates the scene.
- Motion blur:
samplessub-frames spread acrossshutter× frame interval, averaged (0.5 = 180° shutter).
Layout
src/buttery/
expr.py AST models (Op, Tween, Ref), operator overloading, sugar (T, sin, tween, keyframes, ...)
parse.py shorthand parser -> AST, constant folding
objects.py Circle, Rect, Line, Text, Group
scene.py Scene, semantic checks, state(t)
evaluate.py compile + topological evaluation
render.py skia rasterizer, motion blur, parallel render, ffmpeg
tools.py validate / state / preview / render (JSON in, JSON out)
cli.py `buttery` command
mcp_server.py MCP server
examples/ bounce.py, squash_bounce.py (Python), bounce.json, squash_bounce.json, launch_demo.json
skill/ Claude Code skill
tests/
Not in v1
Equations/LaTeX, 3D or cameras, GUI, audio, plugins, manim parity.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file buttery-0.1.0.tar.gz.
File metadata
- Download URL: buttery-0.1.0.tar.gz
- Upload date:
- Size: 124.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b027ba672be190b8d846f8c69420046c54e8ed564d5d46798f74ede50c880dfa
|
|
| MD5 |
cb0911cfa7ab573941820ac20f593a39
|
|
| BLAKE2b-256 |
fc89f972aa19f0cae7278936ce9950eb41533228e70c8c99e77de3b8e84a6284
|
Provenance
The following attestation bundles were made for buttery-0.1.0.tar.gz:
Publisher:
publish.yml on fletchgraham/buttery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
buttery-0.1.0.tar.gz -
Subject digest:
b027ba672be190b8d846f8c69420046c54e8ed564d5d46798f74ede50c880dfa - Sigstore transparency entry: 2752771714
- Sigstore integration time:
-
Permalink:
fletchgraham/buttery@754e04e84de24a2e8ff0736e9caf9d521e6d32a2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/fletchgraham
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@754e04e84de24a2e8ff0736e9caf9d521e6d32a2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file buttery-0.1.0-py3-none-any.whl.
File metadata
- Download URL: buttery-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e24e86a2ef96441bdc7c323859d81a3e2d007d5d6f83d82d38b6347e8c46eb6
|
|
| MD5 |
41500f5b821ee6ed5e853444cfe3adbb
|
|
| BLAKE2b-256 |
1c748a6eb0f5fe1fa8b74d87d7dd60c7cb1f985c033e0bbd8ad89137e3abb4a6
|
Provenance
The following attestation bundles were made for buttery-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on fletchgraham/buttery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
buttery-0.1.0-py3-none-any.whl -
Subject digest:
4e24e86a2ef96441bdc7c323859d81a3e2d007d5d6f83d82d38b6347e8c46eb6 - Sigstore transparency entry: 2752771736
- Sigstore integration time:
-
Permalink:
fletchgraham/buttery@754e04e84de24a2e8ff0736e9caf9d521e6d32a2 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/fletchgraham
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@754e04e84de24a2e8ff0736e9caf9d521e6d32a2 -
Trigger Event:
release
-
Statement type: