Metadata-Version: 2.5
Name: kiln-gl
Version: 0.2.0
Summary: Shape ideas in code. Fire them into art. A fast creative-coding framework for Python; macOS (Apple Silicon) only for now.
Project-URL: Homepage, https://github.com/slegroux/kiln
Project-URL: Sisyphe, https://slegroux.github.io/sisyphe/
Project-URL: Source, https://github.com/slegroux/kiln
Project-URL: Issues, https://github.com/slegroux/kiln/issues
Project-URL: Documentation, https://slegroux.github.io/kiln/
Project-URL: Changelog, https://github.com/slegroux/kiln/blob/main/CHANGELOG.md
Author: Sisyphe
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: creative coding,generative art,graphics,opengl
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Artistic Software
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.11
Requires-Dist: glfw<3,>=2.10
Requires-Dist: mapbox-earcut<3,>=2.1
Requires-Dist: numpy>=2.0
Requires-Dist: pillow<13,>=11.0
Requires-Dist: zengl<3,>=2.7.3
Provides-Extra: sound
Requires-Dist: numba<1,>=0.68; extra == 'sound'
Requires-Dist: sounddevice<1,>=0.5; extra == 'sound'
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/slegroux/kiln/main/docs/assets/logo/kiln-logo-dark.svg">
    <img alt="Kiln" src="https://raw.githubusercontent.com/slegroux/kiln/main/docs/assets/logo/kiln-logo-light.svg" height="96">
  </picture>
</p>

<p align="center">
  <a href="https://github.com/slegroux/kiln/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/slegroux/kiln/actions/workflows/ci.yml/badge.svg?branch=main"></a>
  <img alt="Python 3.11+" src="https://img.shields.io/badge/Python-3.11%2B-3776AB">
  <img alt="macOS, OpenGL 4.1" src="https://img.shields.io/badge/macOS-OpenGL%204.1-1F1F22">
  <a href="https://slegroux.github.io/kiln/"><img alt="Docs: Quarto" src="https://img.shields.io/badge/docs-Quarto-1F1F22"></a>
  <img alt="Licence: Apache-2.0" src="https://img.shields.io/badge/licence-Apache--2.0-1F1F22">
  <a href="https://slegroux.github.io/sisyphe/"><img alt="Made by Sisyphe" src="https://img.shields.io/badge/made%20by-Sisyphe-D9432B"></a>
</p>

<p align="center">
  <a href="https://slegroux.github.io/kiln/getting-started.html">Getting started</a> ·
  <a href="https://slegroux.github.io/kiln/tutorials/">Tutorials</a> ·
  <a href="https://slegroux.github.io/kiln/gallery.html">Gallery</a> ·
  Coming from <a href="https://slegroux.github.io/kiln/of-to-kiln.html">openFrameworks</a> or <a href="https://slegroux.github.io/kiln/p5js-to-kiln.html">p5.js</a> ·
  <a href="https://github.com/slegroux/kiln/tree/main/examples/">Examples</a>
</p>

<p align="center"><em>Shape ideas in code. Fire them into art.</em></p>

<p align="center">
  <a href="https://github.com/slegroux/kiln/blob/main/examples/showcase.py"><img alt="Kiln's showcase: one window, twenty panels and a sound row, every API name" src="https://raw.githubusercontent.com/slegroux/kiln/main/docs/media/showcase.jpg" width="880"></a><br>
  <em>One live window exercising every name in the API: <code>uv run python examples/showcase.py</code></em>
</p>

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](https://slegroux.github.io/kiln/naming.html) has the rules, and the
[changelog](https://github.com/slegroux/kiln/blob/main/CHANGELOG.md) 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](https://github.com/szabolcsdombi/zengl) with
[glfw](https://pypi.org/project/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_frames` runs any `App` in a hidden
  window, with scripted mouse and keyboard input, and returns the last frame as a numpy array
  (every frame through `on_frame`). An agent, a test or a notebook on a Mac with a display
  session gets pixels back:

  ```python
  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 N` to 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.agents` and
  `kiln.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](https://slegroux.github.io/kiln/tutorials/agents.html).

## 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 a `tint` for pictures; transforms and styles scoped by
  `with`, blend modes,
  bitmap and TrueType text; mouse, keyboard and file-drop events; simplex noise and
  colour (HSB, hex).
- **Images and textures.** `kiln.Image` and `kiln.Texture` load 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.Shader` colours 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.Mesh` holds vertices, colours, texture coordinates, normals and indices,
  draws every `PrimitiveMode`, loads `.obj` and `.ply` files, and warps a picture on a grid.
- **Vbos.** `kiln.Vbo` keeps 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 as `kiln.Kick`),
  effects, a step `kiln.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 busy `draw()`
  never makes it stutter. Install with `pip install "kiln-gl[sound]"`; the
  [sound tutorial](https://slegroux.github.io/kiln/tutorials/sound.html) 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](https://slegroux.github.io/kiln/remixes.html) of well-loved sketches, read through the kiln (`examples/`,
  rendered in the [gallery](https://slegroux.github.io/kiln/gallery.html)).

## Install

Kiln needs macOS on Apple Silicon, Python 3.11 or newer and OpenGL 4.1. Install it from PyPI:

```bash
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](https://docs.astral.sh/uv/):

```bash
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](https://github.com/slegroux/kiln/blob/main/CHANGELOG.md).

- **Stable for 0.x:** the `App` lifecycle, shapes, `Style` and transforms, `Color`, `Image`,
  `Texture`, `Fbo`, `Mesh`, `Font`, the math helpers and `run()`.
- **Experimental (may change in any release):** `kiln.agents`, `kiln.sound`, the `Shader`
  material API (`shade(Material)`, raw GLSL, custom `Vbo` attributes and instancing),
  `kiln.testing`, the more advanced `Gui` widgets and the presets JSON format.

## A first sketch

```python
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:

```python
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](https://slegroux.github.io/kiln/tutorials/gui.html).

## Documentation

The docs site is not published yet. Build it locally (this needs the `docs` dependency
group and [Quarto](https://quarto.org) 1.7 or newer):

```bash
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](https://slegroux.github.io/kiln/remixes.html) read through the
  kiln, a picture of each.
- **Coming from**: [openFrameworks](https://slegroux.github.io/kiln/of-to-kiln.html) and [p5.js](https://slegroux.github.io/kiln/p5js-to-kiln.html), 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](https://slegroux.github.io/kiln/remixes.html) (`remix_*.py`)
and `logo.py`, which draws the logo. First, Kiln's own:

```bash
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](https://github.com/slegroux/kiln/blob/main/NOTICE)):

```bash
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:

```bash
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

```bash
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:

```bash
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](#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`:

```bash
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](https://github.com/slegroux/kiln/blob/main/LICENSE); see [NOTICE](https://github.com/slegroux/kiln/blob/main/NOTICE) for
third-party material: the ported examples stay MIT (see [examples/NOTICE](https://github.com/slegroux/kiln/blob/main/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](https://slegroux.github.io/sisyphe/) name and
logo, are trademarks of Sisyphe LLC and are not covered by the code licence.

## Built by [Sisyphe](https://slegroux.github.io/sisyphe/)

Kiln is built by [Sisyphe](https://slegroux.github.io/sisyphe/).
