Getting started · Tutorials · Gallery · Coming from openFrameworks or p5.js · Examples
Shape ideas in code. Fire them into art.
One live window exercising every name in the API: uv run python examples/showcase.py
Kiln is a fast creative-coding framework for Python. You subclass kiln.App, fill in
setup, update and draw, and call functions like kiln.fill, kiln.circle and
kiln.rect. Live state reads like data (kiln.mouse.x, kiln.clock.time), scopes are
with blocks (with kiln.transform():), and modes are enums that also take strings
(kiln.blend("add")). The naming page has the rules, and the
changelog maps every 0.1 name to its 0.2 name. It runs on macOS on Apple
Silicon. It draws in 2D and puts the GPU in reach: pictures, offscreen buffers,
your own shaders, meshes and instanced geometry, TrueType text and GUI panels. 3D comes later.
Why it's fast. A drawing call never talks to the GPU. It does a little arithmetic in
plain Python (it applies the current transform) and appends one record to a batch. At the
end of the frame the whole batch is uploaded once and drawn in a handful of GPU calls, with
each circle, rectangle, line or glyph evaluated as a signed distance field in the fragment
shader, so there is no tessellation and edges are anti-aliased for free. For thousands
of identical-style shapes, the bulk functions (circles, rects,
lines, ellipses) take numpy arrays and build the records in one go.
The GL layer is zengl with
glfw.
Why Kiln
Sketch with agents. Sketch agents.
Sketch with agents. Kiln is the creative-coding framework your agents can see: a coding agent can write a Kiln sketch, run it, look at what it drew and fix it, without you in the loop. Three things make that work, and they help you as much as the agent:
-
A sketch renders to an array.
kiln.testing.render_framesruns anyAppin a hidden window, with scripted mouse and keyboard input, and returns the last frame as a numpy array (every frame throughon_frame). An agent, a test or a notebook on a Mac with a display session gets pixels back:from kiln.testing import MouseMove, render_frames frame = render_frames(Sketch(), frames=30, size=(800, 600), events=[(10, MouseMove(400, 300))]) frame.shape # (600, 800, 4): RGBA pixels to save, diff, or show to a model
Every example takes
--frames Nto run N frames and exit. A shader that fails to compile raises an error that names the lines of your own GLSL. -
Arrays in, arrays out. The bulk draw calls take numpy arrays and an image's pixels are an array you edit in place, so anything in Python that produces arrays, from PyTorch or MLX to a data frame, can feed a sketch from the same file.
-
One obvious way. One name per concept, a small public API (
kiln,kiln.agentsandkiln.testing), names a model already knows, and docs whose every code cell is executed, so the examples an agent learns from are known to run.
Sketch agents. kiln.agents puts agents inside the sketch: a Population kept as
arrays, senses, steering forces and a behaviour tree that decides for every agent at once,
so two thousand sparks flock and run from the mouse in examples/16_flocking.py. See the
agents tutorial.
What's in it
- Shapes and style. Circles, ellipses, rectangles, rounded rectangles, lines, triangles,
and custom shapes with holes, curves and beziers (
with kiln.shape()); a fill and a stroke in two colours (fill,stroke) and atintfor pictures; transforms and styles scoped bywith, blend modes, bitmap and TrueType text; mouse, keyboard and file-drop events; simplex noise and colour (HSB, hex). - Images and textures.
kiln.Imageandkiln.Textureload PNG, JPEG and the rest, and draw them scaled, tinted, cropped (draw(source=)) and as thousands of sprites in one batch. Read and write pixels as numpy arrays, set filters, wrap modes and mipmaps, and copy the screen back (grab_screen,save_screen). - Fbos.
with fbo:draws offscreen, in 8-bit or float, and the Fbo is a texture: posters and masks, feedback trails, and ping-pong simulations. Transparency composites as it does on screen. - Shaders.
kiln.Shadercolours anything Kiln draws with one GLSL function (vec4 shade(Material m)), keeping every shape's anti-aliasing; or loads a raw vertex and fragment program (Shader.load) for meshes and Vbos. Compile errors name your lines. - Meshes.
kiln.Meshholds vertices, colours, texture coordinates, normals and indices, draws everyPrimitiveMode, loads.objand.plyfiles, and warps a picture on a grid. - Vbos.
kiln.Vbokeeps geometry on the GPU and draws it again without re-uploading, with partial updates and your own vertex attributes. - Instancing.
Vbo.draw(instances=)and per-instance attributes draw thousands of copies of one shape in one call: a swarm of 4,096 fish at 0.5 ms a frame. - GPGPU. Float Fbos and raw shaders simulate on the GPU: a quarter of a million particles in a curl-noise flow.
- Sound. Instruments you play from Python (
kiln.Synth,kiln.Pluck,kiln.Sfx, drums such askiln.Kick), effects, a stepkiln.Sequencer, and a spectrum, waveform and level to draw from. It plays through the default audio device, from compiled code on its own thread, so a busydraw()never makes it stutter. Install withpip install "kiln-gl[sound]"; the sound tutorial makes a first beep. - Examples. A live showcase of the whole API, six original scenes (Abyss, after
Haeckel, Pilgrim, inspired by thatgamecompany's Journey, and four agent scenes,
Embers in a draught, Shoal, Meadow and Neuroevolution), a stress scene, nine
ported sketches, seven sound examples,
and nine
remixes of well-loved sketches, read through the kiln (
examples/, rendered in the gallery).
Install
Kiln needs macOS on Apple Silicon, Python 3.11 or newer and OpenGL 4.1. Install it from PyPI:
pip install kiln-gl
# or, with uv:
uv add kiln-gl
For sound, add the extra: pip install "kiln-gl[sound]" (or uv add "kiln-gl[sound]").
To work from source, clone the repository and use uv:
git clone https://github.com/slegroux/kiln && cd kiln && uv sync
# or add the development version to your own project:
uv add "kiln-gl @ git+https://github.com/slegroux/kiln"
Status
Kiln is 0.x: a minor version may break the API, and every break is listed in the changelog.
- Stable for 0.x: the
Applifecycle, shapes,Styleand transforms,Color,Image,Texture,Fbo,Mesh,Font, the math helpers andrun(). - Experimental (may change in any release):
kiln.agents,kiln.sound, theShadermaterial API (shade(Material), raw GLSL, customVboattributes and instancing),kiln.testing, the more advancedGuiwidgets and the presets JSON format.
A first sketch
import kiln
class Sketch(kiln.App):
def setup(self):
kiln.background(18, 16, 28)
def draw(self):
kiln.background(18, 16, 28)
for i in range(12):
kiln.fill(255, 140 + i * 8, 60, 200)
kiln.circle(kiln.mouse.x + i * 20, kiln.mouse.y, 12 + i * 3)
kiln.run(Sketch(), 800, 600)
A window opens and a row of circles follows the mouse. ESC closes it.
Controls
kiln.Gui is a panel Kiln draws on top of your sketch, with sliders, toggles, buttons, labels,
colour pickers, XY pads, ranges, knobs, choices, text fields, graphs, meters and folders. Make it
in setup; each control returns a Parameter you read in draw like the value it holds:
class Sketch(kiln.App):
def setup(self):
self.gui = kiln.Gui("Circle")
self.radius = self.gui.slider("radius", 70.0, 10.0, 200.0)
self.ink = self.gui.color("ink", kiln.Color(255, 122, 69))
self.shape = self.gui.choice("shape", ["disc", "ring"])
self.pos = self.gui.xy("position", (0.0, 0.0))
def draw(self):
x, y = self.pos
ring = self.shape == "ring"
with kiln.style(fill=None if ring else self.ink, stroke=self.ink if ring else None):
kiln.circle(320 + x * 100, 240 + y * 100, self.radius)
Click or drag a control, Shift for fine control, double-click to reset, Tab to hide the panel.
Panels keep their clicks, never show up in save_screen, and gui.save(path) / gui.load(path)
keep your settings as JSON. See the GUI tutorial.
Documentation
The docs site is not published yet. Build it locally (this needs the docs dependency
group and Quarto 1.7 or newer):
uv sync --group docs
scripts/docs_publish.sh --render
then open http://localhost:4040. The script renders this checkout's docs and copies
them to one fixed folder served at that address, so renders from any checkout or
worktree show up at the same URL: refresh the tab. --render only re-renders the pages
that changed since the last render (seconds, not minutes); the first run, or a change to
a site-wide file such as _quarto.yml, renders everything. --full forces a full render,
--page tutorials/gui.qmd renders just that page, and --render --dry-run lists what
would re-render. Without a flag it publishes what was last rendered, and
http://localhost:4040/published.txt says which branch and commit you're looking at.
The site has:
- Getting started: the sketch lifecycle and
run(). - Tutorials: shapes, colour, transforms, custom shapes, images, pixels and the screen, offscreen drawing, meshes and Vbos, shaders, GPGPU particles, instancing, blending and trails, noise and randomness, interaction, bitmap and TrueType text, GUI panels, agents and behaviour trees, ecosystems and evolved brains, performance, and sound (instruments, effects, a sequencer, drums, audio-reactive visuals and a whole track). Every code cell is executed and its render shown; animations are short looping videos.
- Gallery: the showcase, the Abyss, Pilgrim, Embers in a draught, Shoal, Meadow and Neuroevolution scenes and the nine ported sketches, rendered, and the seven sound examples, each linked to its tutorial. The stress scene is in the Performance tutorial.
- Remixes (in the Gallery menu): nine well-loved sketches read through the kiln, a picture of each.
- Coming from: openFrameworks and p5.js, a page each:
every name and its Kiln name, and every place Kiln behaves differently. Checkers
(
scripts/check_api_map.py,scripts/check_p5_map.py) keep both in step with the code. - Reference: the API, generated from the docstrings.
Public API. Everything you need is in kiln (kiln.circle, kiln.App, ...),
in kiln.agents for agents and behaviour trees, and in kiln.testing for offscreen renders
in tests. Those three namespaces are the public API. Modules such as kiln._window, kiln.graphics.renderer or kiln.graphics.tessellate
are internals and may change without notice.
Examples
Twenty-four examples live in examples/, with nine remixes (remix_*.py)
and logo.py, which draws the logo. First, Kiln's own:
uv run python examples/showcase.py # every API name in one window: twenty panels and a sound row
uv run python examples/abyss.py # Kunstformen der Tiefe: medusae and plankton, after Haeckel
uv run python examples/pilgrim.py # a traveller, dunes and a scarf, inspired by Journey
uv run python examples/16_flocking.py # two thousand sparks flock and run from the mouse, with a behaviour tree
uv run python examples/17_predators.py # a shoal and its hunters: one population, one tree
uv run python examples/23_ecosystem.py # a meadow: grazers eat, breed and die, and their genes drift
uv run python examples/24_neuroevolution.py # foragers whose small neural brains evolve to find food
uv run python examples/stress.py # five layers that drive Kiln as hard as it goes
Then nine ported sketches (their sources are credited in NOTICE):
uv run python examples/01_graphics.py # shapes, transparency, lines
uv run python examples/02_polygon.py # winding rules, curves, draggable points
uv run python examples/03_blending.py # blend modes
uv run python examples/04_noise_field.py # 4,096 particles in a noise field
uv run python examples/05_image_loader.py # a Ming jar, a halftone of 3,600 circles, a loupe
uv run python examples/06_fbo_trails.py # 8-bit against float Fbo trails
uv run python examples/07_shader.py # a vertex shader on a Vbo, a print-line fragment shader
uv run python examples/08_vbo.py # 25 lit reliefs: Mesh against Vbo
uv run python examples/09_gui.py # a panel of controls shapes a circle
And seven that make sound. They need the sound extra (uv sync --extra sound, or
pip install "kiln-gl[sound]") and play through your speakers:
uv run python examples/10_synth.py # a mouse synth: hold to play, pitch across, filter cutoff up
uv run python examples/11_audio_reactive.py # a synth plays, and its spectrum, waveform and level draw the picture
uv run python examples/13_sfx.py # bouncing balls pluck a melody, and everything they touch goes bling
uv run python examples/14_drums.py # a drum machine you can see: a 16-step beat at 122 BPM
uv run python examples/18_sequencer.py # a step sequencer: the picture pulses exactly with the beat
uv run python examples/19_melodic_techno.py # a one-minute melodic techno track, with visuals on its beat
uv run python examples/20_gui_synth.py # a synth you play and shape from a panel, with the sound drawn live
Add --frames 120 to any of them to run 120 frames and exit.
Performance
bench/bench_2d.py times the frame in a visible 800x600 window with vsync off (no pixel
readback). Run it with uv run python bench/bench_2d.py --check. The numbers below are
median frame times on the Apple M3 Pro development machine, from one run on 2026-10-10,
after 20 warm-up frames and over 120 timed frames, at load average 3.7 on 12 cores (other
jobs were running, so the bench flagged them as inflated; a quiet machine should do better).
| Scenario | Frame, median |
|---|---|
10,000 circle calls |
6.8 ms |
10,000 rect calls |
7.3 ms |
1,000 five-vertex shape polygons |
12.7 ms |
| 2,000 bitmap glyphs | 1.8 ms |
10,000 circles (bulk) |
0.41 ms |
100,000 circles (bulk) |
1.8 ms |
200,000 circles (bulk) |
3.9 ms |
| 500 alternating SDF / custom-shape segments | 3.6 ms |
| 10,000 sprites of one texture | 8.2 ms |
| 1,000 sprites alternating two textures | 3.3 ms |
| One Fbo pass (10,000 circles in, one draw out) | 0.45 ms |
| A full-screen material (a 3-octave loop) | 0.35 ms |
A static 1M-vertex Vbo |
1.4 ms |
| 4,096 instances of 12 floats, rewritten every frame | 0.53 ms |
The project goal is 10,000 immediate-mode shapes within 16.7 ms (60 fps), 200,000 bulk
circles, 10,000 sprites of one texture and 4,096 instances rewritten every frame within the
same budget; all pass (--check exits 0). Immediate calls cost
about 0.65 µs each in Python; bulk calls about 10-15 ns per shape.
Tests
uv sync
uv run pytest -q
The long seeded simulation runs of kiln.agents (the Meadow's population bands and its
hunters' selection, and the river selection test, 10k-20k steps each) are marked sim and
skipped unless asked for, also under CI's -m "not gpu". Run them on demand:
uv run pytest -m sim
uv sync installs the dev group with the package: pytest, ruff, and the two packages the
docs-helper tests use (ipython and imageio-ffmpeg). Building the docs site needs the
docs group as well (see Documentation).
Tests open hidden (and a few visible) windows. They run as a background app, so they never
take focus from your terminal or editor. To run examples or the benchmark the same way,
set KILN_BACKGROUND=1:
KILN_BACKGROUND=1 uv run python examples/01_graphics.py --frames 120
KILN_BACKGROUND=1 uv run python bench/bench_2d.py --check
The pixel tests open a hidden OpenGL window, so they need a macOS GUI session.
Licence
Kiln is licensed under the Apache License 2.0; see NOTICE for third-party material: the ported examples stay MIT (see examples/NOTICE), the bundled Inter font is under the SIL Open Font License 1.1, the bitmap font is public domain, and the sample image is dedicated to the public domain (CC0).
The Kiln name and logo, and the Sisyphe name and logo, are trademarks of Sisyphe LLC and are not covered by the code licence.
Built by Sisyphe
Kiln is built by Sisyphe.
Metadata
Release files for kiln-gl 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kiln_gl-0.2.0.tar.gz | 1.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kiln_gl-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.1 MB
Release files / kiln_gl-0.2.0.tar.gz
| Download URL | kiln_gl-0.2.0.tar.gz |
|---|---|
| Size | 1.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
abc75b197c38a8922380895024d61c646dbcb42787446d3ad31a95ee74a571d7
|
|
BLAKE2b-256 checksum How to use checksums |
a07903eea57808d5ebf1c72b3d23426ac7ff791919736143fb3619454be446b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / kiln_gl-0.2.0-py3-none-any.whl
| Download URL | kiln_gl-0.2.0-py3-none-any.whl |
|---|---|
| Size | 496.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bf9a410eae8e7141c10ed2c19a536ce6af84d3a67f20dba0f7a6dea40a1c588d
|
|
BLAKE2b-256 checksum How to use checksums |
00eeb1e47a23eef2a0676b3b3ae14336508ceec20aa23e980f04e0503f975809
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log