Metadata-Version: 2.4
Name: grafix
Version: 0.0.6
Summary: Line-based geometry toolkit with chained effects and real-time preview.
License: MIT License
        
        Copyright (c) 2025 tyhts0829
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Graphics
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: numba
Requires-Dist: shapely
Requires-Dist: pyclipper
Requires-Dist: moderngl
Requires-Dist: pyglet
Requires-Dist: imgui
Requires-Dist: fontPens
Requires-Dist: fontTools
Requires-Dist: PyOpenGL
Requires-Dist: PyOpenGL_accelerate
Requires-Dist: PyYAML
Requires-Dist: mido
Requires-Dist: python-rtmidi
Requires-Dist: psutil
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Dynamic: license-file

# Grafix

Grafix is a Python-based creative coding framework for line-based geometry:

- Generate primitives (`G`)
- Chain effects (`E`)
- Real-time interactive rendering (`run`)
- Export plotter-ready G-code
- Export visuals (SVG / PNG / MP4)

<img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/top_movie.gif" width="1200" alt="Grafix demo" />
<img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/penplot_movie.gif" width="1200" alt="Penplotting" />

<img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/penplot1.JPG" width="800" alt="pen plotter art example" />

## Installation

```bash
pip install grafix
```

## Requirements

- Python >= 3.11
- macOS-first (tested on macOS / Apple Silicon).
- Optional external tools:
  - `resvg` for PNG export (`P` key / headless PNG export)
  - `ffmpeg` for MP4 recording (`V` key)

macOS (Homebrew):

```bash
brew install resvg ffmpeg
```

## Quick start

```python
from grafix import E, G, run

CANVAS_SIZE = (148, 210)  # A5 [mm]


def draw(t: float):
    # Coordinates are in canvas units: (0,0)=top-left, +x=right, +y=down.
    # Keyword arguments are discovered at runtime and show up in the Parameter GUI.
    geometry = G.polyhedron()
    effect = E.fill().subdivide().displace().rotate(rotation=(t * 6, t * 5, t * 4))
    return effect(geometry)


if __name__ == "__main__":
    run(draw, canvas_size=CANVAS_SIZE, render_scale=5.0)
```

## Core API

- `G`: primitive Geometry factories (`G.polygon(...)`, `G.grid(...)`, ...)
- `E`: Effect chain builders (`E.fill(...).rotate(...)`)
- `L`: wrap Geometry into Layers (color / thickness) for multi-pen / multi-pass workflows
- `P` / `@preset`: reusable components
- `cc`: MIDI CC(`cc[1]` -> 0..1) to control parameters with physical controllers
- `run(draw)`: interactive rendering + Parameter GUI

## Export & shortcuts

When the draw window is focused:

- `S`: save SVG
- `P`: save PNG (requires `resvg`; also saves the underlying SVG)
- `V`: start/stop MP4 recording (requires `ffmpeg`)
- `G`: save G-code
- `Shift+G`: save G-code per layer (when your sketch returns multiple Layers)

Outputs are written under `paths.output_dir` (default: `data/output`), under per-kind subdirectories (`svg/`, `png/`, `gcode/`, ...).

## Examples

<!-- BEGIN:README_EXAMPLES_GRN -->
<table>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/1.png" width="320" alt="grn 1" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/2.png" width="320" alt="grn 2" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/3.png" width="320" alt="grn 3" /></td>
  </tr>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/4.png" width="320" alt="grn 4" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/5.png" width="320" alt="grn 5" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/6.png" width="320" alt="grn 6" /></td>
  </tr>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/7.png" width="320" alt="grn 7" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/8.png" width="320" alt="grn 8" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/9.png" width="320" alt="grn 9" /></td>
  </tr>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/10.png" width="320" alt="grn 10" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/11.png" width="320" alt="grn 11" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/12.png" width="320" alt="grn 12" /></td>
  </tr>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/13.png" width="320" alt="grn 13" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/14.png" width="320" alt="grn 14" /></td>
    <td><img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/grn/15.png" width="320" alt="grn 15" /></td>
  </tr>
</table>
<!-- END:README_EXAMPLES_GRN -->

## Extending

You can register your own primitives and effects via decorators:

```python
import numpy as np

from grafix import effect, primitive

prim_meta = {"r": {"kind": "float", "ui_min": 1.0, "ui_max": 100.0}}
eff_meta = {"amount": {"kind": "float", "ui_min": 0.0, "ui_max": 2.0}}

@primitive(meta=prim_meta)
def user_prim(*, r=10.0) -> tuple[np.ndarray, np.ndarray]:
    coords = ...  # shape (N, 3)
    offsets = ...  # shape (M+1,)
    return coords, offsets


@effect(meta=eff_meta)
def user_eff(g: tuple[np.ndarray, np.ndarray], *, amount=1.0) -> tuple[np.ndarray, np.ndarray]:
    coords, offsets = g
    coords_out = ...
    return coords_out, offsets
```

Notes:

- Built-in primitives/effects must provide `meta=...` (enforced).
- User-defined primitives/effects use `(coords, offsets)` tuples (`coords` must be shape `(N,3)`).
- For user-defined ops, `meta` is optional. If omitted, parameters are not shown in the Parameter GUI.
- User-defined modules need to be imported once to register the ops.

## Presets (reusable components)

Use `@preset` to register a component, and call it via `P.<name>(...)`:

```python
from grafix import P, preset

meta = {
    "n_rows": {"kind": "int", "ui_min": 1, "ui_max": 20},
    "n_cols": {"kind": "int", "ui_min": 1, "ui_max": 20},
}

@preset(meta=meta)
def grid_system_frame(
    *,
    n_rows: int = 5,
    n_cols: int = 8,
    name=None,
    key=None,
):
    ...


P.grid_system_frame()
```

For IDE completion of `P.<name>(...)`, regenerate stubs after adding/changing presets:

```bash
python -m grafix stub
```

## Configuration (`config.yaml`)

A `config.yaml` lets you locate external fonts and choose where Grafix writes runtime outputs (`.svg`, `.png`, `.mp4`, `.gcode`).

Grafix starts from the packaged defaults (`grafix/resource/default_config.yaml`) and then overlays user config(s).

Load order (later wins):

1. packaged defaults
2. discovered config (0 or 1 file; first found wins)
3. explicit config path (if provided)

Config search (first found wins):

- `./.grafix/config.yaml` (project-local)
- `~/.config/grafix/config.yaml` (per-user)

You can also pass an explicit config path:

- `run(..., config_path="path/to/config.yaml")`
- `python -m grafix export --config path/to/config.yaml`

Paths support `~` and environment variables like `$HOME`.

To create a project-local config (starting from the packaged defaults):

```bash
mkdir -p .grafix
python -c "from importlib.resources import files; print(files('grafix').joinpath('resource','default_config.yaml').read_text())" > .grafix/config.yaml
$EDITOR .grafix/config.yaml
```

Overlay is a top-level shallow update (no deep merge). If you override `export:`, keep both `export.png` and `export.gcode` blocks
from the packaged defaults.

To autoload user presets from a directory:

```yaml
paths:
  preset_module_dirs:
    - "sketch/presets"
```

## Prompt-to-Physical art (WIP)

I'm experimenting with a fully autonomous LLM loop that creates Grafix sketches end-to-end from a single prompt.

It iterates through:

- ideate
- implement
- render
- critique
- improve

No human intervention, just continuous iteration and unexpected visual evolution.
The image below was generated by the LLM in this closed loop.

<img src="https://raw.githubusercontent.com/tyhts0829/grafix/main/docs/readme/agent_generated_art.png" width="1200" alt="LLM-generated sketches (work in progress)" />

### Headless export (batch rendering)

The loop uses `python -m grafix export` to render `draw(t)` without opening any window:

```bash
python -m grafix export --callable sketch.main:draw --t 0.0 --canvas 300 300
python -m grafix export --callable sketch.main:draw --t 0.0 1.0 2.0 --canvas 300 300 --out-dir data/output
```

With an explicit config file:

```bash
python -m grafix export --config path/to/config.yaml --callable sketch.main:draw --t 0.0 --canvas 300 300
```

If you want to use the API directly, `Export` lives in `grafix.api`:

```python
from grafix.api import Export
```

## Troubleshooting

- `resvg が見つかりません`: install `resvg` and ensure it is on `PATH` (macOS: `brew install resvg`)
- `ffmpeg が見つかりません`: install `ffmpeg` (macOS: `brew install ffmpeg`)

## Development

```bash
# run without installation
PYTHONPATH=src python sketch/main.py

# tests / lint / typecheck
PYTHONPATH=src pytest -q
ruff check .
mypy src/grafix
```

See: `architecture.md` and `docs/developer_guide.md`.
